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,
|
||||
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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user