feat(server): improve context metadata & matching (#12064)
fix AI-20 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Enhanced file metadata with MIME type, blob ID, and file name across context and workspace, now visible in UI and API. - Added workspace-level matching for files and documents with configurable thresholds and workspace scoping in search queries. - Introduced a new error type and user-friendly messaging for global workspace context matching failures. - **Bug Fixes** - Improved consistent handling of file MIME types and nullable context IDs for accurate metadata. - **Documentation** - Updated GraphQL schema, queries, and mutations to include new metadata fields, optional parameters, and error types. - **Style** - Added new localization strings for global context matching error messages. - **Tests** - Extended test coverage with new and updated snapshot tests for metadata and matching logic. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ai_workspace_files" ADD COLUMN "blob_id" VARCHAR NOT NULL DEFAULT '';
|
||||||
@@ -518,6 +518,7 @@ model AiWorkspaceIgnoredDocs {
|
|||||||
model AiWorkspaceFiles {
|
model AiWorkspaceFiles {
|
||||||
workspaceId String @map("workspace_id") @db.VarChar
|
workspaceId String @map("workspace_id") @db.VarChar
|
||||||
fileId String @map("file_id") @db.VarChar
|
fileId String @map("file_id") @db.VarChar
|
||||||
|
blobId String @map("blob_id") @db.VarChar @default("")
|
||||||
fileName String @map("file_name") @db.VarChar
|
fileName String @map("file_name") @db.VarChar
|
||||||
mimeType String @map("mime_type") @db.VarChar
|
mimeType String @map("mime_type") @db.VarChar
|
||||||
size Int @db.Integer
|
size Int @db.Integer
|
||||||
|
|||||||
@@ -1316,7 +1316,11 @@ test('should be able to manage context', async t => {
|
|||||||
// file record
|
// file record
|
||||||
{
|
{
|
||||||
await storage.put(userId, session.workspaceId, 'blob', buffer);
|
await storage.put(userId, session.workspaceId, 'blob', buffer);
|
||||||
const file = await session.addFile('blob', 'sample.pdf');
|
const file = await session.addFile(
|
||||||
|
'blob',
|
||||||
|
'sample.pdf',
|
||||||
|
'application/pdf'
|
||||||
|
);
|
||||||
|
|
||||||
const handler = Sinon.spy(event, 'emit');
|
const handler = Sinon.spy(event, 'emit');
|
||||||
|
|
||||||
@@ -1345,7 +1349,7 @@ test('should be able to manage context', async t => {
|
|||||||
'should list file id'
|
'should list file id'
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = await session.matchFileChunks('test', 1, undefined, 1);
|
const result = await session.matchFiles('test', 1, undefined, 1);
|
||||||
t.is(result.length, 1, 'should match context');
|
t.is(result.length, 1, 'should match context');
|
||||||
t.is(result[0].fileId, file.id, 'should match file id');
|
t.is(result[0].fileId, file.id, 'should match file id');
|
||||||
}
|
}
|
||||||
@@ -1499,13 +1503,13 @@ test('should be able to manage workspace embedding', async t => {
|
|||||||
});
|
});
|
||||||
const contextSession = await context.create(sessionId);
|
const contextSession = await context.create(sessionId);
|
||||||
|
|
||||||
const ret = await contextSession.matchFileChunks('test', 1, undefined, 1);
|
const ret = await contextSession.matchFiles('test', 1, undefined, 1);
|
||||||
t.is(ret.length, 1, 'should match workspace context');
|
t.is(ret.length, 1, 'should match workspace context');
|
||||||
t.is(ret[0].content, 'content', 'should match content');
|
t.is(ret[0].content, 'content', 'should match content');
|
||||||
|
|
||||||
await workspace.update(ws.id, { enableDocEmbedding: false });
|
await workspace.update(ws.id, { enableDocEmbedding: false });
|
||||||
|
|
||||||
const ret2 = await contextSession.matchFileChunks('test', 1, undefined, 1);
|
const ret2 = await contextSession.matchFiles('test', 1, undefined, 1);
|
||||||
t.is(ret2.length, 0, 'should not match workspace context');
|
t.is(ret2.length, 0, 'should not match workspace context');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
|
|
||||||
## should insert embedding by doc id
|
## should insert embedding by doc id
|
||||||
|
|
||||||
|
> should match file embedding
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
chunk: 0,
|
||||||
|
content: 'content',
|
||||||
|
distance: 0,
|
||||||
|
fileId: 'file-id',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
> should return empty array when embedding deleted
|
> should return empty array when embedding deleted
|
||||||
|
|
||||||
[]
|
[]
|
||||||
|
|||||||
Binary file not shown.
@@ -54,3 +54,18 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
docId: 'new_doc',
|
docId: 'new_doc',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
## should insert and search embedding
|
||||||
|
|
||||||
|
> should match workspace file embedding
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
blobId: 'blob1',
|
||||||
|
chunk: 0,
|
||||||
|
content: 'content',
|
||||||
|
distance: 0,
|
||||||
|
mimeType: 'text/plain',
|
||||||
|
name: 'file1',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|||||||
Binary file not shown.
@@ -111,8 +111,7 @@ test('should insert embedding by doc id', async t => {
|
|||||||
1,
|
1,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
t.is(ret.length, 1);
|
t.snapshot(ret, 'should match file embedding');
|
||||||
t.is(ret[0].content, 'content');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ test('should insert and search embedding', async t => {
|
|||||||
{
|
{
|
||||||
const { fileId } = await t.context.copilotWorkspace.addFile(workspace.id, {
|
const { fileId } = await t.context.copilotWorkspace.addFile(workspace.id, {
|
||||||
fileName: 'file1',
|
fileName: 'file1',
|
||||||
|
blobId: 'blob1',
|
||||||
mimeType: 'text/plain',
|
mimeType: 'text/plain',
|
||||||
size: 1,
|
size: 1,
|
||||||
});
|
});
|
||||||
@@ -129,8 +130,10 @@ test('should insert and search embedding', async t => {
|
|||||||
1,
|
1,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
t.is(ret.length, 1);
|
t.snapshot(
|
||||||
t.is(ret[0].content, 'content');
|
cleanObject(ret, ['fileId']),
|
||||||
|
'should match workspace file embedding'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -714,6 +714,12 @@ export const USER_FRIENDLY_ERRORS = {
|
|||||||
message: ({ contextId, content, message }) =>
|
message: ({ contextId, content, message }) =>
|
||||||
`Failed to match context ${contextId} with "${escape(content)}": ${message}`,
|
`Failed to match context ${contextId} with "${escape(content)}": ${message}`,
|
||||||
},
|
},
|
||||||
|
copilot_failed_to_match_global_context: {
|
||||||
|
type: 'internal_server_error',
|
||||||
|
args: { workspaceId: 'string', content: 'string', message: 'string' },
|
||||||
|
message: ({ workspaceId, content, message }) =>
|
||||||
|
`Failed to match context in workspace ${workspaceId} with "${escape(content)}": ${message}`,
|
||||||
|
},
|
||||||
copilot_embedding_disabled: {
|
copilot_embedding_disabled: {
|
||||||
type: 'action_forbidden',
|
type: 'action_forbidden',
|
||||||
message: `Embedding feature is disabled, please contact the administrator to enable it in the workspace settings.`,
|
message: `Embedding feature is disabled, please contact the administrator to enable it in the workspace settings.`,
|
||||||
|
|||||||
@@ -780,6 +780,18 @@ export class CopilotFailedToMatchContext extends UserFriendlyError {
|
|||||||
super('internal_server_error', 'copilot_failed_to_match_context', message, args);
|
super('internal_server_error', 'copilot_failed_to_match_context', message, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ObjectType()
|
||||||
|
class CopilotFailedToMatchGlobalContextDataType {
|
||||||
|
@Field() workspaceId!: string
|
||||||
|
@Field() content!: string
|
||||||
|
@Field() message!: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CopilotFailedToMatchGlobalContext extends UserFriendlyError {
|
||||||
|
constructor(args: CopilotFailedToMatchGlobalContextDataType, message?: string | ((args: CopilotFailedToMatchGlobalContextDataType) => string)) {
|
||||||
|
super('internal_server_error', 'copilot_failed_to_match_global_context', message, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class CopilotEmbeddingDisabled extends UserFriendlyError {
|
export class CopilotEmbeddingDisabled extends UserFriendlyError {
|
||||||
constructor(message?: string) {
|
constructor(message?: string) {
|
||||||
@@ -1078,6 +1090,7 @@ export enum ErrorNames {
|
|||||||
COPILOT_CONTEXT_FILE_NOT_SUPPORTED,
|
COPILOT_CONTEXT_FILE_NOT_SUPPORTED,
|
||||||
COPILOT_FAILED_TO_MODIFY_CONTEXT,
|
COPILOT_FAILED_TO_MODIFY_CONTEXT,
|
||||||
COPILOT_FAILED_TO_MATCH_CONTEXT,
|
COPILOT_FAILED_TO_MATCH_CONTEXT,
|
||||||
|
COPILOT_FAILED_TO_MATCH_GLOBAL_CONTEXT,
|
||||||
COPILOT_EMBEDDING_DISABLED,
|
COPILOT_EMBEDDING_DISABLED,
|
||||||
COPILOT_EMBEDDING_UNAVAILABLE,
|
COPILOT_EMBEDDING_UNAVAILABLE,
|
||||||
COPILOT_TRANSCRIPTION_JOB_EXISTS,
|
COPILOT_TRANSCRIPTION_JOB_EXISTS,
|
||||||
@@ -1114,5 +1127,5 @@ registerEnumType(ErrorNames, {
|
|||||||
export const ErrorDataUnionType = createUnionType({
|
export const ErrorDataUnionType = createUnionType({
|
||||||
name: 'ErrorDataUnion',
|
name: 'ErrorDataUnion',
|
||||||
types: () =>
|
types: () =>
|
||||||
[GraphqlBadRequestDataType, HttpRequestErrorDataType, QueryTooLongDataType, ValidationErrorDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, InvalidOauthCallbackCodeDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, WorkspacePermissionNotFoundDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, SpaceShouldHaveOnlyOneOwnerDataType, DocNotFoundDataType, DocActionDeniedDataType, DocUpdateBlockedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, ExpectToGrantDocUserRolesDataType, ExpectToRevokeDocUserRolesDataType, ExpectToUpdateDocUserRoleDataType, NoMoreSeatDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotDocNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, CopilotInvalidContextDataType, CopilotContextFileNotSupportedDataType, CopilotFailedToModifyContextDataType, CopilotFailedToMatchContextDataType, CopilotFailedToAddWorkspaceFileEmbeddingDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseToActivateDataType, InvalidLicenseUpdateParamsDataType, UnsupportedClientVersionDataType, MentionUserDocAccessDeniedDataType] as const,
|
[GraphqlBadRequestDataType, HttpRequestErrorDataType, QueryTooLongDataType, ValidationErrorDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, InvalidOauthCallbackCodeDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, WorkspacePermissionNotFoundDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, SpaceShouldHaveOnlyOneOwnerDataType, DocNotFoundDataType, DocActionDeniedDataType, DocUpdateBlockedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, ExpectToGrantDocUserRolesDataType, ExpectToRevokeDocUserRolesDataType, ExpectToUpdateDocUserRoleDataType, NoMoreSeatDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotDocNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, CopilotInvalidContextDataType, CopilotContextFileNotSupportedDataType, CopilotFailedToModifyContextDataType, CopilotFailedToMatchContextDataType, CopilotFailedToMatchGlobalContextDataType, CopilotFailedToAddWorkspaceFileEmbeddingDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseToActivateDataType, InvalidLicenseUpdateParamsDataType, UnsupportedClientVersionDataType, MentionUserDocAccessDeniedDataType] as const,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const ContextFileSchema = z.object({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
chunkSize: z.number(),
|
chunkSize: z.number(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
mimeType: z.string().optional(),
|
||||||
status: ContextEmbedStatusSchema,
|
status: ContextEmbedStatusSchema,
|
||||||
error: z.string().nullable(),
|
error: z.string().nullable(),
|
||||||
blobId: z.string(),
|
blobId: z.string(),
|
||||||
@@ -100,6 +101,9 @@ export type ChunkSimilarity = {
|
|||||||
|
|
||||||
export type FileChunkSimilarity = ChunkSimilarity & {
|
export type FileChunkSimilarity = ChunkSimilarity & {
|
||||||
fileId: string;
|
fileId: string;
|
||||||
|
blobId: string;
|
||||||
|
name: string;
|
||||||
|
mimeType: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DocChunkSimilarity = ChunkSimilarity & {
|
export type DocChunkSimilarity = ChunkSimilarity & {
|
||||||
@@ -108,6 +112,7 @@ export type DocChunkSimilarity = ChunkSimilarity & {
|
|||||||
|
|
||||||
export const CopilotWorkspaceFileSchema = z.object({
|
export const CopilotWorkspaceFileSchema = z.object({
|
||||||
fileName: z.string(),
|
fileName: z.string(),
|
||||||
|
blobId: z.string(),
|
||||||
mimeType: z.string(),
|
mimeType: z.string(),
|
||||||
size: z.number(),
|
size: z.number(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Transactional } from '@nestjs-cls/transactional';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
import { CopilotSessionNotFound } from '../base';
|
import { CopilotSessionNotFound } from '../base';
|
||||||
@@ -179,9 +180,9 @@ export class CopilotContextModel extends BaseModel {
|
|||||||
contextId: string,
|
contextId: string,
|
||||||
topK: number,
|
topK: number,
|
||||||
threshold: number
|
threshold: number
|
||||||
): Promise<FileChunkSimilarity[]> {
|
): Promise<Omit<FileChunkSimilarity, 'blobId' | 'name' | 'mimeType'>[]> {
|
||||||
const similarityChunks = await this.db.$queryRaw<
|
const similarityChunks = await this.db.$queryRaw<
|
||||||
Array<FileChunkSimilarity>
|
Array<Omit<FileChunkSimilarity, 'blobId' | 'name' | 'mimeType'>>
|
||||||
>`
|
>`
|
||||||
SELECT "file_id" as "fileId", "chunk", "content", "embedding" <=> ${embedding}::vector as "distance"
|
SELECT "file_id" as "fileId", "chunk", "content", "embedding" <=> ${embedding}::vector as "distance"
|
||||||
FROM "ai_context_embeddings"
|
FROM "ai_context_embeddings"
|
||||||
@@ -217,6 +218,7 @@ export class CopilotContextModel extends BaseModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional()
|
||||||
async matchWorkspaceEmbedding(
|
async matchWorkspaceEmbedding(
|
||||||
embedding: number[],
|
embedding: number[],
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
@@ -232,6 +234,18 @@ export class CopilotContextModel extends BaseModel {
|
|||||||
ORDER BY "distance" ASC
|
ORDER BY "distance" ASC
|
||||||
LIMIT ${topK};
|
LIMIT ${topK};
|
||||||
`;
|
`;
|
||||||
return similarityChunks.filter(c => Number(c.distance) <= threshold);
|
|
||||||
|
const matchedChunks = similarityChunks.filter(
|
||||||
|
c => Number(c.distance) <= threshold
|
||||||
|
);
|
||||||
|
const matchedDocIds = Array.from(new Set(matchedChunks.map(c => c.docId)));
|
||||||
|
if (!matchDocIds?.length && matchedDocIds.length) {
|
||||||
|
const ignoredDocs = await this.models.copilotWorkspace.checkIgnoredDocs(
|
||||||
|
workspaceId,
|
||||||
|
matchedDocIds
|
||||||
|
);
|
||||||
|
return matchedChunks.filter(c => !ignoredDocs.includes(c.docId));
|
||||||
|
}
|
||||||
|
return matchedChunks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,25 @@ import type {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CopilotWorkspaceConfigModel extends BaseModel {
|
export class CopilotWorkspaceConfigModel extends BaseModel {
|
||||||
|
@Transactional()
|
||||||
|
private async listIgnoredDocIds(
|
||||||
|
workspaceId: string,
|
||||||
|
options?: PaginationInput
|
||||||
|
) {
|
||||||
|
return await this.db.aiWorkspaceIgnoredDocs.findMany({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
docId: true,
|
||||||
|
createdAt: true,
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
skip: options?.offset,
|
||||||
|
take: options?.first,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional()
|
@Transactional()
|
||||||
async updateIgnoredDocs(
|
async updateIgnoredDocs(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
@@ -23,7 +42,7 @@ export class CopilotWorkspaceConfigModel extends BaseModel {
|
|||||||
remove: string[] = []
|
remove: string[] = []
|
||||||
) {
|
) {
|
||||||
const removed = new Set(remove);
|
const removed = new Set(remove);
|
||||||
const ignored = await this.listIgnoredDocs(workspaceId).then(
|
const ignored = await this.listIgnoredDocIds(workspaceId).then(
|
||||||
r => new Set(r.map(r => r.docId).filter(id => !removed.has(id)))
|
r => new Set(r.map(r => r.docId).filter(id => !removed.has(id)))
|
||||||
);
|
);
|
||||||
const added = add.filter(id => !ignored.has(id));
|
const added = add.filter(id => !ignored.has(id));
|
||||||
@@ -51,25 +70,11 @@ export class CopilotWorkspaceConfigModel extends BaseModel {
|
|||||||
return added.length + ignored.size;
|
return added.length + ignored.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional()
|
|
||||||
async listIgnoredDocs(
|
async listIgnoredDocs(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
options?: {
|
options?: PaginationInput
|
||||||
includeRead?: boolean;
|
|
||||||
} & PaginationInput
|
|
||||||
): Promise<IgnoredDoc[]> {
|
): Promise<IgnoredDoc[]> {
|
||||||
const row = await this.db.aiWorkspaceIgnoredDocs.findMany({
|
const row = await this.listIgnoredDocIds(workspaceId, options);
|
||||||
where: {
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
docId: true,
|
|
||||||
createdAt: true,
|
|
||||||
},
|
|
||||||
orderBy: { createdAt: 'desc' },
|
|
||||||
skip: options?.offset,
|
|
||||||
take: options?.first,
|
|
||||||
});
|
|
||||||
const ids = row.map(r => ({ workspaceId, docId: r.docId }));
|
const ids = row.map(r => ({ workspaceId, docId: r.docId }));
|
||||||
const docs = await this.models.doc.findMetas(ids);
|
const docs = await this.models.doc.findMetas(ids);
|
||||||
const docsMap = new Map(
|
const docsMap = new Map(
|
||||||
@@ -106,7 +111,7 @@ export class CopilotWorkspaceConfigModel extends BaseModel {
|
|||||||
|
|
||||||
@Transactional()
|
@Transactional()
|
||||||
async checkIgnoredDocs(workspaceId: string, docIds: string[]) {
|
async checkIgnoredDocs(workspaceId: string, docIds: string[]) {
|
||||||
const ignored = await this.listIgnoredDocs(workspaceId).then(
|
const ignored = await this.listIgnoredDocIds(workspaceId).then(
|
||||||
r => new Set(r.map(r => r.docId))
|
r => new Set(r.map(r => r.docId))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -214,9 +219,19 @@ export class CopilotWorkspaceConfigModel extends BaseModel {
|
|||||||
const similarityChunks = await this.db.$queryRaw<
|
const similarityChunks = await this.db.$queryRaw<
|
||||||
Array<FileChunkSimilarity>
|
Array<FileChunkSimilarity>
|
||||||
>`
|
>`
|
||||||
SELECT "file_id" as "fileId", "chunk", "content", "embedding" <=> ${embedding}::vector as "distance"
|
SELECT
|
||||||
FROM "ai_workspace_file_embeddings"
|
e."file_id" as "fileId",
|
||||||
WHERE workspace_id = ${workspaceId}
|
f."file_name" as "name",
|
||||||
|
f."blob_id" as "blobId",
|
||||||
|
f."mime_type" as "mimeType",
|
||||||
|
e."chunk",
|
||||||
|
e."content",
|
||||||
|
e."embedding" <=> ${embedding}::vector as "distance"
|
||||||
|
FROM "ai_workspace_file_embeddings" e
|
||||||
|
JOIN "ai_workspace_files" f
|
||||||
|
ON e."workspace_id" = f."workspace_id"
|
||||||
|
AND e."file_id" = f."file_id"
|
||||||
|
WHERE e.workspace_id = ${workspaceId}
|
||||||
ORDER BY "distance" ASC
|
ORDER BY "distance" ASC
|
||||||
LIMIT ${topK};
|
LIMIT ${topK};
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
CallMetric,
|
CallMetric,
|
||||||
CopilotEmbeddingUnavailable,
|
CopilotEmbeddingUnavailable,
|
||||||
CopilotFailedToMatchContext,
|
CopilotFailedToMatchContext,
|
||||||
|
CopilotFailedToMatchGlobalContext,
|
||||||
CopilotFailedToModifyContext,
|
CopilotFailedToModifyContext,
|
||||||
CopilotSessionNotFound,
|
CopilotSessionNotFound,
|
||||||
EventBus,
|
EventBus,
|
||||||
@@ -117,8 +118,8 @@ class RemoveContextFileInput {
|
|||||||
|
|
||||||
@ObjectType('CopilotContext')
|
@ObjectType('CopilotContext')
|
||||||
export class CopilotContextType {
|
export class CopilotContextType {
|
||||||
@Field(() => ID)
|
@Field(() => ID, { nullable: true })
|
||||||
id!: string;
|
id!: string | undefined;
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
workspaceId!: string;
|
workspaceId!: string;
|
||||||
@@ -169,6 +170,9 @@ class CopilotContextFile implements ContextFile {
|
|||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
name!: string;
|
name!: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
mimeType!: string;
|
||||||
|
|
||||||
@Field(() => SafeIntResolver)
|
@Field(() => SafeIntResolver)
|
||||||
chunkSize!: number;
|
chunkSize!: number;
|
||||||
|
|
||||||
@@ -190,6 +194,15 @@ class ContextMatchedFileChunk implements FileChunkSimilarity {
|
|||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
fileId!: string;
|
fileId!: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
blobId!: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
mimeType!: string;
|
||||||
|
|
||||||
@Field(() => SafeIntResolver)
|
@Field(() => SafeIntResolver)
|
||||||
chunk!: number;
|
chunk!: number;
|
||||||
|
|
||||||
@@ -283,6 +296,15 @@ export class CopilotContextRootResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (copilot.workspaceId) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: undefined,
|
||||||
|
workspaceId: copilot.workspaceId,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,6 +409,9 @@ export class CopilotContextResolver {
|
|||||||
async collections(
|
async collections(
|
||||||
@Parent() context: CopilotContextType
|
@Parent() context: CopilotContextType
|
||||||
): Promise<CopilotContextCategory[]> {
|
): Promise<CopilotContextCategory[]> {
|
||||||
|
if (!context.id) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const session = await this.context.get(context.id);
|
const session = await this.context.get(context.id);
|
||||||
const collections = session.collections;
|
const collections = session.collections;
|
||||||
await this.models.copilotContext.mergeDocStatus(
|
await this.models.copilotContext.mergeDocStatus(
|
||||||
@@ -404,6 +429,9 @@ export class CopilotContextResolver {
|
|||||||
async tags(
|
async tags(
|
||||||
@Parent() context: CopilotContextType
|
@Parent() context: CopilotContextType
|
||||||
): Promise<CopilotContextCategory[]> {
|
): Promise<CopilotContextCategory[]> {
|
||||||
|
if (!context.id) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const session = await this.context.get(context.id);
|
const session = await this.context.get(context.id);
|
||||||
const tags = session.tags;
|
const tags = session.tags;
|
||||||
await this.models.copilotContext.mergeDocStatus(
|
await this.models.copilotContext.mergeDocStatus(
|
||||||
@@ -419,6 +447,9 @@ export class CopilotContextResolver {
|
|||||||
})
|
})
|
||||||
@CallMetric('ai', 'context_file_list')
|
@CallMetric('ai', 'context_file_list')
|
||||||
async docs(@Parent() context: CopilotContextType): Promise<CopilotDocType[]> {
|
async docs(@Parent() context: CopilotContextType): Promise<CopilotDocType[]> {
|
||||||
|
if (!context.id) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const session = await this.context.get(context.id);
|
const session = await this.context.get(context.id);
|
||||||
const docs = session.docs;
|
const docs = session.docs;
|
||||||
await this.models.copilotContext.mergeDocStatus(session.workspaceId, docs);
|
await this.models.copilotContext.mergeDocStatus(session.workspaceId, docs);
|
||||||
@@ -433,6 +464,9 @@ export class CopilotContextResolver {
|
|||||||
async files(
|
async files(
|
||||||
@Parent() context: CopilotContextType
|
@Parent() context: CopilotContextType
|
||||||
): Promise<CopilotContextFile[]> {
|
): Promise<CopilotContextFile[]> {
|
||||||
|
if (!context.id) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const session = await this.context.get(context.id);
|
const session = await this.context.get(context.id);
|
||||||
return session.files;
|
return session.files;
|
||||||
}
|
}
|
||||||
@@ -593,7 +627,11 @@ export class CopilotContextResolver {
|
|||||||
const session = await this.context.get(options.contextId);
|
const session = await this.context.get(options.contextId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const file = await session.addFile(options.blobId, content.filename);
|
const file = await session.addFile(
|
||||||
|
options.blobId,
|
||||||
|
content.filename,
|
||||||
|
content.mimetype
|
||||||
|
);
|
||||||
|
|
||||||
const buffer = await readStream(content.createReadStream());
|
const buffer = await readStream(content.createReadStream());
|
||||||
await this.storage.put(
|
await this.storage.put(
|
||||||
@@ -664,6 +702,8 @@ export class CopilotContextResolver {
|
|||||||
@Args('content') content: string,
|
@Args('content') content: string,
|
||||||
@Args('limit', { type: () => SafeIntResolver, nullable: true })
|
@Args('limit', { type: () => SafeIntResolver, nullable: true })
|
||||||
limit?: number,
|
limit?: number,
|
||||||
|
@Args('scopedThreshold', { type: () => Float, nullable: true })
|
||||||
|
scopedThreshold?: number,
|
||||||
@Args('threshold', { type: () => Float, nullable: true })
|
@Args('threshold', { type: () => Float, nullable: true })
|
||||||
threshold?: number
|
threshold?: number
|
||||||
): Promise<ContextMatchedFileChunk[]> {
|
): Promise<ContextMatchedFileChunk[]> {
|
||||||
@@ -671,22 +711,46 @@ export class CopilotContextResolver {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await this.context.get(context.id);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await session.matchFileChunks(
|
if (!context.id) {
|
||||||
|
return await this.context.matchWorkspaceFiles(
|
||||||
|
context.workspaceId,
|
||||||
|
content,
|
||||||
|
limit,
|
||||||
|
this.getSignal(ctx.req),
|
||||||
|
threshold
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = await this.context.get(context.id);
|
||||||
|
return await session.matchFiles(
|
||||||
content,
|
content,
|
||||||
limit,
|
limit,
|
||||||
this.getSignal(ctx.req),
|
this.getSignal(ctx.req),
|
||||||
|
scopedThreshold,
|
||||||
threshold
|
threshold
|
||||||
);
|
);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
throw new CopilotFailedToMatchContext({
|
// passthrough user friendly error
|
||||||
contextId: context.id,
|
if (e instanceof UserFriendlyError) {
|
||||||
// don't record the large content
|
throw e;
|
||||||
content: content.slice(0, 512),
|
}
|
||||||
message: e.message,
|
|
||||||
});
|
if (context.id) {
|
||||||
|
throw new CopilotFailedToMatchContext({
|
||||||
|
contextId: context.id,
|
||||||
|
// don't record the large content
|
||||||
|
content: content.slice(0, 512),
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new CopilotFailedToMatchGlobalContext({
|
||||||
|
workspaceId: context.workspaceId,
|
||||||
|
// don't record the large content
|
||||||
|
content: content.slice(0, 512),
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,20 +775,38 @@ export class CopilotContextResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const session = await this.context.get(context.id);
|
|
||||||
await this.ac
|
await this.ac
|
||||||
.user(user.id)
|
.user(user.id)
|
||||||
.workspace(session.workspaceId)
|
.workspace(context.workspaceId)
|
||||||
.allowLocal()
|
.allowLocal()
|
||||||
.assert('Workspace.Copilot');
|
.assert('Workspace.Copilot');
|
||||||
const allowEmbedding = await this.models.workspace.allowEmbedding(
|
const allowEmbedding = await this.models.workspace.allowEmbedding(
|
||||||
session.workspaceId
|
context.workspaceId
|
||||||
);
|
);
|
||||||
if (!allowEmbedding) {
|
if (!allowEmbedding) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const chunks = await session.matchWorkspaceChunks(
|
if (!context.id) {
|
||||||
|
return await this.context.matchWorkspaceDocs(
|
||||||
|
context.workspaceId,
|
||||||
|
content,
|
||||||
|
limit,
|
||||||
|
this.getSignal(ctx.req),
|
||||||
|
threshold
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = await this.context.get(context.id);
|
||||||
|
if (session.workspaceId !== context.workspaceId) {
|
||||||
|
throw new CopilotFailedToMatchContext({
|
||||||
|
contextId: context.id,
|
||||||
|
// don't record the large content
|
||||||
|
content: content.slice(0, 512),
|
||||||
|
message: 'context not in the same workspace',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const chunks = await session.matchWorkspaceDocs(
|
||||||
content,
|
content,
|
||||||
limit,
|
limit,
|
||||||
this.getSignal(ctx.req),
|
this.getSignal(ctx.req),
|
||||||
@@ -748,12 +830,22 @@ export class CopilotContextResolver {
|
|||||||
if (e instanceof UserFriendlyError) {
|
if (e instanceof UserFriendlyError) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
throw new CopilotFailedToMatchContext({
|
|
||||||
contextId: context.id,
|
if (context.id) {
|
||||||
// don't record the large content
|
throw new CopilotFailedToMatchContext({
|
||||||
content: content.slice(0, 512),
|
contextId: context.id,
|
||||||
message: e.message,
|
// don't record the large content
|
||||||
});
|
content: content.slice(0, 512),
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new CopilotFailedToMatchGlobalContext({
|
||||||
|
workspaceId: context.workspaceId,
|
||||||
|
// don't record the large content
|
||||||
|
content: content.slice(0, 512),
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,6 +148,48 @@ export class CopilotContextService implements OnApplicationBootstrap {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async matchWorkspaceFiles(
|
||||||
|
workspaceId: string,
|
||||||
|
content: string,
|
||||||
|
topK: number = 5,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
threshold: number = 0.5
|
||||||
|
) {
|
||||||
|
if (!this.embeddingClient) return [];
|
||||||
|
const embedding = await this.embeddingClient.getEmbedding(content, signal);
|
||||||
|
if (!embedding) return [];
|
||||||
|
|
||||||
|
const chunks = await this.models.copilotWorkspace.matchFileEmbedding(
|
||||||
|
workspaceId,
|
||||||
|
embedding,
|
||||||
|
topK * 2,
|
||||||
|
threshold
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.embeddingClient.reRank(content, chunks, topK, signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
async matchWorkspaceDocs(
|
||||||
|
workspaceId: string,
|
||||||
|
content: string,
|
||||||
|
topK: number = 5,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
threshold: number = 0.5
|
||||||
|
) {
|
||||||
|
if (!this.embeddingClient) return [];
|
||||||
|
const embedding = await this.embeddingClient.getEmbedding(content, signal);
|
||||||
|
if (!embedding) return [];
|
||||||
|
|
||||||
|
const workspace = await this.models.copilotContext.matchWorkspaceEmbedding(
|
||||||
|
embedding,
|
||||||
|
workspaceId,
|
||||||
|
topK * 2,
|
||||||
|
threshold
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.embeddingClient.reRank(content, workspace, topK);
|
||||||
|
}
|
||||||
|
|
||||||
@OnEvent('workspace.doc.embed.failed')
|
@OnEvent('workspace.doc.embed.failed')
|
||||||
async onDocEmbedFailed({
|
async onDocEmbedFailed({
|
||||||
contextId,
|
contextId,
|
||||||
|
|||||||
@@ -52,12 +52,16 @@ export class ContextSession implements AsyncDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get files() {
|
get files() {
|
||||||
return this.config.files.map(f => ({ ...f }));
|
return this.config.files.map(f => this.fulfillFile(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
get docIds() {
|
get docIds() {
|
||||||
return Array.from(
|
return Array.from(
|
||||||
new Set([this.config.docs, this.config.categories].flat().map(d => d.id))
|
new Set(
|
||||||
|
[this.config.docs, this.config.categories.flatMap(c => c.docs)]
|
||||||
|
.flat()
|
||||||
|
.map(d => d.id)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,14 +140,25 @@ export class ContextSession implements AsyncDisposable {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addFile(blobId: string, name: string): Promise<ContextFile> {
|
private fulfillFile(file: ContextFile): Required<ContextFile> {
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
mimeType: file.mimeType || 'application/octet-stream',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async addFile(
|
||||||
|
blobId: string,
|
||||||
|
name: string,
|
||||||
|
mimeType: string
|
||||||
|
): Promise<Required<ContextFile>> {
|
||||||
let fileId = nanoid();
|
let fileId = nanoid();
|
||||||
const existsBlob = this.config.files.find(f => f.blobId === blobId);
|
const existsBlob = this.config.files.find(f => f.blobId === blobId);
|
||||||
if (existsBlob) {
|
if (existsBlob) {
|
||||||
// use exists file id if the blob exists
|
// use exists file id if the blob exists
|
||||||
// we assume that the file content pointed to by the same blobId is consistent.
|
// we assume that the file content pointed to by the same blobId is consistent.
|
||||||
if (existsBlob.status === ContextEmbedStatus.finished) {
|
if (existsBlob.status === ContextEmbedStatus.finished) {
|
||||||
return existsBlob;
|
return this.fulfillFile(existsBlob);
|
||||||
}
|
}
|
||||||
fileId = existsBlob.id;
|
fileId = existsBlob.id;
|
||||||
} else {
|
} else {
|
||||||
@@ -152,11 +167,12 @@ export class ContextSession implements AsyncDisposable {
|
|||||||
blobId,
|
blobId,
|
||||||
chunkSize: 0,
|
chunkSize: 0,
|
||||||
name,
|
name,
|
||||||
|
mimeType,
|
||||||
error: null,
|
error: null,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return this.getFile(fileId) as ContextFile;
|
return this.fulfillFile(this.getFile(fileId) as ContextFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFile(fileId: string): ContextFile | undefined {
|
getFile(fileId: string): ContextFile | undefined {
|
||||||
@@ -181,15 +197,14 @@ export class ContextSession implements AsyncDisposable {
|
|||||||
* @param threshold relevance threshold for the similarity score, higher threshold means more similar chunks, default 0.7, good enough based on prior experiments
|
* @param threshold relevance threshold for the similarity score, higher threshold means more similar chunks, default 0.7, good enough based on prior experiments
|
||||||
* @returns list of similar chunks
|
* @returns list of similar chunks
|
||||||
*/
|
*/
|
||||||
async matchFileChunks(
|
async matchFiles(
|
||||||
content: string,
|
content: string,
|
||||||
topK: number = 5,
|
topK: number = 5,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
threshold: number = 0.85
|
scopedThreshold: number = 0.85,
|
||||||
|
threshold: number = 0.5
|
||||||
): Promise<FileChunkSimilarity[]> {
|
): Promise<FileChunkSimilarity[]> {
|
||||||
const embedding = await this.client
|
const embedding = await this.client.getEmbedding(content, signal);
|
||||||
.getEmbeddings([content], signal)
|
|
||||||
.then(r => r?.[0]?.embedding);
|
|
||||||
if (!embedding) return [];
|
if (!embedding) return [];
|
||||||
|
|
||||||
const [context, workspace] = await Promise.all([
|
const [context, workspace] = await Promise.all([
|
||||||
@@ -197,7 +212,7 @@ export class ContextSession implements AsyncDisposable {
|
|||||||
embedding,
|
embedding,
|
||||||
this.id,
|
this.id,
|
||||||
topK * 2,
|
topK * 2,
|
||||||
threshold
|
scopedThreshold
|
||||||
),
|
),
|
||||||
this.models.copilotWorkspace.matchFileEmbedding(
|
this.models.copilotWorkspace.matchFileEmbedding(
|
||||||
this.workspaceId,
|
this.workspaceId,
|
||||||
@@ -206,10 +221,21 @@ export class ContextSession implements AsyncDisposable {
|
|||||||
threshold
|
threshold
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
const files = new Map(this.files.map(f => [f.id, f]));
|
||||||
|
|
||||||
return this.client.reRank(
|
return this.client.reRank(
|
||||||
content,
|
content,
|
||||||
[...context, ...workspace],
|
[
|
||||||
|
...context
|
||||||
|
.filter(f => files.has(f.fileId))
|
||||||
|
.map(c => {
|
||||||
|
const { blobId, name, mimeType } = files.get(
|
||||||
|
c.fileId
|
||||||
|
) as Required<ContextFile>;
|
||||||
|
return { ...c, blobId, name, mimeType };
|
||||||
|
}),
|
||||||
|
...workspace,
|
||||||
|
],
|
||||||
topK,
|
topK,
|
||||||
signal
|
signal
|
||||||
);
|
);
|
||||||
@@ -223,16 +249,14 @@ export class ContextSession implements AsyncDisposable {
|
|||||||
* @param threshold relevance threshold for the similarity score, higher threshold means more similar chunks, default 0.7, good enough based on prior experiments
|
* @param threshold relevance threshold for the similarity score, higher threshold means more similar chunks, default 0.7, good enough based on prior experiments
|
||||||
* @returns list of similar chunks
|
* @returns list of similar chunks
|
||||||
*/
|
*/
|
||||||
async matchWorkspaceChunks(
|
async matchWorkspaceDocs(
|
||||||
content: string,
|
content: string,
|
||||||
topK: number = 5,
|
topK: number = 5,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
scopedThreshold: number = 0.5,
|
scopedThreshold: number = 0.85,
|
||||||
threshold: number = 0.85
|
threshold: number = 0.5
|
||||||
) {
|
) {
|
||||||
const embedding = await this.client
|
const embedding = await this.client.getEmbedding(content, signal);
|
||||||
.getEmbeddings([content], signal)
|
|
||||||
.then(r => r?.[0]?.embedding);
|
|
||||||
if (!embedding) return [];
|
if (!embedding) return [];
|
||||||
|
|
||||||
const docIds = this.docIds;
|
const docIds = this.docIds;
|
||||||
|
|||||||
@@ -128,6 +128,11 @@ export abstract class EmbeddingClient {
|
|||||||
.slice(0, topK);
|
.slice(0, topK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getEmbedding(query: string, signal?: AbortSignal) {
|
||||||
|
const embedding = await this.getEmbeddings([query], signal);
|
||||||
|
return embedding?.[0]?.embedding;
|
||||||
|
}
|
||||||
|
|
||||||
abstract getEmbeddings(
|
abstract getEmbeddings(
|
||||||
input: string[],
|
input: string[],
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export class CopilotWorkspaceService implements OnApplicationBootstrap {
|
|||||||
await this.storage.put(userId, workspaceId, blobId, buffer);
|
await this.storage.put(userId, workspaceId, blobId, buffer);
|
||||||
const file = await this.models.copilotWorkspace.addFile(workspaceId, {
|
const file = await this.models.copilotWorkspace.addFile(workspaceId, {
|
||||||
fileName,
|
fileName,
|
||||||
|
blobId,
|
||||||
mimeType: content.mimetype,
|
mimeType: content.mimetype,
|
||||||
size: buffer.length,
|
size: buffer.length,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ export class CopilotWorkspaceFileType implements CopilotWorkspaceFile {
|
|||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
fileId!: string;
|
fileId!: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
blobId!: string;
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
fileName!: string;
|
fileName!: string;
|
||||||
|
|
||||||
|
|||||||
@@ -69,10 +69,13 @@ type ContextMatchedDocChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ContextMatchedFileChunk {
|
type ContextMatchedFileChunk {
|
||||||
|
blobId: String!
|
||||||
chunk: SafeInt!
|
chunk: SafeInt!
|
||||||
content: String!
|
content: String!
|
||||||
distance: Float
|
distance: Float
|
||||||
fileId: String!
|
fileId: String!
|
||||||
|
mimeType: String!
|
||||||
|
name: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContextWorkspaceEmbeddingStatus {
|
type ContextWorkspaceEmbeddingStatus {
|
||||||
@@ -107,10 +110,10 @@ type CopilotContext {
|
|||||||
|
|
||||||
"""list files in context"""
|
"""list files in context"""
|
||||||
files: [CopilotContextFile!]!
|
files: [CopilotContextFile!]!
|
||||||
id: ID!
|
id: ID
|
||||||
|
|
||||||
"""match file in context"""
|
"""match file in context"""
|
||||||
matchFiles(content: String!, limit: SafeInt, threshold: Float): [ContextMatchedFileChunk!]!
|
matchFiles(content: String!, limit: SafeInt, scopedThreshold: Float, threshold: Float): [ContextMatchedFileChunk!]!
|
||||||
|
|
||||||
"""match workspace docs"""
|
"""match workspace docs"""
|
||||||
matchWorkspaceDocs(content: String!, limit: SafeInt, scopedThreshold: Float, threshold: Float): [ContextMatchedDocChunk!]!
|
matchWorkspaceDocs(content: String!, limit: SafeInt, scopedThreshold: Float, threshold: Float): [ContextMatchedDocChunk!]!
|
||||||
@@ -140,6 +143,7 @@ type CopilotContextFile {
|
|||||||
createdAt: SafeInt!
|
createdAt: SafeInt!
|
||||||
error: String
|
error: String
|
||||||
id: ID!
|
id: ID!
|
||||||
|
mimeType: String!
|
||||||
name: String!
|
name: String!
|
||||||
status: ContextEmbedStatus!
|
status: ContextEmbedStatus!
|
||||||
}
|
}
|
||||||
@@ -169,6 +173,12 @@ type CopilotFailedToMatchContextDataType {
|
|||||||
message: String!
|
message: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CopilotFailedToMatchGlobalContextDataType {
|
||||||
|
content: String!
|
||||||
|
message: String!
|
||||||
|
workspaceId: String!
|
||||||
|
}
|
||||||
|
|
||||||
type CopilotFailedToModifyContextDataType {
|
type CopilotFailedToModifyContextDataType {
|
||||||
contextId: String!
|
contextId: String!
|
||||||
message: String!
|
message: String!
|
||||||
@@ -280,6 +290,7 @@ type CopilotWorkspaceConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CopilotWorkspaceFile {
|
type CopilotWorkspaceFile {
|
||||||
|
blobId: String!
|
||||||
createdAt: DateTime!
|
createdAt: DateTime!
|
||||||
fileId: String!
|
fileId: String!
|
||||||
fileName: String!
|
fileName: String!
|
||||||
@@ -464,7 +475,7 @@ type EditorType {
|
|||||||
name: String!
|
name: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotContextFileNotSupportedDataType | CopilotDocNotFoundDataType | CopilotFailedToAddWorkspaceFileEmbeddingDataType | CopilotFailedToMatchContextDataType | CopilotFailedToModifyContextDataType | CopilotInvalidContextDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocActionDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | DocUpdateBlockedDataType | ExpectToGrantDocUserRolesDataType | ExpectToRevokeDocUserRolesDataType | ExpectToUpdateDocUserRoleDataType | GraphqlBadRequestDataType | HttpRequestErrorDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseToActivateDataType | InvalidLicenseUpdateParamsDataType | InvalidOauthCallbackCodeDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MemberNotFoundInSpaceDataType | MentionUserDocAccessDeniedDataType | MissingOauthQueryParameterDataType | NoMoreSeatDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SpaceShouldHaveOnlyOneOwnerDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedClientVersionDataType | UnsupportedSubscriptionPlanDataType | ValidationErrorDataType | VersionRejectedDataType | WorkspacePermissionNotFoundDataType | WrongSignInCredentialsDataType
|
union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotContextFileNotSupportedDataType | CopilotDocNotFoundDataType | CopilotFailedToAddWorkspaceFileEmbeddingDataType | CopilotFailedToMatchContextDataType | CopilotFailedToMatchGlobalContextDataType | CopilotFailedToModifyContextDataType | CopilotInvalidContextDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocActionDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | DocUpdateBlockedDataType | ExpectToGrantDocUserRolesDataType | ExpectToRevokeDocUserRolesDataType | ExpectToUpdateDocUserRoleDataType | GraphqlBadRequestDataType | HttpRequestErrorDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseToActivateDataType | InvalidLicenseUpdateParamsDataType | InvalidOauthCallbackCodeDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MemberNotFoundInSpaceDataType | MentionUserDocAccessDeniedDataType | MissingOauthQueryParameterDataType | NoMoreSeatDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SpaceShouldHaveOnlyOneOwnerDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedClientVersionDataType | UnsupportedSubscriptionPlanDataType | ValidationErrorDataType | VersionRejectedDataType | WorkspacePermissionNotFoundDataType | WrongSignInCredentialsDataType
|
||||||
|
|
||||||
enum ErrorNames {
|
enum ErrorNames {
|
||||||
ACCESS_DENIED
|
ACCESS_DENIED
|
||||||
@@ -491,6 +502,7 @@ enum ErrorNames {
|
|||||||
COPILOT_FAILED_TO_CREATE_MESSAGE
|
COPILOT_FAILED_TO_CREATE_MESSAGE
|
||||||
COPILOT_FAILED_TO_GENERATE_TEXT
|
COPILOT_FAILED_TO_GENERATE_TEXT
|
||||||
COPILOT_FAILED_TO_MATCH_CONTEXT
|
COPILOT_FAILED_TO_MATCH_CONTEXT
|
||||||
|
COPILOT_FAILED_TO_MATCH_GLOBAL_CONTEXT
|
||||||
COPILOT_FAILED_TO_MODIFY_CONTEXT
|
COPILOT_FAILED_TO_MODIFY_CONTEXT
|
||||||
COPILOT_INVALID_CONTEXT
|
COPILOT_INVALID_CONTEXT
|
||||||
COPILOT_MESSAGE_NOT_FOUND
|
COPILOT_MESSAGE_NOT_FOUND
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mutation addContextFile($content: Upload!, $options: AddContextFileInput!) {
|
|||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
name
|
name
|
||||||
|
mimeType
|
||||||
chunkSize
|
chunkSize
|
||||||
error
|
error
|
||||||
status
|
status
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ query listContextObject(
|
|||||||
files {
|
files {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
mimeType
|
||||||
blobId
|
blobId
|
||||||
chunkSize
|
chunkSize
|
||||||
error
|
error
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
query matchContext($contextId: String!, $content: String!, $limit: SafeInt, $threshold: Float) {
|
query matchContext($contextId: String, $workspaceId: String, $content: String!, $limit: SafeInt, $scopedThreshold: Float, $threshold: Float) {
|
||||||
currentUser {
|
currentUser {
|
||||||
copilot {
|
copilot(workspaceId: $workspaceId) {
|
||||||
contexts(contextId: $contextId) {
|
contexts(contextId: $contextId) {
|
||||||
matchFiles(content: $content, limit: $limit, threshold: $threshold) {
|
matchFiles(content: $content, limit: $limit, scopedThreshold: $scopedThreshold, threshold: $threshold) {
|
||||||
fileId
|
fileId
|
||||||
|
blobId
|
||||||
|
name
|
||||||
|
mimeType
|
||||||
chunk
|
chunk
|
||||||
content
|
content
|
||||||
distance
|
distance
|
||||||
}
|
}
|
||||||
matchWorkspaceDocs(content: $content, limit: $limit, threshold: $threshold) {
|
matchWorkspaceDocs(content: $content, limit: $limit, scopedThreshold: $scopedThreshold, threshold: $threshold) {
|
||||||
docId
|
docId
|
||||||
chunk
|
chunk
|
||||||
content
|
content
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
query matchWorkspaceDocs($contextId: String!, $content: String!, $limit: SafeInt, $scopedThreshold: Float, $threshold: Float) {
|
query matchWorkspaceDocs($contextId: String, $workspaceId: String, $content: String!, $limit: SafeInt, $scopedThreshold: Float, $threshold: Float) {
|
||||||
currentUser {
|
currentUser {
|
||||||
copilot {
|
copilot(workspaceId: $workspaceId) {
|
||||||
contexts(contextId: $contextId) {
|
contexts(contextId: $contextId) {
|
||||||
matchWorkspaceDocs(content: $content, limit: $limit, scopedThreshold: $scopedThreshold, threshold: $threshold) {
|
matchWorkspaceDocs(content: $content, limit: $limit, scopedThreshold: $scopedThreshold, threshold: $threshold) {
|
||||||
docId
|
docId
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
query matchFiles($contextId: String!, $content: String!, $limit: SafeInt, $threshold: Float) {
|
query matchFiles($contextId: String, $workspaceId: String, $content: String!, $limit: SafeInt, $scopedThreshold: Float, $threshold: Float) {
|
||||||
currentUser {
|
currentUser {
|
||||||
copilot {
|
copilot(workspaceId: $workspaceId) {
|
||||||
contexts(contextId: $contextId) {
|
contexts(contextId: $contextId) {
|
||||||
matchFiles(content: $content, limit: $limit, threshold: $threshold) {
|
matchFiles(content: $content, limit: $limit, scopedThreshold: $scopedThreshold, threshold: $threshold) {
|
||||||
fileId
|
fileId
|
||||||
|
blobId
|
||||||
chunk
|
chunk
|
||||||
content
|
content
|
||||||
distance
|
distance
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ mutation addWorkspaceEmbeddingFiles($workspaceId: String!, $blob: Upload!) {
|
|||||||
addWorkspaceEmbeddingFiles(workspaceId: $workspaceId, blob: $blob) {
|
addWorkspaceEmbeddingFiles(workspaceId: $workspaceId, blob: $blob) {
|
||||||
fileId
|
fileId
|
||||||
fileName
|
fileName
|
||||||
|
blobId
|
||||||
mimeType
|
mimeType
|
||||||
size
|
size
|
||||||
createdAt
|
createdAt
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ query getWorkspaceEmbeddingFiles($workspaceId: String!, $pagination: PaginationI
|
|||||||
node {
|
node {
|
||||||
fileId
|
fileId
|
||||||
fileName
|
fileName
|
||||||
|
blobId
|
||||||
mimeType
|
mimeType
|
||||||
size
|
size
|
||||||
createdAt
|
createdAt
|
||||||
|
|||||||
@@ -390,6 +390,7 @@ export const addContextFileMutation = {
|
|||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
name
|
name
|
||||||
|
mimeType
|
||||||
chunkSize
|
chunkSize
|
||||||
error
|
error
|
||||||
status
|
status
|
||||||
@@ -423,6 +424,7 @@ export const listContextObjectQuery = {
|
|||||||
files {
|
files {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
mimeType
|
||||||
blobId
|
blobId
|
||||||
chunkSize
|
chunkSize
|
||||||
error
|
error
|
||||||
@@ -473,17 +475,30 @@ export const listContextQuery = {
|
|||||||
export const matchContextQuery = {
|
export const matchContextQuery = {
|
||||||
id: 'matchContextQuery' as const,
|
id: 'matchContextQuery' as const,
|
||||||
op: 'matchContext',
|
op: 'matchContext',
|
||||||
query: `query matchContext($contextId: String!, $content: String!, $limit: SafeInt, $threshold: Float) {
|
query: `query matchContext($contextId: String, $workspaceId: String, $content: String!, $limit: SafeInt, $scopedThreshold: Float, $threshold: Float) {
|
||||||
currentUser {
|
currentUser {
|
||||||
copilot {
|
copilot(workspaceId: $workspaceId) {
|
||||||
contexts(contextId: $contextId) {
|
contexts(contextId: $contextId) {
|
||||||
matchFiles(content: $content, limit: $limit, threshold: $threshold) {
|
matchFiles(
|
||||||
|
content: $content
|
||||||
|
limit: $limit
|
||||||
|
scopedThreshold: $scopedThreshold
|
||||||
|
threshold: $threshold
|
||||||
|
) {
|
||||||
fileId
|
fileId
|
||||||
|
blobId
|
||||||
|
name
|
||||||
|
mimeType
|
||||||
chunk
|
chunk
|
||||||
content
|
content
|
||||||
distance
|
distance
|
||||||
}
|
}
|
||||||
matchWorkspaceDocs(content: $content, limit: $limit, threshold: $threshold) {
|
matchWorkspaceDocs(
|
||||||
|
content: $content
|
||||||
|
limit: $limit
|
||||||
|
scopedThreshold: $scopedThreshold
|
||||||
|
threshold: $threshold
|
||||||
|
) {
|
||||||
docId
|
docId
|
||||||
chunk
|
chunk
|
||||||
content
|
content
|
||||||
@@ -498,9 +513,9 @@ export const matchContextQuery = {
|
|||||||
export const matchWorkspaceDocsQuery = {
|
export const matchWorkspaceDocsQuery = {
|
||||||
id: 'matchWorkspaceDocsQuery' as const,
|
id: 'matchWorkspaceDocsQuery' as const,
|
||||||
op: 'matchWorkspaceDocs',
|
op: 'matchWorkspaceDocs',
|
||||||
query: `query matchWorkspaceDocs($contextId: String!, $content: String!, $limit: SafeInt, $scopedThreshold: Float, $threshold: Float) {
|
query: `query matchWorkspaceDocs($contextId: String, $workspaceId: String, $content: String!, $limit: SafeInt, $scopedThreshold: Float, $threshold: Float) {
|
||||||
currentUser {
|
currentUser {
|
||||||
copilot {
|
copilot(workspaceId: $workspaceId) {
|
||||||
contexts(contextId: $contextId) {
|
contexts(contextId: $contextId) {
|
||||||
matchWorkspaceDocs(
|
matchWorkspaceDocs(
|
||||||
content: $content
|
content: $content
|
||||||
@@ -522,12 +537,18 @@ export const matchWorkspaceDocsQuery = {
|
|||||||
export const matchFilesQuery = {
|
export const matchFilesQuery = {
|
||||||
id: 'matchFilesQuery' as const,
|
id: 'matchFilesQuery' as const,
|
||||||
op: 'matchFiles',
|
op: 'matchFiles',
|
||||||
query: `query matchFiles($contextId: String!, $content: String!, $limit: SafeInt, $threshold: Float) {
|
query: `query matchFiles($contextId: String, $workspaceId: String, $content: String!, $limit: SafeInt, $scopedThreshold: Float, $threshold: Float) {
|
||||||
currentUser {
|
currentUser {
|
||||||
copilot {
|
copilot(workspaceId: $workspaceId) {
|
||||||
contexts(contextId: $contextId) {
|
contexts(contextId: $contextId) {
|
||||||
matchFiles(content: $content, limit: $limit, threshold: $threshold) {
|
matchFiles(
|
||||||
|
content: $content
|
||||||
|
limit: $limit
|
||||||
|
scopedThreshold: $scopedThreshold
|
||||||
|
threshold: $threshold
|
||||||
|
) {
|
||||||
fileId
|
fileId
|
||||||
|
blobId
|
||||||
chunk
|
chunk
|
||||||
content
|
content
|
||||||
distance
|
distance
|
||||||
@@ -750,6 +771,7 @@ export const addWorkspaceEmbeddingFilesMutation = {
|
|||||||
addWorkspaceEmbeddingFiles(workspaceId: $workspaceId, blob: $blob) {
|
addWorkspaceEmbeddingFiles(workspaceId: $workspaceId, blob: $blob) {
|
||||||
fileId
|
fileId
|
||||||
fileName
|
fileName
|
||||||
|
blobId
|
||||||
mimeType
|
mimeType
|
||||||
size
|
size
|
||||||
createdAt
|
createdAt
|
||||||
@@ -774,6 +796,7 @@ export const getWorkspaceEmbeddingFilesQuery = {
|
|||||||
node {
|
node {
|
||||||
fileId
|
fileId
|
||||||
fileName
|
fileName
|
||||||
|
blobId
|
||||||
mimeType
|
mimeType
|
||||||
size
|
size
|
||||||
createdAt
|
createdAt
|
||||||
|
|||||||
@@ -109,10 +109,13 @@ export interface ContextMatchedDocChunk {
|
|||||||
|
|
||||||
export interface ContextMatchedFileChunk {
|
export interface ContextMatchedFileChunk {
|
||||||
__typename?: 'ContextMatchedFileChunk';
|
__typename?: 'ContextMatchedFileChunk';
|
||||||
|
blobId: Scalars['String']['output'];
|
||||||
chunk: Scalars['SafeInt']['output'];
|
chunk: Scalars['SafeInt']['output'];
|
||||||
content: Scalars['String']['output'];
|
content: Scalars['String']['output'];
|
||||||
distance: Maybe<Scalars['Float']['output']>;
|
distance: Maybe<Scalars['Float']['output']>;
|
||||||
fileId: Scalars['String']['output'];
|
fileId: Scalars['String']['output'];
|
||||||
|
mimeType: Scalars['String']['output'];
|
||||||
|
name: Scalars['String']['output'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContextWorkspaceEmbeddingStatus {
|
export interface ContextWorkspaceEmbeddingStatus {
|
||||||
@@ -172,7 +175,7 @@ export interface CopilotContext {
|
|||||||
docs: Array<CopilotContextDoc>;
|
docs: Array<CopilotContextDoc>;
|
||||||
/** list files in context */
|
/** list files in context */
|
||||||
files: Array<CopilotContextFile>;
|
files: Array<CopilotContextFile>;
|
||||||
id: Scalars['ID']['output'];
|
id: Maybe<Scalars['ID']['output']>;
|
||||||
/** match file in context */
|
/** match file in context */
|
||||||
matchFiles: Array<ContextMatchedFileChunk>;
|
matchFiles: Array<ContextMatchedFileChunk>;
|
||||||
/** match workspace docs */
|
/** match workspace docs */
|
||||||
@@ -185,6 +188,7 @@ export interface CopilotContext {
|
|||||||
export interface CopilotContextMatchFilesArgs {
|
export interface CopilotContextMatchFilesArgs {
|
||||||
content: Scalars['String']['input'];
|
content: Scalars['String']['input'];
|
||||||
limit?: InputMaybe<Scalars['SafeInt']['input']>;
|
limit?: InputMaybe<Scalars['SafeInt']['input']>;
|
||||||
|
scopedThreshold?: InputMaybe<Scalars['Float']['input']>;
|
||||||
threshold?: InputMaybe<Scalars['Float']['input']>;
|
threshold?: InputMaybe<Scalars['Float']['input']>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +222,7 @@ export interface CopilotContextFile {
|
|||||||
createdAt: Scalars['SafeInt']['output'];
|
createdAt: Scalars['SafeInt']['output'];
|
||||||
error: Maybe<Scalars['String']['output']>;
|
error: Maybe<Scalars['String']['output']>;
|
||||||
id: Scalars['ID']['output'];
|
id: Scalars['ID']['output'];
|
||||||
|
mimeType: Scalars['String']['output'];
|
||||||
name: Scalars['String']['output'];
|
name: Scalars['String']['output'];
|
||||||
status: ContextEmbedStatus;
|
status: ContextEmbedStatus;
|
||||||
}
|
}
|
||||||
@@ -252,6 +257,13 @@ export interface CopilotFailedToMatchContextDataType {
|
|||||||
message: Scalars['String']['output'];
|
message: Scalars['String']['output'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CopilotFailedToMatchGlobalContextDataType {
|
||||||
|
__typename?: 'CopilotFailedToMatchGlobalContextDataType';
|
||||||
|
content: Scalars['String']['output'];
|
||||||
|
message: Scalars['String']['output'];
|
||||||
|
workspaceId: Scalars['String']['output'];
|
||||||
|
}
|
||||||
|
|
||||||
export interface CopilotFailedToModifyContextDataType {
|
export interface CopilotFailedToModifyContextDataType {
|
||||||
__typename?: 'CopilotFailedToModifyContextDataType';
|
__typename?: 'CopilotFailedToModifyContextDataType';
|
||||||
contextId: Scalars['String']['output'];
|
contextId: Scalars['String']['output'];
|
||||||
@@ -383,6 +395,7 @@ export interface CopilotWorkspaceConfigIgnoredDocsArgs {
|
|||||||
|
|
||||||
export interface CopilotWorkspaceFile {
|
export interface CopilotWorkspaceFile {
|
||||||
__typename?: 'CopilotWorkspaceFile';
|
__typename?: 'CopilotWorkspaceFile';
|
||||||
|
blobId: Scalars['String']['output'];
|
||||||
createdAt: Scalars['DateTime']['output'];
|
createdAt: Scalars['DateTime']['output'];
|
||||||
fileId: Scalars['String']['output'];
|
fileId: Scalars['String']['output'];
|
||||||
fileName: Scalars['String']['output'];
|
fileName: Scalars['String']['output'];
|
||||||
@@ -582,6 +595,7 @@ export type ErrorDataUnion =
|
|||||||
| CopilotDocNotFoundDataType
|
| CopilotDocNotFoundDataType
|
||||||
| CopilotFailedToAddWorkspaceFileEmbeddingDataType
|
| CopilotFailedToAddWorkspaceFileEmbeddingDataType
|
||||||
| CopilotFailedToMatchContextDataType
|
| CopilotFailedToMatchContextDataType
|
||||||
|
| CopilotFailedToMatchGlobalContextDataType
|
||||||
| CopilotFailedToModifyContextDataType
|
| CopilotFailedToModifyContextDataType
|
||||||
| CopilotInvalidContextDataType
|
| CopilotInvalidContextDataType
|
||||||
| CopilotMessageNotFoundDataType
|
| CopilotMessageNotFoundDataType
|
||||||
@@ -651,6 +665,7 @@ export enum ErrorNames {
|
|||||||
COPILOT_FAILED_TO_CREATE_MESSAGE = 'COPILOT_FAILED_TO_CREATE_MESSAGE',
|
COPILOT_FAILED_TO_CREATE_MESSAGE = 'COPILOT_FAILED_TO_CREATE_MESSAGE',
|
||||||
COPILOT_FAILED_TO_GENERATE_TEXT = 'COPILOT_FAILED_TO_GENERATE_TEXT',
|
COPILOT_FAILED_TO_GENERATE_TEXT = 'COPILOT_FAILED_TO_GENERATE_TEXT',
|
||||||
COPILOT_FAILED_TO_MATCH_CONTEXT = 'COPILOT_FAILED_TO_MATCH_CONTEXT',
|
COPILOT_FAILED_TO_MATCH_CONTEXT = 'COPILOT_FAILED_TO_MATCH_CONTEXT',
|
||||||
|
COPILOT_FAILED_TO_MATCH_GLOBAL_CONTEXT = 'COPILOT_FAILED_TO_MATCH_GLOBAL_CONTEXT',
|
||||||
COPILOT_FAILED_TO_MODIFY_CONTEXT = 'COPILOT_FAILED_TO_MODIFY_CONTEXT',
|
COPILOT_FAILED_TO_MODIFY_CONTEXT = 'COPILOT_FAILED_TO_MODIFY_CONTEXT',
|
||||||
COPILOT_INVALID_CONTEXT = 'COPILOT_INVALID_CONTEXT',
|
COPILOT_INVALID_CONTEXT = 'COPILOT_INVALID_CONTEXT',
|
||||||
COPILOT_MESSAGE_NOT_FOUND = 'COPILOT_MESSAGE_NOT_FOUND',
|
COPILOT_MESSAGE_NOT_FOUND = 'COPILOT_MESSAGE_NOT_FOUND',
|
||||||
@@ -2876,6 +2891,7 @@ export type AddContextFileMutation = {
|
|||||||
id: string;
|
id: string;
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
mimeType: string;
|
||||||
chunkSize: number;
|
chunkSize: number;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
status: ContextEmbedStatus;
|
status: ContextEmbedStatus;
|
||||||
@@ -2917,6 +2933,7 @@ export type ListContextObjectQuery = {
|
|||||||
__typename?: 'CopilotContextFile';
|
__typename?: 'CopilotContextFile';
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
mimeType: string;
|
||||||
blobId: string;
|
blobId: string;
|
||||||
chunkSize: number;
|
chunkSize: number;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
@@ -2965,7 +2982,7 @@ export type ListContextQuery = {
|
|||||||
__typename?: 'Copilot';
|
__typename?: 'Copilot';
|
||||||
contexts: Array<{
|
contexts: Array<{
|
||||||
__typename?: 'CopilotContext';
|
__typename?: 'CopilotContext';
|
||||||
id: string;
|
id: string | null;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
@@ -2973,9 +2990,11 @@ export type ListContextQuery = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type MatchContextQueryVariables = Exact<{
|
export type MatchContextQueryVariables = Exact<{
|
||||||
contextId: Scalars['String']['input'];
|
contextId?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||||
content: Scalars['String']['input'];
|
content: Scalars['String']['input'];
|
||||||
limit?: InputMaybe<Scalars['SafeInt']['input']>;
|
limit?: InputMaybe<Scalars['SafeInt']['input']>;
|
||||||
|
scopedThreshold?: InputMaybe<Scalars['Float']['input']>;
|
||||||
threshold?: InputMaybe<Scalars['Float']['input']>;
|
threshold?: InputMaybe<Scalars['Float']['input']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@@ -2990,6 +3009,9 @@ export type MatchContextQuery = {
|
|||||||
matchFiles: Array<{
|
matchFiles: Array<{
|
||||||
__typename?: 'ContextMatchedFileChunk';
|
__typename?: 'ContextMatchedFileChunk';
|
||||||
fileId: string;
|
fileId: string;
|
||||||
|
blobId: string;
|
||||||
|
name: string;
|
||||||
|
mimeType: string;
|
||||||
chunk: number;
|
chunk: number;
|
||||||
content: string;
|
content: string;
|
||||||
distance: number | null;
|
distance: number | null;
|
||||||
@@ -3007,7 +3029,8 @@ export type MatchContextQuery = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type MatchWorkspaceDocsQueryVariables = Exact<{
|
export type MatchWorkspaceDocsQueryVariables = Exact<{
|
||||||
contextId: Scalars['String']['input'];
|
contextId?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||||
content: Scalars['String']['input'];
|
content: Scalars['String']['input'];
|
||||||
limit?: InputMaybe<Scalars['SafeInt']['input']>;
|
limit?: InputMaybe<Scalars['SafeInt']['input']>;
|
||||||
scopedThreshold?: InputMaybe<Scalars['Float']['input']>;
|
scopedThreshold?: InputMaybe<Scalars['Float']['input']>;
|
||||||
@@ -3035,9 +3058,11 @@ export type MatchWorkspaceDocsQuery = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type MatchFilesQueryVariables = Exact<{
|
export type MatchFilesQueryVariables = Exact<{
|
||||||
contextId: Scalars['String']['input'];
|
contextId?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||||
content: Scalars['String']['input'];
|
content: Scalars['String']['input'];
|
||||||
limit?: InputMaybe<Scalars['SafeInt']['input']>;
|
limit?: InputMaybe<Scalars['SafeInt']['input']>;
|
||||||
|
scopedThreshold?: InputMaybe<Scalars['Float']['input']>;
|
||||||
threshold?: InputMaybe<Scalars['Float']['input']>;
|
threshold?: InputMaybe<Scalars['Float']['input']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@@ -3052,6 +3077,7 @@ export type MatchFilesQuery = {
|
|||||||
matchFiles: Array<{
|
matchFiles: Array<{
|
||||||
__typename?: 'ContextMatchedFileChunk';
|
__typename?: 'ContextMatchedFileChunk';
|
||||||
fileId: string;
|
fileId: string;
|
||||||
|
blobId: string;
|
||||||
chunk: number;
|
chunk: number;
|
||||||
content: string;
|
content: string;
|
||||||
distance: number | null;
|
distance: number | null;
|
||||||
@@ -3321,6 +3347,7 @@ export type AddWorkspaceEmbeddingFilesMutation = {
|
|||||||
__typename?: 'CopilotWorkspaceFile';
|
__typename?: 'CopilotWorkspaceFile';
|
||||||
fileId: string;
|
fileId: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
|
blobId: string;
|
||||||
mimeType: string;
|
mimeType: string;
|
||||||
size: number;
|
size: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@@ -3352,6 +3379,7 @@ export type GetWorkspaceEmbeddingFilesQuery = {
|
|||||||
__typename?: 'CopilotWorkspaceFile';
|
__typename?: 'CopilotWorkspaceFile';
|
||||||
fileId: string;
|
fileId: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
|
blobId: string;
|
||||||
mimeType: string;
|
mimeType: string;
|
||||||
size: number;
|
size: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
|||||||
@@ -329,9 +329,12 @@ declare global {
|
|||||||
abortSignal: AbortSignal
|
abortSignal: AbortSignal
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
matchContext: (
|
matchContext: (
|
||||||
contextId: string,
|
|
||||||
content: string,
|
content: string,
|
||||||
limit?: number
|
contextId?: string,
|
||||||
|
workspaceId?: string,
|
||||||
|
limit?: number,
|
||||||
|
scopedThreshold?: number,
|
||||||
|
threshold?: number
|
||||||
) => Promise<{
|
) => Promise<{
|
||||||
files?: ContextMatchedFileChunk[];
|
files?: ContextMatchedFileChunk[];
|
||||||
docs?: ContextMatchedDocChunk[];
|
docs?: ContextMatchedDocChunk[];
|
||||||
|
|||||||
@@ -661,7 +661,7 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
>();
|
>();
|
||||||
|
|
||||||
const { files: matchedFiles = [], docs: matchedDocs = [] } =
|
const { files: matchedFiles = [], docs: matchedDocs = [] } =
|
||||||
(await AIProvider.context?.matchContext(contextId, userInput)) ?? {};
|
(await AIProvider.context?.matchContext(userInput, contextId)) ?? {};
|
||||||
|
|
||||||
matchedDocs.forEach(doc => {
|
matchedDocs.forEach(doc => {
|
||||||
docContexts.set(doc.docId, {
|
docContexts.set(doc.docId, {
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ export class CopilotClient {
|
|||||||
sessionId,
|
sessionId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return res.currentUser?.copilot?.contexts?.[0]?.id;
|
return res.currentUser?.copilot?.contexts?.[0]?.id || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addContextDoc(options: OptionsField<typeof addContextDocMutation>) {
|
async addContextDoc(options: OptionsField<typeof addContextDocMutation>) {
|
||||||
@@ -333,13 +333,23 @@ export class CopilotClient {
|
|||||||
return res.currentUser?.copilot?.contexts?.[0];
|
return res.currentUser?.copilot?.contexts?.[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
async matchContext(contextId: string, content: string, limit?: number) {
|
async matchContext(
|
||||||
|
content: string,
|
||||||
|
contextId?: string,
|
||||||
|
workspaceId?: string,
|
||||||
|
limit?: number,
|
||||||
|
scopedThreshold?: number,
|
||||||
|
threshold?: number
|
||||||
|
) {
|
||||||
const res = await this.gql({
|
const res = await this.gql({
|
||||||
query: matchContextQuery,
|
query: matchContextQuery,
|
||||||
variables: {
|
variables: {
|
||||||
contextId,
|
|
||||||
content,
|
content,
|
||||||
|
contextId,
|
||||||
|
workspaceId,
|
||||||
limit,
|
limit,
|
||||||
|
scopedThreshold,
|
||||||
|
threshold,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const { matchFiles: files, matchWorkspaceDocs: docs } =
|
const { matchFiles: files, matchWorkspaceDocs: docs } =
|
||||||
|
|||||||
@@ -700,11 +700,21 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
matchContext: async (
|
matchContext: async (
|
||||||
contextId: string,
|
|
||||||
content: string,
|
content: string,
|
||||||
limit?: number
|
contextId?: string,
|
||||||
|
workspaceId?: string,
|
||||||
|
limit?: number,
|
||||||
|
scopedThreshold?: number,
|
||||||
|
threshold?: number
|
||||||
) => {
|
) => {
|
||||||
return client.matchContext(contextId, content, limit);
|
return client.matchContext(
|
||||||
|
content,
|
||||||
|
contextId,
|
||||||
|
workspaceId,
|
||||||
|
limit,
|
||||||
|
scopedThreshold,
|
||||||
|
threshold
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8412,6 +8412,13 @@ export function useAFFiNEI18N(): {
|
|||||||
contextId: string;
|
contextId: string;
|
||||||
message: string;
|
message: string;
|
||||||
}>): string;
|
}>): string;
|
||||||
|
/**
|
||||||
|
* `Failed to match context in workspace {{workspaceId}} with "%7B%7Bcontent%7D%7D": {{message}}`
|
||||||
|
*/
|
||||||
|
["error.COPILOT_FAILED_TO_MATCH_GLOBAL_CONTEXT"](options: Readonly<{
|
||||||
|
workspaceId: string;
|
||||||
|
message: string;
|
||||||
|
}>): string;
|
||||||
/**
|
/**
|
||||||
* `Embedding feature is disabled, please contact the administrator to enable it in the workspace settings.`
|
* `Embedding feature is disabled, please contact the administrator to enable it in the workspace settings.`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2081,6 +2081,7 @@
|
|||||||
"error.COPILOT_CONTEXT_FILE_NOT_SUPPORTED": "File {{fileName}} is not supported to use as context: {{message}}",
|
"error.COPILOT_CONTEXT_FILE_NOT_SUPPORTED": "File {{fileName}} is not supported to use as context: {{message}}",
|
||||||
"error.COPILOT_FAILED_TO_MODIFY_CONTEXT": "Failed to modify context {{contextId}}: {{message}}",
|
"error.COPILOT_FAILED_TO_MODIFY_CONTEXT": "Failed to modify context {{contextId}}: {{message}}",
|
||||||
"error.COPILOT_FAILED_TO_MATCH_CONTEXT": "Failed to match context {{contextId}} with \"%7B%7Bcontent%7D%7D\": {{message}}",
|
"error.COPILOT_FAILED_TO_MATCH_CONTEXT": "Failed to match context {{contextId}} with \"%7B%7Bcontent%7D%7D\": {{message}}",
|
||||||
|
"error.COPILOT_FAILED_TO_MATCH_GLOBAL_CONTEXT": "Failed to match context in workspace {{workspaceId}} with \"%7B%7Bcontent%7D%7D\": {{message}}",
|
||||||
"error.COPILOT_EMBEDDING_DISABLED": "Embedding feature is disabled, please contact the administrator to enable it in the workspace settings.",
|
"error.COPILOT_EMBEDDING_DISABLED": "Embedding feature is disabled, please contact the administrator to enable it in the workspace settings.",
|
||||||
"error.COPILOT_EMBEDDING_UNAVAILABLE": "Embedding feature not available, you may need to install pgvector extension to your database",
|
"error.COPILOT_EMBEDDING_UNAVAILABLE": "Embedding feature not available, you may need to install pgvector extension to your database",
|
||||||
"error.COPILOT_TRANSCRIPTION_JOB_EXISTS": "Transcription job already exists",
|
"error.COPILOT_TRANSCRIPTION_JOB_EXISTS": "Transcription job already exists",
|
||||||
|
|||||||
Reference in New Issue
Block a user