From 4e43df00c07cbcd43fcb8ebf928e3a584170dc89 Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Wed, 9 Apr 2025 21:48:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=8F=90=E7=A4=BA=E8=AF=8D?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=92=8C=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=9A=84=E5=A2=9E=E5=88=A0=E6=94=B9=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/prompts/prompts.ts | 493 ++++++++++++++++++++++ app/api/system_setting/config-lists.ts | 5 +- app/components/layout/Sidebar.tsx | 12 +- app/routes/config-lists.new.tsx | 166 ++++---- app/routes/document-types._index.tsx | 0 app/routes/document-types.tsx | 22 + app/routes/prompts._index.tsx | 261 ++++++------ app/routes/prompts.new.tsx | 428 +++++++++---------- app/styles/pages/config-lists_new.css | 53 ++- app/styles/pages/document-types_index.css | 0 app/styles/pages/prompts_new.css | 3 +- html/文档类型-列表.html | 8 +- html/文档类型-新增.html | 8 +- json.txt | 125 +++++- 14 files changed, 1100 insertions(+), 484 deletions(-) create mode 100644 app/api/prompts/prompts.ts create mode 100644 app/routes/document-types._index.tsx create mode 100644 app/routes/document-types.tsx create mode 100644 app/styles/pages/document-types_index.css diff --git a/app/api/prompts/prompts.ts b/app/api/prompts/prompts.ts new file mode 100644 index 0000000..18b4b2d --- /dev/null +++ b/app/api/prompts/prompts.ts @@ -0,0 +1,493 @@ +import { postgrestGet, postgrestPut, postgrestPost, postgrestDelete, type PostgrestParams } from '../postgrest-client'; +import dayjs from 'dayjs'; + +// 提示词模板接口 +export interface PromptTemplate { + id: number; + template_name: string; + template_type: string; + description: string | null; + template_content: string; + variables: Record; // 变量定义 + status: number; + version: string; + created_by: number; + created_at: string; + updated_at: string; +} + +// 提示词模板前端接口 +export interface PromptTemplateUI { + id: string; + template_name: string; + template_type: 'Extraction' | 'Evaluation' | 'Summary' | 'Common'; + description: string; + template_content: string; + variables: Record; // 变量定义 + status: 'active' | 'inactive' | 'system'; + version: string; + created_by: number; + created_at: string; + updated_at: string; +} + +// 搜索参数接口 +export interface PromptSearchParams { + name?: string; + type?: string; + status?: string; + page?: number; + pageSize?: number; +} + +/** + * 格式化日期 + * @param dateString 日期字符串 + * @returns 格式化后的日期字符串 + */ +function formatDate(dateString: string): string { + if (!dateString) return ''; + try { + return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss'); + } catch (error) { + console.error('日期格式化失败:', error); + return dateString; + } +} + +/** + * 从不同格式的 API 响应中提取数据 + * @param responseData API 响应数据 + * @returns 提取后的数据或 null + */ +function extractApiData(responseData: unknown): T | null { + if (!responseData) return null; + + // 格式1: { code: number, msg: string, data: T } + if (typeof responseData === 'object' && responseData !== null && + 'code' in responseData && + 'data' in responseData && + (responseData as { data: unknown }).data) { + return (responseData as { data: T }).data; + } + + // 格式2: 直接是数据对象 + return responseData as T; +} + +/** + * 将API状态值转换为UI状态值 + * @param status API状态值(数字) + * @returns UI状态值(字符串) + */ +function mapStatusToUI(status: number): 'active' | 'inactive' | 'system' { + switch(status) { + case 0: return 'inactive'; + case 1: return 'active'; + case 2: return 'system'; + default: return 'inactive'; + } +} + +/** + * 将UI状态值转换为API状态值 + * @param status UI状态值(字符串) + * @returns API状态值(数字) + */ +function mapStatusToAPI(status: string): number { + switch(status) { + case 'active': return 1; + case 'inactive': return 0; + case 'system': return 2; + default: return 0; + } +} + +/** + * 将数据库模板转换为UI模板 + * @param template 数据库模板 + * @returns UI模板 + */ +export function convertToUITemplate(template: PromptTemplate): PromptTemplateUI { + return { + id: template.id ? template.id.toString() : '', + template_name: template.template_name, + template_type: template.template_type as "Extraction" | "Evaluation" | "Summary" | "Common", + description: template.description || '', + template_content: template.template_content, + variables: template.variables, + status: mapStatusToUI(template.status), + version: template.version, + created_by: template.created_by, + created_at: formatDate(template.created_at), + updated_at: formatDate(template.updated_at) + }; +} + +/** + * 获取提示词模板列表 + * @param searchParams 搜索参数 + * @returns 提示词模板列表和总数 + */ +export async function getPromptTemplates(searchParams: PromptSearchParams = {}): Promise<{ + data?: { templates: PromptTemplateUI[], total: number }; + error?: string; + status?: number; +}> { + try { + console.log('获取提示词模板列表,参数:', searchParams); + + const page = searchParams.page || 1; + const pageSize = searchParams.pageSize || 10; + + // 构建查询参数 + const params: PostgrestParams = { + select: ` + id, + template_name, + template_type, + description, + template_content, + variables, + status, + version, + created_by, + created_at, + updated_at + `, + order: 'updated_at.desc', + headers: { + 'Prefer': 'count=exact' + }, + limit: pageSize, + offset: (page - 1) * pageSize, + filter: {} as Record + }; + + // 添加筛选条件 + const filter: Record = {}; + filter['status'] = `gte.0`; + if (searchParams.name) { + filter['template_name'] = `ilike.%${searchParams.name}%`; + } + + if (searchParams.type) { + filter['template_type'] = `eq.${searchParams.type}`; + } + + if (searchParams.status) { + let statusValue: number; + switch (searchParams.status) { + case 'active': statusValue = 1; break; + case 'inactive': statusValue = 0; break; + case 'system': statusValue = 2; break; + default: statusValue = -1; + } + + if (statusValue >= 0) { + filter['status'] = `eq.${statusValue}`; + } + } + params.filter = filter; + + // 发送API请求 + console.log('API请求参数:', params); + const response = await postgrestGet('prompt_templates', params); + + if (response.error) { + console.error('API返回错误:', response.error); + return { error: response.error, status: response.status }; + } + + // 提取API返回的数据 + const extractedData = extractApiData(response.data); + + if (!extractedData) { + console.error('提取数据失败,原始响应:', response.data); + return { error: '获取提示词模板数据失败', status: 500 }; + } + + console.log(`成功获取${extractedData.length}条提示词模板数据`); + + // 从响应头中获取总数 + let totalCount = 0; + const responseWithHeaders = response as { data: PromptTemplate[]; headers: Record }; + if(responseWithHeaders.headers){ + const rangeHeader = responseWithHeaders.headers['content-range']; + if(rangeHeader){ + const total = rangeHeader.split('/')[1]; + if(total !== '*'){ + totalCount = parseInt(total, 10); + } + } + } + // 返回转换后的数据 + return { + data: { + templates: extractedData.map(convertToUITemplate), + total: totalCount + } + }; + } catch (error) { + console.error('获取提示词模板出错:', error); + return { + error: error instanceof Error ? error.message : '获取提示词模板失败', + status: 500 + }; + } +} + +/** + * 获取提示词模板详情 + * @param id 模板ID + * @returns 提示词模板详情 + */ +export async function getPromptTemplate(id: string): Promise<{ + data?: PromptTemplateUI; + error?: string; + status?: number; +}> { + try { + if (!id) { + return { error: '模板ID不能为空', status: 400 }; + } + + const params: PostgrestParams = { + select: ` + id, + template_name, + template_type, + description, + template_content, + variables, + status, + version, + created_by, + created_at, + updated_at + `, + filter: { + 'id': `eq.${id}` + } + }; + + const response = await postgrestGet('prompt_templates', params); + + if (response.error) { + return { error: response.error, status: response.status }; + } + + const extractedData = extractApiData(response.data); + + if (!extractedData || extractedData.length === 0) { + return { error: '未找到指定模板', status: 404 }; + } + + return { data: convertToUITemplate(extractedData[0]) }; + } catch (error) { + console.error('获取提示词模板详情失败:', error); + return { + error: error instanceof Error ? error.message : '获取提示词模板详情失败', + status: 500 + }; + } +} + +/** + * 创建提示词模板 + * @param template 提示词模板数据 + * @returns 创建的提示词模板 + */ +export async function createPromptTemplate(template: Partial): Promise<{ + data?: PromptTemplateUI; + error?: string; + status?: number; +}> { + try { + // 验证必填字段 + if (!template.template_name || !template.template_type || !template.template_content) { + return { error: '模板名称、类型和内容不能为空', status: 400 }; + } + + // 准备变量数据 + let variablesData: Record = {}; + if (typeof template.variables === 'string') { + try { + variablesData = JSON.parse(template.variables); + } catch (e) { + console.error('解析变量JSON失败:', e); + } + } else if (template.variables) { + variablesData = template.variables; + } + + // 准备API数据 + const apiTemplate: Partial = { + template_name: template.template_name, + template_type: template.template_type, + description: template.description || null, + template_content: template.template_content, + variables: variablesData, + status: mapStatusToAPI(template.status || 'active'), + version: template.version || 'v1.0', + created_by: 1 // 固定创建者为1 + }; + + if(apiTemplate){ + console.log('apiTemplate', apiTemplate); + // throw new Error('测试错误'); + } + + const response = await postgrestPost>( + 'prompt_templates', + apiTemplate + ); + + if (response.error) { + return { error: response.error, status: response.status }; + } + + const extractedData = extractApiData(response.data); + + if (!extractedData) { + return { error: '创建提示词模板失败', status: 500 }; + } + + return { data: convertToUITemplate(extractedData) }; + } catch (error) { + console.error('创建提示词模板失败:', error); + return { + error: error instanceof Error ? error.message : '创建提示词模板失败', + status: 500 + }; + } +} + +/** + * 更新提示词模板 + * @param id 模板ID + * @param template 提示词模板数据 + * @returns 更新后的提示词模板 + */ +export async function updatePromptTemplate(id: string, template: Partial): Promise<{ + data?: PromptTemplateUI; + error?: string; + status?: number; +}> { + try { + if (!id) { + return { error: '模板ID不能为空', status: 400 }; + } + + // 准备变量数据 + let variablesData: Record = {}; + if (typeof template.variables === 'string') { + try { + variablesData = JSON.parse(template.variables); + } catch (e) { + console.error('解析变量JSON失败:', e); + } + } else if (template.variables) { + variablesData = template.variables; + } + + // 准备API数据 + const apiTemplate: Partial = {}; + + if (template.template_name !== undefined) { + apiTemplate.template_name = template.template_name; + } + + if (template.template_type !== undefined) { + apiTemplate.template_type = template.template_type; + } + + if (template.description !== undefined) { + apiTemplate.description = template.description; + } + + if (template.template_content !== undefined) { + apiTemplate.template_content = template.template_content; + } + + if (template.variables !== undefined) { + apiTemplate.variables = variablesData; + } + + if (template.status !== undefined) { + apiTemplate.status = mapStatusToAPI(template.status); + } + + if (template.version !== undefined) { + apiTemplate.version = template.version; + } + + // if(apiTemplate){ + // console.log('apiTemplate', apiTemplate); + // throw new Error('测试错误'); + // } + + const response = await postgrestPut>( + 'prompt_templates', + apiTemplate, + { id } + ); + + if (response.error) { + return { error: response.error, status: response.status }; + } + + const extractedData = extractApiData(response.data); + + if (!extractedData) { + return { error: '更新提示词模板失败', status: 500 }; + } + + return { data: convertToUITemplate(extractedData) }; + } catch (error) { + console.error('更新提示词模板失败:', error); + return { + error: error instanceof Error ? error.message : '更新提示词模板失败', + status: 500 + }; + } +} + +/** + * 删除提示词模板 + * @param id 模板ID + * @returns 成功或失败信息 + */ +export async function deletePromptTemplate(id: string): Promise<{ + success?: boolean; + error?: string; + status?: number; +}> { + try { + if (!id) { + return { error: '模板ID不能为空', status: 400 }; + } + + // 使用真实删除替代状态更新 + const response = await postgrestDelete( + 'prompt_templates', + { + filter: { + 'id': `eq.${id}` + } + } + ); + + if (response.error) { + return { error: response.error, status: response.status }; + } + + return { success: true }; + } catch (error) { + console.error('删除提示词模板失败:', error); + return { + error: error instanceof Error ? error.message : '删除提示词模板失败', + status: 500 + }; + } +} \ No newline at end of file diff --git a/app/api/system_setting/config-lists.ts b/app/api/system_setting/config-lists.ts index 743f13c..cf90cb2 100644 --- a/app/api/system_setting/config-lists.ts +++ b/app/api/system_setting/config-lists.ts @@ -8,6 +8,7 @@ export interface ConfigItem { description: string; environment: string; config: Record; + remark: string; is_active: boolean; version: string; created_by: number; @@ -229,7 +230,7 @@ export async function createConfig(data: { environment: string; config: Record; is_active: boolean; - remarks?: string; + remark?: string; }): Promise<{data: ConfigItem; error?: never} | {data?: never; error: string}> { try { const response = await postgrestPost('configurations', data); @@ -263,7 +264,7 @@ export async function updateConfig(id: string, data: { environment: string; config: Record; is_active: boolean; - remarks?: string; + remark?: string; }): Promise<{data: ConfigItem; error?: never} | {data?: never; error: string}> { try { const response = await postgrestPut('configurations', data, { diff --git a/app/components/layout/Sidebar.tsx b/app/components/layout/Sidebar.tsx index 0ea5319..663fb02 100644 --- a/app/components/layout/Sidebar.tsx +++ b/app/components/layout/Sidebar.tsx @@ -101,12 +101,12 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) { path: '/config-lists', icon: 'ri-list-check-3' }, - { - id: 'basic-settings', - title: '基础设置', - path: '/settings', - icon: 'ri-equalizer-line' - }, + // { + // id: 'basic-settings', + // title: '基础设置', + // path: '/settings', + // icon: 'ri-equalizer-line' + // }, { id: 'document-types', title: '文档类型', diff --git a/app/routes/config-lists.new.tsx b/app/routes/config-lists.new.tsx index e5cb2bf..7689981 100644 --- a/app/routes/config-lists.new.tsx +++ b/app/routes/config-lists.new.tsx @@ -40,13 +40,13 @@ export const EXTENDED_ENVIRONMENT_LABELS: Record = { }; interface ConfigData { - id: string; + id: number; name: string; type: string; environment: string; is_active: boolean; config: Record; - remarks?: string; + remark?: string; } interface LoaderData { @@ -76,11 +76,11 @@ export async function loader({ request }: LoaderFunctionArgs) { config = detailResponse.data; } - return json({ + return Response.json({ config, isEdit: !!config, - types: optionsResponse.data.types, - environments: optionsResponse.data.environments + types: optionsResponse.data?.types || [], + environments: optionsResponse.data?.environments || [] }); } @@ -103,7 +103,7 @@ export async function action({ request }: ActionFunctionArgs) { const environment = formData.get("environment") as string; const config = formData.get("config") as string; const is_active = formData.get("is_active") === "true"; - const remarks = formData.get("remarks") as string; + const remark = formData.get("remark") as string; const errors: ActionData["errors"] = {}; @@ -131,7 +131,7 @@ export async function action({ request }: ActionFunctionArgs) { } if (Object.keys(errors).length > 0) { - return json({ errors }); + return Response.json({ errors }); } try { @@ -141,7 +141,7 @@ export async function action({ request }: ActionFunctionArgs) { environment, config: JSON.parse(config), is_active, - remarks + remark }; if (id) { @@ -161,7 +161,7 @@ export async function action({ request }: ActionFunctionArgs) { return redirect("/config-lists"); } catch (error) { console.error("保存配置失败:", error); - return json({ + return Response.json({ success: false, errors: { general: "保存配置失败,请稍后重试" @@ -170,18 +170,17 @@ export async function action({ request }: ActionFunctionArgs) { } } -// JSON模板数据 -const JSON_TEMPLATES = { +// 配置模板常量 +const CONFIG_TEMPLATES = { database: { - database: { - host: "localhost", - port: 5432, - username: "db_user", - password: "******", - name: "app_database", - pool_size: 10, - timeout: 5000, - ssl: false + host: "localhost", + port: 5432, + database: "mydb", + username: "admin", + password: "******", + pool: { + min: 2, + max: 10 } }, file: { @@ -220,6 +219,9 @@ export default function ConfigNew() { const [selectedModule, setSelectedModule] = useState(""); const [selectedEnvironment, setSelectedEnvironment] = useState(""); + // 在 ConfigNew 组件中添加状态来跟踪当前选中的模板 + const [selectedTemplate, setSelectedTemplate] = useState(null); + useEffect(() => { // 初始化配置数据 if (config) { @@ -268,6 +270,7 @@ export default function ConfigNew() { // 格式化JSON const handleFormatJson = () => { + if (configDataValue.trim() === "") return; try { @@ -283,50 +286,6 @@ export default function ConfigNew() { } }; - // 加载JSON模板 - const handleLoadTemplate = (type: keyof typeof JSON_TEMPLATES) => { - const template = JSON_TEMPLATES[type]; - setConfigDataValue(JSON.stringify(template, null, 2)); - setJsonError(null); - }; - - // 显示JSON语法高亮 - const renderJsonWithSyntaxHighlight = (json: string) => { - try { - // 如果是空字符串,直接返回 - if (!json.trim()) return ""; - - // 解析并格式化JSON - const parsed = JSON.parse(json); - const formatted = JSON.stringify(parsed, null, 2); - - // 添加语法高亮 - return formatted - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g, (match) => { - let cls = 'number'; - if (/^"/.test(match)) { - if (/:$/.test(match)) { - cls = 'key'; - match = match.replace(':', ''); - } else { - cls = 'string'; - } - } else if (/true|false/.test(match)) { - cls = 'boolean'; - } else if (/null/.test(match)) { - cls = 'null'; - } - return `${match}`; - }); - } catch (e) { - // 如果解析失败,返回原始JSON - return json; - } - }; - return (
@@ -342,6 +301,12 @@ export default function ConfigNew() {
+ + {actionData?.errors?.general && ( +
+
{actionData.errors.general}
+
+ )}
@@ -412,7 +377,7 @@ export default function ConfigNew() {
{actionData.errors.type}
)}
- {types.map(type => ( + {types.map((type: string) => (
)}
- {environments.map(env => ( + {environments.map((env: string) => ( @@ -501,32 +469,62 @@ export default function ConfigNew() {
配置示例
-
+                    
+ {exampleJsonValue.split('\n').map((line, index) => ( +
+ {line.split('').map((char, charIndex) => { + let className = 'json-char'; + if (char === '{' || char === '}') className += ' json-brace'; + if (char === '[' || char === ']') className += ' json-bracket'; + if (char === '"') className += ' json-quote'; + if (char === ':') className += ' json-colon'; + if (char === ',') className += ' json-comma'; + return ( + + {char} + + ); + })} +
+ ))} +
常用配置模板:
@@ -542,12 +540,12 @@ export default function ConfigNew() { {/* 备注 */}
- + + {actionData?.errors?.template_content && ( +
{actionData.errors.template_content}
+ )}
提示词模板是AI完成特定任务的指令,请清晰描述任务需求和输出格式
@@ -642,6 +641,7 @@ export default function PromptsNew() { className="form-input" value={varName} readOnly + /> {!isViewMode && (
- +
); } \ No newline at end of file diff --git a/app/styles/pages/config-lists_new.css b/app/styles/pages/config-lists_new.css index db8c050..b6171d6 100644 --- a/app/styles/pages/config-lists_new.css +++ b/app/styles/pages/config-lists_new.css @@ -41,7 +41,7 @@ .config-new-page .form-input, .config-new-page .form-select, .config-new-page .form-textarea { - @apply block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm; + @apply block w-full border rounded-md shadow-sm py-2 px-3 focus:outline-none sm:text-sm; } .config-new-page .form-textarea { @@ -97,9 +97,9 @@ /* JSON编辑器 */ .config-new-page .json-editor { - @apply w-full min-h-[320px] font-mono text-sm leading-relaxed - border border-gray-300 rounded-md p-3 bg-gray-50 text-gray-800 - focus:outline-none focus:ring-primary-500 focus:border-primary-500; + @apply w-full min-h-[400px] font-mono text-sm leading-relaxed + border rounded-md p-3 bg-gray-50 text-gray-800 + focus:outline-none focus:border-[#00684a] focus:ring focus:ring-[#00684a] focus:ring-opacity-20; } .config-new-page .editor-actions { @@ -108,7 +108,7 @@ /* 示例卡片 */ .config-new-page .example-card { - @apply h-full flex flex-col bg-gray-50 rounded-md border border-gray-200 overflow-hidden; + @apply h-[500px] flex flex-col bg-gray-50 rounded-md border border-gray-200 overflow-hidden; } .config-new-page .example-header { @@ -120,7 +120,7 @@ } .config-new-page .example-content { - @apply p-3 flex-grow overflow-auto; + @apply p-3 flex-grow overflow-auto text-sm ; } .config-new-page .example-pre { @@ -136,4 +136,43 @@ .config-new-page .code-json .string { @apply text-green-600; } .config-new-page .code-json .number { @apply text-purple-600; } .config-new-page .code-json .boolean { @apply text-blue-600; } -.config-new-page .code-json .null { @apply text-gray-500; } \ No newline at end of file +.config-new-page .code-json .null { @apply text-gray-500; } + +.config-new-page .json-display { + font-family: monospace; + white-space: pre; + background-color: #f8fafc; + padding: 1rem; + border-radius: 0.375rem; + overflow-x: auto; +} + +.config-new-page .json-line { + line-height: 1.5; +} + +.config-new-page .json-char { + color: #334155; +} + +.config-new-page .json-brace { + color: #64748b; + font-weight: bold; +} + +.config-new-page .json-bracket { + color: #64748b; + font-weight: bold; +} + +.config-new-page .json-quote { + color: #0ea5e9; +} + +.config-new-page .json-colon { + color: #64748b; +} + +.config-new-page .json-comma { + color: #64748b; +} \ No newline at end of file diff --git a/app/styles/pages/document-types_index.css b/app/styles/pages/document-types_index.css new file mode 100644 index 0000000..e69de29 diff --git a/app/styles/pages/prompts_new.css b/app/styles/pages/prompts_new.css index 2e3586d..5dac2aa 100644 --- a/app/styles/pages/prompts_new.css +++ b/app/styles/pages/prompts_new.css @@ -41,7 +41,8 @@ .prompt-new-page .form-input:focus, .prompt-new-page .form-select:focus, -.prompt-new-page .form-textarea:focus { +.prompt-new-page .form-textarea:focus, +.prompt-new-page .form-code-editor:focus { @apply outline-none border-[var(--primary-color)] shadow-[0_0_0_2px_rgba(0,104,74,0.2)]; } diff --git a/html/文档类型-列表.html b/html/文档类型-列表.html index 99f4cb1..300010c 100644 --- a/html/文档类型-列表.html +++ b/html/文档类型-列表.html @@ -4,10 +4,12 @@ 中国烟草AI合同及卷宗审核系统 - 文档类型列表 - - + + + - +