+
@@ -28,19 +32,22 @@ type GridState = {
const {
items,
+ gridStyle,
bufferRows = 1,
scrollThrottle = 64,
resizeDebounce = 64,
defaultItemHeight = 200,
- defaultItemWidth = 200
+ defaultItemWidth = 200,
+ maxColumns = Infinity
} = defineProps<{
items: (T & { key: string })[]
- gridStyle: Partial
+ gridStyle: CSSProperties
bufferRows?: number
scrollThrottle?: number
resizeDebounce?: number
defaultItemHeight?: number
defaultItemWidth?: number
+ maxColumns?: number
}>()
const emit = defineEmits<{
@@ -59,7 +66,18 @@ const { y: scrollY } = useScroll(container, {
eventListenerOptions: { passive: true }
})
-const cols = computed(() => Math.floor(width.value / itemWidth.value) || 1)
+const cols = computed(() =>
+ Math.min(Math.floor(width.value / itemWidth.value) || 1, maxColumns)
+)
+
+const mergedGridStyle = computed(() => {
+ if (maxColumns === Infinity) return gridStyle
+ return {
+ ...gridStyle,
+ gridTemplateColumns: `repeat(${maxColumns}, minmax(0, 1fr))`
+ }
+})
+
const viewRows = computed(() => Math.ceil(height.value / itemHeight.value))
const offsetRows = computed(() => Math.floor(scrollY.value / itemHeight.value))
const isValidGrid = computed(() => height.value && width.value && items?.length)
@@ -83,6 +101,16 @@ const renderedItems = computed(() =>
isValidGrid.value ? items.slice(state.value.start, state.value.end) : []
)
+function rowsToHeight(rows: number): string {
+ return `${(rows / cols.value) * itemHeight.value}px`
+}
+const topSpacerStyle = computed(() => ({
+ height: rowsToHeight(state.value.start)
+}))
+const bottomSpacerStyle = computed(() => ({
+ height: rowsToHeight(items.length - state.value.end)
+}))
+
whenever(
() => state.value.isNearEnd,
() => {
@@ -109,15 +137,6 @@ const onResize = debounce(updateItemSize, resizeDebounce)
watch([width, height], onResize, { flush: 'post' })
whenever(() => items, updateItemSize, { flush: 'post' })
onBeforeUnmount(() => {
- onResize.cancel() // Clear pending debounced calls
+ onResize.cancel()
})
-
-
diff --git a/src/components/common/WorkspaceProfilePic.vue b/src/components/common/WorkspaceProfilePic.vue
new file mode 100644
index 000000000..642317267
--- /dev/null
+++ b/src/components/common/WorkspaceProfilePic.vue
@@ -0,0 +1,43 @@
+
+
+ {{ letter }}
+
+
+
+
diff --git a/src/components/common/statusBadge.variants.ts b/src/components/common/statusBadge.variants.ts
new file mode 100644
index 000000000..479a0dda8
--- /dev/null
+++ b/src/components/common/statusBadge.variants.ts
@@ -0,0 +1,26 @@
+import type { VariantProps } from 'cva'
+import { cva } from 'cva'
+
+export const statusBadgeVariants = cva({
+ base: 'inline-flex items-center justify-center rounded-full',
+ variants: {
+ severity: {
+ default: 'bg-primary-background text-base-foreground',
+ secondary: 'bg-secondary-background text-base-foreground',
+ warn: 'bg-warning-background text-base-background',
+ danger: 'bg-destructive-background text-white',
+ contrast: 'bg-base-foreground text-base-background'
+ },
+ variant: {
+ label: 'h-3.5 px-1 text-xxxs font-semibold uppercase',
+ dot: 'size-2',
+ circle: 'size-3.5 text-xxxs font-semibold'
+ }
+ },
+ defaultVariants: {
+ severity: 'default',
+ variant: 'label'
+ }
+})
+
+export type StatusBadgeVariants = VariantProps
diff --git a/src/components/dialog/GlobalDialog.vue b/src/components/dialog/GlobalDialog.vue
index afc056d61..2a1f0ef3d 100644
--- a/src/components/dialog/GlobalDialog.vue
+++ b/src/components/dialog/GlobalDialog.vue
@@ -4,7 +4,12 @@
v-for="item in dialogStore.dialogStack"
:key="item.key"
v-model:visible="item.visible"
- class="global-dialog"
+ :class="[
+ 'global-dialog',
+ item.key === 'global-settings' && teamWorkspacesEnabled
+ ? 'settings-dialog-workspace'
+ : ''
+ ]"
v-bind="item.dialogComponentProps"
:pt="item.dialogComponentProps.pt"
:aria-labelledby="item.key"
@@ -38,7 +43,15 @@
@@ -55,4 +68,27 @@ const dialogStore = useDialogStore()
@apply p-2 2xl:p-[var(--p-dialog-content-padding)];
@apply pt-0;
}
+
+/* Workspace mode: wider settings dialog */
+.settings-dialog-workspace {
+ width: 100%;
+ max-width: 1440px;
+}
+
+.settings-dialog-workspace .p-dialog-content {
+ width: 100%;
+}
+
+.manager-dialog {
+ height: 80vh;
+ max-width: 1724px;
+ max-height: 1026px;
+}
+
+@media (min-width: 3000px) {
+ .manager-dialog {
+ max-width: 2200px;
+ max-height: 1320px;
+ }
+}
diff --git a/src/components/dialog/content/setting/WorkspacePanel.vue b/src/components/dialog/content/setting/WorkspacePanel.vue
new file mode 100644
index 000000000..aff8f3733
--- /dev/null
+++ b/src/components/dialog/content/setting/WorkspacePanel.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/src/components/dialog/content/setting/WorkspacePanelContent.vue b/src/components/dialog/content/setting/WorkspacePanelContent.vue
new file mode 100644
index 000000000..9366a573f
--- /dev/null
+++ b/src/components/dialog/content/setting/WorkspacePanelContent.vue
@@ -0,0 +1,163 @@
+
+
+
+
+
+ {{ workspaceName }}
+
+
+
+
+
+ {{ $t('workspacePanel.tabs.planCredits') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/dialog/content/setting/WorkspaceSidebarItem.vue b/src/components/dialog/content/setting/WorkspaceSidebarItem.vue
new file mode 100644
index 000000000..cab92c7a8
--- /dev/null
+++ b/src/components/dialog/content/setting/WorkspaceSidebarItem.vue
@@ -0,0 +1,19 @@
+
+
+
+
+ {{ workspaceName }}
+
+
+
+
diff --git a/src/components/dialog/content/workspace/CreateWorkspaceDialogContent.vue b/src/components/dialog/content/workspace/CreateWorkspaceDialogContent.vue
new file mode 100644
index 000000000..b9444ce58
--- /dev/null
+++ b/src/components/dialog/content/workspace/CreateWorkspaceDialogContent.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+ {{ $t('workspacePanel.createWorkspaceDialog.title') }}
+
+
+
+
+
+
+
+ {{ $t('workspacePanel.createWorkspaceDialog.message') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/dialog/content/workspace/DeleteWorkspaceDialogContent.vue b/src/components/dialog/content/workspace/DeleteWorkspaceDialogContent.vue
new file mode 100644
index 000000000..dea2da18d
--- /dev/null
+++ b/src/components/dialog/content/workspace/DeleteWorkspaceDialogContent.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+ {{ $t('workspacePanel.deleteDialog.title') }}
+
+
+
+
+
+
+
+ {{
+ workspaceName
+ ? $t('workspacePanel.deleteDialog.messageWithName', {
+ name: workspaceName
+ })
+ : $t('workspacePanel.deleteDialog.message')
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/dialog/content/workspace/EditWorkspaceDialogContent.vue b/src/components/dialog/content/workspace/EditWorkspaceDialogContent.vue
new file mode 100644
index 000000000..62b650a4e
--- /dev/null
+++ b/src/components/dialog/content/workspace/EditWorkspaceDialogContent.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+ {{ $t('workspacePanel.editWorkspaceDialog.title') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/dialog/content/workspace/LeaveWorkspaceDialogContent.vue b/src/components/dialog/content/workspace/LeaveWorkspaceDialogContent.vue
new file mode 100644
index 000000000..6a3d16c36
--- /dev/null
+++ b/src/components/dialog/content/workspace/LeaveWorkspaceDialogContent.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+ {{ $t('workspacePanel.leaveDialog.title') }}
+
+
+
+
+
+
+
+ {{ $t('workspacePanel.leaveDialog.message') }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/queue/QueueProgressOverlay.vue b/src/components/queue/QueueProgressOverlay.vue
index d6b3edcd8..626709593 100644
--- a/src/components/queue/QueueProgressOverlay.vue
+++ b/src/components/queue/QueueProgressOverlay.vue
@@ -200,7 +200,13 @@ const onCancelItem = wrapWithErrorHandlingAsync(async (item: JobListItem) => {
if (item.state === 'running' || item.state === 'initialization') {
// Running/initializing jobs: interrupt execution
- await api.interrupt(promptId)
+ // Cloud backend uses deleteItem, local uses interrupt
+ if (isCloud) {
+ await api.deleteItem('queue', promptId)
+ } else {
+ await api.interrupt(promptId)
+ }
+ executionStore.clearInitializationByPromptId(promptId)
await queueStore.update()
} else if (item.state === 'pending') {
// Pending jobs: remove from queue
@@ -268,7 +274,15 @@ const inspectJobAsset = wrapWithErrorHandlingAsync(
)
const cancelQueuedWorkflows = wrapWithErrorHandlingAsync(async () => {
+ // Capture pending promptIds before clearing
+ const pendingPromptIds = queueStore.pendingTasks
+ .map((task) => task.promptId)
+ .filter((id): id is string => typeof id === 'string' && id.length > 0)
+
await commandStore.execute('Comfy.ClearPendingTasks')
+
+ // Clear initialization state for removed prompts
+ executionStore.clearInitializationByPromptIds(pendingPromptIds)
})
const interruptAll = wrapWithErrorHandlingAsync(async () => {
@@ -284,10 +298,14 @@ const interruptAll = wrapWithErrorHandlingAsync(async () => {
// on cloud to ensure we cancel the workflow the user clicked.
if (isCloud) {
await Promise.all(promptIds.map((id) => api.deleteItem('queue', id)))
+ executionStore.clearInitializationByPromptIds(promptIds)
+ await queueStore.update()
return
}
await Promise.all(promptIds.map((id) => api.interrupt(id)))
+ executionStore.clearInitializationByPromptIds(promptIds)
+ await queueStore.update()
})
const showClearHistoryDialog = () => {
diff --git a/src/components/rightSidePanel/layout/PropertiesAccordionItem.vue b/src/components/rightSidePanel/layout/PropertiesAccordionItem.vue
index d3ee425dd..0b1b88820 100644
--- a/src/components/rightSidePanel/layout/PropertiesAccordionItem.vue
+++ b/src/components/rightSidePanel/layout/PropertiesAccordionItem.vue
@@ -5,25 +5,32 @@ import { cn } from '@/utils/tailwindUtil'
import TransitionCollapse from './TransitionCollapse.vue'
-const props = defineProps<{
+const {
+ disabled,
+ label,
+ enableEmptyState,
+ tooltip,
+ class: className
+} = defineProps<{
disabled?: boolean
label?: string
enableEmptyState?: boolean
tooltip?: string
+ class?: string
}>()
const isCollapse = defineModel('collapse', { default: false })
-const isExpanded = computed(() => !isCollapse.value && !props.disabled)
+const isExpanded = computed(() => !isCollapse.value && !disabled)
const tooltipConfig = computed(() => {
- if (!props.tooltip) return undefined
- return { value: props.tooltip, showDelay: 1000 }
+ if (!tooltip) return undefined
+ return { value: tooltip, showDelay: 1000 }
})
-