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()