lingjing/app/waiting/page.jsx

138 lines
4.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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>
);
}