Refine report citation anchors
This commit is contained in:
parent
a6a12d7775
commit
c5cc5912c0
|
|
@ -697,14 +697,14 @@ def _expanded_report_sections() -> List[tuple[str, str]]:
|
|||
|
||||
def _chat_citations() -> List[Dict[str, Any]]:
|
||||
return [
|
||||
{"id": "S01", "label": "S01", "title": "执行结论", "section_index": 1},
|
||||
{"id": "S02", "label": "S02", "title": "Agent问答暴露的问题", "section_index": 2},
|
||||
{"id": "S03", "label": "S03", "title": "商业机会", "section_index": 3},
|
||||
{"id": "A01", "label": "A01", "title": "高净值客户A", "section_index": 2, "agent_id": 1},
|
||||
{"id": "A08", "label": "A08", "title": "配偶共同决策人", "section_index": 2, "agent_id": 8},
|
||||
{"id": "A10", "label": "A10", "title": "企业财务负责人", "section_index": 2, "agent_id": 10},
|
||||
{"id": "A05", "label": "A05", "title": "风控合规经理", "section_index": 2, "agent_id": 5},
|
||||
{"id": "A29", "label": "A29", "title": "销售管理看板", "section_index": 2, "agent_id": 29},
|
||||
{"id": "S01", "anchor_id": "S01", "label": "S01", "title": "执行结论", "section_index": 1},
|
||||
{"id": "S02", "anchor_id": "S02", "label": "S02", "title": "Agent问答暴露的问题", "section_index": 2},
|
||||
{"id": "S03", "anchor_id": "S03", "label": "S03", "title": "商业机会", "section_index": 3},
|
||||
{"id": "A01", "anchor_id": "A01", "label": "A01", "title": "高净值客户A|核心客户", "section_index": 2, "agent_id": 1},
|
||||
{"id": "A08", "anchor_id": "A08", "label": "A08", "title": "配偶共同决策人|家庭安全垫", "section_index": 2, "agent_id": 8},
|
||||
{"id": "A10", "anchor_id": "A10", "label": "A10", "title": "企业财务负责人|资金分层", "section_index": 2, "agent_id": 10},
|
||||
{"id": "A05", "anchor_id": "A05", "label": "A05", "title": "风控合规经理|适当性与话术", "section_index": 2, "agent_id": 5},
|
||||
{"id": "A29", "anchor_id": "A29", "label": "A29", "title": "销售管理看板|管理层指标", "section_index": 2, "agent_id": 29},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -279,10 +279,11 @@
|
|||
:key="citation.id"
|
||||
class="citation-chip"
|
||||
type="button"
|
||||
:data-citation-id="citation.anchor_id || citation.id"
|
||||
:data-section-index="citation.section_index"
|
||||
:data-agent-id="citation.agent_id ?? ''"
|
||||
:title="citation.title"
|
||||
@click="jumpToCitation(citation)"
|
||||
@click.stop="jumpToCitation(citation)"
|
||||
>
|
||||
{{ citation.label }}
|
||||
</button>
|
||||
|
|
@ -506,6 +507,37 @@ const citationTargetSection = (citation) => {
|
|||
return 1
|
||||
}
|
||||
|
||||
const citationIdFromDataset = (target) => {
|
||||
if (target.dataset.citationId) return target.dataset.citationId
|
||||
if (target.dataset.agentId) return `A${String(Number(target.dataset.agentId)).padStart(2, '0')}`
|
||||
if (target.dataset.sectionIndex) return `S${String(Number(target.dataset.sectionIndex)).padStart(2, '0')}`
|
||||
return null
|
||||
}
|
||||
|
||||
const clearCitationTargetHighlights = () => {
|
||||
leftPanel.value
|
||||
?.querySelectorAll('.citation-target-highlight')
|
||||
.forEach(el => el.classList.remove('citation-target-highlight'))
|
||||
}
|
||||
|
||||
const highlightCitationTarget = (target) => {
|
||||
clearCitationTargetHighlights()
|
||||
if (!target) return
|
||||
|
||||
target.classList.add('citation-target-highlight')
|
||||
if (target.matches('[data-citation-anchor^="A"]')) {
|
||||
let sibling = target.nextElementSibling
|
||||
let highlighted = 0
|
||||
while (sibling && highlighted < 3 && !sibling.matches('[data-citation-anchor], .md-h4, .md-h3, .md-h2')) {
|
||||
sibling.classList.add('citation-target-highlight')
|
||||
sibling = sibling.nextElementSibling
|
||||
highlighted++
|
||||
}
|
||||
}
|
||||
|
||||
window.setTimeout(() => clearCitationTargetHighlights(), 3200)
|
||||
}
|
||||
|
||||
const jumpToSection = (sectionIndex) => {
|
||||
const index = Number(sectionIndex)
|
||||
if (!Number.isFinite(index) || index < 1) return
|
||||
|
|
@ -528,14 +560,46 @@ const jumpToSection = (sectionIndex) => {
|
|||
}
|
||||
|
||||
const jumpToCitation = (citation) => {
|
||||
jumpToSection(citationTargetSection(citation))
|
||||
const sectionIndex = citationTargetSection(citation)
|
||||
const citationId = citation.anchor_id || citation.id || (
|
||||
citation.agent_id !== undefined
|
||||
? `A${String(Number(citation.agent_id)).padStart(2, '0')}`
|
||||
: `S${String(sectionIndex).padStart(2, '0')}`
|
||||
)
|
||||
|
||||
const zeroIndex = sectionIndex - 1
|
||||
const newSet = new Set(collapsedSections.value)
|
||||
newSet.delete(zeroIndex)
|
||||
collapsedSections.value = newSet
|
||||
currentSectionIndex.value = sectionIndex
|
||||
highlightedSectionIndex.value = citationId?.startsWith('S') ? sectionIndex : null
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const exactTarget = citationId
|
||||
? leftPanel.value?.querySelector(`[data-citation-anchor="${citationId}"]`)
|
||||
: null
|
||||
const fallbackTarget = leftPanel.value?.querySelector(`[data-section-index="${sectionIndex}"]`)
|
||||
const target = exactTarget || fallbackTarget
|
||||
target?.scrollIntoView({ behavior: 'smooth', block: exactTarget ? 'center' : 'start' })
|
||||
highlightCitationTarget(exactTarget || (citationId?.startsWith('S') ? fallbackTarget : null))
|
||||
})
|
||||
|
||||
if (citationId?.startsWith('S')) {
|
||||
window.setTimeout(() => {
|
||||
if (highlightedSectionIndex.value === sectionIndex) highlightedSectionIndex.value = null
|
||||
}, 2200)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCitationClick = (event) => {
|
||||
const target = event.target?.closest?.('[data-section-index], [data-agent-id]')
|
||||
const target = event.target?.closest?.('.citation-chip')
|
||||
if (!target) return
|
||||
const sectionIndex = target.dataset.sectionIndex || (target.dataset.agentId ? 2 : 1)
|
||||
jumpToSection(sectionIndex)
|
||||
jumpToCitation({
|
||||
id: citationIdFromDataset(target),
|
||||
anchor_id: citationIdFromDataset(target),
|
||||
section_index: target.dataset.sectionIndex || (target.dataset.agentId ? 2 : 1),
|
||||
agent_id: target.dataset.agentId || undefined,
|
||||
})
|
||||
}
|
||||
|
||||
const selectChatTarget = (target) => {
|
||||
|
|
@ -620,11 +684,13 @@ const renderMarkdown = (content) => {
|
|||
html = html.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>')
|
||||
html = html.replace(/\[\[(S|A)(\d{1,2})\]\]/g, (match, type, number) => {
|
||||
const value = String(Number(number)).padStart(2, '0')
|
||||
const citationId = `${type}${value}`
|
||||
const sectionIndex = type === 'S' ? Number(number) : 2
|
||||
const agentId = type === 'A' ? Number(number) : ''
|
||||
const title = type === 'S' ? `跳转到报告第 ${value} 节` : `跳转到 Agent ${value} 问答证据`
|
||||
return `<button type="button" class="citation-chip citation-inline" data-section-index="${sectionIndex}" data-agent-id="${agentId}" title="${title}">${type}${value}</button>`
|
||||
return `<button type="button" class="citation-chip citation-inline" data-citation-id="${citationId}" data-section-index="${sectionIndex}" data-agent-id="${agentId}" title="${title}">${citationId}</button>`
|
||||
})
|
||||
html = html.replace(/^#### (A\d{2})\s+(.+)$/gm, '<h5 class="md-h5 citation-anchor-heading" data-citation-anchor="$1">$1 $2</h5>')
|
||||
html = html.replace(/^#### (.+)$/gm, '<h5 class="md-h5">$1</h5>')
|
||||
html = html.replace(/^### (.+)$/gm, '<h4 class="md-h4">$1</h4>')
|
||||
html = html.replace(/^## (.+)$/gm, '<h3 class="md-h3">$1</h3>')
|
||||
|
|
@ -1279,6 +1345,31 @@ watch(() => props.simulationId, (newId) => {
|
|||
font-family: 'Times New Roman', Times, serif;
|
||||
}
|
||||
|
||||
.generated-content :deep([data-citation-anchor]) {
|
||||
scroll-margin-top: 36px;
|
||||
}
|
||||
|
||||
.generated-content :deep(.citation-target-highlight) {
|
||||
background: rgba(16, 185, 129, 0.10);
|
||||
box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.24);
|
||||
}
|
||||
|
||||
.generated-content :deep(.citation-anchor-heading.citation-target-highlight) {
|
||||
display: block;
|
||||
margin-left: -10px;
|
||||
margin-right: -10px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.generated-content :deep(.citation-target-highlight.md-quote),
|
||||
.generated-content :deep(.citation-target-highlight.md-p) {
|
||||
margin-left: -10px;
|
||||
margin-right: -10px;
|
||||
padding-left: 18px;
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
.generated-content :deep(.code-block) {
|
||||
background: #F9FAFB;
|
||||
padding: 12px;
|
||||
|
|
@ -2712,4 +2803,9 @@ html[data-theme='dark'] .report-section-item.is-citation-highlight {
|
|||
background: rgba(16, 185, 129, 0.14);
|
||||
box-shadow: 0 0 0 1px rgba(52, 211, 153, 0.32), 0 12px 30px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .generated-content .citation-target-highlight {
|
||||
background: rgba(52, 211, 153, 0.16);
|
||||
box-shadow: 0 0 0 1px rgba(52, 211, 153, 0.34);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue