Add guided topic discovery panel and chat handoff

This commit is contained in:
732642856
2026-06-29 11:41:09 +08:00
parent 38f7ab7ca9
commit 178251e7e6
5 changed files with 241 additions and 1 deletions
+12
View File
@@ -41,6 +41,18 @@ export function aiChatSetChartData(cd) {
}
}
export function openAIChatWithPrompt(prompt) {
const text = String(prompt || '').trim();
if (!text) return;
if (_panelEl && !_panelEl.classList.contains('open')) {
_panelEl.classList.add('open');
}
const input = _panelEl?.querySelector('#ai-input');
if (!input) return;
input.value = text;
input.focus();
}
// ============================================================================
// 认证状态同步(由 auth.js 调用)
// ============================================================================
+1
View File
@@ -277,6 +277,7 @@
<div id="special-lagna-section"></div>
<div id="mevg-audit-section"></div>
<div id="ai-prompt-pack-panel"></div>
<div id="guided-topic-discovery-panel"></div>
<div id="skill-coverage-section"></div>
</div>
+89 -1
View File
@@ -57,7 +57,7 @@ import { computeTajika } from './tajika.js';
import { computeNakshatraAdvanced, computeCharaDasha, computeKarakamsha } from './jyotish-advanced.js';
import { computeValidation, computeAudit, computeActionableContext } from './jyotish-export-modules.js';
import { initTooltip, bindTerms, setGlossaryTerminologyMode } from './glossary.js';
import { initAIChat, aiChatSetChartData } from './ai-chat.js';
import { initAIChat, aiChatSetChartData, openAIChatWithPrompt } from './ai-chat.js';
import { initAuth } from './auth.js';
import { initSubscription } from './subscription.js';
import { renderRectificationTab, initRectification } from './rectification.js';
@@ -863,6 +863,7 @@ function renderCompleteReadingTab({ ascendant, moonP, allYogas, dashaData, extra
const extraDashaCount = Object.keys(extraDasas || {}).length;
const apiDashaCount = chartData?._extended?.dasha_count || chartData?.available_dashas?.length || 0;
const dashaCount = Math.max(apiDashaCount, extraDashaCount, dashaData ? 1 : 0);
renderGuidedTopicDiscovery(chartData);
renderSkillCoverage($('skill-coverage-section'), {
ascendant: ascendant?.sign ? `${ascendant.sign} · ${signName(ascendant.sign)}` : '-',
moonNakshatra: formatMoonNakshatra(moonP) || '-',
@@ -875,6 +876,93 @@ function renderCompleteReadingTab({ ascendant, moonP, allYogas, dashaData, extra
renderMEVGAudit($('mevg-audit-section'), mevg);
}
function renderGuidedTopicDiscovery(chartData) {
const host = $('guided-topic-discovery-panel');
if (!host) return;
const topics = Array.isArray(chartData?.modules?.guided_topics)
? chartData.modules.guided_topics
: Array.isArray(chartData?.ai_prompt_pack?.evidence_snapshot?.guided_topics)
? chartData.ai_prompt_pack.evidence_snapshot.guided_topics
: [];
if (!topics.length) {
host.innerHTML = '';
return;
}
host.innerHTML = `
<section class="guided-topic-discovery">
<div class="guided-topic-discovery-head">
<div>
<span>继续深入</span>
<strong>系统根据真实证据建议的下一步主题</strong>
</div>
<small>不是随机建议只读取 full-reading 已算出的 guided_topics</small>
</div>
<div class="guided-topic-grid">
${topics.map(topic => renderGuidedTopicCard(topic)).join('')}
</div>
</section>
`;
host.querySelectorAll('[data-guided-topic-question]').forEach(button => {
button.addEventListener('click', () => {
const question = button.getAttribute('data-guided-topic-question') || '';
openAIChatWithPrompt(question);
});
});
}
function renderGuidedTopicCard(topic) {
const evidence = Array.isArray(topic?.evidence) ? topic.evidence : [];
const questions = Array.isArray(topic?.suggested_questions) ? topic.suggested_questions : [];
const vedastro = topic?.vedastro || {};
const confidenceLabel = topic?.confidence === 'high'
? '高'
: topic?.confidence === 'low'
? '低'
: '中';
const statusLabel = vedastro?.status === 'used'
? '已接入'
: vedastro?.status === 'blocked'
? '受阻'
: '未接入';
return `
<article class="guided-topic-card">
<div class="guided-topic-card-head">
<div>
<strong>${escapeHtml(topic?.title || '-')}</strong>
<p>${escapeHtml(topic?.reality_value || '')}</p>
</div>
<span class="guided-topic-confidence">置信度${escapeHtml(confidenceLabel)}</span>
</div>
<div class="guided-topic-why">${escapeHtml(topic?.why_worth_exploring || '')}</div>
<div class="guided-topic-evidence">
<span class="guided-topic-subtitle">数据依据</span>
${evidence.slice(0, 4).map(row => `
<div class="guided-topic-evidence-row">
<strong>${escapeHtml(row?.label || '-')}</strong>
<span>${escapeHtml(row?.value || '-')}</span>
</div>
`).join('')}
</div>
<div class="guided-topic-questions">
<span class="guided-topic-subtitle">适合继续问</span>
<div class="guided-topic-question-list">
${questions.slice(0, 3).map(question => `
<button type="button" class="guided-topic-question" data-guided-topic-question="${escapeAttr(question)}">
${escapeHtml(question)}
</button>
`).join('')}
</div>
</div>
<div class="guided-topic-footer">
<span>VedAstro${escapeHtml(statusLabel)}</span>
<span>${escapeHtml(topic?.answer_mode || 'tap_or_ask')}</span>
</div>
</article>
`;
}
function renderAIPromptPackPanel(cd) {
const host = $('ai-prompt-pack-panel');
if (!host) return;
+112
View File
@@ -4626,6 +4626,118 @@ body { font-family: var(--font-body); background: var(--bg-page); color: var(--t
.rect-config-row { display: flex; gap: 16px; flex-wrap: wrap; }
.rect-config-row .form-group { flex: 1; min-width: 140px; }
.guided-topic-discovery {
margin: 16px 0;
padding: 14px;
border: 1px solid var(--border-light);
border-radius: 8px;
background: var(--bg-card, #fff);
}
.guided-topic-discovery-head {
display: flex;
justify-content: space-between;
gap: 12px;
align-items: flex-start;
margin-bottom: 12px;
}
.guided-topic-discovery-head span {
display: block;
font-size: 11px;
color: var(--text-muted);
margin-bottom: 4px;
}
.guided-topic-discovery-head strong {
color: var(--text-heading);
font-size: 16px;
}
.guided-topic-discovery-head small {
color: var(--text-secondary);
font-size: 11px;
max-width: 260px;
text-align: right;
}
.guided-topic-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 12px;
}
.guided-topic-card {
border: 1px solid var(--border-light);
border-radius: 8px;
background: var(--bg-surface);
padding: 12px;
display: grid;
gap: 10px;
}
.guided-topic-card-head {
display: flex;
justify-content: space-between;
gap: 10px;
align-items: flex-start;
}
.guided-topic-card-head strong {
display: block;
color: var(--text-heading);
font-size: 14px;
}
.guided-topic-card-head p,
.guided-topic-why {
color: var(--text-secondary);
font-size: 12px;
line-height: 1.6;
}
.guided-topic-confidence {
flex: 0 0 auto;
font-size: 11px;
color: var(--text-muted);
border: 1px solid var(--border-card);
border-radius: 999px;
padding: 3px 8px;
}
.guided-topic-subtitle {
display: block;
font-size: 11px;
color: var(--text-muted);
margin-bottom: 6px;
}
.guided-topic-evidence-row {
display: flex;
gap: 8px;
justify-content: space-between;
font-size: 12px;
color: var(--text-secondary);
margin-bottom: 4px;
}
.guided-topic-evidence-row strong {
color: var(--text-heading);
font-weight: 600;
}
.guided-topic-question-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.guided-topic-question {
border: 1px solid var(--border-card);
border-radius: 999px;
background: var(--bg-white);
color: var(--text-heading);
font-size: 12px;
padding: 6px 10px;
cursor: pointer;
text-align: left;
}
.guided-topic-question:hover {
border-color: var(--accent);
}
.guided-topic-footer {
display: flex;
justify-content: space-between;
gap: 8px;
font-size: 11px;
color: var(--text-muted);
}
.rect-interview { margin-bottom: 16px; }
.rect-interview-head {
display: flex;
+27
View File
@@ -2348,6 +2348,33 @@ def test_static_demo_has_user_visible_capability_boundary() -> None:
assert "Vercel / Netlify / GitHub Pages" in readme
def test_complete_reading_surfaces_guided_topic_discovery() -> None:
"""After chart generation, users should see evidence-backed next topics."""
html = read("index.html")
main = read("main.js")
style = read("style.css")
assert 'id="guided-topic-discovery-panel"' in html
assert "renderGuidedTopicDiscovery(chartData)" in main
assert "function renderGuidedTopicDiscovery" in main
assert "guided_topics" in main
assert "继续深入" in main
assert "数据依据" in main
assert "适合继续问" in main
assert "data-guided-topic-question" in main
assert ".guided-topic-discovery" in style
assert ".guided-topic-card" in style
def test_guided_topic_questions_reuse_ai_chat_entry() -> None:
main = read("main.js")
ai_chat = read("ai-chat.js")
assert "openAIChatWithPrompt" in ai_chat
assert "data-guided-topic-question" in main
assert "openAIChatWithPrompt(question)" in main
def test_real_case_revalidation_is_release_gate_and_accuracy_boundary() -> None:
runner_path = ROOT / "tests" / "run_real_case_revalidation.py"
assert runner_path.exists()