From 5742608b97b4580a57786bda2a259dfec9d0c0fa Mon Sep 17 00:00:00 2001
From: 2910410219 <2910410219@qq.com>
Date: Wed, 15 Jul 2026 16:33:58 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20JSON=E7=BC=96=E8=BE=91=E5=99=A8?=
=?UTF-8?q?=E5=A2=9E=E5=BC=BA=20-=20Token=E6=98=A0=E5=B0=84=E4=B8=8B?=
=?UTF-8?q?=E6=8B=89=E3=80=81=E5=AD=97=E6=AE=B5=E9=80=89=E9=A1=B9key:value?=
=?UTF-8?q?=E3=80=81=E5=BA=8F=E5=88=97=E5=8C=96=E5=A7=8B=E7=BB=88=E5=8C=85?=
=?UTF-8?q?=E8=A3=85type?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. 模型配置弹窗: Token映射三个字段由输入框改为下拉菜单
- 同步/流式从响应映射取key, 异步从回调响应映射取key
- 切换返回类型时清空Token映射, 无映射配置时禁用并提示
2. JSON编辑器序列化: 所有节点始终输出{type, value/attrs}格式
- 容器(object/array)始终{type, attrs}, 标量始终{type, value}
3. 字段配置增强:
- 对象类型新增"循环生成"开关
- 下拉控件选项列表改为key:value双输入框
4. 类型注释更新
---
src/api/settings/modelConfigV2/index.ts | 2 +-
.../json-schema-editor/FieldConfigDialog.vue | 28 ++++++++++---
.../json-schema-editor/JsonNode.vue | 2 +-
src/components/json-schema-editor/index.vue | 27 ++++++++++---
.../modelConfigV2/component/editModule.vue | 40 +++++++++++++++----
5 files changed, 78 insertions(+), 21 deletions(-)
diff --git a/src/api/settings/modelConfigV2/index.ts b/src/api/settings/modelConfigV2/index.ts
index 92aa625..237894a 100644
--- a/src/api/settings/modelConfigV2/index.ts
+++ b/src/api/settings/modelConfigV2/index.ts
@@ -1,6 +1,6 @@
import request from '/@/utils/request';
-/** Token 映射 */
+/** Token 映射(三个字段存的是对应响应映射中的 key,不是 JSON Path) */
export interface TokenMapping {
promptTokens: string;
completionTokens: string;
diff --git a/src/components/json-schema-editor/FieldConfigDialog.vue b/src/components/json-schema-editor/FieldConfigDialog.vue
index bc223ca..0564042 100644
--- a/src/components/json-schema-editor/FieldConfigDialog.vue
+++ b/src/components/json-schema-editor/FieldConfigDialog.vue
@@ -25,6 +25,10 @@
JSON 数据类型
+
+
+ 此对象字段是否循环生成
+
@@ -53,10 +57,11 @@
@@ -125,6 +130,10 @@
JSON 数据类型
+
+
+ 此对象字段是否循环生成
+
@@ -153,10 +162,11 @@
@@ -204,7 +214,7 @@ interface UploadRule { format: string; maxSize?: number; maxCount?: number; }
interface FieldFormData {
nodeKey: string; nodeId: string; jsonType: string; fieldType: string;
label: string; description: string; role: string;
- isForm: boolean; required: boolean; defaultValue: string; options: string[];
+ isForm: boolean; required: boolean; loop: boolean; defaultValue: string; options: Array<{ label: string; value: string }>;
constraint: {
minLength?: number; maxLength?: number; pattern?: string;
min?: number; max?: number; numberType?: string;
@@ -231,7 +241,7 @@ const emit = defineEmits<{
const form = reactive({
nodeKey: '', nodeId: '', jsonType: 'string', fieldType: 'string',
label: '', description: '', role: '',
- isForm: true, required: false, defaultValue: '', options: [], constraint: {},
+ isForm: true, required: false, loop: false, defaultValue: '', options: [], constraint: {},
});
const uploadRules = ref([]);
@@ -251,6 +261,7 @@ function resetForm() {
form.nodeKey = ''; form.nodeId = ''; form.jsonType = 'string'; form.fieldType = 'string';
form.label = ''; form.description = ''; form.role = '';
form.isForm = true; form.required = false; form.defaultValue = '';
+ form.loop = false;
form.options = []; form.constraint = {};
// @ts-ignore
form._createParentId = undefined; form._isArrayParent = false;
@@ -265,8 +276,13 @@ function loadForm(data: FieldFormData | null) {
form.jsonType = data.jsonType || 'string'; form.fieldType = data.fieldType;
form.label = data.label || ''; form.description = data.description || ''; form.role = data.role || '';
form.isForm = data.isForm ?? true; form.required = data.required || false;
+ form.loop = data.loop ?? false;
form.defaultValue = data.defaultValue || '';
form.options = data.options ? [...data.options] : [];
+ // 兼容旧格式:如果 options 是字符串数组,转为 { label, value }
+ if (form.options.length > 0 && typeof (form.options as any)[0] === 'string') {
+ form.options = (data.options as string[]).map(s => ({ label: s, value: s }));
+ }
form.constraint = data.constraint ? { ...data.constraint } : {};
// @ts-ignore
form._createParentId = data._createParentId; form._isArrayParent = data._isArrayParent ?? false;
diff --git a/src/components/json-schema-editor/JsonNode.vue b/src/components/json-schema-editor/JsonNode.vue
index 66563d6..69a8750 100644
--- a/src/components/json-schema-editor/JsonNode.vue
+++ b/src/components/json-schema-editor/JsonNode.vue
@@ -93,7 +93,7 @@ interface JsonNodeData {
isForm?: boolean;
required?: boolean;
defaultValue?: string | number;
- options?: string[];
+ options?: Array<{ label: string; value: string }>;
constraint?: Record;
};
}
diff --git a/src/components/json-schema-editor/index.vue b/src/components/json-schema-editor/index.vue
index 7065d6f..175bfcd 100644
--- a/src/components/json-schema-editor/index.vue
+++ b/src/components/json-schema-editor/index.vue
@@ -75,8 +75,9 @@ interface FieldConfig {
role?: string;
isForm?: boolean;
required?: boolean;
+ loop?: boolean;
defaultValue?: string | number;
- options?: string[];
+ options?: Array<{ label: string; value: string }>;
constraint?: {
minLength?: number; maxLength?: number; pattern?: string;
min?: number; max?: number; numberType?: string;
@@ -100,7 +101,7 @@ interface FieldFormData {
nodeId: string;
jsonType: string;
fieldType: string; label: string; description: string; role: string;
- isForm: boolean; required: boolean; defaultValue: string; options: string[];
+ isForm: boolean; required: boolean; loop: boolean; defaultValue: string; options: Array<{ label: string; value: string }>;
constraint: {
minLength?: number; maxLength?: number; pattern?: string;
min?: number; max?: number; numberType?: string;
@@ -182,11 +183,21 @@ function nodeToValue(node: JsonNodeData): any {
else if (node.type === 'boolean') rawValue = node.primitiveValue === true || node.primitiveValue === 'true';
else rawValue = String(node.primitiveValue);
- if (node.config && hasAnyConfig(node)) {
- const dataKey = (node.type === 'object' || node.type === 'array') ? 'attrs' : 'value';
- return { type: node.type, [dataKey]: rawValue, ...node.config };
+ // 容器类型(object/array)始终输出 { type, attrs, ...config }
+ if (node.type === 'object' || node.type === 'array') {
+ const result: Record = { type: node.type, attrs: rawValue };
+ if (node.config && hasAnyConfig(node)) {
+ Object.assign(result, node.config);
+ }
+ return result;
}
- return rawValue;
+
+ // 标量类型也始终输出 { type, value, ...config }
+ const result: Record = { type: node.type, value: rawValue };
+ if (node.config && hasAnyConfig(node)) {
+ Object.assign(result, node.config);
+ }
+ return result;
}
function defaultFormType(jsonType: string): string {
@@ -274,6 +285,7 @@ function formToConfig(form: FieldFormData): FieldConfig {
cfg.fieldType = form.fieldType || defaultFormType(form.jsonType);
cfg.isForm = form.isForm ?? true;
cfg.required = form.required ?? false;
+ cfg.loop = form.loop ?? false;
// 非骨架字段:有值才发
if (form.description) cfg.description = form.description;
if (form.role) cfg.role = form.role;
@@ -299,6 +311,7 @@ function hasAnyConfig(node: JsonNodeData): boolean {
if (c.label || c.description || c.role || c.defaultValue) return true;
if (c.options?.length) return true;
if (c.required) return true;
+ if (c.loop) return true;
if (c.isForm === false) return true;
if (c.constraint && Object.keys(c.constraint).length > 0) return true;
if (c.fieldType && c.fieldType !== defaultFormType(node.type)) return true;
@@ -324,6 +337,7 @@ function openFieldDialog(nodeId: string) {
role: cfg.role || '',
isForm: cfg.isForm ?? true,
required: cfg.required ?? false,
+ loop: cfg.loop ?? false,
defaultValue: String(cfg.defaultValue ?? ''),
options: cfg.options || [],
constraint: cfg.constraint ? { ...cfg.constraint } : {},
@@ -355,6 +369,7 @@ function openAddChildDialog(parentNodeId: string) {
role: '',
isForm: true,
required: false,
+ loop: false,
defaultValue: '',
options: [],
constraint: {},
diff --git a/src/views/settings/modelConfigV2/component/editModule.vue b/src/views/settings/modelConfigV2/component/editModule.vue
index f5f94e7..554d895 100644
--- a/src/views/settings/modelConfigV2/component/editModule.vue
+++ b/src/views/settings/modelConfigV2/component/editModule.vue
@@ -128,17 +128,23 @@
-
+
+
+
-
+
+
+
-
+
+
+
@@ -379,6 +385,27 @@ watch(isReasoningModel, (val) => {
if (!val) formData.chatModel = false;
});
+/** 根据返回类型获取 Token 映射下拉选项(响应映射的 key 列表) */
+const tokenMappingOptions = computed(() => {
+ const source = formData.responseType === 2
+ ? formData.asyncTaskMapping.responseMapping
+ : formData.responseMapping;
+ return source ? Object.keys(source) : [];
+});
+
+/** 是否有可用的映射配置 */
+const hasMappingConfig = computed(() => tokenMappingOptions.value.length > 0);
+
+/** 下拉占位文本 */
+const mappingPlaceholder = computed(() =>
+ hasMappingConfig.value ? '请选择' : '请先配置响应映射'
+);
+
+/** 切换返回类型时清空 token mapping */
+watch(() => formData.responseType, () => {
+ formData.tokenMapping = { ...defaultTokenMapping };
+});
+
/** JSON 编辑器弹窗状态 */
const jsonEditorVisible = ref(false);
const jsonEditorData = ref>({});
@@ -547,10 +574,9 @@ const handleSubmit = async () => {
let asyncTaskMapping: AsyncTaskMapping | undefined;
let lastFrame: string | undefined;
- if (formData.responseType === 3) {
- const hasTokenConfig = Object.values(formData.tokenMapping).some((v) => v);
- tokenMapping = hasTokenConfig ? { ...formData.tokenMapping } : undefined;
- }
+ // 所有返回类型共用 Token 映射(存的是响应映射中的 key)
+ const hasTokenConfig = Object.values(formData.tokenMapping).some((v) => v);
+ tokenMapping = hasTokenConfig ? { ...formData.tokenMapping } : undefined;
if (formData.responseType === 2) {
const atm = formData.asyncTaskMapping;