diff --git a/ComfyUI_vibe/src/components/v2/workspace/WorkspaceCard.vue b/ComfyUI_vibe/src/components/v2/workspace/WorkspaceCard.vue
new file mode 100644
index 000000000..af20d0e8e
--- /dev/null
+++ b/ComfyUI_vibe/src/components/v2/workspace/WorkspaceCard.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ title }}
+
+ {{ description }}
+
+
+
+
+
+ {{ actionLabel }}
+
+
+
+
+
+
+ {{ badge }}
+
+
+
+
+
+
+ {{ stat.value }}
+
+
+
+
+ {{ updatedAt }}
+
+
+
+
diff --git a/ComfyUI_vibe/src/components/v2/workspace/WorkspaceFilterSelect.vue b/ComfyUI_vibe/src/components/v2/workspace/WorkspaceFilterSelect.vue
new file mode 100644
index 000000000..b68648f97
--- /dev/null
+++ b/ComfyUI_vibe/src/components/v2/workspace/WorkspaceFilterSelect.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
diff --git a/ComfyUI_vibe/src/components/v2/workspace/index.ts b/ComfyUI_vibe/src/components/v2/workspace/index.ts
index 16b068068..c46a919c8 100644
--- a/ComfyUI_vibe/src/components/v2/workspace/index.ts
+++ b/ComfyUI_vibe/src/components/v2/workspace/index.ts
@@ -3,4 +3,6 @@ export { default as WorkspaceEmptyState } from './WorkspaceEmptyState.vue'
export { default as WorkspaceViewToggle } from './WorkspaceViewToggle.vue'
export { default as WorkspaceSearchInput } from './WorkspaceSearchInput.vue'
export { default as WorkspaceSortSelect } from './WorkspaceSortSelect.vue'
+export { default as WorkspaceFilterSelect } from './WorkspaceFilterSelect.vue'
+export { default as WorkspaceCard } from './WorkspaceCard.vue'
export { default as CreateProjectDialog } from './CreateProjectDialog.vue'
diff --git a/ComfyUI_vibe/src/data/sidebarMockData.ts b/ComfyUI_vibe/src/data/sidebarMockData.ts
index 5d87ef5c6..d25f14fb2 100644
--- a/ComfyUI_vibe/src/data/sidebarMockData.ts
+++ b/ComfyUI_vibe/src/data/sidebarMockData.ts
@@ -244,6 +244,7 @@ export interface SharedWorkflow {
nodes: number
category: string
starred: boolean
+ thumbnail?: string
}
export interface TeamModel {
@@ -254,6 +255,7 @@ export interface TeamModel {
size: string
author: TeamMember
downloads: number
+ thumbnail?: string
}
export interface NodePack {
@@ -264,6 +266,7 @@ export interface NodePack {
nodes: number
author: string
installed: boolean
+ thumbnail?: string
}
export const TEMPLATE_CATEGORIES_DATA: TemplateCategory[] = [
@@ -354,6 +357,7 @@ export function createSharedWorkflowsData(members: TeamMember[]): SharedWorkflow
nodes: 12,
category: 'Production',
starred: true,
+ thumbnail: '/assets/card_images/workflow_01.webp',
},
{
id: '2',
@@ -364,6 +368,7 @@ export function createSharedWorkflowsData(members: TeamMember[]): SharedWorkflow
nodes: 18,
category: 'Marketing',
starred: true,
+ thumbnail: '/assets/card_images/2690a78c-c210-4a52-8c37-3cb5bc4d9e71.webp',
},
{
id: '3',
@@ -374,6 +379,7 @@ export function createSharedWorkflowsData(members: TeamMember[]): SharedWorkflow
nodes: 24,
category: 'Production',
starred: false,
+ thumbnail: '/assets/card_images/bacb46ea-7e63-4f19-a253-daf41461e98f.webp',
},
{
id: '4',
@@ -384,6 +390,7 @@ export function createSharedWorkflowsData(members: TeamMember[]): SharedWorkflow
nodes: 8,
category: 'Marketing',
starred: false,
+ thumbnail: '/assets/card_images/228616f4-12ad-426d-84fb-f20e488ba7ee.webp',
},
]
}
@@ -398,6 +405,7 @@ export function createTeamModelsData(members: TeamMember[]): TeamModel[] {
size: '144 MB',
author: members[0]!,
downloads: 156,
+ thumbnail: '/assets/card_images/683255d3-1d10-43d9-a6ff-ef142061e88a.webp',
},
{
id: '2',
@@ -407,6 +415,7 @@ export function createTeamModelsData(members: TeamMember[]): TeamModel[] {
size: '6.94 GB',
author: members[1]!,
downloads: 89,
+ thumbnail: '/assets/card_images/91f1f589-ddb4-4c4f-b3a7-ba30fc271987.webp',
},
{
id: '3',
@@ -416,6 +425,7 @@ export function createTeamModelsData(members: TeamMember[]): TeamModel[] {
size: '72 MB',
author: members[2]!,
downloads: 234,
+ thumbnail: '/assets/card_images/28e9f7ea-ef00-48e8-849d-8752a34939c7.webp',
},
{
id: '4',
@@ -425,6 +435,7 @@ export function createTeamModelsData(members: TeamMember[]): TeamModel[] {
size: '24 KB',
author: members[0]!,
downloads: 312,
+ thumbnail: '/assets/card_images/comfyui_workflow.jpg',
},
]
}
@@ -438,6 +449,7 @@ export const NODE_PACKS_DATA: NodePack[] = [
nodes: 8,
author: 'Netflix Creative Tech',
installed: true,
+ thumbnail: '/assets/card_images/can-you-rate-my-comfyui-workflow-v0-o9clchhji39c1.webp',
},
{
id: '2',
@@ -447,6 +459,7 @@ export const NODE_PACKS_DATA: NodePack[] = [
nodes: 4,
author: 'Netflix Creative Tech',
installed: true,
+ thumbnail: '/assets/card_images/dda28581-37c8-44da-8822-57d1ccc2118c_2130x1658.png',
},
{
id: '3',
@@ -456,5 +469,6 @@ export const NODE_PACKS_DATA: NodePack[] = [
nodes: 6,
author: 'Netflix Creative Tech',
installed: false,
+ thumbnail: '/assets/card_images/workflow_01.webp',
},
]
diff --git a/ComfyUI_vibe/src/main.ts b/ComfyUI_vibe/src/main.ts
index 67a964453..966b485b5 100644
--- a/ComfyUI_vibe/src/main.ts
+++ b/ComfyUI_vibe/src/main.ts
@@ -47,6 +47,11 @@ const ComfyPreset = definePreset(Aura, {
},
select: {
borderRadius: '8px'
+ },
+ popover: {
+ borderRadius: '8px',
+ shadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)',
+ padding: '0'
}
}
})
@@ -79,7 +84,24 @@ app.use(PrimeVue, {
app.use(ToastService)
app.use(ConfirmationService)
-// PrimeVue directives
-app.directive('tooltip', Tooltip)
+// PrimeVue directives with custom defaults
+app.directive('tooltip', {
+ ...Tooltip,
+ getSSRProps() {
+ return {}
+ },
+ mounted(el, binding) {
+ // Set fast show delay (100ms) as default
+ const value = binding.value
+ if (typeof value === 'string') {
+ binding.value = { value, showDelay: 100, hideDelay: 0 }
+ } else if (typeof value === 'object' && value !== null) {
+ binding.value = { showDelay: 100, hideDelay: 0, ...value }
+ }
+ Tooltip.mounted(el, binding)
+ },
+ updated: Tooltip.updated,
+ unmounted: Tooltip.unmounted
+})
app.mount('#app')
diff --git a/ComfyUI_vibe/src/router.ts b/ComfyUI_vibe/src/router.ts
index 3c4de070c..49acdc4c2 100644
--- a/ComfyUI_vibe/src/router.ts
+++ b/ComfyUI_vibe/src/router.ts
@@ -54,6 +54,16 @@ const v2Routes: RouteRecordRaw[] = [
name: 'workspace-recents',
component: () => import('./views/v2/workspace/RecentsView.vue')
},
+ {
+ path: 'templates',
+ name: 'workspace-templates',
+ component: () => import('./views/v2/workspace/TemplatesView.vue')
+ },
+ {
+ path: 'library',
+ name: 'workspace-library',
+ component: () => import('./views/v2/workspace/LibraryView.vue')
+ },
{
path: 'trash',
name: 'workspace-trash',
diff --git a/ComfyUI_vibe/src/views/v2/CanvasView.vue b/ComfyUI_vibe/src/views/v2/CanvasView.vue
index 982ddd88d..4df62cbea 100644
--- a/ComfyUI_vibe/src/views/v2/CanvasView.vue
+++ b/ComfyUI_vibe/src/views/v2/CanvasView.vue
@@ -8,6 +8,8 @@ import '@vue-flow/core/dist/theme-default.css'
import CanvasTabBar from '@/components/v2/canvas/CanvasTabBar.vue'
import CanvasLeftSidebar from '@/components/v2/canvas/CanvasLeftSidebar.vue'
import CanvasBottomBar from '@/components/v2/canvas/CanvasBottomBar.vue'
+import CanvasRightToolbar from '@/components/v2/canvas/CanvasRightToolbar.vue'
+import CanvasRunControls from '@/components/v2/canvas/CanvasRunControls.vue'
import NodePropertiesPanel from '@/components/v2/canvas/NodePropertiesPanel.vue'
import { FlowNode } from '@/components/v2/nodes'
import { useWorkspaceStore } from '@/stores/workspaceStore'
@@ -36,7 +38,7 @@ onMounted(() => {
})
// Vue Flow
-const { onNodeClick, onPaneClick, fitView } = useVueFlow()
+const { onNodeClick, onPaneClick, fitView, zoomIn, zoomOut } = useVueFlow()
// Center the workflow on mount with 50% zoom
onMounted(() => {
@@ -109,6 +111,14 @@ function closePropertiesPanel(): void {
+
+
+
+
+
+
diff --git a/ComfyUI_vibe/src/views/v2/workspace/AssetsView.vue b/ComfyUI_vibe/src/views/v2/workspace/AssetsView.vue
index 948235d52..79e7cc906 100644
--- a/ComfyUI_vibe/src/views/v2/workspace/AssetsView.vue
+++ b/ComfyUI_vibe/src/views/v2/workspace/AssetsView.vue
@@ -1,13 +1,14 @@
@@ -89,33 +90,31 @@ const starterTemplates = [
Start from a template
-
+
+
+
+
+
+ 803+ workflows, models, nodes by Comfy & community
+
+
diff --git a/ComfyUI_vibe/src/views/v2/workspace/LibraryView.vue b/ComfyUI_vibe/src/views/v2/workspace/LibraryView.vue
new file mode 100644
index 000000000..3b265e017
--- /dev/null
+++ b/ComfyUI_vibe/src/views/v2/workspace/LibraryView.vue
@@ -0,0 +1,409 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Switch Library
+
+
+
+
+
+
+
+
+
+ Library Hub
+
+
+ Shared workflows, models, nodepacks, and brand assets
+
+
+
+
+
+
+
+ Linear
+
+
+
+ Node
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
No items found
+
+ {{ searchQuery || filterBy !== 'all' ? 'Try different filters' : 'Add items to get started' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
Icon
+
Name
+
Type
+
Uses
+
Author
+
Updated
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
{{ item.description }}
+
+
+
+ {{ getTypeLabel(item.type) }}
+
+
+
+ {{ formatUses(item.uses) }}
+
+
+ {{ item.author }}
+
+
+ {{ item.updatedAt }}
+
+
+
+
+
+
+
+
+
diff --git a/ComfyUI_vibe/src/views/v2/workspace/ProjectsView.vue b/ComfyUI_vibe/src/views/v2/workspace/ProjectsView.vue
index a6383ba65..67e4667a9 100644
--- a/ComfyUI_vibe/src/views/v2/workspace/ProjectsView.vue
+++ b/ComfyUI_vibe/src/views/v2/workspace/ProjectsView.vue
@@ -8,6 +8,7 @@ import {
WorkspaceSearchInput,
WorkspaceSortSelect,
CreateProjectDialog,
+ WorkspaceCard,
} from '@/components/v2/workspace'
const route = useRoute()
@@ -31,10 +32,10 @@ const sortOptions = [
// Projects data
const projects = ref([
- { id: 'img-gen', name: 'Image Generation', description: 'AI image generation workflows', canvasCount: 5, modelCount: 12, updatedAt: '2 hours ago', updatedTimestamp: Date.now() - 2 * 60 * 60 * 1000 },
- { id: 'video-proc', name: 'Video Processing', description: 'Video enhancement and editing', canvasCount: 3, modelCount: 8, updatedAt: '1 day ago', updatedTimestamp: Date.now() - 24 * 60 * 60 * 1000 },
- { id: 'audio-enh', name: 'Audio Enhancement', description: 'Audio processing pipelines', canvasCount: 2, modelCount: 4, updatedAt: '3 days ago', updatedTimestamp: Date.now() - 3 * 24 * 60 * 60 * 1000 },
- { id: 'upscale', name: 'Upscaling', description: 'Image and video upscaling', canvasCount: 4, modelCount: 6, updatedAt: '1 week ago', updatedTimestamp: Date.now() - 7 * 24 * 60 * 60 * 1000 }
+ { id: 'img-gen', name: 'Image Generation', description: 'AI image generation workflows', canvasCount: 5, modelCount: 12, updatedAt: '2 hours ago', updatedTimestamp: Date.now() - 2 * 60 * 60 * 1000, thumbnail: '/thumbnails/project-1.jpg' },
+ { id: 'video-proc', name: 'Video Processing', description: 'Video enhancement and editing', canvasCount: 3, modelCount: 8, updatedAt: '1 day ago', updatedTimestamp: Date.now() - 24 * 60 * 60 * 1000, thumbnail: '/thumbnails/project-2.jpg' },
+ { id: 'audio-enh', name: 'Audio Enhancement', description: 'Audio processing pipelines', canvasCount: 2, modelCount: 4, updatedAt: '3 days ago', updatedTimestamp: Date.now() - 3 * 24 * 60 * 60 * 1000, thumbnail: '/assets/card_images/28e9f7ea-ef00-48e8-849d-8752a34939c7.webp' },
+ { id: 'upscale', name: 'Upscaling', description: 'Image and video upscaling', canvasCount: 4, modelCount: 6, updatedAt: '1 week ago', updatedTimestamp: Date.now() - 7 * 24 * 60 * 60 * 1000, thumbnail: '/assets/card_images/comfyui_workflow.jpg' }
])
// Create dialog
@@ -125,42 +126,19 @@ const emptyStateDescription = computed(() =>
v-else-if="viewMode === 'grid'"
class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5"
>
-
-
-
-
-
{{ project.name }}
-
- {{ project.description }}
-
-
-
-
- {{ project.canvasCount }}
-
-
-
- {{ project.modelCount }}
-
-
-
-
-
+ />
diff --git a/ComfyUI_vibe/src/views/v2/workspace/RecentsView.vue b/ComfyUI_vibe/src/views/v2/workspace/RecentsView.vue
index 842381b2a..59c718a12 100644
--- a/ComfyUI_vibe/src/views/v2/workspace/RecentsView.vue
+++ b/ComfyUI_vibe/src/views/v2/workspace/RecentsView.vue
@@ -1,6 +1,15 @@