首页调试工作流管理

This commit is contained in:
2026-08-13 18:02:35 +08:00
parent 2ae59c9252
commit 8e7948cff5
8 changed files with 967 additions and 135 deletions
@@ -22,7 +22,11 @@
@click="handleSelectModel(model)"
>
<div class="model-card-header">
<div class="model-type">{{ getModelTypeName(model.modelType) }}</div>
<div class="model-type">
{{ getModelTypeName(model.modelType) }}
<el-tag v-if="model.systemModel" size="small" type="warning" class="model-owner-tag">内置</el-tag>
<el-tag v-else size="small" type="success" class="model-owner-tag">我的</el-tag>
</div>
<el-icon v-if="selectedModel?.id === model.id" class="check-icon" color="#67c23a">
<CircleCheck />
</el-icon>
@@ -56,16 +60,56 @@
<el-button type="primary" @click="handleConfirm" :disabled="!selectedModel">确定</el-button>
</template>
</el-dialog>
<!-- 系统内置模型填写 API Key经修改接口转换为用户模型后自动绑定 -->
<el-dialog
v-model="apiKeyDialogVisible"
title="填写 API Key"
width="480px"
append-to-body
:close-on-click-modal="false"
@close="handleApiKeyClose"
>
<el-alert
type="info"
:closable="false"
show-icon
title="该模型为系统内置模型,填写你的 API Key 后将创建一条用户模型并自动绑定到当前节点。"
class="api-key-alert"
/>
<el-form label-position="top" class="api-key-form">
<el-form-item label="API Key" required>
<el-input
v-model="apiKeyForm.apiKey"
type="password"
show-password
placeholder="请输入你的 API Key"
@keyup.enter="handleApiKeyConfirm"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleApiKeyClose">取消</el-button>
<el-button type="primary" :loading="converting" @click="handleApiKeyConfirm">确认并转换</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { Search, CircleCheck } from '@element-plus/icons-vue';
import { getWorkflowModelList, type WorkflowModelItem } from '/@/api/settings/workflow';
import { updateModelManage } from '/@/api/settings/modelConfigV2';
interface Props {
modelValue: boolean;
defaultModel?: WorkflowModelItem | null;
// 非空时仅列出同类型模型(首页「重新选择模型」场景);工作流管理不传,行为不变
sameTypeModelType?: string | number | null;
// 系统模型是否必须填写 API Key(默认 true)。
// 超级管理员绘制/编辑模板工作流时传 false:选系统模型直接选中,不再弹 API Key。
systemModelRequireKey?: boolean;
}
interface Emits {
@@ -76,6 +120,8 @@ interface Emits {
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
defaultModel: null,
sameTypeModelType: null,
systemModelRequireKey: true,
});
const emit = defineEmits<Emits>();
@@ -86,6 +132,11 @@ const pagination = reactive({ pageNum: 1, pageSize: 10, total: 0 });
const modelList = ref<WorkflowModelItem[]>([]);
const loading = ref(false);
const selectedModel = ref<WorkflowModelItem | null>(null);
// 系统内置模型(systemModel === true)转用户模型:填 API Key → 调修改接口,后端自动克隆出新用户模型
const apiKeyDialogVisible = ref(false);
const converting = ref(false);
const pendingCloneModel = ref<WorkflowModelItem | null>(null);
const apiKeyForm = reactive({ apiKey: '' });
watch(
() => props.modelValue,
@@ -143,6 +194,9 @@ const fetchModelList = async () => {
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
modelName: searchParams.modelName || undefined,
...(props.sameTypeModelType !== undefined && props.sameTypeModelType !== null && props.sameTypeModelType !== ''
? { isSameType: true, modelType: props.sameTypeModelType }
: {}),
};
const res = await getWorkflowModelList(params);
modelList.value = res.data?.list || [];
@@ -170,6 +224,14 @@ const handlePageChange = () => {
};
const handleSelectModel = (model: WorkflowModelItem) => {
// 系统内置模型:默认需填写 API Key,经修改接口转成用户模型后再绑定;
// 超级管理员绘制模板(systemModelRequireKey=false)时直接选中,模板保留系统模型供用户使用时再补 Key
if (model.systemModel && props.systemModelRequireKey) {
pendingCloneModel.value = model;
apiKeyForm.apiKey = '';
apiKeyDialogVisible.value = true;
return;
}
selectedModel.value = model;
};
@@ -180,6 +242,68 @@ const handleConfirm = () => {
}
};
// 确认 API Key:调用修改接口,后端克隆出用户模型并直接返回,前端立即绑定
const handleApiKeyConfirm = async () => {
if (!apiKeyForm.apiKey.trim()) {
ElMessage.warning('请输入 API Key');
return;
}
if (!pendingCloneModel.value?.id) return;
converting.value = true;
try {
const src = pendingCloneModel.value;
const res = await updateModelManage({
id: src.id,
// 传递系统模型全部配置,供后端克隆用户模型时继承
modelName: src.modelName,
modelType: src.modelType,
modelSupplier: src.modelSupplier,
baseUrl: src.baseUrl,
responseType: src.responseType ?? src.invokeType,
apiKey: apiKeyForm.apiKey.trim(),
enabled: src.enabled,
chatModel: src.ChatModel ?? src.chatModel,
maxConcurrency: src.maxConcurrency,
maxTokens: src.maxTokens,
tokenPredictPrice: src.tokenPredictPrice,
requestHeadMapping: src.requestHeadMapping ?? src.requestMapping,
requestBodyMapping: src.requestBodyMapping,
responseMapping: src.responseMapping,
responseBodyMapping: src.responseBodyMapping,
...(src.tokenMapping ? { tokenMapping: src.tokenMapping } : {}),
...(src.asyncTaskMapping ? { asyncTaskMapping: src.asyncTaskMapping } : {}),
...(src.lastFrame ? { lastFrame: src.lastFrame } : {}),
...(src.maxDuration ? { maxDuration: src.maxDuration } : {}),
...(src.tokenPredictPriceUnit ? { tokenPredictPriceUnit: src.tokenPredictPriceUnit } : {}),
} as any);
// 返回结构为 { data: { modelManage: {...} } },新用户模型 id 在 modelManage 中
const newModelManage = res?.data?.modelManage || res?.data;
if (!newModelManage?.id) throw new Error('接口未返回新的用户模型');
// 后端返回的 modelManage 仅带新 id/apiKeymodelName/requestBodyMapping 等为空。
// 用原系统模型完整配置 + 新 id 合并,保证绑定后模型名、参数表单、下游引用、isSameType 过滤都正常。
const newModel: WorkflowModelItem = {
...(pendingCloneModel.value || {}),
id: newModelManage.id,
systemModel: false,
apiKey: apiKeyForm.apiKey.trim(),
};
apiKeyDialogVisible.value = false;
pendingCloneModel.value = null;
emit('confirm', newModel);
handleClose();
ElMessage.success('已创建用户模型并绑定');
} catch (e: any) {
ElMessage.error(e?.message || '模型转换失败,请重试');
} finally {
converting.value = false;
}
};
const handleApiKeyClose = () => {
apiKeyDialogVisible.value = false;
pendingCloneModel.value = null;
};
const handleClose = () => {
visible.value = false;
selectedModel.value = null;
@@ -232,13 +356,19 @@ const handleClose = () => {
margin-bottom: 12px;
}
.model-type {
display: inline-block;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 2px 8px;
background: #eff6ff;
color: #3b82f6;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
.model-owner-tag {
margin-left: 2px;
}
}
.check-icon {
font-size: 20px;
@@ -272,4 +402,12 @@ const handleClose = () => {
justify-content: center;
margin-top: 20px;
}
.api-key-alert {
margin-bottom: 16px;
}
.api-key-form {
margin-bottom: 4px;
}
</style>