feat(server): add pinned & action filter for session query (#12876)
fix AI-222
This commit is contained in:
@@ -110,7 +110,10 @@ test('should list and filter session type', async t => {
|
|||||||
|
|
||||||
// should list sessions
|
// should list sessions
|
||||||
{
|
{
|
||||||
const workspaceSessions = await copilotSession.list(user.id, workspace.id);
|
const workspaceSessions = await copilotSession.list({
|
||||||
|
userId: user.id,
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
});
|
||||||
|
|
||||||
t.snapshot(
|
t.snapshot(
|
||||||
workspaceSessions.map(s => ({ docId: s.docId, pinned: s.pinned })),
|
workspaceSessions.map(s => ({ docId: s.docId, pinned: s.pinned })),
|
||||||
@@ -119,16 +122,19 @@ test('should list and filter session type', async t => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const docSessions = await copilotSession.list(user.id, workspace.id, docId);
|
const docSessions = await copilotSession.list({
|
||||||
|
userId: user.id,
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
docId,
|
||||||
|
});
|
||||||
|
|
||||||
t.snapshot(
|
t.snapshot(
|
||||||
cleanObject(docSessions, [
|
cleanObject(
|
||||||
'id',
|
docSessions.toSorted(s =>
|
||||||
'userId',
|
s.docId!.localeCompare(s.docId!, undefined, { numeric: true })
|
||||||
'createdAt',
|
),
|
||||||
'messages',
|
['id', 'userId', 'workspaceId', 'createdAt', 'tokenCost']
|
||||||
'tokenCost',
|
),
|
||||||
]),
|
|
||||||
'doc sessions should only include sessions with matching docId'
|
'doc sessions should only include sessions with matching docId'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,13 +58,20 @@ export type UpdateChatSession = Pick<ChatSession, 'userId' | 'sessionId'> &
|
|||||||
UpdateChatSessionData;
|
UpdateChatSessionData;
|
||||||
|
|
||||||
export type ListSessionOptions = {
|
export type ListSessionOptions = {
|
||||||
sessionId: string | undefined;
|
userId: string;
|
||||||
action: boolean | undefined;
|
sessionId?: string;
|
||||||
fork: boolean | undefined;
|
workspaceId?: string;
|
||||||
limit: number | undefined;
|
docId?: string;
|
||||||
skip: number | undefined;
|
action?: boolean;
|
||||||
sessionOrder: 'asc' | 'desc' | undefined;
|
fork?: boolean;
|
||||||
messageOrder: 'asc' | 'desc' | undefined;
|
limit?: number;
|
||||||
|
skip?: number;
|
||||||
|
sessionOrder?: 'asc' | 'desc';
|
||||||
|
messageOrder?: 'asc' | 'desc';
|
||||||
|
|
||||||
|
// extra condition
|
||||||
|
withPrompt?: boolean;
|
||||||
|
withMessages?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -197,12 +204,9 @@ export class CopilotSessionModel extends BaseModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async list(
|
async list(options: ListSessionOptions) {
|
||||||
userId: string,
|
const { userId, sessionId, workspaceId, docId } = options;
|
||||||
workspaceId?: string,
|
|
||||||
docId?: string,
|
|
||||||
options?: ListSessionOptions
|
|
||||||
) {
|
|
||||||
const extraCondition = [];
|
const extraCondition = [];
|
||||||
|
|
||||||
if (!options?.action && options?.fork) {
|
if (!options?.action && options?.fork) {
|
||||||
@@ -211,7 +215,10 @@ export class CopilotSessionModel extends BaseModel {
|
|||||||
userId: { not: userId },
|
userId: { not: userId },
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
docId: docId ?? null,
|
docId: docId ?? null,
|
||||||
id: options?.sessionId ? { equals: options.sessionId } : undefined,
|
id: sessionId ? { equals: sessionId } : undefined,
|
||||||
|
prompt: {
|
||||||
|
action: options.action ? { not: null } : null,
|
||||||
|
},
|
||||||
// should only find forked session
|
// should only find forked session
|
||||||
parentSessionId: { not: null },
|
parentSessionId: { not: null },
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
@@ -223,9 +230,9 @@ export class CopilotSessionModel extends BaseModel {
|
|||||||
OR: [
|
OR: [
|
||||||
{
|
{
|
||||||
userId,
|
userId,
|
||||||
workspaceId: workspaceId,
|
workspaceId,
|
||||||
docId: docId ?? null,
|
docId: docId ?? null,
|
||||||
id: options?.sessionId ? { equals: options.sessionId } : undefined,
|
id: sessionId ? { equals: sessionId } : undefined,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
},
|
},
|
||||||
...extraCondition,
|
...extraCondition,
|
||||||
@@ -234,26 +241,30 @@ export class CopilotSessionModel extends BaseModel {
|
|||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
|
workspaceId: true,
|
||||||
docId: true,
|
docId: true,
|
||||||
|
parentSessionId: true,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
promptName: true,
|
promptName: true,
|
||||||
tokenCost: true,
|
tokenCost: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
messages: {
|
messages: options.withMessages
|
||||||
select: {
|
? {
|
||||||
id: true,
|
select: {
|
||||||
role: true,
|
id: true,
|
||||||
content: true,
|
role: true,
|
||||||
attachments: true,
|
content: true,
|
||||||
params: true,
|
attachments: true,
|
||||||
streamObjects: true,
|
params: true,
|
||||||
createdAt: true,
|
streamObjects: true,
|
||||||
},
|
createdAt: true,
|
||||||
orderBy: {
|
},
|
||||||
// message order is asc by default
|
orderBy: {
|
||||||
createdAt: options?.messageOrder === 'desc' ? 'desc' : 'asc',
|
// message order is asc by default
|
||||||
},
|
createdAt: options?.messageOrder === 'desc' ? 'desc' : 'asc',
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
: false,
|
||||||
},
|
},
|
||||||
take: options?.limit,
|
take: options?.limit,
|
||||||
skip: options?.skip,
|
skip: options?.skip,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import { CurrentUser } from '../../core/auth';
|
|||||||
import { Admin } from '../../core/common';
|
import { Admin } from '../../core/common';
|
||||||
import { AccessController } from '../../core/permission';
|
import { AccessController } from '../../core/permission';
|
||||||
import { UserType } from '../../core/user';
|
import { UserType } from '../../core/user';
|
||||||
import type { UpdateChatSession } from '../../models';
|
import type { ListSessionOptions, UpdateChatSession } from '../../models';
|
||||||
import { PromptService } from './prompt';
|
import { PromptService } from './prompt';
|
||||||
import { PromptMessage, StreamObject } from './providers';
|
import { PromptMessage, StreamObject } from './providers';
|
||||||
import { ChatSessionService } from './session';
|
import { ChatSessionService } from './session';
|
||||||
@@ -43,7 +43,6 @@ import {
|
|||||||
type ChatHistory,
|
type ChatHistory,
|
||||||
type ChatMessage,
|
type ChatMessage,
|
||||||
type ChatSessionState,
|
type ChatSessionState,
|
||||||
type ListHistoriesOptions,
|
|
||||||
SubmittedMessage,
|
SubmittedMessage,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
@@ -151,25 +150,28 @@ enum ChatHistoryOrder {
|
|||||||
registerEnumType(ChatHistoryOrder, { name: 'ChatHistoryOrder' });
|
registerEnumType(ChatHistoryOrder, { name: 'ChatHistoryOrder' });
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
class QueryChatSessionsInput {
|
class QueryChatSessionsInput implements Partial<ListSessionOptions> {
|
||||||
@Field(() => Boolean, { nullable: true })
|
|
||||||
action: boolean | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
@InputType()
|
|
||||||
class QueryChatHistoriesInput implements Partial<ListHistoriesOptions> {
|
|
||||||
@Field(() => Boolean, { nullable: true })
|
@Field(() => Boolean, { nullable: true })
|
||||||
action: boolean | undefined;
|
action: boolean | undefined;
|
||||||
|
|
||||||
@Field(() => Boolean, { nullable: true })
|
@Field(() => Boolean, { nullable: true })
|
||||||
fork: boolean | undefined;
|
fork: boolean | undefined;
|
||||||
|
|
||||||
|
@Field(() => Boolean, { nullable: true })
|
||||||
|
pinned: boolean | undefined;
|
||||||
|
|
||||||
@Field(() => Number, { nullable: true })
|
@Field(() => Number, { nullable: true })
|
||||||
limit: number | undefined;
|
limit: number | undefined;
|
||||||
|
|
||||||
@Field(() => Number, { nullable: true })
|
@Field(() => Number, { nullable: true })
|
||||||
skip: number | undefined;
|
skip: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
class QueryChatHistoriesInput
|
||||||
|
extends QueryChatSessionsInput
|
||||||
|
implements Partial<ListSessionOptions>
|
||||||
|
{
|
||||||
@Field(() => ChatHistoryOrder, { nullable: true })
|
@Field(() => ChatHistoryOrder, { nullable: true })
|
||||||
messageOrder: 'asc' | 'desc' | undefined;
|
messageOrder: 'asc' | 'desc' | undefined;
|
||||||
|
|
||||||
@@ -370,20 +372,6 @@ export class CopilotResolver {
|
|||||||
return await this.chatSession.getQuota(user.id);
|
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, {
|
@ResolveField(() => CopilotSessionType, {
|
||||||
description: 'Get the session by id',
|
description: 'Get the session by id',
|
||||||
complexity: 2,
|
complexity: 2,
|
||||||
@@ -426,12 +414,15 @@ export class CopilotResolver {
|
|||||||
.workspace(copilot.workspaceId)
|
.workspace(copilot.workspaceId)
|
||||||
.allowLocal()
|
.allowLocal()
|
||||||
.assert('Workspace.Copilot');
|
.assert('Workspace.Copilot');
|
||||||
|
|
||||||
const sessions = await this.chatSession.listSessions(
|
const sessions = await this.chatSession.listSessions(
|
||||||
user.id,
|
Object.assign({}, options, {
|
||||||
copilot.workspaceId,
|
userId: user.id,
|
||||||
docId,
|
workspaceId: copilot.workspaceId,
|
||||||
options
|
docId,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return sessions.map(this.transformToSessionType);
|
return sessions.map(this.transformToSessionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,10 +452,7 @@ export class CopilotResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const histories = await this.chatSession.listHistories(
|
const histories = await this.chatSession.listHistories(
|
||||||
user.id,
|
Object.assign({}, options, { userId: user.id, workspaceId, docId })
|
||||||
workspaceId,
|
|
||||||
docId,
|
|
||||||
options
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return histories.map(h => ({
|
return histories.map(h => ({
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from '../../base';
|
} from '../../base';
|
||||||
import { QuotaService } from '../../core/quota';
|
import { QuotaService } from '../../core/quota';
|
||||||
import {
|
import {
|
||||||
|
ListSessionOptions,
|
||||||
Models,
|
Models,
|
||||||
type UpdateChatSession,
|
type UpdateChatSession,
|
||||||
UpdateChatSessionData,
|
UpdateChatSessionData,
|
||||||
@@ -29,7 +30,6 @@ import {
|
|||||||
type ChatSessionOptions,
|
type ChatSessionOptions,
|
||||||
type ChatSessionState,
|
type ChatSessionState,
|
||||||
getTokenEncoder,
|
getTokenEncoder,
|
||||||
type ListHistoriesOptions,
|
|
||||||
type SubmittedMessage,
|
type SubmittedMessage,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
@@ -314,65 +314,38 @@ export class ChatSessionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async listSessions(
|
async listSessions(
|
||||||
userId: string,
|
options: ListSessionOptions
|
||||||
workspaceId: string,
|
|
||||||
docId?: string,
|
|
||||||
options?: { action?: boolean }
|
|
||||||
): Promise<Omit<ChatSessionState, 'messages'>[]> {
|
): Promise<Omit<ChatSessionState, 'messages'>[]> {
|
||||||
return await this.db.aiSession
|
const sessions = await this.models.copilotSession.list({
|
||||||
.findMany({
|
...options,
|
||||||
where: {
|
withMessages: false,
|
||||||
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 });
|
|
||||||
|
|
||||||
return {
|
return Promise.all(
|
||||||
sessionId: session.id,
|
sessions.map(async session => {
|
||||||
userId: session.userId,
|
const prompt = await this.prompt.get(session.promptName);
|
||||||
workspaceId: session.workspaceId,
|
if (!prompt)
|
||||||
docId: session.docId,
|
throw new CopilotPromptNotFound({ name: session.promptName });
|
||||||
pinned: session.pinned,
|
|
||||||
parentSessionId: session.parentSessionId,
|
return {
|
||||||
prompt,
|
sessionId: session.id,
|
||||||
};
|
userId: session.userId,
|
||||||
})
|
workspaceId: session.workspaceId,
|
||||||
);
|
docId: session.docId,
|
||||||
});
|
pinned: session.pinned,
|
||||||
|
parentSessionId: session.parentSessionId,
|
||||||
|
prompt,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async listHistories(
|
async listHistories(options: ListSessionOptions): Promise<ChatHistory[]> {
|
||||||
userId: string,
|
const { userId } = options;
|
||||||
workspaceId?: string,
|
const sessions = await this.models.copilotSession.list({
|
||||||
docId?: string,
|
...options,
|
||||||
options?: ListHistoriesOptions
|
withMessages: true,
|
||||||
): Promise<ChatHistory[]> {
|
});
|
||||||
const sessions = await this.models.copilotSession.list(
|
|
||||||
userId,
|
|
||||||
workspaceId,
|
|
||||||
docId,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
const histories = await Promise.all(
|
const histories = await Promise.all(
|
||||||
sessions.map(
|
sessions.map(
|
||||||
async ({
|
async ({
|
||||||
|
|||||||
@@ -127,17 +127,6 @@ export interface ChatSessionState
|
|||||||
messages: ChatMessage[];
|
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 = {
|
export type CopilotContextFile = {
|
||||||
id: string; // fileId
|
id: string; // fileId
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
|||||||
@@ -145,9 +145,6 @@ type Copilot {
|
|||||||
"""Get the session by id"""
|
"""Get the session by id"""
|
||||||
session(sessionId: String!): CopilotSessionType!
|
session(sessionId: String!): CopilotSessionType!
|
||||||
|
|
||||||
"""Get the session id list in the workspace"""
|
|
||||||
sessionIds(docId: String, options: QueryChatSessionsInput): [String!]! @deprecated(reason: "Use `sessions` instead")
|
|
||||||
|
|
||||||
"""Get the session list in the workspace"""
|
"""Get the session list in the workspace"""
|
||||||
sessions(docId: String, options: QueryChatSessionsInput): [CopilotSessionType!]!
|
sessions(docId: String, options: QueryChatSessionsInput): [CopilotSessionType!]!
|
||||||
workspaceId: ID
|
workspaceId: ID
|
||||||
@@ -1440,6 +1437,7 @@ input QueryChatHistoriesInput {
|
|||||||
fork: Boolean
|
fork: Boolean
|
||||||
limit: Int
|
limit: Int
|
||||||
messageOrder: ChatHistoryOrder
|
messageOrder: ChatHistoryOrder
|
||||||
|
pinned: Boolean
|
||||||
sessionId: String
|
sessionId: String
|
||||||
sessionOrder: ChatHistoryOrder
|
sessionOrder: ChatHistoryOrder
|
||||||
skip: Int
|
skip: Int
|
||||||
@@ -1448,6 +1446,10 @@ input QueryChatHistoriesInput {
|
|||||||
|
|
||||||
input QueryChatSessionsInput {
|
input QueryChatSessionsInput {
|
||||||
action: Boolean
|
action: Boolean
|
||||||
|
fork: Boolean
|
||||||
|
limit: Int
|
||||||
|
pinned: Boolean
|
||||||
|
skip: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryTooLongDataType {
|
type QueryTooLongDataType {
|
||||||
|
|||||||
@@ -186,11 +186,6 @@ export interface Copilot {
|
|||||||
quota: CopilotQuota;
|
quota: CopilotQuota;
|
||||||
/** Get the session by id */
|
/** Get the session by id */
|
||||||
session: CopilotSessionType;
|
session: CopilotSessionType;
|
||||||
/**
|
|
||||||
* Get the session id list in the workspace
|
|
||||||
* @deprecated Use `sessions` instead
|
|
||||||
*/
|
|
||||||
sessionIds: Array<Scalars['String']['output']>;
|
|
||||||
/** Get the session list in the workspace */
|
/** Get the session list in the workspace */
|
||||||
sessions: Array<CopilotSessionType>;
|
sessions: Array<CopilotSessionType>;
|
||||||
workspaceId: Maybe<Scalars['ID']['output']>;
|
workspaceId: Maybe<Scalars['ID']['output']>;
|
||||||
@@ -215,11 +210,6 @@ export interface CopilotSessionArgs {
|
|||||||
sessionId: Scalars['String']['input'];
|
sessionId: Scalars['String']['input'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CopilotSessionIdsArgs {
|
|
||||||
docId?: InputMaybe<Scalars['String']['input']>;
|
|
||||||
options?: InputMaybe<QueryChatSessionsInput>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CopilotSessionsArgs {
|
export interface CopilotSessionsArgs {
|
||||||
docId?: InputMaybe<Scalars['String']['input']>;
|
docId?: InputMaybe<Scalars['String']['input']>;
|
||||||
options?: InputMaybe<QueryChatSessionsInput>;
|
options?: InputMaybe<QueryChatSessionsInput>;
|
||||||
@@ -2008,6 +1998,7 @@ export interface QueryChatHistoriesInput {
|
|||||||
fork?: InputMaybe<Scalars['Boolean']['input']>;
|
fork?: InputMaybe<Scalars['Boolean']['input']>;
|
||||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||||
messageOrder?: InputMaybe<ChatHistoryOrder>;
|
messageOrder?: InputMaybe<ChatHistoryOrder>;
|
||||||
|
pinned?: InputMaybe<Scalars['Boolean']['input']>;
|
||||||
sessionId?: InputMaybe<Scalars['String']['input']>;
|
sessionId?: InputMaybe<Scalars['String']['input']>;
|
||||||
sessionOrder?: InputMaybe<ChatHistoryOrder>;
|
sessionOrder?: InputMaybe<ChatHistoryOrder>;
|
||||||
skip?: InputMaybe<Scalars['Int']['input']>;
|
skip?: InputMaybe<Scalars['Int']['input']>;
|
||||||
@@ -2016,6 +2007,10 @@ export interface QueryChatHistoriesInput {
|
|||||||
|
|
||||||
export interface QueryChatSessionsInput {
|
export interface QueryChatSessionsInput {
|
||||||
action?: InputMaybe<Scalars['Boolean']['input']>;
|
action?: InputMaybe<Scalars['Boolean']['input']>;
|
||||||
|
fork?: InputMaybe<Scalars['Boolean']['input']>;
|
||||||
|
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||||
|
pinned?: InputMaybe<Scalars['Boolean']['input']>;
|
||||||
|
skip?: InputMaybe<Scalars['Int']['input']>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryTooLongDataType {
|
export interface QueryTooLongDataType {
|
||||||
|
|||||||
Reference in New Issue
Block a user