fix(knowledge): bind summary cards to cited evidence

This commit is contained in:
2026-07-25 01:45:41 +08:00
parent 80af8a0870
commit f545983de8
13 changed files with 211 additions and 151 deletions
+14 -3
View File
@@ -161,7 +161,7 @@
<button v-if="msg.result.snippets?.length" class="bubble-action" @click="msg.citationsOpen = !msg.citationsOpen">
引用 {{ msg.result.snippets.length }} 条 {{ msg.citationsOpen ? '▲' : '▼' }}
</button>
<button v-if="msg.result.agentIntent === 'KNOWLEDGE_QA' && !broadcastContext" class="bubble-action" :disabled="summaryLoading" @click="buildSummaryCard(msg)">
<button v-if="msg.result.agentIntent === 'KNOWLEDGE_QA' && summaryFragmentIds(msg).length && !broadcastContext" class="bubble-action" :disabled="summaryLoading" @click="buildSummaryCard(msg)">
{{ summaryLoading ? '生成中' : '总结卡' }}
</button>
<button v-if="msg.result.noEvidence && msg.result.agentIntent === 'KNOWLEDGE_QA' && !broadcastContext" class="bubble-action primary" @click="openWebAi">去查全网</button>
@@ -1332,11 +1332,15 @@ const buildSummaryCard = async (msg: MasterMsg) => {
}
if (summaryLoading.value) return;
const value = (msg.result.queryText || '').trim();
if (!value) return;
const fragmentIds = summaryFragmentIds(msg);
if (!value || !fragmentIds.length) {
pushSystem('当前回答没有可用于总结卡的引用片段,请重新提问');
return;
}
summaryLoading.value = true;
summaryCard.value = null;
try {
summaryCard.value = await generateSummaryCard(value);
summaryCard.value = await generateSummaryCard({ queryText: value, fragmentIds });
} catch (error) {
pushSystem(requestErrorText(error, '总结卡生成失败'));
} finally {
@@ -1344,6 +1348,13 @@ const buildSummaryCard = async (msg: MasterMsg) => {
}
};
const summaryFragmentIds = (msg: MasterMsg) => Array.from(new Set(
(msg.result.snippets || [])
.filter((item) => !item.sourceType || item.sourceType === 'DOCUMENT')
.map((item) => item.fragmentId)
.filter((id): id is number => typeof id === 'number' && Number.isSafeInteger(id) && id > 0)
)).slice(0, 5);
const closeSummary = () => {
if (speechState.value.key === 'summary') speechPlayback.stop();
summaryCard.value = null;
+4 -3
View File
@@ -250,13 +250,14 @@ export const submitAnswerFeedback = (params: {
timeout: 15000
});
export const generateSummaryCard = (queryText: string) =>
export const generateSummaryCard = (params: { queryText: string; fragmentIds: number[] }) =>
apiRequest<SummaryCardResponse>({
url: '/api/knowledge/summary-card',
method: 'POST',
data: {
queryText,
category: 'sop'
queryText: params.queryText,
category: 'sop',
fragmentIds: params.fragmentIds
},
timeout: 45000
});
+8 -1
View File
@@ -286,13 +286,20 @@ test('超大字模式下今日安排将文字和操作按钮纵向排布', async
});
test('问师傅页保留搜索框、答案区和总结卡入口', async () => {
const source = await pageSource('../src/pages/user/sop/index.vue');
const [source, service] = await Promise.all([
pageSource('../src/pages/user/sop/index.vue'),
pageSource('../src/services/knowledge.ts')
]);
assert.match(source, /query-panel/);
assert.match(source, /answer-panel/);
assert.match(source, /summary-panel/);
assert.match(source, /MY_PRACTICE_SUMMARY/);
assert.match(source, /TEAM_PRACTICE_SUMMARY/);
assert.match(source, /const summaryFragmentIds = \(msg: MasterMsg\)/);
assert.match(source, /generateSummaryCard\(\{ queryText: value, fragmentIds \}\)/);
assert.match(service, /fragmentIds: number\[\]/);
assert.match(service, /fragmentIds: params\.fragmentIds/);
});
test('SOP 没解决先收集结构化原因再提交', async () => {