215 lines
7.7 KiB
TypeScript
215 lines
7.7 KiB
TypeScript
"use client";
|
||
|
||
import { FormEvent, useState } from "react";
|
||
|
||
type GenerateResponse = {
|
||
message: string;
|
||
unique_id: string;
|
||
url: string;
|
||
query_url: string;
|
||
title?: string | null;
|
||
source?: string | null;
|
||
request_id?: string | null;
|
||
size_bytes: number;
|
||
created_at: string;
|
||
expires_at: string;
|
||
};
|
||
|
||
const apiBaseUrl =
|
||
process.env.NEXT_PUBLIC_API_BASE_URL?.replace(/\/$/, "") ||
|
||
"http://localhost:8000";
|
||
|
||
const initialHtml = `<section>
|
||
<h1>勾股定理</h1>
|
||
<p>在直角三角形中,两条直角边长度分别为 a、b,斜边长度为 c,则 a² + b² = c²。</p>
|
||
<ul>
|
||
<li>适用对象:直角三角形</li>
|
||
<li>核心关系:两直角边平方和等于斜边平方</li>
|
||
<li>常见用途:求边长、验证三角形是否为直角三角形</li>
|
||
</ul>
|
||
</section>`;
|
||
|
||
export default function Home() {
|
||
const [title, setTitle] = useState("知识点讲解");
|
||
const [source, setSource] = useState("tencent-agent");
|
||
const [requestId, setRequestId] = useState("");
|
||
const [htmlContent, setHtmlContent] = useState(initialHtml);
|
||
const [loading, setLoading] = useState(false);
|
||
const [result, setResult] = useState<GenerateResponse | null>(null);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
||
event.preventDefault();
|
||
setLoading(true);
|
||
setError(null);
|
||
setResult(null);
|
||
|
||
try {
|
||
const response = await fetch(`${apiBaseUrl}/api/html/generate`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({
|
||
title,
|
||
source,
|
||
request_id: requestId || undefined,
|
||
html_content: htmlContent,
|
||
}),
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (!response.ok) {
|
||
throw new Error(data.detail || data.message || "生成 HTML 失败");
|
||
}
|
||
|
||
setResult(data);
|
||
} catch (submissionError) {
|
||
setError(
|
||
submissionError instanceof Error
|
||
? submissionError.message
|
||
: "生成 HTML 失败,请稍后重试。"
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="mx-auto max-w-5xl space-y-8">
|
||
<section className="rounded-3xl border border-border bg-card p-8 shadow-sm">
|
||
<div className="max-w-3xl space-y-3">
|
||
<p className="text-sm uppercase tracking-[0.3em] text-muted-foreground">
|
||
HTML Explanation API
|
||
</p>
|
||
<h2 className="text-3xl font-bold text-foreground">
|
||
教育智能体 HTML 发布接口演示
|
||
</h2>
|
||
<p className="text-muted-foreground">
|
||
这个页面用于手工联调。智能体调用后端接口提交 HTML 内容,后端会返回唯一链接和查询地址。
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
<form
|
||
onSubmit={handleSubmit}
|
||
className="space-y-6 rounded-3xl border border-border bg-card p-8 shadow-sm"
|
||
>
|
||
<div className="grid gap-4 md:grid-cols-2">
|
||
<label className="space-y-2">
|
||
<span className="text-sm font-medium">title</span>
|
||
<input
|
||
value={title}
|
||
onChange={(event) => setTitle(event.target.value)}
|
||
className="w-full rounded-2xl border border-border bg-background px-4 py-3 outline-none ring-0 transition focus:border-primary"
|
||
placeholder="页面标题"
|
||
/>
|
||
</label>
|
||
<label className="space-y-2">
|
||
<span className="text-sm font-medium">source</span>
|
||
<input
|
||
value={source}
|
||
onChange={(event) => setSource(event.target.value)}
|
||
className="w-full rounded-2xl border border-border bg-background px-4 py-3 outline-none ring-0 transition focus:border-primary"
|
||
placeholder="来源,例如 tencent-agent"
|
||
/>
|
||
</label>
|
||
</div>
|
||
|
||
<label className="block space-y-2">
|
||
<span className="text-sm font-medium">request_id</span>
|
||
<input
|
||
value={requestId}
|
||
onChange={(event) => setRequestId(event.target.value)}
|
||
className="w-full rounded-2xl border border-border bg-background px-4 py-3 outline-none ring-0 transition focus:border-primary"
|
||
placeholder="可选的请求追踪 ID"
|
||
/>
|
||
</label>
|
||
|
||
<label className="block space-y-2">
|
||
<span className="text-sm font-medium">html_content</span>
|
||
<textarea
|
||
value={htmlContent}
|
||
onChange={(event) => setHtmlContent(event.target.value)}
|
||
rows={18}
|
||
className="w-full rounded-3xl border border-border bg-background px-4 py-4 font-mono text-sm outline-none ring-0 transition focus:border-primary"
|
||
placeholder="请输入完整 HTML 或 HTML 片段"
|
||
/>
|
||
</label>
|
||
|
||
<div className="flex flex-wrap items-center gap-4">
|
||
<button
|
||
type="submit"
|
||
disabled={loading || !htmlContent.trim()}
|
||
className="rounded-full bg-primary px-6 py-3 text-sm font-semibold text-primary-foreground transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50"
|
||
>
|
||
{loading ? "生成中..." : "生成 HTML 链接"}
|
||
</button>
|
||
<p className="text-sm text-muted-foreground">
|
||
默认请求地址:{apiBaseUrl}/api/html/generate
|
||
</p>
|
||
</div>
|
||
</form>
|
||
|
||
{error ? (
|
||
<section className="rounded-3xl border border-destructive/20 bg-destructive/10 p-6 text-destructive">
|
||
{error}
|
||
</section>
|
||
) : null}
|
||
|
||
{result ? (
|
||
<section className="space-y-4 rounded-3xl border border-border bg-card p-8 shadow-sm">
|
||
<div>
|
||
<h3 className="text-xl font-semibold">返回结果</h3>
|
||
<p className="text-sm text-muted-foreground">{result.message}</p>
|
||
</div>
|
||
<div className="grid gap-4 md:grid-cols-2">
|
||
<div className="rounded-2xl bg-background p-4">
|
||
<p className="text-xs uppercase tracking-[0.2em] text-muted-foreground">
|
||
unique_id
|
||
</p>
|
||
<p className="mt-2 break-all font-mono text-sm">{result.unique_id}</p>
|
||
</div>
|
||
<div className="rounded-2xl bg-background p-4">
|
||
<p className="text-xs uppercase tracking-[0.2em] text-muted-foreground">
|
||
size_bytes
|
||
</p>
|
||
<p className="mt-2 font-mono text-sm">{result.size_bytes}</p>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-3 rounded-2xl bg-background p-4">
|
||
<p className="text-xs uppercase tracking-[0.2em] text-muted-foreground">
|
||
url
|
||
</p>
|
||
<a
|
||
href={result.url}
|
||
target="_blank"
|
||
rel="noreferrer"
|
||
className="break-all text-sm text-primary underline-offset-4 hover:underline"
|
||
>
|
||
{result.url}
|
||
</a>
|
||
</div>
|
||
<div className="space-y-3 rounded-2xl bg-background p-4">
|
||
<p className="text-xs uppercase tracking-[0.2em] text-muted-foreground">
|
||
query_url
|
||
</p>
|
||
<a
|
||
href={result.query_url}
|
||
target="_blank"
|
||
rel="noreferrer"
|
||
className="break-all text-sm text-primary underline-offset-4 hover:underline"
|
||
>
|
||
{result.query_url}
|
||
</a>
|
||
</div>
|
||
<p className="text-sm text-muted-foreground">
|
||
创建时间:{new Date(result.created_at).toLocaleString("zh-CN")},
|
||
过期时间:{new Date(result.expires_at).toLocaleString("zh-CN")}
|
||
</p>
|
||
</section>
|
||
) : null}
|
||
</div>
|
||
);
|
||
}
|