feat: 实现阶段二实体体系——AI预处理真实化+实体CRUD+审核归一化
- 新增 entities 数据表(含迁移)、Entity 类型、DAO 层完整 CRUD
- AI 预处理管道接入真实模型(generateText),输出结构化 JSON(摘要+规范化内容+候选实体)
- 模板接口重构为 {systemPrompt, buildUserPrompt, parseOutput},general/meeting 模板真实化
- 新增 5 个实体路由端点 + 实体管理前端页面(列表/详情/编辑弹窗)
- 审核面板增强:展示 AI 预处理结构化结果+候选实体归一化面板(合并/新建/选择/放弃)
- 素材通过时根据用户确认的候选实体写入 entities 表
- 工作台菜单新增"实体"入口
- 新增 entities DAO 测试(16)、processor 测试(11)、路由测试(8),服务端 367 测试全部通过
- TypeScript 0 错误
This commit is contained in:
@@ -75,7 +75,15 @@ function setMaterialStatus(
|
||||
);
|
||||
}
|
||||
|
||||
class FailingProcessor extends MaterialProcessor {
|
||||
class FakeProcessor extends MaterialProcessor {
|
||||
public processOneResult = '{"summary":"test","normalizedContent":"test","candidateEntities":[]}';
|
||||
|
||||
protected override async processOne(_material: ProcessableMaterial): Promise<string> {
|
||||
return Promise.resolve(this.processOneResult);
|
||||
}
|
||||
}
|
||||
|
||||
class FailingProcessor extends FakeProcessor {
|
||||
public attempts = 0;
|
||||
public failUntilAttempt = Number.POSITIVE_INFINITY;
|
||||
|
||||
@@ -98,7 +106,7 @@ describe("素材处理器", () => {
|
||||
setMaterialStatus(db, id1, "processing");
|
||||
setMaterialStatus(db, id2, "processing");
|
||||
|
||||
const processor = new MaterialProcessor(db, LOG);
|
||||
const processor = new FakeProcessor(db, LOG);
|
||||
const recovered = processor.recoverStuckMaterials();
|
||||
|
||||
expect(recovered).toBe(2);
|
||||
@@ -112,7 +120,7 @@ describe("素材处理器", () => {
|
||||
const projectId = setupProject(db);
|
||||
setupMaterial(db, projectId);
|
||||
|
||||
const processor = new MaterialProcessor(db, LOG);
|
||||
const processor = new FakeProcessor(db, LOG);
|
||||
const recovered = processor.recoverStuckMaterials();
|
||||
|
||||
expect(recovered).toBe(0);
|
||||
@@ -124,16 +132,17 @@ describe("素材处理器", () => {
|
||||
const projectId = setupProject(db);
|
||||
const id = setupMaterial(db, projectId, { description: "测试内容" });
|
||||
|
||||
const processor = new MaterialProcessor(db, LOG);
|
||||
const processor = new FakeProcessor(db, LOG);
|
||||
processor.processOneResult = '{"summary":"概要","normalizedContent":"规范内容","candidateEntities":[]}';
|
||||
await processor.processNext();
|
||||
|
||||
const row = getMaterialRow(db, id);
|
||||
expect(row?.status).toBe("review");
|
||||
expect(row?.processedContent).toBe("测试内容");
|
||||
expect(row?.processedContent).toBe('{"summary":"概要","normalizedContent":"规范内容","candidateEntities":[]}');
|
||||
});
|
||||
});
|
||||
|
||||
test("processNext 根据 materialType 选择模板", async () => {
|
||||
test("processNext 根据 materialType 调用对应模板", async () => {
|
||||
await withProcessorDbAsync(async (db) => {
|
||||
const projectId = setupProject(db);
|
||||
const id = setupMaterial(db, projectId, {
|
||||
@@ -141,12 +150,12 @@ describe("素材处理器", () => {
|
||||
materialType: "meeting",
|
||||
});
|
||||
|
||||
const processor = new MaterialProcessor(db, LOG);
|
||||
const processor = new FakeProcessor(db, LOG);
|
||||
await processor.processNext();
|
||||
|
||||
const row = getMaterialRow(db, id);
|
||||
expect(row?.status).toBe("review");
|
||||
expect(row?.processedContent).toBe("会议内容");
|
||||
expect(row?.processedContent).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -188,7 +197,7 @@ describe("素材处理器", () => {
|
||||
await withProcessorDbAsync(async (db) => {
|
||||
setupProject(db);
|
||||
|
||||
const processor = new MaterialProcessor(db, LOG);
|
||||
const processor = new FakeProcessor(db, LOG);
|
||||
await processor.processNext();
|
||||
});
|
||||
});
|
||||
@@ -201,7 +210,7 @@ describe("素材处理器", () => {
|
||||
await new Promise((r) => setTimeout(r, 20));
|
||||
const id2 = setupMaterial(db, projectId, { description: "第二个" });
|
||||
|
||||
const processor = new MaterialProcessor(db, LOG);
|
||||
const processor = new FakeProcessor(db, LOG);
|
||||
await processor.processNext();
|
||||
|
||||
expect(getMaterialRow(db, id1)?.status).toBe("review");
|
||||
@@ -211,7 +220,7 @@ describe("素材处理器", () => {
|
||||
|
||||
test("start 启动后能正常 stop", () => {
|
||||
withProcessorDb((db) => {
|
||||
const processor = new MaterialProcessor(db, LOG);
|
||||
const processor = new FakeProcessor(db, LOG);
|
||||
processor.start(100);
|
||||
processor.stop();
|
||||
});
|
||||
@@ -222,7 +231,7 @@ describe("素材处理器", () => {
|
||||
const projectId = setupProject(db);
|
||||
const id = setupMaterial(db, projectId, { description: "定时扫描" });
|
||||
|
||||
const processor = new MaterialProcessor(db, LOG);
|
||||
const processor = new FakeProcessor(db, LOG);
|
||||
processor.start(50);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 300));
|
||||
@@ -231,7 +240,6 @@ describe("素材处理器", () => {
|
||||
|
||||
const row = getMaterialRow(db, id);
|
||||
expect(row?.status).toBe("review");
|
||||
expect(row?.processedContent).toBe("定时扫描");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user