添加会话模型和API Key配置功能
- 在模型模块中新增会话开关状态字段,支持会话模型的管理。 - 更新模型选择器,增加系统模型的API Key配置弹窗,提升用户体验。 - 优化错误处理逻辑,确保接口错误由全局拦截器处理,减少冗余提示。 - 更新相关样式以增强界面可读性和美观性。
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user