更新模型配置,新增字段支持和请求映射功能,优化界面元素,移除冗余代码

This commit is contained in:
2026-06-05 15:56:44 +08:00
parent eea5874dbf
commit 56e1517743
7 changed files with 219 additions and 48 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,8 @@
.DS_Store
node_modules
/dist
# Vue language service / accidental transpile artifacts
src/**/*.vue.js

View File

@@ -88,6 +88,7 @@ export interface ExecutionItem {
timestamp: string;
content: string;
label: string;
type?: string;
}
export interface ExecutionFlowItem {

View File

@@ -129,10 +129,12 @@ export interface ModelModuleItem {
retryTimes: number;
retryQueueMaxSeconds: number;
autoCleanSeconds: number;
remark?: string;
headMsg?: string | Record<string, string>;
form?: ModelFormEntry[] | Record<string, { value: string }>;
requestMapping?: Record<string, unknown>;
requiredFields?: string[];
firstFrame?: string;
lastFrame?: string;
responseMapping?: Record<string, unknown>;
}
@@ -161,6 +163,9 @@ export interface CreateModelParams {
apiKey?: string;
form: ModelFormEntry[];
requestMapping?: Record<string, unknown>;
requiredFields?: string[];
firstFrame?: string;
lastFrame?: string;
responseMapping?: Record<string, unknown>;
responseBody?: Record<string, unknown>;
extendMapping?: Record<string, unknown>;
@@ -175,7 +180,6 @@ export interface CreateModelParams {
retryTimes?: number;
retryQueueMaxSeconds: number;
autoCleanSeconds: number;
remark?: string;
}
export interface ModelConfigTypeItem {

View File

@@ -444,10 +444,13 @@ const handleCreatePrivateModel = async () => {
isPrivate: builtInModel.isPrivate ?? 1,
enabled: builtInModel.enabled ?? 1,
isChatModel: builtInModel.isChatModel || 0,
isAsync: builtInModel.isAsync ?? 0,
callMode: builtInModel.callMode ?? builtInModel.isAsync ?? 0,
apiKey: apiKeyForm.apiKey,
form: formList,
requestMapping: (builtInModel.requestMapping as Record<string, unknown>) || {},
requiredFields: Array.isArray(builtInModel.requiredFields) ? builtInModel.requiredFields : [],
firstFrame: String(builtInModel.firstFrame || ''),
lastFrame: String(builtInModel.lastFrame || ''),
responseMapping: (builtInModel.responseMapping as Record<string, unknown>) || {},
responseBody: builtInModel.responseBody || {},
maxConcurrency: builtInModel.maxConcurrency || 10,
@@ -457,8 +460,6 @@ const handleCreatePrivateModel = async () => {
retryTimes: builtInModel.retryTimes || 3,
retryQueueMaxSeconds: builtInModel.retryQueueMaxSeconds || 60,
autoCleanSeconds: builtInModel.autoCleanSeconds || 300,
remark: builtInModel.remark || '',
extendMapping: fieldsToUnknownObject(
Object.entries(parseJsonObjectField(builtInModel.extendMapping)).map(([k, v]) => ({ key: k, value: String(v ?? '') }))
),
@@ -470,6 +471,10 @@ const handleCreatePrivateModel = async () => {
? (builtInModel.queryConfig as Record<string, unknown>)
: null
),
streamConfig:
builtInModel.streamConfig && typeof builtInModel.streamConfig === 'object' && !Array.isArray(builtInModel.streamConfig)
? (builtInModel.streamConfig as Record<string, unknown>)
: undefined,
};
const res: any = await addModelModule(createParams);

View File

@@ -1558,9 +1558,13 @@ const handleCreateChatModelFromBuiltIn = async () => {
isPrivate: builtInModel.isPrivate ?? 1,
enabled: builtInModel.enabled ?? 1,
isChatModel: 1, // 设置为会话模型
callMode: builtInModel.callMode ?? builtInModel.isAsync ?? 0,
apiKey: chatModelApiKeyForm.apiKey,
form: builtInModel.form || {},
requestMapping: builtInModel.requestMapping || {},
requiredFields: Array.isArray(builtInModel.requiredFields) ? builtInModel.requiredFields : [],
firstFrame: String(builtInModel.firstFrame || ''),
lastFrame: String(builtInModel.lastFrame || ''),
responseMapping: builtInModel.responseMapping || {},
responseBody: builtInModel.responseBody || {},
tokenMapping: builtInModel.tokenMapping || '',
@@ -1572,7 +1576,6 @@ const handleCreateChatModelFromBuiltIn = async () => {
retryTimes: builtInModel.retryTimes || 3,
retryQueueMaxSeconds: builtInModel.retryQueueMaxSeconds || 60,
autoCleanSeconds: builtInModel.autoCleanSeconds || 300,
remark: builtInModel.remark || '',
};
await addModelModule(createParams);
@@ -3799,10 +3802,13 @@ const cleanupReferencesToNode = (deletedNodeId: string) => {
inputSource: normalizedInputSource,
});
if (selectedElement.value?.id === node.id) {
selectedElement.value.properties = {
...props,
inputSource: normalizedInputSource,
if (selectedElement.value?.id === node.id && selectedElement.value) {
selectedElement.value = {
...selectedElement.value,
properties: {
...props,
inputSource: normalizedInputSource,
},
};
}
});

View File

@@ -23,13 +23,6 @@
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="运营商名称" prop="operatorName" required>
<el-select v-model="state.ruleForm.operatorName" placeholder="请选择运营商" clearable style="width: 100%">
<el-option v-for="item in operatorNameOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="模型服务地址" prop="baseUrl">
<el-input v-model="state.ruleForm.baseUrl" placeholder="请输入模型服务地址" clearable></el-input>
@@ -45,6 +38,9 @@
</el-select>
</el-form-item>
</el-col>
<el-col v-if="props.isSuperAdmin || state.ruleForm.isPrivate === 1" :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20 provider-section-col">
<div class="provider-section-title">服务商模型配置</div>
</el-col>
<el-col v-if="!props.isSuperAdmin" :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="访问类型" prop="isPrivate">
<el-radio-group v-model="state.ruleForm.isPrivate" @change="onIsPrivateChange">
@@ -53,6 +49,13 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="props.isSuperAdmin || state.ruleForm.isPrivate === 1" :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="运营商名称" prop="operatorName" required>
<el-select v-model="state.ruleForm.operatorName" placeholder="请选择运营商" clearable style="width: 100%">
<el-option v-for="item in operatorNameOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col v-if="!props.isSuperAdmin && state.ruleForm.isPrivate === 1" :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="API 密钥" prop="apiKey" required>
<el-input v-model="state.ruleForm.apiKey" type="password" show-password placeholder="请输入 API 密钥字符串" clearable></el-input>
@@ -103,11 +106,6 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="备注说明" prop="remark">
<el-input v-model="state.ruleForm.remark" type="textarea" placeholder="请输入备注说明" :rows="3" clearable></el-input>
</el-form-item>
</el-col>
</el-row>
<!-- 高级配置折叠区域 -->
@@ -405,12 +403,21 @@
</template>
</el-dialog>
<!-- 请求映射配置弹窗 -->
<el-dialog v-model="showRequestMappingDialog" title="配置请求映射" width="600px" :close-on-click-modal="false">
<el-dialog v-model="showRequestMappingDialog" title="配置请求映射" width="860px" :close-on-click-modal="false">
<div class="mapping-config-container">
<div v-for="(field, index) in state.requestMappingFields" :key="index" class="mapping-field-item">
<el-input v-model="field.key" placeholder="请输入字段名 (Key)" style="width: 40%" clearable></el-input>
<el-input v-model="field.key" placeholder="请输入字段名 (Key)" style="width: 18%" clearable @input="syncRequestSpecialFieldsOnKeyChange(index)"></el-input>
<span class="separator">=</span>
<el-input v-model="field.value" placeholder="请输入字段值 (Value)" style="width: 40%" clearable></el-input>
<el-input v-model="field.value" placeholder="请输入字段值 (Value)" style="width: 18%" clearable></el-input>
<el-button :type="field.required ? 'success' : 'primary'" :plain="!field.required" @click="toggleRequiredField(index)" size="small">
{{ field.required ? '✓ 必填字段' : '设置必填字段' }}
</el-button>
<el-button :type="field.isFirstFrame ? 'success' : 'primary'" :plain="!field.isFirstFrame" @click="setFirstFrameField(index)" size="small">
{{ field.isFirstFrame ? '✓ 首帧参数' : '设置首帧参数' }}
</el-button>
<el-button :type="field.isLastFrame ? 'success' : 'primary'" :plain="!field.isLastFrame" @click="setLastFrameField(index)" size="small">
{{ field.isLastFrame ? '✓ 尾帧参数' : '设置尾帧参数' }}
</el-button>
<el-button type="danger" link @click="removeRequestMappingField(index)">删除</el-button>
</div>
<el-button type="primary" link @click="addRequestMappingField" style="margin-top: 10px">+ 添加字段</el-button>
@@ -436,7 +443,7 @@
></el-input>
<span class="separator">=</span>
<el-input v-model="field.value" placeholder="请输入字段值 (Value)" style="width: 30%" clearable></el-input>
<el-button :type="field.isTokenField ? 'warning' : 'primary'" :plain="!field.isTokenField" @click="setTokenField(index)" size="small">
<el-button :type="field.isTokenField ? 'success' : 'primary'" :plain="!field.isTokenField" @click="setTokenField(index)" size="small">
{{ field.isTokenField ? '✓ 计费字段' : '设置计费字段' }}
</el-button>
<el-button :type="field.isMainBody ? 'success' : 'primary'" :plain="!field.isMainBody" @click="setMainBody(index)" size="small">
@@ -531,6 +538,22 @@ export interface FormField {
options?: FormFieldOption[]; // 选项列表
}
export interface KeyValueField {
key: string;
value: string;
}
export interface RequestMappingField extends KeyValueField {
required?: boolean;
isFirstFrame?: boolean;
isLastFrame?: boolean;
}
export interface ResponseMappingField extends KeyValueField {
isMainBody?: boolean;
isTokenField?: boolean;
}
const props = withDefaults(
defineProps<{
modelTypes?: ModelTypeOption[];
@@ -597,6 +620,9 @@ const state = reactive({
enabled: 1,
isChatModel: 0,
callMode: 0,
firstFrame: '',
lastFrame: '',
requiredFields: [] as string[],
maxConcurrency: 10,
queueLimit: 100,
timeoutSeconds: 30,
@@ -604,7 +630,6 @@ const state = reactive({
retryTimes: 3,
retryQueueMaxSeconds: 60,
autoCleanSeconds: 300,
remark: '',
extendMapping: '{}',
responseTokenField: '',
tokenConfig: '{}',
@@ -678,7 +703,22 @@ const state = reactive({
trigger: 'change',
},
],
operatorName: [{ required: true, message: '请选择运营商名称', trigger: 'change' }],
operatorName: [
{
validator: (_rule: unknown, value: unknown, callback: (e?: Error) => void) => {
if (!(props.isSuperAdmin || state.ruleForm.isPrivate === 1)) {
callback();
return;
}
if (!value || String(value).trim() === '') {
callback(new Error('请选择运营商名称'));
return;
}
callback();
},
trigger: 'change',
},
],
apiKey: [
{
validator: (_rule: unknown, value: unknown, callback: (e?: Error) => void) => {
@@ -713,6 +753,11 @@ const state = reactive({
callback(new Error('请求映射字段名不能为空'));
return;
}
const duplicatedSpecialSelection = state.requestMappingFields.some((x) => x.isFirstFrame && x.isLastFrame);
if (duplicatedSpecialSelection) {
callback(new Error('同一行不能同时设置为首帧和尾帧参数'));
return;
}
callback();
},
trigger: 'change',
@@ -777,12 +822,12 @@ const state = reactive({
detailLoading: false,
},
showAdvanced: false,
headers: [] as Array<{ key: string; value: string }>,
headers: [] as KeyValueField[],
formFields: [] as Array<FormField>,
requestMappingFields: [] as Array<{ key: string; value: string }>,
responseMappingFields: [] as Array<{ key: string; value: string; isMainBody?: boolean; isTokenField?: boolean }>,
extendMappingFields: [] as Array<{ key: string; value: string }>,
tokenConfigFields: [] as Array<{ key: string; value: string }>,
requestMappingFields: [] as RequestMappingField[],
responseMappingFields: [] as ResponseMappingField[],
extendMappingFields: [] as KeyValueField[],
tokenConfigFields: [] as KeyValueField[],
mainBodyIndex: -1, // 记录哪一行被设置为返回主体
});
@@ -806,7 +851,7 @@ const createEmptyFormField = (): FormField => {
};
// 将数组转换为对象
const fieldsToObject = (fields: Array<{ key: string; value: string }>) => {
const fieldsToObject = (fields: KeyValueField[]) => {
const obj: Record<string, string> = {};
fields.forEach((f) => {
if (f.key && f.key.trim()) {
@@ -820,7 +865,7 @@ const fieldsToObject = (fields: Array<{ key: string; value: string }>) => {
// 1. 旧格式:逗号分隔的"key1:value1,key2:value2"字符串
// 2. JSON字符串格式化为JSON对象的字符串
// 3. 新格式直接是Record<string, string>对象
const parseHeaders = (raw: unknown): Array<{ key: string; value: string }> => {
const parseHeaders = (raw: unknown): KeyValueField[] => {
if (!raw) return [];
// 如果是字符串先尝试解析为JSON如果失败则尝试解析为旧格式key:value,key2:value2
@@ -1051,11 +1096,78 @@ const confirmFormFields = () => {
};
// 请求映射字段操作
const addRequestMappingField = () => {
state.requestMappingFields.push({ key: '', value: '' });
state.requestMappingFields.push({ key: '', value: '', required: false, isFirstFrame: false, isLastFrame: false });
};
const removeRequestMappingField = (index: number) => {
const removed = state.requestMappingFields[index];
state.requestMappingFields.splice(index, 1);
if (removed?.required) {
state.ruleForm.requiredFields = state.ruleForm.requiredFields.filter((item) => item !== String(removed.key || '').trim());
}
if (removed?.isFirstFrame) {
state.ruleForm.firstFrame = '';
}
if (removed?.isLastFrame) {
state.ruleForm.lastFrame = '';
}
};
const toggleRequiredField = (index: number) => {
const row = state.requestMappingFields[index];
if (!row) return;
row.required = !row.required;
state.ruleForm.requiredFields = state.requestMappingFields
.filter((field) => field.required)
.map((field) => String(field.key || '').trim())
.filter(Boolean);
};
const setFirstFrameField = (index: number) => {
state.requestMappingFields.forEach((field, i) => {
field.isFirstFrame = i === index;
if (i === index) {
field.isLastFrame = false;
}
});
state.ruleForm.firstFrame = state.requestMappingFields[index]?.key?.trim?.() || '';
if (state.requestMappingFields[index]?.isLastFrame) {
state.ruleForm.lastFrame = '';
}
};
const setLastFrameField = (index: number) => {
state.requestMappingFields.forEach((field, i) => {
field.isLastFrame = i === index;
if (i === index) {
field.isFirstFrame = false;
}
});
state.ruleForm.lastFrame = state.requestMappingFields[index]?.key?.trim?.() || '';
if (state.requestMappingFields[index]?.isFirstFrame) {
state.ruleForm.firstFrame = '';
}
};
const syncRequestSpecialFieldsOnKeyChange = (index: number) => {
const row = state.requestMappingFields[index];
if (!row) return;
if (row.required) {
const nextKey = row.key?.trim?.() || '';
state.ruleForm.requiredFields = state.requestMappingFields
.filter((field) => field.required)
.map((field) => String(field.key || '').trim())
.filter(Boolean);
if (!nextKey) {
row.required = false;
}
}
if (row.isFirstFrame) {
state.ruleForm.firstFrame = row.key?.trim?.() || '';
}
if (row.isLastFrame) {
state.ruleForm.lastFrame = row.key?.trim?.() || '';
}
};
const confirmRequestMappingFields = () => {
@@ -1101,7 +1213,7 @@ const confirmResponseMappingFields = () => {
showResponseMappingDialog.value = false;
};
const ensureKeyValueRows = (rows: Array<{ key: string; value: string }>) => (rows.length ? rows : [{ key: '', value: '' }]);
const ensureKeyValueRows = <T extends KeyValueField>(rows: T[], createEmpty: () => T): T[] => (rows.length ? rows : [createEmpty()]);
// 解析旧格式的form数据兼容到新格式
const parseFormFieldsUnified = (raw: unknown): Array<FormField> => {
@@ -1182,6 +1294,9 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
enabled: Number(row.enabled ?? 1),
isChatModel: row.isChatModel !== undefined && row.isChatModel !== null ? Number(row.isChatModel) : 0,
callMode: row.callMode !== undefined && row.callMode !== null ? Number(row.callMode) : row.isAsync !== undefined && row.isAsync !== null ? Number(row.isAsync) : 0,
firstFrame: String(row.firstFrame || ''),
lastFrame: String(row.lastFrame || ''),
requiredFields: Array.isArray(row.requiredFields) ? row.requiredFields.map((item) => String(item || '').trim()).filter(Boolean) : [],
maxConcurrency: Number(row.maxConcurrency ?? 10),
queueLimit: Number(row.queueLimit ?? 100),
timeoutSeconds,
@@ -1189,17 +1304,33 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
retryTimes: Number(row.retryTimes ?? 3),
retryQueueMaxSeconds: Number(row.retryQueueMaxSeconds ?? 60),
autoCleanSeconds: Number(row.autoCleanSeconds ?? 300),
remark: String(row.remark || ''),
extendMapping: '{}',
responseTokenField: String(row.responseTokenField || ''),
tokenConfig: '{}',
};
state.headers = ensureKeyValueRows(parseHeaders(row.headMsg));
state.headers = ensureKeyValueRows(parseHeaders(row.headMsg), () => ({ key: '', value: '' }));
state.formFields = parseFormFieldsUnified(row.form);
state.requestMappingFields = ensureKeyValueRows(parseRequestMappingFields(row.requestMapping));
state.responseMappingFields = ensureKeyValueRows(parseResponseMappingFields(row.responseMapping));
state.extendMappingFields = ensureKeyValueRows(parseFieldsUnified(row.extendMapping));
state.tokenConfigFields = ensureKeyValueRows(parseFieldsUnified(row.tokenConfig));
state.requestMappingFields = ensureKeyValueRows(parseRequestMappingFields(row.requestMapping) as RequestMappingField[], () => ({
key: '',
value: '',
required: false,
isFirstFrame: false,
isLastFrame: false,
}));
state.requestMappingFields.forEach((field) => {
const fieldKey = String(field.key || '').trim();
field.required = fieldKey !== '' && state.ruleForm.requiredFields.includes(fieldKey);
field.isFirstFrame = fieldKey !== '' && fieldKey === String(row.firstFrame || '').trim();
field.isLastFrame = fieldKey !== '' && fieldKey === String(row.lastFrame || '').trim();
});
state.responseMappingFields = ensureKeyValueRows(parseResponseMappingFields(row.responseMapping) as ResponseMappingField[], () => ({
key: '',
value: '',
isMainBody: false,
isTokenField: false,
}));
state.extendMappingFields = ensureKeyValueRows(parseFieldsUnified(row.extendMapping), () => ({ key: '', value: '' }));
state.tokenConfigFields = ensureKeyValueRows(parseFieldsUnified(row.tokenConfig), () => ({ key: '', value: '' }));
// 根据 responseTokenField 字段设置计费字段标记(单选)
const tokenFieldKey = String(row.responseTokenField || '').trim();
@@ -1230,7 +1361,7 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
asyncQueryConfigForm.resultPath = String(qc.result_path || '');
asyncQueryConfigForm.statusPath = String(qc.status_path || '');
asyncQueryConfigForm.intervalSeconds = Number(qc.interval_seconds ?? 2) || 2;
asyncQueryConfigForm.statusValueFields = ensureKeyValueRows(objectToFields((qc.status_values as Record<string, unknown>) || {}));
asyncQueryConfigForm.statusValueFields = ensureKeyValueRows(objectToFields((qc.status_values as Record<string, unknown>) || {}), () => ({ key: '', value: '' }));
} else {
asyncQueryConfigForm.url = '';
asyncQueryConfigForm.method = 'POST';
@@ -1292,6 +1423,9 @@ const openDialog = async (type: string, row?: Record<string, unknown>) => {
enabled: 1,
isChatModel: 0,
callMode: 0,
firstFrame: '',
lastFrame: '',
requiredFields: [],
maxConcurrency: 10,
queueLimit: 100,
timeoutSeconds: 30,
@@ -1299,14 +1433,13 @@ const openDialog = async (type: string, row?: Record<string, unknown>) => {
retryTimes: 3,
retryQueueMaxSeconds: 60,
autoCleanSeconds: 300,
remark: '',
extendMapping: '{}',
responseTokenField: '',
tokenConfig: '{}',
};
state.headers = [{ key: '', value: '' }];
state.formFields = [createEmptyFormField()];
state.requestMappingFields = [{ key: '', value: '' }];
state.requestMappingFields = [{ key: '', value: '', required: false, isFirstFrame: false, isLastFrame: false }];
state.responseMappingFields = [{ key: '', value: '', isMainBody: false, isTokenField: false }];
state.mainBodyIndex = -1;
state.extendMappingFields = [{ key: '', value: '' }];
@@ -1363,6 +1496,10 @@ const onSubmit = () => {
// 过滤掉空键名的字段
const requestMapping = fieldsToObject(state.requestMappingFields.filter((f) => String(f.key || '').trim() !== ''));
const requiredFields = state.requestMappingFields
.filter((f) => Boolean(f.required) && String(f.key || '').trim() !== '')
.map((f) => String(f.key || '').trim());
state.ruleForm.requiredFields = requiredFields;
const responseMapping = fieldsToObject(state.responseMappingFields.filter((f) => String(f.key || '').trim() !== ''));
// 获取被设置为返回主体的字段 {key: value}
@@ -1419,6 +1556,9 @@ const onSubmit = () => {
apiKey: state.ruleForm.isPrivate === 1 ? String(state.ruleForm.apiKey ?? '').trim() : '',
form: processedFormFields,
requestMapping,
requiredFields,
firstFrame: String(state.ruleForm.firstFrame || '').trim(),
lastFrame: String(state.ruleForm.lastFrame || '').trim(),
responseMapping,
responseBody,
maxConcurrency: state.ruleForm.maxConcurrency,
@@ -1428,7 +1568,6 @@ const onSubmit = () => {
retryTimes: state.ruleForm.retryTimes,
retryQueueMaxSeconds: state.ruleForm.retryQueueMaxSeconds,
autoCleanSeconds: state.ruleForm.autoCleanSeconds,
remark: state.ruleForm.remark || '',
extendMapping: fieldsToUnknownObject(state.extendMappingFields.filter((f) => String(f.key || '').trim() !== '')),
responseTokenField,
tokenConfig: fieldsToUnknownObject(state.tokenConfigFields.filter((f) => String(f.key || '').trim() !== '')),
@@ -1482,6 +1621,17 @@ onMounted(() => {
width: 100%;
}
.provider-section-col {
margin-bottom: 0;
}
.provider-section-title {
font-size: 13px;
font-weight: 600;
color: #606266;
padding: 2px 0 4px;
}
.form-config-container {
max-height: 550px;
overflow-y: auto;

View File

@@ -281,10 +281,13 @@ const handleCreatePrivateModelAndSetChat = async () => {
isPrivate: builtInModel.isPrivate ?? 1,
enabled: builtInModel.enabled ?? 1,
isChatModel: 1, // 设置为会话模型
isAsync: builtInModel.isAsync ?? 0,
callMode: builtInModel.callMode ?? builtInModel.isAsync ?? 0,
apiKey: apiKeyForm.apiKey,
form: builtInModel.form || {},
requestMapping: builtInModel.requestMapping || {},
requiredFields: Array.isArray(builtInModel.requiredFields) ? builtInModel.requiredFields : [],
firstFrame: String(builtInModel.firstFrame || ''),
lastFrame: String(builtInModel.lastFrame || ''),
responseMapping: builtInModel.responseMapping || {},
responseBody: builtInModel.responseBody || {},
tokenMapping: builtInModel.tokenMapping || '',