(() => { const {useState, useEffect, useRef} = React; const SIGN_NAMES = {Ari:'白羊座',Tau:'金牛座',Gem:'双子座',Can:'巨蟹座',Leo:'狮子座',Vir:'处女座',Lib:'天秤座',Sco:'天蝎座',Sag:'射手座',Cap:'摩羯座',Aqu:'水瓶座',Pis:'双鱼座'}; const PERIODS = [{id:'today',name:'今天',hint:'当天'}, {id:'week',name:'七天',hint:'短期节奏'}, {id:'month',name:'一个月',hint:'30天'}, {id:'quarter',name:'三个月',hint:'90天'}, {id:'year',name:'一年',hint:'365天'}]; const ZONES = [['Asia/Shanghai','中国大陆'],['Asia/Hong_Kong','香港'],['Asia/Taipei','台湾'],['Asia/Tokyo','日本'],['Asia/Seoul','韩国'],['Asia/Singapore','新加坡'],['America/New_York','美国东部'],['America/Los_Angeles','美国西部'],['Europe/London','英国'],['Europe/Paris','法国'],['Australia/Sydney','澳大利亚悉尼'],['UTC','世界协调时']]; const LAYERS = {a:'A方行运',b:'B方行运',composite:'组合盘行运',synastry:'双方比较盘'}; const fresh = () => ({name:'',date:'',time:'12:00',known:false,city:'',tz_str:'Asia/Shanghai'}); function localDate() {const d=new Date();return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;} function OrbitArt(){return ;} function InfoIcon(){return ;} function BirthCard({id,value,setValue,cities}) { const change=(key,next)=>setValue({...value,[key]:next}); return
{id.toUpperCase()}{id==='a'?'你的出生资料':'另一位的出生资料'}
change('name',e.target.value)} placeholder={id==='a'?'你':'另一位'} autoComplete="off"/>
change('date',e.target.value)}/>
{!value.city &&
}
{value.known && <>change('time',e.target.value)}/>}
; } function Evidence({events,precision}) { const display=(value)=> value ? (precision==='timed'?value.slice(0,16).replace('T',' '):value.slice(0,10)) : '搜索范围内未确定'; return
查看计算依据 · {events.length} 项
{events.map(event=>
{LAYERS[event.layer]}{event.background?' · 持续背景':''}{event.repeated?' · 有重复经过':''}{event.title}{event.windows ? event.windows.map((w,i)=>

{display(w.visible_start)} 至 {display(w.visible_end)}{w.exact_in_period.length>0?` · ${precision==='timed'?'精确相位':'估算日期'}:${w.exact_in_period.map(display).join('、')}`:''}{w.continuing_from_before?' · 此前已持续':''}{w.continuing_after?' · 延续至区间后':''}

) :

{event.tone} · 相位误差 {event.orb}°

}
)}
{!events.length &&

本区间未筛选出突出相位,不代表现实关系必然平静。

}
; } function Actions({items}) {return ;} function App(){ const [a,setA]=useState(fresh),[b,setB]=useState(fresh),[cities,setCities]=useState([]); const [busy,setBusy]=useState(false),[error,setError]=useState(''),[report,setReport]=useState(null),[active,setActive]=useState('today'),[editing,setEditing]=useState(true),[elapsed,setElapsed]=useState(0),[names,setNames]=useState({a:'你',b:'另一位'}); const controller=useRef(null),sequence=useRef(0),tabRefs=useRef([]); useEffect(()=>{const abort=new AbortController();fetch('/api/relationship-forecast/options',{signal:abort.signal}).then(r=>r.ok?r.json():Promise.reject()).then(data=>setCities(data.cities||[])).catch(()=>{});return ()=>abort.abort();},[]); useEffect(()=>{if(!busy)return;const timer=setInterval(()=>setElapsed(t=>t+1),1000);return()=>clearInterval(timer);},[busy]); useEffect(()=>()=>controller.current?.abort(),[]); const cancel=()=>{sequence.current+=1;controller.current?.abort();setBusy(false);setError('已取消等待。出生资料仍在,可重新生成。');}; const generate=async event=>{ event.preventDefault();if(busy)return; setError('');setBusy(true);setElapsed(0); const seq=++sequence.current,abort=new AbortController();controller.current=abort; const timeout=setTimeout(()=>abort.abort(),240000); const profile=v=>({name:v.name,date:v.date,time:v.known?v.time:null,city:v.city||null,tz_str:v.tz_str}); try{ const response=await fetch('/api/relationship-forecast/calculate',{method:'POST',headers:getMysticCalculationHeaders(),body:JSON.stringify({a:profile(a),b:profile(b),report_tz:Intl.DateTimeFormat().resolvedOptions().timeZone||'Asia/Shanghai'}),signal:abort.signal}); const body=await response.json().catch(()=>({})); if(!response.ok)throw new Error(response.status===429?'请求较多,请稍后再试。':typeof body.detail==='string'?body.detail:response.status===422?'请核对出生日期、时间与时区,尤其是历史夏令时。':'解读暂未完成,请稍后重试。出生资料已保留。'); const complete=body.success&&body.data?.participants?.a?.sign&&body.data?.participants?.b?.sign&&body.data?.baseline&&Array.isArray(body.data?.synastry)&&body.data?.outlook?.reading&&Array.isArray(body.data?.outlook?.events)&&PERIODS.every(p=>body.data?.periods?.[p.id]?.reading&&Array.isArray(body.data.periods[p.id].events)&&Array.isArray(body.data.periods[p.id].timeline)&&Array.isArray(body.data.periods[p.id].reading.actions)); if(!complete)throw new Error('这次报告未完整生成,请重试。'); if(seq!==sequence.current)return; setReport(body.data);setNames({a:a.name.trim()||'你',b:b.name.trim()||'另一位'});setActive('today');setEditing(false);window.scrollTo({top:0,behavior:'auto'}); }catch(err){if(seq===sequence.current)setError(err.name==='AbortError'?'等待时间较长,本次请求已停止,可以重新尝试。':err.message||'连接暂时中断,请重试。');} finally{clearTimeout(timeout);if(seq===sequence.current){setBusy(false);controller.current=null;}} }; const changeTab=(id)=>{setActive(id);}; const tabKeys=(event,index)=>{let next=index;if(event.key==='ArrowRight')next=(index+1)%PERIODS.length;else if(event.key==='ArrowLeft')next=(index+PERIODS.length-1)%PERIODS.length;else if(event.key==='Home')next=0;else if(event.key==='End')next=PERIODS.length-1;else return;event.preventDefault();setActive(PERIODS[next].id);tabRefs.current[next]?.focus();}; const current=report?.periods?.[active]; return <>
知了返回测算
{(editing||busy)&&
感情走势 / RELATIONSHIP

两人的下一章

以出生资料为起点,
看看两个人的时间节奏。

双方比较盘双方行运组合盘行运
} {error&&
{error}
} {busy?

正在展开两人的时间线

计算双方星盘、组合盘与全年相位,再整理五个时间范围的解读。

计算与解读中 · 已等待 {elapsed} 秒
: editing ?
不知道出生时间也可以继续。报告会降低精度,不使用缺失的月亮、上升等信息。出生资料用于生成本次关系解读。

近期侧重具体时间窗口,长期侧重阶段主题。
占星解读不能确认对方心意或保证现实结果。

{report&&}
: report&&<>
两人的关系时间线

{names.a} 与 {names.b}

{SIGN_NAMES[report.participants.a.sign]} × {SIGN_NAMES[report.participants.b.sign]} · {report.date}

{report.generation_source!=='model'&&
部分文字解读暂未完成,标有“计算摘要”的部分展示实际相位,可通过“修改资料”重新生成。
}
相处背景 · 双方比较盘

{report.baseline.headline}

{report.baseline.text}

{PERIODS.map((period,index)=>)}

{report.precision==='date_only'?'出生时间不完整,以下日期为近似参考。':'所有窗口按报告时区显示。'} 近期看细节,长期看阶段。

{current.range_label} · {report.timezone}

{current.reading.headline}

{current.reading.text}

这一阶段,可以留意

把时间线放远一些
一年末趋势 · {report.outlook.range_label}

{report.outlook.reading.headline}

{report.outlook.reading.text}

{report.limitations.map((line,index)=>

{line}

)}

AI 辅助整理 · 占星文化与自我探索

}
; } ReactDOM.createRoot(document.getElementById('root')).render(); })();