feat: 更新数字人创作页面以支持工作流和输入功能
- 修改工作流项接口,新增流模板名称和用户流列表支持 - 引入新的输入区域,允许用户上传文件并选择创作技能 - 实现工作流列表的Tab切换,分为“我的工作流”和“模板工作流” - 优化界面布局和交互,提升用户体验
This commit is contained in:
@@ -144,7 +144,8 @@ export function saveWorkflow(data: { flowName: string; description: string; flow
|
|||||||
|
|
||||||
export interface WorkflowItem {
|
export interface WorkflowItem {
|
||||||
id: string;
|
id: string;
|
||||||
flowName: string;
|
flowName?: string;
|
||||||
|
flowTemplateName?: string;
|
||||||
description: string;
|
description: string;
|
||||||
flowContent: any;
|
flowContent: any;
|
||||||
nodeInputParams?: any[];
|
nodeInputParams?: any[];
|
||||||
@@ -154,7 +155,15 @@ export interface WorkflowListResponse {
|
|||||||
code: number;
|
code: number;
|
||||||
message: string;
|
message: string;
|
||||||
data: {
|
data: {
|
||||||
list: WorkflowItem[];
|
listFlowUserRes: {
|
||||||
|
list: WorkflowItem[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
listFlowTemplateRes: {
|
||||||
|
list: WorkflowItem[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
isAdmin?: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,3 +207,72 @@ export function deleteWorkflow(id: string, requestOptions?: 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-empty v-else description="暂无表单配置" :image-size="100" />
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -343,41 +380,80 @@
|
|||||||
|
|
||||||
<!-- 右侧:工作流列表(竖状) -->
|
<!-- 右侧:工作流列表(竖状) -->
|
||||||
<div class="panel right-panel">
|
<div class="panel right-panel">
|
||||||
<div class="right-panel-header">
|
<el-tabs v-model="workflowTab" class="workflow-tabs">
|
||||||
<div class="title-sm">我的工作流</div>
|
<!-- Tab 1: 我的工作流 -->
|
||||||
<el-button type="primary" link size="small" @click="refreshWorkflowList">刷新</el-button>
|
<el-tab-pane label="我的工作流" name="user">
|
||||||
</div>
|
<div class="right-panel-header">
|
||||||
<div class="workflow-list-vertical" v-loading="workflowListLoading">
|
<el-button type="success" size="small" @click="createNewWorkflow">新建</el-button>
|
||||||
<el-empty v-if="!workflowListLoading && workflowList.length === 0" description="暂无工作流" :image-size="60" />
|
<el-button type="primary" link size="small" @click="refreshWorkflowList">刷新</el-button>
|
||||||
<div v-else class="workflow-list-scroll">
|
</div>
|
||||||
<div
|
<div class="workflow-list-vertical" v-loading="workflowListLoading">
|
||||||
v-for="workflow in workflowList"
|
<el-empty v-if="!workflowListLoading && userWorkflowList.length === 0" description="暂无工作流" :image-size="60" />
|
||||||
:key="workflow.id"
|
<div v-else class="workflow-list-scroll">
|
||||||
class="workflow-item"
|
<div
|
||||||
:class="{ active: currentEditingWorkflowId === workflow.id }"
|
v-for="workflow in userWorkflowList"
|
||||||
@click="useWorkflow(workflow)"
|
:key="workflow.id"
|
||||||
>
|
class="workflow-item"
|
||||||
<div class="workflow-item-content">
|
:class="{ active: currentEditingWorkflowId === workflow.id }"
|
||||||
<div class="workflow-item-name">{{ workflow.flowName }}</div>
|
@click="useWorkflow(workflow)"
|
||||||
<div class="workflow-item-desc">{{ workflow.description || '暂无描述' }}</div>
|
>
|
||||||
</div>
|
<div class="workflow-item-content">
|
||||||
<div class="workflow-item-actions">
|
<div class="workflow-item-name">{{ workflow.flowName }}</div>
|
||||||
<el-button type="primary" link size="small" @click.stop="editWorkflow(workflow)">编辑</el-button>
|
<div class="workflow-item-desc">{{ workflow.description || '暂无描述' }}</div>
|
||||||
<el-button type="danger" link size="small" @click.stop="deleteWorkflowAction(workflow)">删除</el-button>
|
</div>
|
||||||
|
<div class="workflow-item-actions">
|
||||||
|
<el-button type="primary" link size="small" @click.stop="editWorkflow(workflow)">编辑</el-button>
|
||||||
|
<el-button type="danger" link size="small" @click.stop="deleteWorkflowAction(workflow)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- 分页 -->
|
||||||
</div>
|
<div v-if="userWorkflowPagination.total > 0" class="workflow-pagination">
|
||||||
<!-- 分页 -->
|
<el-pagination
|
||||||
<div v-if="workflowPagination.total > 0" class="workflow-pagination">
|
v-model:current-page="userWorkflowPagination.pageNum"
|
||||||
<el-pagination
|
:page-size="userWorkflowPagination.pageSize"
|
||||||
v-model:current-page="workflowPagination.pageNum"
|
:total="userWorkflowPagination.total"
|
||||||
:page-size="workflowPagination.pageSize"
|
layout="prev, pager, next"
|
||||||
:total="workflowPagination.total"
|
@current-change="handleUserPageChange"
|
||||||
layout="prev, pager, next"
|
/>
|
||||||
@current-change="handlePageChange"
|
</div>
|
||||||
/>
|
</el-tab-pane>
|
||||||
</div>
|
|
||||||
|
<!-- 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>
|
</div>
|
||||||
|
|
||||||
<!-- 保存工作流对话框 -->
|
<!-- 保存工作流对话框 -->
|
||||||
@@ -403,13 +479,16 @@
|
|||||||
|
|
||||||
<!-- 技能选择器 -->
|
<!-- 技能选择器 -->
|
||||||
<SkillSelector v-model="showSkillSelector" :default-skill="selectedSkill" @confirm="handleSkillConfirm" />
|
<SkillSelector v-model="showSkillSelector" :default-skill="selectedSkill" @confirm="handleSkillConfirm" />
|
||||||
|
|
||||||
|
<!-- 创作技能选择器 -->
|
||||||
|
<SkillSelector v-model="showCreationSkillSelector" :default-skill="selectedCreationSkill" @confirm="handleCreationSkillConfirm" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<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 } from '@element-plus/icons-vue';
|
import { Document, Plus, Paperclip, MagicStick, Promotion } 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';
|
||||||
@@ -425,11 +504,13 @@ import {
|
|||||||
updateWorkflow,
|
updateWorkflow,
|
||||||
deleteWorkflow,
|
deleteWorkflow,
|
||||||
saveWorkflow,
|
saveWorkflow,
|
||||||
|
executeFlow,
|
||||||
type CreationListParams,
|
type CreationListParams,
|
||||||
type CreationTreeItem,
|
type CreationTreeItem,
|
||||||
type NodeLibraryFormItem,
|
type NodeLibraryFormItem,
|
||||||
type NodeLibraryGroup,
|
type NodeLibraryGroup,
|
||||||
type WorkflowItem,
|
type WorkflowItem,
|
||||||
|
type ExecuteFlowParams,
|
||||||
} from '/@/api/digitalHuman/creation';
|
} from '/@/api/digitalHuman/creation';
|
||||||
|
|
||||||
type NodeType = 'date' | 'contentType' | 'theme' | 'title' | 'html' | 'image';
|
type NodeType = 'date' | 'contentType' | 'theme' | 'title' | 'html' | 'image';
|
||||||
@@ -467,17 +548,40 @@ const saveForm = reactive({
|
|||||||
flowName: '',
|
flowName: '',
|
||||||
description: '',
|
description: '',
|
||||||
});
|
});
|
||||||
const workflowList = ref<WorkflowItem[]>([]);
|
|
||||||
const workflowListLoading = ref(false);
|
const workflowListLoading = ref(false);
|
||||||
const currentEditingWorkflowId = ref<string | null>(null);
|
const currentEditingWorkflowId = ref<string | null>(null);
|
||||||
const isCreationMode = ref(false); // 是否处于创作模式
|
const isCreationMode = ref(false); // 是否处于创作模式
|
||||||
const currentWorkflowForCreation = ref<any>(null); // 当前用于创作的工作流数据
|
const currentWorkflowForCreation = ref<any>(null); // 当前用于创作的工作流数据
|
||||||
const creationFormValues = reactive<Record<string, any>>({}); // 创作表单的值
|
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,
|
pageNum: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
total: 0,
|
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) => {
|
const formatParamReference = (value: string) => {
|
||||||
// 从 ${nodeId.field} 提取节点名和字段名
|
// 从 ${nodeId.field} 提取节点名和字段名
|
||||||
@@ -715,28 +819,61 @@ const fetchWorkflowList = async () => {
|
|||||||
workflowListLoading.value = true;
|
workflowListLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await getWorkflowList({ errorMode: 'page' });
|
const res = await getWorkflowList({ errorMode: 'page' });
|
||||||
const allWorkflows = res.data?.list || [];
|
|
||||||
workflowPagination.total = allWorkflows.length;
|
|
||||||
|
|
||||||
// 计算分页
|
// 分别处理用户工作流和模板工作流
|
||||||
const start = (workflowPagination.pageNum - 1) * workflowPagination.pageSize;
|
const userWorkflows = res.data?.listFlowUserRes?.list || [];
|
||||||
const end = start + workflowPagination.pageSize;
|
const templateWorkflows = res.data?.listFlowTemplateRes?.list || [];
|
||||||
workflowList.value = allWorkflows.slice(start, end);
|
|
||||||
|
// 获取管理员权限
|
||||||
|
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 {
|
} catch {
|
||||||
workflowList.value = [];
|
userWorkflowList.value = [];
|
||||||
workflowPagination.total = 0;
|
templateWorkflowList.value = [];
|
||||||
|
userWorkflowPagination.total = 0;
|
||||||
|
templateWorkflowPagination.total = 0;
|
||||||
|
isAdmin.value = false;
|
||||||
} finally {
|
} finally {
|
||||||
workflowListLoading.value = false;
|
workflowListLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 刷新工作流列表
|
// 刷新工作流列表
|
||||||
const refreshWorkflowList = () => {
|
const refreshWorkflowList = () => {
|
||||||
workflowPagination.pageNum = 1;
|
userWorkflowPagination.pageNum = 1;
|
||||||
|
templateWorkflowPagination.pageNum = 1;
|
||||||
fetchWorkflowList();
|
fetchWorkflowList();
|
||||||
};
|
};
|
||||||
// 处理分页变化
|
// 新建工作流
|
||||||
const handlePageChange = (page: number) => {
|
const createNewWorkflow = () => {
|
||||||
workflowPagination.pageNum = page;
|
// 切换回画布编辑模式
|
||||||
|
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();
|
fetchWorkflowList();
|
||||||
};
|
};
|
||||||
// 处理技能选择确认
|
// 处理技能选择确认
|
||||||
@@ -754,7 +891,6 @@ const handleSkillConfirm = (skill: SkillItem) => {
|
|||||||
selectedElement.value.properties.skillName = skill.name;
|
selectedElement.value.properties.skillName = skill.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ElMessage.success('技能选择成功');
|
|
||||||
};
|
};
|
||||||
// 移除已选择的技能
|
// 移除已选择的技能
|
||||||
const handleRemoveSkill = () => {
|
const handleRemoveSkill = () => {
|
||||||
@@ -807,7 +943,7 @@ const useWorkflow = async (workflow: WorkflowItem) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ElMessage.success(`已进入创作模式:${res.data.flowName}`);
|
ElMessage.success(`已进入创作模式`);
|
||||||
} else {
|
} else {
|
||||||
ElMessage.warning('该工作流没有内容');
|
ElMessage.warning('该工作流没有内容');
|
||||||
}
|
}
|
||||||
@@ -831,9 +967,8 @@ const editWorkflow = async (workflow: WorkflowItem) => {
|
|||||||
loadWorkflowFromDsl(res.data.flowContent);
|
loadWorkflowFromDsl(res.data.flowContent);
|
||||||
// 预填充保存表单,并记录当前编辑的工作流ID
|
// 预填充保存表单,并记录当前编辑的工作流ID
|
||||||
currentEditingWorkflowId.value = res.data.id;
|
currentEditingWorkflowId.value = res.data.id;
|
||||||
saveForm.flowName = res.data.flowName;
|
saveForm.flowName = res.data.flowName || res.data.flowTemplateName || '';
|
||||||
saveForm.description = res.data.description || '';
|
saveForm.description = res.data.description || '';
|
||||||
ElMessage.success(`正在编辑工作流:${res.data.flowName}`);
|
|
||||||
} else {
|
} else {
|
||||||
ElMessage.warning('该工作流没有内容');
|
ElMessage.warning('该工作流没有内容');
|
||||||
}
|
}
|
||||||
@@ -857,25 +992,12 @@ const backToCanvas = async () => {
|
|||||||
lf.zoom(1);
|
lf.zoom(1);
|
||||||
lf.translateCenter();
|
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) => {
|
const deleteWorkflowAction = async (workflow: WorkflowItem) => {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定要删除工作流"${workflow.flowName}"吗?此操作不可恢复。`, '删除确认', {
|
const workflowName = workflow.flowName || workflow.flowTemplateName || '该工作流';
|
||||||
|
await ElMessageBox.confirm(`确定要删除工作流"${workflowName}"吗?此操作不可恢复。`, '删除确认', {
|
||||||
confirmButtonText: '确定删除',
|
confirmButtonText: '确定删除',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning',
|
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) => {
|
const handleNodeClick = (d: TreeNode) => {
|
||||||
if (d.nodeType !== 'html' && d.nodeType !== 'image') return;
|
if (d.nodeType !== 'html' && d.nodeType !== 'image') return;
|
||||||
const url = buildAssetUrl(d.fileUrl);
|
const url = buildAssetUrl(d.fileUrl);
|
||||||
@@ -1536,7 +1788,7 @@ const applySelected = () => {
|
|||||||
|
|
||||||
syncDsl();
|
syncDsl();
|
||||||
|
|
||||||
ElMessage.success('已更新当前元素配置');
|
// 静默更新,不显示提示
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('应用配置失败,请查看控制台');
|
ElMessage.error('应用配置失败,请查看控制台');
|
||||||
}
|
}
|
||||||
@@ -1734,7 +1986,6 @@ const resetFlow = () => {
|
|||||||
nodeSpawnIndex.value = 0;
|
nodeSpawnIndex.value = 0;
|
||||||
selectedElement.value = null;
|
selectedElement.value = null;
|
||||||
syncDsl();
|
syncDsl();
|
||||||
ElMessage.success('流程已重置');
|
|
||||||
};
|
};
|
||||||
// 从后端 DSL 恢复工作流
|
// 从后端 DSL 恢复工作流
|
||||||
const loadWorkflowFromDsl = (dsl: any) => {
|
const loadWorkflowFromDsl = (dsl: any) => {
|
||||||
@@ -1781,7 +2032,6 @@ const loadWorkflowFromDsl = (dsl: any) => {
|
|||||||
lf.zoom(1);
|
lf.zoom(1);
|
||||||
lf.translateCenter();
|
lf.translateCenter();
|
||||||
syncDsl();
|
syncDsl();
|
||||||
ElMessage.success('工作流已加载');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('工作流加载失败');
|
ElMessage.error('工作流加载失败');
|
||||||
}
|
}
|
||||||
@@ -2317,7 +2567,7 @@ onBeforeUnmount(() => {
|
|||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
}
|
}
|
||||||
.workflow-pagination {
|
.workflow-pagination {
|
||||||
margin-top: 20px;
|
margin-top: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
@@ -2671,10 +2921,28 @@ onBeforeUnmount(() => {
|
|||||||
box-shadow: 0 4px 18px rgba(15, 23, 42, 0.05);
|
box-shadow: 0 4px 18px rgba(15, 23, 42, 0.05);
|
||||||
overflow: hidden;
|
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 {
|
.right-panel-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -2762,4 +3030,75 @@ onBeforeUnmount(() => {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 8px 16px;
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user