处理模型回显

This commit is contained in:
2026-05-09 22:01:28 +08:00
parent cae76234b7
commit 76420713fa
3 changed files with 546 additions and 130 deletions

View File

@@ -0,0 +1,285 @@
<template>
<el-dialog v-model="visible" title="选择模型" width="1000px" :close-on-click-modal="false" @close="handleClose">
<div class="model-selector-header">
<div class="search-bar">
<el-input v-model="searchParams.keyword" placeholder="搜索模型名称" clearable @clear="handleSearch">
<template #prefix><el-icon><Search /></el-icon></template>
</el-input>
<el-button type="primary" @click="handleSearch">搜索</el-button>
</div>
<el-button type="success" @click="handleAddModel">+ 新建模型</el-button>
</div>
<div class="model-list" v-loading="loading">
<el-empty v-if="!loading && modelList.length === 0" description="暂无模型数据" :image-size="100" />
<div v-else class="model-grid">
<div
v-for="model in modelList"
:key="model.id"
class="model-card"
:class="{ selected: selectedModel?.id === model.id }"
@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>
<div class="model-card-body">
<h3 class="model-name">{{ model.modelName }}</h3>
<p class="model-url">{{ model.baseUrl }}</p>
<div class="model-status">
<el-tag :type="model.enabled === 1 ? 'success' : 'info'" size="small">
{{ model.enabled === 1 ? '已启用' : '已禁用' }}
</el-tag>
</div>
</div>
</div>
</div>
</div>
<div v-if="pagination.total > 0" class="pagination-wrap">
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50]"
layout="total, prev, pager, next"
small
@current-change="handlePageChange"
/>
</div>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleConfirm" :disabled="!selectedModel">确定</el-button>
</template>
<!-- 新建模型弹窗 -->
<EditModule ref="editModuleRef" @refresh="handleRefresh" />
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue';
import { Search, CircleCheck } from '@element-plus/icons-vue';
import { getModelModuleList } from '/@/api/digitalHuman/modelConfig/modelModule';
import EditModule from '/@/views/digitalHuman/modelConfig/modelModule/component/editModule.vue';
interface ModelItem {
id: string;
modelName: string;
modelsType: number;
baseUrl: string;
route: string;
httpMethod: string;
enabled: number;
[key: string]: any;
}
interface Props {
modelValue: boolean;
defaultModel?: ModelItem | null;
}
interface Emits {
(e: 'update:modelValue', value: boolean): void;
(e: 'confirm', model: ModelItem): void;
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
defaultModel: null,
});
const emit = defineEmits<Emits>();
const visible = ref(false);
const searchParams = reactive({ keyword: '' });
const pagination = reactive({ pageNum: 1, pageSize: 10, total: 0 });
const modelList = ref<ModelItem[]>([]);
const loading = ref(false);
const selectedModel = ref<ModelItem | null>(null);
const editModuleRef = ref();
watch(
() => props.modelValue,
(val) => {
visible.value = val;
if (val) {
selectedModel.value = props.defaultModel || null;
fetchModelList();
}
}
);
watch(visible, (val) => {
if (!val) {
emit('update:modelValue', false);
}
});
const getModelTypeName = (type: number) => {
const typeMap: Record<number, string> = {
1: '图片模型',
2: '语音模型',
3: '推理模型',
};
return typeMap[type] || '未知类型';
};
const fetchModelList = async () => {
loading.value = true;
try {
const params = {
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
keyword: searchParams.keyword || undefined,
};
const res = await getModelModuleList(params, { errorMode: 'message' });
modelList.value = res.data?.list || [];
pagination.total = res.data?.total || 0;
} catch (error) {
modelList.value = [];
pagination.total = 0;
} finally {
loading.value = false;
}
};
const handleSearch = () => {
pagination.pageNum = 1;
fetchModelList();
};
const handlePageChange = () => {
fetchModelList();
};
const handleSelectModel = (model: ModelItem) => {
selectedModel.value = model;
};
const handleAddModel = () => {
editModuleRef.value?.openDialog('add');
};
const handleRefresh = () => {
fetchModelList();
};
const handleConfirm = () => {
if (selectedModel.value) {
emit('confirm', selectedModel.value);
handleClose();
}
};
const handleClose = () => {
visible.value = false;
selectedModel.value = null;
};
</script>
<style scoped lang="scss">
.model-selector-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
gap: 12px;
}
.search-bar {
display: flex;
gap: 12px;
flex: 1;
}
.model-list {
min-height: 300px;
max-height: 400px;
overflow-y: auto;
}
.model-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.model-card {
background: #f8fafc;
border-radius: 8px;
padding: 16px;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.model-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.model-card.selected {
border-color: #67c23a;
background: #f0f9ff;
}
.model-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.model-type {
display: inline-block;
padding: 2px 8px;
background: #eff6ff;
color: #3b82f6;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
}
.check-icon {
font-size: 20px;
}
.model-card-body {
flex: 1;
}
.model-name {
font-size: 16px;
font-weight: 600;
color: #1f2937;
margin: 0 0 8px 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.model-url {
font-size: 13px;
color: #64748b;
line-height: 1.5;
margin: 0 0 8px 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.model-status {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-wrap {
display: flex;
justify-content: center;
margin-top: 20px;
}
</style>

View File

@@ -20,22 +20,8 @@
<div class="tree-node"> <div class="tree-node">
<span class="ellipsis">{{ data.label }}</span> <span class="ellipsis">{{ data.label }}</span>
<div v-if="data.nodeType === 'html' || data.nodeType === 'image'" class="tree-node-actions"> <div v-if="data.nodeType === 'html' || data.nodeType === 'image'" class="tree-node-actions">
<el-button <el-button type="primary" link size="small" @click.stop="previewNode(data)"> 预览 </el-button>
type="primary" <el-button type="primary" link size="small" @click.stop="downloadNode(data)"> 下载 </el-button>
link
size="small"
@click.stop="previewNode(data)"
>
预览
</el-button>
<el-button
type="primary"
link
size="small"
@click.stop="downloadNode(data)"
>
下载
</el-button>
</div> </div>
</div> </div>
</template> </template>
@@ -54,18 +40,17 @@
<template v-if="selectedElement.kind === 'node'"> <template v-if="selectedElement.kind === 'node'">
<!-- 模型选择如果有模型配置 --> <!-- 模型选择如果有模型配置 -->
<el-form-item v-if="currentNodeModelConfig.length > 0" label="选择模型"> <el-form-item v-if="currentNodeModelConfig.length > 0" label="选择模型">
<el-select v-model="selectedModel" placeholder="请选择模型" class="w100"> <div class="model-selector-wrapper">
<el-option <el-button type="primary" @click="showModelSelector = true" style="width: 100%">
v-for="modelConfig in currentNodeModelConfig" <el-icon><Plus /></el-icon>
:key="modelConfig.modelName" 选择模型
:label="modelConfig.modelName" </el-button>
:value="modelConfig.modelName" <div v-if="selectedModel" class="selected-model-tag">
/> <el-tag type="success" size="large" closable @close="handleRemoveModel">
</el-select> {{ selectedModel }}
</el-form-item> </el-tag>
<!-- 模型 API Key --> </div>
<el-form-item v-if="selectedModel" label="模型 API Key"> </div>
<el-input v-model="dynamicFormValues.modelApiKey" placeholder="请输入模型 API Key" type="password" show-password />
</el-form-item> </el-form-item>
<!-- 技能选择如果节点支持 --> <!-- 技能选择如果节点支持 -->
<el-form-item v-if="currentNodeSkillOption" label="选择技能"> <el-form-item v-if="currentNodeSkillOption" label="选择技能">
@@ -329,14 +314,7 @@
<el-input v-model="userInput" placeholder="说点什么..." class="chat-input" @keydown.enter="sendMessage" /> <el-input v-model="userInput" placeholder="说点什么..." class="chat-input" @keydown.enter="sendMessage" />
<!-- 右侧发送按钮 --> <!-- 右侧发送按钮 -->
<el-button <el-button type="primary" :icon="Promotion" :loading="isCreating" @click="sendMessage" class="send-btn" circle />
type="primary"
:icon="Promotion"
:loading="isCreating"
@click="sendMessage"
class="send-btn"
circle
/>
</div> </div>
<!-- 已选技能标签 --> <!-- 已选技能标签 -->
@@ -491,6 +469,9 @@
<!-- 创作技能选择器 --> <!-- 创作技能选择器 -->
<SkillSelector v-model="showCreationSkillSelector" :default-skill="selectedCreationSkill" @confirm="handleCreationSkillConfirm" /> <SkillSelector v-model="showCreationSkillSelector" :default-skill="selectedCreationSkill" @confirm="handleCreationSkillConfirm" />
<!-- 模型选择器 -->
<ModelSelector v-model="showModelSelector" :default-model="selectedModelData" @confirm="handleModelConfirm" />
<!-- 预览弹窗 --> <!-- 预览弹窗 -->
<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">
@@ -510,6 +491,7 @@ import { Control, SelectionSelect } from '@logicflow/extension';
import '@logicflow/core/dist/index.css'; import '@logicflow/core/dist/index.css';
import '@logicflow/extension/lib/style/index.css'; import '@logicflow/extension/lib/style/index.css';
import SkillSelector from '/@/components/skill/NodeSkillSelector.vue'; import SkillSelector from '/@/components/skill/NodeSkillSelector.vue';
import ModelSelector from '/@/components/model/ModelSelector.vue';
import type { SkillItem } from '/@/api/digitalHuman/skill'; import type { SkillItem } from '/@/api/digitalHuman/skill';
import { import {
downloadToFile, downloadToFile,
@@ -592,6 +574,9 @@ const isCreating = ref(false);
// 预览相关状态 // 预览相关状态
const previewDialogVisible = ref(false); const previewDialogVisible = ref(false);
const previewUrl = ref(''); const previewUrl = ref('');
// 模型选择器相关状态
const showModelSelector = ref(false);
const selectedModelData = ref<any>(null);
// 会话ID管理存储在 sessionStorage 中) // 会话ID管理存储在 sessionStorage 中)
const getSessionId = () => { const getSessionId = () => {
let sessionId = sessionStorage.getItem('ai_creation_session_id'); let sessionId = sessionStorage.getItem('ai_creation_session_id');
@@ -667,9 +652,8 @@ const currentNodeSkillOption = computed(() => {
}); });
// 获取当前选中模型的表单字段 // 获取当前选中模型的表单字段
const currentModelForm = computed<NodeLibraryFormItem[]>(() => { const currentModelForm = computed<NodeLibraryFormItem[]>(() => {
if (!selectedModel.value) return []; // 不显示模型的表单字段,返回空数组
const modelConfig = currentNodeModelConfig.value.find((m: any) => m.modelName === selectedModel.value); return [];
return modelConfig?.modelForm || [];
}); });
// 合并基础表单和模型表单 // 合并基础表单和模型表单
const allFormFields = computed<NodeLibraryFormItem[]>(() => { const allFormFields = computed<NodeLibraryFormItem[]>(() => {
@@ -886,36 +870,23 @@ const handleTemplatePageChange = (page: number) => {
templateWorkflowPagination.pageNum = page; templateWorkflowPagination.pageNum = page;
fetchWorkflowList(); fetchWorkflowList();
}; };
// 处理技能选择确认 // 处理技能选择确认(只更新临时状态,不保存到节点)
const handleSkillConfirm = (skill: SkillItem) => { const handleSkillConfirm = (skill: SkillItem) => {
selectedSkill.value = skill; selectedSkill.value = skill;
// 将技能名称保存到节点属性中(只保存 skillName
if (selectedElement.value && logicFlowInstance.value) {
const nodeData = logicFlowInstance.value.getNodeModelById(selectedElement.value.id);
if (nodeData) {
logicFlowInstance.value.setProperties(selectedElement.value.id, {
...nodeData.properties,
skillName: skill.name,
});
// 同步更新 selectedElement
selectedElement.value.properties.skillName = skill.name;
}
}
}; };
// 移除已选择的技能 // 移除已选择的技能(只更新临时状态)
const handleRemoveSkill = () => { const handleRemoveSkill = () => {
selectedSkill.value = null; selectedSkill.value = null;
// 从节点属性中移除技能信息 };
if (selectedElement.value && logicFlowInstance.value) { // 处理模型选择确认(只更新临时状态,不保存到节点)
const nodeData = logicFlowInstance.value.getNodeModelById(selectedElement.value.id); const handleModelConfirm = (model: any) => {
if (nodeData) { selectedModel.value = model.modelName;
const props = { ...nodeData.properties }; selectedModelData.value = model;
delete props.skillName; };
logicFlowInstance.value.setProperties(selectedElement.value.id, props); // 移除已选择的模型(只更新临时状态)
// 同步更新 selectedElement const handleRemoveModel = () => {
delete selectedElement.value.properties.skillName; selectedModel.value = '';
} selectedModelData.value = null;
}
}; };
// 使用工作流 // 使用工作流
const useWorkflow = async (workflow: WorkflowItem) => { const useWorkflow = async (workflow: WorkflowItem) => {
@@ -937,7 +908,17 @@ const useWorkflow = async (workflow: WorkflowItem) => {
if (node.formConfig && Array.isArray(node.formConfig)) { if (node.formConfig && Array.isArray(node.formConfig)) {
node.formConfig.forEach((field: any) => { node.formConfig.forEach((field: any) => {
const fieldKey = `${node.id}_${field.label}`; const fieldKey = `${node.id}_${field.label}`;
creationFormValues[fieldKey] = field.value || ''; // 根据字段类型转换值
if (field.type === 'number') {
// 数字类型:转换为数字或 null
creationFormValues[fieldKey] = field.value ? Number(field.value) : null;
} else if (field.type === 'switch') {
// 开关类型:转换为布尔值
creationFormValues[fieldKey] = Boolean(field.value);
} else {
// 其他类型:保持原值或空字符串
creationFormValues[fieldKey] = field.value || '';
}
}); });
} }
@@ -1060,9 +1041,7 @@ const sendMessage = async () => {
try { try {
const uploadRes = await uploadFile(file, { errorMode: 'page' }); const uploadRes = await uploadFile(file, { errorMode: 'page' });
// 拼接完整的文件地址 // 拼接完整的文件地址
const fullUrl = uploadRes.data.fileAddressPrefix const fullUrl = uploadRes.data.fileAddressPrefix ? `${uploadRes.data.fileAddressPrefix}${uploadRes.data.fileURL}` : uploadRes.data.fileURL;
? `${uploadRes.data.fileAddressPrefix}${uploadRes.data.fileURL}`
: uploadRes.data.fileURL;
fileUrls.push(fullUrl); fileUrls.push(fullUrl);
} catch (error) { } catch (error) {
ElMessage.error(`文件 ${file.name} 上传失败`); ElMessage.error(`文件 ${file.name} 上传失败`);
@@ -1072,43 +1051,44 @@ const sendMessage = async () => {
} }
// 2. 构建节点输入参数 // 2. 构建节点输入参数
const nodeInputParams = currentWorkflowForCreation.value.nodeInputParams?.map((node: any) => { const nodeInputParams =
const nodeParam: any = { currentWorkflowForCreation.value.nodeInputParams?.map((node: any) => {
id: node.id, const nodeParam: any = {
nodeCode: node.nodeCode, id: node.id,
name: node.name, nodeCode: node.nodeCode,
}; name: node.name,
};
// 添加表单配置和值 // 添加表单配置和值
if (node.formConfig && Array.isArray(node.formConfig)) { if (node.formConfig && Array.isArray(node.formConfig)) {
nodeParam.formConfig = node.formConfig.map((field: any) => { nodeParam.formConfig = node.formConfig.map((field: any) => {
const fieldKey = `${node.id}_${field.label}`; const fieldKey = `${node.id}_${field.label}`;
return { return {
...field, ...field,
value: creationFormValues[fieldKey] || field.value || '', value: creationFormValues[fieldKey] || field.value || '',
}; };
}); });
} }
// 添加其他配置 // 添加其他配置
if (node.config) { if (node.config) {
nodeParam.config = { ...node.config }; nodeParam.config = { ...node.config };
// 更新 config 中的值 // 更新 config 中的值
Object.keys(node.config).forEach((key) => { Object.keys(node.config).forEach((key) => {
const fieldKey = `${node.id}_${key}`; const fieldKey = `${node.id}_${key}`;
if (creationFormValues[fieldKey] !== undefined) { if (creationFormValues[fieldKey] !== undefined) {
nodeParam.config[key] = creationFormValues[fieldKey]; nodeParam.config[key] = creationFormValues[fieldKey];
} }
}); });
} }
// 添加其他字段 // 添加其他字段
if (node.inputSource) nodeParam.inputSource = node.inputSource; if (node.inputSource) nodeParam.inputSource = node.inputSource;
if (node.modelConfig) nodeParam.modelConfig = node.modelConfig; if (node.modelConfig) nodeParam.modelConfig = node.modelConfig;
if (node.skillName) nodeParam.skillName = node.skillName; if (node.skillName) nodeParam.skillName = node.skillName;
return nodeParam; return nodeParam;
}) || []; }) || [];
// 3. 同步更新 flowContent.nodes使其与 nodeInputParams 一致 // 3. 同步更新 flowContent.nodes使其与 nodeInputParams 一致
const updatedFlowContent = { const updatedFlowContent = {
@@ -1137,7 +1117,6 @@ const sendMessage = async () => {
userInput.value = ''; userInput.value = '';
selectedFiles.value = []; selectedFiles.value = [];
selectedCreationSkill.value = null; selectedCreationSkill.value = null;
} catch (error) { } catch (error) {
ElMessage.error('创作失败,请重试'); ElMessage.error('创作失败,请重试');
} finally { } finally {
@@ -1573,8 +1552,17 @@ watch(
if (e?.properties?.formConfig && Array.isArray(e.properties.formConfig)) { if (e?.properties?.formConfig && Array.isArray(e.properties.formConfig)) {
e.properties.formConfig.forEach((fieldConfig: any) => { e.properties.formConfig.forEach((fieldConfig: any) => {
if (baseFieldNames.has(fieldConfig.field)) { if (baseFieldNames.has(fieldConfig.field)) {
// 基础字段:加载到 dynamicFormValues // 基础字段:加载到 dynamicFormValues,根据类型转换值
dynamicFormValues[fieldConfig.field] = fieldConfig.value; if (fieldConfig.type === 'number') {
// 数字类型:转换为数字或 null
dynamicFormValues[fieldConfig.field] = fieldConfig.value ? Number(fieldConfig.value) : null;
} else if (fieldConfig.type === 'switch') {
// 开关类型:转换为布尔值
dynamicFormValues[fieldConfig.field] = Boolean(fieldConfig.value);
} else {
// 其他类型:保持原值
dynamicFormValues[fieldConfig.field] = fieldConfig.value;
}
} else { } else {
// 自定义字段:加载到 customFields // 自定义字段:加载到 customFields
customFields.value.push({ customFields.value.push({
@@ -1592,19 +1580,31 @@ watch(
if (modelConfig && typeof modelConfig === 'object') { if (modelConfig && typeof modelConfig === 'object') {
// 从 modelConfig 加载 // 从 modelConfig 加载
selectedModel.value = modelConfig.modelName || ''; selectedModel.value = modelConfig.modelName || '';
selectedModelData.value = modelConfig.modelName ? { modelName: modelConfig.modelName } : null;
dynamicFormValues.modelApiKey = modelConfig.modelApiKey || ''; dynamicFormValues.modelApiKey = modelConfig.modelApiKey || '';
// 加载模型表单数据(数组格式) // 加载模型表单数据(数组格式)
if (modelConfig.modelForm && Array.isArray(modelConfig.modelForm)) { if (modelConfig.modelForm && Array.isArray(modelConfig.modelForm)) {
modelConfig.modelForm.forEach((fieldConfig: any) => { modelConfig.modelForm.forEach((fieldConfig: any) => {
if (fieldConfig.field) { if (fieldConfig.field) {
dynamicFormValues[fieldConfig.field] = fieldConfig.value; // 根据字段类型转换值
if (fieldConfig.type === 'number') {
// 数字类型:转换为数字或 null
dynamicFormValues[fieldConfig.field] = fieldConfig.value ? Number(fieldConfig.value) : null;
} else if (fieldConfig.type === 'switch') {
// 开关类型:转换为布尔值
dynamicFormValues[fieldConfig.field] = Boolean(fieldConfig.value);
} else {
// 其他类型:保持原值
dynamicFormValues[fieldConfig.field] = fieldConfig.value;
}
} }
}); });
} }
} else { } else {
// 兼容旧数据格式 // 兼容旧数据格式
selectedModel.value = String(e?.properties?.selectedModel || ''); selectedModel.value = String(e?.properties?.selectedModel || '');
selectedModelData.value = e?.properties?.modelData || null;
dynamicFormValues.modelApiKey = e?.properties?.modelApiKey || ''; dynamicFormValues.modelApiKey = e?.properties?.modelApiKey || '';
} }
@@ -1642,9 +1642,18 @@ watch(
// 初始化所有表单字段(基础 + 模型)- 只设置还没有值的字段 // 初始化所有表单字段(基础 + 模型)- 只设置还没有值的字段
allFormFields.value.forEach((fieldItem) => { allFormFields.value.forEach((fieldItem) => {
// 如果已经从 formConfig 或 modelConfig 加载过,跳过 const currentValue = dynamicFormValues[fieldItem.field];
if (dynamicFormValues[fieldItem.field] !== undefined && dynamicFormValues[fieldItem.field] !== '') {
return; // 如果已经从 formConfig 或 modelConfig 加载过有效值,跳过
// 对于数字类型,空字符串不是有效值
if (fieldItem.type === 'number') {
if (currentValue !== undefined && currentValue !== '' && currentValue !== null) {
return;
}
} else {
if (currentValue !== undefined && currentValue !== '') {
return;
}
} }
// 使用默认值 // 使用默认值
@@ -1657,8 +1666,8 @@ watch(
if (fieldItem.type === 'switch') { if (fieldItem.type === 'switch') {
dynamicFormValues[fieldItem.field] = false; dynamicFormValues[fieldItem.field] = false;
} else if (fieldItem.type === 'number') { } else if (fieldItem.type === 'number') {
// 所有数字字段默认为 1 // 数字字段默认为 null而不是空字符串
dynamicFormValues[fieldItem.field] = 1; dynamicFormValues[fieldItem.field] = null;
} else { } else {
dynamicFormValues[fieldItem.field] = ''; dynamicFormValues[fieldItem.field] = '';
} }
@@ -1722,9 +1731,22 @@ const applySelected = () => {
modelApiKey: dynamicFormValues.modelApiKey || '', modelApiKey: dynamicFormValues.modelApiKey || '',
modelForm: modelForm, modelForm: modelForm,
}; };
// 保存模型选择状态
p.selectedModel = selectedModel.value;
p.modelData = selectedModelData.value;
} else { } else {
// 如果没有选择模型,删除 modelConfig // 如果没有选择模型,删除 modelConfig 和模型状态
delete p.modelConfig; delete p.modelConfig;
delete p.selectedModel;
delete p.modelData;
}
// 保存技能选择状态
if (selectedSkill.value) {
p.skillName = selectedSkill.value.name;
} else {
delete p.skillName;
} }
// 不再保存基础字段到根级别,所有字段都通过 formConfig 保存 // 不再保存基础字段到根级别,所有字段都通过 formConfig 保存
@@ -2054,6 +2076,34 @@ watch(isCreationMode, (newValue) => {
leftPanelTab.value = 'selected'; leftPanelTab.value = 'selected';
} }
}); });
// 监听选中元素变化,恢复模型和技能状态
watch(selectedElement, (newElement) => {
if (newElement && newElement.kind === 'node') {
// 从节点属性的 modelConfig 中恢复模型选择状态
const modelName = newElement.properties.modelConfig?.modelName || '';
if (modelName) {
selectedModel.value = modelName;
selectedModelData.value = { modelName };
} else {
selectedModel.value = '';
selectedModelData.value = null;
}
// 从节点属性中恢复技能选择状态
if (newElement.properties.skillName) {
selectedSkill.value = { name: newElement.properties.skillName } as SkillItem;
} else {
selectedSkill.value = null;
}
} else {
// 如果不是节点或没有选中元素,清空状态
selectedModel.value = '';
selectedModelData.value = null;
selectedSkill.value = null;
}
});
onMounted(async () => { onMounted(async () => {
await getList(); await getList();
await nextTick(); await nextTick();
@@ -3049,6 +3099,21 @@ onBeforeUnmount(() => {
padding: 8px 16px; padding: 8px 16px;
} }
.model-selector-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
}
.selected-model-tag {
margin-top: 12px;
}
.selected-model-tag .el-tag {
font-size: 14px;
padding: 8px 16px;
}
/* AI 创作输入区域样式 */ /* AI 创作输入区域样式 */
.creation-input-area { .creation-input-area {
padding: 20px; padding: 20px;

View File

@@ -40,9 +40,12 @@
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="请求头绑定" prop="headMsg"> <el-form-item label="请求头绑定" prop="headMsg">
<el-button @click="showHeaderDialog = true" style="width: 100%"> <el-button @click="showHeaderDialog = true" style="width: 100%"> 配置请求头 ({{ state.headers.length }}) </el-button>
配置请求头 ({{ state.headers.length }}) </el-form-item>
</el-button> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="自定义字段" prop="form">
<el-button @click="showFormDialog = true" style="width: 100%"> 配置表单字段 ({{ state.formFields.length }}) </el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
@@ -136,19 +139,38 @@
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
<!-- 自定义字段配置弹窗 -->
<el-dialog v-model="showFormDialog" title="配置表单字段" width="600px" :close-on-click-modal="false">
<div class="form-config-container">
<div v-for="(field, index) in state.formFields" :key="index" class="form-field-item">
<el-input v-model="field.key" placeholder="请输入字段名 (Key)" style="width: 40%" clearable></el-input>
<span class="separator">=</span>
<el-input v-model="field.value" placeholder="请输入字段值 (Value)" style="width: 40%" clearable></el-input>
<el-button type="danger" link @click="removeFormField(index)">删除</el-button>
</div>
<el-button type="primary" link @click="addFormField" style="margin-top: 10px">+ 添加字段</el-button>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showFormDialog = false" size="default"> </el-button>
<el-button type="primary" @click="confirmFormFields" size="default"> </el-button>
</span>
</template>
</el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts" name="systemEditModule"> <script setup lang="ts" name="systemEditModule">
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { ArrowUp, ArrowDown } from '@element-plus/icons-vue'; import { ArrowUp, ArrowDown } from '@element-plus/icons-vue';
import { addModelModule, updateModelModule } from '/@/api/digitalHuman/modelConfig/modelModule/index'; import { addModelModule, updateModelModule } from '/@/api/digitalHuman/modelConfig/modelModule/index';
const editModuleFormRef = ref(); const editModuleFormRef = ref();
const emit = defineEmits(['refresh']); const emit = defineEmits(['refresh']);
const showHeaderDialog = ref(false); const showHeaderDialog = ref(false);
const showFormDialog = ref(false);
const state = reactive({ const state = reactive({
ruleForm: { ruleForm: {
id: '', id: '',
@@ -188,6 +210,7 @@ const state = reactive({
}, },
showAdvanced: false, // 是否展开高级配置 showAdvanced: false, // 是否展开高级配置
headers: [] as Array<{ key: string; value: string }>, // 请求头列表 headers: [] as Array<{ key: string; value: string }>, // 请求头列表
formFields: [] as Array<{ key: string; value: string }>,
}); });
// 解析 headMsg 字符串为数组 // 解析 headMsg 字符串为数组
@@ -203,6 +226,30 @@ const parseHeaders = (headMsg: string) => {
}); });
return headers; return headers;
}; };
// 解析 form 对象为数组
const parseFormFields = (form: any) => {
if (!form || typeof form !== 'object') return [];
const fields: Array<{ key: string; value: string }> = [];
Object.keys(form).forEach((key) => {
if (form[key] && typeof form[key] === 'object' && form[key].value !== undefined) {
fields.push({ key, value: form[key].value });
}
});
return fields;
};
// 将数组转换为 form 对象
const stringifyFormFields = () => {
const formObj: Record<string, { value: string }> = {};
state.formFields.forEach((field) => {
const key = field.key?.trim();
const value = field.value?.trim();
if (key && value) {
formObj[key] = { value: value };
}
});
return formObj;
};
// 将数组转换为 headMsg 字符串 // 将数组转换为 headMsg 字符串
const stringifyHeaders = () => { const stringifyHeaders = () => {
@@ -227,6 +274,20 @@ const confirmHeaders = () => {
state.ruleForm.headMsg = stringifyHeaders(); state.ruleForm.headMsg = stringifyHeaders();
showHeaderDialog.value = false; showHeaderDialog.value = false;
}; };
// 添加表单字段
const addFormField = () => {
state.formFields.push({ key: '', value: '' });
};
// 删除表单字段
const removeFormField = (index: number) => {
state.formFields.splice(index, 1);
};
// 确认表单字段配置
const confirmFormFields = () => {
showFormDialog.value = false;
};
// 打开弹窗 // 打开弹窗
const openDialog = (type: string, row?: any) => { const openDialog = (type: string, row?: any) => {
@@ -252,6 +313,7 @@ const openDialog = (type: string, row?: any) => {
}; };
// 解析请求头 // 解析请求头
state.headers = parseHeaders(row.headMsg || ''); state.headers = parseHeaders(row.headMsg || '');
state.formFields = parseFormFields(row.form);
state.dialog.title = '修改模型配置'; state.dialog.title = '修改模型配置';
state.dialog.submitTxt = '修 改'; state.dialog.submitTxt = '修 改';
} else { } else {
@@ -275,6 +337,7 @@ const openDialog = (type: string, row?: any) => {
}; };
// 初始化一个空的请求头 // 初始化一个空的请求头
state.headers = [{ key: '', value: '' }]; state.headers = [{ key: '', value: '' }];
state.formFields = [{ key: '', value: '' }];
state.dialog.title = '新增模型配置'; state.dialog.title = '新增模型配置';
state.dialog.submitTxt = '新 增'; state.dialog.submitTxt = '新 增';
} }
@@ -304,15 +367,7 @@ const onSubmit = () => {
// 将 headMsg 转换为 form 的 JSON 格式 // 将 headMsg 转换为 form 的 JSON 格式
// headMsg: "X-API-Key:xxx,operation:true" // headMsg: "X-API-Key:xxx,operation:true"
// 转换为 form: { "X-API-Key": { "value": "xxx" }, "operation": { "value": "true" } } // 转换为 form: { "X-API-Key": { "value": "xxx" }, "operation": { "value": "true" } }
const formObj: Record<string, { value: string }> = {}; const formObj = stringifyFormFields();
state.headers.forEach((header) => {
// 去除 key 和 value 的首尾空格
const key = header.key?.trim();
const value = header.value?.trim();
if (key && value) {
formObj[key] = { value: value };
}
});
// 提交数据 // 提交数据
const submitData = { const submitData = {
@@ -330,7 +385,7 @@ const onSubmit = () => {
closeDialog(); closeDialog();
emit('refresh'); emit('refresh');
} catch (error) { } catch (error) {
console.error('保存失败:', error); ElMessage.error('保存失败');
} finally { } finally {
state.dialog.loading = false; state.dialog.loading = false;
} }
@@ -344,6 +399,17 @@ defineExpose({
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.form-config-container {
max-height: 400px;
overflow-y: auto;
}
.form-field-item {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}
.ml5 { .ml5 {
margin-left: 5px; margin-left: 5px;
} }