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:
2026-03-24 19:34:45 +08:00
parent 5d3cb2ba34
commit bc3e26c93e
2 changed files with 86 additions and 26 deletions
+68 -24
View File
@@ -2,7 +2,7 @@ import React, {
useState,
useEffect,
} 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";
/**
@@ -35,6 +35,14 @@ import { EVALUATION_OPTIONS, VLMFieldType } from "~/models/evaluation_points";
* - '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 {
onChange: (data: Record<string, unknown>) => void;
initialData: EvaluationPoint;
@@ -196,10 +204,16 @@ export function ExtractionSettings({
const inputs = inputValue[type].split(/[,\s]+/).filter(Boolean);
if (type === 'llm') {
const newFields = [...fields.llm];
const newFields = [...fields.llm] as LLMFieldType[];
inputs.forEach(input => {
if (!newFields.includes(input)) {
newFields.push(input);
const exists = newFields.some(f => getLLMFieldName(f) === input);
if (!exists) {
if (multiEntityEnabled) {
// 多实体模式:新字段默认 multi_entity=true
newFields.push({ name: input, multi_entity: true });
} else {
newFields.push(input);
}
}
});
setFields({ ...fields, llm: newFields });
@@ -263,6 +277,18 @@ export function ExtractionSettings({
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字段信息
const getFieldInfo = (field: string | { name: string, type: string, template?: string }) => {
let fieldName, fieldType, typeName, badgeClass;
@@ -523,7 +549,7 @@ export function ExtractionSettings({
// 验证字段唯一性
const allFieldNames = [
...fields.llm,
...fields.llm.map(f => getLLMFieldName(f)),
...fields.vlm.map(f => typeof f === 'string' ? f : f.name),
...validRegexFields.map(f => f.field)
];
@@ -579,6 +605,19 @@ export function ExtractionSettings({
const handleMultiEntityToggle = () => {
const newValue = !multiEntityEnabled;
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);
};
@@ -595,7 +634,7 @@ export function ExtractionSettings({
<i className="ri-group-line text-lg mr-2 text-gray-600"></i>
<div>
<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>
@@ -677,25 +716,30 @@ export function ExtractionSettings({
<div className="form-tip mb-2 text-xs">
</div>
<div className="chips-container" id="fields-container">
{fields.llm.map((field, index) => (
<div className="chip" key={`llm-field-${index}`}>
{field}
<span
className="close-btn"
onClick={() => removeField("llm", index)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ")
removeField("llm", index);
}}
role="button"
tabIndex={0}
aria-label={`删除字段 ${field}`}
<div className="flex flex-wrap gap-2" id="fields-container">
{fields.llm.map((field, index) => {
const name = getLLMFieldName(field);
const isMulti = isLLMFieldMultiEntity(field);
return (
<button
type="button"
key={`llm-field-${index}`}
className={`ant-btn ${multiEntityEnabled && isMulti ? 'ant-btn-primary' : 'ant-btn-default tag-button'}`}
onClick={() => multiEntityEnabled && toggleLLMFieldMultiEntity(index)}
title={multiEntityEnabled ? (isMulti ? '点击关闭多实体展开' : '点击开启多实体展开') : name}
>
×
</span>
</div>
))}
{name}
<span
className="ml-1 cursor-pointer hover:text-red-500"
onClick={(e) => { e.stopPropagation(); removeField("llm", index); }}
role="button"
tabIndex={0}
>
×
</span>
</button>
);
})}
</div>
</div>
+18 -2
View File
@@ -55,19 +55,33 @@ interface MultiEntityConfig {
*/
type MultiEntityExpandMode = 'awareness'; // AI感知模式
/**
* LLM 字段配置(支持字段级多实体标记)
*/
interface LLMFieldConfig {
name: string; // 字段名称(完整路径)
multi_entity?: boolean; // 是否对此字段做多实体展开(默认 false)
}
/**
* LLM 字段类型:支持旧格式字符串和新格式对象
*/
type LLMFieldType = string | LLMFieldConfig;
/**
* 抽取配置类型定义
*/
interface ExtactionConfigType {
multi_entity?: MultiEntityConfig; // 多实体抽取配置(控制抽取阶段是否按实体展开字段
multi_entity?: MultiEntityConfig; // 多实体抽取配置(EP级开关,作为旧格式str字段的fallback
llm: {
fields: string[];
fields: LLMFieldType[]; // 支持 "字段名" 或 {"name": "字段名", "multi_entity": true}
prompt_setting: LLMPromptSetting;
};
vlm: {
fields: Array<{
name: string;
type: VLMFieldType; // 多模态字段类型 默认、货币、打印、印章、骑缝章、英文、数字、手写、自定义
multi_entity?: boolean; // 是否对此字段做多实体展开
template?: string; // 自定义类型的提示词模板(仅当 type 为 custom 时使用)
}>;
prompt_setting: VLMPromptSetting;
@@ -329,6 +343,8 @@ export type {
ExtactionConfigType,
MultiEntityConfig,
MultiEntityExpandMode,
LLMFieldConfig,
LLMFieldType,
VLMPromptSetting,
VLMFieldType,
LLMPromptSetting,