feat: govern knowledge assets and source citations

This commit is contained in:
key
2026-08-02 01:43:43 +08:00
parent cafb836cda
commit 699cc08050
144 changed files with 17205 additions and 453 deletions
+61 -3
View File
@@ -217,7 +217,14 @@
</view>
<view v-if="msg.citationsOpen && msg.result.snippets?.length" class="bubble-citations">
<view v-for="(snippet, index) in msg.result.snippets" :key="`${snippet.docTitle}-${index}`" class="citation">
<component
:is="snippet.detailRef && snippet.sourceType === 'DOCUMENT' ? 'button' : 'view'"
v-for="(snippet, index) in msg.result.snippets"
:key="`${snippet.docTitle}-${index}`"
class="citation"
:class="{ 'citation-action': Boolean(snippet.detailRef && snippet.sourceType === 'DOCUMENT') }"
@click="openCitationDetail(snippet)"
>
<text class="citation-domain" :class="{ project: snippet.domain === 'PROJECT_SERVICE' }">
{{ citationDomainLabel(snippet) }}
</text>
@@ -227,8 +234,11 @@
</text>
<text v-else-if="snippet.spaceCode" class="citation-meta">知识空间:{{ snippet.spaceCode }}</text>
<text class="citation-content">{{ snippetExcerpt(snippet.content) }}</text>
<text v-if="snippet.fragmentId" class="citation-meta">片段 #{{ snippet.fragmentId }}</text>
</view>
<view v-if="snippet.detailRef && snippet.sourceType === 'DOCUMENT'" class="citation-open-row">
<text class="citation-location">{{ citationLocationLabel(snippet) }}</text>
<text class="citation-open">查看出处 ›</text>
</view>
</component>
</view>
<view v-if="msg.voiceStatus !== 'unavailable'" class="transcript-collapse" @click="collapseTranscript(msg)">
@@ -1304,6 +1314,28 @@ const serviceMemoryStatusLabel = (status?: string | null) => ({
const displayCitationDate = (value?: string | null) => value ? value.replace('T', ' ').slice(0, 16) : '未记录';
const formatCitationTime = (milliseconds?: number | null) => {
if (typeof milliseconds !== 'number' || milliseconds < 0) return '';
const seconds = Math.floor(milliseconds / 1000);
return `${String(Math.floor(seconds / 60)).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`;
};
const citationLocationLabel = (snippet: KnowledgeSearchResponse['snippets'][number]) => {
const locator = snippet.locatorSummary;
const at = locator?.frameMs ?? locator?.startMs;
if (snippet.mediaType === 'VIDEO' && typeof at === 'number') return `视频 ${formatCitationTime(at)}`;
if (locator?.pageNumber) return `第 ${locator.pageNumber} 页`;
if (locator?.slideNumber) return `第 ${locator.slideNumber} 页`;
if (locator?.sheetName && locator?.rowStart) return `${locator.sheetName} · 第 ${locator.rowStart} 行`;
if (locator?.paragraphStart) return `第 ${locator.paragraphStart} 段`;
return snippet.mediaType === 'IMAGE' ? '原图' : '来源全文';
};
const openCitationDetail = (snippet: KnowledgeSearchResponse['snippets'][number]) => {
if (!snippet.detailRef || snippet.sourceType !== 'DOCUMENT') return;
uni.navigateTo({ url: `/pages/user/sop/citation-detail?ref=${encodeURIComponent(snippet.detailRef)}` });
};
const noEvidenceText = (result: KnowledgeSearchResponse) => {
if (result.agentIntent === 'WEB_RESEARCH') return '公开网络中没有找到可核验来源。';
if (result.agentIntent === 'MEDIA_UNDERSTANDING') return '已分析附件,但当前资料中没有补充依据。';
@@ -1354,6 +1386,7 @@ const feedback = async (verdict: AnswerFeedbackVerdict, msg: MasterMsg) => {
try {
await submitAnswerFeedback({
queryText: msg.result.queryText || '',
requestId: msg.result.requestId,
fragmentIds,
verdict,
reviewId: msg.result.reviewId,
@@ -3253,6 +3286,31 @@ onUnload(() => {
background: #fafbfc;
}
.citation-action {
box-sizing: border-box;
width: 100%;
margin: 0;
border: 0;
border-left: 3px solid #d3132a;
text-align: left;
line-height: normal;
}
.citation-action::after { border: 0; }
.citation-open-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-top: 8px;
padding-top: 7px;
border-top: 1px solid #e8ebef;
}
.citation-location { color: #697586; font-size: 12px; }
.citation-open { color: #c90018; font-size: 12px; font-weight: 700; }
.citation-title {
display: block;
color: #232a33;