|
|
@@ -101,7 +101,56 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div v-if="message.content" class="message-text" v-html="renderMarkdown(message.content, tableIcons)"></div>
|
|
|
+ <div
|
|
|
+ v-if="message.content"
|
|
|
+ class="message-text"
|
|
|
+ v-html="
|
|
|
+ renderMarkdown(
|
|
|
+ message.content,
|
|
|
+ tableIcons,
|
|
|
+ message.citations?.items?.length ? Math.max(...message.citations.items.map((i) => i.id)) : undefined
|
|
|
+ )
|
|
|
+ "
|
|
|
+ @click="handleFootnoteClick($event, index)"
|
|
|
+ ></div>
|
|
|
+
|
|
|
+ <!-- 数据来源卡片 citations -->
|
|
|
+ <div v-if="message.citations" class="citations-card">
|
|
|
+ <div v-if="message.citations.model_only" class="citations-header citations-header-simple">
|
|
|
+ <span class="citations-title">本回答依据模型知识生成,仅供参考</span>
|
|
|
+ </div>
|
|
|
+ <template v-else-if="message.citations.items?.length">
|
|
|
+ <div class="citations-header" @click="toggleCitations(index)">
|
|
|
+ <div class="citations-header-left">
|
|
|
+ <span class="citations-title">数据来源</span>
|
|
|
+ <span class="citations-count">{{ message.citations.items.length }}</span>
|
|
|
+ </div>
|
|
|
+ <div :class="['thinking-arrow', { expanded: !collapsedCitations[index] }]">
|
|
|
+ <img :src="getCssUrl('--img-chat-arrow-icon')" width="10" height="10" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-show="!collapsedCitations[index]" class="citations-body">
|
|
|
+ <div v-for="item in message.citations.items" :key="item.id" :id="`citation-${index}-${item.id}`" class="citation-item">
|
|
|
+ <div class="citation-item-row">
|
|
|
+ <span :class="['citation-badge', `citation-badge-${item.type}`]">{{ item.type_cn }}</span>
|
|
|
+ <span class="citation-title-text">{{ item.title }}</span>
|
|
|
+ <a v-if="item.search_url" :href="item.search_url" target="_blank" class="citation-link">知识库检索</a>
|
|
|
+ <a v-if="item.url" :href="item.url" target="_blank" class="citation-link">链接</a>
|
|
|
+ </div>
|
|
|
+ <div class="citation-item-attrs">
|
|
|
+ <span v-if="item.type === 'knowledge_base' && item.score != null" class="citation-attr">
|
|
|
+ 相似度 {{ item.score.toFixed(2) }}
|
|
|
+ <span v-if="item.low_score" class="citation-low-score">(低相关度)</span>
|
|
|
+ </span>
|
|
|
+ <span v-if="item.source_system" class="citation-attr">{{ item.source_system }}</span>
|
|
|
+ <span v-if="item.fetched_at" class="citation-attr">{{ item.fetched_at }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="item.snippet" class="citation-snippet" :title="item.snippet">{{ item.snippet }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div v-if="message.wordDownloadUrl" class="word-download-area">
|
|
|
<a-button type="link" @click="emit('download-word', message.wordDownloadUrl)"> 下载报告文件 (.docx) </a-button>
|
|
|
</div>
|
|
|
@@ -505,6 +554,7 @@
|
|
|
const collapsedThinking = reactive<Record<number, boolean>>({});
|
|
|
const collapsedThinkingContent = reactive<Record<number, boolean>>({});
|
|
|
const expandedAgents = reactive<Record<string, boolean>>({});
|
|
|
+ const collapsedCitations = reactive<Record<number, boolean>>({});
|
|
|
const isAtBottom = ref(true);
|
|
|
const copiedMessageIndex = ref(-1);
|
|
|
|
|
|
@@ -633,6 +683,23 @@
|
|
|
expandedAgents[key] = !expandedAgents[key];
|
|
|
};
|
|
|
|
|
|
+ const toggleCitations = (index: number) => {
|
|
|
+ collapsedCitations[index] = !collapsedCitations[index];
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleFootnoteClick = (e: Event, msgIndex: number) => {
|
|
|
+ const target = (e.target as HTMLElement).closest('.footnote-ref') as HTMLElement;
|
|
|
+ if (!target) return;
|
|
|
+ const n = target.dataset.n;
|
|
|
+ if (!n) return;
|
|
|
+ const el = document.getElementById(`citation-${msgIndex}-${n}`);
|
|
|
+ if (el) {
|
|
|
+ el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
|
+ el.classList.add('citation-highlight');
|
|
|
+ setTimeout(() => el.classList.remove('citation-highlight'), 1500);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
const getLatestStepMessage = (group: AgentGroup): string => {
|
|
|
if (group.steps.length === 0) return '';
|
|
|
const last = group.steps[group.steps.length - 1];
|
|
|
@@ -1367,6 +1434,167 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ .citations-card {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .citations-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 8px 14px;
|
|
|
+ border-bottom: 1px solid rgba(63, 80, 106, 0.3);
|
|
|
+ cursor: pointer;
|
|
|
+ user-select: none;
|
|
|
+ transition: background 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: rgba(10, 132, 255, 0.08);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.citations-header-simple {
|
|
|
+ cursor: default;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: transparent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .citations-header-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .citations-title {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #8b949e;
|
|
|
+ }
|
|
|
+
|
|
|
+ .citations-count {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #8b949e;
|
|
|
+ background: rgba(10, 132, 255, 0.1);
|
|
|
+ padding: 1px 6px;
|
|
|
+ border-radius: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-arrow {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ transition: transform 0.2s;
|
|
|
+
|
|
|
+ &.expanded {
|
|
|
+ transform: rotate(180deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .citations-body {
|
|
|
+ padding: 4px 14px;
|
|
|
+
|
|
|
+ .citation-item {
|
|
|
+ padding: 6px 0;
|
|
|
+ border-bottom: 1px solid rgba(63, 80, 106, 0.15);
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.citation-highlight {
|
|
|
+ background: rgba(10, 132, 255, 0.08);
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 6px 8px;
|
|
|
+ margin: 0 -8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .citation-item-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ line-height: 1.5;
|
|
|
+
|
|
|
+ .citation-badge {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #79c0ff;
|
|
|
+ font-family: monospace;
|
|
|
+ background: rgba(10, 132, 255, 0.1);
|
|
|
+ padding: 1px 5px;
|
|
|
+ border-radius: 3px;
|
|
|
+ white-space: nowrap;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .citation-badge-knowledge_base {
|
|
|
+ color: #3fb950;
|
|
|
+ background: rgba(46, 160, 67, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .citation-badge-web {
|
|
|
+ color: #d29922;
|
|
|
+ background: rgba(210, 153, 34, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .citation-badge-calc {
|
|
|
+ color: #a371f7;
|
|
|
+ background: rgba(163, 113, 247, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .citation-title-text {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #b0b8c4;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .citation-link {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #0a84ff;
|
|
|
+ text-decoration: none;
|
|
|
+ white-space: nowrap;
|
|
|
+ flex-shrink: 0;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ text-decoration: underline;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .citation-item-attrs {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 4px 10px;
|
|
|
+ padding-left: 0;
|
|
|
+ margin-top: 2px;
|
|
|
+
|
|
|
+ .citation-attr {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #8b949e;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .citation-low-score {
|
|
|
+ color: #d29922;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .citation-snippet {
|
|
|
+ margin-top: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #8b949e;
|
|
|
+ line-height: 1.5;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
.download-file-cards {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|