更新对话模型管理功能

- 修改 `updateChatModel` 函数的参数名称,从 `chatSessionEnabled` 更改为 `isChatModel`,以提高一致性。
- 在创作页面中新增对话模型选择器,支持用户搜索和选择对话模型,提升用户体验。
- 实现对话模型的分页和搜索功能,优化模型列表的展示。
- 更新相关样式和逻辑,确保对话模型设置的顺畅交互。
This commit is contained in:
2026-05-11 22:40:58 +08:00
parent c7152f5d92
commit 03de9595d1
3 changed files with 297 additions and 14 deletions

View File

@@ -213,18 +213,23 @@ export function getModelModuleDetail(id: number | string) {
}); });
} }
// TODO: 列表「会话开关」提交接口确定后在此封装,例如:
// export function updateModelChatSessionSwitch(data: { id: number | string; chatSessionEnabled: 0 | 1 }) {
// return request({ url: '/model-gateway/model/...', method: 'post', data });
// }
/** /**
* 更新模型会话开关状态 * 更新模型会话开关状态
*/ */
export function updateChatModel(data: { id: number | string; chatSessionEnabled: 0 | 1 }) { export function updateChatModel(data: { id: number | string; isChatModel: 0 | 1 }) {
return request({ return request({
url: '/model-gateway/model/updateChatModel', url: '/model-gateway/model/updateChatModel',
method: 'post', method: 'post',
data, data,
}); });
} }
/**
* 获取当前会话模型
*/
export function getIsChatModel() {
return request({
url: '/model-gateway/model/getIsChatModel',
method: 'get',
});
}

View File

