fix(t128+t129): 圖搜尋補 template:triplet+AI 問答出處按頁去重

t128 真因(總管實測定罪):t116 只補 kbdb_base,同一行 URL 還吃 {{input.template}}
⇒ /records/by-template/?owner_id=... 查不到;手動補 template 即 count=1
(企業版功能解鎖→控制→授權系統)。**同種病三犯,已記 mistakes。**
t129:一卡切 3-5 block 每段都算一筆命中 ⇒ dedupeSourcesByPage 後端去重+hit_count。
vitest 52 passed(1 紅=console HTML 搬遷陳舊測試,非本案)。
(實作=子 CC;驗證+commit=總管)
This commit is contained in:
uncle6me-web
2026-07-29 14:11:52 +08:00
parent eb9f2db513
commit ccb86481ae
3 changed files with 183 additions and 3 deletions
+31 -2
View File
@@ -73,6 +73,32 @@ export function mapGraphWorkflowOutput(data: unknown): { neighbors: unknown[]; e
return { neighbors, edges, count: neighbors.length };
}
/**
* 出處清單按 page_name 去重(t129):
* rag_chat workflow 把同一張卡拆成多個 block,每個 block 各回一筆 source(同頁名)→ 前端列一整頁重複。
* 後端去重:同一個 page_name / page 只保留第一筆,hit_count > 1 時附計數。
* page_name 優先;page 備用;兩者皆無 → key 為空字串(歸為同一「無頁名」組)。
* 純函式,單測用 export。
*/
export function dedupeSourcesByPage(sources: unknown[]): unknown[] {
const seen = new Map<string, { item: Record<string, unknown>; count: number }>();
for (const s of sources) {
if (!s || typeof s !== 'object') continue;
const item = s as Record<string, unknown>;
const page = typeof item.page_name === 'string' ? item.page_name :
typeof item.page === 'string' ? item.page : '';
const existing = seen.get(page);
if (existing) {
existing.count += 1;
} else {
seen.set(page, { item, count: 1 });
}
}
return [...seen.values()].map(({ item, count }) =>
count > 1 ? { ...item, hit_count: count } : item,
);
}
/** 越庫/不存在 一律同一句 404(不洩存在性)。 */
function notFound(c: Context<{ Bindings: Bindings }>): Response {
return c.json({ error: '找不到這筆資料' }, 404);
@@ -274,7 +300,8 @@ portalDataRouter.get('/portal/data/graph/neighbors/:name', (c) =>
const result = await executeWebhookGraph(
c.env,
wfGraph,
{ node: nodeName, depth, namespace: tenant, owner: tenant },
// t116: 補傳 kbdb_baset128: 補傳 templateworkflow fetch_triplets.url 用 {{input.template}}
{ node: nodeName, depth, namespace: tenant, owner: tenant, kbdb_base: c.env.KBDB_BASE_URL ?? '', template: 'triplet' },
'graph_neighbors',
tenant,
c.executionCtx,
@@ -390,10 +417,12 @@ portalDataRouter.get('/portal/data/chat', (c) =>
return c.json({ error: `rag_chat workflow 執行失敗:${result.error ?? '未知錯誤'}` }, 502);
}
// 回 workflow 回應內層 data{answer, sources, graph_facts}(缺欄位誠實回空,不編造)
// t129: sources 按 page_name 去重——同一卡拆多 block 每個各一筆,前端列一整頁重複;後端去重後乾淨。
const inner = unwrapWorkflowData(result.data, 'answer');
const rawSources = Array.isArray(inner.sources) ? inner.sources : [];
return c.json({
answer: typeof inner.answer === 'string' ? inner.answer : '',
sources: Array.isArray(inner.sources) ? inner.sources : [],
sources: dedupeSourcesByPage(rawSources),
graph_facts: inner.graph_facts ?? null,
});
}),