feat(frontend): field-level multi_entity toggle in extraction settings
- evaluation_points.ts: LLMFieldConfig type, LLMFieldType union,
VLMField.multi_entity
- ExtractionSettings.tsx:
- LLM fields render as colored buttons (green=multi_entity, gray=normal)
- Click to toggle individual field multi_entity when switch is on
- Toggle switch on: converts all string fields to {name, multi_entity:true}
- Toggle switch off: converts back to plain strings
- New fields default to multi_entity:true when switch is on
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
useEffect,
|
useEffect,
|
||||||
} from "react";
|
} from "react";
|
||||||
import type { EvaluationPoint } from "~/models/evaluation_points";
|
import type { EvaluationPoint, LLMFieldType } from "~/models/evaluation_points";
|
||||||
import { EVALUATION_OPTIONS, VLMFieldType } from "~/models/evaluation_points";
|
import { EVALUATION_OPTIONS, VLMFieldType } from "~/models/evaluation_points";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,6 +35,14 @@ import { EVALUATION_OPTIONS, VLMFieldType } from "~/models/evaluation_points";
|
|||||||
* - 'or': 规则内任一条件满足即可
|
* - 'or': 规则内任一条件满足即可
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/** 获取 LLM 字段名称 */
|
||||||
|
const getLLMFieldName = (field: LLMFieldType): string =>
|
||||||
|
typeof field === 'string' ? field : field.name;
|
||||||
|
|
||||||
|
/** 获取 LLM 字段的 multi_entity 状态 */
|
||||||
|
const isLLMFieldMultiEntity = (field: LLMFieldType): boolean =>
|
||||||
|
typeof field === 'string' ? false : !!field.multi_entity;
|
||||||
|
|
||||||
interface ExtractionSettingsProps {
|
interface ExtractionSettingsProps {
|
||||||
onChange: (data: Record<string, unknown>) => void;
|
onChange: (data: Record<string, unknown>) => void;
|
||||||
initialData: EvaluationPoint;
|
initialData: EvaluationPoint;
|
||||||
@@ -196,10 +204,16 @@ export function ExtractionSettings({
|
|||||||
const inputs = inputValue[type].split(/[,,\s]+/).filter(Boolean);
|
const inputs = inputValue[type].split(/[,,\s]+/).filter(Boolean);
|
||||||
|
|
||||||
if (type === 'llm') {
|
if (type === 'llm') {
|
||||||
const newFields = [...fields.llm];
|
const newFields = [...fields.llm] as LLMFieldType[];
|
||||||
inputs.forEach(input => {
|
inputs.forEach(input => {
|
||||||
if (!newFields.includes(input)) {
|
const exists = newFields.some(f => getLLMFieldName(f) === input);
|
||||||
newFields.push(input);
|
if (!exists) {
|
||||||
|
if (multiEntityEnabled) {
|
||||||
|
// 多实体模式:新字段默认 multi_entity=true
|
||||||
|
newFields.push({ name: input, multi_entity: true });
|
||||||
|
} else {
|
||||||
|
newFields.push(input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setFields({ ...fields, llm: newFields });
|
setFields({ ...fields, llm: newFields });
|
||||||
@@ -263,6 +277,18 @@ export function ExtractionSettings({
|
|||||||
setHasPendingChanges(true);
|
setHasPendingChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 切换 LLM 字段的多实体状态
|
||||||
|
const toggleLLMFieldMultiEntity = (index: number) => {
|
||||||
|
if (!multiEntityEnabled) return; // 多实体未开启时不允许切换
|
||||||
|
const newFields = [...fields.llm] as LLMFieldType[];
|
||||||
|
const field = newFields[index];
|
||||||
|
const name = getLLMFieldName(field);
|
||||||
|
const currentMulti = isLLMFieldMultiEntity(field);
|
||||||
|
newFields[index] = { name, multi_entity: !currentMulti };
|
||||||
|
setFields({ ...fields, llm: newFields });
|
||||||
|
setHasPendingChanges(true);
|
||||||
|
};
|
||||||
|
|
||||||
// 获取VLM字段信息
|
// 获取VLM字段信息
|
||||||
const getFieldInfo = (field: string | { name: string, type: string, template?: string }) => {
|
const getFieldInfo = (field: string | { name: string, type: string, template?: string }) => {
|
||||||
let fieldName, fieldType, typeName, badgeClass;
|
let fieldName, fieldType, typeName, badgeClass;
|
||||||
@@ -523,7 +549,7 @@ export function ExtractionSettings({
|
|||||||
|
|
||||||
// 验证字段唯一性
|
// 验证字段唯一性
|
||||||
const allFieldNames = [
|
const allFieldNames = [
|
||||||
...fields.llm,
|
...fields.llm.map(f => getLLMFieldName(f)),
|
||||||
...fields.vlm.map(f => typeof f === 'string' ? f : f.name),
|
...fields.vlm.map(f => typeof f === 'string' ? f : f.name),
|
||||||
...validRegexFields.map(f => f.field)
|
...validRegexFields.map(f => f.field)
|
||||||
];
|
];
|
||||||
@@ -579,6 +605,19 @@ export function ExtractionSettings({
|
|||||||
const handleMultiEntityToggle = () => {
|
const handleMultiEntityToggle = () => {
|
||||||
const newValue = !multiEntityEnabled;
|
const newValue = !multiEntityEnabled;
|
||||||
setMultiEntityEnabled(newValue);
|
setMultiEntityEnabled(newValue);
|
||||||
|
|
||||||
|
if (newValue) {
|
||||||
|
// 开启:将所有字符串字段转为 dict(默认 multi_entity=true)
|
||||||
|
const converted = fields.llm.map(f =>
|
||||||
|
typeof f === 'string' ? { name: f, multi_entity: true } : f
|
||||||
|
);
|
||||||
|
setFields({ ...fields, llm: converted });
|
||||||
|
} else {
|
||||||
|
// 关闭:将所有字段转回字符串
|
||||||
|
const simplified = fields.llm.map(f => getLLMFieldName(f));
|
||||||
|
setFields({ ...fields, llm: simplified });
|
||||||
|
}
|
||||||
|
|
||||||
setHasPendingChanges(true);
|
setHasPendingChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -595,7 +634,7 @@ export function ExtractionSettings({
|
|||||||
<i className="ri-group-line text-lg mr-2 text-gray-600"></i>
|
<i className="ri-group-line text-lg mr-2 text-gray-600"></i>
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-gray-800">多实体抽取</span>
|
<span className="font-medium text-gray-800">多实体抽取</span>
|
||||||
<span className="text-xs text-gray-500 ml-3">启用后,系统将按实体展开字段进行抽取(AI感知模式)</span>
|
<span className="text-xs text-gray-500 ml-3">启用后,点击字段可切换是否按实体展开抽取(绿色=展开)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -677,25 +716,30 @@ export function ExtractionSettings({
|
|||||||
<div className="form-tip mb-2 text-xs">
|
<div className="form-tip mb-2 text-xs">
|
||||||
支持一次输入多个字段,用逗号、空格或顿号分隔
|
支持一次输入多个字段,用逗号、空格或顿号分隔
|
||||||
</div>
|
</div>
|
||||||
<div className="chips-container" id="fields-container">
|
<div className="flex flex-wrap gap-2" id="fields-container">
|
||||||
{fields.llm.map((field, index) => (
|
{fields.llm.map((field, index) => {
|
||||||
<div className="chip" key={`llm-field-${index}`}>
|
const name = getLLMFieldName(field);
|
||||||
{field}
|
const isMulti = isLLMFieldMultiEntity(field);
|
||||||
<span
|
return (
|
||||||
className="close-btn"
|
<button
|
||||||
onClick={() => removeField("llm", index)}
|
type="button"
|
||||||
onKeyDown={(e) => {
|
key={`llm-field-${index}`}
|
||||||
if (e.key === "Enter" || e.key === " ")
|
className={`ant-btn ${multiEntityEnabled && isMulti ? 'ant-btn-primary' : 'ant-btn-default tag-button'}`}
|
||||||
removeField("llm", index);
|
onClick={() => multiEntityEnabled && toggleLLMFieldMultiEntity(index)}
|
||||||
}}
|
title={multiEntityEnabled ? (isMulti ? '点击关闭多实体展开' : '点击开启多实体展开') : name}
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
aria-label={`删除字段 ${field}`}
|
|
||||||
>
|
>
|
||||||
×
|
{name}
|
||||||
</span>
|
<span
|
||||||
</div>
|
className="ml-1 cursor-pointer hover:text-red-500"
|
||||||
))}
|
onClick={(e) => { e.stopPropagation(); removeField("llm", index); }}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,19 +55,33 @@ interface MultiEntityConfig {
|
|||||||
*/
|
*/
|
||||||
type MultiEntityExpandMode = 'awareness'; // AI感知模式
|
type MultiEntityExpandMode = 'awareness'; // AI感知模式
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LLM 字段配置(支持字段级多实体标记)
|
||||||
|
*/
|
||||||
|
interface LLMFieldConfig {
|
||||||
|
name: string; // 字段名称(完整路径)
|
||||||
|
multi_entity?: boolean; // 是否对此字段做多实体展开(默认 false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LLM 字段类型:支持旧格式字符串和新格式对象
|
||||||
|
*/
|
||||||
|
type LLMFieldType = string | LLMFieldConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 抽取配置类型定义
|
* 抽取配置类型定义
|
||||||
*/
|
*/
|
||||||
interface ExtactionConfigType {
|
interface ExtactionConfigType {
|
||||||
multi_entity?: MultiEntityConfig; // 多实体抽取配置(控制抽取阶段是否按实体展开字段)
|
multi_entity?: MultiEntityConfig; // 多实体抽取配置(EP级开关,作为旧格式str字段的fallback)
|
||||||
llm: {
|
llm: {
|
||||||
fields: string[];
|
fields: LLMFieldType[]; // 支持 "字段名" 或 {"name": "字段名", "multi_entity": true}
|
||||||
prompt_setting: LLMPromptSetting;
|
prompt_setting: LLMPromptSetting;
|
||||||
};
|
};
|
||||||
vlm: {
|
vlm: {
|
||||||
fields: Array<{
|
fields: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
type: VLMFieldType; // 多模态字段类型 默认、货币、打印、印章、骑缝章、英文、数字、手写、自定义
|
type: VLMFieldType; // 多模态字段类型 默认、货币、打印、印章、骑缝章、英文、数字、手写、自定义
|
||||||
|
multi_entity?: boolean; // 是否对此字段做多实体展开
|
||||||
template?: string; // 自定义类型的提示词模板(仅当 type 为 custom 时使用)
|
template?: string; // 自定义类型的提示词模板(仅当 type 为 custom 时使用)
|
||||||
}>;
|
}>;
|
||||||
prompt_setting: VLMPromptSetting;
|
prompt_setting: VLMPromptSetting;
|
||||||
@@ -329,6 +343,8 @@ export type {
|
|||||||
ExtactionConfigType,
|
ExtactionConfigType,
|
||||||
MultiEntityConfig,
|
MultiEntityConfig,
|
||||||
MultiEntityExpandMode,
|
MultiEntityExpandMode,
|
||||||
|
LLMFieldConfig,
|
||||||
|
LLMFieldType,
|
||||||
VLMPromptSetting,
|
VLMPromptSetting,
|
||||||
VLMFieldType,
|
VLMFieldType,
|
||||||
LLMPromptSetting,
|
LLMPromptSetting,
|
||||||
|
|||||||
Reference in New Issue
Block a user