fix: 修改评查点设置中的多模态抽取设置的逻辑。

This commit is contained in:
2025-11-10 20:40:08 +08:00
parent b375c35825
commit ddad57529d
3 changed files with 395 additions and 182 deletions
+181 -148
View File
@@ -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) => (
<option key={`${type}-${option.value}`} value={option.value}>
{option.label}
</option>
))}
{type === 'vlm' && EVALUATION_OPTIONS.vlmPromptTypeOptions.map((option) => (
<option key={`${type}-${option.value}`} value={option.value}>
{option.label}
</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({
</div>
<div className="chips-container" id="fields-container-vlm">
{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 (
<div className="chip" key={`vlm-field-${index}`}>
{fieldName}
@@ -781,115 +831,98 @@ export function ExtractionSettings({
</div>
</div>
<div className="grid grid-cols-1 gap-3 mt-3">
<div className="col-span-1">
<label
className="form-label mb-1"
htmlFor="multimodal-prompt-settings"
>
</label>
<div
className="flex items-center mb-2"
id="multimodal-prompt-settings"
>
{renderPromptTypeSelect(promptType.vlm, "vlm")}
</div>
<div
className="bg-gray-50 p-2 rounded text-xs text-gray-600 mb-2"
id="multimodal-system-prompt-info"
style={{
display: promptType.vlm === "system" ? "block" : "none",
}}
>
</div>
<div
id="multimodal-custom-prompt-container"
style={{
display: promptType.vlm === "custom" ? "block" : "none",
}}
className="border border-dashed border-gray-300 p-3 rounded-md"
>
<div className="mb-2">
<label
className="form-label mb-1 text-sm"
htmlFor="multimodal-prompt-template"
>
</label>
<select
className="form-select"
id="multimodal-prompt-template"
value={selectedTemplate.vlm}
onChange={(e) => handleTemplateChange(e, "vlm")}
>
<option value=""></option>
<option value="7">-</option>
<option value="8">-</option>
<option value="9">-</option>
</select>
</div>
<div className="mb-2">
<label
className="form-label mb-1 text-sm"
htmlFor="multimodal-prompt-content"
>
</label>
<textarea
className="form-textarea"
id="multimodal-prompt-content"
rows={4}
placeholder="选择模板后自动填充,您也可以进行修改..."
value={promptContent.vlm}
onChange={(e) => handlePromptContentChange(e, "vlm")}
readOnly={!selectedTemplate.vlm}
></textarea>
<div className="form-tip mt-1 bg-gray-50 p-2 rounded text-xs">
<p className="mb-1">
<strong></strong>
</p>
<div className="flex flex-wrap gap-1">
{[
"docType",
"fieldsList",
"companyName",
"documentId",
"date",
"industry",
"contentType",
"pageRange",
"colorMode",
"ocrText",
].map((variable) => (
<button
key={variable}
type="button"
className="var-tag"
onClick={() => applyVariableToPrompt(variable, "vlm")}
>
{variable=='docType' ? '文档类型:{docType}':
variable=='fieldsList' ? '抽取字段列表:{fieldsList}':
variable=='companyName' ? '公司名称:{companyName}':
variable=='documentId' ? '文档编号:{documentId}':
variable=='date' ? '日期:{date}':
variable=='industry' ? '行业:{industry}':
variable=='contentType' ? '内容类型:{contentType}':
variable=='pageRange' ? '页面范围:{pageRange}':
variable=='colorMode' ? '色彩模式:{colorMode}':
variable=='ocrText' ? 'OCR文本:{ocrText}':
variable}
</button>
))}
{/* 只有当选择了自定义类型时才显示提示词设置 */}
{selectedVlmFieldType === 'custom' && (
<div className="grid grid-cols-1 gap-3 mt-3">
<div className="col-span-1">
<label
className="form-label mb-1"
htmlFor="multimodal-prompt-settings"
>
</label>
<div className="border border-dashed border-gray-300 p-3 rounded-md">
<div className="mb-2">
<label
className="form-label mb-1 text-sm"
htmlFor="multimodal-prompt-type"
>
</label>
<input
type="text"
className="form-input bg-gray-50"
id="multimodal-prompt-type"
value="使用自定义提示词"
readOnly
/>
</div>
<div className="mb-2">
<label
className="form-label mb-1 text-sm"
htmlFor="custom-vlm-prompt-content"
>
</label>
<textarea
className="form-textarea"
id="custom-vlm-prompt-content"
rows={4}
placeholder="请输入自定义提示词..."
value={customVlmPrompt}
onChange={(e) => {
setCustomVlmPrompt(e.target.value);
setHasPendingChanges(true);
}}
></textarea>
<div className="form-tip mt-1 bg-gray-50 p-2 rounded text-xs">
<p className="mb-1">
<strong></strong>
</p>
<div className="flex flex-wrap gap-1">
{[
"docType",
"fieldsList",
"companyName",
"documentId",
"date",
"industry",
"contentType",
"pageRange",
"colorMode",
"ocrText",
].map((variable) => (
<button
key={variable}
type="button"
className="var-tag"
onClick={() => {
const variableText = `\${${variable}}`;
setCustomVlmPrompt(customVlmPrompt + variableText);
setHasPendingChanges(true);
}}
>
{variable=='docType' ? '文档类型:{docType}':
variable=='fieldsList' ? '抽取字段列表:{fieldsList}':
variable=='companyName' ? '公司名称:{companyName}':
variable=='documentId' ? '文档编号:{documentId}':
variable=='date' ? '日期:{date}':
variable=='industry' ? '行业:{industry}':
variable=='contentType' ? '内容类型:{contentType}':
variable=='pageRange' ? '页面范围:{pageRange}':
variable=='colorMode' ? '色彩模式:{colorMode}':
variable=='ocrText' ? 'OCR文本:{ocrText}':
variable}
</button>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)}
</div>
<div