Add API security and async job contracts

This commit is contained in:
732642856
2026-07-16 23:40:01 +08:00
parent a7fdfcb033
commit e013fed79c
10 changed files with 573 additions and 17 deletions
+26
View File
@@ -0,0 +1,26 @@
<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Jyotish Evidence Packet</title>
<style>
body{margin:0;background:#f5f7f8;color:#17212b;font:15px system-ui,-apple-system,"PingFang SC",sans-serif}
main{max-width:960px;margin:0 auto;padding:28px 18px 56px}.bar{display:grid;grid-template-columns:1fr 1fr auto;gap:8px;margin:18px 0}
input,button{font:inherit;padding:10px;border:1px solid #b6c1c6;border-radius:4px}button{background:#006b6b;color:white;border-color:#006b6b;cursor:pointer}
section{background:white;border:1px solid #dce3e5;border-radius:6px;padding:18px;margin:14px 0}h1,h2{margin:0 0 10px}h1{font-size:24px}h2{font-size:17px}pre{white-space:pre-wrap;word-break:break-word;margin:0}.badge{display:inline-block;padding:3px 8px;border-radius:99px;background:#e1f2f0;color:#075d5a}
@media(max-width:640px){.bar{grid-template-columns:1fr}main{padding:20px 12px}}
</style>
<main>
<h1>Evidence Packet</h1>
<p>仅显示已完成任务的可审计计算状态、证据包和技法审计。不会展示内部提示词或原始出生输入。</p>
<div class="bar"><input id="url" placeholder="/api/evidence_packet/high_rigor_workflow/{job_id}"><input id="token" placeholder="access token"><button id="load">加载</button></div>
<section><h2>运行状态</h2><div id="status">等待加载</div></section>
<section><h2>Technique Audit</h2><pre id="audit">-</pre></section>
<section><h2>Machine Evidence Packet</h2><pre id="packet">-</pre></section>
<section><h2>Warnings</h2><pre id="warnings">-</pre></section>
</main>
<script>
const show=(id,value)=>document.getElementById(id).textContent=JSON.stringify(value,null,2);
document.getElementById('load').onclick=async()=>{const url=document.getElementById('url').value.trim(),token=document.getElementById('token').value.trim();if(!url||!token)return alert('需要 job URL 与 access token');const r=await fetch(url,{headers:{Authorization:`Bearer ${token}`}});const d=await r.json();if(!r.ok){show('status',d);return}const s=d.execution_status||{};document.getElementById('status').innerHTML=`<span class="badge">${s.official_evidence_status||'unknown'}</span><pre>${JSON.stringify(s,null,2)}</pre>`;show('audit',d.technique_audit||[]);show('packet',d.machine_evidence_packet||{});show('warnings',d.warnings||[])};
</script>
</html>
+9
View File
@@ -0,0 +1,9 @@
<!doctype html>
<html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Jyotish Consultation</title>
<style>body{margin:0;background:#f5f7f8;color:#17212b;font:16px system-ui,-apple-system,"PingFang SC",sans-serif}main{max-width:820px;margin:auto;padding:36px 16px}section{background:#fff;border:1px solid #dce3e5;border-radius:6px;padding:18px;margin:14px 0}a,button{display:inline-block;padding:10px 14px;margin:4px 6px 4px 0;border-radius:4px;background:#006b6b;color:#fff;text-decoration:none;border:0;font:inherit}input{padding:9px;border:1px solid #b6c1c6;border-radius:4px}#location{white-space:pre-wrap}</style>
<main><h1>Jyotish Consultation</h1><p>先确认出生资料,再选择直接排盘或主动问询式生时校正。外部引擎状态将在证据包中明示。</p>
<section><h2>开始</h2><a href="/rectification">生时不确定:主动问询校正</a><a href="/evidence">查看 Evidence Packet</a></section>
<section><h2>出生地点确认</h2><p>可使用本地城市库;未收录时请手填经纬度。此操作不调用第三方地理服务。</p><input id="city" placeholder="城市名称,例如 北京 / Beijing"><button id="resolve">确认坐标</button><div id="location"></div></section>
<section><h2>运行环境</h2><button id="doctor">检查 API</button><pre id="status">等待检查</pre></section></main>
<script>const out=(id,v)=>document.getElementById(id).textContent=JSON.stringify(v,null,2);document.getElementById('resolve').onclick=async()=>{const city=document.getElementById('city').value.trim();const r=await fetch('/api/location/resolve',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({city})});out('location',await r.json())};document.getElementById('doctor').onclick=async()=>{const r=await fetch('/api/health');out('status',await r.json())};</script></html>
+15
View File
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>主动问询式生时校正</title>
<style>body{margin:0;background:#f5f7f8;color:#17212b;font:15px system-ui,-apple-system,"PingFang SC",sans-serif}main{max-width:860px;margin:auto;padding:28px 16px}form,.question,#result{background:#fff;border:1px solid #dce3e5;border-radius:6px;padding:16px;margin:12px 0}input,button{padding:9px;font:inherit;border:1px solid #b6c1c6;border-radius:4px}button{background:#006b6b;color:#fff;border-color:#006b6b}.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.question label{display:block;padding:5px 0}pre{white-space:pre-wrap;word-break:break-word}@media(max-width:600px){.grid{grid-template-columns:1fr}}</style>
<main><h1>主动问询式生时校正</h1><p>先扫描候选时间,再回答选择题。结果只缩小候选簇,不宣称已经精确到分钟。</p>
<form id="birth"><div class="grid"><input name="year" placeholder="出生年" required><input name="month" placeholder="月" required><input name="day" placeholder="日" required><input name="hour" placeholder="时" required><input name="minute" placeholder="分" required><input name="lat" placeholder="纬度" required><input name="lon" placeholder="经度" required><input name="tz" value="8" placeholder="时区" required><input name="time_uncertainty_minutes" value="30" placeholder="误差分钟"></div><p><button>生成第一轮问题并扫描候选盘</button></p></form><div id="error" role="alert"></div><div id="scan"></div><div id="questions"></div><div id="result"></div></main>
<script>
let questionnaire;
const asObject=f=>Object.fromEntries(new FormData(f).entries());
const fail=e=>document.querySelector('#error').textContent=`请求失败:${e.message}。请检查输入后重试。`;
async function post(url,body){const r=await fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});const d=await r.json();if(!r.ok)throw new Error(d.error||r.status);return d}
document.querySelector('#birth').onsubmit=async e=>{e.preventDefault();document.querySelector('#error').textContent='正在计算候选盘…';try{const p=asObject(e.target);for(const k of Object.keys(p))p[k]=Number(p[k]);const [q,scan]=await Promise.all([post('/api/rectification/questionnaire',p),post('/api/rectification/sensitivity_scan',p)]);questionnaire=q;document.querySelector('#error').textContent='';document.querySelector('#scan').innerHTML=`<h2>实际候选盘差异</h2><pre>${JSON.stringify({candidate_count:scan.candidate_count,step_minutes:scan.step_minutes,transitions:scan.transitions,supported_vargas:scan.supported_vargas,unavailable_vargas:scan.unavailable_vargas,pending_layers:scan.pending_layers,boundary:scan.boundary},null,2)}</pre>`;render(questionnaire.questions||[])}catch(err){fail(err)}};
function render(qs){const root=document.querySelector('#questions');root.innerHTML=qs.map(q=>`<section class="question"><strong>${q.prompt}</strong>${q.options.map(o=>`<label><input type="radio" name="${q.id}" value="${o.key}"> ${o.key}. ${o.label}</label>`).join('')}</section>`).join('')+'<button id="score">提交本轮答案</button>';document.querySelector('#score').onclick=score}
async function score(){try{const answers={};document.querySelectorAll('#questions input:checked').forEach(e=>answers[e.name]=e.value);const d=await post('/api/rectification/answers',{questionnaire,answers});document.querySelector('#result').innerHTML=`<h2>候选簇排序</h2><pre>${JSON.stringify({candidate_cluster_rankings:d.candidate_cluster_rankings,next_round:d.next_round,boundary:d.boundary},null,2)}</pre>`}catch(err){fail(err)}}
</script></html>