配置模型相关
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="system-edit-module-container">
|
||||
<el-dialog :title="state.dialog.title" v-model="state.dialog.isShowDialog" width="900px">
|
||||
<el-form
|
||||
@@ -45,23 +45,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="queryResponseType">
|
||||
<el-select v-model="state.ruleForm.queryResponseType" placeholder="请选择响应类型" clearable style="width: 100%">
|
||||
<el-option v-for="item in queryResponseTypeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="state.ruleForm.queryResponseType === 'callback'" :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
|
||||
<el-form-item label="回调地址" prop="queryCallbackUrl">
|
||||
<el-input v-model="state.ruleForm.queryCallbackUrl" placeholder="请输入回调地址" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="state.ruleForm.queryResponseType === 'pull'" :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="主动拉取配置" prop="queryPullConfig">
|
||||
<el-button @click="showPullConfigDialog = true" style="width: 100%">配置主动拉取</el-button>
|
||||
</el-form-item>
|
||||
</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">
|
||||
@@ -84,11 +67,22 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="执行模式" prop="isAsync">
|
||||
<el-radio-group v-model="state.ruleForm.isAsync">
|
||||
<el-radio :label="0">同步</el-radio>
|
||||
<el-radio :label="1">异步</el-radio>
|
||||
</el-radio-group>
|
||||
<el-form-item label="执行模式" prop="callMode">
|
||||
<el-select v-model="state.ruleForm.callMode" placeholder="请选择调用模式" clearable style="width: 100%">
|
||||
<el-option label="同步" :value="0"></el-option>
|
||||
<el-option label="异步" :value="1"></el-option>
|
||||
<el-option label="流式" :value="2"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="state.ruleForm.callMode === 1" :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="查询/回调配置" prop="asyncQueryConfig">
|
||||
<el-button @click="showAsyncQueryConfigDialog = true" style="width: 100%">配置查询/回调</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="state.ruleForm.callMode === 2" :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="流式配置" prop="streamConfig">
|
||||
<el-button @click="showStreamConfigDialog = true" style="width: 100%">配置流式参数</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
@@ -97,7 +91,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="自定义字段" prop="form">
|
||||
<el-form-item label="自定义表单" prop="form">
|
||||
<el-button @click="showFormDialog = true" style="width: 100%"> 配置表单字段 ({{ state.formFields.length }}) </el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -202,6 +196,65 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查询/回调配置弹窗 -->
|
||||
<el-dialog v-model="showAsyncQueryConfigDialog" title="配置查询/回调" width="800px" :close-on-click-modal="false">
|
||||
<el-form label-width="140px">
|
||||
<el-form-item label="查询地址">
|
||||
<el-input v-model="asyncQueryConfigForm.url" placeholder="请输入查询地址" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="请求方式">
|
||||
<el-select v-model="asyncQueryConfigForm.method" style="width: 100%">
|
||||
<el-option label="GET" value="GET"></el-option>
|
||||
<el-option label="POST" value="POST"></el-option>
|
||||
<el-option label="PUT" value="PUT"></el-option>
|
||||
<el-option label="DELETE" value="DELETE"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务ID路径">
|
||||
<el-input v-model="asyncQueryConfigForm.taskId" placeholder="如 data.task_id" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="结果路径">
|
||||
<el-input v-model="asyncQueryConfigForm.resultPath" placeholder="如 data.audio_url" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态路径">
|
||||
<el-input v-model="asyncQueryConfigForm.statusPath" placeholder="如 data.task_status" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="轮询间隔(秒)">
|
||||
<el-input-number v-model="asyncQueryConfigForm.intervalSeconds" :min="1" :max="3600" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态值映射">
|
||||
<div class="mapping-config-container async-query-status-values">
|
||||
<div v-for="(field, index) in asyncQueryConfigForm.statusValueFields" :key="index" class="mapping-field-item">
|
||||
<el-input v-model="field.key" placeholder="状态名称,如 succeeded" style="width: 40%" clearable></el-input>
|
||||
<span class="separator">=</span>
|
||||
<el-input v-model="field.value" placeholder="状态值,按字符串提交" style="width: 40%" clearable></el-input>
|
||||
<el-button type="danger" link @click="removeAsyncQueryStatusValueField(index)">删除</el-button>
|
||||
</div>
|
||||
<el-button type="primary" link @click="addAsyncQueryStatusValueField" style="margin-top: 10px">+ 添加字段</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showAsyncQueryConfigDialog = false" size="default">取 消</el-button>
|
||||
<el-button type="primary" @click="showAsyncQueryConfigDialog = false" size="default">确 定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 流式配置弹窗 -->
|
||||
<el-dialog v-model="showStreamConfigDialog" title="配置流式参数" width="600px" :close-on-click-modal="false">
|
||||
<div class="stream-config-placeholder">
|
||||
流式配置表单内容待确定,当前将按空对象提交。
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showStreamConfigDialog = false" size="default">取 消</el-button>
|
||||
<el-button type="primary" @click="showStreamConfigDialog = false" size="default">确 定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 请求头配置弹窗 -->
|
||||
<el-dialog v-model="showHeaderDialog" title="配置请求头" width="600px" :close-on-click-modal="false">
|
||||
<div class="header-config-container">
|
||||
@@ -401,38 +454,6 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 主动拉取配置弹窗 -->
|
||||
<el-dialog v-model="showPullConfigDialog" title="配置主动拉取" width="800px" :close-on-click-modal="false">
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="请求方式">
|
||||
<el-select v-model="pullConfigForm.method" style="width: 100%">
|
||||
<el-option label="GET" value="GET"></el-option>
|
||||
<el-option label="POST" value="POST"></el-option>
|
||||
<el-option label="PUT" value="PUT"></el-option>
|
||||
<el-option label="DELETE" value="DELETE"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="拉取地址">
|
||||
<el-input v-model="pullConfigForm.url" placeholder="请输入拉取 URL" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="请求头(headers)">
|
||||
<el-button @click="showPullHeadersDialog = true" style="width: 100%">配置请求头 ({{ pullConfigForm.headersFields.length }})</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="请求体(body)">
|
||||
<el-button @click="showPullBodyDialog = true" style="width: 100%">配置请求体 ({{ pullConfigForm.bodyFields.length }})</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="响应映射(response)">
|
||||
<el-button @click="showPullResponseDialog = true" style="width: 100%">配置响应字段 ({{ pullConfigForm.responseFields.length }})</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showPullConfigDialog = false" size="default">取 消</el-button>
|
||||
<el-button type="primary" @click="showPullConfigDialog = false" size="default">确 定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 附加映射配置 -->
|
||||
<el-dialog v-model="showExtendMappingDialog" title="配置附加映射" width="600px" :close-on-click-modal="false">
|
||||
<div class="mapping-config-container">
|
||||
@@ -460,73 +481,9 @@
|
||||
</div>
|
||||
<el-button type="primary" link @click="state.tokenConfigFields.push({ key: '', value: '' })" style="margin-top: 10px">+ 添加字段</el-button>
|
||||
</div>
|
||||
<template #footer
|
||||
><span class="dialog-footer"><el-button @click="showTokenConfigDialog = false">确 定</el-button></span></template
|
||||
>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Pull headers -->
|
||||
<el-dialog v-model="showPullHeadersDialog" title="配置请求头(headers)" width="600px" :close-on-click-modal="false">
|
||||
<div class="mapping-config-container">
|
||||
<div v-for="(field, index) in pullConfigForm.headersFields" :key="index" class="mapping-field-item">
|
||||
<el-input v-model="field.key" placeholder="请输入字段名 (Key)" style="width: 40%" clearable></el-input>
|
||||
<span class="separator">=</span>
|
||||
<el-input v-model="field.value" placeholder="请输入字段值 (Value)" style="width: 40%" clearable></el-input>
|
||||
<el-button type="danger" link @click="pullConfigForm.headersFields.splice(index, 1)">删除</el-button>
|
||||
</div>
|
||||
<el-button type="primary" link @click="pullConfigForm.headersFields.push({ key: '', value: '' })" style="margin-top: 10px"
|
||||
>+ 添加字段</el-button
|
||||
>
|
||||
</div>
|
||||
<template #footer
|
||||
><span class="dialog-footer"><el-button @click="showPullHeadersDialog = false">确 定</el-button></span></template
|
||||
>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Pull body -->
|
||||
<el-dialog v-model="showPullBodyDialog" title="配置请求体(body)" width="600px" :close-on-click-modal="false">
|
||||
<div class="mapping-config-container">
|
||||
<div v-for="(field, index) in pullConfigForm.bodyFields" :key="index" class="mapping-field-item">
|
||||
<el-input v-model="field.key" placeholder="请输入字段路径 (如 task_id.id)" style="width: 40%" clearable></el-input>
|
||||
<span class="separator">=</span>
|
||||
<el-select v-model="field.value" placeholder="请输入或选择字段值" style="width: 40%" clearable filterable allow-create default-first-option>
|
||||
<el-option v-for="(item, idx) in responseMappingValueOptions" :key="idx" :label="item" :value="item"> </el-option>
|
||||
</el-select>
|
||||
<el-button type="danger" link @click="pullConfigForm.bodyFields.splice(index, 1)">删除</el-button>
|
||||
</div>
|
||||
<el-button type="primary" link @click="pullConfigForm.bodyFields.push({ key: '', value: '' })" style="margin-top: 10px">+ 添加字段</el-button>
|
||||
</div>
|
||||
<template #footer
|
||||
><span class="dialog-footer"><el-button @click="showPullBodyDialog = false">确 定</el-button></span></template
|
||||
>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Pull response -->
|
||||
<el-dialog v-model="showPullResponseDialog" title="配置响应映射(response)" width="600px" :close-on-click-modal="false">
|
||||
<div class="mapping-config-container">
|
||||
<div v-for="(field, index) in pullConfigForm.responseFields" :key="index" class="mapping-field-item">
|
||||
<el-input v-model="field.value" placeholder="请输入响应字段路径 (如 content.video_url)" style="width: 30%" clearable></el-input>
|
||||
<span class="separator">=</span>
|
||||
<el-button :type="field.isTokenField ? 'warning' : 'primary'" :plain="!field.isTokenField" @click="setPullTokenField(index)" size="small">
|
||||
{{ field.isTokenField ? '✓ 计费字段' : '设置计费字段' }}
|
||||
</el-button>
|
||||
<el-button :type="field.isMainBody ? 'success' : 'primary'" :plain="!field.isMainBody" @click="setPullMainBody(index)" size="small">
|
||||
{{ field.isMainBody ? '✓ 返回主体' : '设置返回主体' }}
|
||||
</el-button>
|
||||
<el-button type="danger" link @click="pullConfigForm.responseFields.splice(index, 1)">删除</el-button>
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="pullConfigForm.responseFields.push({ value: '', isTokenField: false, isMainBody: false })"
|
||||
style="margin-top: 10px"
|
||||
>
|
||||
+ 添加字段
|
||||
</el-button>
|
||||
</div>
|
||||
<template #footer
|
||||
><span class="dialog-footer"><el-button @click="showPullResponseDialog = false">确 定</el-button></span></template
|
||||
>
|
||||
<template #footer>
|
||||
<span class="dialog-footer"><el-button @click="showTokenConfigDialog = false">确 定</el-button></span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -555,23 +512,23 @@ export interface FormFieldOption {
|
||||
|
||||
// 定义自定义字段类型
|
||||
export interface FormField {
|
||||
label: string; // 字段描述
|
||||
key: string; // 字段名称
|
||||
type: 'string' | 'number' | 'select' | 'radio' | 'file'; // 字段类型
|
||||
defaultValue: string; // 默认值
|
||||
required: boolean; // 是否必填
|
||||
label: string; // 字段描述
|
||||
key: string; // 字段名称
|
||||
type: 'string' | 'number' | 'select' | 'radio' | 'file'; // 字段类型
|
||||
defaultValue: string; // 默认值
|
||||
required: boolean; // 是否必填
|
||||
// 字符串配置
|
||||
maxLength?: number; // 最大长度
|
||||
maxLength?: number; // 最大长度
|
||||
// 数字配置
|
||||
min?: number; // 最小值
|
||||
max?: number; // 最大值
|
||||
min?: number; // 最小值
|
||||
max?: number; // 最大值
|
||||
numberType?: 'any' | 'integer' | 'float' | 'positive-int' | 'positive-float' | 'negative-int' | 'negative-float'; // 数字子类型
|
||||
// 文件上传配置
|
||||
maxSize?: number; // 最大文件大小(MB)
|
||||
maxCount?: number; // 最大上传数量
|
||||
allowedTypes?: string; // 允许的文件格式,逗号分隔
|
||||
maxSize?: number; // 最大文件大小(MB)
|
||||
maxCount?: number; // 最大上传数量
|
||||
allowedTypes?: string; // 允许的文件格式,逗号分隔
|
||||
// 下拉框/单选框配置
|
||||
options?: FormFieldOption[]; // 选项列表
|
||||
options?: FormFieldOption[]; // 选项列表
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
@@ -586,9 +543,6 @@ const props = withDefaults(
|
||||
);
|
||||
|
||||
const modelTypeOptions = computed(() => props.modelTypes);
|
||||
const responseMappingValueOptions = computed(() => {
|
||||
return state.responseMappingFields.map((f) => String(f.value || '').trim()).filter((v) => v !== '');
|
||||
});
|
||||
|
||||
const operatorNameOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||
|
||||
@@ -612,34 +566,22 @@ const typeOptionValue = (id: number | string): number | string => {
|
||||
const editModuleFormRef = ref();
|
||||
const emit = defineEmits(['refresh']);
|
||||
const showHeaderDialog = ref(false);
|
||||
const showAsyncQueryConfigDialog = ref(false);
|
||||
const showStreamConfigDialog = ref(false);
|
||||
const showFormDialog = ref(false);
|
||||
const showRequestMappingDialog = ref(false);
|
||||
const showResponseMappingDialog = ref(false);
|
||||
const showPullConfigDialog = ref(false);
|
||||
const showExtendMappingDialog = ref(false);
|
||||
const showTokenConfigDialog = ref(false);
|
||||
const showPullHeadersDialog = ref(false);
|
||||
const showPullBodyDialog = ref(false);
|
||||
const showPullResponseDialog = ref(false);
|
||||
const pullConfigForm = reactive({
|
||||
method: 'GET',
|
||||
url: 'https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks',
|
||||
headersFields: [
|
||||
{ key: 'Content-Type', value: 'application/json' },
|
||||
{ key: 'Authorization', value: 'Bearer ' },
|
||||
] as Array<{ key: string; value: string }>,
|
||||
bodyFields: [{ key: 'task_id.id', value: '111' }] as Array<{ key: string; value: string }>,
|
||||
responseFields: [
|
||||
{ value: 'status', isTokenField: false, isMainBody: false },
|
||||
{ value: 'content.video_url', isTokenField: false, isMainBody: false },
|
||||
{ value: 'usage.completion_tokens', isTokenField: false, isMainBody: false },
|
||||
] as Array<{ value: string; isTokenField?: boolean; isMainBody?: boolean }>,
|
||||
const asyncQueryConfigForm = reactive({
|
||||
url: '',
|
||||
method: 'POST',
|
||||
taskId: '',
|
||||
resultPath: '',
|
||||
statusPath: '',
|
||||
intervalSeconds: 2,
|
||||
statusValueFields: [{ key: '', value: '' }] as Array<{ key: string; value: string }>,
|
||||
});
|
||||
const queryResponseTypeOptions = [
|
||||
{ label: '同步返回', value: 'sync' },
|
||||
{ label: '等候回调', value: 'callback' },
|
||||
{ label: '主动拉取', value: 'pull' },
|
||||
];
|
||||
|
||||
const state = reactive({
|
||||
ruleForm: {
|
||||
@@ -649,14 +591,12 @@ const state = reactive({
|
||||
operatorName: '',
|
||||
baseUrl: '',
|
||||
httpMethod: 'POST',
|
||||
queryResponseType: 'sync',
|
||||
queryCallbackUrl: '',
|
||||
headMsg: '',
|
||||
isPrivate: 0,
|
||||
apiKey: '',
|
||||
enabled: 1,
|
||||
isChatModel: 0,
|
||||
isAsync: 0, // 0-同步 1-异步,默认0
|
||||
callMode: 0,
|
||||
maxConcurrency: 10,
|
||||
queueLimit: 100,
|
||||
timeoutSeconds: 30,
|
||||
@@ -685,44 +625,52 @@ const state = reactive({
|
||||
],
|
||||
baseUrl: [{ required: true, message: '请输入模型服务地址', trigger: 'blur' }],
|
||||
httpMethod: [{ required: true, message: '请选择请求方式', trigger: 'change' }],
|
||||
queryResponseType: [{ required: true, message: '请选择响应类型', trigger: 'change' }],
|
||||
queryCallbackUrl: [
|
||||
{
|
||||
validator: (_rule: unknown, value: unknown, callback: (e?: Error) => void) => {
|
||||
if (state.ruleForm.queryResponseType !== 'callback') {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (!value || String(value).trim() === '') {
|
||||
callback(new Error('请选择等候回调时,回调地址必填'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
queryPullConfig: [
|
||||
callMode: [{ required: true, message: '请选择调用模式', trigger: 'change' }],
|
||||
asyncQueryConfig: [
|
||||
{
|
||||
validator: (_rule: unknown, _value: unknown, callback: (e?: Error) => void) => {
|
||||
if (state.ruleForm.queryResponseType !== 'pull') {
|
||||
if (Number(state.ruleForm.callMode) !== 1) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (!pullConfigForm.url || String(pullConfigForm.url).trim() === '') {
|
||||
callback(new Error('主动拉取时,请填写拉取地址'));
|
||||
if (!String(asyncQueryConfigForm.url || '').trim()) {
|
||||
callback(new Error('异步执行时,请填写查询地址'));
|
||||
return;
|
||||
}
|
||||
// 验证响应字段至少有一个有效字段
|
||||
const validResponseFields = pullConfigForm.responseFields.filter((f) => String(f.value || '').trim() !== '');
|
||||
if (validResponseFields.length === 0) {
|
||||
callback(new Error('主动拉取时,至少需要配置一个响应字段'));
|
||||
if (!String(asyncQueryConfigForm.method || '').trim()) {
|
||||
callback(new Error('异步执行时,请选择请求方式'));
|
||||
return;
|
||||
}
|
||||
// 验证是否设置了返回主体字段
|
||||
const hasMainBody = pullConfigForm.responseFields.some((f) => f.isMainBody && String(f.value || '').trim() !== '');
|
||||
if (!hasMainBody) {
|
||||
callback(new Error('主动拉取时,必须设置一个返回主体字段'));
|
||||
if (!String(asyncQueryConfigForm.taskId || '').trim()) {
|
||||
callback(new Error('异步执行时,请填写任务ID路径'));
|
||||
return;
|
||||
}
|
||||
if (!String(asyncQueryConfigForm.resultPath || '').trim()) {
|
||||
callback(new Error('异步执行时,请填写结果路径'));
|
||||
return;
|
||||
}
|
||||
if (!String(asyncQueryConfigForm.statusPath || '').trim()) {
|
||||
callback(new Error('异步执行时,请填写状态路径'));
|
||||
return;
|
||||
}
|
||||
if (!asyncQueryConfigForm.intervalSeconds || Number(asyncQueryConfigForm.intervalSeconds) <= 0) {
|
||||
callback(new Error('异步执行时,请填写有效的轮询间隔'));
|
||||
return;
|
||||
}
|
||||
const validStatusValues = asyncQueryConfigForm.statusValueFields.filter(
|
||||
(item) => String(item.key || '').trim() !== '' && String(item.value || '').trim() !== ''
|
||||
);
|
||||
if (validStatusValues.length === 0) {
|
||||
callback(new Error('异步执行时,请至少配置一个状态值映射'));
|
||||
return;
|
||||
}
|
||||
const hasInvalidStatusValue = asyncQueryConfigForm.statusValueFields.some(
|
||||
(item) =>
|
||||
(String(item.key || '').trim() === '' && String(item.value || '').trim() !== '') ||
|
||||
(String(item.key || '').trim() !== '' && String(item.value || '').trim() === '')
|
||||
);
|
||||
if (hasInvalidStatusValue) {
|
||||
callback(new Error('状态值映射的键和值都必须完整填写'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
@@ -1012,77 +960,41 @@ const fieldsToUnknownObject = (fields: Array<{ key: string; value: string }>) =>
|
||||
return obj;
|
||||
};
|
||||
|
||||
const fieldsToNestedObject = (fields: Array<{ key: string; value: string }>) => {
|
||||
const root: Record<string, unknown> = {};
|
||||
fields.forEach((f) => {
|
||||
const path = String(f.key || '').trim();
|
||||
if (!path) return;
|
||||
const parts = path
|
||||
.split('.')
|
||||
.map((p) => p.trim())
|
||||
.filter(Boolean);
|
||||
if (!parts.length) return;
|
||||
let cur: Record<string, unknown> = root;
|
||||
parts.forEach((part, idx) => {
|
||||
if (idx === parts.length - 1) {
|
||||
cur[part] = String(f.value ?? '');
|
||||
return;
|
||||
}
|
||||
if (!cur[part] || typeof cur[part] !== 'object' || Array.isArray(cur[part])) {
|
||||
cur[part] = {};
|
||||
}
|
||||
cur = cur[part] as Record<string, unknown>;
|
||||
});
|
||||
const buildAsyncQueryConfig = () => {
|
||||
const statusValues: Record<string, string> = {};
|
||||
asyncQueryConfigForm.statusValueFields.forEach((item) => {
|
||||
const key = String(item.key || '').trim();
|
||||
if (!key) return;
|
||||
statusValues[key] = String(item.value ?? '');
|
||||
});
|
||||
return root;
|
||||
return {
|
||||
url: String(asyncQueryConfigForm.url || '').trim(),
|
||||
method: String(asyncQueryConfigForm.method || 'POST').trim() || 'POST',
|
||||
task_id: String(asyncQueryConfigForm.taskId || '').trim(),
|
||||
result_path: String(asyncQueryConfigForm.resultPath || '').trim(),
|
||||
status_path: String(asyncQueryConfigForm.statusPath || '').trim(),
|
||||
status_values: statusValues,
|
||||
interval_seconds: Number(asyncQueryConfigForm.intervalSeconds || 0),
|
||||
};
|
||||
};
|
||||
|
||||
const addAsyncQueryStatusValueField = () => {
|
||||
asyncQueryConfigForm.statusValueFields.push({ key: '', value: '' });
|
||||
};
|
||||
|
||||
const removeAsyncQueryStatusValueField = (index: number) => {
|
||||
asyncQueryConfigForm.statusValueFields.splice(index, 1);
|
||||
};
|
||||
|
||||
const objectToFields = (obj: Record<string, unknown>) => {
|
||||
return Object.entries(obj).map(([key, value]) => ({ key, value: String(value ?? '') }));
|
||||
};
|
||||
|
||||
const nestedObjectToFields = (obj: Record<string, unknown>, prefix = ''): Array<{ key: string; value: string }> => {
|
||||
const rows: Array<{ key: string; value: string }> = [];
|
||||
Object.entries(obj || {}).forEach(([key, value]) => {
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
rows.push(...nestedObjectToFields(value as Record<string, unknown>, fullKey));
|
||||
return;
|
||||
}
|
||||
rows.push({ key: fullKey, value: String(value ?? '') });
|
||||
});
|
||||
return rows;
|
||||
};
|
||||
|
||||
const buildQueryConfig = () => {
|
||||
const responseType = state.ruleForm.queryResponseType || 'sync';
|
||||
if (responseType === 'callback') {
|
||||
return {
|
||||
responseType: 'callback',
|
||||
callbackUrl: String(state.ruleForm.queryCallbackUrl || '').trim(),
|
||||
};
|
||||
if (Number(state.ruleForm.callMode) === 1) {
|
||||
return buildAsyncQueryConfig();
|
||||
}
|
||||
if (responseType === 'pull') {
|
||||
return {
|
||||
responseType: 'pull',
|
||||
callbackUrl: '',
|
||||
method: pullConfigForm.method || 'GET',
|
||||
url: pullConfigForm.url || '',
|
||||
headers: fieldsToUnknownObject(pullConfigForm.headersFields),
|
||||
body: fieldsToNestedObject(pullConfigForm.bodyFields),
|
||||
response: pullConfigForm.responseFields
|
||||
.map((row) => ({
|
||||
value: String(row.value || '').trim(),
|
||||
isTokenField: Boolean(row.isTokenField),
|
||||
isMainBody: Boolean(row.isMainBody),
|
||||
}))
|
||||
.filter((row) => row.value !== ''),
|
||||
};
|
||||
}
|
||||
return {
|
||||
responseType: 'sync',
|
||||
callbackUrl: '',
|
||||
};
|
||||
return {};
|
||||
};
|
||||
|
||||
// 添加请求头
|
||||
@@ -1188,17 +1100,6 @@ const setMainBody = (index: number) => {
|
||||
const confirmResponseMappingFields = () => {
|
||||
showResponseMappingDialog.value = false;
|
||||
};
|
||||
const setPullTokenField = (index: number) => {
|
||||
pullConfigForm.responseFields.forEach((field, i) => {
|
||||
field.isTokenField = i === index;
|
||||
});
|
||||
};
|
||||
|
||||
const setPullMainBody = (index: number) => {
|
||||
pullConfigForm.responseFields.forEach((field, i) => {
|
||||
field.isMainBody = i === index;
|
||||
});
|
||||
};
|
||||
|
||||
const ensureKeyValueRows = (rows: Array<{ key: string; value: string }>) => (rows.length ? rows : [{ key: '', value: '' }]);
|
||||
|
||||
@@ -1210,7 +1111,7 @@ const parseFormFieldsUnified = (raw: unknown): Array<FormField> => {
|
||||
if (Array.isArray(raw)) {
|
||||
if (raw.length === 0) return [createEmptyFormField()];
|
||||
// 确保新增字段有默认值
|
||||
return (raw as any[]).map(item => {
|
||||
return (raw as any[]).map((item) => {
|
||||
const field = createEmptyFormField();
|
||||
return { ...field, ...item };
|
||||
}) as FormField[];
|
||||
@@ -1275,14 +1176,12 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
|
||||
operatorName: String(row.operatorName ?? ''),
|
||||
baseUrl: String(row.baseUrl ?? ''),
|
||||
httpMethod: String(row.httpMethod || 'POST'),
|
||||
queryResponseType: String((row.queryConfig as Record<string, unknown>)?.responseType || 'sync'),
|
||||
queryCallbackUrl: String((row.queryConfig as Record<string, unknown>)?.callbackUrl || ''),
|
||||
headMsg: ruleFormHeadMsg,
|
||||
isPrivate,
|
||||
apiKey: isPrivate === 1 ? String(row.apiKey ?? '') : '',
|
||||
enabled: Number(row.enabled ?? 1),
|
||||
isChatModel: row.isChatModel !== undefined && row.isChatModel !== null ? Number(row.isChatModel) : 0,
|
||||
isAsync: row.isAsync !== undefined && row.isAsync !== null ? Number(row.isAsync) : 0,
|
||||
callMode: row.callMode !== undefined && row.callMode !== null ? Number(row.callMode) : row.isAsync !== undefined && row.isAsync !== null ? Number(row.isAsync) : 0,
|
||||
maxConcurrency: Number(row.maxConcurrency ?? 10),
|
||||
queueLimit: Number(row.queueLimit ?? 100),
|
||||
timeoutSeconds,
|
||||
@@ -1325,27 +1224,21 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
|
||||
|
||||
if (row.queryConfig && typeof row.queryConfig === 'object' && !Array.isArray(row.queryConfig)) {
|
||||
const qc = row.queryConfig as Record<string, unknown>;
|
||||
pullConfigForm.method = String(qc.method || 'GET');
|
||||
pullConfigForm.url = String(qc.url || 'https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks');
|
||||
pullConfigForm.headersFields = ensureKeyValueRows(objectToFields((qc.headers as Record<string, unknown>) || {}));
|
||||
pullConfigForm.bodyFields = ensureKeyValueRows(nestedObjectToFields((qc.body as Record<string, unknown>) || {}));
|
||||
pullConfigForm.responseFields = ((qc.response as unknown[]) || []).map((item) => {
|
||||
if (typeof item === 'string') {
|
||||
return { value: item, isTokenField: false, isMainBody: false };
|
||||
}
|
||||
const row = item as Record<string, unknown>;
|
||||
return {
|
||||
value: String(row.value ?? ''),
|
||||
isTokenField: Boolean(row.isTokenField),
|
||||
isMainBody: Boolean(row.isMainBody),
|
||||
};
|
||||
});
|
||||
asyncQueryConfigForm.url = String(qc.url || '');
|
||||
asyncQueryConfigForm.method = String(qc.method || 'POST') || 'POST';
|
||||
asyncQueryConfigForm.taskId = String(qc.task_id || '');
|
||||
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>) || {}));
|
||||
} else {
|
||||
pullConfigForm.method = 'GET';
|
||||
pullConfigForm.url = '';
|
||||
pullConfigForm.headersFields = [{ key: '', value: '' }];
|
||||
pullConfigForm.bodyFields = [{ key: '', value: '' }];
|
||||
pullConfigForm.responseFields = [{ value: '', isTokenField: false, isMainBody: false }];
|
||||
asyncQueryConfigForm.url = '';
|
||||
asyncQueryConfigForm.method = 'POST';
|
||||
asyncQueryConfigForm.taskId = '';
|
||||
asyncQueryConfigForm.resultPath = '';
|
||||
asyncQueryConfigForm.statusPath = '';
|
||||
asyncQueryConfigForm.intervalSeconds = 2;
|
||||
asyncQueryConfigForm.statusValueFields = [{ key: '', value: '' }];
|
||||
}
|
||||
};
|
||||
// 打开弹窗(编辑时会请求 /model/getModel 详情)
|
||||
@@ -1393,14 +1286,12 @@ const openDialog = async (type: string, row?: Record<string, unknown>) => {
|
||||
operatorName: '',
|
||||
baseUrl: '',
|
||||
httpMethod: 'POST',
|
||||
queryResponseType: 'sync',
|
||||
queryCallbackUrl: '',
|
||||
headMsg: '',
|
||||
isPrivate: props.isSuperAdmin ? 1 : 0,
|
||||
apiKey: '',
|
||||
enabled: 1,
|
||||
isChatModel: 0,
|
||||
isAsync: 0,
|
||||
callMode: 0,
|
||||
maxConcurrency: 10,
|
||||
queueLimit: 100,
|
||||
timeoutSeconds: 30,
|
||||
@@ -1420,11 +1311,13 @@ const openDialog = async (type: string, row?: Record<string, unknown>) => {
|
||||
state.mainBodyIndex = -1;
|
||||
state.extendMappingFields = [{ key: '', value: '' }];
|
||||
state.tokenConfigFields = [{ key: '', value: '' }];
|
||||
pullConfigForm.method = 'GET';
|
||||
pullConfigForm.url = '';
|
||||
pullConfigForm.headersFields = [{ key: '', value: '' }];
|
||||
pullConfigForm.bodyFields = [{ key: '', value: '' }];
|
||||
pullConfigForm.responseFields = [{ value: '', isTokenField: false, isMainBody: false }];
|
||||
asyncQueryConfigForm.url = '';
|
||||
asyncQueryConfigForm.method = 'POST';
|
||||
asyncQueryConfigForm.taskId = '';
|
||||
asyncQueryConfigForm.resultPath = '';
|
||||
asyncQueryConfigForm.statusPath = '';
|
||||
asyncQueryConfigForm.intervalSeconds = 2;
|
||||
asyncQueryConfigForm.statusValueFields = [{ key: '', value: '' }];
|
||||
state.dialog.title = '新增模型配置';
|
||||
state.dialog.submitTxt = '新 增';
|
||||
}
|
||||
@@ -1450,8 +1343,8 @@ const onSubmit = () => {
|
||||
state.dialog.loading = true;
|
||||
try {
|
||||
// 触发所有自定义字段的验证
|
||||
if (state.ruleForm.queryResponseType === 'pull') {
|
||||
await editModuleFormRef.value?.validateField?.('queryPullConfig');
|
||||
if (Number(state.ruleForm.callMode) === 1) {
|
||||
await editModuleFormRef.value?.validateField?.('asyncQueryConfig');
|
||||
}
|
||||
|
||||
// 验证响应映射(如果有配置)
|
||||
@@ -1497,9 +1390,10 @@ const onSubmit = () => {
|
||||
maxSize: f.type === 'file' && f.maxSize ? f.maxSize : undefined,
|
||||
maxCount: f.type === 'file' && f.maxCount ? f.maxCount : undefined,
|
||||
allowedTypes: f.type === 'file' && f.allowedTypes ? f.allowedTypes.trim() : undefined,
|
||||
options: (f.type === 'select' || f.type === 'radio') && f.options
|
||||
? f.options.filter(opt => String(opt.label || '').trim() !== '' || String(opt.value || '').trim() !== '')
|
||||
: undefined,
|
||||
options:
|
||||
(f.type === 'select' || f.type === 'radio') && f.options
|
||||
? f.options.filter((opt) => String(opt.label || '').trim() !== '' || String(opt.value || '').trim() !== '')
|
||||
: undefined,
|
||||
})) as unknown as ModelFormEntry[];
|
||||
|
||||
// 将headers转换为JSON对象格式
|
||||
@@ -1520,7 +1414,7 @@ const onSubmit = () => {
|
||||
isPrivate: state.ruleForm.isPrivate,
|
||||
enabled: state.ruleForm.enabled,
|
||||
isChatModel: state.ruleForm.isChatModel,
|
||||
isAsync: state.ruleForm.isAsync,
|
||||
callMode: state.ruleForm.callMode,
|
||||
// 确保 API 密钥只在 isPrivate=1 时提交
|
||||
apiKey: state.ruleForm.isPrivate === 1 ? String(state.ruleForm.apiKey ?? '').trim() : '',
|
||||
form: processedFormFields,
|
||||
@@ -1539,6 +1433,7 @@ const onSubmit = () => {
|
||||
responseTokenField,
|
||||
tokenConfig: fieldsToUnknownObject(state.tokenConfigFields.filter((f) => String(f.key || '').trim() !== '')),
|
||||
queryConfig: buildQueryConfig(),
|
||||
streamConfig: Number(state.ruleForm.callMode) === 2 ? {} : undefined,
|
||||
};
|
||||
|
||||
if (state.dialog.type === 'edit') {
|
||||
@@ -1583,6 +1478,10 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.async-query-status-values {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-config-container {
|
||||
max-height: 550px;
|
||||
overflow-y: auto;
|
||||
|
||||
Reference in New Issue
Block a user