From bc3e26c93e85f33d20cf644fe3e343651b8c3de2 Mon Sep 17 00:00:00 2001 From: wren Date: Tue, 24 Mar 2026 19:34:45 +0800 Subject: [PATCH] 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) --- .../rules/new/ExtractionSettings.tsx | 92 ++++++++++++++----- app/models/evaluation_points.ts | 20 +++- 2 files changed, 86 insertions(+), 26 deletions(-) diff --git a/app/components/rules/new/ExtractionSettings.tsx b/app/components/rules/new/ExtractionSettings.tsx index ee89d5c..d6d4abd 100644 --- a/app/components/rules/new/ExtractionSettings.tsx +++ b/app/components/rules/new/ExtractionSettings.tsx @@ -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) => 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({
多实体抽取 - 启用后,系统将按实体展开字段进行抽取(AI感知模式) + 启用后,点击字段可切换是否按实体展开抽取(绿色=展开)
@@ -677,25 +716,30 @@ export function ExtractionSettings({
支持一次输入多个字段,用逗号、空格或顿号分隔
-
- {fields.llm.map((field, index) => ( -
- {field} - removeField("llm", index)} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") - removeField("llm", index); - }} - role="button" - tabIndex={0} - aria-label={`删除字段 ${field}`} +
+ {fields.llm.map((field, index) => { + const name = getLLMFieldName(field); + const isMulti = isLLMFieldMultiEntity(field); + return ( +
- ))} + {name} + { e.stopPropagation(); removeField("llm", index); }} + role="button" + tabIndex={0} + > + × + + + ); + })}
diff --git a/app/models/evaluation_points.ts b/app/models/evaluation_points.ts index 24d8131..9810068 100644 --- a/app/models/evaluation_points.ts +++ b/app/models/evaluation_points.ts @@ -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,