feat: 更新数字人创作页面以支持工作流和输入功能
- 修改工作流项接口,新增流模板名称和用户流列表支持 - 引入新的输入区域,允许用户上传文件并选择创作技能 - 实现工作流列表的Tab切换,分为“我的工作流”和“模板工作流” - 优化界面布局和交互,提升用户体验
This commit is contained in:
@@ -144,7 +144,8 @@ export function saveWorkflow(data: { flowName: string; description: string; flow
|
||||
|
||||
export interface WorkflowItem {
|
||||
id: string;
|
||||
flowName: string;
|
||||
flowName?: string;
|
||||
flowTemplateName?: string;
|
||||
description: string;
|
||||
flowContent: any;
|
||||
nodeInputParams?: any[];
|
||||
@@ -154,7 +155,15 @@ export interface WorkflowListResponse {
|
||||
code: number;
|
||||
message: string;
|
||||
data: {
|
||||
listFlowUserRes: {
|
||||
list: WorkflowItem[];
|
||||
total: number;
|
||||
};
|
||||
listFlowTemplateRes: {
|
||||
list: WorkflowItem[];
|
||||
total: number;
|
||||
};
|
||||
isAdmin?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -198,3 +207,72 @@ export function deleteWorkflow(id: string, requestOptions?: RequestOptions) {
|
||||
requestOptions,
|
||||
});
|
||||
}
|
||||
|
||||
// 执行工作流相关类型定义
|
||||
export interface FlowNodeFormField {
|
||||
default?: any;
|
||||
field?: string;
|
||||
label?: string;
|
||||
options?: { label?: string; value?: string }[];
|
||||
required?: boolean;
|
||||
type?: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface FlowNodeInputSource {
|
||||
field?: string[];
|
||||
nodeId?: string;
|
||||
quoteOutput?: boolean;
|
||||
}
|
||||
|
||||
export interface FlowNodeModelItem {
|
||||
modelApiKey?: string;
|
||||
modelForm?: FlowNodeFormField[];
|
||||
modelName?: string;
|
||||
}
|
||||
|
||||
export interface FlowNode {
|
||||
config?: { [key: string]: any };
|
||||
formConfig?: FlowNodeFormField[];
|
||||
id?: string;
|
||||
inputSource?: FlowNodeInputSource[];
|
||||
modelConfig?: FlowNodeModelItem;
|
||||
name?: string;
|
||||
nodeCode?: string;
|
||||
outputResult?: FlowNodeFormField[];
|
||||
skillName?: string;
|
||||
}
|
||||
|
||||
export interface FlowEdge {
|
||||
from?: string;
|
||||
id?: string;
|
||||
to?: string;
|
||||
}
|
||||
|
||||
export interface FlowInfo {
|
||||
edges?: FlowEdge[];
|
||||
nodes?: FlowNode[];
|
||||
startNodeId?: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface ExecuteFlowParams {
|
||||
desc?: string;
|
||||
fileUrl?: string[];
|
||||
flowContent?: FlowInfo;
|
||||
flowId?: number;
|
||||
nodeInputParams?: FlowNode[];
|
||||
sessionId?: string;
|
||||
skillName?: string;
|
||||
}
|
||||
|
||||
export function executeFlow(data: ExecuteFlowParams | FormData, requestOptions?: RequestOptions) {
|
||||
return request({
|
||||
url: '/ai-agent/flow/execution/execute',
|
||||
method: 'post',
|
||||
data,
|
||||
headers: data instanceof FormData ? { 'Content-Type': 'multipart/form-data' } : undefined,
|
||||
timeout: 0,
|
||||
requestOptions,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -296,8 +296,45 @@
|
||||
<el-empty v-else description="暂无表单配置" :image-size="100" />
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="creation-actions">
|
||||
<el-button type="primary" size="large" class="w100">开始创作</el-button>
|
||||
|
||||
<!-- AI 创作输入区域 -->
|
||||
<div class="creation-input-area">
|
||||
<!-- 已选文件列表 -->
|
||||
<div v-if="selectedFiles.length > 0" class="selected-files-top">
|
||||
<el-tag v-for="(file, index) in selectedFiles" :key="index" closable @close="removeFile(index)" type="info" size="small">
|
||||
{{ file.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 输入框容器 -->
|
||||
<div class="chat-input-container">
|
||||
<!-- 左侧工具按钮 -->
|
||||
<div class="input-tools-left">
|
||||
<el-upload :auto-upload="false" :show-file-list="false" :on-change="handleFileSelect" multiple>
|
||||
<el-button text :icon="Paperclip" class="tool-btn" />
|
||||
</el-upload>
|
||||
<el-button text :icon="MagicStick" @click="showCreationSkillSelector = true" class="tool-btn" />
|
||||
</div>
|
||||
|
||||
<!-- 输入框 -->
|
||||
<el-input v-model="userInput" placeholder="说点什么..." class="chat-input" @keydown.enter="sendMessage" />
|
||||
|
||||
<!-- 右侧发送按钮 -->
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="Promotion"
|
||||
:loading="isCreating"
|
||||
:disabled="!userInput.trim()"
|
||||
@click="sendMessage"
|
||||
class="send-btn"
|
||||
circle
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 已选技能标签 -->
|
||||
<div v-if="selectedCreationSkill" class="selected-skill-bottom">
|
||||
<el-tag type="success" closable @close="selectedCreationSkill = null" size="small"> 技能: {{ selectedCreationSkill.name }} </el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -343,15 +380,18 @@
|
||||
|
||||
<!-- 右侧:工作流列表(竖状) -->
|
||||
<div class="panel right-panel">
|
||||
<el-tabs v-model="workflowTab" class="workflow-tabs">
|
||||
<!-- Tab 1: 我的工作流 -->
|
||||
<el-tab-pane label="我的工作流" name="user">
|
||||
<div class="right-panel-header">
|
||||
<div class="title-sm">我的工作流</div>
|
||||
<el-button type="success" size="small" @click="createNewWorkflow">新建</el-button>
|
||||
<el-button type="primary" link size="small" @click="refreshWorkflowList">刷新</el-button>
|
||||
</div>
|
||||
<div class="workflow-list-vertical" v-loading="workflowListLoading">
|
||||
<el-empty v-if="!workflowListLoading && workflowList.length === 0" description="暂无工作流" :image-size="60" />
|
||||
<el-empty v-if="!workflowListLoading && userWorkflowList.length === 0" description="暂无工作流" :image-size="60" />
|
||||
<div v-else class="workflow-list-scroll">
|
||||
<div
|
||||
v-for="workflow in workflowList"
|
||||
v-for="workflow in userWorkflowList"
|
||||
:key="workflow.id"
|
||||
class="workflow-item"
|
||||
:class="{ active: currentEditingWorkflowId === workflow.id }"
|
||||
@@ -369,15 +409,51 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分页 -->
|
||||
<div v-if="workflowPagination.total > 0" class="workflow-pagination">
|
||||
<div v-if="userWorkflowPagination.total > 0" class="workflow-pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="workflowPagination.pageNum"
|
||||
:page-size="workflowPagination.pageSize"
|
||||
:total="workflowPagination.total"
|
||||
v-model:current-page="userWorkflowPagination.pageNum"
|
||||
:page-size="userWorkflowPagination.pageSize"
|
||||
:total="userWorkflowPagination.total"
|
||||
layout="prev, pager, next"
|
||||
@current-change="handlePageChange"
|
||||
@current-change="handleUserPageChange"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Tab 2: 模板工作流 -->
|
||||
<el-tab-pane label="模板工作流" name="template">
|
||||
<div class="right-panel-header">
|
||||
<el-button v-if="isAdmin" type="success" size="small" @click="createNewWorkflow">新建</el-button>
|
||||
<el-button type="primary" link size="small" @click="refreshWorkflowList">刷新</el-button>
|
||||
</div>
|
||||
<div class="workflow-list-vertical" v-loading="workflowListLoading">
|
||||
<el-empty v-if="!workflowListLoading && templateWorkflowList.length === 0" description="暂无模板" :image-size="60" />
|
||||
<div v-else class="workflow-list-scroll">
|
||||
<div v-for="workflow in templateWorkflowList" :key="workflow.id" class="workflow-item" @click="useWorkflow(workflow)">
|
||||
<div class="workflow-item-content">
|
||||
<div class="workflow-item-name">{{ workflow.flowName || workflow.flowTemplateName }}</div>
|
||||
<div class="workflow-item-desc">{{ workflow.description || '暂无描述' }}</div>
|
||||
</div>
|
||||
<div class="workflow-item-actions">
|
||||
<el-button type="primary" link size="small" @click.stop="editWorkflow(workflow)">编辑</el-button>
|
||||
<el-button type="success" link size="small" @click.stop="useWorkflow(workflow)">使用</el-button>
|
||||
<el-button v-if="isAdmin" type="danger" link size="small" @click.stop="deleteWorkflowAction(workflow)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分页 -->
|
||||
<div v-if="templateWorkflowPagination.total > 0" class="workflow-pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="templateWorkflowPagination.pageNum"
|
||||
:page-size="templateWorkflowPagination.pageSize"
|
||||
:total="templateWorkflowPagination.total"
|
||||
layout="prev, pager, next"
|
||||
@current-change="handleTemplatePageChange"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 保存工作流对话框 -->
|
||||
@@ -403,13 +479,16 @@
|
||||
|
||||
<!-- 技能选择器 -->
|
||||
<SkillSelector v-model="showSkillSelector" :default-skill="selectedSkill" @confirm="handleSkillConfirm" />
|
||||
|
||||
<!-- 创作技能选择器 -->
|
||||
<SkillSelector v-model="showCreationSkillSelector" :default-skill="selectedCreationSkill" @confirm="handleCreationSkillConfirm" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Document, Plus } from '@element-plus/icons-vue';
|
||||
import { Document, Plus, Paperclip, MagicStick, Promotion } from '@element-plus/icons-vue';
|
||||
import LogicFlow from '@logicflow/core';
|
||||
import { Control, SelectionSelect } from '@logicflow/extension';
|
||||
import '@logicflow/core/dist/index.css';
|
||||
@@ -425,11 +504,13 @@ import {
|
||||
updateWorkflow,
|
||||
deleteWorkflow,
|
||||
saveWorkflow,
|
||||
executeFlow,
|
||||
type CreationListParams,
|
||||
type CreationTreeItem,
|
||||
type NodeLibraryFormItem,
|
||||
type NodeLibraryGroup,
|
||||
type WorkflowItem,
|
||||
type ExecuteFlowParams,
|
||||
} from '/@/api/digitalHuman/creation';
|
||||
|
||||
type NodeType = 'date' | 'contentType' | 'theme' | 'title' | 'html' | 'image';
|
||||
@@ -467,17 +548,40 @@ const saveForm = reactive({
|
||||
flowName: '',
|
||||
description: '',
|
||||
});
|
||||
const workflowList = ref<WorkflowItem[]>([]);
|
||||
const workflowListLoading = ref(false);
|
||||
const currentEditingWorkflowId = ref<string | null>(null);
|
||||
const isCreationMode = ref(false); // 是否处于创作模式
|
||||
const currentWorkflowForCreation = ref<any>(null); // 当前用于创作的工作流数据
|
||||
const creationFormValues = reactive<Record<string, any>>({}); // 创作表单的值
|
||||
const workflowPagination = reactive({
|
||||
const workflowTab = ref('user'); // 工作流 Tab:user 或 template
|
||||
const userWorkflowList = ref<WorkflowItem[]>([]); // 用户工作流列表
|
||||
const templateWorkflowList = ref<WorkflowItem[]>([]); // 模板工作流列表
|
||||
const isAdmin = ref(false); // 是否为管理员
|
||||
const userWorkflowPagination = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
const templateWorkflowPagination = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
// AI 创作输入相关状态
|
||||
const userInput = ref('');
|
||||
const selectedFiles = ref<File[]>([]);
|
||||
const selectedCreationSkill = ref<SkillItem | null>(null);
|
||||
const showCreationSkillSelector = ref(false);
|
||||
const isCreating = ref(false);
|
||||
// 会话ID管理(存储在 sessionStorage 中)
|
||||
const getSessionId = () => {
|
||||
let sessionId = sessionStorage.getItem('ai_creation_session_id');
|
||||
if (!sessionId) {
|
||||
sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
sessionStorage.setItem('ai_creation_session_id', sessionId);
|
||||
}
|
||||
return sessionId;
|
||||
};
|
||||
// 格式化参数引用显示
|
||||
const formatParamReference = (value: string) => {
|
||||
// 从 ${nodeId.field} 提取节点名和字段名
|
||||
@@ -715,28 +819,61 @@ const fetchWorkflowList = async () => {
|
||||
workflowListLoading.value = true;
|
||||
try {
|
||||
const res = await getWorkflowList({ errorMode: 'page' });
|
||||
const allWorkflows = res.data?.list || [];
|
||||
workflowPagination.total = allWorkflows.length;
|
||||
|
||||
// 计算分页
|
||||
const start = (workflowPagination.pageNum - 1) * workflowPagination.pageSize;
|
||||
const end = start + workflowPagination.pageSize;
|
||||
workflowList.value = allWorkflows.slice(start, end);
|
||||
// 分别处理用户工作流和模板工作流
|
||||
const userWorkflows = res.data?.listFlowUserRes?.list || [];
|
||||
const templateWorkflows = res.data?.listFlowTemplateRes?.list || [];
|
||||
|
||||
// 获取管理员权限
|
||||
isAdmin.value = res.data?.isAdmin || false;
|
||||
|
||||
// 用户工作流分页
|
||||
userWorkflowPagination.total = userWorkflows.length;
|
||||
const userStart = (userWorkflowPagination.pageNum - 1) * userWorkflowPagination.pageSize;
|
||||
const userEnd = userStart + userWorkflowPagination.pageSize;
|
||||
userWorkflowList.value = userWorkflows.slice(userStart, userEnd);
|
||||
|
||||
// 模板工作流分页
|
||||
templateWorkflowPagination.total = templateWorkflows.length;
|
||||
const templateStart = (templateWorkflowPagination.pageNum - 1) * templateWorkflowPagination.pageSize;
|
||||
const templateEnd = templateStart + templateWorkflowPagination.pageSize;
|
||||
templateWorkflowList.value = templateWorkflows.slice(templateStart, templateEnd);
|
||||
} catch {
|
||||
workflowList.value = [];
|
||||
workflowPagination.total = 0;
|
||||
userWorkflowList.value = [];
|
||||
templateWorkflowList.value = [];
|
||||
userWorkflowPagination.total = 0;
|
||||
templateWorkflowPagination.total = 0;
|
||||
isAdmin.value = false;
|
||||
} finally {
|
||||
workflowListLoading.value = false;
|
||||
}
|
||||
};
|
||||
// 刷新工作流列表
|
||||
const refreshWorkflowList = () => {
|
||||
workflowPagination.pageNum = 1;
|
||||
userWorkflowPagination.pageNum = 1;
|
||||
templateWorkflowPagination.pageNum = 1;
|
||||
fetchWorkflowList();
|
||||
};
|
||||
// 处理分页变化
|
||||
const handlePageChange = (page: number) => {
|
||||
workflowPagination.pageNum = page;
|
||||
// 新建工作流
|
||||
const createNewWorkflow = () => {
|
||||
// 切换回画布编辑模式
|
||||
isCreationMode.value = false;
|
||||
currentWorkflowForCreation.value = null;
|
||||
|
||||
// 清空当前编辑状态
|
||||
currentEditingWorkflowId.value = null;
|
||||
|
||||
// 重置画布
|
||||
resetFlow();
|
||||
};
|
||||
// 处理用户工作流分页变化
|
||||
const handleUserPageChange = (page: number) => {
|
||||
userWorkflowPagination.pageNum = page;
|
||||
fetchWorkflowList();
|
||||
};
|
||||
// 处理模板工作流分页变化
|
||||
const handleTemplatePageChange = (page: number) => {
|
||||
templateWorkflowPagination.pageNum = page;
|
||||
fetchWorkflowList();
|
||||
};
|
||||
// 处理技能选择确认
|
||||
@@ -754,7 +891,6 @@ const handleSkillConfirm = (skill: SkillItem) => {
|
||||
selectedElement.value.properties.skillName = skill.name;
|
||||
}
|
||||
}
|
||||
ElMessage.success('技能选择成功');
|
||||
};
|
||||
// 移除已选择的技能
|
||||
const handleRemoveSkill = () => {
|
||||
@@ -807,7 +943,7 @@ const useWorkflow = async (workflow: WorkflowItem) => {
|
||||
});
|
||||
}
|
||||
|
||||
ElMessage.success(`已进入创作模式:${res.data.flowName}`);
|
||||
ElMessage.success(`已进入创作模式`);
|
||||
} else {
|
||||
ElMessage.warning('该工作流没有内容');
|
||||
}
|
||||
@@ -831,9 +967,8 @@ const editWorkflow = async (workflow: WorkflowItem) => {
|
||||
loadWorkflowFromDsl(res.data.flowContent);
|
||||
// 预填充保存表单,并记录当前编辑的工作流ID
|
||||
currentEditingWorkflowId.value = res.data.id;
|
||||
saveForm.flowName = res.data.flowName;
|
||||
saveForm.flowName = res.data.flowName || res.data.flowTemplateName || '';
|
||||
saveForm.description = res.data.description || '';
|
||||
ElMessage.success(`正在编辑工作流:${res.data.flowName}`);
|
||||
} else {
|
||||
ElMessage.warning('该工作流没有内容');
|
||||
}
|
||||
@@ -857,25 +992,12 @@ const backToCanvas = async () => {
|
||||
lf.zoom(1);
|
||||
lf.translateCenter();
|
||||
}
|
||||
|
||||
ElMessage.info('已返回画布编辑模式');
|
||||
};
|
||||
// 判断节点是否有可见字段
|
||||
const hasVisibleFields = (node: any) => {
|
||||
if (!node.config) return false;
|
||||
const excludeKeys = ['nodeCode', 'width', 'height', 'x', 'y', 'formConfig', 'inputSource', 'fieldMetadata', 'selectedModel'];
|
||||
return Object.keys(node.config).some((key) => !excludeKeys.includes(key));
|
||||
};
|
||||
// 根据字段类型返回CSS类名
|
||||
const getFieldClass = (type: string) => {
|
||||
if (type === 'textarea') return 'form-item-full';
|
||||
if (type === 'number' || type === 'switch') return 'form-item-small';
|
||||
return 'form-item-medium';
|
||||
};
|
||||
// 删除工作流
|
||||
const deleteWorkflowAction = async (workflow: WorkflowItem) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定要删除工作流"${workflow.flowName}"吗?此操作不可恢复。`, '删除确认', {
|
||||
const workflowName = workflow.flowName || workflow.flowTemplateName || '该工作流';
|
||||
await ElMessageBox.confirm(`确定要删除工作流"${workflowName}"吗?此操作不可恢复。`, '删除确认', {
|
||||
confirmButtonText: '确定删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
@@ -899,6 +1021,136 @@ const deleteWorkflowAction = async (workflow: WorkflowItem) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
// 处理文件选择
|
||||
const handleFileSelect = (file: any) => {
|
||||
selectedFiles.value.push(file.raw);
|
||||
};
|
||||
// 移除文件
|
||||
const removeFile = (index: number) => {
|
||||
selectedFiles.value.splice(index, 1);
|
||||
};
|
||||
// 处理创作技能选择
|
||||
const handleCreationSkillConfirm = (skill: SkillItem) => {
|
||||
selectedCreationSkill.value = skill;
|
||||
};
|
||||
// 发送消息/开始创作
|
||||
const sendMessage = async () => {
|
||||
if (!userInput.value.trim()) {
|
||||
ElMessage.warning('请输入创作需求');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentWorkflowForCreation.value) {
|
||||
ElMessage.warning('请先选择一个工作流');
|
||||
return;
|
||||
}
|
||||
|
||||
isCreating.value = true;
|
||||
|
||||
try {
|
||||
// 1. 构建节点输入参数
|
||||
const nodeInputParams = currentWorkflowForCreation.value.nodeInputParams?.map((node: any) => {
|
||||
const nodeParam: any = {
|
||||
id: node.id,
|
||||
nodeCode: node.nodeCode,
|
||||
name: node.name,
|
||||
};
|
||||
|
||||
// 添加表单配置和值
|
||||
if (node.formConfig && Array.isArray(node.formConfig)) {
|
||||
nodeParam.formConfig = node.formConfig.map((field: any) => {
|
||||
const fieldKey = `${node.id}_${field.label}`;
|
||||
return {
|
||||
...field,
|
||||
value: creationFormValues[fieldKey] || field.value || '',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 添加其他配置
|
||||
if (node.config) {
|
||||
nodeParam.config = { ...node.config };
|
||||
// 更新 config 中的值
|
||||
Object.keys(node.config).forEach((key) => {
|
||||
const fieldKey = `${node.id}_${key}`;
|
||||
if (creationFormValues[fieldKey] !== undefined) {
|
||||
nodeParam.config[key] = creationFormValues[fieldKey];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 添加其他字段
|
||||
if (node.inputSource) nodeParam.inputSource = node.inputSource;
|
||||
if (node.modelConfig) nodeParam.modelConfig = node.modelConfig;
|
||||
if (node.skillName) nodeParam.skillName = node.skillName;
|
||||
|
||||
return nodeParam;
|
||||
}) || [];
|
||||
|
||||
// 2. 同步更新 flowContent.nodes,使其与 nodeInputParams 一致
|
||||
const updatedFlowContent = {
|
||||
...currentWorkflowForCreation.value.flowContent,
|
||||
nodes: nodeInputParams, // 使用更新后的节点参数
|
||||
};
|
||||
|
||||
// 3. 构建请求参数
|
||||
const params: ExecuteFlowParams = {
|
||||
flowId: parseInt(currentWorkflowForCreation.value.id),
|
||||
flowContent: updatedFlowContent,
|
||||
nodeInputParams: nodeInputParams,
|
||||
sessionId: getSessionId(),
|
||||
desc: userInput.value,
|
||||
skillName: selectedCreationSkill.value?.name,
|
||||
};
|
||||
|
||||
// 4. 使用 FormData 传递文件流
|
||||
const formData = new FormData();
|
||||
|
||||
// 添加文件
|
||||
if (selectedFiles.value.length > 0) {
|
||||
selectedFiles.value.forEach((file) => {
|
||||
formData.append('files', file);
|
||||
});
|
||||
}
|
||||
|
||||
// 添加其他参数(转为 JSON 字符串)
|
||||
formData.append('flowId', params.flowId!.toString());
|
||||
formData.append('flowContent', JSON.stringify(params.flowContent));
|
||||
formData.append('nodeInputParams', JSON.stringify(params.nodeInputParams));
|
||||
formData.append('sessionId', params.sessionId!);
|
||||
formData.append('desc', params.desc!);
|
||||
if (params.skillName) {
|
||||
formData.append('skillName', params.skillName);
|
||||
}
|
||||
|
||||
// 5. 调用执行接口
|
||||
await executeFlow(formData, { errorMode: 'page' });
|
||||
|
||||
ElMessage.success('创作完成!');
|
||||
|
||||
// 6. 清空输入
|
||||
userInput.value = '';
|
||||
selectedFiles.value = [];
|
||||
selectedCreationSkill.value = null;
|
||||
|
||||
} catch (error) {
|
||||
ElMessage.error('创作失败,请重试');
|
||||
} finally {
|
||||
isCreating.value = false;
|
||||
}
|
||||
};
|
||||
// 判断节点是否有可见字段
|
||||
const hasVisibleFields = (node: any) => {
|
||||
if (!node.config) return false;
|
||||
const excludeKeys = ['nodeCode', 'width', 'height', 'x', 'y', 'formConfig', 'inputSource', 'fieldMetadata', 'selectedModel'];
|
||||
return Object.keys(node.config).some((key) => !excludeKeys.includes(key));
|
||||
};
|
||||
// 根据字段类型返回CSS类名
|
||||
const getFieldClass = (type: string) => {
|
||||
if (type === 'textarea') return 'form-item-full';
|
||||
if (type === 'number' || type === 'switch') return 'form-item-small';
|
||||
return 'form-item-medium';
|
||||
};
|
||||
const handleNodeClick = (d: TreeNode) => {
|
||||
if (d.nodeType !== 'html' && d.nodeType !== 'image') return;
|
||||
const url = buildAssetUrl(d.fileUrl);
|
||||
@@ -1536,7 +1788,7 @@ const applySelected = () => {
|
||||
|
||||
syncDsl();
|
||||
|
||||
ElMessage.success('已更新当前元素配置');
|
||||
// 静默更新,不显示提示
|
||||
} catch (error) {
|
||||
ElMessage.error('应用配置失败,请查看控制台');
|
||||
}
|
||||
@@ -1734,7 +1986,6 @@ const resetFlow = () => {
|
||||
nodeSpawnIndex.value = 0;
|
||||
selectedElement.value = null;
|
||||
syncDsl();
|
||||
ElMessage.success('流程已重置');
|
||||
};
|
||||
// 从后端 DSL 恢复工作流
|
||||
const loadWorkflowFromDsl = (dsl: any) => {
|
||||
@@ -1781,7 +2032,6 @@ const loadWorkflowFromDsl = (dsl: any) => {
|
||||
lf.zoom(1);
|
||||
lf.translateCenter();
|
||||
syncDsl();
|
||||
ElMessage.success('工作流已加载');
|
||||
} catch (error) {
|
||||
ElMessage.error('工作流加载失败');
|
||||
}
|
||||
@@ -2317,7 +2567,7 @@ onBeforeUnmount(() => {
|
||||
min-height: 100px;
|
||||
}
|
||||
.workflow-pagination {
|
||||
margin-top: 20px;
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 12px 0;
|
||||
@@ -2671,10 +2921,28 @@ onBeforeUnmount(() => {
|
||||
box-shadow: 0 4px 18px rgba(15, 23, 42, 0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
.workflow-tabs {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.workflow-tabs :deep(.el-tabs__content) {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.workflow-tabs :deep(.el-tab-pane) {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.right-panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -2762,4 +3030,75 @@ onBeforeUnmount(() => {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
/* AI 创作输入区域样式 */
|
||||
.creation-input-area {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
.selected-files-top {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.chat-input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 24px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.chat-input-container:focus-within {
|
||||
border-color: #3b82f6;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
.input-tools-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.tool-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
font-size: 18px;
|
||||
color: #64748b;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.tool-btn:hover {
|
||||
color: #3b82f6;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
.chat-input {
|
||||
flex: 1;
|
||||
}
|
||||
.chat-input :deep(.el-input__wrapper) {
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
.chat-input :deep(.el-input__inner) {
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
}
|
||||
.send-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.send-btn:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.selected-skill-bottom {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user