feat(server): add pinned & action filter for session query (#12876)

fix AI-222
This commit is contained in:
DarkSky
2025-06-20 16:31:04 +08:00
committed by GitHub
parent fb250c6374
commit c7113b0195
7 changed files with 115 additions and 151 deletions
@@ -33,7 +33,7 @@ import { CurrentUser } from '../../core/auth';
import { Admin } from '../../core/common';
import { AccessController } from '../../core/permission';
import { UserType } from '../../core/user';
import type { UpdateChatSession } from '../../models';
import type { ListSessionOptions, UpdateChatSession } from '../../models';
import { PromptService } from './prompt';
import { PromptMessage, StreamObject } from './providers';
import { ChatSessionService } from './session';
@@ -43,7 +43,6 @@ import {
type ChatHistory,
type ChatMessage,
type ChatSessionState,
type ListHistoriesOptions,
SubmittedMessage,
} from './types';
@@ -151,25 +150,28 @@ enum ChatHistoryOrder {
registerEnumType(ChatHistoryOrder, { name: 'ChatHistoryOrder' });
@InputType()
class QueryChatSessionsInput {
@Field(() => Boolean, { nullable: true })
action: boolean | undefined;
}
@InputType()
class QueryChatHistoriesInput implements Partial<ListHistoriesOptions> {
class QueryChatSessionsInput implements Partial<ListSessionOptions> {
@Field(() => Boolean, { nullable: true })
action: boolean | undefined;
@Field(() => Boolean, { nullable: true })
fork: boolean | undefined;
@Field(() => Boolean, { nullable: true })
pinned: boolean | undefined;
@Field(() => Number, { nullable: true })
limit: number | undefined;
@Field(() => Number, { nullable: true })
skip: number | undefined;
}
@InputType()
class QueryChatHistoriesInput
extends QueryChatSessionsInput
implements Partial<ListSessionOptions>
{
@Field(() => ChatHistoryOrder, { nullable: true })
messageOrder: 'asc' | 'desc' | undefined;
@@ -370,20 +372,6 @@ export class CopilotResolver {
return await this.chatSession.getQuota(user.id);
}
@ResolveField(() => [String], {
description: 'Get the session id list in the workspace',
complexity: 2,
deprecationReason: 'Use `sessions` instead',
})
async sessionIds(
@Parent() copilot: CopilotType,
@CurrentUser() user: CurrentUser,
@Args('docId', { nullable: true }) docId?: string,
@Args('options', { nullable: true }) options?: QueryChatSessionsInput
): Promise<string[]> {
return (await this.sessions(copilot, user, docId, options)).map(s => s.id);
}
@ResolveField(() => CopilotSessionType, {
description: 'Get the session by id',
complexity: 2,
@@ -426,12 +414,15 @@ export class CopilotResolver {
.workspace(copilot.workspaceId)
.allowLocal()
.assert('Workspace.Copilot');
const sessions = await this.chatSession.listSessions(
user.id,
copilot.workspaceId,
docId,
options
Object.assign({}, options, {
userId: user.id,
workspaceId: copilot.workspaceId,
docId,
})
);
return sessions.map(this.transformToSessionType);
}
@@ -461,10 +452,7 @@ export class CopilotResolver {
}
const histories = await this.chatSession.listHistories(
user.id,
workspaceId,
docId,
options
Object.assign({}, options, { userId: user.id, workspaceId, docId })
);
return histories.map(h => ({
@@ -14,6 +14,7 @@ import {
} from '../../base';
import { QuotaService } from '../../core/quota';
import {
ListSessionOptions,
Models,
type UpdateChatSession,
UpdateChatSessionData,
@@ -29,7 +30,6 @@ import {
type ChatSessionOptions,
type ChatSessionState,
getTokenEncoder,
type ListHistoriesOptions,
type SubmittedMessage,
} from './types';
@@ -314,65 +314,38 @@ export class ChatSessionService {
}
async listSessions(
userId: string,
workspaceId: string,
docId?: string,
options?: { action?: boolean }
options: ListSessionOptions
): Promise<Omit<ChatSessionState, 'messages'>[]> {
return await this.db.aiSession
.findMany({
where: {
userId,
workspaceId,
docId,
prompt: {
action: options?.action ? { not: null } : null,
},
deletedAt: null,
},
select: {
id: true,
userId: true,
workspaceId: true,
docId: true,
pinned: true,
parentSessionId: true,
promptName: true,
},
})
.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 });
const sessions = await this.models.copilotSession.list({
...options,
withMessages: false,
});
return {
sessionId: session.id,
userId: session.userId,
workspaceId: session.workspaceId,
docId: session.docId,
pinned: session.pinned,
parentSessionId: session.parentSessionId,
prompt,
};
})
);
});
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,
pinned: session.pinned,
parentSessionId: session.parentSessionId,
prompt,
};
})
);
}
async listHistories(
userId: string,
workspaceId?: string,
docId?: string,
options?: ListHistoriesOptions
): Promise<ChatHistory[]> {
const sessions = await this.models.copilotSession.list(
userId,
workspaceId,
docId,
options
);
async listHistories(options: ListSessionOptions): Promise<ChatHistory[]> {
const { userId } = options;
const sessions = await this.models.copilotSession.list({
...options,
withMessages: true,
});
const histories = await Promise.all(
sessions.map(
async ({
@@ -127,17 +127,6 @@ export interface ChatSessionState
messages: ChatMessage[];
}
export type ListHistoriesOptions = {
action: boolean | undefined;
fork: boolean | undefined;
limit: number | undefined;
skip: number | undefined;
sessionOrder: 'asc' | 'desc' | undefined;
messageOrder: 'asc' | 'desc' | undefined;
sessionId: string | undefined;
withPrompt: boolean | undefined;
};
export type CopilotContextFile = {
id: string; // fileId
created_at: number;