﻿<template>
	<div class="creation-page" :class="{ 'creation-mode': isCreationMode }">
		<!-- 左侧面板：工作空间/当前选中元素 Tab切换 -->
		<div class="panel left">
			<el-tabs v-model="leftPanelTab" class="left-tabs">
				<!-- Tab 1: 工作空间 -->
				<el-tab-pane label="工作空间" name="workspace">
					<div class="tree-wrap" v-loading="treeLoading">
						<el-empty v-if="!treeLoading && treeNodes.length === 0" description="暂无作品数据" />
						<el-tree
							v-else
							:data="treeNodes"
							node-key="id"
							:props="treeProps"
							default-expand-all
							:highlight-current="true"
							:expand-on-click-node="false"
						>
							<template #default="{ data }">
								<div class="tree-node">
									<span class="ellipsis">{{ data.label }}</span>
									<div v-if="data.nodeType === 'html' || data.nodeType === 'image'" class="tree-node-actions">
										<el-button type="primary" link size="small" @click.stop="previewNode(data)"> 预览 </el-button>
										<el-button type="primary" link size="small" @click.stop="downloadNode(data)"> 下载 </el-button>
									</div>
								</div>
							</template>
						</el-tree>
					</div>
				</el-tab-pane>
				<!-- Tab 2: 当前选中元素 -->
				<el-tab-pane label="当前选中" name="selected">
					<div class="selected-panel">
						<el-empty v-if="!selectedElement" description="请先点击一个节点或连线" :image-size="84" />
						<div v-else class="form-container">
							<div class="form-scroll-area">
								<el-form label-position="top" class="prop-form">
									<el-form-item v-if="selectedElement.kind === 'node'" label="节点名称"><el-input v-model="formState.text" /></el-form-item>
									<el-form-item v-if="selectedElement.kind === 'edge'" label="字段"><el-input v-model="formState.field" /></el-form-item>
									<template v-if="selectedElement.kind === 'node'">
										<!-- 模型选择（如果有模型配置） -->
										<el-form-item v-if="currentNodeModelConfig.length > 0" label="选择模型">
											<div class="model-selector-wrapper">
												<el-button type="primary" @click="showModelSelector = true" style="width: 100%">
													<el-icon><Plus /></el-icon>
													选择模型
												</el-button>
												<div v-if="selectedModel" class="selected-model-tag">
													<el-tag type="success" size="large" closable @close="handleRemoveModel">
														{{ selectedModel }}
													</el-tag>
												</div>
											</div>
										</el-form-item>
										<!-- 技能选择（如果节点支持） -->
										<el-form-item v-if="currentNodeSkillOption" label="选择技能">
											<div class="skill-selector-wrapper">
												<el-button type="primary" @click="showSkillSelector = true">
													<el-icon><Plus /></el-icon>
													选择技能
												</el-button>
												<div v-if="selectedSkill" class="selected-skill-tag">
													<el-tag type="success" size="large" closable @close="handleRemoveSkill">
														{{ selectedSkill.name }}
													</el-tag>
												</div>
											</div>
										</el-form-item>
										<!-- 基础表单 + 模型表单 -->
										<el-form-item v-for="fieldItem in allFormFields" :key="fieldItem.field" :label="fieldItem.label">
											<el-input
												v-if="fieldItem.type === 'input' && !isSelectField(fieldItem.field)"
												v-model="dynamicFormValues[fieldItem.field]"
												:placeholder="fieldItem.required ? '必填' : '选填'"
											/>
											<el-select
												v-else-if="fieldItem.type === 'input' && isSelectField(fieldItem.field)"
												v-model="dynamicFormValues[fieldItem.field]"
												:placeholder="fieldItem.required ? '必填' : '选填'"
												class="w100"
											>
												<el-option v-for="opt in getSelectOptions(fieldItem.field)" :key="opt.value" :label="opt.label" :value="opt.value" />
											</el-select>
											<el-input-number
												v-else-if="fieldItem.type === 'number'"
												v-model="dynamicFormValues[fieldItem.field]"
												:min="fieldItem.field === 'count' ? 1 : undefined"
												class="w100"
											/>
											<el-input
												v-else-if="fieldItem.type === 'textarea'"
												v-model="dynamicFormValues[fieldItem.field]"
												type="textarea"
												:rows="3"
												:placeholder="fieldItem.required ? '必填' : '选填'"
											/>
											<el-switch v-else-if="fieldItem.type === 'switch'" v-model="dynamicFormValues[fieldItem.field]" />
											<el-input v-else v-model="dynamicFormValues[fieldItem.field]" :placeholder="fieldItem.required ? '必填' : '选填'" />
										</el-form-item>
										<!-- 上级节点参数选择（表单参数节点和开始节点除外） -->
										<template v-if="canSelectParentParams(selectedElement)">
											<el-divider content-position="left">引用上级参数</el-divider>
											<!-- 显示已引用的参数（只显示有字段的节点） -->
											<div v-if="currentInputSource && Array.isArray(currentInputSource) && currentInputSource.length > 0" class="input-source-list">
												<div
													v-for="(sourceNode, index) in currentInputSource.filter((n: any) => n.field && n.field.length > 0)"
													:key="index"
													class="input-source-item"
												>
													<div class="input-source-content">
														<div class="input-source-label">
															<span class="input-source-key"
																>来自节点：{{ formatParamReference(`\${${sourceNode.nodeId}.field}`).split('.')[0] }}</span
															>
														</div>
														<div v-for="fieldName in sourceNode.field" :key="fieldName" class="input-source-field">
															<span class="input-source-field-name">{{ fieldName }}</span>
															<el-button type="danger" link size="small" @click="removeInputSource(sourceNode.nodeId, fieldName)">删除</el-button>
														</div>
														<!-- 引用节点输出开关 -->
														<div class="input-source-quote">
															<el-switch
																:model-value="sourceNode.quoteOutput === true"
																@change="(val: boolean) => updateQuoteOutput(sourceNode.nodeId, val)"
																size="small"
																active-text="引入输出"
																inactive-text=""
															/>
														</div>
													</div>
												</div>
											</div>
											<!-- 显示所有上级节点的输出引用选项 -->
											<div v-if="availableParentNodes.length > 0" class="parent-nodes-output">
												<div class="parent-nodes-title">上级节点输出</div>
												<div v-for="parentNode in availableParentNodes" :key="parentNode.id" class="parent-node-output-item">
													<span class="parent-node-name">{{ parentNode.name }}</span>
													<el-switch
														:model-value="isNodeOutputQuoted(parentNode.id)"
														@change="(val: boolean) => toggleNodeOutput(parentNode.id, val)"
														size="small"
														active-text="引入输出"
														inactive-text=""
													/>
												</div>
											</div>
											<el-form-item label="选择参数">
												<el-select v-model="selectedParentParam" placeholder="选择上级节点的参数" class="w100" @change="addParentParam">
													<el-option v-for="param in availableParentParams" :key="param.value" :label="param.label" :value="param.value" />
												</el-select>
											</el-form-item>
										</template>
										<!-- 自定义表单项（判断节点、开始节点、表单参数节点除外） -->
										<template v-if="canAddCustomFields(selectedElement)">
											<el-divider content-position="left">自定义字段</el-divider>
											<div v-for="(customField, index) in customFields" :key="index" class="custom-field-config">
												<el-input v-model="customField.label" placeholder="字段名" class="custom-field-input" />
												<div class="custom-field-row">
													<el-select v-model="customField.type" placeholder="类型" class="custom-field-type">
														<el-option label="文本" value="input" />
														<el-option label="数字" value="number" />
														<el-option label="多行文本" value="textarea" />
														<el-option label="开关" value="switch" />
													</el-select>
													<el-checkbox v-model="customField.required" class="custom-field-required">必填</el-checkbox>
													<el-button type="danger" link @click="removeCustomField(index)">删除</el-button>
												</div>
												<el-input v-model="customField.value" placeholder="默认值" class="custom-field-value-full" />
											</div>
											<el-button type="primary" link class="w100" @click="addCustomField">+ 添加自定义字段</el-button>
										</template>
									</template>
								</el-form>
							</div>
							<div class="form-actions">
								<el-button type="primary" class="w100" @click="applySelected">应用到当前元素</el-button>
							</div>
						</div>
					</div>
				</el-tab-pane>
			</el-tabs>
		</div>
		<div class="editor-shell">
			<div class="main">
				<!-- 创作模式：动态表单 -->
				<div v-show="isCreationMode" class="creation-mode-container">
					<div class="panel creation-form-panel">
						<div class="creation-header">
							<div>
								<div class="title">{{ currentWorkflowForCreation?.flowName || '内容创作' }}</div>
								<div class="sub">{{ currentWorkflowForCreation?.description || '填写表单参数进行内容创作' }}</div>
							</div>
							<el-button @click="backToCanvas">返回画布</el-button>
						</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 class="node-form-title">
												<el-icon class="node-icon"><Document /></el-icon>
												<span>{{ node.name }}</span>
											</div>

											<div class="form-grid">
												<!-- 自定义表单字段 -->
												<template v-if="node.formConfig && node.formConfig.length > 0">
													<el-form-item
														v-for="field in node.formConfig"
														:key="`${node.id}_${field.label}`"
														:label="field.label"
														:required="field.required"
														:class="getFieldClass(field.type)"
													>
														<el-input
															v-if="field.type === 'input'"
															v-model="creationFormValues[`${node.id}_${field.label}`]"
															:placeholder="field.required ? '必填' : '选填'"
															clearable
														/>
														<el-input-number
															v-else-if="field.type === 'number'"
															v-model="creationFormValues[`${node.id}_${field.label}`]"
															class="w100"
															:controls="true"
														/>
														<el-input
															v-else-if="field.type === 'textarea'"
															v-model="creationFormValues[`${node.id}_${field.label}`]"
															type="textarea"
															:rows="4"
															:placeholder="field.required ? '必填' : '选填'"
															show-word-limit
															:maxlength="500"
														/>
														<el-switch
															v-else-if="field.type === 'switch'"
															v-model="creationFormValues[`${node.id}_${field.label}`]"
															active-text="开启"
															inactive-text="关闭"
														/>
													</el-form-item>
												</template>

												<!-- 其他配置字段（排除已在 formConfig 中的字段） -->
												<template v-if="node.config">
													<el-form-item
														v-for="(value, key) in node.config"
														:key="`${node.id}_${key}`"
														v-show="
															!['nodeCode', 'width', 'height', 'x', 'y', 'formConfig', 'inputSource', 'fieldMetadata', 'selectedModel'].includes(
																String(key)
															) && !(node.formConfig || []).some((f: any) => f.label === key || f.label === node.config.fieldMetadata?.[key]?.label)
														"
														:label="node.config.fieldMetadata?.[key]?.label || String(key)"
														:required="node.config.fieldMetadata?.[key]?.required || false"
														:class="getFieldClass(node.config.fieldMetadata?.[key]?.type || (typeof value === 'string' ? 'textarea' : 'number'))"
													>
														<el-input
															v-if="typeof value === 'string'"
															v-model="creationFormValues[`${node.id}_${key}`]"
															:type="node.config.fieldMetadata?.[key]?.type === 'textarea' || String(value).length > 50 ? 'textarea' : 'text'"
															:rows="4"
															:placeholder="node.config.fieldMetadata?.[key]?.required ? '必填' : '选填'"
															clearable
															:show-word-limit="node.config.fieldMetadata?.[key]?.type === 'textarea'"
															:maxlength="500"
														/>
														<el-input-number
															v-else-if="typeof value === 'number'"
															v-model="creationFormValues[`${node.id}_${key}`]"
															class="w100"
															:controls="true"
															:precision="2"
															:step="0.1"
														/>
														<el-switch
															v-else-if="typeof value === 'boolean'"
															v-model="creationFormValues[`${node.id}_${key}`]"
															active-text="开启"
															inactive-text="关闭"
														/>
													</el-form-item>
												</template>
											</div>
										</div>
									</div>
								</template>
								<el-empty v-else description="暂无表单配置" :image-size="100" />
							</el-form>
						</div>

						<!-- 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" @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 v-show="!isCreationMode" class="panel canvas-panel">
					<div class="meta">
						<div class="meta-left">
							<span class="meta-title">工作流画布</span>
							<span class="meta-info">节点 {{ flowDsl.nodes.length }} / 连线 {{ flowDsl.edges.length }}</span>
						</div>
						<div class="meta-actions">
							<el-button size="small" @click="resetFlow">清空画布</el-button>
							<el-button type="primary" size="small" @click="saveWorkflowAction" :loading="saving">保存工作流</el-button>
						</div>
					</div>
					<div class="canvas-layout">
						<div class="node-library">
							<div class="title-sm">节点库</div>
							<el-empty v-if="nodeLibraryGroups.length === 0" description="暂无节点" :image-size="60" />
							<div v-else class="node-library-groups">
								<div v-for="group in nodeLibraryGroups" :key="group.group" class="node-group">
									<div class="node-group-title">{{ group.label }}</div>
									<div class="node-group-items">
										<el-button
											v-for="item in group.items"
											:key="item.nodeCode"
											text
											class="node-item"
											@click="addNodeFromLibrary(item.nodeCode, item.nodeName)"
										>
											{{ item.nodeName }}
										</el-button>
									</div>
								</div>
							</div>
						</div>
						<div class="canvas-wrap"><div ref="logicFlowRef" class="logicflow-canvas"></div></div>
					</div>
				</div>
			</div>
		</div>

		<!-- 右侧：工作流列表（竖状） -->
		<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">
						<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 && userWorkflowList.length === 0" description="暂无工作流" :image-size="60" />
						<div v-else class="workflow-list-scroll">
							<div
								v-for="workflow in userWorkflowList"
								:key="workflow.id"
								class="workflow-item"
								:class="{ active: currentEditingWorkflowId === workflow.id }"
								@click="useWorkflow(workflow)"
							>
								<div class="workflow-item-content">
									<div class="workflow-item-name">{{ workflow.flowName }}</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="danger" link size="small" @click.stop="deleteWorkflowAction(workflow)">删除</el-button>
								</div>
							</div>
						</div>
					</div>
					<!-- 分页 -->
					<div v-if="userWorkflowPagination.total > 0" class="workflow-pagination">
						<el-pagination
							v-model:current-page="userWorkflowPagination.pageNum"
							:page-size="userWorkflowPagination.pageSize"
							:total="userWorkflowPagination.total"
							layout="prev, pager, next"
							@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>

		<!-- 保存工作流对话框 -->
		<el-dialog
			v-model="saveDialogVisible"
			:title="currentEditingWorkflowId ? '编辑工作流' : '保存工作流'"
			width="500px"
			:close-on-click-modal="false"
		>
			<el-form :model="saveForm" label-position="top">
				<el-form-item label="工作流名称" required>
					<el-input v-model="saveForm.flowName" placeholder="请输入工作流名称" maxlength="50" show-word-limit />
				</el-form-item>
				<el-form-item label="工作流描述">
					<el-input v-model="saveForm.description" type="textarea" :rows="4" placeholder="请输入工作流描述（选填）" maxlength="200" show-word-limit />
				</el-form-item>
			</el-form>
			<template #footer>
				<el-button @click="saveDialogVisible = false">取消</el-button>
				<el-button type="primary" @click="confirmSaveWorkflow" :loading="saving">{{ currentEditingWorkflowId ? '确定更新' : '确定保存' }}</el-button>
			</template>
		</el-dialog>

		<!-- 技能选择器 -->
		<SkillSelector v-model="showSkillSelector" :default-skill="selectedSkill" @confirm="handleSkillConfirm" />

		<!-- 创作技能选择器 -->
		<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>
			<div class="preview-container">
				<iframe v-if="previewUrl" :src="previewUrl" class="preview-iframe" frameborder="0"></iframe>
				<el-empty v-else description="无法加载预览内容" />
			</div>
		</el-dialog>
	</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, 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';
