feat: M2-03 — Material & Source, SourceReference citation tracking
Some checks failed
Deploy API Server / build-and-deploy (push) Failing after 11s
Some checks failed
Deploy API Server / build-and-deploy (push) Failing after 11s
- SourceReference model for artifact→chunk→source citation chain - Admin source list + reference tracing endpoints - Existing KnowledgeSource already covers Material status/version Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
052cd5cba8
commit
b3bce7ff78
@ -0,0 +1,14 @@
|
||||
CREATE TABLE IF NOT EXISTS `SourceReference` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`sourceId` VARCHAR(191) NOT NULL,
|
||||
`chunkId` VARCHAR(191) NULL,
|
||||
`artifactType` VARCHAR(32) NOT NULL,
|
||||
`artifactId` VARCHAR(100) NOT NULL,
|
||||
`pageNumber` INT NULL,
|
||||
`sectionTitle` VARCHAR(500) NULL,
|
||||
`excerptText` VARCHAR(2000) NULL,
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
INDEX `SourceReference_artifactType_artifactId_idx`(`artifactType`, `artifactId`),
|
||||
INDEX `SourceReference_sourceId_idx`(`sourceId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
@ -738,6 +738,24 @@ model KnowledgeChunk {
|
||||
@@index([externalVectorId])
|
||||
}
|
||||
|
||||
model SourceReference {
|
||||
id String @id @default(cuid())
|
||||
sourceId String
|
||||
chunkId String?
|
||||
artifactType String @db.VarChar(32)
|
||||
artifactId String @db.VarChar(100)
|
||||
pageNumber Int?
|
||||
sectionTitle String? @db.VarChar(500)
|
||||
excerptText String? @db.VarChar(2000)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
source KnowledgeSource @relation(fields: [sourceId], references: [id])
|
||||
chunk KnowledgeChunk? @relation(fields: [chunkId], references: [id])
|
||||
|
||||
@@index([artifactType, artifactId])
|
||||
@@index([sourceId])
|
||||
}
|
||||
|
||||
model ImportCandidate {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
|
||||
@ -47,4 +47,24 @@ export class AdminKnowledgeController {
|
||||
await this.prisma.knowledgeBase.update({ where: { id }, data: { deletedAt: new Date() } });
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Get(':id/sources')
|
||||
@ApiOperation({ summary: '知识库资料来源列表' })
|
||||
async sources(@Param('id') id: string) {
|
||||
return this.prisma.knowledgeSource.findMany({
|
||||
where: { knowledgeBaseId: id, deletedAt: null },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 50,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('sources/:sourceId/references')
|
||||
@ApiOperation({ summary: 'Source 引用追踪' })
|
||||
async sourceReferences(@Param('sourceId') sourceId: string) {
|
||||
return this.prisma.sourceReference.findMany({
|
||||
where: { sourceId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 50,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,4 +145,44 @@ describe('M2 E2E Tests', () => {
|
||||
expect(res.body.data).toHaveProperty('total');
|
||||
});
|
||||
});
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// M2-03: Material & Source
|
||||
// ══════════════════════════════════════════════
|
||||
describe('M2-03 Material & Source', () => {
|
||||
let token: string;
|
||||
beforeAll(async () => { token = await loginAdmin(); });
|
||||
|
||||
it('POST /api/knowledge-bases/:kbId/sources → 201 add source', async () => {
|
||||
const kb = await request(app.getHttpServer())
|
||||
.post('/api/knowledge-bases')
|
||||
.send({ title: 'Source Test KB', description: 'test' });
|
||||
const kbId = kb.body?.data?.id;
|
||||
if (!kbId) return;
|
||||
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(`/api/knowledge-bases/${kbId}/sources`)
|
||||
.send({ type: 'file', title: 'Test PDF', originalFilename: 'test.pdf', mimeType: 'application/pdf' })
|
||||
.expect([200, 201]);
|
||||
expect(res.body.data).toHaveProperty('id');
|
||||
});
|
||||
|
||||
it('GET /admin-api/knowledge-bases/:id/sources → 200 source list', async () => {
|
||||
if (!token) return;
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/admin-api/knowledge-bases/kb-test/sources')
|
||||
.set('Authorization', `Bearer ${token}`)
|
||||
.expect(200);
|
||||
expect(Array.isArray(res.body.data)).toBe(true);
|
||||
});
|
||||
|
||||
it('GET /admin-api/knowledge-bases/sources/:sourceId/references → 200', async () => {
|
||||
if (!token) return;
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/admin-api/knowledge-bases/sources/src-test/references')
|
||||
.set('Authorization', `Bearer ${token}`)
|
||||
.expect(200);
|
||||
expect(Array.isArray(res.body.data)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -91,7 +91,7 @@ const modelNames = [
|
||||
'userMembership', 'quotaUsage', 'costDailySummary', 'secretRecord',
|
||||
'secretAccessLog', 'modelRoute', 'providerConfig', 'fallbackEvent',
|
||||
'violationRecord', 'contentReport', 'userDevice', 'accountDeletionRequest',
|
||||
'workspace', 'knowledgeFolder',
|
||||
'workspace', 'knowledgeFolder', 'sourceReference',
|
||||
]
|
||||
|
||||
for (const name of modelNames) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user