mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-06 08:50:50 +08:00
feat: fork session support (#7367)
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ai_sessions_metadata" ADD COLUMN "parent_session_id" VARCHAR(36);
|
||||||
@@ -481,15 +481,17 @@ model AiSessionMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model AiSession {
|
model AiSession {
|
||||||
id String @id @default(uuid()) @db.VarChar(36)
|
id String @id @default(uuid()) @db.VarChar(36)
|
||||||
userId String @map("user_id") @db.VarChar(36)
|
userId String @map("user_id") @db.VarChar(36)
|
||||||
workspaceId String @map("workspace_id") @db.VarChar(36)
|
workspaceId String @map("workspace_id") @db.VarChar(36)
|
||||||
docId String @map("doc_id") @db.VarChar(36)
|
docId String @map("doc_id") @db.VarChar(36)
|
||||||
promptName String @map("prompt_name") @db.VarChar(32)
|
promptName String @map("prompt_name") @db.VarChar(32)
|
||||||
messageCost Int @default(0)
|
// the session id of the parent session if this session is a forked session
|
||||||
tokenCost Int @default(0)
|
parentSessionId String? @map("parent_session_id") @db.VarChar(36)
|
||||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
messageCost Int @default(0)
|
||||||
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
|
tokenCost Int @default(0)
|
||||||
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
|
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
prompt AiPrompt @relation(fields: [promptName], references: [name], onDelete: Cascade)
|
prompt AiPrompt @relation(fields: [promptName], references: [name], onDelete: Cascade)
|
||||||
|
|||||||
@@ -440,7 +440,8 @@ export const USER_FRIENDLY_ERRORS = {
|
|||||||
},
|
},
|
||||||
copilot_message_not_found: {
|
copilot_message_not_found: {
|
||||||
type: 'resource_not_found',
|
type: 'resource_not_found',
|
||||||
message: `Copilot message not found.`,
|
args: { messageId: 'string' },
|
||||||
|
message: ({ messageId }) => `Copilot message ${messageId} not found.`,
|
||||||
},
|
},
|
||||||
copilot_prompt_not_found: {
|
copilot_prompt_not_found: {
|
||||||
type: 'resource_not_found',
|
type: 'resource_not_found',
|
||||||
|
|||||||
@@ -391,10 +391,14 @@ export class CopilotActionTaken extends UserFriendlyError {
|
|||||||
super('action_forbidden', 'copilot_action_taken', message);
|
super('action_forbidden', 'copilot_action_taken', message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ObjectType()
|
||||||
|
class CopilotMessageNotFoundDataType {
|
||||||
|
@Field() messageId!: string
|
||||||
|
}
|
||||||
|
|
||||||
export class CopilotMessageNotFound extends UserFriendlyError {
|
export class CopilotMessageNotFound extends UserFriendlyError {
|
||||||
constructor(message?: string) {
|
constructor(args: CopilotMessageNotFoundDataType, message?: string | ((args: CopilotMessageNotFoundDataType) => string)) {
|
||||||
super('resource_not_found', 'copilot_message_not_found', message);
|
super('resource_not_found', 'copilot_message_not_found', message, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
@@ -542,5 +546,5 @@ registerEnumType(ErrorNames, {
|
|||||||
export const ErrorDataUnionType = createUnionType({
|
export const ErrorDataUnionType = createUnionType({
|
||||||
name: 'ErrorDataUnion',
|
name: 'ErrorDataUnion',
|
||||||
types: () =>
|
types: () =>
|
||||||
[UnknownOauthProviderDataType, MissingOauthQueryParameterDataType, InvalidPasswordLengthDataType, WorkspaceNotFoundDataType, NotInWorkspaceDataType, WorkspaceAccessDeniedDataType, WorkspaceOwnerNotFoundDataType, DocNotFoundDataType, DocAccessDeniedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType] as const,
|
[UnknownOauthProviderDataType, MissingOauthQueryParameterDataType, InvalidPasswordLengthDataType, WorkspaceNotFoundDataType, NotInWorkspaceDataType, WorkspaceAccessDeniedDataType, WorkspaceOwnerNotFoundDataType, DocNotFoundDataType, DocAccessDeniedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType] as const,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
FileUpload,
|
FileUpload,
|
||||||
MutexService,
|
MutexService,
|
||||||
Throttle,
|
Throttle,
|
||||||
TooManyRequestsException,
|
TooManyRequest,
|
||||||
} from '../../fundamentals';
|
} from '../../fundamentals';
|
||||||
import { PromptService } from './prompt';
|
import { PromptService } from './prompt';
|
||||||
import { ChatSessionService } from './session';
|
import { ChatSessionService } from './session';
|
||||||
@@ -60,6 +60,24 @@ class CreateChatSessionInput {
|
|||||||
promptName!: string;
|
promptName!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
class ForkChatSessionInput {
|
||||||
|
@Field(() => String)
|
||||||
|
workspaceId!: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
docId!: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
sessionId!: string;
|
||||||
|
|
||||||
|
@Field(() => String, {
|
||||||
|
description:
|
||||||
|
'Identify a message in the array and keep it with all previous messages into a forked session.',
|
||||||
|
})
|
||||||
|
latestMessageId!: string;
|
||||||
|
}
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
class DeleteSessionInput {
|
class DeleteSessionInput {
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
@@ -109,6 +127,10 @@ class QueryChatHistoriesInput implements Partial<ListHistoriesOptions> {
|
|||||||
|
|
||||||
@ObjectType('ChatMessage')
|
@ObjectType('ChatMessage')
|
||||||
class ChatMessageType implements Partial<ChatMessage> {
|
class ChatMessageType implements Partial<ChatMessage> {
|
||||||
|
// id will be null if message is a prompt message
|
||||||
|
@Field(() => ID, { nullable: true })
|
||||||
|
id!: string;
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
role!: 'system' | 'assistant' | 'user';
|
role!: 'system' | 'assistant' | 'user';
|
||||||
|
|
||||||
@@ -301,7 +323,7 @@ export class CopilotResolver {
|
|||||||
const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${options.workspaceId}`;
|
const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${options.workspaceId}`;
|
||||||
await using lock = await this.mutex.lock(lockFlag);
|
await using lock = await this.mutex.lock(lockFlag);
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
return new TooManyRequestsException('Server is busy');
|
return new TooManyRequest('Server is busy');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.chatSession.checkQuota(user.id);
|
await this.chatSession.checkQuota(user.id);
|
||||||
@@ -313,6 +335,34 @@ export class CopilotResolver {
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mutation(() => String, {
|
||||||
|
description: 'Create a chat session',
|
||||||
|
})
|
||||||
|
async forkCopilotSession(
|
||||||
|
@CurrentUser() user: CurrentUser,
|
||||||
|
@Args({ name: 'options', type: () => ForkChatSessionInput })
|
||||||
|
options: ForkChatSessionInput
|
||||||
|
) {
|
||||||
|
await this.permissions.checkCloudPagePermission(
|
||||||
|
options.workspaceId,
|
||||||
|
options.docId,
|
||||||
|
user.id
|
||||||
|
);
|
||||||
|
const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${options.workspaceId}`;
|
||||||
|
await using lock = await this.mutex.lock(lockFlag);
|
||||||
|
if (!lock) {
|
||||||
|
return new TooManyRequest('Server is busy');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.chatSession.checkQuota(user.id);
|
||||||
|
|
||||||
|
const session = await this.chatSession.fork({
|
||||||
|
...options,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
@Mutation(() => [String], {
|
@Mutation(() => [String], {
|
||||||
description: 'Cleanup sessions',
|
description: 'Cleanup sessions',
|
||||||
})
|
})
|
||||||
@@ -332,7 +382,7 @@ export class CopilotResolver {
|
|||||||
const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${options.workspaceId}`;
|
const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${options.workspaceId}`;
|
||||||
await using lock = await this.mutex.lock(lockFlag);
|
await using lock = await this.mutex.lock(lockFlag);
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
return new TooManyRequestsException('Server is busy');
|
return new TooManyRequest('Server is busy');
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.chatSession.cleanup({
|
return await this.chatSession.cleanup({
|
||||||
@@ -352,7 +402,7 @@ export class CopilotResolver {
|
|||||||
const lockFlag = `${COPILOT_LOCKER}:message:${user?.id}:${options.sessionId}`;
|
const lockFlag = `${COPILOT_LOCKER}:message:${user?.id}:${options.sessionId}`;
|
||||||
await using lock = await this.mutex.lock(lockFlag);
|
await using lock = await this.mutex.lock(lockFlag);
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
return new TooManyRequestsException('Server is busy');
|
return new TooManyRequest('Server is busy');
|
||||||
}
|
}
|
||||||
const session = await this.chatSession.get(options.sessionId);
|
const session = await this.chatSession.get(options.sessionId);
|
||||||
if (!session || session.config.userId !== user.id) {
|
if (!session || session.config.userId !== user.id) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
ChatHistory,
|
ChatHistory,
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
ChatMessageSchema,
|
ChatMessageSchema,
|
||||||
|
ChatSessionForkOptions,
|
||||||
ChatSessionOptions,
|
ChatSessionOptions,
|
||||||
ChatSessionState,
|
ChatSessionState,
|
||||||
getTokenEncoder,
|
getTokenEncoder,
|
||||||
@@ -81,7 +82,7 @@ export class ChatSession implements AsyncDisposable {
|
|||||||
async getMessageById(messageId: string) {
|
async getMessageById(messageId: string) {
|
||||||
const message = await this.messageCache.get(messageId);
|
const message = await this.messageCache.get(messageId);
|
||||||
if (!message || message.sessionId !== this.state.sessionId) {
|
if (!message || message.sessionId !== this.state.sessionId) {
|
||||||
throw new CopilotMessageNotFound();
|
throw new CopilotMessageNotFound({ messageId });
|
||||||
}
|
}
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
@@ -89,7 +90,7 @@ export class ChatSession implements AsyncDisposable {
|
|||||||
async pushByMessageId(messageId: string) {
|
async pushByMessageId(messageId: string) {
|
||||||
const message = await this.messageCache.get(messageId);
|
const message = await this.messageCache.get(messageId);
|
||||||
if (!message || message.sessionId !== this.state.sessionId) {
|
if (!message || message.sessionId !== this.state.sessionId) {
|
||||||
throw new CopilotMessageNotFound();
|
throw new CopilotMessageNotFound({ messageId });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.push({
|
this.push({
|
||||||
@@ -200,6 +201,7 @@ export class ChatSessionService {
|
|||||||
workspaceId: state.workspaceId,
|
workspaceId: state.workspaceId,
|
||||||
docId: state.docId,
|
docId: state.docId,
|
||||||
prompt: { action: { equals: null } },
|
prompt: { action: { equals: null } },
|
||||||
|
parentSessionId: state.parentSessionId,
|
||||||
},
|
},
|
||||||
select: { id: true, deletedAt: true },
|
select: { id: true, deletedAt: true },
|
||||||
})) || {};
|
})) || {};
|
||||||
@@ -271,8 +273,9 @@ export class ChatSessionService {
|
|||||||
userId: true,
|
userId: true,
|
||||||
workspaceId: true,
|
workspaceId: true,
|
||||||
docId: true,
|
docId: true,
|
||||||
|
parentSessionId: true,
|
||||||
messages: {
|
messages: {
|
||||||
select: { role: true, content: true, createdAt: true },
|
select: { id: true, role: true, content: true, createdAt: true },
|
||||||
orderBy: { createdAt: 'asc' },
|
orderBy: { createdAt: 'asc' },
|
||||||
},
|
},
|
||||||
promptName: true,
|
promptName: true,
|
||||||
@@ -291,6 +294,7 @@ export class ChatSessionService {
|
|||||||
userId: session.userId,
|
userId: session.userId,
|
||||||
workspaceId: session.workspaceId,
|
workspaceId: session.workspaceId,
|
||||||
docId: session.docId,
|
docId: session.docId,
|
||||||
|
parentSessionId: session.parentSessionId,
|
||||||
prompt,
|
prompt,
|
||||||
messages: messages.success ? messages.data : [],
|
messages: messages.success ? messages.data : [],
|
||||||
};
|
};
|
||||||
@@ -396,6 +400,7 @@ export class ChatSessionService {
|
|||||||
createdAt: true,
|
createdAt: true,
|
||||||
messages: {
|
messages: {
|
||||||
select: {
|
select: {
|
||||||
|
id: true,
|
||||||
role: true,
|
role: true,
|
||||||
content: true,
|
content: true,
|
||||||
attachments: true,
|
attachments: true,
|
||||||
@@ -430,7 +435,8 @@ export class ChatSessionService {
|
|||||||
.filter(({ role }) => role !== 'system')
|
.filter(({ role }) => role !== 'system')
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
// `createdAt` is required for history sorting in frontend, let's fake the creating time of prompt messages
|
// `createdAt` is required for history sorting in frontend
|
||||||
|
// let's fake the creating time of prompt messages
|
||||||
(preload as ChatMessage[]).forEach((msg, i) => {
|
(preload as ChatMessage[]).forEach((msg, i) => {
|
||||||
msg.createdAt = new Date(
|
msg.createdAt = new Date(
|
||||||
createdAt.getTime() - preload.length - i - 1
|
createdAt.getTime() - preload.length - i - 1
|
||||||
@@ -495,9 +501,39 @@ export class ChatSessionService {
|
|||||||
sessionId,
|
sessionId,
|
||||||
prompt,
|
prompt,
|
||||||
messages: [],
|
messages: [],
|
||||||
|
// when client create chat session, we always find root session
|
||||||
|
parentSessionId: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fork(options: ChatSessionForkOptions): Promise<string> {
|
||||||
|
const state = await this.getSession(options.sessionId);
|
||||||
|
if (!state) {
|
||||||
|
throw new CopilotSessionNotFound();
|
||||||
|
}
|
||||||
|
const lastMessageIdx = state.messages.findLastIndex(
|
||||||
|
({ id, role }) =>
|
||||||
|
role === AiPromptRole.assistant && id === options.latestMessageId
|
||||||
|
);
|
||||||
|
if (lastMessageIdx < 0) {
|
||||||
|
throw new CopilotMessageNotFound({ messageId: options.latestMessageId });
|
||||||
|
}
|
||||||
|
const messages = state.messages
|
||||||
|
.slice(0, lastMessageIdx + 1)
|
||||||
|
.map(m => ({ ...m, id: undefined }));
|
||||||
|
|
||||||
|
const forkedState = {
|
||||||
|
...state,
|
||||||
|
sessionId: randomUUID(),
|
||||||
|
messages: [],
|
||||||
|
parentSessionId: options.sessionId,
|
||||||
|
};
|
||||||
|
// create session
|
||||||
|
await this.setSession(forkedState);
|
||||||
|
// save message
|
||||||
|
return await this.setSession({ ...forkedState, messages });
|
||||||
|
}
|
||||||
|
|
||||||
async cleanup(
|
async cleanup(
|
||||||
options: Omit<ChatSessionOptions, 'promptName'> & { sessionIds: string[] }
|
options: Omit<ChatSessionOptions, 'promptName'> & { sessionIds: string[] }
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export type PromptMessage = z.infer<typeof PromptMessageSchema>;
|
|||||||
export type PromptParams = NonNullable<PromptMessage['params']>;
|
export type PromptParams = NonNullable<PromptMessage['params']>;
|
||||||
|
|
||||||
export const ChatMessageSchema = PromptMessageSchema.extend({
|
export const ChatMessageSchema = PromptMessageSchema.extend({
|
||||||
|
id: z.string().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
}).strict();
|
}).strict();
|
||||||
|
|
||||||
@@ -98,10 +99,17 @@ export interface ChatSessionOptions {
|
|||||||
promptName: string;
|
promptName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ChatSessionForkOptions
|
||||||
|
extends Omit<ChatSessionOptions, 'promptName'> {
|
||||||
|
sessionId: string;
|
||||||
|
latestMessageId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChatSessionState
|
export interface ChatSessionState
|
||||||
extends Omit<ChatSessionOptions, 'promptName'> {
|
extends Omit<ChatSessionOptions, 'promptName'> {
|
||||||
// connect ids
|
// connect ids
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
|
parentSessionId: string | null;
|
||||||
// states
|
// states
|
||||||
prompt: ChatPrompt;
|
prompt: ChatPrompt;
|
||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type ChatMessage {
|
|||||||
attachments: [String!]
|
attachments: [String!]
|
||||||
content: String!
|
content: String!
|
||||||
createdAt: DateTime!
|
createdAt: DateTime!
|
||||||
|
id: ID
|
||||||
params: JSON
|
params: JSON
|
||||||
role: String!
|
role: String!
|
||||||
}
|
}
|
||||||
@@ -39,6 +40,10 @@ type CopilotHistories {
|
|||||||
tokens: Int!
|
tokens: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CopilotMessageNotFoundDataType {
|
||||||
|
messageId: String!
|
||||||
|
}
|
||||||
|
|
||||||
enum CopilotModels {
|
enum CopilotModels {
|
||||||
DallE3
|
DallE3
|
||||||
Gpt4Omni
|
Gpt4Omni
|
||||||
@@ -175,7 +180,7 @@ enum EarlyAccessType {
|
|||||||
App
|
App
|
||||||
}
|
}
|
||||||
|
|
||||||
union ErrorDataUnion = BlobNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocAccessDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | InvalidHistoryTimestampDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MissingOauthQueryParameterDataType | NotInWorkspaceDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | VersionRejectedDataType | WorkspaceAccessDeniedDataType | WorkspaceNotFoundDataType | WorkspaceOwnerNotFoundDataType
|
union ErrorDataUnion = BlobNotFoundDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocAccessDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | InvalidHistoryTimestampDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MissingOauthQueryParameterDataType | NotInWorkspaceDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | VersionRejectedDataType | WorkspaceAccessDeniedDataType | WorkspaceNotFoundDataType | WorkspaceOwnerNotFoundDataType
|
||||||
|
|
||||||
enum ErrorNames {
|
enum ErrorNames {
|
||||||
ACCESS_DENIED
|
ACCESS_DENIED
|
||||||
@@ -252,6 +257,17 @@ enum FeatureType {
|
|||||||
UnlimitedWorkspace
|
UnlimitedWorkspace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input ForkChatSessionInput {
|
||||||
|
docId: String!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Identify a message in the array and keep it with all previous messages into a forked session.
|
||||||
|
"""
|
||||||
|
latestMessageId: String!
|
||||||
|
sessionId: String!
|
||||||
|
workspaceId: String!
|
||||||
|
}
|
||||||
|
|
||||||
type HumanReadableQuotaType {
|
type HumanReadableQuotaType {
|
||||||
blobLimit: String!
|
blobLimit: String!
|
||||||
copilotActionLimit: String
|
copilotActionLimit: String
|
||||||
@@ -399,6 +415,9 @@ type Mutation {
|
|||||||
"""Delete a user account"""
|
"""Delete a user account"""
|
||||||
deleteUser(id: String!): DeleteAccount!
|
deleteUser(id: String!): DeleteAccount!
|
||||||
deleteWorkspace(id: String!): Boolean!
|
deleteWorkspace(id: String!): Boolean!
|
||||||
|
|
||||||
|
"""Create a chat session"""
|
||||||
|
forkCopilotSession(options: ForkChatSessionInput!): String!
|
||||||
invite(email: String!, permission: Permission!, sendInviteMail: Boolean, workspaceId: String!): String!
|
invite(email: String!, permission: Permission!, sendInviteMail: Boolean, workspaceId: String!): String!
|
||||||
leaveWorkspace(sendLeaveMail: Boolean, workspaceId: String!, workspaceName: String!): Boolean!
|
leaveWorkspace(sendLeaveMail: Boolean, workspaceId: String!, workspaceName: String!): Boolean!
|
||||||
publishPage(mode: PublicPageMode = Page, pageId: String!, workspaceId: String!): WorkspacePage!
|
publishPage(mode: PublicPageMode = Page, pageId: String!, workspaceId: String!): WorkspacePage!
|
||||||
|
|||||||
@@ -208,11 +208,13 @@ test('should be able to manage chat session', async t => {
|
|||||||
{ role: 'system', content: 'hello {{word}}' },
|
{ role: 'system', content: 'hello {{word}}' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const params = { word: 'world' };
|
||||||
|
const commonParams = { docId: 'test', workspaceId: 'test' };
|
||||||
|
|
||||||
const sessionId = await session.create({
|
const sessionId = await session.create({
|
||||||
docId: 'test',
|
|
||||||
workspaceId: 'test',
|
|
||||||
userId,
|
userId,
|
||||||
promptName: 'prompt',
|
promptName: 'prompt',
|
||||||
|
...commonParams,
|
||||||
});
|
});
|
||||||
t.truthy(sessionId, 'should create session');
|
t.truthy(sessionId, 'should create session');
|
||||||
|
|
||||||
@@ -221,8 +223,6 @@ test('should be able to manage chat session', async t => {
|
|||||||
t.is(s.config.promptName, 'prompt', 'should have prompt name');
|
t.is(s.config.promptName, 'prompt', 'should have prompt name');
|
||||||
t.is(s.model, 'model', 'should have model');
|
t.is(s.model, 'model', 'should have model');
|
||||||
|
|
||||||
const params = { word: 'world' };
|
|
||||||
|
|
||||||
s.push({ role: 'user', content: 'hello', createdAt: new Date() });
|
s.push({ role: 'user', content: 'hello', createdAt: new Date() });
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const finalMessages = s.finish(params).map(({ createdAt: _, ...m }) => m);
|
const finalMessages = s.finish(params).map(({ createdAt: _, ...m }) => m);
|
||||||
@@ -239,19 +239,112 @@ test('should be able to manage chat session', async t => {
|
|||||||
const s1 = (await session.get(sessionId))!;
|
const s1 = (await session.get(sessionId))!;
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
s1.finish(params).map(({ createdAt: _, ...m }) => m),
|
s1.finish(params).map(({ id: _, createdAt: __, ...m }) => m),
|
||||||
finalMessages,
|
finalMessages,
|
||||||
'should same as before message'
|
'should same as before message'
|
||||||
);
|
);
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
s1.finish({}).map(({ createdAt: _, ...m }) => m),
|
s1.finish({}).map(({ id: _, createdAt: __, ...m }) => m),
|
||||||
[
|
[
|
||||||
{ content: 'hello ', params: {}, role: 'system' },
|
{ content: 'hello ', params: {}, role: 'system' },
|
||||||
{ content: 'hello', role: 'user' },
|
{ content: 'hello', role: 'user' },
|
||||||
],
|
],
|
||||||
'should generate different message with another params'
|
'should generate different message with another params'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// should get main session after fork if re-create a chat session for same docId and workspaceId
|
||||||
|
{
|
||||||
|
const newSessionId = await session.create({
|
||||||
|
userId,
|
||||||
|
promptName: 'prompt',
|
||||||
|
...commonParams,
|
||||||
|
});
|
||||||
|
t.is(newSessionId, sessionId, 'should get same session id');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to fork chat session', async t => {
|
||||||
|
const { prompt, session } = t.context;
|
||||||
|
|
||||||
|
await prompt.set('prompt', 'model', [
|
||||||
|
{ role: 'system', content: 'hello {{word}}' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const params = { word: 'world' };
|
||||||
|
const commonParams = { docId: 'test', workspaceId: 'test' };
|
||||||
|
// create session
|
||||||
|
const sessionId = await session.create({
|
||||||
|
userId,
|
||||||
|
promptName: 'prompt',
|
||||||
|
...commonParams,
|
||||||
|
});
|
||||||
|
const s = (await session.get(sessionId))!;
|
||||||
|
s.push({ role: 'user', content: 'hello', createdAt: new Date() });
|
||||||
|
s.push({ role: 'assistant', content: 'world', createdAt: new Date() });
|
||||||
|
s.push({ role: 'user', content: 'aaa', createdAt: new Date() });
|
||||||
|
s.push({ role: 'assistant', content: 'bbb', createdAt: new Date() });
|
||||||
|
await s.save();
|
||||||
|
|
||||||
|
// fork session
|
||||||
|
const s1 = (await session.get(sessionId))!;
|
||||||
|
// @ts-expect-error
|
||||||
|
const latestMessageId = s1.finish({}).find(m => m.role === 'assistant')!.id;
|
||||||
|
const forkedSessionId = await session.fork({
|
||||||
|
userId,
|
||||||
|
sessionId,
|
||||||
|
latestMessageId,
|
||||||
|
...commonParams,
|
||||||
|
});
|
||||||
|
t.not(sessionId, forkedSessionId, 'should fork a new session');
|
||||||
|
|
||||||
|
// check forked session messages
|
||||||
|
{
|
||||||
|
const s2 = (await session.get(forkedSessionId))!;
|
||||||
|
|
||||||
|
const finalMessages = s2
|
||||||
|
.finish(params) // @ts-expect-error
|
||||||
|
.map(({ id: _, createdAt: __, ...m }) => m);
|
||||||
|
t.deepEqual(
|
||||||
|
finalMessages,
|
||||||
|
[
|
||||||
|
{ role: 'system', content: 'hello world', params },
|
||||||
|
{ role: 'user', content: 'hello' },
|
||||||
|
{ role: 'assistant', content: 'world' },
|
||||||
|
],
|
||||||
|
'should generate the final message'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check original session messages
|
||||||
|
{
|
||||||
|
const s3 = (await session.get(sessionId))!;
|
||||||
|
|
||||||
|
const finalMessages = s3
|
||||||
|
.finish(params) // @ts-expect-error
|
||||||
|
.map(({ id: _, createdAt: __, ...m }) => m);
|
||||||
|
t.deepEqual(
|
||||||
|
finalMessages,
|
||||||
|
[
|
||||||
|
{ role: 'system', content: 'hello world', params },
|
||||||
|
{ role: 'user', content: 'hello' },
|
||||||
|
{ role: 'assistant', content: 'world' },
|
||||||
|
{ role: 'user', content: 'aaa' },
|
||||||
|
{ role: 'assistant', content: 'bbb' },
|
||||||
|
],
|
||||||
|
'should generate the final message'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// should get main session after fork if re-create a chat session for same docId and workspaceId
|
||||||
|
{
|
||||||
|
const newSessionId = await session.create({
|
||||||
|
userId,
|
||||||
|
promptName: 'prompt',
|
||||||
|
...commonParams,
|
||||||
|
});
|
||||||
|
t.is(newSessionId, sessionId, 'should get same session id');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should be able to process message id', async t => {
|
test('should be able to process message id', async t => {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mutation forkCopilotSession($options: ForkChatSessionInput!) {
|
||||||
|
forkCopilotSession(options: $options)
|
||||||
|
}
|
||||||
@@ -241,6 +241,17 @@ mutation removeEarlyAccess($email: String!) {
|
|||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const forkCopilotSessionMutation = {
|
||||||
|
id: 'forkCopilotSessionMutation' as const,
|
||||||
|
operationName: 'forkCopilotSession',
|
||||||
|
definitionName: 'forkCopilotSession',
|
||||||
|
containsFile: false,
|
||||||
|
query: `
|
||||||
|
mutation forkCopilotSession($options: ForkChatSessionInput!) {
|
||||||
|
forkCopilotSession(options: $options)
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
export const getCopilotHistoriesQuery = {
|
export const getCopilotHistoriesQuery = {
|
||||||
id: 'getCopilotHistoriesQuery' as const,
|
id: 'getCopilotHistoriesQuery' as const,
|
||||||
operationName: 'getCopilotHistories',
|
operationName: 'getCopilotHistories',
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export interface ChatMessage {
|
|||||||
attachments: Maybe<Array<Scalars['String']['output']>>;
|
attachments: Maybe<Array<Scalars['String']['output']>>;
|
||||||
content: Scalars['String']['output'];
|
content: Scalars['String']['output'];
|
||||||
createdAt: Scalars['DateTime']['output'];
|
createdAt: Scalars['DateTime']['output'];
|
||||||
|
id: Maybe<Scalars['ID']['output']>;
|
||||||
params: Maybe<Scalars['JSON']['output']>;
|
params: Maybe<Scalars['JSON']['output']>;
|
||||||
role: Scalars['String']['output'];
|
role: Scalars['String']['output'];
|
||||||
}
|
}
|
||||||
@@ -81,6 +82,11 @@ export interface CopilotHistories {
|
|||||||
tokens: Scalars['Int']['output'];
|
tokens: Scalars['Int']['output'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CopilotMessageNotFoundDataType {
|
||||||
|
__typename?: 'CopilotMessageNotFoundDataType';
|
||||||
|
messageId: Scalars['String']['output'];
|
||||||
|
}
|
||||||
|
|
||||||
export enum CopilotModels {
|
export enum CopilotModels {
|
||||||
DallE3 = 'DallE3',
|
DallE3 = 'DallE3',
|
||||||
Gpt4Omni = 'Gpt4Omni',
|
Gpt4Omni = 'Gpt4Omni',
|
||||||
@@ -224,6 +230,7 @@ export enum EarlyAccessType {
|
|||||||
|
|
||||||
export type ErrorDataUnion =
|
export type ErrorDataUnion =
|
||||||
| BlobNotFoundDataType
|
| BlobNotFoundDataType
|
||||||
|
| CopilotMessageNotFoundDataType
|
||||||
| CopilotPromptNotFoundDataType
|
| CopilotPromptNotFoundDataType
|
||||||
| CopilotProviderSideErrorDataType
|
| CopilotProviderSideErrorDataType
|
||||||
| DocAccessDeniedDataType
|
| DocAccessDeniedDataType
|
||||||
@@ -320,6 +327,14 @@ export enum FeatureType {
|
|||||||
UnlimitedWorkspace = 'UnlimitedWorkspace',
|
UnlimitedWorkspace = 'UnlimitedWorkspace',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ForkChatSessionInput {
|
||||||
|
docId: Scalars['String']['input'];
|
||||||
|
/** Identify a message in the array and keep it with all previous messages into a forked session. */
|
||||||
|
latestMessageId: Scalars['String']['input'];
|
||||||
|
sessionId: Scalars['String']['input'];
|
||||||
|
workspaceId: Scalars['String']['input'];
|
||||||
|
}
|
||||||
|
|
||||||
export interface HumanReadableQuotaType {
|
export interface HumanReadableQuotaType {
|
||||||
__typename?: 'HumanReadableQuotaType';
|
__typename?: 'HumanReadableQuotaType';
|
||||||
blobLimit: Scalars['String']['output'];
|
blobLimit: Scalars['String']['output'];
|
||||||
@@ -449,6 +464,8 @@ export interface Mutation {
|
|||||||
/** Delete a user account */
|
/** Delete a user account */
|
||||||
deleteUser: DeleteAccount;
|
deleteUser: DeleteAccount;
|
||||||
deleteWorkspace: Scalars['Boolean']['output'];
|
deleteWorkspace: Scalars['Boolean']['output'];
|
||||||
|
/** Create a chat session */
|
||||||
|
forkCopilotSession: Scalars['String']['output'];
|
||||||
invite: Scalars['String']['output'];
|
invite: Scalars['String']['output'];
|
||||||
leaveWorkspace: Scalars['Boolean']['output'];
|
leaveWorkspace: Scalars['Boolean']['output'];
|
||||||
publishPage: WorkspacePage;
|
publishPage: WorkspacePage;
|
||||||
@@ -562,6 +579,10 @@ export interface MutationDeleteWorkspaceArgs {
|
|||||||
id: Scalars['String']['input'];
|
id: Scalars['String']['input'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MutationForkCopilotSessionArgs {
|
||||||
|
options: ForkChatSessionInput;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MutationInviteArgs {
|
export interface MutationInviteArgs {
|
||||||
email: Scalars['String']['input'];
|
email: Scalars['String']['input'];
|
||||||
permission: Permission;
|
permission: Permission;
|
||||||
@@ -1340,6 +1361,15 @@ export type RemoveEarlyAccessMutation = {
|
|||||||
removeEarlyAccess: number;
|
removeEarlyAccess: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ForkCopilotSessionMutationVariables = Exact<{
|
||||||
|
options: ForkChatSessionInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type ForkCopilotSessionMutation = {
|
||||||
|
__typename?: 'Mutation';
|
||||||
|
forkCopilotSession: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type CredentialsRequirementFragment = {
|
export type CredentialsRequirementFragment = {
|
||||||
__typename?: 'CredentialsRequirementType';
|
__typename?: 'CredentialsRequirementType';
|
||||||
password: {
|
password: {
|
||||||
@@ -2354,6 +2384,11 @@ export type Mutations =
|
|||||||
variables: RemoveEarlyAccessMutationVariables;
|
variables: RemoveEarlyAccessMutationVariables;
|
||||||
response: RemoveEarlyAccessMutation;
|
response: RemoveEarlyAccessMutation;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
name: 'forkCopilotSessionMutation';
|
||||||
|
variables: ForkCopilotSessionMutationVariables;
|
||||||
|
response: ForkCopilotSessionMutation;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
name: 'leaveWorkspaceMutation';
|
name: 'leaveWorkspaceMutation';
|
||||||
variables: LeaveWorkspaceMutationVariables;
|
variables: LeaveWorkspaceMutationVariables;
|
||||||
|
|||||||
Reference in New Issue
Block a user