import { useEffect, useState } from "react"; import { MetaFunction, LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node"; import { Link, useLoaderData, useSubmit, useNavigation } from "@remix-run/react"; import { Button } from "~/components/ui/Button"; import { PromptTemplate } from "./prompts._index"; import newStyles from "~/styles/pages/prompts_new.css?url"; // 样式链接 export function links() { return [{ rel: "stylesheet", href: newStyles }]; } // 页面元数据 export const meta: MetaFunction = () => { return [ { title: "提示词模板编辑 - 中国烟草AI合同及卷宗审核系统" }, { name: "description", content: "创建或编辑提示词模板" }, ]; }; // 面包屑导航 export const handle = { breadcrumb: (data:LoaderData) => { if (data.mode === "edit") { return "编辑提示词模板"; } else if (data.mode === "view") { return "查看提示词模板"; } else { return "新增提示词模板"; } } }; interface LoaderData { template: PromptTemplate; mode: string; } // 从模拟数据中获取模板 const getTemplateById = (id: string): PromptTemplate | undefined => { // 与prompts._index.tsx中的模拟数据保持一致 const MOCK_TEMPLATES: PromptTemplate[] = [ { id: "1", template_name: "行政处罚-抽取通用模板", template_type: "Extraction", description: "本模板用于抽取行政处罚决定书编号等信息", version: "v1.0", status: "system", created_by: "system", template_content: `你是一个专业的文档信息抽取助手。请从以下{docType}文档中抽取关键信息: 1. 处罚决定书编号 2. 处罚对象名称 3. 处罚事由 4. 处罚依据 5. 处罚内容 6. 处罚金额 7. 发文日期 请将结果以JSON格式输出,包含以上字段。如果某个字段在文档中未找到,则该字段的值设为null。`, variables: JSON.stringify({ "docType": "文档类型" }) }, { id: "2", template_name: "销售合同-甲方信息评估", template_type: "Evaluation", description: "评估销售合同中甲方信息是否完整", version: "v1.2", status: "active", created_by: "admin", template_content: `你是一个专业的合同审核助手。请评估以下{docType}中甲方信息的完整性: 请检查以下要素是否存在且完整: 1. 甲方全称 2. 注册地址 3. 统一社会信用代码 4. 法定代表人 5. 联系方式 请给出评估结果,并标明缺失或不完整的信息。`, variables: JSON.stringify({ "docType": "文档类型" }) }, { id: "3", template_name: "专卖许可证-摘要模板", template_type: "Summary", description: "生成专卖许可证申请文件的内容摘要", version: "v1.0", status: "active", created_by: "admin", template_content: `你是一个专业的文档摘要助手。请为以下{docType}生成一份简洁的摘要: 摘要应包含以下要点: 1. 申请人基本信息 2. 许可证类型 3. 申请事项 4. 经营范围 5. 申请日期 请控制摘要在200字以内,保留关键信息。`, variables: JSON.stringify({ "docType": "文档类型" }) }, { id: "4", template_name: "采购合同-乙方资质抽取", template_type: "Extraction", description: "抽取采购合同中乙方的资质信息", version: "v1.1", status: "inactive", created_by: "zhangsan", template_content: `你是一个专业的合同信息抽取助手。请从以下{docType}中抽取乙方的资质信息: 需要抽取的信息包括: 1. 乙方全称 2. 资质证书类型 3. 资质证书编号 4. 资质等级 5. 证书有效期 请将结果以JSON格式输出,包含以上字段。`, variables: JSON.stringify({ "docType": "文档类型" }) }, { id: "5", template_name: "合同通用-关键条款评估", template_type: "Evaluation", description: "评估合同中关键条款是否明确、合规", version: "v2.0", status: "active", created_by: "lisi", template_content: `你是一个专业的{industry}行业合同审核助手。请评估以下合同中的关键条款是否明确、合规: 请重点关注以下条款: 1. 合同标的 2. 价格条款 3. 付款条件 4. 交付方式 5. 违约责任 6. 争议解决 请对每一项给出评估结果,并指出不明确或存在风险的条款。`, variables: JSON.stringify({ "industry": "行业类型", "docType": "文档类型" }) } ]; return MOCK_TEMPLATES.find(t => t.id === id); }; // 加载函数 export async function loader({ request }: LoaderFunctionArgs) { try { const url = new URL(request.url); const id = url.searchParams.get("id"); const mode = url.searchParams.get("mode") || "create"; // 模板数据,如果是新建则为空 let template = null; if (id) { // 实际应用中,这里应该调用API获取数据 // const response = await fetch(`${process.env.API_BASE_URL}/api/prompt-templates/${id}`); // if (!response.ok) throw new Error(`获取提示词模板失败: ${response.status}`); // template = await response.json(); // 使用模拟数据 template = getTemplateById(id); if (!template) { throw new Error(`未找到ID为${id}的模板`); } } return Response.json({ template, mode }); } catch (error) { console.error("加载提示词模板失败:", error); return Response.json( { template: null, mode: "create", error: error instanceof Error ? error.message : "加载提示词模板失败" }, { status: 500 } ); } } // Action函数 - 处理表单提交 export async function action({ request }: ActionFunctionArgs) { try { const formData = await request.formData(); const templateData = Object.fromEntries(formData); // 表单验证 const errors: Record = {}; if (!templateData.template_name) { errors.template_name = "模板名称不能为空"; } if (!templateData.template_type) { errors.template_type = "请选择模板类型"; } if (!templateData.template_content) { errors.template_content = "模板内容不能为空"; } if (Object.keys(errors).length > 0) { return Response.json({ errors, success: false }, { status: 400 }); } // 实际应用中,这里应该调用API保存数据 // const apiUrl = templateData.id // ? `${process.env.API_BASE_URL}/api/prompt-templates/${templateData.id}` // : `${process.env.API_BASE_URL}/api/prompt-templates`; // // const response = await fetch(apiUrl, { // method: templateData.id ? "PUT" : "POST", // headers: { "Content-Type": "application/json" }, // body: JSON.stringify(templateData) // }); // // if (!response.ok) throw new Error(`保存提示词模板失败: ${response.status}`); // const result = await response.json(); // 模拟API响应 console.log("提交的模板数据:", templateData); return Response.json({ success: true, message: "提示词模板保存成功", template: { ...templateData, id: templateData.id || Math.random().toString(36).substring(2, 10), created_by: "当前用户", created_at: new Date().toISOString() } }); } catch (error) { console.error("保存提示词模板失败:", error); return Response.json( { success: false, message: error instanceof Error ? error.message : "保存提示词模板失败" }, { status: 500 } ); } } // 提取变量函数 const extractVariables = (content: string) => { const regex = /{([^{}]+)}/g; const variables: Record = {}; let match; while ((match = regex.exec(content)) !== null) { const varName = match[1].trim(); if (varName && !variables[varName]) { variables[varName] = varName; } } return variables; }; // 页面组件 export default function PromptsNew() { const { template, mode } = useLoaderData(); const submit = useSubmit(); const navigation = useNavigation(); const isSubmitting = navigation.state === "submitting"; // 表单状态 const [formData, setFormData] = useState>({ id: "", template_name: "", template_type: "Common", description: "", version: "v1.0", status: "active", template_content: "", variables: "{}" }); // 模式状态 const [isViewMode, setIsViewMode] = useState(false); const [pageTitle, setPageTitle] = useState("新增提示词模板"); // 变量相关状态 const [detectedVariables, setDetectedVariables] = useState>({}); const [exampleValues, setExampleValues] = useState>({}); const [previewContent, setPreviewContent] = useState(""); // 初始化表单数据 useEffect(() => { if (template) { const newFormData = { ...template, // 如果是克隆模式,则清除ID并修改名称 id: mode === "clone" ? "" : template.id, template_name: mode === "clone" ? `${template.template_name} (副本)` : template.template_name, // 如果是克隆模式,重置版本 version: mode === "clone" ? "v1.0" : template.version }; setFormData(newFormData); try { // 解析模板变量 const vars = JSON.parse(template.variables); setExampleValues(vars); } catch (e) { console.error("解析变量失败:", e); } // 检测模板内容中的变量 if (template.template_content) { const vars = extractVariables(template.template_content); setDetectedVariables(vars); } } // 设置页面模式 setIsViewMode(mode === "view"); // 设置页面标题 if (mode === "view") { setPageTitle("查看提示词模板"); } else if (mode === "edit") { setPageTitle("编辑提示词模板"); } else if (mode === "clone") { setPageTitle("复制创建提示词模板"); } else { setPageTitle("新增提示词模板"); } }, [template, mode]); // 处理输入变化 const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; // 如果是模板内容,检测变量 if (name === "template_content") { const vars = extractVariables(value); setDetectedVariables(vars); // 更新变量JSON const varsJson = JSON.stringify( Object.keys(vars).reduce((acc, key) => { acc[key] = exampleValues[key] || key; return acc; }, {} as Record) ); setFormData(prev => ({ ...prev, [name]: value, variables: varsJson })); } else { setFormData(prev => ({ ...prev, [name]: value })); } }; // 处理状态切换 const handleStatusToggle = (e: React.ChangeEvent) => { const status = e.target.checked ? "active" : "inactive"; setFormData(prev => ({ ...prev, status })); }; // 处理示例值变更 const handleExampleValueChange = (varName: string, value: string) => { setExampleValues(prev => ({ ...prev, [varName]: value })); }; // 更新预览 const updatePreview = () => { let content = formData.template_content || ""; // 替换变量 Object.entries(detectedVariables).forEach(([key]) => { const exampleValue = exampleValues[key] || `[${key}]`; const regex = new RegExp(`{${key}}`, 'g'); content = content.replace(regex, exampleValue); }); setPreviewContent(content); }; // 当检测到的变量变化时,更新变量JSON useEffect(() => { const varsJson = JSON.stringify( Object.keys(detectedVariables).reduce((acc, key) => { acc[key] = exampleValues[key] || key; return acc; }, {} as Record) ); setFormData(prev => ({ ...prev, variables: varsJson })); }, [detectedVariables, exampleValues]); // 处理表单提交 const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (isViewMode) { return; } const formElement = e.target as HTMLFormElement; const submittingFormData = new FormData(formElement); // 确保变量JSON被包含在提交中 submittingFormData.set("variables", formData.variables || "{}"); submit(submittingFormData, { method: "post" }); }; return (
{/* 页面头部 */}

{pageTitle}

{!isViewMode && ( )}
{/* 查看模式提示 */} {isViewMode && (
您正在查看系统预设模板,此模板不可修改。如需基于此模板创建新模板,请点击"复制创建"按钮。
)} {/* 模板表单 */}
{/* 模板ID - 隐藏字段 */} {/* 模板信息卡片 */}
提示词模板信息
{/* 模板名称 */}
建议使用"文档类型-功能"的命名方式
{/* 模板类型 */}
{/* 模板描述 */}
{/* 模板状态 */}
{formData.status === 'active' ? '启用' : '停用'}
{/* 模板版本 */}
{/* 模板内容卡片 */}
提示词模板内容
模板内容支持使用变量,变量格式为 {varName},在使用时会自动替换。系统将自动识别模板中的变量。
例如:请从以下{docType}文档中抽取关键信息...
{/* 模板内容 */}
提示词模板是AI完成特定任务的指令,请清晰描述任务需求和输出格式
{/* 变量识别区域 */}
系统已自动识别出模板中的变量。变量以 {varName} 形式在模板中使用,无需手动定义。
{Object.keys(detectedVariables).length > 0 ? ( Object.keys(detectedVariables).map(varName => (
{varName}
)) ) : (
暂未识别到任何变量,请在模板内容中使用 {变量名} 格式添加变量
)}
{/* 模板效果预览 */}
变量赋值示例:
{Object.keys(detectedVariables).length > 0 ? ( Object.keys(detectedVariables).map(varName => (
handleExampleValueChange(varName, e.target.value)} readOnly={isViewMode} />
)) ) : (
暂无变量,无需赋值
)}
预览效果:
{previewContent ? (
{previewContent}
) : (
根据变量值生成的模板预览将显示在这里...
)}
{/* 底部按钮区域 */}
{isViewMode && ( )}
{!isViewMode && ( )}
); }