import '@logicflow/extension/lib/style/index.css';
import SkillSelector from '/@/components/skill/NodeSkillSelector.vue';
import ModelSelector from '/@/components/model/ModelSelector.vue';
import type { SkillItem } from '/@/api/digitalHuman/skill';
import {
	downloadToFile,
	getExecutionList,
	getNodeLibraryList,
	getWorkflowList,
	getWorkflowDetail,
	updateWorkflow,
	deleteWorkflow,
	saveWorkflow,
	executeFlow,
	type ExecutionTreeItem,
	type NodeLibraryFormItem,
	type NodeLibraryGroup,
	type WorkflowItem,
	type ExecuteFlowParams,
} from '/@/api/digitalHuman/creation';
import { uploadFile } from '/@/api/common/upload';

type NodeType = 'date' | 'contentType' | 'theme' | 'title' | 'html' | 'image';
type Item = Record<string, any>;
const START_NODE_CODE = '__start__';
const START_NODE_TEXT = '开始';
const JUDGE_KEYWORDS = ['判断', 'judge', 'condition', 'if', 'branch', 'gateway'];
interface TreeNode {
	id: string;
	label: string;
	nodeType: NodeType;
	children?: TreeNode[];
	fileUrl?: string;
}
interface SelectedState {
	id: string;
	type: string;
	kind: 'node' | 'edge';
	properties: Item;
	text?: string;
}
const treeLoading = ref(false);
const treeNodes = ref<TreeNode[]>([]);
const imgAddressPrefix = ref('');
const selectedElement = ref<SelectedState | null>(null);
const customFields = ref<Array<{ label: string; value: string; type: string; required: boolean }>>([]);
const selectedParentParam = ref('');
const selectedModel = ref('');
const showSkillSelector = ref(false);
const selectedSkill = ref<SkillItem | null>(null);
const saving = ref(false);
const leftPanelTab = ref('selected'); // 默认显示"当前选中"Tab
const saveDialogVisible = ref(false);
const saveForm = reactive({
	flowName: '',
	description: '',
});
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 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);
// 预览相关状态
const previewDialogVisible = ref(false);
const previewUrl = ref('');
// 模型选择器相关状态
const showModelSelector = ref(false);
const selectedModelData = ref<any>(null);
// 会话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} 提取节点名和字段名
	const match = value.match(/\$\{(.+?)\.(.+?)\}/);
	if (!match) return value;

	const nodeId = match[1];
	const field = match[2];

	// 查找节点名称
	const lf = logicFlowInstance.value;
	if (!lf) return `${field}`;

	const graphData = lf.getGraphData() as { nodes?: Item[]; edges?: Item[] };
	const node = (graphData.nodes || []).find((n: any) => n.id === nodeId);
	const nodeName = node ? (typeof node.text === 'string' ? node.text : node.text?.value || nodeId) : nodeId;

	return `${nodeName}.${field}`;
};
// 当前节点的 inputSource
const currentInputSource = computed(() => {
	const inputSource = selectedElement.value?.properties?.inputSource;
	return Array.isArray(inputSource) ? inputSource : [];
});
const flowDsl = ref<{ nodes: Item[]; edges: Item[] }>({ nodes: [], edges: [] });
const logicFlowRef = ref<HTMLDivElement | null>(null);
const logicFlowInstance = ref<LogicFlow | null>(null);
const nodeSpawnIndex = ref(0);
const formState = reactive({ text: '', nodeCode: '', field: '' });
const dynamicFormValues = reactive<Record<string, any>>({ modelApiKey: '' });
const nodeSchemaMap = computed(() => {
	const map: Record<string, NodeLibraryFormItem[]> = {};
	nodeLibraryGroups.value.forEach((group) => {
		(group.items || []).forEach((item) => {
			map[item.nodeCode] = item.formConfig || [];
		});
	});
	return map;
});
const currentNodeForm = computed<NodeLibraryFormItem[]>(() => nodeSchemaMap.value[formState.nodeCode] || []);
// 获取当前节点的模型配置
const currentNodeModelConfig = computed(() => {
	let modelConfigs: any[] = [];
	nodeLibraryGroups.value.forEach((group) => {
		(group.items || []).forEach((item) => {
			if (item.nodeCode === formState.nodeCode) {
				modelConfigs = item.modelConfig || [];
			}
		});
	});
	return modelConfigs;
});
// 获取当前节点是否支持技能选择
const currentNodeSkillOption = computed(() => {
	let skillOption = false;
	nodeLibraryGroups.value.forEach((group) => {
		(group.items || []).forEach((item) => {
			if (item.nodeCode === formState.nodeCode) {
				skillOption = item.skillOption || false;
			}
		});
	});
	return skillOption;
});
// 获取当前选中模型的表单字段
const currentModelForm = computed<NodeLibraryFormItem[]>(() => {
	// 不显示模型的表单字段，返回空数组
	return [];
});
// 合并基础表单和模型表单
const allFormFields = computed<NodeLibraryFormItem[]>(() => {
	return [...currentNodeForm.value, ...currentModelForm.value];
});
// 获取可用的上级节点参数
const availableParentParams = computed(() => {
	if (!selectedElement.value) return [];
	const lf = logicFlowInstance.value;
	if (!lf) return [];

	const graphData = lf.getGraphData() as { nodes?: Item[]; edges?: Item[] };
	const edges = graphData.edges || [];
	const nodes = graphData.nodes || [];

	// 递归查找所有上级节点
	const findAllParentNodes = (nodeId: string, visited = new Set<string>()): string[] => {
		if (visited.has(nodeId)) return [];
		visited.add(nodeId);

		const incomingEdges = edges.filter((e) => e.targetNodeId === nodeId);
		const parentIds: string[] = [];

		incomingEdges.forEach((edge) => {
			parentIds.push(edge.sourceNodeId);
			// 递归查找上级的上级
			parentIds.push(...findAllParentNodes(edge.sourceNodeId, visited));
		});

		return parentIds;
	};

	const allParentIds = findAllParentNodes(selectedElement.value.id);
	const params: Array<{ label: string; value: string }> = [];

	// 遍历所有上级节点
	allParentIds.forEach((parentId) => {
		const parentNode = nodes.find((n) => n.id === parentId);
		if (!parentNode) return;

		const parentNodeName = typeof parentNode.text === 'string' ? parentNode.text : parentNode.text?.value || '';
		const parentProps = parentNode.properties || {};

		// 判断是否为判断节点
		const nodeCode = String(parentProps.nodeCode || '').toLowerCase();
		const nodeText = parentNodeName.toLowerCase();
		const isJudge = JUDGE_KEYWORDS.some((k) => nodeCode.includes(k) || nodeText.includes(k));

		// 如果是判断节点，跳过不添加其字段
		if (isJudge) return;

		// 只添加自定义字段（formConfig）
		if (parentProps.formConfig && Array.isArray(parentProps.formConfig)) {
			parentProps.formConfig.forEach((field: any) => {
				if (field.label) {
					params.push({
						label: `${parentNodeName}.${field.label}`,
						value: `\${${parentId}.${field.label}}`,
					});
				}
			});
		}
	});

	return params;
});
const treeProps = { children: 'children', label: 'label' };
const apiBaseUrl = (import.meta.env.VITE_API_URL || '').replace(/\/$/, '');
const nodeLibraryGroups = ref<NodeLibraryGroup[]>([]);
const workflowDsl = computed(() => ({
	version: '1.0.0',
	startNodeId: flowDsl.value.nodes[0]?.id || '',
	nodes: flowDsl.value.nodes.map((n) => ({
		id: n.id,
		nodeCode: n.properties?.nodeCode || 'unknown',
		name: typeof n.text === 'string' ? n.text : n.text?.value || '',
		type: n.type || 'rect',
		skillName: n.properties?.skillName || null,
		config: {
			nodeCode: n.properties?.nodeCode || 'unknown',
			width: n.properties?.width || 100,
			height: n.properties?.height || 80,
			x: n.x || 0,
			y: n.y || 0,
		},
		inputSource: n.properties?.inputSource || null,
		formConfig: n.properties?.formConfig || null,
		modelConfig: n.properties?.modelConfig || null,
		outputResult: null,
	})),
	edges: flowDsl.value.edges.map((e) => ({
		id: e.id,
		from: e.sourceNodeId,
		to: e.targetNodeId,
		type: e.type || 'polyline',
		mapping: { ...e.properties },
	})),
}));
const joinUrl = (b: string, p: string) => `${b.replace(/\/$/, '')}${p.startsWith('/') ? p : `/${p}`}`;
const buildAssetUrl = (p?: string) =>
	!p
		? ''
		: /^https?:\/\//i.test(p)
			? p
			: /^https?:\/\//i.test(imgAddressPrefix.value || '')
				? joinUrl(imgAddressPrefix.value, p)
				: imgAddressPrefix.value
					? joinUrl(joinUrl(apiBaseUrl, imgAddressPrefix.value), p)
					: joinUrl(apiBaseUrl, p);
