添加会话模型和API Key配置功能

- 在模型模块中新增会话开关状态字段,支持会话模型的管理。
- 更新模型选择器,增加系统模型的API Key配置弹窗,提升用户体验。
- 优化错误处理逻辑,确保接口错误由全局拦截器处理,减少冗余提示。
- 更新相关样式以增强界面可读性和美观性。
This commit is contained in:
2026-05-11 20:01:03 +08:00
parent 0a42e700e2
commit 29838b030f
19 changed files with 1296 additions and 274 deletions

View File

@@ -19,12 +19,15 @@
v-for="model in modelList"
:key="model.id"
class="model-card"
:class="{ selected: selectedModel?.id === model.id }"
:class="{ selected: selectedModel?.id === model.id, 'system-model': model.tenantId === 1 }"
@click="handleSelectModel(model)"
>
<div class="model-card-header">
<div class="model-type">{{ getModelTypeName(model.modelsType) }}</div>
<el-icon v-if="selectedModel?.id === model.id" class="check-icon" color="#67c23a"><CircleCheck /></el-icon>
<div class="model-badges">
<el-tag v-if="model.tenantId === 1" type="warning" size="small">系统模型</el-tag>
<el-icon v-if="selectedModel?.id === model.id" class="check-icon" color="#67c23a"><CircleCheck /></el-icon>
</div>
</div>
<div class="model-card-body">
<h3 class="model-name">{{ model.modelName }}</h3>
@@ -58,23 +61,65 @@
<!-- 新建模型弹窗 -->
<EditModule ref="editModuleRef" @refresh="handleRefresh" />
<!-- 系统模型 API Key 输入弹窗 -->
<el-dialog v-model="apiKeyDialogVisible" title="配置系统模型" width="500px" :close-on-click-modal="false" append-to-body>
<el-alert type="info" :closable="false" style="margin-bottom: 16px">
<template #title>
<div style="line-height: 1.6">
您选择的是系统模型需要配置您自己的 API Key<br />
系统将为您创建一个模型副本
</div>
</template>
</el-alert>
<el-form :model="apiKeyForm" :rules="apiKeyRules" ref="apiKeyFormRef" label-width="100px">
<el-form-item label="模型名称" prop="modelName">
<el-input v-model="apiKeyForm.modelName" placeholder="请输入模型名称" />
</el-form-item>
<el-form-item label="API Key" prop="apiKey">
<el-input v-model="apiKeyForm.apiKey" type="password" show-password placeholder="请输入您的 API Key" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="apiKeyDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleCreatePrivateModel" :loading="creatingModel">确定</el-button>
</template>
</el-dialog>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { Search, CircleCheck } from '@element-plus/icons-vue';
import { getModelModuleList } from '/@/api/digitalHuman/modelConfig/modelModule';
import { getModelModuleList, addModelModule } from '/@/api/digitalHuman/modelConfig/modelModule';
import { getApiErrorMessage } from '/@/utils/request';
import EditModule from '/@/views/digitalHuman/modelConfig/modelModule/component/editModule.vue';
interface ModelItem {
id: string;
tenantId?: number;
modelName: string;
modelsType: number;
baseUrl: string;
route: string;
httpMethod: string;
enabled: number;
apiKey?: string;
isPrivate?: number;
isChatModel?: number;
headMsg?: string;
form?: any;
requestMapping?: any;
responseMapping?: any;
maxConcurrency?: number;
queueLimit?: number;
timeoutSeconds?: number;
expectedSeconds?: number;
retryTimes?: number;
retryQueueMaxSeconds?: number;
autoCleanSeconds?: number;
remark?: string;
[key: string]: any;
}
@@ -103,6 +148,20 @@ const loading = ref(false);
const selectedModel = ref<ModelItem | null>(null);
const editModuleRef = ref();
// 系统模型 API Key 配置
const apiKeyDialogVisible = ref(false);
const apiKeyFormRef = ref<FormInstance>();
const apiKeyForm = reactive({
modelName: '',
apiKey: '',
});
const apiKeyRules: FormRules = {
modelName: [{ required: true, message: '请输入模型名称', trigger: 'blur' }],
apiKey: [{ required: true, message: '请输入 API Key', trigger: 'blur' }],
};
const creatingModel = ref(false);
const systemModelToClone = ref<ModelItem | null>(null);
watch(
() => props.modelValue,
(val) => {
@@ -122,9 +181,9 @@ watch(visible, (val) => {
const getModelTypeName = (type: number) => {
const typeMap: Record<number, string> = {
1: '图片模型',
2: '语音模型',
3: '推理模型',
1: '推理模型',
2: '图片模型',
3: '音频模型',
};
return typeMap[type] || '未知类型';
};
@@ -137,10 +196,11 @@ const fetchModelList = async () => {
pageSize: pagination.pageSize,
modelName: searchParams.modelName || undefined,
};
const res = await getModelModuleList(params, { errorMode: 'message' });
const res: any = await getModelModuleList(params);
modelList.value = res.data?.list || [];
pagination.total = res.data?.total || 0;
} catch (error) {
} catch {
// 接口错误由 request 全局提示后端 message
modelList.value = [];
pagination.total = 0;
} finally {
@@ -158,7 +218,77 @@ const handlePageChange = () => {
};
const handleSelectModel = (model: ModelItem) => {
selectedModel.value = model;
// 判断是否是系统模型tenantId === 1
if (model.tenantId === 1) {
// 系统模型,需要用户配置 API Key
systemModelToClone.value = model;
apiKeyForm.modelName = `${model.modelName} - 副本`;
apiKeyForm.apiKey = '';
apiKeyDialogVisible.value = true;
} else {
// 非系统模型,直接选中
selectedModel.value = model;
}
};
const handleCreatePrivateModel = async () => {
if (!apiKeyFormRef.value || !systemModelToClone.value) return;
try {
await apiKeyFormRef.value.validate();
creatingModel.value = true;
// 基于系统模型创建新模型(继承原模型的所有配置,只替换 apiKey
const systemModel = systemModelToClone.value;
const createParams = {
modelName: apiKeyForm.modelName,
modelsType: systemModel.modelsType,
baseUrl: systemModel.baseUrl,
httpMethod: systemModel.httpMethod || 'POST',
headMsg: systemModel.headMsg || '',
isPrivate: systemModel.isPrivate ?? 1, // 继承原模型的公有/私有属性
enabled: systemModel.enabled ?? 1,
isChatModel: systemModel.isChatModel || 0,
apiKey: apiKeyForm.apiKey, // 使用用户输入的新 API Key
form: systemModel.form || {},
requestMapping: systemModel.requestMapping || {},
responseMapping: systemModel.responseMapping || {},
maxConcurrency: systemModel.maxConcurrency || 10,
queueLimit: systemModel.queueLimit || 100,
timeoutSeconds: systemModel.timeoutSeconds || 30,
expectedSeconds: systemModel.expectedSeconds || 15,
retryTimes: systemModel.retryTimes || 3,
retryQueueMaxSeconds: systemModel.retryQueueMaxSeconds || 60,
autoCleanSeconds: systemModel.autoCleanSeconds || 300,
remark: systemModel.remark || '',
};
const res: any = await addModelModule(createParams);
ElMessage.success('模型创建成功');
// 关闭对话框
apiKeyDialogVisible.value = false;
// 刷新列表
await fetchModelList();
// 选中新创建的模型
const newModelId = res.data?.id || res.data;
if (newModelId) {
const newModel = modelList.value.find((m) => m.id === String(newModelId));
if (newModel) {
selectedModel.value = newModel;
}
}
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(getApiErrorMessage(error, '创建模型失败'));
}
} finally {
creatingModel.value = false;
}
};
const handleAddModel = () => {
@@ -179,6 +309,8 @@ const handleConfirm = () => {
const handleClose = () => {
visible.value = false;
selectedModel.value = null;
apiKeyDialogVisible.value = false;
systemModelToClone.value = null;
};
</script>
@@ -228,6 +360,15 @@ const handleClose = () => {
background: #f0f9ff;
}
.model-card.system-model {
border-color: #fbbf24;
background: #fffbeb;
}
.model-card.system-model:hover {
border-color: #f59e0b;
}
.model-card-header {
display: flex;
justify-content: space-between;
@@ -245,6 +386,12 @@ const handleClose = () => {
font-weight: 600;
}
.model-badges {
display: flex;
align-items: center;
gap: 8px;
}
.check-icon {
font-size: 20px;
}