From a1d150a748cd311b422d5cc5894abd98cef70355 Mon Sep 17 00:00:00 2001 From: DarkSky <25152247+darkskygit@users.noreply.github.com> Date: Tue, 12 May 2026 18:54:42 +0800 Subject: [PATCH] fix(server): realtime module not loaded (#14952) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### PR Dependency Tree * **PR #14952** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) ## Summary by CodeRabbit * **Refactor** * Optimized workspace invite link fetching by separating it from general workspace configuration queries for improved performance. * Reorganized transcription-related backend modules to better separate concerns and enable real-time functionality. * **Chores** * Updated generated GraphQL types and iOS query definitions to reflect API changes. [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/toeverything/AFFiNE/pull/14952) --- packages/backend/server/src/app.module.ts | 5 +- .../core/realtime/__tests__/registry.spec.ts | 58 ++++++++++++++++++ .../server/src/plugins/copilot/index.ts | 7 +++ .../src/plugins/copilot/module-providers.ts | 8 ++- .../src/plugins/copilot/transcript/index.ts | 2 + .../src/plugins/copilot/transcript/job.ts | 52 ++++++++++++++++ .../src/plugins/copilot/transcript/reader.ts | 24 ++++++++ .../plugins/copilot/transcript/realtime.ts | 4 +- .../plugins/copilot/transcript/resolver.ts | 3 +- .../src/plugins/copilot/transcript/service.ts | 59 ++----------------- packages/common/graphql/src/graphql/index.ts | 17 ++++-- .../graphql/src/graphql/workspace-config.gql | 4 -- .../src/graphql/workspace-invite-link-get.gql | 8 +++ packages/common/graphql/src/schema.ts | 26 ++++++-- .../GetWorkspaceConfigQuery.graphql.swift | 28 +-------- .../members/cloud-members-panel.tsx | 10 +++- .../share-setting/entities/share-setting.ts | 18 +++++- .../share-setting/stores/share-setting.ts | 17 ++++++ 18 files changed, 246 insertions(+), 104 deletions(-) create mode 100644 packages/backend/server/src/plugins/copilot/transcript/job.ts create mode 100644 packages/backend/server/src/plugins/copilot/transcript/reader.ts create mode 100644 packages/common/graphql/src/graphql/workspace-invite-link-get.gql diff --git a/packages/backend/server/src/app.module.ts b/packages/backend/server/src/app.module.ts index 0bc74a019d..df1cf97fcb 100644 --- a/packages/backend/server/src/app.module.ts +++ b/packages/backend/server/src/app.module.ts @@ -55,7 +55,7 @@ import { Env } from './env'; import { ModelsModule } from './models'; import { CalendarModule } from './plugins/calendar'; import { CaptchaModule } from './plugins/captcha'; -import { CopilotModule } from './plugins/copilot'; +import { CopilotModule, CopilotRealtimeModule } from './plugins/copilot'; import { CustomerIoModule } from './plugins/customerio'; import { GCloudModule } from './plugins/gcloud'; import { IndexerModule } from './plugins/indexer'; @@ -185,7 +185,8 @@ export function buildAppModule(env: Env) { .useIf( () => env.flavors.sync || env.flavors.front, SyncModule, - TelemetryModule + TelemetryModule, + CopilotRealtimeModule ) // graphql server only .useIf( diff --git a/packages/backend/server/src/core/realtime/__tests__/registry.spec.ts b/packages/backend/server/src/core/realtime/__tests__/registry.spec.ts index e89039ff6b..8bcb28cd4a 100644 --- a/packages/backend/server/src/core/realtime/__tests__/registry.spec.ts +++ b/packages/backend/server/src/core/realtime/__tests__/registry.spec.ts @@ -2,7 +2,10 @@ import { getRealtimeInputKey } from '@affine/realtime'; import test from 'ava'; import { z } from 'zod'; +import type { CopilotTranscriptionReader } from '../../../plugins/copilot/transcript'; +import { CopilotTranscriptRealtimeProvider } from '../../../plugins/copilot/transcript'; import type { CurrentUser } from '../../auth'; +import type { AccessController } from '../../permission'; import { RealtimeGateway } from '../gateway'; import { realtimeCommentRoom, @@ -191,6 +194,61 @@ test('registerRealtimeLiveQuery registers paired request and topic handlers', as ); }); +test('copilot transcript realtime provider registers task live query handlers', async t => { + const registry = new RealtimeRegistry(); + const assertions: unknown[] = []; + const ac = { + user(userId: string) { + return { + workspace(workspaceId: string) { + return { + allowLocal() { + return this; + }, + async assert(action: string) { + assertions.push({ userId, workspaceId, action }); + }, + }; + }, + }; + }, + } as unknown as AccessController; + const transcript = { + async queryTask( + userId: string, + workspaceId: string, + taskId?: string, + blobId?: string + ) { + return { id: taskId ?? blobId, status: 'finished', userId, workspaceId }; + }, + } as unknown as CopilotTranscriptionReader; + + new CopilotTranscriptRealtimeProvider( + ac, + transcript, + registry + ).onModuleInit(); + + t.deepEqual( + await registry.getRequest('copilot.transcript.task.get').handle(user, { + workspaceId: 'space', + taskId: 'task', + }), + { + task: { + id: 'task', + status: 'finished', + userId: 'u1', + workspaceId: 'space', + }, + } + ); + t.deepEqual(assertions, [ + { userId: 'u1', workspaceId: 'space', action: 'Workspace.Copilot' }, + ]); +}); + test('publisher emits realtime event with shared input key', t => { const registry = new RealtimeRegistry(); registry.registerTopic({ diff --git a/packages/backend/server/src/plugins/copilot/index.ts b/packages/backend/server/src/plugins/copilot/index.ts index 46401ab586..0faed54e4e 100644 --- a/packages/backend/server/src/plugins/copilot/index.ts +++ b/packages/backend/server/src/plugins/copilot/index.ts @@ -16,6 +16,7 @@ import { COPILOT_API_PROVIDERS, COPILOT_FEATURE_PROVIDERS, COPILOT_KERNEL_PROVIDERS, + COPILOT_TRANSCRIPT_REALTIME_PROVIDERS, } from './module-providers'; const COPILOT_SHARED_IMPORTS = [ @@ -36,6 +37,12 @@ const COPILOT_SHARED_IMPORTS = [ }) export class CopilotKernelModule {} +@Module({ + imports: [PermissionModule], + providers: [...COPILOT_TRANSCRIPT_REALTIME_PROVIDERS], +}) +export class CopilotRealtimeModule {} + @Module({ imports: [...COPILOT_SHARED_IMPORTS, CopilotKernelModule], providers: [...COPILOT_FEATURE_PROVIDERS], diff --git a/packages/backend/server/src/plugins/copilot/module-providers.ts b/packages/backend/server/src/plugins/copilot/module-providers.ts index 3cc1e8ca28..91a00f3e0a 100644 --- a/packages/backend/server/src/plugins/copilot/module-providers.ts +++ b/packages/backend/server/src/plugins/copilot/module-providers.ts @@ -54,6 +54,7 @@ import { TurnOrchestrator } from './runtime/turn-orchestrator'; import { ChatSessionService } from './session'; import { CopilotStorage } from './storage'; import { + CopilotTranscriptionReader, CopilotTranscriptionResolver, CopilotTranscriptionService, CopilotTranscriptRealtimeProvider, @@ -113,10 +114,15 @@ export const COPILOT_CONTEXT_PROVIDERS = [ CopilotEmbeddingRealtimeProvider, ]; +export const COPILOT_TRANSCRIPT_REALTIME_PROVIDERS = [ + CopilotTranscriptionReader, + CopilotTranscriptRealtimeProvider, +]; + export const COPILOT_TRANSCRIPT_PROVIDERS = [ CopilotTranscriptionService, CopilotTranscriptionResolver, - CopilotTranscriptRealtimeProvider, + ...COPILOT_TRANSCRIPT_REALTIME_PROVIDERS, ]; export const COPILOT_WORKSPACE_PROVIDERS = [ diff --git a/packages/backend/server/src/plugins/copilot/transcript/index.ts b/packages/backend/server/src/plugins/copilot/transcript/index.ts index 11f1b5541a..cc1c85afed 100644 --- a/packages/backend/server/src/plugins/copilot/transcript/index.ts +++ b/packages/backend/server/src/plugins/copilot/transcript/index.ts @@ -1,3 +1,5 @@ +export type { TranscriptionJob } from './job'; +export { CopilotTranscriptionReader } from './reader'; export { CopilotTranscriptRealtimeProvider } from './realtime'; export { CopilotTranscriptionResolver } from './resolver'; export { CopilotTranscriptionService } from './service'; diff --git a/packages/backend/server/src/plugins/copilot/transcript/job.ts b/packages/backend/server/src/plugins/copilot/transcript/job.ts new file mode 100644 index 0000000000..7fcd6805e7 --- /dev/null +++ b/packages/backend/server/src/plugins/copilot/transcript/job.ts @@ -0,0 +1,52 @@ +import { AiJobStatus } from '@prisma/client'; + +import { TranscriptPayloadSchema } from './schema'; +import type { AudioBlobInfos, TranscriptionPayload } from './types'; + +export type TranscriptionJob = { + id: string; + status: AiJobStatus; + infos?: AudioBlobInfos; + transcription?: TranscriptionPayload; +}; + +export function taskStatusToPublicStatus(status: string): AiJobStatus { + switch (status) { + case 'pending': + return AiJobStatus.pending; + case 'running': + return AiJobStatus.running; + case 'ready': + case 'settled': + return AiJobStatus.finished; + default: + return AiJobStatus.failed; + } +} + +export function taskToJob( + task: { + id: string; + status: string; + protectedResult: unknown; + } | null, + mapStatus: (status: string) => AiJobStatus = taskStatusToPublicStatus +): TranscriptionJob | null { + if (!task) { + return null; + } + + const status = mapStatus(task.status); + const ret: TranscriptionJob = { + id: task.id, + status, + }; + if (task.protectedResult) { + const parsed = TranscriptPayloadSchema.safeParse(task.protectedResult); + ret.infos = parsed.success ? (parsed.data.infos ?? []) : []; + if (task.status === 'settled' && parsed.success) { + ret.transcription = parsed.data; + } + } + return ret; +} diff --git a/packages/backend/server/src/plugins/copilot/transcript/reader.ts b/packages/backend/server/src/plugins/copilot/transcript/reader.ts new file mode 100644 index 0000000000..78b99acfbc --- /dev/null +++ b/packages/backend/server/src/plugins/copilot/transcript/reader.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; + +import { Models } from '../../../models'; +import { taskToJob } from './job'; + +@Injectable() +export class CopilotTranscriptionReader { + constructor(private readonly models: Models) {} + + async queryTask( + userId: string, + workspaceId: string, + taskId?: string, + blobId?: string + ) { + const task = await this.models.copilotTranscriptTask.getWithUser( + userId, + workspaceId, + taskId, + blobId + ); + return taskToJob(task); + } +} diff --git a/packages/backend/server/src/plugins/copilot/transcript/realtime.ts b/packages/backend/server/src/plugins/copilot/transcript/realtime.ts index 98a006f7bf..384e79d85c 100644 --- a/packages/backend/server/src/plugins/copilot/transcript/realtime.ts +++ b/packages/backend/server/src/plugins/copilot/transcript/realtime.ts @@ -8,13 +8,13 @@ import { realtimeTranscriptTaskRoom, registerRealtimeLiveQuery, } from '../../../core/realtime'; -import { CopilotTranscriptionService } from './service'; +import { CopilotTranscriptionReader } from './reader'; @Injectable() export class CopilotTranscriptRealtimeProvider implements OnModuleInit { constructor( private readonly ac: AccessController, - private readonly transcript: CopilotTranscriptionService, + private readonly transcript: CopilotTranscriptionReader, @Optional() private readonly registry?: RealtimeRegistry ) {} diff --git a/packages/backend/server/src/plugins/copilot/transcript/resolver.ts b/packages/backend/server/src/plugins/copilot/transcript/resolver.ts index e8d952a844..07de9ade57 100644 --- a/packages/backend/server/src/plugins/copilot/transcript/resolver.ts +++ b/packages/backend/server/src/plugins/copilot/transcript/resolver.ts @@ -23,8 +23,9 @@ import { import { CurrentUser } from '../../../core/auth'; import { AccessController } from '../../../core/permission'; import { CopilotType } from '../resolver'; +import type { TranscriptionJob } from './job'; import { buildLegacyProjection } from './projection'; -import { CopilotTranscriptionService, TranscriptionJob } from './service'; +import { CopilotTranscriptionService } from './service'; import type { AudioSliceManifestItem, MeetingActionItem, diff --git a/packages/backend/server/src/plugins/copilot/transcript/service.ts b/packages/backend/server/src/plugins/copilot/transcript/service.ts index eb1d685f3d..0cf0989d31 100644 --- a/packages/backend/server/src/plugins/copilot/transcript/service.ts +++ b/packages/backend/server/src/plugins/copilot/transcript/service.ts @@ -20,13 +20,13 @@ import { CopilotProviderType } from '../providers/types'; import { ActionRuntimeBridge } from '../runtime/action-runtime-bridge'; import { TaskPolicy } from '../runtime/task-policy'; import { CopilotStorage } from '../storage'; +import { taskToJob, type TranscriptionJob } from './job'; import { TranscriptActionResultContract, TranscriptPayloadSchema, } from './schema'; import type { AudioBlobInfos, - TranscriptionPayload, TranscriptionPayloadV2, TranscriptionSubmitInput, } from './types'; @@ -36,27 +36,6 @@ const TRANSCRIPT_ACTION_ID = 'transcript.audio.gemini'; const TRANSCRIPT_ACTION_VERSION = 'v1'; const TRANSCRIPT_STRATEGY = 'gemini'; -export type TranscriptionJob = { - id: string; - status: AiJobStatus; - infos?: AudioBlobInfos; - transcription?: TranscriptionPayload; -}; - -function taskStatusToPublicStatus(status: string): AiJobStatus { - switch (status) { - case 'pending': - return AiJobStatus.pending; - case 'running': - return AiJobStatus.running; - case 'ready': - case 'settled': - return AiJobStatus.finished; - default: - return AiJobStatus.failed; - } -} - @Injectable() export class CopilotTranscriptionService { constructor( @@ -85,33 +64,6 @@ export class CopilotTranscriptionService { }; } - private taskToJob( - task: { - id: string; - status: string; - protectedResult: unknown; - } | null, - mapStatus: (status: string) => AiJobStatus = taskStatusToPublicStatus - ): TranscriptionJob | null { - if (!task) { - return null; - } - - const status = mapStatus(task.status); - const ret: TranscriptionJob = { - id: task.id, - status, - }; - if (task.protectedResult) { - const parsed = TranscriptPayloadSchema.safeParse(task.protectedResult); - ret.infos = parsed.success ? (parsed.data.infos ?? []) : []; - if (task.status === 'settled' && parsed.success) { - ret.transcription = parsed.data; - } - } - return ret; - } - private async resolveTranscriptStrategy(userId: string, strategy?: string) { if (strategy && strategy !== TRANSCRIPT_STRATEGY) { throw new BadRequestException( @@ -327,7 +279,7 @@ export class CopilotTranscriptionService { } if (task.status === 'settled') { - return this.taskToJob(task); + return taskToJob(task); } await this.access?.assertQuotaOrByok({ @@ -337,7 +289,7 @@ export class CopilotTranscriptionService { }); const settled = await this.models.copilotTranscriptTask.settle(task.id); - return this.taskToJob(settled); + return taskToJob(settled); } async queryTask( @@ -352,10 +304,7 @@ export class CopilotTranscriptionService { taskId, blobId ); - if (task) { - return this.taskToJob(task); - } - return null; + return taskToJob(task); } @OnJob('copilot.transcript.task.submit') diff --git a/packages/common/graphql/src/graphql/index.ts b/packages/common/graphql/src/graphql/index.ts index 46f99badb6..c8741d5d47 100644 --- a/packages/common/graphql/src/graphql/index.ts +++ b/packages/common/graphql/src/graphql/index.ts @@ -3126,10 +3126,6 @@ export const getWorkspaceConfigQuery = { enableSharing enableUrlPreview enableDocEmbedding - inviteLink { - link - expireTime - } } }`, }; @@ -3195,6 +3191,19 @@ export const acceptInviteByInviteIdMutation = { }`, }; +export const getWorkspaceInviteLinkQuery = { + id: 'getWorkspaceInviteLinkQuery' as const, + op: 'getWorkspaceInviteLink', + query: `query getWorkspaceInviteLink($id: String!) { + workspace(id: $id) { + inviteLink { + link + expireTime + } + } +}`, +}; + export const createInviteLinkMutation = { id: 'createInviteLinkMutation' as const, op: 'createInviteLink', diff --git a/packages/common/graphql/src/graphql/workspace-config.gql b/packages/common/graphql/src/graphql/workspace-config.gql index cb2be6ac76..dbab91c3bc 100644 --- a/packages/common/graphql/src/graphql/workspace-config.gql +++ b/packages/common/graphql/src/graphql/workspace-config.gql @@ -4,9 +4,5 @@ query getWorkspaceConfig($id: String!) { enableSharing enableUrlPreview enableDocEmbedding - inviteLink { - link - expireTime - } } } diff --git a/packages/common/graphql/src/graphql/workspace-invite-link-get.gql b/packages/common/graphql/src/graphql/workspace-invite-link-get.gql new file mode 100644 index 0000000000..ab20c3d0ba --- /dev/null +++ b/packages/common/graphql/src/graphql/workspace-invite-link-get.gql @@ -0,0 +1,8 @@ +query getWorkspaceInviteLink($id: String!) { + workspace(id: $id) { + inviteLink { + link + expireTime + } + } +} diff --git a/packages/common/graphql/src/schema.ts b/packages/common/graphql/src/schema.ts index a0d9762b10..bd50352535 100644 --- a/packages/common/graphql/src/schema.ts +++ b/packages/common/graphql/src/schema.ts @@ -7794,11 +7794,6 @@ export type GetWorkspaceConfigQuery = { enableSharing: boolean; enableUrlPreview: boolean; enableDocEmbedding: boolean; - inviteLink: { - __typename?: 'InviteLink'; - link: string; - expireTime: string; - } | null; }; }; @@ -7867,6 +7862,22 @@ export type AcceptInviteByInviteIdMutation = { acceptInviteById: boolean; }; +export type GetWorkspaceInviteLinkQueryVariables = Exact<{ + id: Scalars['String']['input']; +}>; + +export type GetWorkspaceInviteLinkQuery = { + __typename?: 'Query'; + workspace: { + __typename?: 'WorkspaceType'; + inviteLink: { + __typename?: 'InviteLink'; + link: string; + expireTime: string; + } | null; + }; +}; + export type CreateInviteLinkMutationVariables = Exact<{ workspaceId: Scalars['String']['input']; expireTime: WorkspaceInviteLinkExpireTime; @@ -8418,6 +8429,11 @@ export type Queries = variables: GetWorkspaceConfigQueryVariables; response: GetWorkspaceConfigQuery; } + | { + name: 'getWorkspaceInviteLinkQuery'; + variables: GetWorkspaceInviteLinkQueryVariables; + response: GetWorkspaceInviteLinkQuery; + } | { name: 'workspaceInvoicesQuery'; variables: WorkspaceInvoicesQueryVariables; diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetWorkspaceConfigQuery.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetWorkspaceConfigQuery.graphql.swift index cb3e0db5d6..01aa16626e 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetWorkspaceConfigQuery.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetWorkspaceConfigQuery.graphql.swift @@ -7,7 +7,7 @@ public class GetWorkspaceConfigQuery: GraphQLQuery { public static let operationName: String = "getWorkspaceConfig" public static let operationDocument: ApolloAPI.OperationDocument = .init( definition: .init( - #"query getWorkspaceConfig($id: String!) { workspace(id: $id) { __typename enableAi enableSharing enableUrlPreview enableDocEmbedding inviteLink { __typename link expireTime } } }"# + #"query getWorkspaceConfig($id: String!) { workspace(id: $id) { __typename enableAi enableSharing enableUrlPreview enableDocEmbedding } }"# )) public var id: String @@ -47,7 +47,6 @@ public class GetWorkspaceConfigQuery: GraphQLQuery { .field("enableSharing", Bool.self), .field("enableUrlPreview", Bool.self), .field("enableDocEmbedding", Bool.self), - .field("inviteLink", InviteLink?.self), ] } public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [ GetWorkspaceConfigQuery.Data.Workspace.self @@ -61,31 +60,6 @@ public class GetWorkspaceConfigQuery: GraphQLQuery { public var enableUrlPreview: Bool { __data["enableUrlPreview"] } /// Enable doc embedding public var enableDocEmbedding: Bool { __data["enableDocEmbedding"] } - /// invite link for workspace - public var inviteLink: InviteLink? { __data["inviteLink"] } - - /// Workspace.InviteLink - /// - /// Parent Type: `InviteLink` - public struct InviteLink: AffineGraphQL.SelectionSet { - public let __data: DataDict - public init(_dataDict: DataDict) { __data = _dataDict } - - public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.InviteLink } - public static var __selections: [ApolloAPI.Selection] { [ - .field("__typename", String.self), - .field("link", String.self), - .field("expireTime", AffineGraphQL.DateTime.self), - ] } - public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [ - GetWorkspaceConfigQuery.Data.Workspace.InviteLink.self - ] } - - /// Invite link - public var link: String { __data["link"] } - /// Invite link expire time - public var expireTime: AffineGraphQL.DateTime { __data["expireTime"] } - } } } } diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/members/cloud-members-panel.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/members/cloud-members-panel.tsx index f0e891f110..d6a2cbe72c 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/members/cloud-members-panel.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/members/cloud-members-panel.tsx @@ -85,6 +85,12 @@ export const CloudWorkspaceMembersPanel = ({ membersService.members.revalidate(); }, [membersService]); + useEffect(() => { + if (isOwnerOrAdmin) { + workspaceShareSettingService.sharePreview.revalidateInviteLink(); + } + }, [isOwnerOrAdmin, workspaceShareSettingService.sharePreview]); + const workspaceQuotaService = useService(WorkspaceQuotaService); useEffect(() => { workspaceQuotaService.quota.revalidate(); @@ -178,7 +184,7 @@ export const CloudWorkspaceMembersPanel = ({ const onGenerateInviteLink = useCallback( async (expireTime: WorkspaceInviteLinkExpireTime) => { const { link } = await membersService.generateInviteLink(expireTime); - workspaceShareSettingService.sharePreview.revalidate(); + workspaceShareSettingService.sharePreview.revalidateInviteLink(); return link; }, [membersService, workspaceShareSettingService.sharePreview] @@ -186,7 +192,7 @@ export const CloudWorkspaceMembersPanel = ({ const onRevokeInviteLink = useCallback(async () => { const success = await membersService.revokeInviteLink(); - workspaceShareSettingService.sharePreview.revalidate(); + workspaceShareSettingService.sharePreview.revalidateInviteLink(); return success; }, [membersService, workspaceShareSettingService.sharePreview]); diff --git a/packages/frontend/core/src/modules/share-setting/entities/share-setting.ts b/packages/frontend/core/src/modules/share-setting/entities/share-setting.ts index e25acb00f1..e94b9ba185 100644 --- a/packages/frontend/core/src/modules/share-setting/entities/share-setting.ts +++ b/packages/frontend/core/src/modules/share-setting/entities/share-setting.ts @@ -52,7 +52,6 @@ export class WorkspaceShareSetting extends Entity { this.enableAi$.next(value.enableAi); this.enableSharing$.next(value.enableSharing); this.enableUrlPreview$.next(value.enableUrlPreview); - this.inviteLink$.next(value.inviteLink); } }), catchErrorInto(this.error$, error => { @@ -64,6 +63,22 @@ export class WorkspaceShareSetting extends Entity { }) ); + revalidateInviteLink = effect( + exhaustMap(() => { + return fromPromise(signal => + this.store.fetchInviteLink(this.workspaceService.workspace.id, signal) + ).pipe( + smartRetry(), + tap(value => { + this.inviteLink$.next(value); + }), + catchErrorInto(this.error$, error => { + logger.error('Failed to fetch workspace invite link', error); + }) + ); + }) + ); + async waitForRevalidation(signal?: AbortSignal) { this.revalidate(); await this.isLoading$.waitFor(isLoading => !isLoading, signal); @@ -95,5 +110,6 @@ export class WorkspaceShareSetting extends Entity { override dispose(): void { this.revalidate.unsubscribe(); + this.revalidateInviteLink.unsubscribe(); } } diff --git a/packages/frontend/core/src/modules/share-setting/stores/share-setting.ts b/packages/frontend/core/src/modules/share-setting/stores/share-setting.ts index e00b0d9f46..70270023d5 100644 --- a/packages/frontend/core/src/modules/share-setting/stores/share-setting.ts +++ b/packages/frontend/core/src/modules/share-setting/stores/share-setting.ts @@ -1,6 +1,7 @@ import type { WorkspaceServerService } from '@affine/core/modules/cloud'; import { getWorkspaceConfigQuery, + getWorkspaceInviteLinkQuery, setEnableAiMutation, setEnableSharingMutation, setEnableUrlPreviewMutation, @@ -28,6 +29,22 @@ export class WorkspaceShareSettingStore extends Store { return data.workspace; } + async fetchInviteLink(workspaceId: string, signal?: AbortSignal) { + if (!this.workspaceServerService.server) { + throw new Error('No Server'); + } + const data = await this.workspaceServerService.server.gql({ + query: getWorkspaceInviteLinkQuery, + variables: { + id: workspaceId, + }, + context: { + signal, + }, + }); + return data.workspace.inviteLink; + } + async updateWorkspaceEnableAi( workspaceId: string, enableAi: boolean,