const buildTreeNodes = (tree: ExecutionTreeItem[]): TreeNode[] =>
	tree.map((d, di) => ({
		id: `date-${di}`,
		label: d.createDate,
		nodeType: 'date',
		children: (d.flows || []).map((f, fi) => ({
			id: `flow-${di}-${fi}`,
			label: f.flowName || '未命名工作流',
			nodeType: 'contentType',
			children: (f.items || []).map((item, ii) => ({
				id: `item-${di}-${fi}-${ii}`,
				label: item.label || `作品${ii + 1}`,
				nodeType: 'title',
				children: [
					{
						id: `html-${di}-${fi}-${ii}`,
						label: 'HTML',
						nodeType: 'html' as const,
						fileUrl: item.content,
					},
				],
			})),
		})),
	}));
const getList = async () => {
	treeLoading.value = true;
	try {
		const res = await getExecutionList();
		imgAddressPrefix.value = res.data?.imgAddressPrefix || '';
		treeNodes.value = buildTreeNodes(res.data?.tree || []);
	} catch {
		// 错误已由全局拦截器处理
		treeNodes.value = [];
		imgAddressPrefix.value = '';
	} finally {
		treeLoading.value = false;
	}
};
const getNodeLibrary = async () => {
	try {
		const res = await getNodeLibraryList();
		nodeLibraryGroups.value = res.data?.groups || [];
	} catch {
		// 错误已由全局拦截器处理
		nodeLibraryGroups.value = [];
	}
};
// 获取工作流列表
const fetchWorkflowList = async () => {
	workflowListLoading.value = true;
	try {
		const res = await getWorkflowList();

		// 分别处理用户工作流和模板工作流
		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 {
		// 错误已由全局拦截器处理
		userWorkflowList.value = [];
		templateWorkflowList.value = [];
		userWorkflowPagination.total = 0;
		templateWorkflowPagination.total = 0;
		isAdmin.value = false;
	} finally {
		workflowListLoading.value = false;
	}
};
// 刷新工作流列表
const refreshWorkflowList = () => {
	userWorkflowPagination.pageNum = 1;
	templateWorkflowPagination.pageNum = 1;
	fetchWorkflowList();
};
// 新建工作流
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();
};
// 处理技能选择确认（只更新临时状态，不保存到节点）
const handleSkillConfirm = (skill: SkillItem) => {
	selectedSkill.value = skill;
};
// 移除已选择的技能（只更新临时状态）
const handleRemoveSkill = () => {
	selectedSkill.value = null;
};
// 处理模型选择确认（只更新临时状态，不保存到节点）
const handleModelConfirm = (model: any) => {
	selectedModel.value = model.modelName;
	selectedModelData.value = model;
};
// 移除已选择的模型（只更新临时状态）
const handleRemoveModel = () => {
	selectedModel.value = '';
	selectedModelData.value = null;
};
// 使用工作流
const useWorkflow = async (workflow: WorkflowItem) => {
	try {
		// 调用详情接口获取最新的工作流数据
		const res = await getWorkflowDetail(workflow.id);
		if (res.data) {
			// 切换到创作模式
			isCreationMode.value = true;
			currentWorkflowForCreation.value = res.data;

			// 初始化创作表单的值
			Object.keys(creationFormValues).forEach((key) => delete creationFormValues[key]);

			// 根据 nodeInputParams 初始化表单默认值
			if (res.data.nodeInputParams && Array.isArray(res.data.nodeInputParams)) {
				res.data.nodeInputParams.forEach((node: any) => {
					// 从节点根级别的 formConfig 读取（不是 node.config.formConfig）
					if (node.formConfig && Array.isArray(node.formConfig)) {
						node.formConfig.forEach((field: any) => {
							const fieldKey = `${node.id}_${field.label}`;
							// 根据字段类型转换值
							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 || '';
							}
						});
					}

					// 初始化其他配置字段（从 config 中读取）
					if (node.config) {
						Object.keys(node.config).forEach((key) => {
							if (!['nodeCode', 'width', 'height', 'x', 'y', 'formConfig', 'inputSource', 'fieldMetadata', 'selectedModel'].includes(key)) {
								const fieldKey = `${node.id}_${key}`;
								creationFormValues[fieldKey] = node.config[key];
							}
						});
					}
				});
			}

			ElMessage.success(`已进入创作模式`);
		} else {
			ElMessage.warning('该工作流没有内容');
		}
	} catch (error) {
		// 后端错误会自动显示
	}
};
// 编辑工作流
const editWorkflow = async (workflow: WorkflowItem) => {
	try {
		// 调用详情接口获取最新的工作流数据
		const res = await getWorkflowDetail(workflow.id);
		if (res.data?.flowContent) {
			// 切换回画布编辑模式
			isCreationMode.value = false;
			currentWorkflowForCreation.value = null;

			// 等待 DOM 更新后再加载工作流
			await nextTick();

			loadWorkflowFromDsl(res.data.flowContent);
			// 预填充保存表单，并记录当前编辑的工作流ID
			currentEditingWorkflowId.value = res.data.id;
			saveForm.flowName = res.data.flowName || res.data.flowTemplateName || '';
			saveForm.description = res.data.description || '';
		} else {
			ElMessage.warning('该工作流没有内容');
		}
	} catch (error) {
		// 后端错误会自动显示
	}
};
// 返回画布编辑模式
const backToCanvas = async () => {
	isCreationMode.value = false;
	currentWorkflowForCreation.value = null;

	// 等待 DOM 更新后重新渲染画布
	await nextTick();

	const lf = logicFlowInstance.value;
	if (lf) {
		// 重新渲染当前的工作流数据
		const currentData = lf.getGraphData() as any;
		lf.render(currentData);
		lf.zoom(1);
		lf.translateCenter();
	}
};
// 删除工作流
const deleteWorkflowAction = async (workflow: WorkflowItem) => {
	try {
		const workflowName = workflow.flowName || workflow.flowTemplateName || '该工作流';
		await ElMessageBox.confirm(`确定要删除工作流"${workflowName}"吗？此操作不可恢复。`, '删除确认', {
			confirmButtonText: '确定删除',
			cancelButtonText: '取消',
			type: 'warning',
		});

		await deleteWorkflow(workflow.id);
		ElMessage.success('工作流删除成功');

		// 如果删除的是当前正在编辑的工作流，清空编辑状态
		if (currentEditingWorkflowId.value === workflow.id) {
			currentEditingWorkflowId.value = null;
			saveForm.flowName = '';
			saveForm.description = '';
		}

		// 刷新工作流列表
		await fetchWorkflowList();
	} catch (error) {
		if (error === 'cancel') {
			// 用户取消操作，不显示错误
		}
	}
};
// 处理文件选择
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 (!currentWorkflowForCreation.value) {
		ElMessage.warning('请先选择一个工作流');
		return;
	}

	isCreating.value = true;

	try {
		// 1. 先上传文件到 OSS，获取文件 URL
		const fileUrls: string[] = [];
		if (selectedFiles.value.length > 0) {
			for (const file of selectedFiles.value) {
				const uploadRes = await uploadFile(file);
				// 拼接完整的文件地址
				const fullUrl = uploadRes.data.fileAddressPrefix ? `${uploadRes.data.fileAddressPrefix}${uploadRes.data.fileURL}` : uploadRes.data.fileURL;
				fileUrls.push(fullUrl);
			}
		}

		// 2. 构建节点输入参数
		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;
			}) || [];

		// 3. 同步更新 flowContent.nodes，使其与 nodeInputParams 一致
		const updatedFlowContent = {
			...currentWorkflowForCreation.value.flowContent,
			nodes: nodeInputParams, // 使用更新后的节点参数
		};

		// 4. 构建请求参数
		const params: ExecuteFlowParams = {
			flowId: currentWorkflowForCreation.value.id, // ID 是字符串
			flowContent: updatedFlowContent,
			nodeInputParams: nodeInputParams,
			sessionId: getSessionId(),
			desc: userInput.value,
			skillName: selectedCreationSkill.value?.name,
			flowName: currentWorkflowForCreation.value.flowName || currentWorkflowForCreation.value.flowTemplateName, // 工作流名称
			fileUrl: fileUrls, // 添加文件 URL 数组
		};

		// 5. 调用执行接口（不再使用 FormData，直接传 JSON）
		await executeFlow(params);

		ElMessage.success('创作完成！');

		// 6. 清空输入
		userInput.value = '';
		selectedFiles.value = [];
		selectedCreationSkill.value = null;
	} catch {
		// 接口错误由 request 全局提示后端 message
	} 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 previewNode = (d: TreeNode) => {
	if (d.nodeType !== 'html' && d.nodeType !== 'image') return;
	const url = buildAssetUrl(d.fileUrl);
	if (!url) return ElMessage.warning('当前节点没有可用预览地址');
	previewUrl.value = url;
	previewDialogVisible.value = true;
};
// 下载节点
const downloadNode = async (d: TreeNode) => {
	if (d.nodeType !== 'html' && d.nodeType !== 'image') return;
	if (!d.fileUrl) return ElMessage.warning('当前节点没有可下载地址');
	try {
		// 下载失败时希望展示更贴近页面语义的提示，因此改为 page 模式。
		const r = await downloadToFile({ fileURL: d.fileUrl });
		const blob = r instanceof Blob ? r : r?.data;
		if (!(blob instanceof Blob)) throw new Error('invalid blob');
		const name = decodeURIComponent(d.fileUrl.split('/').pop() || `${d.label}.${d.nodeType === 'html' ? 'html' : 'png'}`);
		const u = URL.createObjectURL(blob);
		const a = document.createElement('a');
		a.href = u;
		a.download = name;
		document.body.appendChild(a);
		a.click();
		document.body.removeChild(a);
		URL.revokeObjectURL(u);
		ElMessage.success('下载成功');
	} catch {
		// 下载失败由 request 全局提示后端 message
	}
};
const syncDsl = () => {
	const lf = logicFlowInstance.value;
	if (!lf) return;
	const data = lf.getGraphData() as { nodes?: Item[]; edges?: Item[] };
	flowDsl.value = { nodes: data.nodes || [], edges: data.edges || [] };
};
const getNodeText = (node: Item) => (typeof node?.text === 'string' ? node.text : node?.text?.value || '');
const getNodeCode = (node: Item) => String(node?.properties?.nodeCode || '');
const isStartNode = (node: Item) => getNodeCode(node) === START_NODE_CODE || getNodeText(node) === START_NODE_TEXT;
const isJudgeNode = (node: Item) => {
	const code = getNodeCode(node).toLowerCase();
	const text = getNodeText(node).toLowerCase();
	return JUDGE_KEYWORDS.some((k) => code.includes(k) || text.includes(k));
};
// 判断字段是否应该显示为下拉选择
const isSelectField = (field: string) => {
	return field === 'size' || field === 'resolution';
};
// 获取下拉选项
const getSelectOptions = (field: string) => {
	if (field === 'size') {
		return [
			{ label: '1024x1024', value: '1024x1024' },
			{ label: '512x512', value: '512x512' },
			{ label: '256x256', value: '256x256' },
		];
	}
	if (field === 'resolution') {
		return [
			{ label: '1920x1080', value: '1920x1080' },
			{ label: '1280x720', value: '1280x720' },
			{ label: '3840x2160', value: '3840x2160' },
		];
	}
	return [];
};
// 添加自定义字段
const addCustomField = () => {
	customFields.value.push({ label: '', value: '', type: 'input', required: false });
};
// 删除自定义字段
const removeCustomField = (index: number) => {
	customFields.value.splice(index, 1);
};
// 判断是否可以添加自定义字段（排除判断节点、开始节点）
const canAddCustomFields = (element: SelectedState | null) => {
	if (!element || element.kind !== 'node') return false;
	const nodeCode = String(element.properties?.nodeCode || '').toLowerCase();
	const text = String(element.text || '').toLowerCase();

	// 排除判断节点
	if (JUDGE_KEYWORDS.some((k) => nodeCode.includes(k) || text.includes(k))) return false;

	// 排除开始节点
	if (nodeCode === START_NODE_CODE || text === START_NODE_TEXT.toLowerCase()) return false;

	return true;
};
// 判断是否可以选择上级参数（排除表单参数节点和开始节点）
const canSelectParentParams = (element: SelectedState | null) => {
	if (!element || element.kind !== 'node') return false;
	const nodeCode = String(element.properties?.nodeCode || '').toLowerCase();
	const text = String(element.text || '').toLowerCase();

	// 排除开始节点
	if (nodeCode === START_NODE_CODE || text === START_NODE_TEXT.toLowerCase()) return false;

	// 排除表单参数节点
	if (nodeCode === 'form' || text.includes('表单参数')) return false;

	return true;
};
// 添加上级参数到 inputSource
const addParentParam = (value: string) => {
	if (!value) return;
	if (!selectedElement.value) return;

	const lf = logicFlowInstance.value;
	if (!lf) return;

	const currentProps = selectedElement.value.properties || {};
	let inputSource = Array.isArray(currentProps.inputSource) ? [...currentProps.inputSource] : [];

	// 提取节点ID和参数名
	const match = value.match(/\$\{([^.]+)\.(.+)\}/);
	if (!match) return;

	const nodeId = match[1];
	const paramName = match[2];

	// 查找是否已经有这个节点的引用
	const existingIndex = inputSource.findIndex((item: any) => item.nodeId === nodeId);

	if (existingIndex >= 0) {
		// 已存在该节点，添加到 field 数组
		const existing = inputSource[existingIndex];
		if (!existing.field.includes(paramName)) {
			inputSource[existingIndex] = {
				...existing,
				field: [...existing.field, paramName],
			};
		}
	} else {
		// 新节点，创建新的引用
		inputSource.push({
			nodeId: nodeId,
			field: [paramName],
			quoteOutput: false,
		});
	}

	lf.setProperties(selectedElement.value.id, {
		...currentProps,
		inputSource,
	});

	// 只更新 properties，不重新赋值整个 selectedElement，避免触发 watch 重置表单
	selectedElement.value.properties = {
		...currentProps,
		inputSource,
	};

	syncDsl();
	ElMessage.success(`已添加上级参数：${paramName}`);

	selectedParentParam.value = '';
};
// 删除 inputSource 中的参数
const removeInputSource = (nodeId: string, paramName: string) => {
	if (!selectedElement.value) return;
	const lf = logicFlowInstance.value;
	if (!lf) return;

	const currentProps = selectedElement.value.properties || {};
	let inputSource = Array.isArray(currentProps.inputSource) ? [...currentProps.inputSource] : [];

	// 查找该节点
	const nodeIndex = inputSource.findIndex((item: any) => item.nodeId === nodeId);
	if (nodeIndex < 0) return;

	// 从 field 数组中删除
	const node = inputSource[nodeIndex];
	const newField = node.field.filter((f: string) => f !== paramName);

	if (newField.length > 0) {
		// 还有其他参数，更新 field
		inputSource[nodeIndex] = { ...node, field: newField };
	} else {
		// 没有参数了，删除整个节点引用
		inputSource.splice(nodeIndex, 1);
	}

	const newInputSource = inputSource.length > 0 ? inputSource : null;

	lf.setProperties(selectedElement.value.id, {
		...currentProps,
		inputSource: newInputSource,
	});

	// 只更新 properties，不重新赋值整个 selectedElement，避免触发 watch 重置表单
	selectedElement.value.properties = {
		...currentProps,
		inputSource: newInputSource,
	};

	syncDsl();
	ElMessage.success(`已删除参数：${paramName}`);
};
// 更新指定节点的 quoteOutput 状态
const updateQuoteOutput = (nodeId: string, enabled: boolean) => {
	if (!selectedElement.value || !nodeId) return;
	const lf = logicFlowInstance.value;
	if (!lf) return;

	const currentProps = selectedElement.value.properties || {};
	let inputSource = Array.isArray(currentProps.inputSource) ? [...currentProps.inputSource] : [];

	// 查找该节点
	const nodeIndex = inputSource.findIndex((item: any) => item.nodeId === nodeId);
	if (nodeIndex < 0) return;

	// 更新 quoteOutput
	inputSource[nodeIndex] = {
		...inputSource[nodeIndex],
		quoteOutput: enabled,
	};

	lf.setProperties(selectedElement.value.id, {
		...currentProps,
		inputSource,
	});

	// 只更新 properties，不重新赋值整个 selectedElement，避免触发 watch 重置表单
	selectedElement.value.properties = {
		...currentProps,
		inputSource,
	};

	syncDsl();

	// 获取节点名称用于提示
	const nodeName = formatParamReference(`\${${nodeId}.field}`).split('.')[0];
	ElMessage.success(enabled ? `已开启引入 ${nodeName} 的输出` : `已关闭引入 ${nodeName} 的输出`);
};
// 获取所有上级节点（用于显示输出引用选项）
const availableParentNodes = computed(() => {
	if (!selectedElement.value) return [];
	const lf = logicFlowInstance.value;
	if (!lf) return [];

	const graphData = lf.getGraphData() as { nodes?: Item[]; edges?: Item[] };
	const edges = graphData.edges || [];
	const nodes = graphData.nodes || [];

	// 获取已经引用了字段的节点ID列表
	const inputSource = selectedElement.value.properties?.inputSource;
	const nodesWithFields = new Set<string>();
	if (Array.isArray(inputSource)) {
		inputSource.forEach((item: any) => {
			if (item.field && item.field.length > 0) {
				nodesWithFields.add(item.nodeId);
			}
		});
	}

	// 递归查找所有上级节点
	const findAllParentNodes = (nodeId: string, visited = new Set<string>()): string[] => {
		if (visited.has(nodeId)) return [];
		visited.add(nodeId);

		const incomingEdges = edges.filter((e) => e.targetNodeId === nodeId);
		const parentIds: string[] = [];

		incomingEdges.forEach((edge) => {
			parentIds.push(edge.sourceNodeId);
			// 递归查找上级的上级
			parentIds.push(...findAllParentNodes(edge.sourceNodeId, visited));
		});

		return parentIds;
	};

	const allParentIds = findAllParentNodes(selectedElement.value.id);
	const parentNodes = allParentIds
		.map((parentId) => {
			const parentNode = nodes.find((n) => n.id === parentId);
			if (!parentNode) return null;

			const nodeName = typeof parentNode.text === 'string' ? parentNode.text : parentNode.text?.value || '';
			const nodeCode = String(parentNode.properties?.nodeCode || '').toLowerCase();
			const nodeText = nodeName.toLowerCase();

			// 判断是否为判断节点
			const isJudge = JUDGE_KEYWORDS.some((k) => nodeCode.includes(k) || nodeText.includes(k));

			// 判断是否为开始节点
			const isStart = nodeCode === START_NODE_CODE || nodeText === START_NODE_TEXT.toLowerCase();

			// 排除判断节点、开始节点、以及已经引用了字段的节点
			if (isJudge || isStart || nodesWithFields.has(parentId)) return null;

			return {
				id: parentId,
				name: nodeName,
				isJudge: false,
			};
		})
		.filter(Boolean);

	return parentNodes as Array<{ id: string; name: string; isJudge: boolean }>;
});
// 检查节点输出是否被引用
const isNodeOutputQuoted = (nodeId: string): boolean => {
	if (!selectedElement.value) return false;
	const inputSource = selectedElement.value.properties?.inputSource;
	if (!Array.isArray(inputSource)) return false;

	const node = inputSource.find((item: any) => item.nodeId === nodeId);
	return node?.quoteOutput === true;
};
// 切换节点输出引用
const toggleNodeOutput = (nodeId: string, enabled: boolean) => {
	if (!selectedElement.value || !nodeId) return;
	const lf = logicFlowInstance.value;
	if (!lf) return;

	const currentProps = selectedElement.value.properties || {};
	let inputSource = Array.isArray(currentProps.inputSource) ? [...currentProps.inputSource] : [];

	// 查找该节点
	const nodeIndex = inputSource.findIndex((item: any) => item.nodeId === nodeId);

	if (nodeIndex >= 0) {
		// 节点已存在，更新 quoteOutput
		inputSource[nodeIndex] = {
			...inputSource[nodeIndex],
			quoteOutput: enabled,
		};
	} else {
		// 节点不存在，创建新的引用（只引用输出，不引用字段）
		inputSource.push({
			nodeId: nodeId,
			field: [],
			quoteOutput: enabled,
		});
	}

	lf.setProperties(selectedElement.value.id, {
		...currentProps,
		inputSource,
	});

	// 只更新 properties，不重新赋值整个 selectedElement，避免触发 watch 重置表单
	selectedElement.value.properties = {
		...currentProps,
		inputSource,
	};

	syncDsl();

	// 获取节点名称用于提示
	const parentNode = availableParentNodes.value.find((p) => p.id === nodeId);
	const nodeName = parentNode?.name || '节点';
	ElMessage.success(enabled ? `已开启引入 ${nodeName} 的输出` : `已关闭引入 ${nodeName} 的输出`);
};
const ensureDefaultStartNode = () => {
	const lf = logicFlowInstance.value;
	if (!lf) return;
	const g = lf.getGraphData() as { nodes?: Item[]; edges?: Item[] };
	const nodes = g.nodes || [];
	if (nodes.some((n) => isStartNode(n))) return;
	lf.addNode({
		type: 'rect',
		x: 220,
		y: 140,
		text: START_NODE_TEXT,
		properties: { nodeCode: START_NODE_CODE },
	});
};
const validateFlowConstraints = () => {
	const nodes = flowDsl.value.nodes || [];
	const edges = flowDsl.value.edges || [];
	if (!nodes.length) return { ok: true };
	const nodeMap = new Map(nodes.map((n) => [n.id, n]));
	const startNode = nodes.find((n) => isStartNode(n));
	if (!startNode) return { ok: false, message: '工作流必须包含开始节点' };
	for (const e of edges) {
		if (e.targetNodeId === startNode.id) {
			return { ok: false, message: '开始节点不能被其他节点链接' };
		}
		if (e.sourceNodeId === startNode.id) {
			const target = nodeMap.get(e.targetNodeId);
			if (target && isJudgeNode(target)) {
				return { ok: false, message: '开始节点下一个节点不能是判断节点' };
			}
		}
	}
	const hasOutEdge = new Set(edges.map((e) => e.sourceNodeId));
	const endNodes = nodes.filter((n) => !hasOutEdge.has(n.id));
	if (endNodes.some((n) => isJudgeNode(n))) {
		return { ok: false, message: '结尾节点不能是判断节点' };
	}
	return { ok: true };
};
watch(
	selectedElement,
	(e) => {
		formState.text = String(e?.text || '');
		formState.nodeCode = String(e?.properties?.nodeCode || '');
		formState.field = String(e?.properties?.field || '');

		// 重置 dynamicFormValues（不删除键，保持响应式）
		for (const key in dynamicFormValues) {
			dynamicFormValues[key] = '';
		}

		// 获取当前节点的基础表单字段（直接从 nodeSchemaMap 获取，避免响应式延迟）
		const currentNodeCode = formState.nodeCode;
		const baseFormFields = nodeSchemaMap.value[currentNodeCode] || [];
		const baseFieldNames = new Set(baseFormFields.map((f) => f.field));

		// 加载自定义字段和基础字段（从 formConfig）
		customFields.value = [];
		if (e?.properties?.formConfig && Array.isArray(e.properties.formConfig)) {
			e.properties.formConfig.forEach((fieldConfig: any) => {
				if (baseFieldNames.has(fieldConfig.field)) {
					// 基础字段：加载到 dynamicFormValues，根据类型转换值
					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 {
					// 自定义字段：加载到 customFields
					customFields.value.push({
						label: fieldConfig.label || '',
						value: fieldConfig.value || '',
						type: fieldConfig.type || 'input',
						required: fieldConfig.required || false,
					});
				}
			});
		}

		// 初始化模型选择和模型相关数据（从 modelConfig）
		const modelConfig = e?.properties?.modelConfig;
		if (modelConfig && typeof modelConfig === 'object') {
			// 从 modelConfig 加载
			selectedModel.value = modelConfig.modelName || '';
			selectedModelData.value = modelConfig.modelName ? { modelName: modelConfig.modelName } : null;
			dynamicFormValues.modelApiKey = modelConfig.modelApiKey || '';

			// 加载模型表单数据（数组格式）
			if (modelConfig.modelForm && Array.isArray(modelConfig.modelForm)) {
				modelConfig.modelForm.forEach((fieldConfig: any) => {
					if (fieldConfig.field) {
						// 根据字段类型转换值
						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 {
			// 兼容旧数据格式
			selectedModel.value = String(e?.properties?.selectedModel || '');
			selectedModelData.value = e?.properties?.modelData || null;
			dynamicFormValues.modelApiKey = e?.properties?.modelApiKey || '';
		}

		// 获取当前节点的模型配置
		let nodeModelConfigs: any[] = [];
		nodeLibraryGroups.value.forEach((group) => {
			(group.items || []).forEach((item) => {
				if (item.nodeCode === currentNodeCode) {
					nodeModelConfigs = item.modelConfig || [];
				}
			});
		});

		// 如果没有选择模型但有模型配置，选择第一个
		if (!selectedModel.value && nodeModelConfigs.length > 0) {
			selectedModel.value = nodeModelConfigs[0].modelName;
		}

		// 恢复技能信息（只根据 skillName）
		if (e?.properties?.skillName) {
			// 只保存技能名称用于显示，完整信息在选择时已经保存到节点属性
			selectedSkill.value = {
				id: 0,
				name: e.properties.skillName,
				description: '',
				category: '',
				fileName: '',
				fileUrl: '',
				createdAt: '',
				updatedAt: '',
			};
		} else {
			selectedSkill.value = null;
		}

		// 初始化所有表单字段（基础 + 模型）- 只设置还没有值的字段
		allFormFields.value.forEach((fieldItem) => {
			const currentValue = dynamicFormValues[fieldItem.field];
			
			// 如果已经从 formConfig 或 modelConfig 加载过有效值，跳过
			// 对于数字类型，空字符串不是有效值
			if (fieldItem.type === 'number') {
				if (currentValue !== undefined && currentValue !== '' && currentValue !== null) {
					return;
				}
			} else {
				if (currentValue !== undefined && currentValue !== '') {
					return;
				}
			}

			// 使用默认值
			if (fieldItem.default !== undefined) {
				dynamicFormValues[fieldItem.field] = fieldItem.default;
				return;
			}

			// 根据字段类型设置默认值
			if (fieldItem.type === 'switch') {
				dynamicFormValues[fieldItem.field] = false;
			} else if (fieldItem.type === 'number') {
				// 数字字段默认为 null（而不是空字符串）
				dynamicFormValues[fieldItem.field] = null;
			} else {
				dynamicFormValues[fieldItem.field] = '';
			}
		});
	},
	{ immediate: true }
);
const applySelected = () => {
	const lf = logicFlowInstance.value,
		cur = selectedElement.value;

	if (!lf || !cur) {
		return;
	}

	if (cur.kind === 'node') {
		const missingField = allFormFields.value.find(
			(fieldItem) =>
				fieldItem.required &&
				(dynamicFormValues[fieldItem.field] === '' || dynamicFormValues[fieldItem.field] === undefined || dynamicFormValues[fieldItem.field] === null)
		);
		if (missingField) {
			ElMessage.warning(`请填写必填项：${missingField.label}`);
			return;
		}
	}

	const p: Item = { ...cur.properties, nodeCode: formState.nodeCode };
	if (cur.kind === 'edge') {
		formState.field ? (p.field = formState.field) : delete p.field;
	} else {
		// 保留重要的配置字段，删除其他字段
		const keysToKeep = ['nodeCode', 'fieldMetadata', 'modelConfig', 'inputSource', 'formConfig', 'skillName', 'width', 'height'];
		Object.keys(p).forEach((key) => {
			if (!keysToKeep.includes(key)) {
				delete p[key];
			}
		});

		// 保存选中的模型和模型相关配置
		if (selectedModel.value) {
			// 获取当前模型的表单字段
			const currentModelFields = currentModelForm.value;
			const modelForm: Array<any> = [];

			// 保存模型相关的字段配置和值
			currentModelFields.forEach((fieldItem) => {
				const value = dynamicFormValues[fieldItem.field];
				modelForm.push({
					type: fieldItem.type,
					field: fieldItem.field,
					label: fieldItem.label,
					value: value !== undefined && value !== null ? value : fieldItem.default || '',
					required: fieldItem.required || false,
				});
			});

			// 保存到 modelConfig
			p.modelConfig = {
				modelName: selectedModel.value,
				modelApiKey: dynamicFormValues.modelApiKey || '',
				modelForm: modelForm,
			};

			// 保存模型选择状态
			p.selectedModel = selectedModel.value;
			p.modelData = selectedModelData.value;
		} else {
			// 如果没有选择模型，删除 modelConfig 和模型状态
			delete p.modelConfig;
			delete p.selectedModel;
			delete p.modelData;
		}

		// 保存技能选择状态
		if (selectedSkill.value) {
			p.skillName = selectedSkill.value.name;
		} else {
			delete p.skillName;
		}

		// 不再保存基础字段到根级别，所有字段都通过 formConfig 保存

		// 保存字段元数据（label、type、required等）
		const fieldMetadata: Record<string, any> = {};
		allFormFields.value.forEach((fieldItem) => {
			fieldMetadata[fieldItem.field] = {
				label: fieldItem.label,
				type: fieldItem.type,
				required: fieldItem.required,
				field: fieldItem.field,
			};
		});
		if (Object.keys(fieldMetadata).length > 0) {
			p.fieldMetadata = fieldMetadata;
		}

		// 保存 formConfig（包含基础字段 + 自定义字段，不包含模型字段）
		const formConfig: Array<any> = [];

		// 1. 添加基础表单字段（非模型字段）
		// 重用上面的 modelFieldNames
		currentNodeForm.value.forEach((fieldItem) => {
			const value = dynamicFormValues[fieldItem.field];
			formConfig.push({
				type: fieldItem.type,
				field: fieldItem.field,
				label: fieldItem.label,
				value: value !== undefined && value !== null ? value : fieldItem.default || '',
				required: fieldItem.required || false,
			});
		});

		// 2. 添加自定义字段
		customFields.value.forEach((field) => {
			formConfig.push({
				type: field.type,
				field: field.label, // 自定义字段使用 label 作为 field
				label: field.label,
				value: field.value,
				required: field.required,
			});
		});

		// 保存 formConfig
		if (formConfig.length > 0) {
			p.formConfig = formConfig;
		} else {
			delete p.formConfig;
		}
	}

	try {
		lf.setProperties(cur.id, p);

		if (formState.text) {
			lf.updateText(cur.id, formState.text);
		}

		const g = lf.getGraphData() as { nodes: Item[]; edges: Item[] };
		const n = g.nodes.find((x) => x.id === cur.id),
			e = g.edges.find((x) => x.id === cur.id);
		selectedElement.value = n
			? { id: n.id, type: n.type, kind: 'node', properties: n.properties || {}, text: typeof n.text === 'string' ? n.text : n.text?.value }
			: e
				? { id: e.id, type: e.type, kind: 'edge', properties: e.properties || {}, text: typeof e.text === 'string' ? e.text : e.text?.value }
				: null;

		syncDsl();

		// 静默更新，不显示提示
	} catch (error) {
		ElMessage.error('应用配置失败，请查看控制台');
	}
};
// 保存工作流
const saveWorkflowAction = async () => {
	syncDsl();
	const validateResult = validateFlowConstraints();
	if (!validateResult.ok) {
		ElMessage.warning(validateResult.message);
		return;
	}

	// 显示保存对话框
	saveDialogVisible.value = true;
};
// 确认保存工作流
const confirmSaveWorkflow = async () => {
	if (!saveForm.flowName.trim()) {
		ElMessage.warning('请输入工作流名称');
		return;
	}

	saving.value = true;
	try {
		// 判断是新建还是更新
		if (currentEditingWorkflowId.value) {
			// 更新现有工作流
			await updateWorkflow({
				id: currentEditingWorkflowId.value,
				flowName: saveForm.flowName,
				description: saveForm.description,
				flowContent: workflowDsl.value,
			});
			ElMessage.success('工作流更新成功');
		} else {
			// 创建新工作流
			await saveWorkflow({
				flowName: saveForm.flowName,
				description: saveForm.description,
				flowContent: workflowDsl.value,
			});
			ElMessage.success('工作流保存成功');
		}
		saveDialogVisible.value = false;
		// 清空表单和编辑状态
		saveForm.flowName = '';
		saveForm.description = '';
		currentEditingWorkflowId.value = null;
		// 刷新工作流列表
		await fetchWorkflowList();
	} catch (error) {
		// 后端错误会自动显示
	} finally {
		saving.value = false;
	}
};
const addNodeFromLibrary = (nodeCode: string, nodeName: string) => {
	const lf = logicFlowInstance.value;
	if (!lf || !logicFlowRef.value) return;

	// 获取所有现有节点
	const graphData = lf.getGraphData() as { nodes?: Item[]; edges?: Item[] };
	const existingNodes = graphData.nodes || [];

	// 计算新节点位置，避免重叠
	let spawnX = 220;
	let spawnY = 140;

	if (existingNodes.length > 0) {
		// 如果已有节点，在最后一个节点右侧添加
		const lastNode = existingNodes[existingNodes.length - 1];
		spawnX = (lastNode.x || 220) + 180;
		spawnY = lastNode.y || 140;

		// 如果超出画布，换行
		if (spawnX > 800) {
			spawnX = 220;
			spawnY += 120;
		}
	}

	// 判断是否为判断节点
	const isJudge = JUDGE_KEYWORDS.some((k) => nodeCode.toLowerCase().includes(k) || nodeName.toLowerCase().includes(k));
	const nodeType = isJudge ? 'diamond' : 'rect';

	lf.addNode({
		type: nodeType,
		x: spawnX,
		y: spawnY,
		text: nodeName,
		properties: { nodeCode },
	});
	nodeSpawnIndex.value += 1;
	syncDsl();
};
const bindEvents = () => {
	const lf = logicFlowInstance.value;
	if (!lf) return;
	lf.on('node:click', ({ data }: { data: any }) => {
		selectedElement.value = {
			id: data.id,
			type: data.type,
			kind: 'node',
			properties: data.properties || {},
			text: typeof data.text === 'string' ? data.text : data.text?.value,
		};
	});
	lf.on('edge:click', ({ data }: { data: any }) => {
		selectedElement.value = {
			id: data.id,
			type: data.type,
			kind: 'edge',
			properties: data.properties || {},
			text: typeof data.text === 'string' ? data.text : data.text?.value,
		};
	});
	lf.on('blank:click', () => {
		selectedElement.value = null;
	});
	lf.on('connection:not-allowed', ({ msg }: { msg?: string }) => {
		ElMessage.warning(msg || '当前连线不允许');
	});
	['history:change', 'node:add', 'edge:add', 'node:delete', 'edge:delete'].forEach((n) => lf.on(n, syncDsl));
};
const initLogicFlow = () => {
	if (!logicFlowRef.value) return;
	LogicFlow.use(Control);
	LogicFlow.use(SelectionSelect);
	const lf = new LogicFlow({
		container: logicFlowRef.value,
		grid: { size: 16, visible: true, type: 'dot', config: { color: '#d7e0ef', thickness: 1 } },
		background: { backgroundColor: '#fbfcfe' },
		keyboard: { enabled: true },
		adjustEdge: true,
		edgeType: 'polyline',
		style: {
			rect: { width: 100, height: 44, radius: 8, stroke: '#334155', strokeWidth: 1.4, fill: '#fff' },
			diamond: { width: 120, height: 100, stroke: '#334155', strokeWidth: 1.4, fill: '#fff' },
			polyline: { stroke: '#475569', strokeWidth: 1.4 },
			edgeText: { fill: '#64748b', fontSize: 12, textWidth: 100, background: { fill: '#fff' } },
			text: { fontSize: 13, fill: '#1f2937' },
			anchor: { stroke: '#2563eb', fill: '#fff', r: 4 },
			nodeText: { fontSize: 13, overflowMode: 'ellipsis', lineHeight: 1.4 },
		},
	});
	// 先设置实例引用
	logicFlowInstance.value = lf;

	// 渲染空画布
	lf.render({ nodes: [], edges: [] });

	// 添加默认开始节点
	ensureDefaultStartNode();

	// 设置连线验证规则
	if (typeof lf.setValidateConnection === 'function') {
		lf.setValidateConnection(({ sourceNode, targetNode }: any) => {
			if (!sourceNode || !targetNode) return true;
			const source = sourceNode?.model || sourceNode;
			const target = targetNode?.model || targetNode;
			if (isStartNode(target)) {
				return { isAllPass: false, msg: '开始节点不能被其他节点链接' } as any;
			}
			if (isStartNode(source) && isJudgeNode(target)) {
				return { isAllPass: false, msg: '开始节点下一个节点不能是判断节点' } as any;
			}
			return true;
		});
	}

	// 绑定事件
	bindEvents();

	// 设置固定缩放比例，不使用 fitView 自动缩放
	lf.zoom(1);
	lf.translateCenter();

	// 同步 DSL
	syncDsl();
};
const resetFlow = () => {
	const lf = logicFlowInstance.value;
	if (!lf) return;
	lf.render({ nodes: [], edges: [] });
	ensureDefaultStartNode();
	lf.zoom(1);
	lf.translateCenter();
	nodeSpawnIndex.value = 0;
	selectedElement.value = null;
	syncDsl();
};
// 从后端 DSL 恢复工作流
const loadWorkflowFromDsl = (dsl: any) => {
	const lf = logicFlowInstance.value;
	if (!lf || !dsl) return;

	try {
		// 转换后端 DSL 为 LogicFlow 格式
		const nodes = (dsl.nodes || []).map((n: any) => {
			// 判断是否为判断节点
			const nodeCode = String(n.nodeCode || '').toLowerCase();
			const nodeName = String(n.name || '').toLowerCase();
			const isJudge = JUDGE_KEYWORDS.some((k) => nodeCode.includes(k) || nodeName.includes(k));
			const nodeType = isJudge ? 'diamond' : 'rect';

			return {
				id: n.id,
				type: nodeType,
				x: n.config?.x || 220,
				y: n.config?.y || 140,
				text: n.name || '',
				properties: {
					// 保留 config 中的基础配置
					...(n.config || {}),
					// 添加节点级别的重要字段
					nodeCode: n.nodeCode,
					skillName: n.skillName || null,
					formConfig: n.formConfig || null,
					modelConfig: n.modelConfig || null,
					inputSource: n.inputSource || null,
				},
			};
		});

		const edges = (dsl.edges || []).map((e: any) => ({
			id: e.id,
			type: e.type || 'polyline',
			sourceNodeId: e.from,
			targetNodeId: e.to,
			properties: e.mapping || {},
		}));

		lf.render({ nodes, edges });
		lf.zoom(1);
		lf.translateCenter();
		syncDsl();
	} catch (error) {
		ElMessage.error('工作流加载失败');
	}
};
// 监听创作模式变化，自动切换左侧 Tab
watch(isCreationMode, (newValue) => {
	if (newValue) {
		// 进入创作模式，切换到"工作空间"Tab
		leftPanelTab.value = 'workspace';
	} else {
		// 退出创作模式，切换到"当前选中"Tab
		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 () => {
	await getList();
	await nextTick();
	initLogicFlow();
	await getNodeLibrary();
	await fetchWorkflowList();
});
onBeforeUnmount(() => {
	logicFlowInstance.value?.destroy();
	logicFlowInstance.value = null;
});
</script>

<style scoped lang="scss">
.creation-page {
	height: calc(100vh - 100px);
	display: grid;
	grid-template-columns: 320px minmax(0, 1fr) 280px;
	gap: 14px;
	padding: 14px;
	background: #f6f8fb;
	box-sizing: border-box;
}

/* 左侧面板 */
.panel.left {
	display: flex;
	flex-direction: column;
	background: #fff;
	border-radius: 14px;
	padding: 14px;
	box-shadow: 0 4px 18px rgba(15, 23, 42, 0.05);
	overflow: hidden;
}
.panel.left.collapsed {
	width: 70px;
	padding: 14px 10px;
	align-items: center;
}
.panel.left.collapsed .title {
	display: none;
}
.panel.left.collapsed .panel-header {
	justify-content: center;
}
.panel-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 10px;
	flex-shrink: 0;
}
.collapse-btn {
	flex-shrink: 0;
}
.panel.left.collapsed .collapse-btn {
	width: 48px;
	height: 48px;
}
.tree-wrap {
	flex: 1;
	overflow: auto;
}
.tree-node {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
}
.ellipsis {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.editor-shell {
	display: flex;
	flex-direction: column;
	gap: 14px;
	flex: 1;
	min-height: 0;
	min-width: 0;
}
.panel {
	background: #fff;
	border-radius: 14px;
	padding: 14px;
	box-shadow: 0 4px 18px rgba(15, 23, 42, 0.05);
	overflow: hidden;
	display: flex;
	flex-direction: column;
}
.panel.side {
	min-height: 0;
	max-height: 100%;
	display: flex;
	flex-direction: column;
}
.panel.side .title-sm {
	flex-shrink: 0;
	margin-bottom: 10px;
}
.panel.canvas-panel {
	min-height: 0;
}
.title {
	font-size: 18px;
	font-weight: 700;
	color: #1f2937;
	margin-bottom: 10px;
}
.title-sm {
	font-size: 15px;
	font-weight: 700;
	color: #1f2937;
}
.sub {
	font-size: 13px;
	line-height: 1.7;
	color: #64748b;
}
.editor-shell {
	display: flex;
	flex-direction: column;
	gap: 14px;
	flex: 1;
	min-height: 0;
	min-width: 0;
}
.top {
	flex-direction: row;
	justify-content: space-between;
	align-items: flex-start;
}
.actions {
	display: flex;
	gap: 10px;
	flex-shrink: 0;
}
.main {
	display: flex;
	gap: 14px;
	flex: 1;
	min-height: 0;
	overflow: hidden;
}
/* 创作模式：表单占满整个区域 */
.creation-mode .main {
	display: block;
}
.creation-mode-container {
	display: flex;
	flex: 1;
	min-height: 0;
	height: 100%;
}
.creation-form-panel {
	flex: 1;
	display: flex;
	flex-direction: column;
	min-height: 0;
	max-width: 1200px;
	margin: 0 auto;
	width: 100%;
}
/* 画布模式：画布和侧边栏并排 */
.panel.canvas-panel {
	flex: 1;
	min-width: 0;
}
.panel.side {
	width: 380px;
	flex-shrink: 0;
	display: flex;
	flex-direction: column;
	min-height: 0;
	max-height: 100%;
}
.meta {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 10px;
}
.meta-left {
	display: flex;
	align-items: center;
	gap: 12px;
}
.meta-title {
	font-weight: 600;
	font-size: 14px;
	color: #1f2937;
}
.meta-info {
	font-size: 13px;
	color: #64748b;
}
.meta-actions {
	display: flex;
	gap: 8px;
}
.canvas-layout {
	display: grid;
	grid-template-columns: 220px minmax(0, 1fr);
	gap: 12px;
	flex: 1;
	min-height: 560px;
}
.node-library {
	border: 1px solid #e8eef7;
	border-radius: 12px;
	padding: 12px;
	overflow: auto;
	background: #f8fafc;
}
.node-library :deep(.el-empty) {
	padding: 8px 0;
}
.node-library-groups {
	display: flex;
	flex-direction: column;
	gap: 10px;
}
.node-group {
	background: #fff;
	border: 1px solid #e5e7eb;
	border-radius: 10px;
	padding: 10px 10px 8px;
}
.node-group-title {
	font-size: 13px;
	font-weight: 700;
	color: #334155;
	margin-bottom: 8px;
	padding-left: 2px;
}
.node-group-items {
	display: flex;
	flex-direction: column;
	align-items: stretch;
	gap: 4px;
}
.node-item {
	justify-content: flex-start;
	width: 100%;
	margin: 0;
	padding: 6px 8px;
	color: #334155;
	border-radius: 6px;
}
.node-item:hover {
	background: #eef4ff;
}
.canvas-wrap {
	flex: 1;
	min-height: 560px;
	border: 1px solid #e8eef7;
	border-radius: 14px;
	overflow: hidden;
	background: linear-gradient(180deg, #fcfdff 0%, #f8fbff 100%);
}
.logicflow-canvas {
	width: 100%;
	height: 100%;
	min-height: 560px;
}
.form-container {
	display: flex;
	flex-direction: column;
	flex: 1;
	min-height: 0;
	overflow: hidden;
}
.form-scroll-area {
	overflow-y: auto;
	padding-right: 4px;
	max-height: 600px;
}
.form-scroll-area::-webkit-scrollbar {
	width: 6px;
}
.form-scroll-area::-webkit-scrollbar-thumb {
	background: #cbd5e1;
	border-radius: 3px;
}
.form-scroll-area::-webkit-scrollbar-thumb:hover {
	background: #94a3b8;
}
.form-actions {
	padding-top: 12px;
	border-top: 1px solid #e5e7eb;
	flex-shrink: 0;
}
.json-preview {
	margin-top: 16px;
}
.json-preview-title {
	font-size: 13px;
	font-weight: 600;
	color: #475569;
	margin-bottom: 8px;
}
.form-wrap {
	display: flex;
	flex-direction: column;
	gap: 10px;
	font-size: 13px;
	color: #475569;
}
.prop-form {
	display: flex;
	flex-direction: column;
	gap: 4px;
}
.w100 {
	width: 100%;
}
.custom-field-config {
	display: flex;
	flex-direction: column;
	gap: 8px;
	margin-bottom: 12px;
	padding: 12px;
	border: 1px solid #e5e7eb;
	border-radius: 8px;
	background: #f9fafb;
}
.custom-field-row {
	display: flex;
	gap: 8px;
	align-items: center;
}
.custom-field-input {
	flex: 1;
	min-width: 0;
}
.custom-field-type {
	width: 90px;
	flex-shrink: 0;
}
.custom-field-required {
	flex-shrink: 0;
	margin: 0;
}
.custom-field-delete {
	flex-shrink: 0;
}
.custom-field-value-full {
	width: 100%;
}
.input-source-list {
	display: flex;
	flex-direction: column;
	gap: 8px;
	margin-bottom: 12px;
}
.input-source-item {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 10px;
	background: #f0f9ff;
	border: 1px solid #bae6fd;
	border-radius: 6px;
}
.input-source-content {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 4px;
}
.input-source-label {
	display: flex;
	align-items: center;
	gap: 8px;
	font-size: 13px;
}
.input-source-key {
	font-weight: 600;
	color: #0369a1;
}
.input-source-arrow {
	color: #94a3b8;
	font-size: 12px;
}
.input-source-ref {
	color: #0891b2;
}
.input-source-raw {
	font-size: 11px;
	color: #94a3b8;
	font-family: 'Consolas', 'Monaco', monospace;
}
.input-source-field {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 6px 0;
	border-bottom: 1px solid #f1f5f9;
}
.input-source-field:last-child {
	border-bottom: none;
}
.input-source-field-name {
	font-size: 13px;
	color: #475569;
	font-weight: 500;
}
.input-source-quote {
	margin-top: 8px;
	padding-top: 8px;
	border-top: 1px solid #e5e7eb;
}
.parent-nodes-output {
	margin-bottom: 16px;
	padding: 12px;
	background: #f9fafb;
	border-radius: 8px;
	border: 1px solid #e5e7eb;
}
.parent-nodes-title {
	font-size: 13px;
	font-weight: 600;
	color: #374151;
	margin-bottom: 12px;
}
.parent-node-output-item {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 8px 0;
	border-bottom: 1px solid #e5e7eb;
}
.parent-node-output-item:last-child {
	border-bottom: none;
}
.parent-node-name {
	font-size: 13px;
	color: #475569;
	font-weight: 500;
}
.custom-field-item {
	display: flex;
	gap: 8px;
	align-items: center;
	margin-bottom: 12px;
}
.custom-field-label {
	width: 100px;
	flex-shrink: 0;
}
.custom-field-value {
	flex: 1;
}
.json-box {
	margin: 0;
	padding: 12px;
	border-radius: 12px;
	background: #0f172a;
	color: #e2e8f0;
	font-size: 12px;
	line-height: 1.6;
	overflow: auto;
	white-space: pre-wrap;
	word-break: break-word;
}
:deep(.lf-control) {
	right: 14px;
	top: 14px;
	left: auto;
}
:deep(.lf-node-selected .lf-basic-shape) {
	stroke: #2563eb !important;
	stroke-width: 1.8 !important;
}
:deep(.lf-edge-selected path) {
	stroke: #2563eb !important;
}
@media (max-width: 1400px) {
	.panel.side {
		width: 320px;
	}
	.canvas-layout {
		grid-template-columns: 1fr;
	}
}
@media (max-width: 1100px) {
	.main {
		flex-direction: column;
	}
	.panel.side {
		width: 100%;
		max-height: 400px;
	}
	.top {
		flex-direction: column;
	}
	.actions {
		width: 100%;
		flex-wrap: wrap;
	}
}
.workflow-list-panel {
	padding: 16px;
	transition: all 0.3s ease;
}
.workflow-list-panel.collapsed {
	padding: 16px;
}
.workflow-list-panel.collapsed .workflow-list-container {
	display: none;
}
.workflow-list-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 12px;
}
.header-actions {
	display: flex;
	gap: 12px;
	align-items: center;
}
.collapse-btn-workflow {
	padding: 8px 16px;
	font-weight: 600;
}
.workflow-list-container {
	min-height: 100px;
}
.workflow-pagination {
	margin-top: 12px;
	display: flex;
	justify-content: center;
	padding: 12px 0;
	background: #f8fafc;
	border-radius: 8px;
}
.workflow-pagination :deep(.el-pagination) {
	justify-content: center;
}
.workflow-pagination :deep(.el-pager li) {
	min-width: 32px;
	height: 32px;
	line-height: 32px;
	border-radius: 6px;
	font-weight: 500;
}
.workflow-pagination :deep(.el-pager li.is-active) {
	background: #3b82f6;
	color: #fff;
}
.workflow-cards {
	display: flex;
	gap: 12px;
	flex-wrap: wrap;
}
.workflow-card {
	position: relative;
	width: 180px;
	height: 100px;
	border: 1.5px solid #e5e7eb;
	border-radius: 10px;
	padding: 14px;
	background: #fff;
	cursor: pointer;
	transition: all 0.2s ease;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
}
.workflow-card:hover {
	border-color: #3b82f6;
	box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
	transform: translateY(-2px);
}
.workflow-card-name {
	font-size: 14px;
	font-weight: 600;
	color: #3b82f6;
	text-align: center;
	overflow: hidden;
	text-overflow: ellipsis;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	-webkit-box-orient: vertical;
	line-height: 1.4;
}
.workflow-card-actions {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	background: rgba(255, 255, 255, 0.95);
	border-radius: 10px;
	display: flex;
	gap: 1px;
	justify-content: center;
	align-items: center;
	animation: fadeIn 0.2s ease;
}
@keyframes fadeIn {
	from {
		opacity: 0;
	}
	to {
		opacity: 1;
	}
}
.workflow-card-add {
	border-style: dashed;
	border-color: #cbd5e1;
	background: #f8fafc;
	gap: 8px;
}
.workflow-card-add:hover {
	border-color: #3b82f6;
	background: #eff6ff;
}
.workflow-card-add-text {
	font-size: 13px;
	color: #64748b;
	font-weight: 500;
}
.creation-mode-container {
	display: flex;
	flex: 1;
	min-height: 0;
}
.creation-form-panel {
	flex: 1;
	display: flex;
	flex-direction: column;
	min-height: 0;
	max-width: 1200px;
	margin: 0 auto;
	width: 100%;
}
.creation-header {
	display: flex;
	justify-content: space-between;
	align-items: flex-start;
	margin-bottom: 16px;
	padding-bottom: 16px;
	border-bottom: 1px solid #e5e7eb;
}
.creation-form-scroll {
	flex: 1;
	overflow-y: auto;
	padding-right: 8px;
	min-height: 0;
}
.creation-form-scroll::-webkit-scrollbar {
	width: 6px;
}
.creation-form-scroll::-webkit-scrollbar-thumb {
	background: #cbd5e1;
	border-radius: 3px;
}
.creation-form-scroll::-webkit-scrollbar-thumb:hover {
	background: #94a3b8;
}
.creation-form {
	display: flex;
	flex-direction: column;
	gap: 20px;
}
.node-form-wrapper {
	display: contents;
}
.node-form-section {
	padding: 24px;
	background: #ffffff;
	border: 2px solid #e2e8f0;
	border-radius: 16px;
	box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
	transition: all 0.3s ease;
}
.node-form-section:hover {
	box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
	border-color: #cbd5e1;
}
.node-form-title {
	font-size: 17px;
	font-weight: 700;
	color: #0f172a;
	margin-bottom: 24px;
	padding-bottom: 16px;
	border-bottom: 3px solid #3b82f6;
	display: flex;
	align-items: center;
	gap: 10px;
	background: linear-gradient(90deg, #eff6ff 0%, transparent 100%);
	padding: 12px 16px;
	margin: -24px -24px 24px -24px;
	border-radius: 14px 14px 0 0;
	border-bottom: 3px solid #3b82f6;
}
.node-icon {
	color: #3b82f6;
	font-size: 22px;
	font-weight: bold;
}
.form-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 20px 24px;
}
.form-item-full {
	grid-column: 1 / -1;
}
.form-item-medium {
	grid-column: span 1;
}
.form-item-small {
	grid-column: span 1;
}
.form-grid :deep(.el-form-item) {
	margin-bottom: 0;
}
.form-grid :deep(.el-form-item__label) {
	font-weight: 600;
	color: #1e293b;
	margin-bottom: 10px;
	font-size: 14px;
	line-height: 1.5;
}
.form-grid :deep(.el-form-item__label::before) {
	color: #ef4444 !important;
}
.form-grid :deep(.el-input__wrapper) {
	border-radius: 10px;
	box-shadow: 0 0 0 1px #e2e8f0;
	background-color: #f8fafc;
	transition: all 0.2s ease;
	padding: 8px 12px;
}
.form-grid :deep(.el-input__wrapper:hover) {
	box-shadow: 0 0 0 1px #cbd5e1;
	background-color: #ffffff;
}
.form-grid :deep(.el-input__wrapper.is-focus) {
	box-shadow: 0 0 0 2px #3b82f6;
	background-color: #ffffff;
}
.form-grid :deep(.el-input__inner) {
	color: #0f172a;
	font-size: 14px;
}
.form-grid :deep(.el-textarea__inner) {
	border-radius: 10px;
	border: 1px solid #e2e8f0;
	background-color: #f8fafc;
	transition: all 0.2s ease;
	padding: 12px;
	color: #0f172a;
	font-size: 14px;
	line-height: 1.6;
}
.form-grid :deep(.el-textarea__inner:hover) {
	border-color: #cbd5e1;
	background-color: #ffffff;
}
.form-grid :deep(.el-textarea__inner:focus) {
	border-color: #3b82f6;
	border-width: 2px;
	background-color: #ffffff;
	box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.form-grid :deep(.el-input-number) {
	width: 100%;
}
.form-grid :deep(.el-input-number .el-input__wrapper) {
	border-radius: 10px;
}
.form-grid :deep(.el-switch) {
	height: 28px;
}
.form-grid :deep(.el-switch__core) {
	height: 28px;
	border-radius: 14px;
}
.form-grid :deep(.el-switch.is-checked .el-switch__core) {
	background-color: #3b82f6;
}
.creation-actions {
	margin-top: 24px;
	padding-top: 24px;
	border-top: 2px solid #e2e8f0;
}
.creation-actions :deep(.el-button) {
	height: 52px;
	font-size: 16px;
	font-weight: 600;
	border-radius: 12px;
	background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
	border: none;
	box-shadow: 0 4px 14px rgba(59, 130, 246, 0.4);
	transition: all 0.3s ease;
}
.creation-actions :deep(.el-button:hover) {
	transform: translateY(-2px);
	box-shadow: 0 6px 20px rgba(59, 130, 246, 0.5);
	background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
}
.creation-actions :deep(.el-button:active) {
	transform: translateY(0);
}
@media (max-width: 768px) {
	.form-grid {
		grid-template-columns: 1fr;
	}
	.form-item-small,
	.form-item-medium {
		grid-column: span 1;
	}
}
@media (max-width: 1400px) {
	.panel.side {
		width: 320px;
	}
	.canvas-layout {
		grid-template-columns: 1fr;
	}
}
@media (max-width: 1100px) {
	.main {
		flex-direction: column;
	}
	.panel.side {
		width: 100%;
		max-height: 400px;
	}
	.top {
		flex-direction: column;
	}
	.actions {
		width: 100%;
		flex-wrap: wrap;
	}
}

/* 左侧Tab面板样式 */
.panel.left {
	display: flex;
	flex-direction: column;
	overflow: hidden;
}
.left-tabs {
	height: 100%;
	display: flex;
	flex-direction: column;
}
.left-tabs :deep(.el-tabs__content) {
	flex: 1;
	overflow: hidden;
}
.left-tabs :deep(.el-tab-pane) {
	height: 100%;
	overflow: auto;
}
.tree-wrap {
	padding: 14px;
	height: 100%;
	overflow: auto;
}
.selected-panel {
	padding: 14px;
	height: 100%;
	overflow: auto;
}

/* 右侧工作流列表面板样式 */
.right-panel {
	display: flex;
	flex-direction: column;
	background: #fff;
	border-radius: 14px;
	padding: 14px;
	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: flex-end;
	align-items: center;
	gap: 8px;
	margin-bottom: 12px;
	flex-shrink: 0;
}
.workflow-list-vertical {
	flex: 1;
	overflow: hidden;
	display: flex;
	flex-direction: column;
}
.workflow-list-scroll {
	flex: 1;
	overflow-y: auto;
	display: flex;
	flex-direction: column;
	gap: 10px;
	padding-right: 4px;
}
.workflow-list-scroll::-webkit-scrollbar {
	width: 6px;
}
.workflow-list-scroll::-webkit-scrollbar-thumb {
	background: #cbd5e1;
	border-radius: 3px;
}
.workflow-item {
	padding: 12px;
	border: 1px solid #e5e7eb;
	border-radius: 10px;
	cursor: pointer;
	transition: all 0.2s ease;
	background: #fff;
}
.workflow-item:hover {
	border-color: #3b82f6;
	box-shadow: 0 2px 8px rgba(59, 130, 246, 0.15);
}
.workflow-item.active {
	border-color: #3b82f6;
	background: #eff6ff;
}
.workflow-item-content {
	margin-bottom: 8px;
}
.workflow-item-name {
	font-size: 14px;
	font-weight: 600;
	color: #1e293b;
	margin-bottom: 4px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.workflow-item-desc {
	font-size: 12px;
	color: #64748b;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.workflow-item-actions {
	display: flex;
	gap: 8px;
	justify-content: flex-end;
}
.workflow-pagination {
	padding: 12px 0 0;
	display: flex;
	justify-content: center;
	border-top: 1px solid #e5e7eb;
	margin-top: 12px;
	flex-shrink: 0;
}

.skill-selector-wrapper {
	display: flex;
	flex-direction: column;
	gap: 12px;
}

.selected-skill-tag {
	margin-top: 12px;
}

.selected-skill-tag .el-tag {
	font-size: 14px;
	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 创作输入区域样式 */
.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;
}

/* 预览弹窗样式 */
.preview-container {
	width: 100%;
	height: 70vh;
	display: flex;
	align-items: center;
	justify-content: center;
}
.preview-iframe {
	width: 100%;
	height: 100%;
	border: none;
	border-radius: 8px;
	background: #fff;
}

/* 树节点操作按钮样式 */
.tree-node-actions {
	display: flex;
	gap: 4px;
	margin-left: 8px;
}
</style>
