Skip to content

Commit e2156ea

Browse files
authored
feat(server): integrate blob to context (#13491)
1 parent 795bfb2 commit e2156ea

File tree

5 files changed

+85
-12
lines changed

5 files changed

+85
-12
lines changed

packages/backend/server/src/models/blob.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,17 @@ export class BlobModel extends BaseModel {
6767
});
6868
}
6969

70-
async list(workspaceId: string) {
70+
async list(
71+
workspaceId: string,
72+
options?: { where: Prisma.BlobWhereInput; select?: Prisma.BlobSelect }
73+
) {
7174
return await this.db.blob.findMany({
7275
where: {
76+
...options?.where,
7377
workspaceId,
7478
deletedAt: null,
7579
},
80+
select: options?.select,
7681
});
7782
}
7883

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

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import { Prisma, PrismaClient } from '@prisma/client';
66

77
import { PaginationInput } from '../base';
88
import { BaseModel } from './base';
9-
import type {
10-
BlobChunkSimilarity,
11-
CopilotWorkspaceFile,
12-
CopilotWorkspaceFileMetadata,
13-
Embedding,
14-
FileChunkSimilarity,
15-
IgnoredDoc,
9+
import {
10+
type BlobChunkSimilarity,
11+
clearEmbeddingContent,
12+
type CopilotWorkspaceFile,
13+
type CopilotWorkspaceFileMetadata,
14+
type Embedding,
15+
type FileChunkSimilarity,
16+
type IgnoredDoc,
1617
} from './common';
1718

1819
@Injectable()
@@ -413,6 +414,33 @@ export class CopilotWorkspaceConfigModel extends BaseModel {
413414
return similarityChunks.filter(c => Number(c.distance) <= threshold);
414415
}
415416

417+
async getBlobContent(
418+
workspaceId: string,
419+
blobId: string,
420+
chunk?: number
421+
): Promise<string | undefined> {
422+
const blob = await this.db.aiWorkspaceBlobEmbedding.findMany({
423+
where: { workspaceId, blobId, chunk },
424+
select: { content: true },
425+
orderBy: { chunk: 'asc' },
426+
});
427+
return blob?.map(f => clearEmbeddingContent(f.content)).join('\n');
428+
}
429+
430+
async getBlobChunkSizes(workspaceId: string, blobIds: string[]) {
431+
const sizes = await this.db.aiWorkspaceBlobEmbedding.groupBy({
432+
by: ['blobId'],
433+
_count: { chunk: true },
434+
where: { workspaceId, blobId: { in: blobIds } },
435+
});
436+
return sizes.reduce((acc, cur) => {
437+
if (cur._count.chunk) {
438+
acc.set(cur.blobId, cur._count.chunk);
439+
}
440+
return acc;
441+
}, new Map<string, number>());
442+
}
443+
416444
@Transactional()
417445
async insertBlobEmbeddings(
418446
workspaceId: string,

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class ContextSession implements AsyncDisposable {
5555
return this.config.docs.map(d => ({ ...d }));
5656
}
5757

58-
get files() {
58+
get files(): Required<ContextFile>[] {
5959
return this.config.files.map(f => this.fulfillFile(f));
6060
}
6161

@@ -135,6 +135,36 @@ export class ContextSession implements AsyncDisposable {
135135
return record;
136136
}
137137

138+
async getBlobMetadata() {
139+
const blobIds = this.blobs.map(b => b.id);
140+
const blobs = await this.models.blob.list(this.config.workspaceId, {
141+
where: { key: { in: blobIds } },
142+
select: { key: true, mime: true },
143+
});
144+
const blobChunkSizes = await this.models.copilotWorkspace.getBlobChunkSizes(
145+
this.config.workspaceId,
146+
blobIds
147+
);
148+
return blobs
149+
.filter(b => !!blobChunkSizes.get(b.key))
150+
.map(b => ({
151+
id: b.key,
152+
mimeType: b.mime,
153+
chunkSize: blobChunkSizes.get(b.key),
154+
}));
155+
}
156+
157+
async getBlobContent(
158+
blobId: string,
159+
chunk?: number
160+
): Promise<string | undefined> {
161+
return this.models.copilotWorkspace.getBlobContent(
162+
this.config.workspaceId,
163+
blobId,
164+
chunk
165+
);
166+
}
167+
138168
async removeBlobRecord(blobId: string): Promise<boolean> {
139169
const index = this.config.blobs.findIndex(b => b.id === blobId);
140170
if (index >= 0) {

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,14 @@ export class CopilotController implements BeforeApplicationShutdown {
208208

209209
const context = await this.context.getBySessionId(sessionId);
210210
const contextParams =
211-
Array.isArray(context?.files) && context.files.length > 0
212-
? { contextFiles: context.files }
211+
(Array.isArray(context?.files) && context.files.length > 0) ||
212+
(Array.isArray(context?.blobs) && context.blobs.length > 0)
213+
? {
214+
contextFiles: [
215+
...context.files,
216+
...(await context.getBlobMetadata()),
217+
],
218+
}
213219
: {};
214220
const lastParams = latestMessage
215221
? {

packages/backend/server/src/plugins/copilot/tools/blob-read.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ export const buildBlobContentGetter = (
3333
return;
3434
}
3535

36-
const content = await context?.getFileContent(blobId, chunk);
36+
const [file, blob] = await Promise.all([
37+
context?.getFileContent(blobId, chunk),
38+
context?.getBlobContent(blobId, chunk),
39+
]);
40+
const content = file?.trim() || blob?.trim();
3741
if (!content) {
3842
return;
3943
}

0 commit comments

Comments
 (0)