import { useEffect, useState } from "react"; import { MetaFunction, LoaderFunctionArgs, ActionFunctionArgs, redirect } from "@remix-run/node"; import { Link, useLoaderData, useNavigation, useActionData, Form } from "@remix-run/react"; import { Button } from "~/components/ui/Button"; import newStyles from "~/styles/pages/prompts_new.css?url"; import { getPromptTemplate, createPromptTemplate, updatePromptTemplate, type PromptTemplateUI } from "~/api/prompts/prompts"; // 样式链接 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: PromptTemplateUI | null; mode: string; error?: string; } // 定义本地表单数据接口 interface FormDataState extends Omit { variables: string; // 在表单状态中我们保存变量为 JSON 字符串 } interface ActionData { success?: boolean; errors?: { template_name?: string; template_type?: string; template_content?: string; general?: string; }; formData?: { template_name: string; template_type: 'LLM_Extraction' | 'VLM_Extraction' | 'Evaluation' | 'Summary' | 'Common'; description: string; template_content: string; variables: string; status: "active" | "inactive" | "system"; version: string; }; } // 加载函数 export async function loader({ request }: LoaderFunctionArgs) { try { // 获取用户会话信息 const { getUserSession } = await import("~/api/login/auth.server"); const { frontendJWT } = await getUserSession(request); 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 result = await getPromptTemplate(id, frontendJWT); if (result.error) { console.error('获取提示词模板失败:', result.error); throw new Error(result.error); } template = result.data || null; 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) { const { getUserSession } = await import("~/api/login/auth.server"); const { userInfo, frontendJWT } = await getUserSession(request); const userId = userInfo.get('user_id') const formData = await request.formData(); const id = formData.get("id") as string; const template_name = formData.get("template_name") as string; const template_type = formData.get("template_type") as 'LLM_Extraction' | 'VLM_Extraction' | 'Evaluation' | 'Summary' | 'Common'; const description = formData.get("description") as string; const template_content = formData.get("template_content") as string; const variables = formData.get("variables") as string; const status = formData.get("status") as string; const version = formData.get("version") as string; const errors: ActionData["errors"] = {}; // 表单验证 if (!template_name || template_name.trim() === "") { errors.template_name = "模板名称不能为空"; } if (!template_type) { errors.template_type = "请选择模板类型"; } if (!template_content || template_content.trim() === "") { errors.template_content = "模板内容不能为空"; } if (Object.keys(errors).length > 0) { return Response.json({ errors }); } try { // 准备变量数据 let variablesData: Record = {}; try { if (variables) { variablesData = JSON.parse(variables); } } catch (e) { console.error('解析变量JSON失败:', e); } // 准备API数据 const apiTemplate: Partial = { template_name, template_type, description, template_content, variables: variablesData, status: status === "active" ? "active" : "inactive", version: version || "v1.0" }; let result; if (id) { // 更新模板 result = await updatePromptTemplate(id, apiTemplate, frontendJWT); } else { // 创建模板 result = await createPromptTemplate(apiTemplate, userId, frontendJWT); } if (result.error) { return Response.json({ errors: { general: result.error }, formData: { template_name, template_type, description, template_content, variables, status, version } }); } return redirect("/prompts"); } catch (error) { console.error("保存提示词模板失败:", error); return Response.json({ errors: { general: error instanceof Error ? error.message : "保存提示词模板失败" }, formData: { template_name, template_type, description, template_content, variables, status, version } }); } } // 提取变量函数 例如:{var1} {var2} {var3} const extractVariables = (content: string) => { const regex = /{([^{}]+)}/g; const variables: Record = {}; let match; while ((match = regex.exec(content)) !== null) { const varName = match[1].trim(); // 过滤掉无效的变量名: // 1. 不能为空 // 2. 不能包含冒号(避免匹配 JSON 对象) // 3. 不能包含引号(避免匹配 JSON 字符串) // 4. 只允许字母、数字、下划线、中文 if (varName && !variables[varName] && !varName.includes(':') && !varName.includes('"') && !varName.includes("'") && /^[\w\u4e00-\u9fa5]+$/.test(varName)) { variables[varName] = varName; } } return variables; }; // 页面组件 export default function PromptsNew() { const { template, mode, error } = useLoaderData(); const actionData = useActionData(); 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: "", created_by: 1, variables: "{}", created_at: "", updated_at: "" }); // 模式状态 const [isViewMode, setIsViewMode] = useState(false); const [pageTitle, setPageTitle] = useState("新增提示词模板"); // 变量相关状态 // 检测到的变量 const [detectedVariables, setDetectedVariables] = useState>({}); // 示例值 const [exampleValues, setExampleValues] = useState>({}); // 预览内容 const [previewContent, setPreviewContent] = useState(""); // 初始化表单数据 useEffect(() => { if (actionData?.formData) { // 如果有保存失败的表单数据,使用它 setFormData(prev => ({ ...prev, ...actionData.formData, variables: actionData.formData?.variables || "{}", status: actionData.formData?.status || "active" })); } else if (template) { // 否则使用模板数据 // 处理变量数据,确保null和undefined转换为空对象 const variablesObj = template.variables || {}; const variablesJson = typeof variablesObj === 'string' ? variablesObj : JSON.stringify(variablesObj); const newFormData = { ...template, id: mode === "clone" ? "" : template.id, template_name: mode === "clone" ? `${template.template_name} (副本)` : template.template_name, version: mode === "clone" ? "v1.0" : template.version, variables: variablesJson }; setFormData(newFormData); try { const vars = typeof variablesObj === 'string' ? JSON.parse(variablesObj) : variablesObj; setExampleValues(vars || {}); } catch (e) { console.error("解析变量失败:", e); setExampleValues({}); } 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, actionData?.formData]); // 处理输入变化 const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; // 如果是模板内容,检测变量 if (name === "template_content") { const vars = extractVariables(value); // console.log("检测到的变量:",vars); setDetectedVariables(vars); // 更新变量JSON const varsJson = JSON.stringify( Object.keys(vars).reduce((acc, key) => { acc[key] = exampleValues[key] || ""; return acc; }, {} as Record) ); // console.log("更新变量JSON:",varsJson); setFormData(prev => ({ ...prev, [name]: value, variables: varsJson })); } else { setFormData(prev => ({ ...prev, [name]: value })); } }; // 处理状态切换 const handleStatusToggle = (e: React.ChangeEvent) => { // console.log("状态切换前:", formData.status); const status = e.target.checked ? "active" : "inactive"; // console.log("新状态值:", status); // 直接更新formData状态 setFormData(prev => { const newState = { ...prev, status: status as "active" | "inactive" | "system" }; // console.log("状态更新后:", newState.status); return newState; }); }; // 处理示例值变更 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] || ``; 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] || ""; return acc; }, {} as Record) ); setFormData(prev => ({ ...prev, variables: varsJson })); }, [detectedVariables, exampleValues]); return (
{/* 页面头部 */}

