138 lines
4.8 KiB
JavaScript
138 lines
4.8 KiB
JavaScript
'use client';
|
||
|
||
import { useEffect, useMemo, useState } from 'react';
|
||
import { useRouter } from 'next/navigation';
|
||
import Starfield from '../../components/Starfield';
|
||
|
||
const MESSAGES = [
|
||
'正在整理你的回答线索……',
|
||
'正在校准你的五维镜像……',
|
||
'正在提炼你的当下信号……',
|
||
'正在生成你的支点行动……',
|
||
];
|
||
|
||
export default function WaitingPage() {
|
||
const router = useRouter();
|
||
const [querySid, setQuerySid] = useState('');
|
||
const [idx, setIdx] = useState(0);
|
||
const [timedOut, setTimedOut] = useState(false);
|
||
const [error, setError] = useState('');
|
||
useEffect(() => {
|
||
if (typeof window === 'undefined') return;
|
||
const sidFromQuery = new URLSearchParams(window.location.search).get('sessionId') || '';
|
||
setQuerySid(sidFromQuery);
|
||
}, []);
|
||
|
||
const sid = useMemo(() => {
|
||
return querySid || (typeof window !== 'undefined' ? localStorage.getItem('lingjing_sid') || '' : '');
|
||
}, [querySid]);
|
||
|
||
useEffect(() => {
|
||
const t = setInterval(() => setIdx((n) => (n + 1) % MESSAGES.length), 3000);
|
||
return () => clearInterval(t);
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (!sid) return;
|
||
localStorage.setItem('lingjing_sid', sid);
|
||
|
||
let alive = true;
|
||
const timeout = setTimeout(() => setTimedOut(true), 90000); // 90秒超时
|
||
|
||
const run = async () => {
|
||
try {
|
||
const r = await fetch('/api/report', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ sessionId: sid }),
|
||
});
|
||
const d = await r.json();
|
||
if (!alive) return;
|
||
if (d?.ok || d?.status === 'done') {
|
||
router.replace(`/report-preview?sessionId=${encodeURIComponent(sid)}`);
|
||
return;
|
||
}
|
||
if (d?.status === 'generating') {
|
||
poll();
|
||
return;
|
||
}
|
||
setError(d?.error || '生成失败,请重试');
|
||
} catch {
|
||
if (alive) setError('生成失败,请重试');
|
||
}
|
||
};
|
||
|
||
const poll = async () => {
|
||
let notStartedCount = 0;
|
||
for (let i = 0; i < 45; i += 1) { // 45次x2秒=90秒窗口
|
||
await new Promise((r) => setTimeout(r, 2000));
|
||
if (!alive) return;
|
||
try {
|
||
const g = await fetch(`/api/report?sessionId=${encodeURIComponent(sid)}`);
|
||
const d = await g.json();
|
||
if (d?.status === 'done') {
|
||
router.replace(`/report-preview?sessionId=${encodeURIComponent(sid)}`);
|
||
return;
|
||
}
|
||
if (d?.status === 'error') {
|
||
setError(d?.error || '生成失败,请重试');
|
||
return;
|
||
}
|
||
if (d?.status === 'not_started') {
|
||
notStartedCount += 1;
|
||
// 超过5次还没开始,主动触发一次生成
|
||
if (notStartedCount >= 5) {
|
||
fetch('/api/report', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: sid }) }).catch(() => {});
|
||
notStartedCount = 0;
|
||
}
|
||
}
|
||
} catch {
|
||
// ignore single poll failure
|
||
}
|
||
}
|
||
setError('报告生成超时,请重试');
|
||
};
|
||
|
||
run();
|
||
|
||
return () => {
|
||
alive = false;
|
||
clearTimeout(timeout);
|
||
};
|
||
}, [router, sid]);
|
||
|
||
const retry = () => {
|
||
setTimedOut(false);
|
||
setError('');
|
||
window.location.reload();
|
||
};
|
||
|
||
return (
|
||
<main className="relative min-h-screen overflow-hidden bg-[#05030f] text-white">
|
||
<Starfield className="absolute inset-0 h-full w-full" animated />
|
||
<div className="relative z-10 mx-auto flex min-h-screen w-full max-w-2xl flex-col items-center justify-center px-6 text-center">
|
||
<h1 className="text-3xl font-semibold">灵镜正在为你生成报告</h1>
|
||
<p className="mt-6 text-lg text-white/80">{MESSAGES[idx]}</p>
|
||
<p className="mt-12 text-sm text-white/60">已保存你的对话,可放心离开。</p>
|
||
|
||
{timedOut && !error ? (
|
||
<div className="mt-8 flex gap-3">
|
||
<button onClick={retry} className="rounded-xl border border-white/20 bg-white/10 px-4 py-2 text-sm">继续等待</button>
|
||
<button onClick={() => router.push('/')} className="rounded-xl border border-white/20 bg-transparent px-4 py-2 text-sm">稍后查看</button>
|
||
</div>
|
||
) : null}
|
||
|
||
{error ? (
|
||
<div className="mt-8 space-y-3">
|
||
<p className="text-sm text-red-300">{error}</p>
|
||
<div className="flex gap-3">
|
||
<button onClick={retry} className="rounded-xl border border-white/20 bg-white/10 px-4 py-2 text-sm">重新生成</button>
|
||
<button onClick={() => router.push('/')} className="rounded-xl border border-white/20 bg-transparent px-4 py-2 text-sm">稍后查看</button>
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
</main>
|
||
);
|
||
}
|