优化模型管理的添加表单,完善数据源
This commit is contained in:
+2
-2
@@ -3,6 +3,6 @@ ENV = 'development'
|
||||
|
||||
# 统一后端服务地址前缀(网关服务名:admin-go)
|
||||
# 开发环境走本地代理,避免 CORS
|
||||
# VITE_API_URL = 'http://116.204.74.41:8000'
|
||||
VITE_API_URL = 'http://192.168.3.30:8000'
|
||||
VITE_API_URL = 'http://116.204.74.41:8000'
|
||||
# VITE_API_URL = 'http://192.168.3.30:8000'
|
||||
# VITE_API_URL = 'http://192.168.3.135:8000'
|
||||
@@ -98,6 +98,18 @@ This is the **GFast UI** project (`gfast-ui`), a Vue 3 admin management system b
|
||||
|
||||
These rules capture long-term repository preferences confirmed by the user and should be applied in future sessions unless the user explicitly overrides them.
|
||||
|
||||
### 最高优先级规则(覆盖以下所有规则)
|
||||
|
||||
以下规则优先级高于本文件任何其他规则:
|
||||
|
||||
1. **修改前必须确认**:任何代码修改前必须先向用户确认,用户明确同意后才能执行
|
||||
2. **反常问题必须上报**:遇到 Edit 反复失败、渲染异常、非预期行为等反常问题,必须立即向用户报告问题原因、可行方案和推荐方向,由用户选择后再继续
|
||||
3. **不确定必须询问**:不确定、不了解、不清楚的事情一律先问用户,不自行假设或绕路
|
||||
4. **改后影响检查**:代码修改完成后,必须逐一检查对其他代码的影响(渲染条件、接口字段、样式覆盖、其他引用处)
|
||||
5. **改动评分与总结**:每次修改完成后,对本次改动按 1-10 分打分,并附上总结和改进建议
|
||||
6. **中文沟通**:所有与用户的沟通、说明、思考展示必须使用中文
|
||||
7. **操作后清理**:每次文件操作(拆分、替换、脚本执行等)完成后,必须检查并清理产生的临时文件,不得遗留在项目目录中
|
||||
|
||||
### Pagination Rules
|
||||
|
||||
- Prefer the project global `pagination` component by default.
|
||||
|
||||
@@ -54,9 +54,12 @@ export interface ModelManageItem {
|
||||
requestHeadMapping: Record<string, unknown>;
|
||||
requestBodyMapping: Record<string, unknown>;
|
||||
responseMapping: Record<string, unknown>;
|
||||
responseBodyMapping: string[];
|
||||
tokenMapping?: TokenMapping;
|
||||
asyncTaskMapping?: AsyncTaskMapping;
|
||||
lastFrame?: string;
|
||||
maxDuration: number;
|
||||
tokenPredictPriceUnit: string;
|
||||
// 以下旧字段兼容
|
||||
httpMethod?: string;
|
||||
systemModel?: boolean;
|
||||
@@ -164,9 +167,12 @@ export interface CreateModelManageParams {
|
||||
requestHeadMapping?: Record<string, unknown>;
|
||||
requestBodyMapping?: Record<string, unknown>;
|
||||
responseMapping?: Record<string, unknown>;
|
||||
responseBodyMapping?: string[];
|
||||
tokenMapping?: TokenMapping;
|
||||
asyncTaskMapping?: AsyncTaskMapping;
|
||||
lastFrame?: string;
|
||||
maxDuration?: number;
|
||||
tokenPredictPriceUnit?: string;
|
||||
}
|
||||
|
||||
/** 更新模型配置参数 */
|
||||
|
||||
@@ -94,7 +94,13 @@
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="Token 预估价格" prop="tokenPredictPrice">
|
||||
<el-input-number v-model="formData.tokenPredictPrice" :min="0" :precision="4" :step="0.0001" style="width: 100%" />
|
||||
<el-input-number v-model="formData.tokenPredictPrice" :min="0" :precision="4" :step="0.0001" style="width: calc(100% - 148px); margin-right: 8px" />
|
||||
<el-select v-model="formData.tokenPredictPriceUnit" placeholder="单位" clearable style="width: 140px">
|
||||
<el-option label="秒" value="second" />
|
||||
<el-option label="百万 Token" value="per_million_tokens" />
|
||||
<el-option label="千 Token" value="per_kilo_tokens" />
|
||||
<el-option label="字数" value="per_word" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -122,49 +128,30 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8" class="mb20" v-if="formData.responseType !== 2">
|
||||
<el-form-item label="响应主体映射">
|
||||
<el-select
|
||||
v-model="formData.responseBodyMapping"
|
||||
multiple
|
||||
:disabled="!hasMappingConfig"
|
||||
:placeholder="mappingPlaceholder"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in tokenMappingOptions"
|
||||
:key="item.path"
|
||||
:label="`${item.path} — ${item.label}`"
|
||||
:value="item.path"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- ========== Token 映射配置 ========== -->
|
||||
<el-divider content-position="left">Token 映射配置</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8" class="mb20">
|
||||
<el-form-item label="输入 Token 路径">
|
||||
<el-select v-model="formData.tokenMapping.promptTokens" :disabled="!hasMappingConfig" :placeholder="mappingPlaceholder" clearable style="width: 100%">
|
||||
<el-option v-for="item in tokenMappingOptions" :key="item.path" :label="`${item.path} — ${item.label}`" :value="item.path" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8" class="mb20">
|
||||
<el-form-item label="输出 Token 路径">
|
||||
<el-select v-model="formData.tokenMapping.completionTokens" :disabled="!hasMappingConfig" :placeholder="mappingPlaceholder" clearable style="width: 100%">
|
||||
<el-option v-for="item in tokenMappingOptions" :key="item.path" :label="`${item.path} — ${item.label}`" :value="item.path" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8" class="mb20">
|
||||
<el-form-item label="总 Token 路径">
|
||||
<el-select v-model="formData.tokenMapping.totalTokens" :disabled="!hasMappingConfig" :placeholder="mappingPlaceholder" clearable style="width: 100%">
|
||||
<el-option v-for="item in tokenMappingOptions" :key="item.path" :label="`${item.path} — ${item.label}`" :value="item.path" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- ========== 异步任务配置 ========== -->
|
||||
<!-- ========== 异步专属 ========== -->
|
||||
<template v-if="formData.responseType === 2">
|
||||
<el-divider content-position="left">异步任务配置</el-divider>
|
||||
<el-divider content-position="left">异步映射配置</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="回调 URL">
|
||||
<el-input v-model="formData.asyncTaskMapping.url" placeholder="请输入异步回调地址" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
|
||||
<el-form-item label="回调请求头映射">
|
||||
<KeyValueEditor v-model="formData.asyncTaskMapping.requestHeadMapping" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="回调响应映射">
|
||||
<el-button @click="openMappingEditor('asyncResponseMapping')" style="width: 100%">
|
||||
@@ -172,17 +159,53 @@
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="回调 URL">
|
||||
<el-input v-model="formData.asyncTaskMapping.url" placeholder="请输入异步回调地址" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
|
||||
<el-form-item label="回调请求头映射">
|
||||
<KeyValueEditor v-model="formData.asyncTaskMapping.requestHeadMapping" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8" class="mb20">
|
||||
<el-form-item label="响应主体映射">
|
||||
<el-select
|
||||
v-model="formData.responseBodyMapping"
|
||||
multiple
|
||||
:disabled="!hasMappingConfig"
|
||||
:placeholder="mappingPlaceholder"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in tokenMappingOptions"
|
||||
:key="item.path"
|
||||
:label="`${item.path} — ${item.label}`"
|
||||
:value="item.path"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-divider content-position="left" class="sub-divider">任务状态字段路径</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="Task ID 路径">
|
||||
<el-input v-model="formData.asyncTaskMapping.taskId" placeholder="如 data.task_id" clearable />
|
||||
<el-select v-model="formData.asyncTaskMapping.taskId" :disabled="!hasTaskIdConfig" :placeholder="hasTaskIdConfig ? '请选择 Task ID 路径' : '请先配置响应映射'" clearable style="width: 100%">
|
||||
<el-option v-for="item in taskIdOptions" :key="item.path" :label="`${item.path} — ${item.label}`" :value="item.path" />
|
||||
</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="任务状态路径">
|
||||
<el-input v-model="formData.asyncTaskMapping.taskStatus" placeholder="如 data.status" clearable />
|
||||
<el-select v-model="formData.asyncTaskMapping.taskStatus" :disabled="!hasTaskStatusConfig" :placeholder="hasTaskStatusConfig ? '请选择任务状态路径' : '请先配置回调响应映射'" clearable style="width: 100%">
|
||||
<el-option v-for="item in taskStatusOptions" :key="item.path" :label="`${item.path} — ${item.label}`" :value="item.path" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -222,6 +245,31 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-divider content-position="left">Token 映射配置</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8" class="mb20">
|
||||
<el-form-item label="输入 Token 路径">
|
||||
<el-select v-model="formData.tokenMapping.promptTokens" :disabled="!hasMappingConfig" :placeholder="mappingPlaceholder" clearable style="width: 100%">
|
||||
<el-option v-for="item in tokenMappingOptions" :key="item.path" :label="`${item.path} — ${item.label}`" :value="item.path" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8" class="mb20">
|
||||
<el-form-item label="输出 Token 路径">
|
||||
<el-select v-model="formData.tokenMapping.completionTokens" :disabled="!hasMappingConfig" :placeholder="mappingPlaceholder" clearable style="width: 100%">
|
||||
<el-option v-for="item in tokenMappingOptions" :key="item.path" :label="`${item.path} — ${item.label}`" :value="item.path" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8" class="mb20">
|
||||
<el-form-item label="总 Token 路径">
|
||||
<el-select v-model="formData.tokenMapping.totalTokens" :disabled="!hasMappingConfig" :placeholder="mappingPlaceholder" clearable style="width: 100%">
|
||||
<el-option v-for="item in tokenMappingOptions" :key="item.path" :label="`${item.path} — ${item.label}`" :value="item.path" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- ========== 视频配置 ========== -->
|
||||
<template v-if="isVideoModel">
|
||||
<el-divider content-position="left">视频配置</el-divider>
|
||||
@@ -231,6 +279,11 @@
|
||||
<el-input v-model="formData.lastFrame" placeholder="视频的尾帧图像 URL" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="最大视频时长(秒)">
|
||||
<el-input-number v-model="formData.maxDuration" :min="0" :max="999999" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-form>
|
||||
@@ -278,7 +331,6 @@ import {
|
||||
updateModelManage,
|
||||
getModelManage,
|
||||
getModelSupplierList,
|
||||
type ModelManageItem,
|
||||
type ModelTypeTreeNode,
|
||||
type ModelSupplierItem,
|
||||
type TokenMapping,
|
||||
@@ -348,12 +400,15 @@ const formData = reactive({
|
||||
requestHeadMapping: {} as Record<string, unknown>,
|
||||
requestBodyMapping: {} as Record<string, unknown>,
|
||||
responseMapping: {} as Record<string, unknown>,
|
||||
responseBodyMapping: [] as string[],
|
||||
// Token 映射
|
||||
tokenMapping: { ...defaultTokenMapping },
|
||||
// 异步任务映射
|
||||
asyncTaskMapping: { ...defaultAsyncTaskMapping },
|
||||
// 视频
|
||||
lastFrame: '',
|
||||
maxDuration: 0,
|
||||
tokenPredictPriceUnit: '',
|
||||
});
|
||||
|
||||
/** 获取选中模型类型路径上的所有标签 */
|
||||
@@ -434,9 +489,28 @@ const tokenMappingOptions = computed(() => {
|
||||
const hasMappingConfig = computed(() => tokenMappingOptions.value.length > 0);
|
||||
|
||||
/** 下拉占位文本 */
|
||||
const mappingPlaceholder = computed(() =>
|
||||
hasMappingConfig.value ? '请选择' : '请先配置响应映射'
|
||||
);
|
||||
const mappingPlaceholder = computed(() => {
|
||||
if (hasMappingConfig.value) return '请选择';
|
||||
return formData.responseType === 2 ? '请先配置回调响应映射' : '请先配置响应映射';
|
||||
});
|
||||
|
||||
/** Task ID 路径下拉选项(来源响应映射) */
|
||||
const taskIdOptions = computed(() => {
|
||||
const source = formData.responseMapping;
|
||||
return source ? flattenWrappedFields(source) : [];
|
||||
});
|
||||
|
||||
/** 是否有 Task ID 映射配置 */
|
||||
const hasTaskIdConfig = computed(() => taskIdOptions.value.length > 0);
|
||||
|
||||
/** 任务状态路径下拉选项(来源回调响应映射) */
|
||||
const taskStatusOptions = computed(() => {
|
||||
const source = formData.asyncTaskMapping.responseMapping;
|
||||
return source ? flattenWrappedFields(source) : [];
|
||||
});
|
||||
|
||||
/** 是否有任务状态映射配置 */
|
||||
const hasTaskStatusConfig = computed(() => taskStatusOptions.value.length > 0);
|
||||
|
||||
/** 切换返回类型时清空 token mapping(初始加载时跳过) */
|
||||
let _skipTokenClear = false;
|
||||
@@ -548,9 +622,12 @@ const resetForm = () => {
|
||||
formData.requestHeadMapping = {};
|
||||
formData.requestBodyMapping = {};
|
||||
formData.responseMapping = {};
|
||||
formData.responseBodyMapping = [];
|
||||
formData.tokenMapping = { ...defaultTokenMapping };
|
||||
formData.asyncTaskMapping = { ...defaultAsyncTaskMapping };
|
||||
formData.lastFrame = '';
|
||||
formData.maxDuration = 0;
|
||||
formData.tokenPredictPriceUnit = '';
|
||||
};
|
||||
|
||||
const openDialog = async (type: 'add' | 'edit', id?: string) => {
|
||||
@@ -579,9 +656,12 @@ const openDialog = async (type: 'add' | 'edit', id?: string) => {
|
||||
formData.requestHeadMapping = row.requestHeadMapping || row.requestMapping || {};
|
||||
formData.requestBodyMapping = row.requestBodyMapping || {};
|
||||
formData.responseMapping = row.responseMapping || {};
|
||||
formData.responseBodyMapping = row.responseBodyMapping || [];
|
||||
formData.tokenMapping = row.tokenMapping ? { ...defaultTokenMapping, ...row.tokenMapping } : { ...defaultTokenMapping };
|
||||
formData.asyncTaskMapping = row.asyncTaskMapping ? { ...defaultAsyncTaskMapping, ...row.asyncTaskMapping } : { ...defaultAsyncTaskMapping };
|
||||
formData.lastFrame = row.lastFrame || '';
|
||||
formData.maxDuration = row.maxDuration ?? 0;
|
||||
formData.tokenPredictPriceUnit = row.tokenPredictPriceUnit || '';
|
||||
} catch {
|
||||
// 关闭弹窗,错误由全局处理
|
||||
dialogVisible.value = false;
|
||||
@@ -616,6 +696,7 @@ const handleSubmit = async () => {
|
||||
requestHeadMapping: Object.keys(formData.requestHeadMapping).length > 0 ? formData.requestHeadMapping : undefined,
|
||||
requestBodyMapping: Object.keys(formData.requestBodyMapping).length > 0 ? formData.requestBodyMapping : undefined,
|
||||
responseMapping: Object.keys(formData.responseMapping).length > 0 ? formData.responseMapping : undefined,
|
||||
responseBodyMapping: formData.responseBodyMapping.length > 0 ? formData.responseBodyMapping : undefined,
|
||||
};
|
||||
|
||||
// 根据返回类型附加对应配置
|
||||
@@ -636,12 +717,22 @@ const handleSubmit = async () => {
|
||||
if (formData.lastFrame) {
|
||||
lastFrame = formData.lastFrame;
|
||||
}
|
||||
let maxDuration: number | undefined;
|
||||
let tokenPredictPriceUnit: string | undefined;
|
||||
if (isVideoModel.value) {
|
||||
maxDuration = formData.maxDuration || undefined;
|
||||
}
|
||||
if (formData.tokenPredictPriceUnit) {
|
||||
tokenPredictPriceUnit = formData.tokenPredictPriceUnit;
|
||||
}
|
||||
|
||||
const params: Record<string, unknown> = {
|
||||
...baseParams,
|
||||
...(tokenMapping ? { tokenMapping } : {}),
|
||||
...(asyncTaskMapping ? { asyncTaskMapping } : {}),
|
||||
...(lastFrame ? { lastFrame } : {}),
|
||||
...(maxDuration ? { maxDuration } : {}),
|
||||
...(tokenPredictPriceUnit ? { tokenPredictPriceUnit } : {}),
|
||||
};
|
||||
|
||||
if (isEdit.value) {
|
||||
|
||||
Reference in New Issue
Block a user