Add web professional reading gateway surface
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
# Mainland China VedAstro Gateway mode.
|
||||||
|
# Copy to .env.local on the backend host. Do not expose secrets in the browser.
|
||||||
|
|
||||||
|
VEDASTRO_GATEWAY_MODE=cn_gateway
|
||||||
|
VEDASTRO_SELF_HOST_ENDPOINT=https://your-domain.example.com/vedastro
|
||||||
|
|
||||||
|
# Optional official upstream. Keep it backend-only.
|
||||||
|
# VEDASTRO_API_ENDPOINT=https://api.vedastro.org/api
|
||||||
|
# VEDASTRO_API_KEY=
|
||||||
|
# VEDASTRO_ENABLE_NETWORK=0
|
||||||
|
|
||||||
|
# Cache / TTL / free-tier queue. These keep ordinary users from calling upstream directly.
|
||||||
|
VEDASTRO_CACHE_TTL_SECONDS=604800
|
||||||
|
VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS=604800
|
||||||
|
VEDASTRO_GATEWAY_QUEUE_ENABLED=1
|
||||||
|
VEDASTRO_FREE_TIER_QUEUE=1
|
||||||
|
VEDASTRO_FAIL_OPEN_LOCAL=1
|
||||||
|
|
||||||
|
# Runtime guardrails.
|
||||||
|
VEDASTRO_TIMEOUT_SECONDS=20
|
||||||
|
VEDASTRO_FULL_CATALOG_SAMPLE_LIMIT=0
|
||||||
@@ -77,6 +77,33 @@ VEDASTRO_ENABLE_NETWORK=1
|
|||||||
|
|
||||||
默认运行是快速模式:VedAstro official 证据层如果没有在前台预算内闭环,会诚实标记 `official_snapshot_budget_exhausted` 并退回本地 Swiss Ephemeris。要跑 official extended 模式,复制 `.env.official.example` 为 `.env.local` 并填好 endpoint/network/key;运行 `python3 scripts/diagnose_vedastro_mode.py` 可先确认当前是 `fast_local_fallback` 还是 `official_extended`。
|
默认运行是快速模式:VedAstro official 证据层如果没有在前台预算内闭环,会诚实标记 `official_snapshot_budget_exhausted` 并退回本地 Swiss Ephemeris。要跑 official extended 模式,复制 `.env.official.example` 为 `.env.local` 并填好 endpoint/network/key;运行 `python3 scripts/diagnose_vedastro_mode.py` 可先确认当前是 `fast_local_fallback` 还是 `official_extended`。
|
||||||
|
|
||||||
|
### 中国大陆用户:VedAstro Gateway 模式
|
||||||
|
|
||||||
|
普通中国大陆用户不需要、也不应该让浏览器直连 VedAstro。推荐部署方式是:网页只访问你自己的本地或云端后端;后端通过 `VedAstro Gateway` 统一管理 self-host、official upstream、TTL/cache、free-tier queue 和 local fallback。
|
||||||
|
|
||||||
|
最短配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.cn.example .env.local
|
||||||
|
python3 scripts/jyotish_api_server.py --host 127.0.0.1 --port 5200
|
||||||
|
cd jyotish-app && npm run dev -- --host 127.0.0.1 --port 5173
|
||||||
|
```
|
||||||
|
|
||||||
|
网页侧使用:
|
||||||
|
|
||||||
|
- `Trust Center -> Web Professional Reading v1`
|
||||||
|
- `/api/vedastro_gateway/status` 查看当前后端策略
|
||||||
|
- `/api/vedastro_gateway/run` 生成 VedAstro-compatible evidence packet
|
||||||
|
- `/api/professional_reading` 生成网页专业解盘包
|
||||||
|
|
||||||
|
关键边界:
|
||||||
|
|
||||||
|
- 不要让浏览器直连 VedAstro,也不要把 `VEDASTRO_API_KEY` 放进前端。
|
||||||
|
- `VEDASTRO_CACHE_TTL_SECONDS` 和 `VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS` 用于缓存官方或自建服务结果。
|
||||||
|
- `VEDASTRO_GATEWAY_QUEUE_ENABLED=1` / `VEDASTRO_FREE_TIER_QUEUE=1` 用于把昂贵或被限流的外部请求排队。
|
||||||
|
- 如果 VedAstro 官方或自建服务不可达,`VEDASTRO_FAIL_OPEN_LOCAL=1` 会保持本地 Jyotish 引擎继续输出,并在 Technique Audit Table 里降级标注。
|
||||||
|
- Gateway 不会默认声称跑完 641 项;它只把 capability catalog、dynamic selection、cache/queue/fallback 状态作为证据边界交给 strict workflow。
|
||||||
|
|
||||||
### Codex 用户级 VedAstro + strict workflow 入口
|
### Codex 用户级 VedAstro + strict workflow 入口
|
||||||
|
|
||||||
如果用户在 Codex 窗口从云端 Git 仓库拉取本项目,推荐先走这一条稳定入口,而不是手动拼多个底层脚本:
|
如果用户在 Codex 窗口从云端 Git 仓库拉取本项目,推荐先走这一条稳定入口,而不是手动拼多个底层脚本:
|
||||||
|
|||||||
+27
-1
@@ -304,6 +304,7 @@ async function sendMessage() {
|
|||||||
|
|
||||||
function buildChartContext(cd, guidedTopicContext = null) {
|
function buildChartContext(cd, guidedTopicContext = null) {
|
||||||
if (!cd?.planets || !cd?.ascendant) return t('ai.no.data');
|
if (!cd?.planets || !cd?.ascendant) return t('ai.no.data');
|
||||||
|
const professionalReadingContext = buildProfessionalReadingContext(cd);
|
||||||
if (cd.ai_prompt_pack?.prompt_zh && cd.ai_prompt_pack?.evidence_snapshot) {
|
if (cd.ai_prompt_pack?.prompt_zh && cd.ai_prompt_pack?.evidence_snapshot) {
|
||||||
const workflow = cd._consultationWorkflow || {};
|
const workflow = cd._consultationWorkflow || {};
|
||||||
const runtimePlanner = workflow.runtime_planner || {};
|
const runtimePlanner = workflow.runtime_planner || {};
|
||||||
@@ -368,6 +369,8 @@ function buildChartContext(cd, guidedTopicContext = null) {
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
: '';
|
: '';
|
||||||
return [
|
return [
|
||||||
|
professionalReadingContext,
|
||||||
|
'',
|
||||||
'【AI Prompt Pack】',
|
'【AI Prompt Pack】',
|
||||||
cd.ai_prompt_pack.prompt_zh,
|
cd.ai_prompt_pack.prompt_zh,
|
||||||
'',
|
'',
|
||||||
@@ -401,7 +404,30 @@ function buildChartContext(cd, guidedTopicContext = null) {
|
|||||||
if (!p || p.error) continue;
|
if (!p || p.error) continue;
|
||||||
ctx += `${planetName(pn)}: ${signName(p.sign)} ${p.degree_in_sign?.toFixed(2) || ''}° H${p.house} ${p.status || ''} ${p.retrograde ? 'R' : ''} ${p.nakshatra || ''}\n`;
|
ctx += `${planetName(pn)}: ${signName(p.sign)} ${p.degree_in_sign?.toFixed(2) || ''}° H${p.house} ${p.status || ''} ${p.retrograde ? 'R' : ''} ${p.nakshatra || ''}\n`;
|
||||||
}
|
}
|
||||||
return `${ctx}\n${DASHA_SHADBALA_AI_CALIBRATION_BOUNDARY}`;
|
return `${professionalReadingContext}\n\n${ctx}\n${DASHA_SHADBALA_AI_CALIBRATION_BOUNDARY}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildProfessionalReadingContext(cd) {
|
||||||
|
const packet = cd?.professional_reading || window.__jyotishProfessionalReading?.professional_reading || null;
|
||||||
|
if (!packet || typeof packet !== 'object') {
|
||||||
|
return [
|
||||||
|
'【Professional Reading Packet】',
|
||||||
|
'status=not_loaded',
|
||||||
|
'professional_reading: absent',
|
||||||
|
'VedAstro Gateway Boundary: not_loaded',
|
||||||
|
'user_led_calibration_controls: absent',
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
const gateway = packet.vedastro_gateway || {};
|
||||||
|
const controls = packet.user_led_calibration_controls || {};
|
||||||
|
const requiredRows = packet.technique_audit_table_required_rows || [];
|
||||||
|
return [
|
||||||
|
'【Professional Reading Packet】',
|
||||||
|
`status=${gateway.status || 'unknown'} · professional_reading=loaded`,
|
||||||
|
`VedAstro Gateway Boundary: ${gateway.user_visibility?.boundary || gateway.gateway_status?.boundary || 'blocked_or_not_reported'}`,
|
||||||
|
`user_led_calibration_controls: blind_mode=${Boolean(controls.blind_mode)} · disable_life_event_feedback=${Boolean(controls.disable_life_event_feedback)}`,
|
||||||
|
`required_audit_rows=${requiredRows.join(' / ') || 'Functional Benefic/Malefic / MEVG / Real Case Calibration / VedAstro Gateway Boundary'}`,
|
||||||
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildAISetupGuidance() {
|
function buildAISetupGuidance() {
|
||||||
|
|||||||
@@ -240,6 +240,18 @@ async function runVedAstroRangeScan(payload) {
|
|||||||
return postJson('/api/vedastro/range_scan', payload);
|
return postJson('/api/vedastro/range_scan', payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getVedAstroGatewayStatus() {
|
||||||
|
return fetchJson('/api/vedastro_gateway/status');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runVedAstroGateway(payload) {
|
||||||
|
return postJson('/api/vedastro_gateway/run', payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runProfessionalReading(payload) {
|
||||||
|
return postJson('/api/professional_reading', payload);
|
||||||
|
}
|
||||||
|
|
||||||
async function getTechniqueCatalog() {
|
async function getTechniqueCatalog() {
|
||||||
let lastError = null;
|
let lastError = null;
|
||||||
for (const base of getApiBases(true)) {
|
for (const base of getApiBases(true)) {
|
||||||
@@ -465,6 +477,9 @@ window.JyotishAPI = {
|
|||||||
getCapabilityAudit,
|
getCapabilityAudit,
|
||||||
getVedAstroStatus,
|
getVedAstroStatus,
|
||||||
runVedAstroRangeScan,
|
runVedAstroRangeScan,
|
||||||
|
getVedAstroGatewayStatus,
|
||||||
|
runVedAstroGateway,
|
||||||
|
runProfessionalReading,
|
||||||
getTechniqueCatalog,
|
getTechniqueCatalog,
|
||||||
runTechniqueExample,
|
runTechniqueExample,
|
||||||
computeAnnual,
|
computeAnnual,
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ import { escapeHtml, escapeAttr, safeNumber } from './security.js';
|
|||||||
import { renderSkillCoverage } from './skill-map.js';
|
import { renderSkillCoverage } from './skill-map.js';
|
||||||
import { initChartImport } from './import-chart.js';
|
import { initChartImport } from './import-chart.js';
|
||||||
import { buildMEVGAudit, renderMEVGAudit } from './mevg-audit.js';
|
import { buildMEVGAudit, renderMEVGAudit } from './mevg-audit.js';
|
||||||
|
import {
|
||||||
|
buildProfessionalReadingPayload,
|
||||||
|
renderProfessionalReadingPanel,
|
||||||
|
} from './professional-reading.js';
|
||||||
|
|
||||||
// 合并所有 Yoga 定义
|
// 合并所有 Yoga 定义
|
||||||
const ALL_YOGA_DEFS = [...YOGA_DEFINITIONS, ...YOGA_EXTENDED_A, ...YOGA_EXTENDED_B];
|
const ALL_YOGA_DEFS = [...YOGA_DEFINITIONS, ...YOGA_EXTENDED_A, ...YOGA_EXTENDED_B];
|
||||||
@@ -93,6 +97,7 @@ const PANCHANGA_CONDITIONS = [
|
|||||||
['avoid_new_start', '不宜新开始'],
|
['avoid_new_start', '不宜新开始'],
|
||||||
['good_choghadiya', '有吉利 Choghadiya'],
|
['good_choghadiya', '有吉利 Choghadiya'],
|
||||||
];
|
];
|
||||||
|
let professionalReadingState = {};
|
||||||
const PANCHANGA_CONDITION_GUIDE = {
|
const PANCHANGA_CONDITION_GUIDE = {
|
||||||
all: '显示范围内所有日期。',
|
all: '显示范围内所有日期。',
|
||||||
has_vrata: '当天存在 vrata、lunar observance 或节日候选标签。',
|
has_vrata: '当天存在 vrata、lunar observance 或节日候选标签。',
|
||||||
@@ -2361,6 +2366,9 @@ function renderTrustCenterPanel() {
|
|||||||
</div>
|
</div>
|
||||||
${renderRuntimeHealthPanel(runtime)}
|
${renderRuntimeHealthPanel(runtime)}
|
||||||
${renderVedAstroUserScanPanel()}
|
${renderVedAstroUserScanPanel()}
|
||||||
|
<div id="professional-reading-host">
|
||||||
|
${renderProfessionalReadingPanel(professionalReadingState)}
|
||||||
|
</div>
|
||||||
${renderValidationTransparencyPanel()}
|
${renderValidationTransparencyPanel()}
|
||||||
${renderDashaShadbalaCalibrationPanel()}
|
${renderDashaShadbalaCalibrationPanel()}
|
||||||
${renderOracleEvidenceIntakePanel()}
|
${renderOracleEvidenceIntakePanel()}
|
||||||
@@ -3244,6 +3252,52 @@ async function runVedAstroRangeScanFromPanel(panel) {
|
|||||||
renderAll();
|
renderAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function refreshProfessionalReadingGatewayStatus() {
|
||||||
|
const status = $('professional-reading-status') || $('trust-center-status');
|
||||||
|
professionalReadingState = { ...professionalReadingState, message: '正在刷新 VedAstro Gateway...' };
|
||||||
|
if (status) status.textContent = professionalReadingState.message;
|
||||||
|
try {
|
||||||
|
const gatewayStatus = await window.JyotishAPI.getVedAstroGatewayStatus();
|
||||||
|
professionalReadingState = {
|
||||||
|
...professionalReadingState,
|
||||||
|
gatewayStatus,
|
||||||
|
message: 'VedAstro Gateway 状态已刷新。',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
professionalReadingState = {
|
||||||
|
...professionalReadingState,
|
||||||
|
message: `VedAstro Gateway 状态未完成:${error?.message || '本地 API 未连接'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
renderAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runProfessionalReadingFromPanel() {
|
||||||
|
const status = $('professional-reading-status') || $('trust-center-status');
|
||||||
|
professionalReadingState = { ...professionalReadingState, message: '正在运行专业解盘代理...' };
|
||||||
|
if (status) status.textContent = professionalReadingState.message;
|
||||||
|
try {
|
||||||
|
const payload = buildProfessionalReadingPayload(chartData || {});
|
||||||
|
const result = await window.JyotishAPI.runProfessionalReading(payload);
|
||||||
|
professionalReadingState = {
|
||||||
|
...professionalReadingState,
|
||||||
|
result,
|
||||||
|
gatewayStatus: result?.professional_reading?.vedastro_gateway?.gateway_status || professionalReadingState.gatewayStatus || {},
|
||||||
|
message: '专业解盘包已生成。',
|
||||||
|
};
|
||||||
|
if (chartData) {
|
||||||
|
chartData.professional_reading = result?.professional_reading || result;
|
||||||
|
window.__jyotishProfessionalReading = result;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
professionalReadingState = {
|
||||||
|
...professionalReadingState,
|
||||||
|
message: `专业解盘未完成:${error?.message || '本地 API 未连接'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
renderAll();
|
||||||
|
}
|
||||||
|
|
||||||
async function promptPWAInstall() {
|
async function promptPWAInstall() {
|
||||||
const status = $('trust-center-status');
|
const status = $('trust-center-status');
|
||||||
const prompt = window.__jyotishDeferredInstallPrompt;
|
const prompt = window.__jyotishDeferredInstallPrompt;
|
||||||
@@ -3797,6 +3851,12 @@ function bindProvenanceActions() {
|
|||||||
if (btn.dataset.action === 'vedastro-run-range-scan') {
|
if (btn.dataset.action === 'vedastro-run-range-scan') {
|
||||||
runVedAstroRangeScanFromPanel(panel);
|
runVedAstroRangeScanFromPanel(panel);
|
||||||
}
|
}
|
||||||
|
if (btn.dataset.action === 'professional-reading-gateway') {
|
||||||
|
refreshProfessionalReadingGatewayStatus();
|
||||||
|
}
|
||||||
|
if (btn.dataset.action === 'professional-reading-run') {
|
||||||
|
runProfessionalReadingFromPanel();
|
||||||
|
}
|
||||||
if (btn.dataset.action === 'pwa-install') {
|
if (btn.dataset.action === 'pwa-install') {
|
||||||
promptPWAInstall();
|
promptPWAInstall();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { escapeHtml } from './security.js';
|
||||||
|
|
||||||
|
const DEFAULT_QUESTION = '请按高严谨流程做盲推解盘,先列 Technique Audit Table,再给结论。';
|
||||||
|
const DEFAULT_THEMES = ['career', 'relationship', 'finance', 'health', 'timing'];
|
||||||
|
|
||||||
|
export function renderProfessionalReadingPanel(state = {}) {
|
||||||
|
const status = state.gatewayStatus || {};
|
||||||
|
const result = state.result?.professional_reading || null;
|
||||||
|
return `
|
||||||
|
<section class="provenance-card professional-reading-panel">
|
||||||
|
<div class="provenance-head">
|
||||||
|
<span>Web Professional Reading v1</span>
|
||||||
|
<strong>${escapeHtml(status.active_backend || 'gateway-ready')}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="trust-status-grid">
|
||||||
|
${renderMetric('Technique Audit Table', 'required', '必须显示 Functional Benefic/Malefic、MEVG、Real Case Calibration。')}
|
||||||
|
${renderMetric('MEVG / Global Web Evidence', 'queued-or-blocked', '外部资料采集必须进入队列或说明 blocked。')}
|
||||||
|
${renderMetric('Real Case Calibration', 'required', '找不到相似案例时必须降级,不得静默跳过。')}
|
||||||
|
${renderMetric('VedAstro Gateway Boundary', status.mode || 'local_first', '普通中国大陆用户不需要浏览器直连 VedAstro。')}
|
||||||
|
</div>
|
||||||
|
<div class="professional-reading-actions">
|
||||||
|
<button type="button" class="provenance-action" data-action="professional-reading-run">运行专业解盘</button>
|
||||||
|
<button type="button" class="provenance-action" data-action="professional-reading-gateway">刷新网关状态</button>
|
||||||
|
</div>
|
||||||
|
<div id="professional-reading-status" class="workspace-import-status" aria-live="polite">
|
||||||
|
${escapeHtml(state.message || '等待运行。')}
|
||||||
|
</div>
|
||||||
|
${result ? renderProfessionalReadingResult(result) : ''}
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildProfessionalReadingPayload(chartData = {}, overrides = {}) {
|
||||||
|
const birth = window.__jyotishBirth || chartData.birth_info || chartData.birth || {};
|
||||||
|
return {
|
||||||
|
year: Number(birth.year || birth.date?.slice?.(0, 4) || overrides.year || REDACTED_YEAR),
|
||||||
|
month: Number(birth.month || birth.date?.slice?.(5, 7) || overrides.month || 1),
|
||||||
|
day: Number(birth.day || birth.date?.slice?.(8, 10) || overrides.day || 1),
|
||||||
|
hour: Number(birth.hour ?? overrides.hour ?? 12),
|
||||||
|
minute: Number(birth.minute ?? overrides.minute ?? 0),
|
||||||
|
second: Number(birth.second ?? overrides.second ?? 0),
|
||||||
|
lat: Number(birth.lat ?? overrides.lat ?? 0),
|
||||||
|
lon: Number(birth.lon ?? overrides.lon ?? 0),
|
||||||
|
tz: Number(birth.tz ?? overrides.tz ?? 8),
|
||||||
|
question: overrides.question || DEFAULT_QUESTION,
|
||||||
|
themes: overrides.themes || DEFAULT_THEMES,
|
||||||
|
reference_date: overrides.reference_date || new Date().toISOString().slice(0, 10),
|
||||||
|
blind_mode: overrides.blind_mode ?? true,
|
||||||
|
disable_life_event_feedback: overrides.disable_life_event_feedback ?? true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bindProfessionalReadingPanel(container, chartData = null, setState = () => {}) {
|
||||||
|
container?.querySelector('[data-action="professional-reading-gateway"]')?.addEventListener('click', async () => {
|
||||||
|
setState({ message: '正在刷新 VedAstro Gateway...' });
|
||||||
|
const gatewayStatus = await window.JyotishAPI.getVedAstroGatewayStatus();
|
||||||
|
setState({ gatewayStatus, message: 'VedAstro Gateway 状态已刷新。' });
|
||||||
|
});
|
||||||
|
container?.querySelector('[data-action="professional-reading-run"]')?.addEventListener('click', async () => {
|
||||||
|
setState({ message: '正在运行专业解盘代理...' });
|
||||||
|
const payload = buildProfessionalReadingPayload(chartData);
|
||||||
|
const result = await window.JyotishAPI.runProfessionalReading(payload);
|
||||||
|
setState({ result, gatewayStatus: result?.professional_reading?.vedastro_gateway?.gateway_status || {}, message: '专业解盘包已生成。' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMetric(label, value, note) {
|
||||||
|
return `
|
||||||
|
<div class="trust-status-card">
|
||||||
|
<span>${escapeHtml(label)}</span>
|
||||||
|
<strong>${escapeHtml(value)}</strong>
|
||||||
|
<p>${escapeHtml(note)}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderProfessionalReadingResult(result) {
|
||||||
|
const gateway = result.vedastro_gateway || {};
|
||||||
|
const controls = result.user_led_calibration_controls || {};
|
||||||
|
const requiredRows = result.technique_audit_table_required_rows || [];
|
||||||
|
return `
|
||||||
|
<div class="professional-reading-result">
|
||||||
|
<h3>Professional Reading Packet</h3>
|
||||||
|
<p>status: ${escapeHtml(gateway.status || 'unknown')}</p>
|
||||||
|
<p>blind_mode: ${escapeHtml(String(Boolean(controls.blind_mode)))}</p>
|
||||||
|
<ul>
|
||||||
|
${requiredRows.map(row => `<li>${escapeHtml(row)}</li>`).join('')}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
@@ -240,6 +240,18 @@ async function runVedAstroRangeScan(payload) {
|
|||||||
return postJson('/api/vedastro/range_scan', payload);
|
return postJson('/api/vedastro/range_scan', payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getVedAstroGatewayStatus() {
|
||||||
|
return fetchJson('/api/vedastro_gateway/status');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runVedAstroGateway(payload) {
|
||||||
|
return postJson('/api/vedastro_gateway/run', payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runProfessionalReading(payload) {
|
||||||
|
return postJson('/api/professional_reading', payload);
|
||||||
|
}
|
||||||
|
|
||||||
async function getTechniqueCatalog() {
|
async function getTechniqueCatalog() {
|
||||||
let lastError = null;
|
let lastError = null;
|
||||||
for (const base of getApiBases(true)) {
|
for (const base of getApiBases(true)) {
|
||||||
@@ -465,6 +477,9 @@ window.JyotishAPI = {
|
|||||||
getCapabilityAudit,
|
getCapabilityAudit,
|
||||||
getVedAstroStatus,
|
getVedAstroStatus,
|
||||||
runVedAstroRangeScan,
|
runVedAstroRangeScan,
|
||||||
|
getVedAstroGatewayStatus,
|
||||||
|
runVedAstroGateway,
|
||||||
|
runProfessionalReading,
|
||||||
getTechniqueCatalog,
|
getTechniqueCatalog,
|
||||||
runTechniqueExample,
|
runTechniqueExample,
|
||||||
computeAnnual,
|
computeAnnual,
|
||||||
|
|||||||
@@ -1170,6 +1170,56 @@ def test_api_bridge_failures_have_recovery_guidance() -> None:
|
|||||||
assert "const data = await resp.json();" not in public_bridge
|
assert "const data = await resp.json();" not in public_bridge
|
||||||
|
|
||||||
|
|
||||||
|
def test_professional_reading_web_surface_uses_gateway_and_backend_agent() -> None:
|
||||||
|
bridge = read("api-bridge.js")
|
||||||
|
public_bridge = read("public/api-bridge.js")
|
||||||
|
main = read("main.js")
|
||||||
|
professional = read("professional-reading.js")
|
||||||
|
|
||||||
|
assert bridge == public_bridge
|
||||||
|
assert "getVedAstroGatewayStatus" in bridge
|
||||||
|
assert "postJson('/api/vedastro_gateway/run'" in bridge
|
||||||
|
assert "postJson('/api/professional_reading'" in bridge
|
||||||
|
assert "renderProfessionalReadingPanel" in professional
|
||||||
|
assert "Technique Audit Table" in professional
|
||||||
|
assert "MEVG / Global Web Evidence" in professional
|
||||||
|
assert "Real Case Calibration" in professional
|
||||||
|
assert "VedAstro Gateway Boundary" in professional
|
||||||
|
assert "renderProfessionalReadingPanel" in main
|
||||||
|
|
||||||
|
|
||||||
|
def test_ai_chat_consumes_professional_reading_packet() -> None:
|
||||||
|
ai_chat = read("ai-chat.js")
|
||||||
|
assert "【Professional Reading Packet】" in ai_chat
|
||||||
|
assert "professional_reading" in ai_chat
|
||||||
|
assert "user_led_calibration_controls" in ai_chat
|
||||||
|
assert "VedAstro Gateway Boundary" in ai_chat
|
||||||
|
|
||||||
|
|
||||||
|
def test_cn_gateway_docs_and_env_example_exist() -> None:
|
||||||
|
env = (ROOT / ".env.cn.example").read_text(encoding="utf-8")
|
||||||
|
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
for token in [
|
||||||
|
"VEDASTRO_GATEWAY_MODE=cn_gateway",
|
||||||
|
"VEDASTRO_SELF_HOST_ENDPOINT",
|
||||||
|
"VEDASTRO_CACHE_TTL_SECONDS",
|
||||||
|
"VEDASTRO_GATEWAY_QUEUE_ENABLED=1",
|
||||||
|
"VEDASTRO_FAIL_OPEN_LOCAL=1",
|
||||||
|
]:
|
||||||
|
assert token in env
|
||||||
|
for token in [
|
||||||
|
"中国大陆用户",
|
||||||
|
"VedAstro Gateway",
|
||||||
|
"不要让浏览器直连 VedAstro",
|
||||||
|
"/api/vedastro_gateway/status",
|
||||||
|
"/api/professional_reading",
|
||||||
|
"TTL/cache",
|
||||||
|
"free-tier queue",
|
||||||
|
]:
|
||||||
|
assert token in readme
|
||||||
|
|
||||||
|
|
||||||
def test_chart_compute_failures_have_visible_recovery_guidance() -> None:
|
def test_chart_compute_failures_have_visible_recovery_guidance() -> None:
|
||||||
html = read("index.html")
|
html = read("index.html")
|
||||||
main = read("main.js")
|
main = read("main.js")
|
||||||
|
|||||||
Reference in New Issue
Block a user