// 模板库 — 拆解 / 复刻模板的 CRUD(原 AIDS 拆解库已拆分:规则库 → rule-pack.jsx) const _CATEGORY_TABS = [ { id: "teardown", label: "拆解模板", desc: "用于「2·拆解爆款」analyze stage 的系统提示" }, { id: "replicate", label: "复刻模板", desc: "用于「3·复刻爆款」chunk_regroup 复刻草稿生成" }, ]; const TemplatesPage = () => { const [tab, setTab] = React.useState("teardown"); const [tpls, setTpls] = React.useState([]); const [selTplId, setSelTplId] = React.useState(null); const [loading, setLoading] = React.useState(false); const [err, setErr] = React.useState(""); const [saveBusy, setSaveBusy] = React.useState(false); const [editName, setEditName] = React.useState(""); const [editDesc, setEditDesc] = React.useState(""); const [editBody, setEditBody] = React.useState(""); React.useEffect(() => { loadTemplates(); }, [tab]); const loadTemplates = async () => { setLoading(true); setErr(""); try { const d = await API.templates.list({ category: tab }); const items = d.items || []; setTpls(items); // 切 tab 时如果当前选中模板不在新分类里,重选 const stillThere = items.some(t => t.id === selTplId); if (!stillThere) { const firstNonDefault = items.find(t => !t.is_default) || items[0]; setSelTplId(firstNonDefault ? firstNonDefault.id : null); } } catch (e) { setErr(String(e.message || e)); } finally { setLoading(false); } }; const selTpl = tpls.find(t => t.id === selTplId); React.useEffect(() => { if (selTpl) { setEditName(selTpl.name || ""); setEditDesc(selTpl.description || ""); setEditBody(selTpl.prompt_body || ""); } }, [selTplId, selTpl?.updated_at]); const onSaveTpl = async () => { if (!selTpl || selTpl.is_default) return; setSaveBusy(true); try { await API.templates.update(selTpl.id, { name: editName.trim(), description: editDesc, prompt_body: editBody, }); await loadTemplates(); } catch (e) { setErr(String(e.message || e)); } finally { setSaveBusy(false); } }; const onDuplicateTpl = async () => { if (!selTpl) return; setSaveBusy(true); try { const dup = await API.templates.duplicate(selTpl.id); await loadTemplates(); setSelTplId(dup.id); } catch (e) { setErr(String(e.message || e)); } finally { setSaveBusy(false); } }; const onDeleteTpl = async () => { if (!selTpl || selTpl.is_default) return; if (!confirm(`删除模板「${selTpl.name}」?`)) return; setSaveBusy(true); try { await API.templates.delete(selTpl.id); setSelTplId(null); await loadTemplates(); } catch (e) { setErr(String(e.message || e)); } finally { setSaveBusy(false); } }; const onNewTpl = async () => { const src = tpls.find(t => t.is_default) || tpls[0]; const now = new Date(); const ts = `${String(now.getMonth()+1).padStart(2,"0")}-${String(now.getDate()).padStart(2,"0")} ${String(now.getHours()).padStart(2,"0")}:${String(now.getMinutes()).padStart(2,"0")}`; setSaveBusy(true); try { const t = await API.templates.create({ name: `新模板 ${ts}`, description: "", category: tab, prompt_body: (src && src.prompt_body) || `# 在这里写你的${tab === "replicate" ? "复刻" : "拆解"}模板正文\n`, }); await loadTemplates(); setSelTplId(t.id); } catch (e) { setErr(String(e.message || e)); } finally { setSaveBusy(false); } }; return ( <> 新建模板 } /> {err && (
{err}
)} {/* 分类切换 Tab:拆解 / 复刻 */}
{_CATEGORY_TABS.map(t => ( ))}
{(_CATEGORY_TABS.find(t => t.id === tab) || {}).desc || ""}
{/* 左:模板列表 */}
{tab === "replicate" ? "复刻模板" : "拆解模板"} {tpls.length} 条
{loading && !tpls.length &&
加载中…
} {tpls.map(t => (
setSelTplId(t.id)} className="row gap-8" style={{ padding:8, borderRadius:5, cursor:"pointer", alignItems:"flex-start", background: t.id===selTplId ? "color-mix(in oklch, var(--cyan) 10%, var(--bg-1))" : "var(--bg-1)", border: `1px solid ${t.id===selTplId ? "var(--cyan)" : "var(--line-1)"}`, }}>
{t.name} {t.is_default && 默认}
v{t.version} · {t.updated_at ? t.updated_at.slice(0,16).replace("T"," ") : ""}
))} {!loading && !tpls.length &&
暂无模板
}
{/* 右:模板编辑器 */}
{selTpl && (
{selTpl.is_default ? "默认模板(只读)" : "编辑模板"} v{selTpl.version}
{!selTpl.is_default && ( <> )}
名称 setEditName(e.target.value)} disabled={selTpl.is_default} style={{fontSize:12.5}} />
描述 setEditDesc(e.target.value)} disabled={selTpl.is_default} style={{fontSize:12.5}} />
Prompt 正文