Add guided topic discovery panel and chat handoff
This commit is contained in:
@@ -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 调用)
|
||||
// ============================================================================
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user