(() => {
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
;
}
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 {items.map((item,index)=>- {item}
)}
;}
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&&<>
两人的关系时间线{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();
})();