72 lines
3.5 KiB
TypeScript
72 lines
3.5 KiB
TypeScript
import Link from 'next/link';
|
||
|
||
const ANALYTICS_BACKEND = process.env.ANALYTICS_BACKEND_URL || 'http://127.0.0.1:8000';
|
||
|
||
type AgentVerificationCheck = {
|
||
agent: string;
|
||
role: string;
|
||
status_label: string;
|
||
evidence?: string[];
|
||
};
|
||
|
||
type AgentVerificationResponse = {
|
||
overall_label?: string;
|
||
production_ready?: boolean;
|
||
checks?: AgentVerificationCheck[];
|
||
};
|
||
|
||
type GeminiUsageResponse = {
|
||
estimated_cost_usd?: number;
|
||
cap_usd?: number;
|
||
};
|
||
|
||
async function fetchJson<T>(path: string): Promise<T> {
|
||
const response = await fetch(`${ANALYTICS_BACKEND}/analytics/${path}`, { cache: 'no-store' });
|
||
if (!response.ok) throw new Error(`${path} 暫時無法回應`);
|
||
return response.json() as Promise<T>;
|
||
}
|
||
|
||
export default async function AgentVerificationPage() {
|
||
let verification: AgentVerificationResponse | null = null;
|
||
let usage: GeminiUsageResponse | null = null;
|
||
let error = '';
|
||
try {
|
||
[verification, usage] = await Promise.all([
|
||
fetchJson<AgentVerificationResponse>('agent-verification'),
|
||
fetchJson<GeminiUsageResponse>('gemini-usage'),
|
||
]);
|
||
} catch (err) {
|
||
error = err instanceof Error ? err.message : 'AI 驗證資料暫時無法讀取';
|
||
}
|
||
const checks = verification?.checks ?? [];
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<section className="rounded-[2rem] border border-[#e7c89b] bg-[#fff8e6]/95 p-6 md:p-8">
|
||
<p className="dot-matrix text-sm font-bold text-[#b83822]">AI 驗證室</p>
|
||
<h1 className="mt-3 text-4xl font-black text-[#3f2f25]">Codex + Gemini + NemoTron + 量化閘門</h1>
|
||
<p className="mt-3 max-w-3xl text-sm leading-7 text-[#6f4f3c]">AI 只能協助復核資料、新聞、盤口與模型理由;是否能成為正式推薦,仍必須通過賠率、勝率、風險上限與賽後校準閘門。</p>
|
||
</section>
|
||
{error ? <p className="rounded-2xl border border-[#e7a49a] bg-[#fff0e8] p-4 text-sm font-bold text-[#b83822]">{error}</p> : null}
|
||
<section className="grid gap-4 md:grid-cols-3">
|
||
{[
|
||
['整體狀態', verification?.overall_label ?? '-', 'AI 復核結論'],
|
||
['可否正式上線', verification?.production_ready ? '可' : '不可', '仍需量化閘門'],
|
||
['Gemini 費用', usage ? `$${Number(usage.estimated_cost_usd ?? 0).toFixed(4)} / $${Number(usage.cap_usd ?? 5).toFixed(2)}` : '-', '超過上限會暫停'],
|
||
].map(([label, value, helper]) => <article key={label} className="panel-glow rounded-2xl p-5"><p className="text-xs font-semibold tracking-[0.2em] text-[#8a6b58]">{helper}</p><p className="mt-3 text-2xl font-black text-[#7d2a15]">{value}</p><p className="mt-1 text-sm text-[#6f4f3c]">{label}</p></article>)}
|
||
</section>
|
||
<section className="grid gap-4 lg:grid-cols-2">
|
||
{checks.map((item) => (
|
||
<article key={item.agent} className="rounded-3xl border border-[#eadcb9] bg-white/75 p-5">
|
||
<p className="dot-matrix text-sm font-bold text-[#7d2a15]">{item.agent}</p>
|
||
<h2 className="mt-2 text-xl font-black text-[#3f2f25]">{item.role}</h2>
|
||
<p className="mt-2 rounded-full bg-[#fff8e6] px-3 py-1 text-xs font-black text-[#7d2a15]">{item.status_label}</p>
|
||
<ul className="mt-3 space-y-2 text-sm leading-6 text-[#7a5b46]">{(item.evidence ?? []).map((line) => <li key={line}>・{line}</li>)}</ul>
|
||
</article>
|
||
))}
|
||
</section>
|
||
<Link href="/recommendation-readiness" className="inline-flex rounded-full bg-[#d1432d] px-5 py-3 text-sm font-black text-white">看推薦閘門</Link>
|
||
</div>
|
||
);
|
||
}
|