Files
writer-work-flow/apps/web/components/toolbox/TemplateFiller.tsx

116 lines
3.7 KiB
TypeScript
Raw 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 { useCallback, useEffect, useState } from "react";
import { useToast } from "@/components/Toast";
import { Button } from "@/components/ui/Button";
import { api } from "@/lib/api/client";
import type { TemplateResponse } from "@/lib/api/types";
import { buttonClass } from "@/lib/ui/variants";
interface TemplateFillerProps {
// 可填入的目标输入字段名(如 brief / text空 → 不渲染(无可填字段)。匹配/回写按此 name。
targetField: string | null;
// 目标字段的中文展示名(如「需求」「原文」);缺省回退到 name。仅用于展示不暴露英文 key。
targetLabel?: string | null;
// 选定模板 → 把其 body 填进目标字段(纯前端,不改生成器后端)。
onFill: (field: string, body: string) => void;
}
// 「从模板填入」F3纯前端进场懒加载模板库选一个把 body 填进生成器的 brief/原文输入。
// 与生成器后端解耦——只读 /templates 并回写本地表单值。无模板/无目标字段则不渲染。
export function TemplateFiller({
targetField,
targetLabel,
onFill,
}: TemplateFillerProps) {
const toast = useToast();
const [templates, setTemplates] = useState<TemplateResponse[] | null>(null);
const [open, setOpen] = useState(false);
const load = useCallback(async (): Promise<void> => {
try {
const { data, error } = await api.GET("/templates", {});
if (error || !data) {
toast("加载模板失败。", "error");
setTemplates([]);
return;
}
setTemplates(data);
} catch {
toast("加载模板请求异常,请检查网络。", "error");
setTemplates([]);
}
}, [toast]);
useEffect(() => {
if (open && templates === null) void load();
}, [open, templates, load]);
if (!targetField) return null;
const field = targetField;
const fieldLabel = targetLabel ?? targetField;
if (!open) {
return (
<Button
variant="secondary"
size="sm"
onClick={() => setOpen(true)}
className="self-start"
>
</Button>
);
}
return (
<div
className="flex flex-col gap-2 rounded border border-line bg-bg p-3"
aria-label="从模板填入"
>
<div className="flex items-center justify-between">
<span className="text-sm text-ink-soft">{fieldLabel}</span>
<Button
variant="ghost"
size="sm"
onClick={() => setOpen(false)}
>
</Button>
</div>
{templates === null ? (
<p className="text-sm text-ink-soft"></p>
) : templates.length === 0 ? (
<p className="text-sm text-ink-soft"></p>
) : (
<ul className="flex flex-col gap-1">
{templates.map((t) => (
<li key={t.id}>
<button
type="button"
onClick={() => {
onFill(field, t.body);
setOpen(false);
toast(`已填入模板「${t.title}」。`, "success");
}}
className={buttonClass({
variant: "secondary",
className: "w-full justify-start text-left text-ink",
})}
>
<span className="font-serif">{t.title}</span>
{t.tool_key ? (
<span className="ml-2 text-xs text-ink-soft">
{t.tool_key}
</span>
) : null}
</button>
</li>
))}
</ul>
)}
</div>
);
}