更新对话模型管理功能
- 修改 `updateChatModel` 函数的参数名称,从 `chatSessionEnabled` 更改为 `isChatModel`,以提高一致性。 - 在创作页面中新增对话模型选择器,支持用户搜索和选择对话模型,提升用户体验。 - 实现对话模型的分页和搜索功能,优化模型列表的展示。 - 更新相关样式和逻辑,确保对话模型设置的顺畅交互。
This commit is contained in:
@@ -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({
|
||||
url: '/model-gateway/model/updateChatModel',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会话模型
|
||||
*/
|
||||
export function getIsChatModel() {
|
||||
return request({
|
||||
url: '/model-gateway/model/getIsChatModel',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -173,7 +173,10 @@
|
||||
</el-form>
|
||||
</div>
|
||||
<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>
|
||||
@@ -190,20 +193,37 @@
|
||||
<div class="title">{{ currentWorkflowForCreation?.flowName || '内容创作' }}</div>
|
||||
<div class="sub">{{ currentWorkflowForCreation?.description || '填写表单参数进行内容创作' }}</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 class="creation-form-scroll">
|
||||
<el-form label-position="top" class="creation-form">
|
||||
<template v-if="currentWorkflowForCreation?.nodeInputParams">
|
||||
<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">
|
||||
<el-icon class="node-icon"><Document /></el-icon>
|
||||
<span>{{ node.name }}</span>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<el-form-item
|
||||
@@ -472,6 +492,47 @@
|
||||
<!-- 模型选择器 -->
|
||||
<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>
|
||||
<div class="preview-container">
|
||||
@@ -485,7 +546,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
||||
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 { Control, SelectionSelect } from '@logicflow/extension';
|
||||
import '@logicflow/core/dist/index.css';
|
||||
@@ -510,6 +571,7 @@ import {
|
||||
type ExecuteFlowParams,
|
||||
} from '/@/api/digitalHuman/creation';
|
||||
import { uploadFile } from '/@/api/common/upload';
|
||||
import { getModelModuleList, updateChatModel, getIsChatModel } from '/@/api/digitalHuman/modelConfig/modelModule';
|
||||
|
||||
type NodeType = 'date' | 'contentType' | 'theme' | 'title' | 'html' | 'image';
|
||||
type Item = Record<string, any>;
|
||||
@@ -577,6 +639,21 @@ const previewUrl = ref('');
|
||||
// 模型选择器相关状态
|
||||
const showModelSelector = ref(false);
|
||||
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 中)
|
||||
const getSessionId = () => {
|
||||
let sessionId = sessionStorage.getItem('ai_creation_session_id');
|
||||
@@ -897,6 +974,54 @@ const handleRemoveModel = () => {
|
||||
selectedModel.value = '';
|
||||
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) => {
|
||||
try {
|
||||
@@ -1040,6 +1165,23 @@ const sendMessage = async () => {
|
||||
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;
|
||||
|
||||
try {
|
||||
@@ -1647,7 +1789,7 @@ watch(
|
||||
// 初始化所有表单字段(基础 + 模型)- 只设置还没有值的字段
|
||||
allFormFields.value.forEach((fieldItem) => {
|
||||
const currentValue = dynamicFormValues[fieldItem.field];
|
||||
|
||||
|
||||
// 如果已经从 formConfig 或 modelConfig 加载过有效值,跳过
|
||||
// 对于数字类型,空字符串不是有效值
|
||||
if (fieldItem.type === 'number') {
|
||||
@@ -1816,7 +1958,8 @@ const applySelected = () => {
|
||||
|
||||
syncDsl();
|
||||
|
||||
// 静默更新,不显示提示
|
||||
// 显示应用成功提示
|
||||
ElMessage.success('应用成功');
|
||||
} catch (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 () => {
|
||||
await getList();
|
||||
await nextTick();
|
||||
@@ -2389,6 +2544,23 @@ onBeforeUnmount(() => {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
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 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
@@ -2745,6 +2917,11 @@ onBeforeUnmount(() => {
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
.creation-header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
.creation-form-scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
@@ -3199,4 +3376,105 @@ onBeforeUnmount(() => {
|
||||
gap: 4px;
|
||||
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>
|
||||
|
||||
@@ -110,7 +110,7 @@ const onChatModelSwitchRequest = async (row: { id?: number | string; isChatModel
|
||||
const newStatus = Number(row.isChatModel) === 1 ? 0 : 1;
|
||||
await updateChatModel({
|
||||
id: row.id!,
|
||||
chatSessionEnabled: newStatus as 0 | 1,
|
||||
isChatModel: newStatus as 0 | 1,
|
||||
});
|
||||
ElMessage.success(newStatus === 1 ? '已设置为会话模型' : '已取消会话模型');
|
||||
// 重新获取列表数据
|
||||
|
||||
Reference in New Issue
Block a user