feat(core): add get session graphql api (#12237)

Close [AI-116](https://linear.app/affine-design/issue/AI-116)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## 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.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
akumatus
2025-05-15 04:55:50 +00:00
parent 6052743671
commit fcc9b31da9
10 changed files with 173 additions and 20 deletions

View File

@@ -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<CopilotSessionType> {
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<CopilotSessionType[]> {
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<ChatSessionState, 'messages'>
): CopilotSessionType {
return {
id: session.sessionId,
parentSessionId: session.parentSessionId,
promptName: session.prompt.name,
model: session.prompt.model,
optionalModels: session.prompt.optionalModels,
};
}
}
@Throttle()

View File

@@ -307,9 +307,7 @@ export class ChatSessionService {
});
}
private async getSession(
sessionId: string
): Promise<ChatSessionState | undefined> {
async getSession(sessionId: string): Promise<ChatSessionState | undefined> {
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<Omit<ChatSessionState, 'messages'>[]> {
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(

View File

@@ -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!
}