mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 22:37:32 +00:00
feat: implemented workspace flow
This commit is contained in:
@@ -234,12 +234,7 @@
|
||||
<!-- Invite info -->
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
v-tooltip="{
|
||||
value: $t('workspacePanel.members.actions.acceptInvite'),
|
||||
showDelay: 300
|
||||
}"
|
||||
class="flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-full bg-secondary-background hover:bg-secondary-background-hover"
|
||||
@click="handleAcceptInvite(invite)"
|
||||
class="flex size-8 shrink-0 items-center justify-center rounded-full bg-secondary-background"
|
||||
>
|
||||
<span class="text-sm font-bold text-base-foreground">
|
||||
{{ invite.name.charAt(0).toUpperCase() }}
|
||||
@@ -343,8 +338,8 @@ const {
|
||||
pendingInvites,
|
||||
fetchMembers,
|
||||
fetchPendingInvites,
|
||||
copyInviteLinkAndAccept,
|
||||
acceptInvite,
|
||||
copyInviteLink,
|
||||
revokeInvite,
|
||||
isPersonalWorkspace,
|
||||
permissions,
|
||||
uiConfig,
|
||||
@@ -451,18 +446,14 @@ function formatDate(date: Date): string {
|
||||
return d(date, { dateStyle: 'medium' })
|
||||
}
|
||||
|
||||
async function handleCopyInviteLink(invite: PendingInvite) {
|
||||
// Demo: copying invite link simulates user accepting, moves to active members
|
||||
await copyInviteLinkAndAccept(invite.id)
|
||||
}
|
||||
|
||||
function handleAcceptInvite(invite: PendingInvite) {
|
||||
// Demo: clicking avatar accepts the invite, moves to active members
|
||||
acceptInvite(invite.id)
|
||||
function handleCopyInviteLink(invite: PendingInvite) {
|
||||
copyInviteLink(invite.id)
|
||||
}
|
||||
|
||||
function handleRevokeInvite(invite: PendingInvite) {
|
||||
showRevokeInviteDialog(invite.id)
|
||||
showRevokeInviteDialog(() => {
|
||||
revokeInvite(invite.id)
|
||||
})
|
||||
}
|
||||
|
||||
function handleCreateWorkspace() {
|
||||
@@ -471,7 +462,9 @@ function handleCreateWorkspace() {
|
||||
})
|
||||
}
|
||||
|
||||
function handleRemoveMember(member: WorkspaceMember) {
|
||||
showRemoveMemberDialog(member.id)
|
||||
function handleRemoveMember(_member: WorkspaceMember) {
|
||||
showRemoveMemberDialog(() => {
|
||||
// TODO: Implement actual remove member API call
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
: null
|
||||
"
|
||||
:class="[
|
||||
'flex cursor-pointer items-center gap-2 px-3 py-2',
|
||||
'flex items-center gap-2 px-3 py-2',
|
||||
item.class,
|
||||
item.disabled ? 'pointer-events-auto' : ''
|
||||
]"
|
||||
@@ -110,8 +110,7 @@ const { t } = useI18n()
|
||||
const {
|
||||
showLeaveWorkspaceDialog,
|
||||
showDeleteWorkspaceDialog,
|
||||
showInviteMemberDialog,
|
||||
showEditWorkspaceDialog
|
||||
showInviteMemberDialog
|
||||
} = useDialogService()
|
||||
const { isActiveSubscription } = useSubscription()
|
||||
const {
|
||||
@@ -130,11 +129,15 @@ const {
|
||||
const menu = ref<InstanceType<typeof Menu> | null>(null)
|
||||
|
||||
function handleLeaveWorkspace() {
|
||||
showLeaveWorkspaceDialog()
|
||||
showLeaveWorkspaceDialog(() => {
|
||||
// TODO: Implement actual leave workspace API call
|
||||
})
|
||||
}
|
||||
|
||||
function handleDeleteWorkspace() {
|
||||
showDeleteWorkspaceDialog()
|
||||
showDeleteWorkspaceDialog(() => {
|
||||
// TODO: Implement actual delete workspace API call
|
||||
})
|
||||
}
|
||||
|
||||
const isDeleteDisabled = computed(
|
||||
@@ -161,50 +164,31 @@ function handleInviteMember() {
|
||||
})
|
||||
}
|
||||
|
||||
function handleEditWorkspace() {
|
||||
showEditWorkspaceDialog()
|
||||
}
|
||||
|
||||
const menuItems = computed(() => {
|
||||
const items: Array<{
|
||||
label: string
|
||||
icon: string
|
||||
class?: string
|
||||
disabled?: boolean
|
||||
command?: () => void
|
||||
}> = []
|
||||
const action = uiConfig.value.workspaceMenuAction
|
||||
if (!action) return []
|
||||
|
||||
// Add "Edit workspace details" for PERSONAL and OWNER
|
||||
if (uiConfig.value.showEditWorkspaceMenuItem) {
|
||||
items.push({
|
||||
label: t('workspacePanel.menu.editWorkspaceDetails'),
|
||||
icon: 'pi pi-pencil',
|
||||
command: handleEditWorkspace
|
||||
})
|
||||
if (action === 'delete') {
|
||||
return [
|
||||
{
|
||||
label: t('workspacePanel.menu.deleteWorkspace'),
|
||||
icon: 'pi pi-trash',
|
||||
class: isDeleteDisabled.value
|
||||
? 'text-danger/50 cursor-not-allowed'
|
||||
: 'text-danger',
|
||||
disabled: isDeleteDisabled.value,
|
||||
command: isDeleteDisabled.value ? undefined : handleDeleteWorkspace
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const action = uiConfig.value.workspaceMenuAction
|
||||
|
||||
// Add role-specific action
|
||||
if (action === 'delete') {
|
||||
items.push({
|
||||
label: t('workspacePanel.menu.deleteWorkspace'),
|
||||
icon: 'pi pi-trash',
|
||||
class: isDeleteDisabled.value
|
||||
? 'text-danger/50 cursor-not-allowed'
|
||||
: 'text-danger',
|
||||
disabled: isDeleteDisabled.value,
|
||||
command: isDeleteDisabled.value ? undefined : handleDeleteWorkspace
|
||||
})
|
||||
} else if (action === 'leave') {
|
||||
items.push({
|
||||
return [
|
||||
{
|
||||
label: t('workspacePanel.menu.leaveWorkspace'),
|
||||
icon: 'pi pi-sign-out',
|
||||
command: handleLeaveWorkspace
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -62,16 +62,14 @@ import { useToast } from 'primevue/usetoast'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useWorkspace } from '@/platform/workspace/composables/useWorkspace'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const { onConfirm } = defineProps<{
|
||||
onConfirm?: (name: string) => void | Promise<void>
|
||||
onConfirm: (name: string) => void | Promise<void>
|
||||
}>()
|
||||
|
||||
const dialogStore = useDialogStore()
|
||||
const toast = useToast()
|
||||
const { createWorkspace } = useWorkspace()
|
||||
const loading = ref(false)
|
||||
const workspaceName = ref('')
|
||||
|
||||
@@ -90,13 +88,8 @@ async function onCreate() {
|
||||
if (!isValidName.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const name = workspaceName.value.trim()
|
||||
// Create workspace using global state (creates OWNER unsubscribed workspace)
|
||||
createWorkspace(name)
|
||||
// Call optional callback if provided
|
||||
await onConfirm?.(name)
|
||||
await onConfirm(workspaceName.value.trim())
|
||||
dialogStore.closeDialog({ key: 'create-workspace' })
|
||||
// Show toast prompting to subscribe
|
||||
toast.add({
|
||||
group: 'workspace-created'
|
||||
})
|
||||
|
||||
@@ -41,11 +41,13 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useWorkspace } from '@/platform/workspace/composables/useWorkspace'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const { onConfirm } = defineProps<{
|
||||
onConfirm: () => void | Promise<void>
|
||||
}>()
|
||||
|
||||
const dialogStore = useDialogStore()
|
||||
const { deleteWorkspace } = useWorkspace()
|
||||
const loading = ref(false)
|
||||
|
||||
function onCancel() {
|
||||
@@ -55,7 +57,7 @@ function onCancel() {
|
||||
async function onDelete() {
|
||||
loading.value = true
|
||||
try {
|
||||
await deleteWorkspace()
|
||||
await onConfirm()
|
||||
dialogStore.closeDialog({ key: 'delete-workspace' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
|
||||
@@ -41,11 +41,13 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useWorkspace } from '@/platform/workspace/composables/useWorkspace'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const { onConfirm } = defineProps<{
|
||||
onConfirm: () => void | Promise<void>
|
||||
}>()
|
||||
|
||||
const dialogStore = useDialogStore()
|
||||
const { leaveWorkspace } = useWorkspace()
|
||||
const loading = ref(false)
|
||||
|
||||
function onCancel() {
|
||||
@@ -55,7 +57,7 @@ function onCancel() {
|
||||
async function onLeave() {
|
||||
loading.value = true
|
||||
try {
|
||||
await leaveWorkspace()
|
||||
await onConfirm()
|
||||
dialogStore.closeDialog({ key: 'leave-workspace' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
|
||||
@@ -41,15 +41,13 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useWorkspace } from '@/platform/workspace/composables/useWorkspace'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const { memberId } = defineProps<{
|
||||
memberId: string
|
||||
const { onConfirm } = defineProps<{
|
||||
onConfirm: () => void | Promise<void>
|
||||
}>()
|
||||
|
||||
const dialogStore = useDialogStore()
|
||||
const { removeMember } = useWorkspace()
|
||||
const loading = ref(false)
|
||||
|
||||
function onCancel() {
|
||||
@@ -59,7 +57,7 @@ function onCancel() {
|
||||
async function onRemove() {
|
||||
loading.value = true
|
||||
try {
|
||||
await removeMember(memberId)
|
||||
await onConfirm()
|
||||
dialogStore.closeDialog({ key: 'remove-member' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
|
||||
@@ -41,15 +41,13 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useWorkspace } from '@/platform/workspace/composables/useWorkspace'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const { inviteId } = defineProps<{
|
||||
inviteId: string
|
||||
const { onConfirm } = defineProps<{
|
||||
onConfirm: () => void | Promise<void>
|
||||
}>()
|
||||
|
||||
const dialogStore = useDialogStore()
|
||||
const { revokeInvite } = useWorkspace()
|
||||
const loading = ref(false)
|
||||
|
||||
function onCancel() {
|
||||
@@ -59,7 +57,7 @@ function onCancel() {
|
||||
async function onRevoke() {
|
||||
loading.value = true
|
||||
try {
|
||||
await revokeInvite(inviteId)
|
||||
await onConfirm()
|
||||
dialogStore.closeDialog({ key: 'revoke-invite' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
|
||||
@@ -43,10 +43,10 @@
|
||||
workspaceName
|
||||
}}</span>
|
||||
<div
|
||||
v-if="workspaceTierName"
|
||||
v-if="subscriptionTierName"
|
||||
class="shrink-0 rounded bg-secondary-background-hover px-1.5 py-0.5 text-xs"
|
||||
>
|
||||
{{ workspaceTierName }}
|
||||
{{ subscriptionTierName }}
|
||||
</div>
|
||||
<span v-else class="shrink-0 text-xs text-muted-foreground">
|
||||
{{ $t('workspaceSwitcher.subscribe') }}
|
||||
@@ -112,7 +112,7 @@
|
||||
size="sm"
|
||||
class="w-full"
|
||||
data-testid="subscribe-button"
|
||||
@click="handleOpenWorkspaceSettings"
|
||||
@click="handleOpenPlansAndPricing"
|
||||
>
|
||||
{{ $t('subscription.subscribeNow') }}
|
||||
</Button>
|
||||
@@ -246,8 +246,7 @@ const {
|
||||
workspaceName,
|
||||
workspaceRole,
|
||||
isPersonalWorkspace,
|
||||
isWorkspaceSubscribed,
|
||||
subscriptionPlan
|
||||
isWorkspaceSubscribed
|
||||
} = useWorkspace()
|
||||
const workspaceSwitcherPopover = ref<InstanceType<typeof Popover> | null>(null)
|
||||
|
||||
@@ -262,9 +261,14 @@ const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
|
||||
const authActions = useFirebaseAuthActions()
|
||||
const authStore = useFirebaseAuthStore()
|
||||
const dialogService = useDialogService()
|
||||
const { isActiveSubscription, fetchStatus } = useSubscription()
|
||||
const {
|
||||
isActiveSubscription,
|
||||
subscriptionTierName,
|
||||
subscriptionTier,
|
||||
fetchStatus
|
||||
} = useSubscription()
|
||||
const subscriptionDialog = useSubscriptionDialog()
|
||||
const { locale, t } = useI18n()
|
||||
const { locale } = useI18n()
|
||||
|
||||
const formattedBalance = computed(() => {
|
||||
const cents =
|
||||
@@ -281,23 +285,11 @@ const formattedBalance = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
// Workspace subscription tier name (not user tier)
|
||||
const workspaceTierName = computed(() => {
|
||||
if (!isWorkspaceSubscribed.value) return null
|
||||
if (!subscriptionPlan.value) return null
|
||||
// Convert plan to display name
|
||||
if (subscriptionPlan.value === 'PRO_MONTHLY')
|
||||
return t('subscription.tiers.pro.name')
|
||||
if (subscriptionPlan.value === 'PRO_YEARLY')
|
||||
return t('subscription.tierNameYearly', {
|
||||
name: t('subscription.tiers.pro.name')
|
||||
})
|
||||
return null
|
||||
})
|
||||
|
||||
const canUpgrade = computed(() => {
|
||||
// For workspace-based subscriptions, can upgrade if not on highest tier
|
||||
return isWorkspaceSubscribed.value && subscriptionPlan.value !== null
|
||||
const tier = subscriptionTier.value
|
||||
return (
|
||||
tier === 'FOUNDERS_EDITION' || tier === 'STANDARD' || tier === 'CREATOR'
|
||||
)
|
||||
})
|
||||
|
||||
// Menu visibility based on role
|
||||
|
||||
@@ -2128,8 +2128,7 @@
|
||||
"actions": {
|
||||
"copyLink": "Copy invite link",
|
||||
"revokeInvite": "Revoke invite",
|
||||
"removeMember": "Remove member",
|
||||
"acceptInvite": "Click to accept invite (demo)"
|
||||
"removeMember": "Remove member"
|
||||
},
|
||||
"noInvites": "No pending invites",
|
||||
"noMembers": "No members",
|
||||
@@ -2137,7 +2136,6 @@
|
||||
"createNewWorkspace": "create a new one."
|
||||
},
|
||||
"menu": {
|
||||
"editWorkspaceDetails": "Edit workspace details",
|
||||
"leaveWorkspace": "Leave Workspace",
|
||||
"deleteWorkspace": "Delete Workspace",
|
||||
"deleteWorkspaceDisabledTooltip": "Cancel your workspace's active subscription first"
|
||||
@@ -2180,11 +2178,6 @@
|
||||
"namePlaceholder": "Enter workspace name",
|
||||
"create": "Create"
|
||||
},
|
||||
"editWorkspaceDialog": {
|
||||
"title": "Edit workspace details",
|
||||
"nameLabel": "Workspace name",
|
||||
"save": "Save changes"
|
||||
},
|
||||
"toast": {
|
||||
"workspaceCreated": {
|
||||
"title": "Workspace created",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<Button
|
||||
variant="primary"
|
||||
class="ml-auto rounded-lg px-4 py-2 text-sm font-normal"
|
||||
@click="handleSubscribeWorkspace"
|
||||
@click="showSubscriptionDialog"
|
||||
>
|
||||
{{ $t('subscription.subscribeNow') }}
|
||||
</Button>
|
||||
@@ -259,12 +259,7 @@ import { useWorkspace } from '@/platform/workspace/composables/useWorkspace'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
const authActions = useFirebaseAuthActions()
|
||||
const {
|
||||
permissions,
|
||||
isWorkspaceSubscribed,
|
||||
workspaceRole,
|
||||
subscribeWorkspace
|
||||
} = useWorkspace()
|
||||
const { permissions, isWorkspaceSubscribed, workspaceRole } = useWorkspace()
|
||||
const { t, n } = useI18n()
|
||||
|
||||
// OWNER with unsubscribed workspace
|
||||
@@ -272,11 +267,6 @@ const isOwnerUnsubscribed = computed(
|
||||
() => workspaceRole.value === 'OWNER' && !isWorkspaceSubscribed.value
|
||||
)
|
||||
|
||||
// Demo: Subscribe workspace to PRO monthly plan
|
||||
function handleSubscribeWorkspace() {
|
||||
subscribeWorkspace('PRO_MONTHLY')
|
||||
}
|
||||
|
||||
const {
|
||||
isActiveSubscription,
|
||||
isCancelled,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
|
||||
type WorkspaceRole = 'PERSONAL' | 'MEMBER' | 'OWNER'
|
||||
type SubscriptionPlan = 'PRO_MONTHLY' | 'PRO_YEARLY' | null
|
||||
export type WorkspaceRole = 'PERSONAL' | 'MEMBER' | 'OWNER'
|
||||
|
||||
export interface WorkspaceMember {
|
||||
id: string
|
||||
@@ -21,14 +20,10 @@ export interface PendingInvite {
|
||||
inviteLink: string
|
||||
}
|
||||
|
||||
interface Workspace {
|
||||
interface WorkspaceMockData {
|
||||
id: string | null
|
||||
name: string
|
||||
role: WorkspaceRole
|
||||
isSubscribed: boolean
|
||||
subscriptionPlan: SubscriptionPlan
|
||||
members: WorkspaceMember[]
|
||||
pendingInvites: PendingInvite[]
|
||||
}
|
||||
|
||||
export interface AvailableWorkspace {
|
||||
@@ -38,7 +33,7 @@ export interface AvailableWorkspace {
|
||||
}
|
||||
|
||||
/** Permission flags for workspace actions */
|
||||
interface WorkspacePermissions {
|
||||
export interface WorkspacePermissions {
|
||||
canViewOtherMembers: boolean
|
||||
canViewPendingInvites: boolean
|
||||
canInviteMembers: boolean
|
||||
@@ -50,7 +45,7 @@ interface WorkspacePermissions {
|
||||
}
|
||||
|
||||
/** UI configuration for workspace role */
|
||||
interface WorkspaceUIConfig {
|
||||
export interface WorkspaceUIConfig {
|
||||
showMembersList: boolean
|
||||
showPendingTab: boolean
|
||||
showSearch: boolean
|
||||
@@ -59,7 +54,6 @@ interface WorkspaceUIConfig {
|
||||
membersGridCols: string
|
||||
pendingGridCols: string
|
||||
headerGridCols: string
|
||||
showEditWorkspaceMenuItem: boolean
|
||||
workspaceMenuAction: 'leave' | 'delete' | null
|
||||
workspaceMenuDisabledTooltip: string | null
|
||||
}
|
||||
@@ -72,7 +66,7 @@ const ROLE_PERMISSIONS: Record<WorkspaceRole, WorkspacePermissions> = {
|
||||
canManageInvites: false,
|
||||
canRemoveMembers: false,
|
||||
canLeaveWorkspace: false,
|
||||
canAccessWorkspaceMenu: true,
|
||||
canAccessWorkspaceMenu: false,
|
||||
canManageSubscription: true
|
||||
},
|
||||
MEMBER: {
|
||||
@@ -107,7 +101,6 @@ const ROLE_UI_CONFIG: Record<WorkspaceRole, WorkspaceUIConfig> = {
|
||||
membersGridCols: 'grid-cols-1',
|
||||
pendingGridCols: 'grid-cols-[50%_20%_20%_10%]',
|
||||
headerGridCols: 'grid-cols-1',
|
||||
showEditWorkspaceMenuItem: true,
|
||||
workspaceMenuAction: null,
|
||||
workspaceMenuDisabledTooltip: null
|
||||
},
|
||||
@@ -120,7 +113,6 @@ const ROLE_UI_CONFIG: Record<WorkspaceRole, WorkspaceUIConfig> = {
|
||||
membersGridCols: 'grid-cols-[1fr_auto]',
|
||||
pendingGridCols: 'grid-cols-[50%_20%_20%_10%]',
|
||||
headerGridCols: 'grid-cols-[1fr_auto]',
|
||||
showEditWorkspaceMenuItem: false,
|
||||
workspaceMenuAction: 'leave',
|
||||
workspaceMenuDisabledTooltip: null
|
||||
},
|
||||
@@ -133,202 +125,129 @@ const ROLE_UI_CONFIG: Record<WorkspaceRole, WorkspaceUIConfig> = {
|
||||
membersGridCols: 'grid-cols-[50%_40%_10%]',
|
||||
pendingGridCols: 'grid-cols-[50%_20%_20%_10%]',
|
||||
headerGridCols: 'grid-cols-[50%_40%_10%]',
|
||||
showEditWorkspaceMenuItem: true,
|
||||
workspaceMenuAction: 'delete',
|
||||
workspaceMenuDisabledTooltip:
|
||||
'workspacePanel.menu.deleteWorkspaceDisabledTooltip'
|
||||
}
|
||||
}
|
||||
|
||||
const MOCK_DATA: Record<WorkspaceRole, WorkspaceMockData> = {
|
||||
PERSONAL: {
|
||||
id: null,
|
||||
name: 'Personal',
|
||||
role: 'PERSONAL'
|
||||
},
|
||||
MEMBER: {
|
||||
id: 'workspace-abc-123',
|
||||
name: 'Acme Corp',
|
||||
role: 'MEMBER'
|
||||
},
|
||||
OWNER: {
|
||||
id: 'workspace-xyz-789',
|
||||
name: 'Acme Corp',
|
||||
role: 'OWNER'
|
||||
}
|
||||
}
|
||||
|
||||
/** Mock list of all available workspaces for the current user */
|
||||
const MOCK_AVAILABLE_WORKSPACES: AvailableWorkspace[] = [
|
||||
{ id: null, name: 'Personal workspace', role: 'PERSONAL' },
|
||||
{ id: 'workspace-comfy-001', name: 'Team Comfy', role: 'OWNER' },
|
||||
{ id: 'workspace-orange-002', name: 'OrangeDesignStudio', role: 'MEMBER' },
|
||||
{ id: 'workspace-001', name: 'Workspace001', role: 'MEMBER' },
|
||||
{ id: 'workspace-002', name: 'Workspace002', role: 'MEMBER' }
|
||||
]
|
||||
|
||||
const MAX_OWNED_WORKSPACES = 10
|
||||
|
||||
const MOCK_MEMBERS: WorkspaceMember[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Alice',
|
||||
email: 'alice@example.com',
|
||||
joinDate: new Date('2025-11-15')
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Bob',
|
||||
email: 'bob@example.com',
|
||||
joinDate: new Date('2025-12-01')
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Charlie',
|
||||
email: 'charlie@example.com',
|
||||
joinDate: new Date('2026-01-05')
|
||||
}
|
||||
]
|
||||
|
||||
const MOCK_PENDING_INVITES: PendingInvite[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'John',
|
||||
email: 'john@gmail.com',
|
||||
inviteDate: new Date('2026-01-02'),
|
||||
expiryDate: new Date('2026-01-09'),
|
||||
inviteLink: 'https://example.com/invite/abc123'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'User102',
|
||||
email: 'user102@gmail.com',
|
||||
inviteDate: new Date('2026-01-01'),
|
||||
expiryDate: new Date('2026-01-08'),
|
||||
inviteLink: 'https://example.com/invite/def456'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'User944',
|
||||
email: 'user944@gmail.com',
|
||||
inviteDate: new Date('2026-01-01'),
|
||||
expiryDate: new Date('2026-01-08'),
|
||||
inviteLink: 'https://example.com/invite/ghi789'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'User45',
|
||||
email: 'user45@gmail.com',
|
||||
inviteDate: new Date('2025-12-15'),
|
||||
expiryDate: new Date('2025-12-22'),
|
||||
inviteLink: 'https://example.com/invite/jkl012'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: 'User944',
|
||||
email: 'user944@gmail.com',
|
||||
inviteDate: new Date('2025-12-05'),
|
||||
expiryDate: new Date('2025-12-22'),
|
||||
inviteLink: 'https://example.com/invite/mno345'
|
||||
}
|
||||
]
|
||||
|
||||
// Constants
|
||||
const MAX_WORKSPACE_MEMBERS = 50
|
||||
|
||||
function generateId(): string {
|
||||
return Math.random().toString(36).substring(2, 10)
|
||||
}
|
||||
|
||||
function createPersonalWorkspace(): Workspace {
|
||||
return {
|
||||
id: null,
|
||||
name: 'Personal workspace',
|
||||
role: 'PERSONAL',
|
||||
isSubscribed: true,
|
||||
subscriptionPlan: null,
|
||||
members: [],
|
||||
pendingInvites: []
|
||||
}
|
||||
}
|
||||
|
||||
// Global state - start with personal workspace only
|
||||
const _workspaces = shallowRef<Workspace[]>([createPersonalWorkspace()])
|
||||
const _currentWorkspaceIndex = ref(0)
|
||||
// Shared state for workspace
|
||||
const _workspaceId = ref<string | null>(null)
|
||||
const _workspaceName = ref<string>('Personal workspace')
|
||||
const _workspaceRole = ref<WorkspaceRole>('PERSONAL')
|
||||
const _isWorkspaceSubscribed = ref<boolean>(true)
|
||||
const _activeTab = ref<string>('plan')
|
||||
|
||||
// Helper to get current workspace
|
||||
function getCurrentWorkspace(): Workspace {
|
||||
return _workspaces.value[_currentWorkspaceIndex.value]
|
||||
}
|
||||
|
||||
// Helper to update current workspace
|
||||
function updateCurrentWorkspace(updates: Partial<Workspace>) {
|
||||
const index = _currentWorkspaceIndex.value
|
||||
const updated = { ..._workspaces.value[index], ...updates }
|
||||
_workspaces.value = [
|
||||
..._workspaces.value.slice(0, index),
|
||||
updated,
|
||||
..._workspaces.value.slice(index + 1)
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to a different workspace
|
||||
*/
|
||||
function switchWorkspace(workspace: AvailableWorkspace) {
|
||||
const index = _workspaces.value.findIndex((w) => w.id === workspace.id)
|
||||
if (index !== -1) {
|
||||
_currentWorkspaceIndex.value = index
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new workspace
|
||||
*/
|
||||
function createNewWorkspace(name: string): Workspace {
|
||||
const newWorkspace: Workspace = {
|
||||
id: `workspace-${generateId()}`,
|
||||
name,
|
||||
role: 'OWNER',
|
||||
isSubscribed: false,
|
||||
subscriptionPlan: null,
|
||||
members: [],
|
||||
pendingInvites: [
|
||||
// Add one pending invite for testing revoke
|
||||
{
|
||||
id: generateId(),
|
||||
name: 'PendingUser',
|
||||
email: 'pending@example.com',
|
||||
inviteDate: new Date(),
|
||||
expiryDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
||||
inviteLink: `https://cloud.comfy.org/workspace/invite/${generateId()}`
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
_workspaces.value = [..._workspaces.value, newWorkspace]
|
||||
// Switch to the new workspace
|
||||
_currentWorkspaceIndex.value = _workspaces.value.length - 1
|
||||
|
||||
return newWorkspace
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe the current workspace to a plan
|
||||
*/
|
||||
function subscribeCurrentWorkspace(plan: SubscriptionPlan = 'PRO_MONTHLY') {
|
||||
updateCurrentWorkspace({
|
||||
isSubscribed: true,
|
||||
subscriptionPlan: plan
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the current workspace (OWNER only)
|
||||
*/
|
||||
function deleteCurrentWorkspace() {
|
||||
const current = getCurrentWorkspace()
|
||||
if (current.role === 'OWNER') {
|
||||
_workspaces.value = _workspaces.value.filter((w) => w.id !== current.id)
|
||||
_currentWorkspaceIndex.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Leave the current workspace (MEMBER only)
|
||||
*/
|
||||
function leaveCurrentWorkspace() {
|
||||
const current = getCurrentWorkspace()
|
||||
if (current.role === 'MEMBER') {
|
||||
_workspaces.value = _workspaces.value.filter((w) => w.id !== current.id)
|
||||
_currentWorkspaceIndex.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a member to the current workspace
|
||||
*/
|
||||
function addMemberToWorkspace(
|
||||
member: Omit<WorkspaceMember, 'id' | 'joinDate'>
|
||||
) {
|
||||
const current = getCurrentWorkspace()
|
||||
const newMember: WorkspaceMember = {
|
||||
...member,
|
||||
id: generateId(),
|
||||
joinDate: new Date()
|
||||
}
|
||||
updateCurrentWorkspace({
|
||||
members: [...current.members, newMember]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a member from the current workspace
|
||||
*/
|
||||
function removeMemberFromWorkspace(memberId: string) {
|
||||
const current = getCurrentWorkspace()
|
||||
updateCurrentWorkspace({
|
||||
members: current.members.filter((m) => m.id !== memberId)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current workspace name
|
||||
*/
|
||||
function updateWorkspaceNameFn(name: string) {
|
||||
updateCurrentWorkspace({ name })
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a pending invite
|
||||
*/
|
||||
function revokePendingInvite(inviteId: string) {
|
||||
const current = getCurrentWorkspace()
|
||||
updateCurrentWorkspace({
|
||||
pendingInvites: current.pendingInvites.filter((i) => i.id !== inviteId)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a pending invite (move to active members)
|
||||
* For demo: simulates user accepting the invite
|
||||
*/
|
||||
function acceptPendingInvite(inviteId: string) {
|
||||
const current = getCurrentWorkspace()
|
||||
const invite = current.pendingInvites.find((i) => i.id === inviteId)
|
||||
if (invite) {
|
||||
// Remove from pending
|
||||
const updatedPending = current.pendingInvites.filter(
|
||||
(i) => i.id !== inviteId
|
||||
)
|
||||
// Add to active members
|
||||
const newMember: WorkspaceMember = {
|
||||
id: generateId(),
|
||||
name: invite.name,
|
||||
email: invite.email,
|
||||
joinDate: new Date()
|
||||
}
|
||||
updateCurrentWorkspace({
|
||||
pendingInvites: updatedPending,
|
||||
members: [...current.members, newMember]
|
||||
})
|
||||
}
|
||||
}
|
||||
const _members = ref<WorkspaceMember[]>([])
|
||||
const _pendingInvites = ref<PendingInvite[]>([])
|
||||
const _availableWorkspaces = ref<AvailableWorkspace[]>(
|
||||
MOCK_AVAILABLE_WORKSPACES
|
||||
)
|
||||
|
||||
/**
|
||||
* Set workspace mock state for testing UI
|
||||
* Usage in browser console: window.__setWorkspaceRole('OWNER')
|
||||
*/
|
||||
function setMockRole(role: WorkspaceRole) {
|
||||
updateCurrentWorkspace({ role })
|
||||
const data = MOCK_DATA[role]
|
||||
_workspaceId.value = data.id
|
||||
_workspaceName.value = data.name
|
||||
_workspaceRole.value = data.role
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,7 +255,19 @@ function setMockRole(role: WorkspaceRole) {
|
||||
* Usage in browser console: window.__setWorkspaceSubscribed(false)
|
||||
*/
|
||||
function setMockSubscribed(subscribed: boolean) {
|
||||
updateCurrentWorkspace({ isSubscribed: subscribed })
|
||||
_isWorkspaceSubscribed.value = subscribed
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to a different workspace
|
||||
*/
|
||||
function switchWorkspace(workspace: AvailableWorkspace) {
|
||||
_workspaceId.value = workspace.id
|
||||
_workspaceName.value = workspace.name
|
||||
_workspaceRole.value = workspace.role
|
||||
// Reset members/invites when switching
|
||||
_members.value = []
|
||||
_pendingInvites.value = []
|
||||
}
|
||||
|
||||
// Expose to window for dev testing
|
||||
@@ -359,70 +290,43 @@ if (typeof window !== 'undefined') {
|
||||
export function useWorkspace() {
|
||||
const { userDisplayName, userEmail } = useCurrentUser()
|
||||
|
||||
// Computed from current workspace
|
||||
const currentWorkspace = computed(() => getCurrentWorkspace())
|
||||
const workspaceId = computed(() => currentWorkspace.value?.id ?? null)
|
||||
const workspaceName = computed(
|
||||
() => currentWorkspace.value?.name ?? 'Personal workspace'
|
||||
)
|
||||
const workspaceRole = computed(
|
||||
() => currentWorkspace.value?.role ?? 'PERSONAL'
|
||||
)
|
||||
const workspaceId = computed(() => _workspaceId.value)
|
||||
const workspaceName = computed(() => _workspaceName.value)
|
||||
const workspaceRole = computed(() => _workspaceRole.value)
|
||||
const activeTab = computed(() => _activeTab.value)
|
||||
|
||||
const isPersonalWorkspace = computed(
|
||||
() => currentWorkspace.value?.role === 'PERSONAL'
|
||||
() => _workspaceRole.value === 'PERSONAL'
|
||||
)
|
||||
|
||||
const isWorkspaceSubscribed = computed(
|
||||
() => currentWorkspace.value?.isSubscribed ?? false
|
||||
)
|
||||
|
||||
const subscriptionPlan = computed(
|
||||
() => currentWorkspace.value?.subscriptionPlan ?? null
|
||||
)
|
||||
const isWorkspaceSubscribed = computed(() => _isWorkspaceSubscribed.value)
|
||||
|
||||
const permissions = computed<WorkspacePermissions>(
|
||||
() => ROLE_PERMISSIONS[workspaceRole.value]
|
||||
() => ROLE_PERMISSIONS[_workspaceRole.value]
|
||||
)
|
||||
|
||||
const uiConfig = computed<WorkspaceUIConfig>(
|
||||
() => ROLE_UI_CONFIG[workspaceRole.value]
|
||||
() => ROLE_UI_CONFIG[_workspaceRole.value]
|
||||
)
|
||||
|
||||
function setActiveTab(tab: string | number) {
|
||||
_activeTab.value = String(tab)
|
||||
}
|
||||
|
||||
// For personal workspace, always show current user as the only member
|
||||
const members = computed<WorkspaceMember[]>(() => {
|
||||
if (isPersonalWorkspace.value) {
|
||||
return [
|
||||
{
|
||||
id: 'current-user',
|
||||
name: userDisplayName.value ?? 'You',
|
||||
email: userEmail.value ?? '',
|
||||
joinDate: new Date()
|
||||
}
|
||||
]
|
||||
}
|
||||
return currentWorkspace.value?.members ?? []
|
||||
})
|
||||
const pendingInvites = computed(
|
||||
() => currentWorkspace.value?.pendingInvites ?? []
|
||||
)
|
||||
const members = computed(() => _members.value)
|
||||
const pendingInvites = computed(() => _pendingInvites.value)
|
||||
|
||||
const totalMemberSlots = computed(
|
||||
() => members.value.length + pendingInvites.value.length
|
||||
() => _members.value.length + _pendingInvites.value.length
|
||||
)
|
||||
const isInviteLimitReached = computed(
|
||||
() => totalMemberSlots.value >= MAX_WORKSPACE_MEMBERS
|
||||
)
|
||||
|
||||
// Fetch members - returns current user for personal workspace
|
||||
// TODO: Replace with actual API calls
|
||||
async function fetchMembers(): Promise<WorkspaceMember[]> {
|
||||
if (isPersonalWorkspace.value) {
|
||||
return [
|
||||
if (_workspaceRole.value === 'PERSONAL') {
|
||||
_members.value = [
|
||||
{
|
||||
id: 'current-user',
|
||||
name: userDisplayName.value ?? 'You',
|
||||
@@ -430,20 +334,27 @@ export function useWorkspace() {
|
||||
joinDate: new Date()
|
||||
}
|
||||
]
|
||||
} else {
|
||||
_members.value = MOCK_MEMBERS
|
||||
}
|
||||
return members.value
|
||||
return _members.value
|
||||
}
|
||||
|
||||
async function fetchPendingInvites(): Promise<PendingInvite[]> {
|
||||
return pendingInvites.value
|
||||
if (_workspaceRole.value === 'PERSONAL') {
|
||||
_pendingInvites.value = []
|
||||
} else {
|
||||
_pendingInvites.value = MOCK_PENDING_INVITES
|
||||
}
|
||||
return _pendingInvites.value
|
||||
}
|
||||
|
||||
async function revokeInvite(inviteId: string): Promise<void> {
|
||||
revokePendingInvite(inviteId)
|
||||
async function revokeInvite(_inviteId: string): Promise<void> {
|
||||
// TODO: API call to revoke invite
|
||||
}
|
||||
|
||||
async function copyInviteLink(inviteId: string): Promise<string> {
|
||||
const invite = pendingInvites.value.find((i) => i.id === inviteId)
|
||||
const invite = _pendingInvites.value.find((i) => i.id === inviteId)
|
||||
if (invite) {
|
||||
await navigator.clipboard.writeText(invite.inviteLink)
|
||||
return invite.inviteLink
|
||||
@@ -451,63 +362,35 @@ export function useWorkspace() {
|
||||
throw new Error('Invite not found')
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy invite link and simulate member accepting (for demo)
|
||||
* When copy link is clicked, the invited user "accepts" and becomes a member
|
||||
*/
|
||||
async function copyInviteLinkAndAccept(inviteId: string): Promise<string> {
|
||||
const invite = pendingInvites.value.find((i) => i.id === inviteId)
|
||||
if (invite) {
|
||||
await navigator.clipboard.writeText(invite.inviteLink)
|
||||
|
||||
// Simulate user accepting invite: move from pending to active
|
||||
revokePendingInvite(inviteId)
|
||||
addMemberToWorkspace({
|
||||
name: invite.name,
|
||||
email: invite.email
|
||||
})
|
||||
|
||||
return invite.inviteLink
|
||||
}
|
||||
throw new Error('Invite not found')
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an invite link for a given email
|
||||
* TODO: Replace with actual API call
|
||||
*/
|
||||
async function createInviteLink(email: string): Promise<string> {
|
||||
// Simulate API delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
const inviteId = generateId()
|
||||
const inviteLink = `https://cloud.comfy.org/workspace/invite/${inviteId}`
|
||||
// Generate mock invite link
|
||||
const inviteId = Math.random().toString(36).substring(2, 10)
|
||||
const inviteLink = `https://cloud.comfy.org/workspace?3423532/invite/hi789jkl012mno345pq`
|
||||
|
||||
// Add to pending invites
|
||||
const current = getCurrentWorkspace()
|
||||
// Add to pending invites (mock)
|
||||
const newInvite: PendingInvite = {
|
||||
id: inviteId,
|
||||
name: email.split('@')[0],
|
||||
email,
|
||||
inviteDate: new Date(),
|
||||
expiryDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
||||
expiryDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
|
||||
inviteLink
|
||||
}
|
||||
updateCurrentWorkspace({
|
||||
pendingInvites: [...current.pendingInvites, newInvite]
|
||||
})
|
||||
_pendingInvites.value = [..._pendingInvites.value, newInvite]
|
||||
|
||||
return inviteLink
|
||||
}
|
||||
|
||||
const availableWorkspaces = computed<AvailableWorkspace[]>(() =>
|
||||
_workspaces.value.map((w) => ({
|
||||
id: w.id,
|
||||
name: w.name,
|
||||
role: w.role
|
||||
}))
|
||||
)
|
||||
const availableWorkspaces = computed(() => _availableWorkspaces.value)
|
||||
const ownedWorkspacesCount = computed(
|
||||
() => _workspaces.value.filter((w) => w.role === 'OWNER').length
|
||||
() => _availableWorkspaces.value.filter((w) => w.role === 'OWNER').length
|
||||
)
|
||||
const canCreateWorkspace = computed(
|
||||
() => ownedWorkspacesCount.value < MAX_OWNED_WORKSPACES
|
||||
@@ -520,7 +403,6 @@ export function useWorkspace() {
|
||||
activeTab,
|
||||
isPersonalWorkspace,
|
||||
isWorkspaceSubscribed,
|
||||
subscriptionPlan,
|
||||
permissions,
|
||||
uiConfig,
|
||||
setActiveTab,
|
||||
@@ -529,11 +411,6 @@ export function useWorkspace() {
|
||||
ownedWorkspacesCount,
|
||||
canCreateWorkspace,
|
||||
switchWorkspace,
|
||||
// Workspace management
|
||||
createWorkspace: createNewWorkspace,
|
||||
subscribeWorkspace: subscribeCurrentWorkspace,
|
||||
deleteWorkspace: deleteCurrentWorkspace,
|
||||
leaveWorkspace: leaveCurrentWorkspace,
|
||||
// Members
|
||||
members,
|
||||
pendingInvites,
|
||||
@@ -542,13 +419,8 @@ export function useWorkspace() {
|
||||
fetchMembers,
|
||||
fetchPendingInvites,
|
||||
revokeInvite,
|
||||
acceptInvite: acceptPendingInvite,
|
||||
copyInviteLink,
|
||||
copyInviteLinkAndAccept,
|
||||
createInviteLink,
|
||||
addMember: addMemberToWorkspace,
|
||||
removeMember: removeMemberFromWorkspace,
|
||||
updateWorkspaceName: updateWorkspaceNameFn,
|
||||
// Dev helpers
|
||||
setMockRole,
|
||||
setMockSubscribed
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Component } from 'vue'
|
||||
|
||||
import ApiNodesSignInContent from '@/components/dialog/content/ApiNodesSignInContent.vue'
|
||||
import CreateWorkspaceDialogContent from '@/components/dialog/content/workspace/CreateWorkspaceDialogContent.vue'
|
||||
import EditWorkspaceDialogContent from '@/components/dialog/content/workspace/EditWorkspaceDialogContent.vue'
|
||||
import DeleteWorkspaceDialogContent from '@/components/dialog/content/workspace/DeleteWorkspaceDialogContent.vue'
|
||||
import InviteMemberDialogContent from '@/components/dialog/content/workspace/InviteMemberDialogContent.vue'
|
||||
import LeaveWorkspaceDialogContent from '@/components/dialog/content/workspace/LeaveWorkspaceDialogContent.vue'
|
||||
@@ -527,10 +526,11 @@ export const useDialogService = () => {
|
||||
show()
|
||||
}
|
||||
|
||||
function showLeaveWorkspaceDialog() {
|
||||
function showLeaveWorkspaceDialog(onConfirm: () => void | Promise<void>) {
|
||||
return dialogStore.showDialog({
|
||||
key: 'leave-workspace',
|
||||
component: LeaveWorkspaceDialogContent,
|
||||
props: { onConfirm },
|
||||
dialogComponentProps: {
|
||||
headless: true,
|
||||
pt: {
|
||||
@@ -542,10 +542,11 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showDeleteWorkspaceDialog() {
|
||||
function showDeleteWorkspaceDialog(onConfirm: () => void | Promise<void>) {
|
||||
return dialogStore.showDialog({
|
||||
key: 'delete-workspace',
|
||||
component: DeleteWorkspaceDialogContent,
|
||||
props: { onConfirm },
|
||||
dialogComponentProps: {
|
||||
headless: true,
|
||||
pt: {
|
||||
@@ -557,11 +558,11 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showRemoveMemberDialog(memberId: string) {
|
||||
function showRemoveMemberDialog(onConfirm: () => void | Promise<void>) {
|
||||
return dialogStore.showDialog({
|
||||
key: 'remove-member',
|
||||
component: RemoveMemberDialogContent,
|
||||
props: { memberId },
|
||||
props: { onConfirm },
|
||||
dialogComponentProps: {
|
||||
headless: true,
|
||||
pt: {
|
||||
@@ -573,11 +574,11 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showRevokeInviteDialog(inviteId: string) {
|
||||
function showRevokeInviteDialog(onConfirm: () => void | Promise<void>) {
|
||||
return dialogStore.showDialog({
|
||||
key: 'revoke-invite',
|
||||
component: RevokeInviteDialogContent,
|
||||
props: { inviteId },
|
||||
props: { onConfirm },
|
||||
dialogComponentProps: {
|
||||
headless: true,
|
||||
pt: {
|
||||
@@ -625,21 +626,6 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showEditWorkspaceDialog() {
|
||||
return dialogStore.showDialog({
|
||||
key: 'edit-workspace',
|
||||
component: EditWorkspaceDialogContent,
|
||||
dialogComponentProps: {
|
||||
headless: true,
|
||||
pt: {
|
||||
header: { class: 'p-0! hidden' },
|
||||
content: { class: 'p-0! m-0! rounded-2xl' },
|
||||
root: { class: 'rounded-2xl max-w-[400px] w-full' }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
showLoadWorkflowWarning,
|
||||
showMissingModelsWarning,
|
||||
@@ -657,7 +643,6 @@ export const useDialogService = () => {
|
||||
showRevokeInviteDialog,
|
||||
showInviteMemberDialog,
|
||||
showCreateWorkspaceDialog,
|
||||
showEditWorkspaceDialog,
|
||||
showExtensionDialog,
|
||||
prompt,
|
||||
showErrorDialog,
|
||||
|
||||
Reference in New Issue
Block a user