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:
@@ -41,6 +41,7 @@ import {
|
|||||||
AvailableModels,
|
AvailableModels,
|
||||||
type ChatHistory,
|
type ChatHistory,
|
||||||
type ChatMessage,
|
type ChatMessage,
|
||||||
|
type ChatSessionState,
|
||||||
type ListHistoriesOptions,
|
type ListHistoriesOptions,
|
||||||
SubmittedMessage,
|
SubmittedMessage,
|
||||||
} from './types';
|
} from './types';
|
||||||
@@ -277,7 +278,7 @@ class CopilotPromptType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
class CopilotSessionType {
|
export class CopilotSessionType {
|
||||||
@Field(() => ID)
|
@Field(() => ID)
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
@@ -286,6 +287,12 @@ class CopilotSessionType {
|
|||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
promptName!: string;
|
promptName!: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
model!: string;
|
||||||
|
|
||||||
|
@Field(() => [String])
|
||||||
|
optionalModels!: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================== Resolver ==================
|
// ================== Resolver ==================
|
||||||
@@ -329,6 +336,30 @@ export class CopilotResolver {
|
|||||||
return (await this.sessions(copilot, user, docId, options)).map(s => s.id);
|
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], {
|
@ResolveField(() => [CopilotSessionType], {
|
||||||
description: 'Get the session list in the workspace',
|
description: 'Get the session list in the workspace',
|
||||||
complexity: 2,
|
complexity: 2,
|
||||||
@@ -339,18 +370,21 @@ export class CopilotResolver {
|
|||||||
@Args('docId', { nullable: true }) docId?: string,
|
@Args('docId', { nullable: true }) docId?: string,
|
||||||
@Args('options', { nullable: true }) options?: QueryChatSessionsInput
|
@Args('options', { nullable: true }) options?: QueryChatSessionsInput
|
||||||
): Promise<CopilotSessionType[]> {
|
): Promise<CopilotSessionType[]> {
|
||||||
if (!copilot.workspaceId) return [];
|
if (!copilot.workspaceId) {
|
||||||
|
throw new NotFoundException('Workspace not found');
|
||||||
|
}
|
||||||
await this.ac
|
await this.ac
|
||||||
.user(user.id)
|
.user(user.id)
|
||||||
.workspace(copilot.workspaceId)
|
.workspace(copilot.workspaceId)
|
||||||
.allowLocal()
|
.allowLocal()
|
||||||
.assert('Workspace.Copilot');
|
.assert('Workspace.Copilot');
|
||||||
return await this.chatSession.listSessions(
|
const sessions = await this.chatSession.listSessions(
|
||||||
user.id,
|
user.id,
|
||||||
copilot.workspaceId,
|
copilot.workspaceId,
|
||||||
docId,
|
docId,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
return sessions.map(this.transformToSessionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResolveField(() => [CopilotHistoriesType], {})
|
@ResolveField(() => [CopilotHistoriesType], {})
|
||||||
@@ -556,6 +590,18 @@ export class CopilotResolver {
|
|||||||
throw new CopilotFailedToCreateMessage(e.message);
|
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()
|
@Throttle()
|
||||||
|
|||||||
@@ -307,9 +307,7 @@ export class ChatSessionService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getSession(
|
async getSession(sessionId: string): Promise<ChatSessionState | undefined> {
|
||||||
sessionId: string
|
|
||||||
): Promise<ChatSessionState | undefined> {
|
|
||||||
return await this.db.aiSession
|
return await this.db.aiSession
|
||||||
.findUnique({
|
.findUnique({
|
||||||
where: { id: sessionId, deletedAt: null },
|
where: { id: sessionId, deletedAt: null },
|
||||||
@@ -414,13 +412,7 @@ export class ChatSessionService {
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
docId?: string,
|
docId?: string,
|
||||||
options?: { action?: boolean }
|
options?: { action?: boolean }
|
||||||
): Promise<
|
): Promise<Omit<ChatSessionState, 'messages'>[]> {
|
||||||
Array<{
|
|
||||||
id: string;
|
|
||||||
parentSessionId: string | null;
|
|
||||||
promptName: string;
|
|
||||||
}>
|
|
||||||
> {
|
|
||||||
return await this.db.aiSession
|
return await this.db.aiSession
|
||||||
.findMany({
|
.findMany({
|
||||||
where: {
|
where: {
|
||||||
@@ -434,17 +426,31 @@ export class ChatSessionService {
|
|||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
userId: true,
|
||||||
|
workspaceId: true,
|
||||||
|
docId: true,
|
||||||
parentSessionId: true,
|
parentSessionId: true,
|
||||||
promptName: true,
|
promptName: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(sessions =>
|
.then(sessions => {
|
||||||
sessions.map(({ id, parentSessionId, promptName }) => ({
|
return Promise.all(
|
||||||
id,
|
sessions.map(async session => {
|
||||||
parentSessionId: parentSessionId || null,
|
const prompt = await this.prompt.get(session.promptName);
|
||||||
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(
|
async listHistories(
|
||||||
|
|||||||
@@ -133,6 +133,9 @@ type Copilot {
|
|||||||
"""Get the quota of the user in the workspace"""
|
"""Get the quota of the user in the workspace"""
|
||||||
quota: CopilotQuota!
|
quota: CopilotQuota!
|
||||||
|
|
||||||
|
"""Get the session by id"""
|
||||||
|
session(sessionId: String!): CopilotSessionType!
|
||||||
|
|
||||||
"""Get the session id list in the workspace"""
|
"""Get the session id list in the workspace"""
|
||||||
sessionIds(docId: String, options: QueryChatSessionsInput): [String!]! @deprecated(reason: "Use `sessions` instead")
|
sessionIds(docId: String, options: QueryChatSessionsInput): [String!]! @deprecated(reason: "Use `sessions` instead")
|
||||||
|
|
||||||
@@ -318,6 +321,8 @@ type CopilotQuota {
|
|||||||
|
|
||||||
type CopilotSessionType {
|
type CopilotSessionType {
|
||||||
id: ID!
|
id: ID!
|
||||||
|
model: String!
|
||||||
|
optionalModels: [String!]!
|
||||||
parentSessionId: ID
|
parentSessionId: ID
|
||||||
promptName: String!
|
promptName: String!
|
||||||
}
|
}
|
||||||
|
|||||||
16
packages/common/graphql/src/graphql/copilot-session-get.gql
Normal file
16
packages/common/graphql/src/graphql/copilot-session-get.gql
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
query getCopilotSession(
|
||||||
|
$workspaceId: String!
|
||||||
|
$sessionId: String!
|
||||||
|
) {
|
||||||
|
currentUser {
|
||||||
|
copilot(workspaceId: $workspaceId) {
|
||||||
|
session(sessionId: $sessionId) {
|
||||||
|
id
|
||||||
|
parentSessionId
|
||||||
|
promptName
|
||||||
|
model
|
||||||
|
optionalModels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ query getCopilotSessions(
|
|||||||
id
|
id
|
||||||
parentSessionId
|
parentSessionId
|
||||||
promptName
|
promptName
|
||||||
|
model
|
||||||
|
optionalModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = {
|
export const updateCopilotSessionMutation = {
|
||||||
id: 'updateCopilotSessionMutation' as const,
|
id: 'updateCopilotSessionMutation' as const,
|
||||||
op: 'updateCopilotSession',
|
op: 'updateCopilotSession',
|
||||||
@@ -758,6 +776,8 @@ export const getCopilotSessionsQuery = {
|
|||||||
id
|
id
|
||||||
parentSessionId
|
parentSessionId
|
||||||
promptName
|
promptName
|
||||||
|
model
|
||||||
|
optionalModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,6 +174,8 @@ export interface Copilot {
|
|||||||
histories: Array<CopilotHistories>;
|
histories: Array<CopilotHistories>;
|
||||||
/** Get the quota of the user in the workspace */
|
/** Get the quota of the user in the workspace */
|
||||||
quota: CopilotQuota;
|
quota: CopilotQuota;
|
||||||
|
/** Get the session by id */
|
||||||
|
session: CopilotSessionType;
|
||||||
/**
|
/**
|
||||||
* Get the session id list in the workspace
|
* Get the session id list in the workspace
|
||||||
* @deprecated Use `sessions` instead
|
* @deprecated Use `sessions` instead
|
||||||
@@ -199,6 +201,10 @@ export interface CopilotHistoriesArgs {
|
|||||||
options?: InputMaybe<QueryChatHistoriesInput>;
|
options?: InputMaybe<QueryChatHistoriesInput>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CopilotSessionArgs {
|
||||||
|
sessionId: Scalars['String']['input'];
|
||||||
|
}
|
||||||
|
|
||||||
export interface CopilotSessionIdsArgs {
|
export interface CopilotSessionIdsArgs {
|
||||||
docId?: InputMaybe<Scalars['String']['input']>;
|
docId?: InputMaybe<Scalars['String']['input']>;
|
||||||
options?: InputMaybe<QueryChatSessionsInput>;
|
options?: InputMaybe<QueryChatSessionsInput>;
|
||||||
@@ -415,6 +421,8 @@ export interface CopilotQuota {
|
|||||||
export interface CopilotSessionType {
|
export interface CopilotSessionType {
|
||||||
__typename?: 'CopilotSessionType';
|
__typename?: 'CopilotSessionType';
|
||||||
id: Scalars['ID']['output'];
|
id: Scalars['ID']['output'];
|
||||||
|
model: Scalars['String']['output'];
|
||||||
|
optionalModels: Array<Scalars['String']['output']>;
|
||||||
parentSessionId: Maybe<Scalars['ID']['output']>;
|
parentSessionId: Maybe<Scalars['ID']['output']>;
|
||||||
promptName: Scalars['String']['output'];
|
promptName: Scalars['String']['output'];
|
||||||
}
|
}
|
||||||
@@ -3453,6 +3461,29 @@ export type ForkCopilotSessionMutation = {
|
|||||||
forkCopilotSession: string;
|
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<string>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type UpdateCopilotSessionMutationVariables = Exact<{
|
export type UpdateCopilotSessionMutationVariables = Exact<{
|
||||||
options: UpdateChatSessionInput;
|
options: UpdateChatSessionInput;
|
||||||
}>;
|
}>;
|
||||||
@@ -3479,6 +3510,8 @@ export type GetCopilotSessionsQuery = {
|
|||||||
id: string;
|
id: string;
|
||||||
parentSessionId: string | null;
|
parentSessionId: string | null;
|
||||||
promptName: string;
|
promptName: string;
|
||||||
|
model: string;
|
||||||
|
optionalModels: Array<string>;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
} | null;
|
} | null;
|
||||||
@@ -4997,6 +5030,11 @@ export type Queries =
|
|||||||
variables: CopilotQuotaQueryVariables;
|
variables: CopilotQuotaQueryVariables;
|
||||||
response: CopilotQuotaQuery;
|
response: CopilotQuotaQuery;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
name: 'getCopilotSessionQuery';
|
||||||
|
variables: GetCopilotSessionQueryVariables;
|
||||||
|
response: GetCopilotSessionQuery;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
name: 'getCopilotSessionsQuery';
|
name: 'getCopilotSessionsQuery';
|
||||||
variables: GetCopilotSessionsQueryVariables;
|
variables: GetCopilotSessionsQueryVariables;
|
||||||
|
|||||||
@@ -380,6 +380,10 @@ declare global {
|
|||||||
docId?: string,
|
docId?: string,
|
||||||
options?: { action?: boolean }
|
options?: { action?: boolean }
|
||||||
) => Promise<CopilotSessionType[] | undefined>;
|
) => Promise<CopilotSessionType[] | undefined>;
|
||||||
|
getSession: (
|
||||||
|
workspaceId: string,
|
||||||
|
sessionId: string
|
||||||
|
) => Promise<CopilotSessionType | undefined>;
|
||||||
updateSession: (sessionId: string, promptName: string) => Promise<string>;
|
updateSession: (sessionId: string, promptName: string) => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
forkCopilotSessionMutation,
|
forkCopilotSessionMutation,
|
||||||
getCopilotHistoriesQuery,
|
getCopilotHistoriesQuery,
|
||||||
getCopilotHistoryIdsQuery,
|
getCopilotHistoryIdsQuery,
|
||||||
|
getCopilotSessionQuery,
|
||||||
getCopilotSessionsQuery,
|
getCopilotSessionsQuery,
|
||||||
type GraphQLQuery,
|
type GraphQLQuery,
|
||||||
listContextObjectQuery,
|
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(
|
async getSessions(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
docId?: string,
|
docId?: string,
|
||||||
|
|||||||
@@ -579,6 +579,9 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
|
|
||||||
AIProvider.provide('session', {
|
AIProvider.provide('session', {
|
||||||
createSession,
|
createSession,
|
||||||
|
getSession: async (workspaceId: string, sessionId: string) => {
|
||||||
|
return client.getSession(workspaceId, sessionId);
|
||||||
|
},
|
||||||
getSessions: async (
|
getSessions: async (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
docId?: string,
|
docId?: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user