From ddad57529d4f0dd2017b7e1d3fe6873f3c8215cf Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Mon, 10 Nov 2025 20:40:08 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=AF=84=E6=9F=A5?= =?UTF-8?q?=E7=82=B9=E8=AE=BE=E7=BD=AE=E4=B8=AD=E7=9A=84=E5=A4=9A=E6=A8=A1?= =?UTF-8?q?=E6=80=81=E6=8A=BD=E5=8F=96=E8=AE=BE=E7=BD=AE=E7=9A=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rules/new/ExtractionSettings.tsx | 329 ++++++++++-------- app/models/evaluation_points.ts | 66 ++-- app/routes/rules-new.tsx | 182 +++++++++- 3 files changed, 395 insertions(+), 182 deletions(-) diff --git a/app/components/rules/new/ExtractionSettings.tsx b/app/components/rules/new/ExtractionSettings.tsx index ef5fb89..abb3331 100644 --- a/app/components/rules/new/ExtractionSettings.tsx +++ b/app/components/rules/new/ExtractionSettings.tsx @@ -54,14 +54,14 @@ export function ExtractionSettings({ llm: initialData?.extraction_config?.llm ?? { fields: [], prompt_setting: { - type: "system", + type: "llm_default_prompt", template: "", }, }, vlm: initialData?.extraction_config?.vlm ?? { fields: [], prompt_setting: { - type: "system", + type: "vlm_default_prompt", template: "", }, }, @@ -84,11 +84,13 @@ export function ExtractionSettings({ vlm: initialData?.extraction_config?.vlm?.fields || [] }); // VLM字段类型 - const [selectedVlmFieldType, setSelectedVlmFieldType] = useState('default'); + const [selectedVlmFieldType, setSelectedVlmFieldType] = useState('vlm_default_prompt'); + // 自定义字段的提示词模板 + const [customVlmPrompt, setCustomVlmPrompt] = useState('请识别文档中的印章信息,提取以下字段'); // 提示词类型 const [promptType, setPromptType] = useState({ - llm: initialData?.extraction_config?.llm?.prompt_setting?.type || 'system', - vlm: initialData?.extraction_config?.vlm?.prompt_setting?.type || 'system' + llm: initialData?.extraction_config?.llm?.prompt_setting?.type || 'llm_default_prompt', + vlm: initialData?.extraction_config?.vlm?.prompt_setting?.type || 'vlm_default_prompt' }); // 提示词模板 const [selectedTemplate, setSelectedTemplate] = useState({ @@ -115,6 +117,20 @@ export function ExtractionSettings({ setCurrentTab(tab); }; + // 初始化自定义字段的提示词 + useEffect(() => { + // 在编辑模式下,如果有自定义类型的字段,加载其 template + const vlmFields = initialData?.extraction_config?.vlm?.fields || []; + const customField = vlmFields.find( + (f: string | { name: string; type: string; template?: string }) => + typeof f === 'object' && f.type === 'custom' && f.template + ); + + if (customField && typeof customField === 'object' && customField.template) { + setCustomVlmPrompt(customField.template); + } + }, [initialData]); + // 自动保存字段变更状态 // 这个效果确保添加字段后自动保存到组件状态,但不自动提交更新 useEffect(() => { @@ -125,15 +141,15 @@ export function ExtractionSettings({ const initialRegexFields = initialData?.extraction_config?.regex?.fields || []; const initialLlmPrompt = initialData?.extraction_config?.llm?.prompt_setting?.template || ''; const initialVlmPrompt = initialData?.extraction_config?.vlm?.prompt_setting?.template || ''; - + // 检查是否有实际变化 const hasLlmFieldsChanged = JSON.stringify(fields.llm) !== JSON.stringify(initialLlmFields); const hasVlmFieldsChanged = JSON.stringify(fields.vlm) !== JSON.stringify(initialVlmFields); const hasRegexFieldsChanged = JSON.stringify(regexFields) !== JSON.stringify(initialRegexFields); - const hasPromptContentChanged = - promptContent.llm !== initialLlmPrompt || + const hasPromptContentChanged = + promptContent.llm !== initialLlmPrompt || promptContent.vlm !== initialVlmPrompt; - + // 只有实际发生变化时才设置为true if (hasLlmFieldsChanged || hasVlmFieldsChanged || hasRegexFieldsChanged || hasPromptContentChanged) { setHasPendingChanges(true); @@ -169,17 +185,26 @@ export function ExtractionSettings({ } else { const newFields = [...fields.vlm]; inputs.forEach(input => { - const exists = newFields.some(field => - typeof field === 'string' - ? field === input + const exists = newFields.some(field => + typeof field === 'string' + ? field === input : field.name === input ); - + if (!exists) { - newFields.push({ - name: input, - type: selectedVlmFieldType as VLMFieldType - }); + // 如果是自定义类型,添加 template 字段 + if (selectedVlmFieldType === 'custom') { + newFields.push({ + name: input, + type: selectedVlmFieldType as VLMFieldType, + template: customVlmPrompt + }); + } else { + newFields.push({ + name: input, + type: selectedVlmFieldType as VLMFieldType + }); + } } }); setFields({ ...fields, vlm: newFields }); @@ -190,7 +215,7 @@ export function ExtractionSettings({ ...inputValue, [type]: '' }); - + setHasPendingChanges(true); }; @@ -218,46 +243,60 @@ export function ExtractionSettings({ }; // 获取VLM字段信息 - const getFieldInfo = (fieldString: string) => { - const parts = fieldString.split('_'); - const fieldName = parts[0]; - const fieldType = parts.length > 1 ? parts[1] : 'default'; - - let typeName, badgeClass; + const getFieldInfo = (field: string | { name: string, type: string, template?: string }) => { + let fieldName, fieldType, typeName, badgeClass; + + if (typeof field === 'string') { + const parts = field.split('_'); + fieldName = parts[0]; + fieldType = parts.length > 1 ? parts[1] : 'default'; + } else { + fieldName = field.name; + fieldType = field.type; + } + switch (fieldType) { - case 'currency': + case 'vlm_default_prompt': + typeName = '默认'; + badgeClass = 'bg-gray-100 text-gray-800'; + break; + case 'vlm_currency_prompt': typeName = '货币'; badgeClass = 'bg-green-100 text-green-800'; break; - case 'print': + case 'vlm_print_prompt': typeName = '打印'; badgeClass = 'bg-blue-100 text-blue-800'; break; - case 'seal': + case 'vlm_seal_prompt': typeName = '印章'; badgeClass = 'bg-red-100 text-red-800'; break; - case 'cross-seal': + case 'vlm_acrossPageSeal_prompt': typeName = '骑缝章'; badgeClass = 'bg-orange-100 text-orange-800'; break; - case 'english': + case 'vlm_english_prompt': typeName = '英文'; badgeClass = 'bg-purple-100 text-purple-800'; break; - case 'number': + case 'vlm_number_prompt': typeName = '数字'; badgeClass = 'bg-yellow-100 text-yellow-800'; break; - case 'handwriting': + case 'vlm_handwriting_prompt': typeName = '手写'; badgeClass = 'bg-pink-100 text-pink-800'; break; + case 'custom': + typeName = '自定义'; + badgeClass = 'bg-indigo-100 text-indigo-800'; + break; default: typeName = '默认'; badgeClass = 'bg-gray-100 text-gray-800'; } - + return { fieldName, fieldType, typeName, badgeClass }; }; @@ -270,7 +309,12 @@ export function ExtractionSettings({ value={value} onChange={(e) => handlePromptTypeChange(e, type)} > - {EVALUATION_OPTIONS.promptTypeOptions.map((option) => ( + {type === 'llm' && EVALUATION_OPTIONS.llmPromptTypeOptions.map((option) => ( + + ))} + {type === 'vlm' && EVALUATION_OPTIONS.vlmPromptTypeOptions.map((option) => ( @@ -436,7 +480,18 @@ export function ExtractionSettings({ const handleUpdateFields = () => { // 过滤掉没有字段名的正则字段 const validRegexFields = regexFields.filter(field => field.field.trim() !== ''); - + + // 更新所有自定义类型字段的 template + const updatedVlmFields = fields.vlm.map(field => { + if (typeof field === 'object' && field.type === 'custom') { + return { + ...field, + template: customVlmPrompt + }; + } + return field; + }); + // 收集所有字段数据 const updatedFormData = { ...formData, @@ -444,14 +499,14 @@ export function ExtractionSettings({ llm: { fields: fields.llm, prompt_setting: { - type: promptType.llm, + type: promptType.llm || 'llm_default_prompt', template: promptType.llm === 'custom' ? promptContent.llm : '' } }, vlm: { - fields: fields.vlm, + fields: updatedVlmFields, prompt_setting: { - type: promptType.vlm, + type: promptType.vlm || 'vlm_default_prompt', template: promptType.vlm === 'custom' ? promptContent.vlm : '' } }, @@ -620,7 +675,7 @@ export function ExtractionSettings({ className="bg-gray-50 p-2 rounded text-xs text-gray-600 mb-2" id="llm-system-prompt-info" style={{ - display: promptType.llm === "system" ? "block" : "none", + display: promptType.llm === "llm_default_prompt" ? "block" : "none", }} > 系统将根据评查点类型和抽取目标自动生成适合的提示词,您无需额外配置。 @@ -745,12 +800,7 @@ export function ExtractionSettings({
{fields.vlm.map((field, index) => { - const { fieldName, fieldType, typeName, badgeClass } = - getFieldInfo( - typeof field === "string" - ? field - : `${field.name}_${field.type}` - ); + const { fieldName, fieldType, typeName, badgeClass } = getFieldInfo(field); return (
{fieldName} @@ -781,115 +831,98 @@ export function ExtractionSettings({
-
-
- -
- {renderPromptTypeSelect(promptType.vlm, "vlm")} -
-
- 系统将根据评查点类型和抽取目标自动生成适合的提示词,支持图表、印章等图像内容抽取。 -
- -
-
- - -
-
- - -
-

- 支持的变量 - (点击变量将其添加到提示词中): -

-
- {[ - "docType", - "fieldsList", - "companyName", - "documentId", - "date", - "industry", - "contentType", - "pageRange", - "colorMode", - "ocrText", - ].map((variable) => ( - - ))} + {/* 只有当选择了自定义类型时才显示提示词设置 */} + {selectedVlmFieldType === 'custom' && ( +
+
+ +
+
+ + +
+
+ + +
+

+ 支持的变量 + (点击变量将其添加到提示词中): +

+
+ {[ + "docType", + "fieldsList", + "companyName", + "documentId", + "date", + "industry", + "contentType", + "pageRange", + "colorMode", + "ocrText", + ].map((variable) => ( + + ))} +
-
+ )}
; - prompt_setting: PromptSetting; + prompt_setting: VLMPromptSetting; }; regex: { fields: Array<{ @@ -67,20 +68,33 @@ interface ExtactionConfigType { /** * VLM字段类型定义 */ -type VLMFieldType = 'default' | 'currency' | 'print' | 'seal' | 'cross-seal' | 'english' | 'number' | 'handwriting'; +type VLMFieldType = 'vlm_default_prompt' | 'vlm_currency_prompt' | 'vlm_print_prompt' | 'vlm_seal_prompt' | 'vlm_acrossPageSeal_prompt' | 'vlm_english_prompt' | 'vlm_number_prompt' | 'vlm_handwriting_prompt' | 'custom'; /** - * 提示配置类型定义 + * llm提示配置类型定义 */ -interface PromptSetting { - type: PromptType; // "system" or "custom" 如果为 "custom" 则为自定义提示 +interface LLMPromptSetting { + type: LLMPromptType; // "llm_default_prompt" or "custom" 如果为 "custom" 则为自定义提示 template: string; // 提示模板 } /** - * 提示类型定义 + * vlm提示配置类型定义 */ -type PromptType = 'system' | 'custom'; +interface VLMPromptSetting { + type: VLMPromptType; // "vlm_default_prompt" or "custom" 如果为 "custom" 则为自定义提示 + template: string; // 提示模板 +} + +/** + * llm提示类型定义 + */ +type LLMPromptType = 'llm_default_prompt' | 'custom'; + +/** + * llm提示类型定义 + */ +type VLMPromptType = 'vlm_default_prompt' | 'custom'; /** * 评查配置类型定义 @@ -200,19 +214,26 @@ export const EVALUATION_OPTIONS = { // VLM字段类型选项 vlmFieldTypeOptions: [ - { value: 'default', label: '默认' }, - { value: 'currency', label: '货币' }, - { value: 'print', label: '打印' }, - { value: 'seal', label: '印章' }, - { value: 'cross-seal', label: '骑缝章' }, - { value: 'english', label: '英文' }, - { value: 'number', label: '数字' }, - { value: 'handwriting', label: '手写' } + { value: 'vlm_default_prompt', label: '默认' }, + { value: 'vlm_currency_prompt', label: '货币' }, + { value: 'vlm_print_prompt', label: '打印' }, + { value: 'vlm_seal_prompt', label: '印章' }, + { value: 'vlm_acrossPageSeal_prompt', label: '骑缝章' }, + { value: 'vlm_english_prompt', label: '英文' }, + { value: 'vlm_number_prompt', label: '数字' }, + { value: 'vlm_handwriting_prompt', label: '手写' }, + { value: 'custom', label: '自定义' } ], - // 提示类型选项 - promptTypeOptions: [ - { value: 'system', label: '使用系统默认提示词' }, + // vlm提示词类型选项 + vlmPromptTypeOptions: [ + { value: 'vlm_default_prompt', label: '使用系统默认提示词' }, + { value: 'custom', label: '使用自定义提示词' } + ], + + // llm提示词类型选项 + llmPromptTypeOptions: [ + { value: 'llm_default_prompt', label: '使用系统默认提示词' }, { value: 'custom', label: '使用自定义提示词' } ], @@ -291,9 +312,10 @@ export type { SuggestionMessageType, PostActionType, ExtactionConfigType, + VLMPromptSetting, VLMFieldType, - PromptSetting, - PromptType, + LLMPromptSetting, + LLMPromptType, EvaluationConfigType, LogicType, Rule, diff --git a/app/routes/rules-new.tsx b/app/routes/rules-new.tsx index e7def2c..70ce34e 100644 --- a/app/routes/rules-new.tsx +++ b/app/routes/rules-new.tsx @@ -217,14 +217,14 @@ export default function RuleNew() { llm: { fields: [], prompt_setting: { - type: 'system', + type: 'llm_default_prompt', template: '' } }, vlm: { fields: [], prompt_setting: { - type: 'system', + type: 'vlm_default_prompt', template: '' } }, @@ -357,18 +357,173 @@ export default function RuleNew() { const handleSave = async () => { // console.log("保存评查点", formData); - - // 验证必填字段 + + // ========== 验证必填字段 ========== + + // 1. 验证评查点名称 if (!formData.name?.trim()) { toastService.warning("评查点名称不能为空"); return; } - + + if (formData.name.trim().length > 30) { + toastService.warning("评查点名称不能超过30个字符"); + return; + } + + // 2. 验证评查点编码 if (!formData.code?.trim()) { toastService.warning("评查点编码不能为空"); return; } - + + // 3. 验证风险等级 + if (!formData.risk) { + toastService.warning("请选择风险等级"); + return; + } + + // 4. 验证评查点类型(父级类型组) + if (!formData.evaluation_point_groups_pid) { + toastService.warning("请选择评查点类型"); + return; + } + + // 5. 验证所属规则组 + if (!formData.evaluation_point_groups_id) { + toastService.warning("请选择所属规则组"); + return; + } + + + // 6. 验证评查设置中的规则 + if (formData.evaluation_config?.rules && Array.isArray(formData.evaluation_config.rules)) { + const rules = formData.evaluation_config.rules; + + if (rules.length <=0){ + toastService.warning('评查设置中尚未添加或完善规则') + return; + } + + // 检查每个规则是否选择了评查类型 + for (let i = 0; i < rules.length; i++) { + const rule = rules[i]; + + console.log("log",rule) + + if (!rule.type) { + toastService.warning(`评查设置中的规则 ${i + 1} 未选择评查类型`); + return; + } + + // 根据不同规则类型验证配置 + if (!rule.config) { + toastService.warning(`评查设置中的规则 ${i + 1} 配置不完整`); + return; + } + + // 验证各类型规则的必填字段 + switch (rule.type) { + case 'exists': + if (!rule.config.fields || rule.config.fields.length === 0) { + toastService.warning(`评查设置中的规则 ${i + 1}(有无判断):请至少选择一个字段`); + return; + } + if (!rule.config.logic) { + toastService.warning(`评查设置中的规则 ${i + 1}(有无判断):请选择逻辑关系`); + return; + } + break; + + case 'consistency': + if (!rule.config.pairs || rule.config.pairs.length === 0) { + toastService.warning(`评查设置中的规则 ${i + 1}(一致性判断):请至少添加一对比较字段`); + return; + } + // 检查每对字段是否完整 + for (let j = 0; j < rule.config.pairs.length; j++) { + const pair = rule.config.pairs[j]; + if (!pair.sourceField || !pair.targetField || !pair.compareMethod) { + toastService.warning(`评查设置中的规则 ${i + 1}(一致性判断):第 ${j + 1} 对比较字段配置不完整`); + return; + } + } + break; + + case 'format': + if (!rule.config.field) { + toastService.warning(`评查设置中的规则 ${i + 1}(格式判断):请选择检查字段`); + return; + } + if (!rule.config.formatType) { + toastService.warning(`评查设置中的规则 ${i + 1}(格式判断):请选择格式类型`); + return; + } + break; + + case 'logic': + if (!rule.config.conditions || rule.config.conditions.length === 0) { + toastService.warning(`评查设置中的规则 ${i + 1}(逻辑判断):请至少添加一个条件`); + return; + } + // 检查每个条件是否完整 + for (let j = 0; j < rule.config.conditions.length; j++) { + const condition = rule.config.conditions[j]; + if (!condition.field || !condition.operator || condition.value === undefined || condition.value === '') { + toastService.warning(`评查设置中的规则 ${i + 1}(逻辑判断):第 ${j + 1} 个条件配置不完整`); + return; + } + } + break; + + case 'regex': + if (!rule.config.field) { + toastService.warning(`评查设置中的规则 ${i + 1}(正则表达式):请选择检查字段`); + return; + } + if (!rule.config.pattern) { + toastService.warning(`评查设置中的规则 ${i + 1}(正则表达式):请输入正则表达式`); + return; + } + if (!rule.config.matchType) { + toastService.warning(`评查设置中的规则 ${i + 1}(正则表达式):请选择匹配类型`); + return; + } + break; + + case 'ai': + if (!rule.config.model) { + toastService.warning(`评查设置中的规则 ${i + 1}(大模型判断):请选择模型`); + return; + } + if (!rule.config.prompt) { + toastService.warning(`评查设置中的规则 ${i + 1}(大模型判断):请输入提示词`); + return; + } + break; + + case 'code': + if (!rule.config.language) { + toastService.warning(`评查设置中的规则 ${i + 1}(自定义代码):请选择编程语言`); + return; + } + if (!rule.config.code) { + toastService.warning(`评查设置中的规则 ${i + 1}(自定义代码):请输入代码`); + return; + } + break; + } + } + } + + // 7. 验证组合逻辑 + if (formData.evaluation_config?.logicType === 'custom') { + if (!formData.evaluation_config.customLogic?.trim()) { + toastService.warning("请输入自定义组合逻辑"); + return; + } + } + // 显示保存中状态 setIsLoading(true); @@ -386,18 +541,18 @@ export default function RuleNew() { evaluation_point_groups_id: formData.evaluation_point_groups_id || null, extraction_config: { llm: { - fields: Array.isArray(formData.extraction_config?.llm?.fields) ? + fields: Array.isArray(formData.extraction_config?.llm?.fields) ? [...formData.extraction_config.llm.fields] : [], prompt_setting: { - type: formData.extraction_config?.llm?.prompt_setting?.type || 'system', + type: formData.extraction_config?.llm?.prompt_setting?.type || 'llm_default_prompt', template: formData.extraction_config?.llm?.prompt_setting?.template || '' } }, vlm: { - fields: Array.isArray(formData.extraction_config?.vlm?.fields) ? + fields: Array.isArray(formData.extraction_config?.vlm?.fields) ? [...formData.extraction_config.vlm.fields] : [], prompt_setting: { - type: formData.extraction_config?.vlm?.prompt_setting?.type || 'system', + type: formData.extraction_config?.vlm?.prompt_setting?.type || 'vlm_default_prompt', template: formData.extraction_config?.vlm?.prompt_setting?.template || '' } }, @@ -546,7 +701,10 @@ export default function RuleNew() { }); } - // console.log("当前评查配置-----------------:", formData.evaluation_config); + + // console.log("当前表单数据-----------------:", formData); + // console.log("当前评查配置-----------------:", formData.evaluation_config); + // return; // 如果是新建模式,则删除id字段 if (!isEditMode) { @@ -799,7 +957,7 @@ export default function RuleNew() {