Skip to content

Commit c7113b0

Browse files
authored
feat(server): add pinned & action filter for session query (#12876)
fix AI-222
1 parent fb250c6 commit c7113b0

File tree

7 files changed

+114
-150
lines changed

7 files changed

+114
-150
lines changed

packages/backend/server/src/__tests__/models/copilot-session.spec.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,10 @@ test('should list and filter session type', async t => {
110110

111111
// should list sessions
112112
{
113-
const workspaceSessions = await copilotSession.list(user.id, workspace.id);
113+
const workspaceSessions = await copilotSession.list({
114+
userId: user.id,
115+
workspaceId: workspace.id,
116+
});
114117

115118
t.snapshot(
116119
workspaceSessions.map(s => ({ docId: s.docId, pinned: s.pinned })),
@@ -119,16 +122,19 @@ test('should list and filter session type', async t => {
119122
}
120123

121124
{
122-
const docSessions = await copilotSession.list(user.id, workspace.id, docId);
125+
const docSessions = await copilotSession.list({
126+
userId: user.id,
127+
workspaceId: workspace.id,
128+
docId,
129+
});
123130

124131
t.snapshot(
125-
cleanObject(docSessions, [
126-
'id',
127-
'userId',
128-
'createdAt',
129-
'messages',
130-
'tokenCost',
131-
]),
132+
cleanObject(
133+
docSessions.toSorted(s =>
134+
s.docId!.localeCompare(s.docId!, undefined, { numeric: true })
135+
),
136+
['id', 'userId', 'workspaceId', 'createdAt', 'tokenCost']
137+
),
132138
'doc sessions should only include sessions with matching docId'
133139
);
134140
}

packages/backend/server/src/models/copilot-session.ts

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,20 @@ export type UpdateChatSession = Pick<ChatSession, 'userId' | 'sessionId'> &
5858
UpdateChatSessionData;
5959

6060
export type ListSessionOptions = {
61-
sessionId: string | undefined;
62-
action: boolean | undefined;
63-
fork: boolean | undefined;
64-
limit: number | undefined;
65-
skip: number | undefined;
66-
sessionOrder: 'asc' | 'desc' | undefined;
67-
messageOrder: 'asc' | 'desc' | undefined;
61+
userId: string;
62+
sessionId?: string;
63+
workspaceId?: string;
64+
docId?: string;
65+
action?: boolean;
66+
fork?: boolean;
67+
limit?: number;
68+
skip?: number;
69+
sessionOrder?: 'asc' | 'desc';
70+
messageOrder?: 'asc' | 'desc';
71+
72+
// extra condition
73+
withPrompt?: boolean;
74+
withMessages?: boolean;
6875
};
6976

7077
@Injectable()
@@ -197,12 +204,9 @@ export class CopilotSessionModel extends BaseModel {
197204
});
198205
}
199206

200-
async list(
201-
userId: string,
202-
workspaceId?: string,
203-
docId?: string,
204-
options?: ListSessionOptions
205-
) {
207+
async list(options: ListSessionOptions) {
208+
const { userId, sessionId, workspaceId, docId } = options;
209+
206210
const extraCondition = [];
207211

208212
if (!options?.action && options?.fork) {
@@ -211,7 +215,10 @@ export class CopilotSessionModel extends BaseModel {
211215
userId: { not: userId },
212216
workspaceId: workspaceId,
213217
docId: docId ?? null,
214-
id: options?.sessionId ? { equals: options.sessionId } : undefined,
218+
id: sessionId ? { equals: sessionId } : undefined,
219+
prompt: {
220+
action: options.action ? { not: null } : null,
221+
},
215222
// should only find forked session
216223
parentSessionId: { not: null },
217224
deletedAt: null,
@@ -223,9 +230,9 @@ export class CopilotSessionModel extends BaseModel {
223230
OR: [
224231
{
225232
userId,
226-
workspaceId: workspaceId,
233+
workspaceId,
227234
docId: docId ?? null,
228-
id: options?.sessionId ? { equals: options.sessionId } : undefined,
235+
id: sessionId ? { equals: sessionId } : undefined,
229236
deletedAt: null,
230237
},
231238
...extraCondition,
@@ -234,26 +241,30 @@ export class CopilotSessionModel extends BaseModel {
234241
select: {
235242
id: true,
236243
userId: true,
244+
workspaceId: true,
237245
docId: true,
246+
parentSessionId: true,
238247
pinned: true,
239248
promptName: true,
240249
tokenCost: true,
241250
createdAt: true,
242-
messages: {
243-
select: {
244-
id: true,
245-
role: true,
246-
content: true,
247-
attachments: true,
248-
params: true,
249-
streamObjects: true,
250-
createdAt: true,
251-
},
252-
orderBy: {
253-
// message order is asc by default
254-
createdAt: options?.messageOrder === 'desc' ? 'desc' : 'asc',
255-
},
256-
},
251+
messages: options.withMessages
252+
? {
253+
select: {
254+
id: true,
255+
role: true,
256+
content: true,
257+
attachments: true,
258+
params: true,
259+
streamObjects: true,
260+
createdAt: true,
261+
},
262+
orderBy: {
263+
// message order is asc by default
264+
createdAt: options?.messageOrder === 'desc' ? 'desc' : 'asc',
265+
},
266+
}
267+
: false,
257268
},
258269
take: options?.limit,
259270
skip: options?.skip,

packages/backend/server/src/plugins/copilot/resolver.ts

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { CurrentUser } from '../../core/auth';
3333
import { Admin } from '../../core/common';
3434
import { AccessController } from '../../core/permission';
3535
import { UserType } from '../../core/user';
36-
import type { UpdateChatSession } from '../../models';
36+
import type { ListSessionOptions, UpdateChatSession } from '../../models';
3737
import { PromptService } from './prompt';
3838
import { PromptMessage, StreamObject } from './providers';
3939
import { ChatSessionService } from './session';
@@ -43,7 +43,6 @@ import {
4343
type ChatHistory,
4444
type ChatMessage,
4545
type ChatSessionState,
46-
type ListHistoriesOptions,
4746
SubmittedMessage,
4847
} from './types';
4948

@@ -151,25 +150,28 @@ enum ChatHistoryOrder {
151150
registerEnumType(ChatHistoryOrder, { name: 'ChatHistoryOrder' });
152151

153152
@InputType()
154-
class QueryChatSessionsInput {
153+
class QueryChatSessionsInput implements Partial<ListSessionOptions> {
155154
@Field(() => Boolean, { nullable: true })
156155
action: boolean | undefined;
157-
}
158156

159-
@InputType()
160-
class QueryChatHistoriesInput implements Partial<ListHistoriesOptions> {
161157
@Field(() => Boolean, { nullable: true })
162-
action: boolean | undefined;
158+
fork: boolean | undefined;
163159

164160
@Field(() => Boolean, { nullable: true })
165-
fork: boolean | undefined;
161+
pinned: boolean | undefined;
166162

167163
@Field(() => Number, { nullable: true })
168164
limit: number | undefined;
169165

170166
@Field(() => Number, { nullable: true })
171167
skip: number | undefined;
168+
}
172169

170+
@InputType()
171+
class QueryChatHistoriesInput
172+
extends QueryChatSessionsInput
173+
implements Partial<ListSessionOptions>
174+
{
173175
@Field(() => ChatHistoryOrder, { nullable: true })
174176
messageOrder: 'asc' | 'desc' | undefined;
175177

@@ -370,20 +372,6 @@ export class CopilotResolver {
370372
return await this.chatSession.getQuota(user.id);
371373
}
372374

373-
@ResolveField(() => [String], {
374-
description: 'Get the session id list in the workspace',
375-
complexity: 2,
376-
deprecationReason: 'Use `sessions` instead',
377-
})
378-
async sessionIds(
379-
@Parent() copilot: CopilotType,
380-
@CurrentUser() user: CurrentUser,
381-
@Args('docId', { nullable: true }) docId?: string,
382-
@Args('options', { nullable: true }) options?: QueryChatSessionsInput
383-
): Promise<string[]> {
384-
return (await this.sessions(copilot, user, docId, options)).map(s => s.id);
385-
}
386-
387375
@ResolveField(() => CopilotSessionType, {
388376
description: 'Get the session by id',
389377
complexity: 2,
@@ -426,12 +414,15 @@ export class CopilotResolver {
426414
.workspace(copilot.workspaceId)
427415
.allowLocal()
428416
.assert('Workspace.Copilot');
417+
429418
const sessions = await this.chatSession.listSessions(
430-
user.id,
431-
copilot.workspaceId,
432-
docId,
433-
options
419+
Object.assign({}, options, {
420+
userId: user.id,
421+
workspaceId: copilot.workspaceId,
422+
docId,
423+
})
434424
);
425+
435426
return sessions.map(this.transformToSessionType);
436427
}
437428

@@ -461,10 +452,7 @@ export class CopilotResolver {
461452
}
462453

463454
const histories = await this.chatSession.listHistories(
464-
user.id,
465-
workspaceId,
466-
docId,
467-
options
455+
Object.assign({}, options, { userId: user.id, workspaceId, docId })
468456
);
469457

470458
return histories.map(h => ({

packages/backend/server/src/plugins/copilot/session.ts

Lines changed: 29 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from '../../base';
1515
import { QuotaService } from '../../core/quota';
1616
import {
17+
ListSessionOptions,
1718
Models,
1819
type UpdateChatSession,
1920
UpdateChatSessionData,
@@ -29,7 +30,6 @@ import {
2930
type ChatSessionOptions,
3031
type ChatSessionState,
3132
getTokenEncoder,
32-
type ListHistoriesOptions,
3333
type SubmittedMessage,
3434
} from './types';
3535

@@ -314,65 +314,38 @@ export class ChatSessionService {
314314
}
315315

316316
async listSessions(
317-
userId: string,
318-
workspaceId: string,
319-
docId?: string,
320-
options?: { action?: boolean }
317+
options: ListSessionOptions
321318
): Promise<Omit<ChatSessionState, 'messages'>[]> {
322-
return await this.db.aiSession
323-
.findMany({
324-
where: {
325-
userId,
326-
workspaceId,
327-
docId,
328-
prompt: {
329-
action: options?.action ? { not: null } : null,
330-
},
331-
deletedAt: null,
332-
},
333-
select: {
334-
id: true,
335-
userId: true,
336-
workspaceId: true,
337-
docId: true,
338-
pinned: true,
339-
parentSessionId: true,
340-
promptName: true,
341-
},
319+
const sessions = await this.models.copilotSession.list({
320+
...options,
321+
withMessages: false,
322+
});
323+
324+
return Promise.all(
325+
sessions.map(async session => {
326+
const prompt = await this.prompt.get(session.promptName);
327+
if (!prompt)
328+
throw new CopilotPromptNotFound({ name: session.promptName });
329+
330+
return {
331+
sessionId: session.id,
332+
userId: session.userId,
333+
workspaceId: session.workspaceId,
334+
docId: session.docId,
335+
pinned: session.pinned,
336+
parentSessionId: session.parentSessionId,
337+
prompt,
338+
};
342339
})
343-
.then(sessions => {
344-
return Promise.all(
345-
sessions.map(async session => {
346-
const prompt = await this.prompt.get(session.promptName);
347-
if (!prompt)
348-
throw new CopilotPromptNotFound({ name: session.promptName });
349-
350-
return {
351-
sessionId: session.id,
352-
userId: session.userId,
353-
workspaceId: session.workspaceId,
354-
docId: session.docId,
355-
pinned: session.pinned,
356-
parentSessionId: session.parentSessionId,
357-
prompt,
358-
};
359-
})
360-
);
361-
});
340+
);
362341
}
363342

364-
async listHistories(
365-
userId: string,
366-
workspaceId?: string,
367-
docId?: string,
368-
options?: ListHistoriesOptions
369-
): Promise<ChatHistory[]> {
370-
const sessions = await this.models.copilotSession.list(
371-
userId,
372-
workspaceId,
373-
docId,
374-
options
375-
);
343+
async listHistories(options: ListSessionOptions): Promise<ChatHistory[]> {
344+
const { userId } = options;
345+
const sessions = await this.models.copilotSession.list({
346+
...options,
347+
withMessages: true,
348+
});
376349
const histories = await Promise.all(
377350
sessions.map(
378351
async ({

packages/backend/server/src/plugins/copilot/types.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,6 @@ export interface ChatSessionState
127127
messages: ChatMessage[];
128128
}
129129

130-
export type ListHistoriesOptions = {
131-
action: boolean | undefined;
132-
fork: boolean | undefined;
133-
limit: number | undefined;
134-
skip: number | undefined;
135-
sessionOrder: 'asc' | 'desc' | undefined;
136-
messageOrder: 'asc' | 'desc' | undefined;
137-
sessionId: string | undefined;
138-
withPrompt: boolean | undefined;
139-
};
140-
141130
export type CopilotContextFile = {
142131
id: string; // fileId
143132
created_at: number;

0 commit comments

Comments
 (0)