@@ -173,7 +173,10 @@
</el-form> </el-form>
</div> </div>
<div class="form-actions"> <div class="form-actions">
<el-button type="primary" class="w100" @click="applySelected">应用到当前元素</el-button> <el-button type="primary" size="large" class="apply-button" @click="applySelected">
<el-icon><Check /></el-icon>
应用到当前元素
</el-button>
</div> </div>
</div> </div>
</div> </div>
@@ -190,20 +193,37 @@
<div class="title">{{ currentWorkflowForCreation?.flowName || '内容创作' }}</div> <div class="title">{{ currentWorkflowForCreation?.flowName || '内容创作' }}</div>
<div class="sub">{{ currentWorkflowForCreation?.description || '填写表单参数进行内容创作' }}</div> <div class="sub">{{ currentWorkflowForCreation?.description || '填写表单参数进行内容创作' }}</div>
</div> </div>
<el-button @click="backToCanvas">返回画布</el-button> <div class="creation-header-actions">
<el-button type="warning" size="large" @click="showChatModelSelector = true">
<el-icon><Setting /></el-icon>
设置对话模型
</el-button>
<el-button @click="backToCanvas">返回画布</el-button>
</div>
</div> </div>
<div class="creation-form-scroll"> <div class="creation-form-scroll">
<el-form label-position="top" class="creation-form"> <el-form label-position="top" class="creation-form">
<template v-if="currentWorkflowForCreation?.nodeInputParams"> <template v-if="currentWorkflowForCreation?.nodeInputParams">
<div v-for="node in currentWorkflowForCreation.nodeInputParams" :key="node.id" class="node-form-wrapper"> <div v-for="node in currentWorkflowForCreation.nodeInputParams" :key="node.id" class="node-form-wrapper">
<!-- 跳过开始节点 --> <!-- 跳过开始节点显示其他所有节点 -->
<div v-if="node.nodeCode !== '__start__' && (node.formConfig?.length > 0 || hasVisibleFields(node))" class="node-form-section"> <div v-if="node.nodeCode !== '__start__'" class="node-form-section">
<div class="node-form-title"> <div class="node-form-title">
<el-icon class="node-icon"><Document /></el-icon> <el-icon class="node-icon"><Document /></el-icon>
<span>{{ node.name }}</span> <span>{{ node.name }}</span>
</div> </div>
<div class="form-grid"> <div class="form-grid">
<!-- 节点基本信息始终显示 -->
<el-form-item label="节点类型" class="form-item-medium">
<el-input :model-value="node.nodeCode" disabled />
</el-form-item>
<el-form-item label="选择模型" class="form-item-medium" v-if="node.modelConfig?.modelName">
<el-input :model-value="node.modelConfig.modelName" disabled />
</el-form-item>
<el-form-item label="技能" class="form-item-medium" v-if="node.skillName">
<el-input :model-value="node.skillName" disabled />
</el-form-item>
<!-- 自定义表单字段 --> <!-- 自定义表单字段 -->
<template v-if="node.formConfig && node.formConfig.length > 0"> <template v-if="node.formConfig && node.formConfig.length > 0">
<el-form-item <el-form-item
@@ -472,6 +492,47 @@
<!-- 模型选择器 --> <!-- 模型选择器 -->
<ModelSelector v-model="showModelSelector" :default-model="selectedModelData" @confirm="handleModelConfirm" /> <ModelSelector v-model="showModelSelector" :default-model="selectedModelData" @confirm="handleModelConfirm" />
<!-- 对话模型选择器 -->
<el-dialog v-model="showChatModelSelector" title="设置对话模型" width="900px" :close-on-click-modal="false">
<div class="chat-model-selector">
<div class="chat-model-search">
<el-input v-model="chatModelSearchKeyword" placeholder="搜索模型名称" clearable @clear="handleChatModelSearch">
<template #prefix><el-icon><Search /></el-icon></template>
</el-input>
<el-button type="primary" @click="handleChatModelSearch">搜索</el-button>
</div>
<div class="chat-model-list" v-loading="chatModelLoading">
<el-empty v-if="!chatModelLoading && filteredChatModels.length === 0" description="暂无推理模型" :image-size="100" />
<div v-else class="chat-model-grid">
<div
v-for="model in filteredChatModels"
:key="model.id"
class="chat-model-card"
:class="{ selected: selectedChatModel?.id === model.id }"
@click="selectedChatModel = model"
>
<div class="chat-model-name">{{ model.modelName }}</div>
<div class="chat-model-url">{{ model.baseUrl }}</div>
<el-icon v-if="selectedChatModel?.id === model.id" class="check-icon" color="#67c23a"><CircleCheck /></el-icon>
</div>
</div>
</div>
<div v-if="chatModelPagination.total > 0" class="chat-model-pagination">
<el-pagination
v-model:current-page="chatModelPagination.pageNum"
:page-size="chatModelPagination.pageSize"
:total="chatModelPagination.total"
layout="total, prev, pager, next"
@current-change="handleChatModelPageChange"
/>
</div>
</div>
<template #footer>
<el-button @click="showChatModelSelector = false">取消</el-button>
<el-button type="primary" @click="handleSetChatModel" :disabled="!selectedChatModel" :loading="settingChatModel">确定</el-button>
</template>
</el-dialog>
<!-- 预览弹窗 --> <!-- 预览弹窗 -->
<el-dialog v-model="previewDialogVisible" title="预览" width="90%" :close-on-click-modal="false" destroy-on-close> <el-dialog v-model="previewDialogVisible" title="预览" width="90%" :close-on-click-modal="false" destroy-on-close>
<div class="preview-container"> <div class="preview-container">
@@ -485,7 +546,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'; import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { Document, Plus, Paperclip, MagicStick, Promotion } from '@element-plus/icons-vue'; import { Document, Plus, Paperclip, MagicStick, Promotion, Check, Setting, Search, CircleCheck } from '@element-plus/icons-vue';
import LogicFlow from '@logicflow/core'; import LogicFlow from '@logicflow/core';
import { Control, SelectionSelect } from '@logicflow/extension'; import { Control, SelectionSelect } from '@logicflow/extension';
import '@logicflow/core/dist/index.css'; import '@logicflow/core/dist/index.css';
@@ -510,6 +571,7 @@ import {
type ExecuteFlowParams, type ExecuteFlowParams,
} from '/@/api/digitalHuman/creation'; } from '/@/api/digitalHuman/creation';
import { uploadFile } from '/@/api/common/upload'; import { uploadFile } from '/@/api/common/upload';
import { getModelModuleList, updateChatModel, getIsChatModel } from '/@/api/digitalHuman/modelConfig/modelModule';
type NodeType = 'date' | 'contentType' | 'theme' | 'title' | 'html' | 'image'; type NodeType = 'date' | 'contentType' | 'theme' | 'title' | 'html' | 'image';
type Item = Record<string, any>; type Item = Record<string, any>;
@@ -577,6 +639,21 @@ const previewUrl = ref('');
// 模型选择器相关状态 // 模型选择器相关状态
const showModelSelector = ref(false); const showModelSelector = ref(false);
const selectedModelData = ref<any>(null); const selectedModelData = ref<any>(null);
// 对话模型选择器相关状态
const showChatModelSelector = ref(false);
const selectedChatModel = ref<any>(null);
const chatModelList = ref<any[]>([]);
const chatModelLoading = ref(false);
const chatModelSearchKeyword = ref('');
const settingChatModel = ref(false);
const chatModelPagination = reactive({
pageNum: 1,
pageSize: 10,
total: 0,
});
const filteredChatModels = computed(() => {
return chatModelList.value;
});
// 会话ID管理存储在 sessionStorage 中) // 会话ID管理存储在 sessionStorage 中)
const getSessionId = () => { const getSessionId = () => {
let sessionId = sessionStorage.getItem('ai_creation_session_id'); let sessionId = sessionStorage.getItem('ai_creation_session_id');
@@ -897,6 +974,54 @@ const handleRemoveModel = () => {
selectedModel.value = ''; selectedModel.value = '';
selectedModelData.value = null; selectedModelData.value = null;
}; };
// 获取对话模型列表
const fetchChatModelList = async () => {
chatModelLoading.value = true;
try {
const res: any = await getModelModuleList({
pageNum: chatModelPagination.pageNum,
pageSize: chatModelPagination.pageSize,
modelsType: 1, // 传递 modelsType=1 给后端,获取推理模型
modelName: chatModelSearchKeyword.value || undefined,
});
chatModelList.value = res.data?.list || [];
chatModelPagination.total = res.data?.total || 0;
} catch {
chatModelList.value = [];
chatModelPagination.total = 0;
} finally {
chatModelLoading.value = false;
}
};
// 处理对话模型分页变化
const handleChatModelPageChange = (page: number) => {
chatModelPagination.pageNum = page;
fetchChatModelList();
};
// 处理对话模型搜索
const handleChatModelSearch = () => {
chatModelPagination.pageNum = 1;
fetchChatModelList();
};
// 设置对话模型
const handleSetChatModel = async () => {
if (!selectedChatModel.value) return;
settingChatModel.value = true;
try {
await updateChatModel({
id: selectedChatModel.value.id,
isChatModel: 1,
});
ElMessage.success('对话模型设置成功');
showChatModelSelector.value = false;
selectedChatModel.value = null;
} catch {
// 接口错误由 request 全局提示后端 message
} finally {
settingChatModel.value = false;
}
};
// 使用工作流 // 使用工作流
const useWorkflow = async (workflow: WorkflowItem) => { const useWorkflow = async (workflow: WorkflowItem) => {
try { try {
@@ -1040,6 +1165,23 @@ const sendMessage = async () => {
return; return;
} }
// 检查是否设置了会话模型
try {
const chatModelRes: any = await getIsChatModel();
if (!chatModelRes.data || Object.keys(chatModelRes.data).length === 0) {
ElMessageBox.alert('请先设置对话模型后再进行创作', '提示', {
confirmButtonText: '去设置',
type: 'warning',
}).then(() => {
showChatModelSelector.value = true;
}).catch(() => {});
return;
}
} catch (error) {
ElMessage.error('获取会话模型失败,请稍后重试');
return;
}
isCreating.value = true; isCreating.value = true;
try { try {
@@ -1647,7 +1789,7 @@ watch(
// 初始化所有表单字段(基础 + 模型)- 只设置还没有值的字段 // 初始化所有表单字段(基础 + 模型)- 只设置还没有值的字段
allFormFields.value.forEach((fieldItem) => { allFormFields.value.forEach((fieldItem) => {
const currentValue = dynamicFormValues[fieldItem.field]; const currentValue = dynamicFormValues[fieldItem.field];
// 如果已经从 formConfig 或 modelConfig 加载过有效值,跳过 // 如果已经从 formConfig 或 modelConfig 加载过有效值,跳过
// 对于数字类型,空字符串不是有效值 // 对于数字类型,空字符串不是有效值
if (fieldItem.type === 'number') { if (fieldItem.type === 'number') {
@@ -1816,7 +1958,8 @@ const applySelected = () => {
syncDsl(); syncDsl();
// 静默更新,不显示提示 // 显示应用成功提示
ElMessage.success('应用成功');
} catch (error) { } catch (error) {
ElMessage.error('应用配置失败,请查看控制台'); ElMessage.error('应用配置失败,请查看控制台');
} }
@@ -2096,6 +2239,18 @@ watch(selectedElement, (newElement) => {
} }
}); });
// 监听对话模型选择器打开,加载模型列表
watch(showChatModelSelector, (val) => {
if (val) {
chatModelPagination.pageNum = 1;
chatModelSearchKeyword.value = '';
fetchChatModelList();
} else {
selectedChatModel.value = null;
chatModelSearchKeyword.value = '';
}
});
onMounted(async () => { onMounted(async () => {
await getList(); await getList();
await nextTick(); await nextTick();
@@ -2389,6 +2544,23 @@ onBeforeUnmount(() => {
border-top: 1px solid #e5e7eb; border-top: 1px solid #e5e7eb;
flex-shrink: 0; flex-shrink: 0;
} }
.apply-button {
width: 100%;
height: 48px;
font-size: 16px;
font-weight: 600;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
transition: all 0.3s ease;
margin-top: 20px;
}
.apply-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}
.apply-button:active {
transform: translateY(0);
}
.json-preview { .json-preview {
margin-top: 16px; margin-top: 16px;
} }
@@ -2745,6 +2917,11 @@ onBeforeUnmount(() => {
padding-bottom: 16px; padding-bottom: 16px;
border-bottom: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb;
} }
.creation-header-actions {
display: flex;
gap: 12px;
align-items: center;
}
.creation-form-scroll { .creation-form-scroll {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
@@ -3199,4 +3376,105 @@ onBeforeUnmount(() => {
gap: 4px; gap: 4px;
margin-left: 8px; margin-left: 8px;
} }
/* 对话模型选择器样式 */
.chat-model-selector {
min-height: 400px;
display: flex;
flex-direction: column;
}
.chat-model-search {
display: flex;
gap: 12px;
margin-bottom: 20px;
}
.chat-model-search .el-input {
flex: 1;
}
.chat-model-list {
flex: 1;
max-height: 450px;
overflow-y: auto;
margin-bottom: 16px;
padding: 4px;
}
.chat-model-list::-webkit-scrollbar {
width: 6px;
}
.chat-model-list::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
.chat-model-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.chat-model-card {
position: relative;
padding: 20px;
border: 2px solid #e5e7eb;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.chat-model-card:hover {
border-color: #3b82f6;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
transform: translateY(-2px);
}
.chat-model-card.selected {
border-color: #67c23a;
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
box-shadow: 0 4px 12px rgba(103, 194, 58, 0.2);
}
.chat-model-name {
font-size: 15px;
font-weight: 700;
color: #1e293b;
margin-bottom: 12px;
padding-right: 28px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.4;
}
.chat-model-url {
font-size: 13px;
color: #64748b;
line-height: 1.6;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 8px 12px;
background: rgba(148, 163, 184, 0.1);
border-radius: 6px;
font-family: 'Consolas', 'Monaco', monospace;
}
.chat-model-card .check-icon {
position: absolute;
top: 12px;
right: 12px;
font-size: 24px;
animation: scaleIn 0.3s ease;
}
@keyframes scaleIn {
from {
transform: scale(0);
}
to {
transform: scale(1);
}
}
.chat-model-pagination {
display: flex;
justify-content: center;
padding-top: 16px;
border-top: 2px solid #e5e7eb;
}
.chat-model-pagination :deep(.el-pagination) {
justify-content: center;
}
</style> </style>

View File

@@ -110,7 +110,7 @@ const onChatModelSwitchRequest = async (row: { id?: number | string; isChatModel
const newStatus = Number(row.isChatModel) === 1 ? 0 : 1; const newStatus = Number(row.isChatModel) === 1 ? 0 : 1;
await updateChatModel({ await updateChatModel({
id: row.id!, id: row.id!,
chatSessionEnabled: newStatus as 0 | 1, isChatModel: newStatus as 0 | 1,
}); });
ElMessage.success(newStatus === 1 ? '已设置为会话模型' : '已取消会话模型'); ElMessage.success(newStatus === 1 ? '已设置为会话模型' : '已取消会话模型');
// 重新获取列表数据 // 重新获取列表数据