{pageTitle}

{!isViewMode && ( )}
{/* 错误信息 */} {error && (
{error}
)} {actionData?.errors?.general && (
{actionData.errors.general}
)} {/* 查看模式提示 */} {isViewMode && (
您正在查看系统预设模板,此模板不可修改。如需基于此模板创建新模板,请点击"复制创建"按钮。
)} {/* 模板表单 */}
{/* 模板ID - 隐藏字段 */} {/* 模板信息卡片 */}
提示词模板信息
{/* 模板名称 */}
{actionData?.errors?.template_name && (
{actionData.errors.template_name}
)}
建议使用"文档类型-功能"的命名方式
{/* 模板类型 */}
{actionData?.errors?.template_type && (
{actionData.errors.template_type}
)}
{/* 模板描述 */}
{/* 模板状态 */}
{formData.status === 'active' ? '启用' : '停用'}
{/* 使用隐藏字段传递状态值 */}
{/* 模板版本 */}
{/* 模板内容卡片 */}
提示词模板内容
模板内容支持使用变量,变量格式为 {varName},在使用时会自动替换。系统将自动识别模板中的变量。
例如:请从以下{docType}文档中抽取关键信息...
{/* 模板内容 */}
{actionData?.errors?.template_content && (
{actionData.errors.template_content}
)}
提示词模板是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 && ( )}
); }