From fcc9b31da95730f9b9fb1950f4a77e111db6c019 Mon Sep 17 00:00:00 2001 From: akumatus Date: Thu, 15 May 2025 04:55:50 +0000 Subject: [PATCH] feat(core): add get session graphql api (#12237) Close [AI-116](https://linear.app/affine-design/issue/AI-116) ## Summary by CodeRabbit - **New Features** - Added the ability to retrieve detailed information for a specific Copilot session by its ID, including model metadata and optional models, via the user interface and API. - Session data now includes additional fields such as the model used and a list of optional models. - Enhanced GraphQL queries and UI components to support fetching and displaying these new session details. - **Improvements** - Session lists now provide richer information, including model details, for each session. --- .../server/src/plugins/copilot/resolver.ts | 52 +++++++++++++++++-- .../server/src/plugins/copilot/session.ts | 40 ++++++++------ packages/backend/server/src/schema.gql | 5 ++ .../src/graphql/copilot-session-get.gql | 16 ++++++ .../src/graphql/copilot-sessions-get.gql | 2 + packages/common/graphql/src/graphql/index.ts | 20 +++++++ packages/common/graphql/src/schema.ts | 38 ++++++++++++++ .../core/src/blocksuite/ai/actions/types.ts | 4 ++ .../blocksuite/ai/provider/copilot-client.ts | 13 +++++ .../blocksuite/ai/provider/setup-provider.tsx | 3 ++ 10 files changed, 173 insertions(+), 20 deletions(-) create mode 100644 packages/common/graphql/src/graphql/copilot-session-get.gql diff --git a/packages/backend/server/src/plugins/copilot/resolver.ts b/packages/backend/server/src/plugins/copilot/resolver.ts index 154002451..797d35b3b 100644 --- a/packages/backend/server/src/plugins/copilot/resolver.ts +++ b/packages/backend/server/src/plugins/copilot/resolver.ts @@ -41,6 +41,7 @@ import { AvailableModels, type ChatHistory, type ChatMessage, + type ChatSessionState, type ListHistoriesOptions, SubmittedMessage, } from './types'; @@ -277,7 +278,7 @@ class CopilotPromptType { } @ObjectType() -class CopilotSessionType { +export class CopilotSessionType { @Field(() => ID) id!: string; @@ -286,6 +287,12 @@ class CopilotSessionType { @Field(() => String) promptName!: string; + + @Field(() => String) + model!: string; + + @Field(() => [String]) + optionalModels!: string[]; } // ================== Resolver ================== @@ -329,6 +336,30 @@ export class CopilotResolver { return (await this.sessions(copilot, user, docId, options)).map(s => s.id); } + @ResolveField(() => CopilotSessionType, { + description: 'Get the session by id', + complexity: 2, + }) + async session( + @Parent() copilot: CopilotType, + @CurrentUser() user: CurrentUser, + @Args('sessionId') sessionId: string + ): Promise { + if (!copilot.workspaceId) { + throw new NotFoundException('Workspace not found'); + } + await this.ac + .user(user.id) + .workspace(copilot.workspaceId) + .allowLocal() + .assert('Workspace.Copilot'); + const session = await this.chatSession.getSession(sessionId); + if (!session) { + throw new NotFoundException('Session not found'); + } + return this.transformToSessionType(session); + } + @ResolveField(() => [CopilotSessionType], { description: 'Get the session list in the workspace', complexity: 2, @@ -339,18 +370,21 @@ export class CopilotResolver { @Args('docId', { nullable: true }) docId?: string, @Args('options', { nullable: true }) options?: QueryChatSessionsInput ): Promise { - if (!copilot.workspaceId) return []; + if (!copilot.workspaceId) { + throw new NotFoundException('Workspace not found'); + } await this.ac .user(user.id) .workspace(copilot.workspaceId) .allowLocal() .assert('Workspace.Copilot'); - return await this.chatSession.listSessions( + const sessions = await this.chatSession.listSessions( user.id, copilot.workspaceId, docId, options ); + return sessions.map(this.transformToSessionType); } @ResolveField(() => [CopilotHistoriesType], {}) @@ -556,6 +590,18 @@ export class CopilotResolver { throw new CopilotFailedToCreateMessage(e.message); } } + + private transformToSessionType( + session: Omit + ): CopilotSessionType { + return { + id: session.sessionId, + parentSessionId: session.parentSessionId, + promptName: session.prompt.name, + model: session.prompt.model, + optionalModels: session.prompt.optionalModels, + }; + } } @Throttle() diff --git a/packages/backend/server/src/plugins/copilot/session.ts b/packages/backend/server/src/plugins/copilot/session.ts index 5ddd44846..08ef63441 100644 --- a/packages/backend/server/src/plugins/copilot/session.ts +++ b/packages/backend/server/src/plugins/copilot/session.ts @@ -307,9 +307,7 @@ export class ChatSessionService { }); } - private async getSession( - sessionId: string - ): Promise { + async getSession(sessionId: string): Promise { return await this.db.aiSession .findUnique({ where: { id: sessionId, deletedAt: null }, @@ -414,13 +412,7 @@ export class ChatSessionService { workspaceId: string, docId?: string, options?: { action?: boolean } - ): Promise< - Array<{ - id: string; - parentSessionId: string | null; - promptName: string; - }> - > { + ): Promise[]> { return await this.db.aiSession .findMany({ where: { @@ -434,17 +426,31 @@ export class ChatSessionService { }, select: { id: true, + userId: true, + workspaceId: true, + docId: true, parentSessionId: true, promptName: true, }, }) - .then(sessions => - sessions.map(({ id, parentSessionId, promptName }) => ({ - id, - parentSessionId: parentSessionId || null, - promptName, - })) - ); + .then(sessions => { + return Promise.all( + sessions.map(async session => { + const prompt = await this.prompt.get(session.promptName); + if (!prompt) + throw new CopilotPromptNotFound({ name: session.promptName }); + + return { + sessionId: session.id, + userId: session.userId, + workspaceId: session.workspaceId, + docId: session.docId, + parentSessionId: session.parentSessionId, + prompt, + }; + }) + ); + }); } async listHistories( diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index ed19ef7a4..df8393510 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -133,6 +133,9 @@ type Copilot { """Get the quota of the user in the workspace""" quota: CopilotQuota! + """Get the session by id""" + session(sessionId: String!): CopilotSessionType! + """Get the session id list in the workspace""" sessionIds(docId: String, options: QueryChatSessionsInput): [String!]! @deprecated(reason: "Use `sessions` instead") @@ -318,6 +321,8 @@ type CopilotQuota { type CopilotSessionType { id: ID! + model: String! + optionalModels: [String!]! parentSessionId: ID promptName: String! } diff --git a/packages/common/graphql/src/graphql/copilot-session-get.gql b/packages/common/graphql/src/graphql/copilot-session-get.gql new file mode 100644 index 000000000..b3238de93 --- /dev/null +++ b/packages/common/graphql/src/graphql/copilot-session-get.gql @@ -0,0 +1,16 @@ +query getCopilotSession( + $workspaceId: String! + $sessionId: String! +) { + currentUser { + copilot(workspaceId: $workspaceId) { + session(sessionId: $sessionId) { + id + parentSessionId + promptName + model + optionalModels + } + } + } +} diff --git a/packages/common/graphql/src/graphql/copilot-sessions-get.gql b/packages/common/graphql/src/graphql/copilot-sessions-get.gql index dd208e4ec..a84bef035 100644 --- a/packages/common/graphql/src/graphql/copilot-sessions-get.gql +++ b/packages/common/graphql/src/graphql/copilot-sessions-get.gql @@ -9,6 +9,8 @@ query getCopilotSessions( id parentSessionId promptName + model + optionalModels } } } diff --git a/packages/common/graphql/src/graphql/index.ts b/packages/common/graphql/src/graphql/index.ts index 6a0ade5ea..6dcf0ee13 100644 --- a/packages/common/graphql/src/graphql/index.ts +++ b/packages/common/graphql/src/graphql/index.ts @@ -740,6 +740,24 @@ export const forkCopilotSessionMutation = { }`, }; +export const getCopilotSessionQuery = { + id: 'getCopilotSessionQuery' as const, + op: 'getCopilotSession', + query: `query getCopilotSession($workspaceId: String!, $sessionId: String!) { + currentUser { + copilot(workspaceId: $workspaceId) { + session(sessionId: $sessionId) { + id + parentSessionId + promptName + model + optionalModels + } + } + } +}`, +}; + export const updateCopilotSessionMutation = { id: 'updateCopilotSessionMutation' as const, op: 'updateCopilotSession', @@ -758,6 +776,8 @@ export const getCopilotSessionsQuery = { id parentSessionId promptName + model + optionalModels } } } diff --git a/packages/common/graphql/src/schema.ts b/packages/common/graphql/src/schema.ts index 8dbb81b42..45e6abd88 100644 --- a/packages/common/graphql/src/schema.ts +++ b/packages/common/graphql/src/schema.ts @@ -174,6 +174,8 @@ export interface Copilot { histories: Array; /** Get the quota of the user in the workspace */ quota: CopilotQuota; + /** Get the session by id */ + session: CopilotSessionType; /** * Get the session id list in the workspace * @deprecated Use `sessions` instead @@ -199,6 +201,10 @@ export interface CopilotHistoriesArgs { options?: InputMaybe; } +export interface CopilotSessionArgs { + sessionId: Scalars['String']['input']; +} + export interface CopilotSessionIdsArgs { docId?: InputMaybe; options?: InputMaybe; @@ -415,6 +421,8 @@ export interface CopilotQuota { export interface CopilotSessionType { __typename?: 'CopilotSessionType'; id: Scalars['ID']['output']; + model: Scalars['String']['output']; + optionalModels: Array; parentSessionId: Maybe; promptName: Scalars['String']['output']; } @@ -3453,6 +3461,29 @@ export type ForkCopilotSessionMutation = { forkCopilotSession: string; }; +export type GetCopilotSessionQueryVariables = Exact<{ + workspaceId: Scalars['String']['input']; + sessionId: Scalars['String']['input']; +}>; + +export type GetCopilotSessionQuery = { + __typename?: 'Query'; + currentUser: { + __typename?: 'UserType'; + copilot: { + __typename?: 'Copilot'; + session: { + __typename?: 'CopilotSessionType'; + id: string; + parentSessionId: string | null; + promptName: string; + model: string; + optionalModels: Array; + }; + }; + } | null; +}; + export type UpdateCopilotSessionMutationVariables = Exact<{ options: UpdateChatSessionInput; }>; @@ -3479,6 +3510,8 @@ export type GetCopilotSessionsQuery = { id: string; parentSessionId: string | null; promptName: string; + model: string; + optionalModels: Array; }>; }; } | null; @@ -4997,6 +5030,11 @@ export type Queries = variables: CopilotQuotaQueryVariables; response: CopilotQuotaQuery; } + | { + name: 'getCopilotSessionQuery'; + variables: GetCopilotSessionQueryVariables; + response: GetCopilotSessionQuery; + } | { name: 'getCopilotSessionsQuery'; variables: GetCopilotSessionsQueryVariables; diff --git a/packages/frontend/core/src/blocksuite/ai/actions/types.ts b/packages/frontend/core/src/blocksuite/ai/actions/types.ts index 38d54c93d..c4b5908be 100644 --- a/packages/frontend/core/src/blocksuite/ai/actions/types.ts +++ b/packages/frontend/core/src/blocksuite/ai/actions/types.ts @@ -380,6 +380,10 @@ declare global { docId?: string, options?: { action?: boolean } ) => Promise; + getSession: ( + workspaceId: string, + sessionId: string + ) => Promise; updateSession: (sessionId: string, promptName: string) => Promise; } diff --git a/packages/frontend/core/src/blocksuite/ai/provider/copilot-client.ts b/packages/frontend/core/src/blocksuite/ai/provider/copilot-client.ts index 05403d2c0..61f779818 100644 --- a/packages/frontend/core/src/blocksuite/ai/provider/copilot-client.ts +++ b/packages/frontend/core/src/blocksuite/ai/provider/copilot-client.ts @@ -11,6 +11,7 @@ import { forkCopilotSessionMutation, getCopilotHistoriesQuery, getCopilotHistoryIdsQuery, + getCopilotSessionQuery, getCopilotSessionsQuery, type GraphQLQuery, listContextObjectQuery, @@ -136,6 +137,18 @@ export class CopilotClient { } } + async getSession(workspaceId: string, sessionId: string) { + try { + const res = await this.gql({ + query: getCopilotSessionQuery, + variables: { sessionId, workspaceId }, + }); + return res.currentUser?.copilot?.session; + } catch (err) { + throw resolveError(err); + } + } + async getSessions( workspaceId: string, docId?: string, diff --git a/packages/frontend/core/src/blocksuite/ai/provider/setup-provider.tsx b/packages/frontend/core/src/blocksuite/ai/provider/setup-provider.tsx index dd02fddbf..ea247219c 100644 --- a/packages/frontend/core/src/blocksuite/ai/provider/setup-provider.tsx +++ b/packages/frontend/core/src/blocksuite/ai/provider/setup-provider.tsx @@ -579,6 +579,9 @@ Could you make a new website based on these notes and send back just the html fi AIProvider.provide('session', { createSession, + getSession: async (workspaceId: string, sessionId: string) => { + return client.getSession(workspaceId, sessionId); + }, getSessions: async ( workspaceId: string, docId?: string,