diff --git a/jyotish-app/ai-chat.js b/jyotish-app/ai-chat.js
index 51da4075..b67a9bee 100644
--- a/jyotish-app/ai-chat.js
+++ b/jyotish-app/ai-chat.js
@@ -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 调用)
// ============================================================================
diff --git a/jyotish-app/index.html b/jyotish-app/index.html
index 9a18f2d4..1cc9bb25 100644
--- a/jyotish-app/index.html
+++ b/jyotish-app/index.html
@@ -277,6 +277,7 @@
+
diff --git a/jyotish-app/main.js b/jyotish-app/main.js
index 28de7d6f..bd1f1555 100644
--- a/jyotish-app/main.js
+++ b/jyotish-app/main.js
@@ -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 = `
+
+
+
+ 继续深入
+ 系统根据真实证据建议的下一步主题
+
+
不是随机建议,只读取 full-reading 已算出的 guided_topics
+
+
+ ${topics.map(topic => renderGuidedTopicCard(topic)).join('')}
+
+
+ `;
+ 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 `
+
+
+
+
${escapeHtml(topic?.title || '-')}
+
${escapeHtml(topic?.reality_value || '')}
+
+
置信度:${escapeHtml(confidenceLabel)}
+
+ ${escapeHtml(topic?.why_worth_exploring || '')}
+
+
数据依据
+ ${evidence.slice(0, 4).map(row => `
+
+ ${escapeHtml(row?.label || '-')}
+ ${escapeHtml(row?.value || '-')}
+
+ `).join('')}
+
+
+
适合继续问
+
+ ${questions.slice(0, 3).map(question => `
+
+ `).join('')}
+
+
+
+
+ `;
+}
+
function renderAIPromptPackPanel(cd) {
const host = $('ai-prompt-pack-panel');
if (!host) return;
diff --git a/jyotish-app/style.css b/jyotish-app/style.css
index c8f6cf08..03ea2414 100644
--- a/jyotish-app/style.css
+++ b/jyotish-app/style.css
@@ -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;
diff --git a/tests/test_frontend_productization.py b/tests/test_frontend_productization.py
index 92c0a3bd..eed4b363 100644
--- a/tests/test_frontend_productization.py
+++ b/tests/test_frontend_productization.py
@@ -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()