<template>
  <div class="workflow-editor">
    <div class="editor-toolbar">
      <el-button 
        type="primary" 
        @click="handleSave"
        :loading="saving"
        :disabled="!hasChanges && !!lastSavedData"
      >
        <el-icon v-if="!saving"><Check /></el-icon>
        {{ saving ? '保存中...' : hasChanges ? '保存*' : '保存' }}
      </el-button>
      <el-button @click="handleRun">运行</el-button>
      <el-button @click="handleClear">清空</el-button>
      <el-button type="warning" @click="handleTestAnimation" :loading="testingAnimation">
        <el-icon><VideoPlay /></el-icon>
        {{ testingAnimation ? '测试中...' : '测试动画' }}
      </el-button>
      <el-button v-if="copiedNode" @click="handlePasteNodeFromButton" type="success">
        <el-icon><DocumentCopy /></el-icon>
        粘贴节点 (Ctrl+V)
      </el-button>
      <el-divider direction="vertical" />
      <!-- 节点对齐功能 -->
      <el-dropdown @command="handleAlignNodes" trigger="click">
        <el-button>
          <el-icon><Rank /></el-icon>
          对齐
          <el-icon class="el-icon--right"><ArrowDown /></el-icon>
        </el-button>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item command="left">
              <el-icon><Sort style="transform: rotate(90deg)" /></el-icon>
              左对齐
            </el-dropdown-item>
            <el-dropdown-item command="right">
              <el-icon><Sort style="transform: rotate(-90deg)" /></el-icon>
              右对齐
            </el-dropdown-item>
            <el-dropdown-item command="top">
              <el-icon><Sort /></el-icon>
              上对齐
            </el-dropdown-item>
            <el-dropdown-item command="bottom">
              <el-icon><Sort style="transform: rotate(180deg)" /></el-icon>
              下对齐
            </el-dropdown-item>
            <el-dropdown-item command="center-h">
              <el-icon><Grid /></el-icon>
              水平居中
            </el-dropdown-item>
            <el-dropdown-item command="center-v">
              <el-icon><Grid style="transform: rotate(90deg)" /></el-icon>
              垂直居中
            </el-dropdown-item>
            <el-dropdown-item command="distribute-h" divided>
              <el-icon><Operation /></el-icon>
              水平分布
            </el-dropdown-item>
            <el-dropdown-item command="distribute-v">
              <el-icon><Operation style="transform: rotate(90deg)" /></el-icon>
              垂直分布
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
      <el-button @click="handleAutoLayout" title="自动布局 (Ctrl+L)">
        <el-icon><Operation /></el-icon>
        自动布局
      </el-button>
      <el-button @click="handleApplyTemplate" title="应用工作流模板">
        <el-icon><Document /></el-icon>
        应用模板
      </el-button>
      <el-button :type="snapEnabled ? 'primary' : 'default'" @click="snapEnabled = !snapEnabled" title="吸附/网格对齐">
        <el-icon><Aim /></el-icon>
        吸附
      </el-button>
      <el-button :type="showSwimlanes ? 'primary' : 'default'" @click="showSwimlanes = !showSwimlanes" title="显示/隐藏泳道">
        <el-icon><Grid /></el-icon>
        泳道
      </el-button>
      <div class="lane-config" v-if="showSwimlanes">
        <span>泳道数</span>
        <el-input-number v-model="laneCount" :min="1" :max="8" size="small" style="width: 120px; margin-left: 6px;" />
      </div>
      <div class="toolbar-spacer"></div>
      <div class="zoom-controls">
        <el-button size="small" @click="zoomIn" title="放大 (Ctrl +)">
          <el-icon><ZoomIn /></el-icon>
        </el-button>
        <el-button size="small" @click="zoomOut" title="缩小 (Ctrl -)">
          <el-icon><ZoomOut /></el-icon>
        </el-button>
        <el-button size="small" @click="resetZoom" title="重置缩放 (Ctrl 0)">
          <el-icon><FullScreen /></el-icon>
        </el-button>
        <span class="zoom-level">{{ Math.round(currentZoom * 100) }}%</span>
      </div>
      <el-tag v-if="hasChanges" type="warning" size="small" style="margin-left: 10px;">
        <el-icon><Warning /></el-icon>
        未保存
      </el-tag>
      <!-- 协作用户列表 -->
      <div v-if="collaborationEnabled && onlineUsers.length > 0" class="collaboration-users">
        <el-popover placement="bottom" :width="200" trigger="hover">
          <template #reference>
            <el-tag type="info" size="small" style="margin-left: 10px; cursor: pointer;">
              <el-icon><User /></el-icon>
              {{ onlineUsers.length }} 人在线
            </el-tag>
          </template>
          <div class="online-users-list">
            <div v-for="user in onlineUsers" :key="user.user_id" class="user-item">
              <span class="user-color" :style="{ backgroundColor: user.color }"></span>
              <span>{{ user.username }}</span>
              <el-tag v-if="user.user_id === currentUser?.user_id" type="success" size="small">我</el-tag>
            </div>
          </div>
        </el-popover>
      </div>
      <el-tag v-if="collaborationEnabled && !collaborationConnected" type="warning" size="small" style="margin-left: 10px;">
        协作未连接
      </el-tag>
    </div>
    
    <div class="editor-container">
      <!-- 左侧节点工具箱 -->
      <div class="node-toolbox">
        <h3>节点类型</h3>
        
        <!-- 搜索框 -->
        <div class="node-search">
          <el-input
            v-model="nodeSearchKeyword"
            placeholder="搜索节点..."
            clearable
            size="small"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
        </div>
        
        <!-- 分类折叠 -->
        <div class="node-filters" v-if="nodeCategories.length > 0">
          <div class="collapse-actions">
            <el-link type="primary" :underline="false" @click="handleExpandAllGroups">展开全部</el-link>
            <el-link type="primary" :underline="false" @click="handleCollapseAllGroups" style="margin-left: 8px;">收起全部</el-link>
          </div>
          <el-collapse v-model="toolboxActiveNames">
            <el-collapse-item
              v-for="group in groupedNodeTypes"
              :key="group.category"
              :name="group.category"
            >
              <template #title>
                <span>{{ group.label }}</span>
                <el-tag size="small" type="info" style="margin-left: 6px;">{{ group.items.length }}</el-tag>
              </template>
              <div class="node-list">
                <div
                  v-for="nodeType in group.items"
                  :key="nodeType.type"
                  class="node-item"
                  draggable="true"
                  @dragstart="handleDragStart($event, nodeType)"
                  :title="nodeType.label"
                >
                  <el-icon><component :is="nodeType.icon" /></el-icon>
                  <span>{{ nodeType.label }}</span>
                </div>
                <div v-if="group.items.length === 0" class="empty-nodes">
                  <el-empty description="该分类下暂无匹配节点" :image-size="60" />
                </div>
              </div>
            </el-collapse-item>
          </el-collapse>
        </div>
        
        <!-- 画布节点搜索 -->
        <div class="canvas-node-search" v-if="nodes.length > 0">
          <el-divider />
          <div class="search-header">
            <h4>画布节点</h4>
            <el-button
              link
              type="primary"
              size="small"
              @click="handleFocusAllNodes"
              title="定位所有节点"
            >
              <el-icon><Aim /></el-icon>
            </el-button>
          </div>
          <el-input
            v-model="canvasNodeSearchKeyword"
            placeholder="搜索画布节点..."
            clearable
            size="small"
            style="margin-bottom: 8px;"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
          <div class="canvas-node-list">
            <div
              v-for="node in filteredCanvasNodes"
              :key="node.id"
              class="canvas-node-item"
              :class="{ 'is-selected': selectedNode?.id === node.id }"
              @click="handleFocusNode(node.id)"
              :title="`${node.data?.label || node.id} (${node.type})`"
            >
              <el-icon><component :is="getNodeIcon(node.type)" /></el-icon>
              <span class="node-name">{{ node.data?.label || node.id }}</span>
              <el-tag size="small" type="info">{{ node.type }}</el-tag>
            </div>
            <div v-if="filteredCanvasNodes.length === 0" class="empty-canvas-nodes">
              <el-empty description="未找到匹配的节点" :image-size="40" />
            </div>
          </div>
        </div>
      </div>
      
      <!-- 中间画布区域 -->
      <div 
        class="editor-canvas" 
        ref="canvasRef"
        @drop="handleDrop"
        @dragover.prevent="handleDragOver"
        @dragenter.prevent="handleDragEnter"
      >
        <div v-if="showSwimlanes" class="swimlane-overlay" :style="laneOverlayStyle"></div>
        <VueFlow
          :nodes="nodes"
          :edges="edges"
          :node-types="customNodeTypes"
          :connection-line-style="{ stroke: '#409eff', strokeWidth: 2.5, strokeDasharray: '5,5' }"
          :default-edge-options="{ 
            type: 'bezier', 
            animated: true,
            selectable: true,
            deletable: true,
            focusable: true,
            style: { stroke: '#409eff', strokeWidth: 2.5 },
            markerEnd: { type: 'arrowclosed', color: '#409eff', width: 20, height: 20 }
          }"
          :connection-line-type="'bezier'"
          :edges-focusable="true"
          :nodes-focusable="true"
          :delete-key-code="'Delete'"
          :connection-radius="20"
          :snap-to-grid="snapEnabled"
          :snap-grid="[20, 20]"
          :is-valid-connection="isValidConnection"
          :default-viewport="{ zoom: 1, x: 0, y: 0 }"
          :min-zoom="0.1"
          :max-zoom="4"
          :pan-on-drag="true"
          :pan-on-scroll="true"
          :zoom-on-scroll="true"
          :zoom-on-double-click="false"
          :fit-view-on-init="false"
          @node-click="onNodeClick"
          @node-double-click="onNodeDoubleClick"
          @edge-click="onEdgeClick"
          @pane-click="onPaneClick"
          @nodes-change="onNodesChange"
          @edges-change="onEdgesChange"
          @connect="onConnect"
          @connect-start="onConnectStart"
          @connect-end="onConnectEnd"
          @viewport-change="onViewportChange"
        >
          <Background />
          <Controls />
          <MiniMap />
        </VueFlow>
      </div>
      
      <!-- 右侧配置面板 -->
      <div class="config-panel" v-if="selectedNode">
        <h3>节点配置</h3>
        
        <!-- 配置标签页 -->
        <el-tabs v-model="configActiveTab" type="border-card">
          <!-- 基础配置 -->
          <el-tab-pane label="基础" name="basic">
            <el-form :model="selectedNode.data" label-width="100px">
              <el-form-item label="节点ID">
                <el-input v-model="selectedNode.id" disabled />
              </el-form-item>
              <el-form-item label="节点类型">
                <el-input v-model="selectedNode.type" disabled />
              </el-form-item>
              <el-form-item label="节点名称">
                <el-input v-model="selectedNode.data.label" />
              </el-form-item>

              <!-- 快速模板 & 变量插入 -->
              <el-divider />
          <div class="quick-actions">
            <div class="quick-item">
              <span class="quick-label">快速模板</span>
              <el-select
                v-model="templateSelection"
                size="small"
                placeholder="选择模板"
                style="width: 180px; margin-left: 8px;"
              >
                <el-option v-if="selectedNode.type === 'http'" label="HTTP GET" value="http_get" />
                <el-option v-if="selectedNode.type === 'http'" label="HTTP POST JSON" value="http_post_json" />
                <el-option v-if="selectedNode.type === 'http'" label="HTTP PUT" value="http_put" />
                <el-option v-if="selectedNode.type === 'http'" label="HTTP DELETE" value="http_delete" />
                <el-option v-if="selectedNode.type === 'llm'" label="LLM 总结" value="llm_summary" />
                <el-option v-if="selectedNode.type === 'llm'" label="LLM 翻译" value="llm_translate" />
                <el-option v-if="selectedNode.type === 'llm'" label="LLM 提取" value="llm_extract" />
                <el-option v-if="selectedNode.type === 'llm'" label="LLM 分类" value="llm_classify" />
                <el-option v-if="selectedNode.type === 'json'" label="JSON 解析" value="json_parse" />
                <el-option v-if="selectedNode.type === 'json'" label="JSON 提取" value="json_extract" />
                <el-option v-if="selectedNode.type === 'text'" label="文本拆分" value="text_split" />
                <el-option v-if="selectedNode.type === 'text'" label="文本格式化" value="text_format" />
                <el-option v-if="selectedNode.type === 'cache'" label="缓存读取" value="cache_get" />
                <el-option v-if="selectedNode.type === 'cache'" label="缓存写入" value="cache_set" />
                <el-option v-if="selectedNode.type === 'vector_db'" label="向量搜索" value="vector_search" />
                <el-option v-if="selectedNode.type === 'vector_db'" label="向量入库" value="vector_upsert" />
                <el-option v-if="selectedNode.type === 'oauth'" label="OAuth Google" value="oauth_google" />
                <el-option v-if="selectedNode.type === 'oauth'" label="OAuth GitHub" value="oauth_github" />
                <el-option v-if="selectedNode.type === 'slack'" label="Slack 发送消息" value="slack_send" />
                <el-option v-if="selectedNode.type === 'dingtalk'" label="钉钉 发送消息" value="dingtalk_send" />
                <el-option v-if="selectedNode.type === 'wechat_work'" label="企业微信 发送消息" value="wechat_send" />
              </el-select>
              <el-button size="small" type="primary" @click="applyTemplate" style="margin-left: 8px;">套用</el-button>
            </div>
            <div class="quick-item">
              <span class="quick-label">变量插入</span>
              <el-select
                v-model="variableInsertField"
                size="small"
                placeholder="选择字段"
                style="width: 150px; margin-left: 8px;"
              >
                <el-option
                  v-for="f in availableStringFields"
                  :key="f"
                  :label="f"
                  :value="f"
                />
              </el-select>
              <el-select
                v-model="variableToInsert"
                size="small"
                placeholder="选择变量"
                style="width: 140px; margin-left: 8px;"
              >
                <el-option
                  v-for="v in availableVariables"
                  :key="v"
                  :label="`{${v}}`"
                  :value="v"
                />
              </el-select>
              <el-button
                size="small"
                :disabled="!variableInsertField || !variableToInsert"
                @click="insertVariable(variableInsertField, variableToInsert)"
                style="margin-left: 8px;"
              >
                插入
              </el-button>
            </div>
          </div>
          <el-divider />
          
          <!-- 根据节点类型显示不同的配置项 -->
          <template v-if="selectedNode.type === 'llm'">
            <el-form-item label="提供商">
              <el-select v-model="selectedNode.data.provider" placeholder="选择提供商">
                <el-option label="OpenAI" value="openai" />
                <el-option label="DeepSeek" value="deepseek" />
              </el-select>
            </el-form-item>
            <el-form-item label="提示词">
              <div class="prompt-input-wrapper" style="position: relative;">
                <el-input
                  ref="promptTextareaRef"
                  v-model="selectedNode.data.prompt"
                  type="textarea"
                  :rows="4"
                  placeholder="输入提示词，可使用 {input} 或 {{variable}} 引用输入数据或变量"
                  @input="handlePromptInput"
                  @keydown="handlePromptKeydown"
                  @blur="handlePromptBlur"
                />
                
                <!-- 变量自动补全下拉框 -->
                <div
                  v-if="showVariableAutocomplete"
                  class="variable-autocomplete-dropdown"
                  :style="autocompletePosition"
                  ref="autocompleteDropdownRef"
                >
                  <div
                    v-for="(varItem, index) in filteredAutocompleteVariables"
                    :key="varItem.name"
                    class="autocomplete-item"
                    :class="{ 'is-active': index === autocompleteSelectedIndex }"
                    @click="selectAutocompleteVariable(varItem.name)"
                    @mouseenter="autocompleteSelectedIndex = index"
                  >
                    <div class="var-item-name">
                      <span class="var-name">{{ varItem.name }}</span>
                      <el-tag size="small" :type="getVarTypeTag(varItem.type)" style="margin-left: 8px;">
                        {{ varItem.type }}
                      </el-tag>
                    </div>
                    <div class="var-item-desc" v-if="varItem.description">
                      {{ varItem.description }}
                    </div>
                  </div>
                  <div v-if="filteredAutocompleteVariables.length === 0" class="autocomplete-empty">
                    未找到匹配的变量
                  </div>
                </div>
              </div>
              
              <!-- 增强的变量提示面板 -->
              <el-collapse v-model="variablePanelActive" style="margin-top: 10px;">
                <el-collapse-item title="可用变量" name="variables">
                  <div class="variable-suggestions">
                    <!-- 基础变量 -->
                    <div class="var-group" v-if="basicVariables.length > 0">
                      <div class="group-title" style="font-size: 12px; font-weight: 500; color: #606266; margin-bottom: 8px;">基础变量</div>
                      <div class="var-items" style="display: flex; flex-wrap: wrap; gap: 6px;">
                        <el-tag
                          v-for="varName in basicVariables"
                          :key="varName"
                          class="var-tag"
                          size="small"
                          type="info"
                          style="cursor: pointer;"
                          @click="insertVariable('prompt', varName)"
                        >
                          {{ varName }}
                        </el-tag>
                      </div>
                    </div>
                    
                    <!-- 上游变量 -->
                    <div class="var-group" v-if="upstreamVariablesList.length > 0" style="margin-top: 12px;">
                      <div class="group-title" style="font-size: 12px; font-weight: 500; color: #606266; margin-bottom: 8px;">上游节点变量</div>
                      <div class="var-items" style="display: flex; flex-wrap: wrap; gap: 6px;">
                        <el-tag
                          v-for="varItem in upstreamVariablesList"
                          :key="varItem.name"
                          class="var-tag"
                          size="small"
                          :type="varItem.type === 'object' ? 'warning' : varItem.type === 'array' ? 'success' : 'info'"
                          style="cursor: pointer;"
                          @click="insertVariable('prompt', varItem.name)"
                        >
                          {{ varItem.name }}
                          <el-tooltip v-if="varItem.description" :content="varItem.description" placement="top">
                            <el-icon style="margin-left: 4px;"><InfoFilled /></el-icon>
                          </el-tooltip>
                        </el-tag>
                      </div>
                    </div>
                    
                    <!-- 记忆变量 -->
                    <div class="var-group" v-if="memoryVariablesList.length > 0" style="margin-top: 12px;">
                      <div class="group-title" style="font-size: 12px; font-weight: 500; color: #606266; margin-bottom: 8px;">记忆变量</div>
                      <div class="var-items" style="display: flex; flex-wrap: wrap; gap: 6px;">
                        <el-tag
                          v-for="varItem in memoryVariablesList"
                          :key="varItem.name"
                          class="var-tag"
                          size="small"
                          type="success"
                          style="cursor: pointer;"
                          @click="insertVariable('prompt', varItem.name)"
                        >
                          {{ varItem.name }}
                          <el-tooltip v-if="varItem.description" :content="varItem.description" placement="top">
                            <el-icon style="margin-left: 4px;"><InfoFilled /></el-icon>
                          </el-tooltip>
                        </el-tag>
                      </div>
                    </div>
                  </div>
                </el-collapse-item>
              </el-collapse>
            </el-form-item>
            <el-form-item label="模型">
              <el-select v-model="selectedNode.data.model" placeholder="选择模型">
                <template v-if="!selectedNode.data.provider || selectedNode.data.provider === 'openai'">
                  <el-option label="GPT-4" value="gpt-4" />
                  <el-option label="GPT-3.5 Turbo" value="gpt-3.5-turbo" />
                  <el-option label="GPT-4 Turbo" value="gpt-4-turbo-preview" />
                </template>
                <template v-else-if="selectedNode.data.provider === 'deepseek'">
                  <el-option label="DeepSeek Chat" value="deepseek-chat" />
                  <el-option label="DeepSeek Coder" value="deepseek-coder" />
                </template>
              </el-select>
            </el-form-item>
            <el-form-item label="温度">
              <el-slider
                v-model="selectedNode.data.temperature"
                :min="0"
                :max="2"
                :step="0.1"
                show-input
              />
            </el-form-item>
            <el-form-item label="最大Token数">
              <el-input-number
                v-model="selectedNode.data.max_tokens"
                :min="1"
                :max="4096"
                placeholder="可选，留空使用默认值"
              />
            </el-form-item>
          </template>
          
          <template v-if="selectedNode.type === 'template'">
            <el-form-item label="选择模板">
              <el-select
                v-model="selectedNode.data.template_id"
                placeholder="选择模板或手动输入提示词"
                filterable
                clearable
                @change="handleTemplateChange"
                style="width: 100%"
              >
                <el-option
                  v-for="template in nodeTemplates"
                  :key="template.id"
                  :label="template.name"
                  :value="template.id"
                >
                  <div>
                    <div>{{ template.name }}</div>
                    <div style="font-size: 12px; color: #909399">{{ template.description || '无描述' }}</div>
                  </div>
                </el-option>
              </el-select>
              <div style="margin-top: 5px">
                <el-button
                  type="primary"
                  link
                  size="small"
                  @click="handleManageTemplates"
                >
                  管理模板
                </el-button>
              </div>
            </el-form-item>
            <el-form-item label="提供商">
              <el-select v-model="selectedNode.data.provider" placeholder="选择提供商">
                <el-option label="OpenAI" value="openai" />
                <el-option label="DeepSeek" value="deepseek" />
              </el-select>
            </el-form-item>
            <el-form-item label="提示词">
              <el-input
                v-model="selectedNode.data.prompt"
                type="textarea"
                :rows="6"
                placeholder="输入提示词，可使用 {input} 或 {{variable}} 引用输入数据或变量"
              />
              <div style="margin-top: 5px; color: #909399; font-size: 12px">
                提示：使用 {{变量名}} 格式定义变量，例如 {{useCase}}、{{templateType}}
              </div>
            </el-form-item>
            <el-form-item label="模型">
              <el-select v-model="selectedNode.data.model" placeholder="选择模型">
                <template v-if="!selectedNode.data.provider || selectedNode.data.provider === 'openai'">
                  <el-option label="GPT-4" value="gpt-4" />
                  <el-option label="GPT-3.5 Turbo" value="gpt-3.5-turbo" />
                  <el-option label="GPT-4 Turbo" value="gpt-4-turbo-preview" />
                </template>
                <template v-else-if="selectedNode.data.provider === 'deepseek'">
                  <el-option label="DeepSeek Chat" value="deepseek-chat" />
                  <el-option label="DeepSeek Coder" value="deepseek-coder" />
                </template>
              </el-select>
            </el-form-item>
            <el-form-item label="温度">
              <el-slider
                v-model="selectedNode.data.temperature"
                :min="0"
                :max="2"
                :step="0.1"
                show-input
              />
            </el-form-item>
            <el-form-item label="最大Token数">
              <el-input-number
                v-model="selectedNode.data.max_tokens"
                :min="1"
                :max="4096"
                placeholder="可选，留空使用默认值"
              />
            </el-form-item>
          </template>
          
          <template v-if="selectedNode.type === 'condition'">
            <el-form-item label="条件表达式">
              <el-input
                v-model="selectedNode.data.condition"
                type="textarea"
                :rows="3"
                placeholder="例如: {value} > 10&#10;或: {value} > 10 and {value} < 20&#10;或: ({status} == 'active' or {status} == 'pending') and {count} > 0"
              />
            </el-form-item>
            <el-form-item label="表达式说明">
              <el-alert
                type="info"
                :closable="false"
                show-icon
              >
                <template #title>
                  <div style="font-size: 12px;">
                    <p><strong>支持的运算符:</strong> ==, !=, >, >=, <, <=, in, not in, contains, not contains</p>
                    <p><strong>逻辑运算符:</strong> and, or, not</p>
                    <p><strong>示例:</strong></p>
                    <ul style="margin: 5px 0; padding-left: 20px;">
                      <li>{value} > 10</li>
                      <li>{status} == 'active'</li>
                      <li>{value} > 10 and {value} < 20</li>
                      <li>({status} == 'a' or {status} == 'b') and {count} > 0</li>
                    </ul>
                  </div>
                </template>
              </el-alert>
            </el-form-item>
          </template>
          
          <!-- 数据转换节点配置 -->
          <template v-if="selectedNode.type === 'transform' || selectedNode.type === 'data'">
            <el-form-item label="转换模式">
              <el-select v-model="selectedNode.data.mode" placeholder="选择转换模式">
                <el-option label="字段映射" value="mapping" />
                <el-option label="数据过滤" value="filter" />
                <el-option label="数据计算" value="compute" />
                <el-option label="全部" value="all" />
              </el-select>
            </el-form-item>
            
            <template v-if="!selectedNode.data.mode || selectedNode.data.mode === 'mapping' || selectedNode.data.mode === 'all'">
              <el-form-item label="字段映射">
                <el-alert
                  type="info"
                  :closable="false"
                  show-icon
                  style="margin-bottom: 10px;"
                >
                  <template #title>
                    <div style="font-size: 12px;">
                      格式: {"目标字段": "源字段"}<br/>
                      示例: {"new_name": "old_name", "user_id": "id"}
                    </div>
                  </template>
                </el-alert>
                <el-input
                  v-model="selectedNode.data.mapping"
                  type="textarea"
                  :rows="4"
                  placeholder='{"target_field": "source_field"}'
                />
              </el-form-item>
            </template>
            
            <template v-if="selectedNode.data.mode === 'filter' || selectedNode.data.mode === 'all'">
              <el-form-item label="过滤规则">
                <el-alert
                  type="info"
                  :closable="false"
                  show-icon
                  style="margin-bottom: 10px;"
                >
                  <template #title>
                    <div style="font-size: 12px;">
                      JSON数组格式，每个规则包含: field, operator, value<br/>
                      示例: [{"field": "status", "operator": "==", "value": "active"}]
                    </div>
                  </template>
                </el-alert>
                <el-input
                  v-model="selectedNode.data.filter_rules"
                  type="textarea"
                  :rows="4"
                  placeholder='[{"field": "status", "operator": "==", "value": "active"}]'
                />
              </el-form-item>
            </template>
            
            <template v-if="selectedNode.data.mode === 'compute' || selectedNode.data.mode === 'all'">
              <el-form-item label="计算规则">
                <el-alert
                  type="info"
                  :closable="false"
                  show-icon
                  style="margin-bottom: 10px;"
                >
                  <template #title>
                    <div style="font-size: 12px;">
                      格式: {"结果字段": "表达式"}<br/>
                      示例: {"total": "{price} * {quantity}", "sum": "{a} + {b}"}
                    </div>
                  </template>
                </el-alert>
                <el-input
                  v-model="selectedNode.data.compute_rules"
                  type="textarea"
                  :rows="4"
                  placeholder='{"result": "{a} + {b}"}'
                />
              </el-form-item>
            </template>
          </template>
          
          <!-- 循环节点配置 -->
          <template v-if="selectedNode.type === 'loop' || selectedNode.type === 'foreach'">
            <el-form-item label="数组路径">
              <el-input
                v-model="selectedNode.data.items_path"
                placeholder="例如: items 或 data.list"
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    输入数据中数组的路径，支持嵌套路径如 "data.items" 或 "items[0].list"
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="循环变量名">
              <el-input
                v-model="selectedNode.data.item_variable"
                placeholder="例如: item"
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    循环体中可通过此变量名访问当前循环项，还会自动添加 {变量名}_index 和 {变量名}_total
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="错误处理">
              <el-select v-model="selectedNode.data.error_handling" placeholder="选择错误处理方式">
                <el-option label="继续执行" value="continue" />
                <el-option label="停止循环" value="stop" />
              </el-select>
            </el-form-item>
          </template>
          
          <!-- Agent节点配置 -->
          <template v-if="selectedNode.type === 'agent'">
            <el-form-item label="Agent ID">
              <el-input
                v-model="selectedNode.data.agent_id"
                placeholder="输入Agent ID"
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    输入要执行的Agent ID，Agent将使用其配置的工作流执行
                  </div>
                </template>
              </el-alert>
            </el-form-item>
          </template>
          
          <!-- HTTP请求节点配置 -->
          <template v-if="selectedNode.type === 'http' || selectedNode.type === 'request'">
            <el-form-item label="请求URL">
              <el-input
                v-model="selectedNode.data.url"
                placeholder="https://api.example.com/endpoint"
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    支持变量替换，使用 {key} 或 ${key} 引用输入数据
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="请求方法">
              <el-select v-model="selectedNode.data.method" placeholder="选择请求方法">
                <el-option label="GET" value="GET" />
                <el-option label="POST" value="POST" />
                <el-option label="PUT" value="PUT" />
                <el-option label="DELETE" value="DELETE" />
                <el-option label="PATCH" value="PATCH" />
              </el-select>
            </el-form-item>
            <el-form-item label="请求头">
              <el-input
                v-model="selectedNode.data.headers"
                type="textarea"
                :rows="3"
                placeholder='{"Content-Type": "application/json", "Authorization": "Bearer {token}"}'
              />
            </el-form-item>
            <el-form-item label="URL参数">
              <el-input
                v-model="selectedNode.data.params"
                type="textarea"
                :rows="3"
                placeholder='{"page": 1, "limit": 10}'
              />
            </el-form-item>
            <el-form-item label="请求体">
              <el-input
                v-model="selectedNode.data.body"
                type="textarea"
                :rows="4"
                placeholder='{"name": "{name}", "value": "{value}"}'
              />
            </el-form-item>
            <!-- 超时时间已移至高级配置 -->
          </template>
          
          <!-- 数据库操作节点配置 -->
          <template v-if="selectedNode.type === 'database' || selectedNode.type === 'db'">
            <el-form-item label="数据源ID">
              <el-input
                v-model="selectedNode.data.data_source_id"
                placeholder="输入数据源ID"
              />
            </el-form-item>
            <el-form-item label="操作类型">
              <el-select v-model="selectedNode.data.operation" placeholder="选择操作类型">
                <el-option label="查询" value="query" />
                <el-option label="插入" value="insert" />
                <el-option label="更新" value="update" />
                <el-option label="删除" value="delete" />
              </el-select>
            </el-form-item>
            <el-form-item v-if="selectedNode.data.operation === 'query'" label="SQL语句">
              <el-input
                v-model="selectedNode.data.sql"
                type="textarea"
                :rows="4"
                placeholder="SELECT * FROM users WHERE id = {id}"
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    支持变量替换，使用 {key} 或 ${key} 引用输入数据
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item v-if="selectedNode.data.operation === 'insert' || selectedNode.data.operation === 'update'" label="表名">
              <el-input
                v-model="selectedNode.data.table"
                placeholder="users"
              />
            </el-form-item>
            <el-form-item v-if="selectedNode.data.operation === 'insert' || selectedNode.data.operation === 'update'" label="数据">
              <el-input
                v-model="selectedNode.data.data"
                type="textarea"
                :rows="3"
                placeholder='{"name": "{name}", "email": "{email}"}'
              />
            </el-form-item>
            <el-form-item v-if="selectedNode.data.operation === 'update' || selectedNode.data.operation === 'delete'" label="WHERE条件">
              <el-input
                v-model="selectedNode.data.where"
                type="textarea"
                :rows="2"
                placeholder='{"id": "{id}"}'
              />
            </el-form-item>
          </template>
          
          <!-- Webhook节点配置 -->
          <template v-if="selectedNode.type === 'webhook'">
            <el-form-item label="Webhook URL">
              <el-input
                v-model="selectedNode.data.url"
                placeholder="https://example.com/webhook"
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    支持变量替换，使用 {key} 或 ${key} 引用输入数据
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="请求方法">
              <el-select v-model="selectedNode.data.method" placeholder="选择请求方法">
                <el-option label="POST" value="POST" />
                <el-option label="PUT" value="PUT" />
                <el-option label="PATCH" value="PATCH" />
              </el-select>
            </el-form-item>
            <el-form-item label="请求头">
              <el-input
                v-model="selectedNode.data.headers"
                type="textarea"
                :rows="4"
                placeholder='{"Content-Type": "application/json", "Authorization": "Bearer {token}"}'
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    JSON格式，支持变量替换
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="请求体">
              <el-input
                v-model="selectedNode.data.body"
                type="textarea"
                :rows="6"
                placeholder='{"key": "value", "data": "{input_data}"}'
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    JSON格式，支持变量替换。如果为空，将使用输入数据作为请求体
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <!-- 超时时间已移至高级配置 -->
          </template>

          <!-- 邮件节点配置 -->
          <template v-if="selectedNode.type === 'email' || selectedNode.type === 'mail'">
            <el-form-item label="SMTP服务器">
              <el-input
                v-model="selectedNode.data.smtp_host"
                placeholder="smtp.example.com"
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    支持变量替换，使用 {key} 或 ${key} 引用输入数据
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="SMTP端口">
              <el-input-number
                v-model="selectedNode.data.smtp_port"
                :min="1"
                :max="65535"
                placeholder="587"
                style="width: 100%"
              />
            </el-form-item>
            <el-form-item label="SMTP用户名">
              <el-input
                v-model="selectedNode.data.smtp_user"
                placeholder="your-email@example.com"
              />
            </el-form-item>
            <el-form-item label="SMTP密码">
              <el-input
                v-model="selectedNode.data.smtp_password"
                type="password"
                placeholder="请输入SMTP密码"
                show-password
              />
            </el-form-item>
            <el-form-item label="使用TLS">
              <el-switch v-model="selectedNode.data.use_tls" />
            </el-form-item>
            <el-form-item label="发件人邮箱">
              <el-input
                v-model="selectedNode.data.from_email"
                placeholder="sender@example.com"
              />
              <el-alert
                type="warning"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    必填项，支持变量替换
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="收件人邮箱">
              <el-input
                v-model="selectedNode.data.to_email"
                placeholder="recipient@example.com"
              />
              <el-alert
                type="warning"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    必填项，多个邮箱用逗号分隔，支持变量替换
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="抄送邮箱（可选）">
              <el-input
                v-model="selectedNode.data.cc_email"
                placeholder="cc@example.com"
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    多个邮箱用逗号分隔，支持变量替换
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="密送邮箱（可选）">
              <el-input
                v-model="selectedNode.data.bcc_email"
                placeholder="bcc@example.com"
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    多个邮箱用逗号分隔，支持变量替换
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="邮件主题">
              <el-input
                v-model="selectedNode.data.subject"
                placeholder="邮件主题"
              />
              <el-alert
                type="warning"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    必填项，支持变量替换
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="邮件正文类型">
              <el-select v-model="selectedNode.data.body_type" placeholder="选择正文类型">
                <el-option label="纯文本" value="text" />
                <el-option label="HTML" value="html" />
              </el-select>
            </el-form-item>
            <el-form-item label="邮件正文">
              <el-input
                v-model="selectedNode.data.body"
                type="textarea"
                :rows="8"
                placeholder="邮件正文内容，支持变量替换"
              />
              <el-alert
                type="warning"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    必填项，支持变量替换。HTML类型支持HTML标签
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="附件（可选）">
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-bottom: 10px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    JSON数组格式，每个附件包含: file_path（文件路径）或 file_content（base64编码内容）、file_name（文件名）<br/>
                    示例: [{"file_path": "/path/to/file.pdf", "file_name": "document.pdf"}]
                  </div>
                </template>
              </el-alert>
              <el-input
                v-model="selectedNode.data.attachments"
                type="textarea"
                :rows="4"
                placeholder='[{"file_path": "/path/to/file.pdf", "file_name": "document.pdf"}]'
              />
            </el-form-item>
          </template>

          <!-- 消息队列节点配置 -->
          <template v-if="selectedNode.type === 'message_queue' || selectedNode.type === 'mq' || selectedNode.type === 'rabbitmq' || selectedNode.type === 'kafka'">
            <el-form-item label="队列类型">
              <el-select v-model="selectedNode.data.queue_type" placeholder="选择队列类型">
                <el-option label="RabbitMQ" value="rabbitmq" />
                <el-option label="Kafka" value="kafka" />
              </el-select>
            </el-form-item>
            
            <!-- RabbitMQ配置 -->
            <template v-if="selectedNode.data.queue_type === 'rabbitmq'">
              <el-form-item label="主机地址">
                <el-input
                  v-model="selectedNode.data.host"
                  placeholder="localhost"
                />
                <el-alert
                  type="info"
                  :closable="false"
                  show-icon
                  style="margin-top: 5px;"
                >
                  <template #title>
                    <div style="font-size: 12px;">
                      支持变量替换，使用 {key} 或 ${key} 引用输入数据
                    </div>
                  </template>
                </el-alert>
              </el-form-item>
              <el-form-item label="端口">
                <el-input-number
                  v-model="selectedNode.data.port"
                  :min="1"
                  :max="65535"
                  placeholder="5672"
                  style="width: 100%"
                />
              </el-form-item>
              <el-form-item label="用户名">
                <el-input
                  v-model="selectedNode.data.username"
                  placeholder="guest"
                />
              </el-form-item>
              <el-form-item label="密码">
                <el-input
                  v-model="selectedNode.data.password"
                  type="password"
                  placeholder="guest"
                  show-password
                />
              </el-form-item>
              <el-form-item label="Exchange（可选）">
                <el-input
                  v-model="selectedNode.data.exchange"
                  placeholder="exchange名称"
                />
                <el-alert
                  type="info"
                  :closable="false"
                  show-icon
                  style="margin-top: 5px;"
                >
                  <template #title>
                    <div style="font-size: 12px;">
                      如果配置了exchange，将使用exchange和routing_key发送消息
                    </div>
                  </template>
                </el-alert>
              </el-form-item>
              <el-form-item label="Routing Key">
                <el-input
                  v-model="selectedNode.data.routing_key"
                  placeholder="routing_key"
                />
                <el-alert
                  type="info"
                  :closable="false"
                  show-icon
                  style="margin-top: 5px;"
                >
                  <template #title>
                    <div style="font-size: 12px;">
                      支持变量替换。如果未配置exchange，将作为queue_name使用
                    </div>
                  </template>
                </el-alert>
              </el-form-item>
              <el-form-item label="队列名称">
                <el-input
                  v-model="selectedNode.data.queue_name"
                  placeholder="queue名称"
                />
                <el-alert
                  type="warning"
                  :closable="false"
                  show-icon
                  style="margin-top: 5px;"
                >
                  <template #title>
                    <div style="font-size: 12px;">
                      必填项（如果未配置exchange），支持变量替换
                    </div>
                  </template>
                </el-alert>
              </el-form-item>
            </template>
            
            <!-- Kafka配置 -->
            <template v-if="selectedNode.data.queue_type === 'kafka'">
              <el-form-item label="Bootstrap Servers">
                <el-input
                  v-model="selectedNode.data.bootstrap_servers"
                  placeholder="localhost:9092"
                />
                <el-alert
                  type="info"
                  :closable="false"
                  show-icon
                  style="margin-top: 5px;"
                >
                  <template #title>
                    <div style="font-size: 12px;">
                      多个服务器用逗号分隔，例如: localhost:9092,localhost:9093<br/>
                      支持变量替换
                    </div>
                  </template>
                </el-alert>
              </el-form-item>
              <el-form-item label="Topic">
                <el-input
                  v-model="selectedNode.data.topic"
                  placeholder="topic名称"
                />
                <el-alert
                  type="warning"
                  :closable="false"
                  show-icon
                  style="margin-top: 5px;"
                >
                  <template #title>
                    <div style="font-size: 12px;">
                      必填项，支持变量替换
                    </div>
                  </template>
                </el-alert>
              </el-form-item>
            </template>
            
            <!-- 消息内容配置（通用） -->
            <el-form-item label="消息内容">
              <el-input
                v-model="selectedNode.data.message"
                type="textarea"
                :rows="6"
                placeholder='{"key": "value"} 或 JSON字符串，支持变量替换。如果为空，将使用输入数据作为消息'
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    JSON格式，支持变量替换。如果为空，将使用输入数据作为消息内容
                  </div>
                </template>
              </el-alert>
            </el-form-item>
          </template>

          <!-- 结束节点配置 -->
          <template v-if="selectedNode.type === 'end' || selectedNode.type === 'output'">
            <el-form-item label="输出格式">
              <el-select v-model="selectedNode.data.output_format" placeholder="选择输出格式">
                <el-option label="纯文本（适合对话）" value="text" />
                <el-option label="JSON格式" value="json" />
              </el-select>
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    <strong>纯文本</strong>：自动提取文本内容，适合对话场景<br/>
                    <strong>JSON格式</strong>：保留完整数据结构，适合API调用
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item label="描述">
              <el-input
                v-model="selectedNode.data.description"
                type="textarea"
                :rows="2"
                placeholder="节点描述（可选）"
              />
            </el-form-item>
          </template>

          <!-- 定时任务节点配置 -->
          <template v-if="isScheduleNodeSelected">
            <el-form-item label="延迟类型">
              <el-select v-model="selectedNode.data.delay_type" placeholder="选择延迟类型">
                <el-option label="固定延迟" value="fixed" />
              </el-select>
            </el-form-item>
            <el-form-item label="延迟值">
              <el-input-number 
                v-model="selectedNode.data.delay_value" 
                :min="0" 
                :precision="0"
                placeholder="请输入延迟值"
                style="width: 100%"
              />
            </el-form-item>
            <el-form-item label="时间单位">
              <el-select v-model="selectedNode.data.delay_unit" placeholder="选择时间单位">
                <el-option label="秒" value="seconds" />
                <el-option label="分钟" value="minutes" />
                <el-option label="小时" value="hours" />
              </el-select>
            </el-form-item>
            <el-alert :title="delayAlertTitle" type="info" :closable="false" style="margin-top: 10px" />

          </template>
          <!-- 文件操作节点配置 -->
          <template v-if="selectedNode.type === 'file' || selectedNode.type === 'file_operation'">
            <el-form-item label="操作类型">
              <el-select v-model="selectedNode.data.operation" placeholder="选择操作类型">
                <el-option label="读取" value="read" />
                <el-option label="写入" value="write" />
                <el-option label="上传" value="upload" />
                <el-option label="下载" value="download" />
              </el-select>
            </el-form-item>
            <el-form-item v-if="selectedNode.data.operation === 'read' || selectedNode.data.operation === 'write' || selectedNode.data.operation === 'download'" label="文件路径">
              <el-input
                v-model="selectedNode.data.file_path"
                placeholder="/path/to/file.txt 或 {path} 使用变量"
              />
              <el-alert
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 5px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    支持变量替换，使用 {key} 或 ${key} 引用输入数据
                  </div>
                </template>
              </el-alert>
            </el-form-item>
            <el-form-item v-if="selectedNode.data.operation === 'write'" label="文件内容">
              <el-input
                v-model="selectedNode.data.content"
                type="textarea"
                :rows="6"
                placeholder="文件内容，支持变量替换"
              />
            </el-form-item>
            <el-form-item v-if="selectedNode.data.operation === 'read' || selectedNode.data.operation === 'write'" label="编码">
              <el-select v-model="selectedNode.data.encoding" placeholder="选择编码">
                <el-option label="UTF-8" value="utf-8" />
                <el-option label="GBK" value="gbk" />
                <el-option label="ASCII" value="ascii" />
              </el-select>
            </el-form-item>
            <el-form-item v-if="selectedNode.data.operation === 'upload'" label="上传类型">
              <el-select v-model="selectedNode.data.upload_type" placeholder="选择上传类型">
                <el-option label="Base64" value="base64" />
                <el-option label="URL" value="url" />
              </el-select>
            </el-form-item>
            <el-form-item v-if="selectedNode.data.operation === 'upload' && selectedNode.data.upload_type === 'url'" label="URL">
              <el-input
                v-model="selectedNode.data.url"
                placeholder="https://example.com/file.pdf"
              />
            </el-form-item>
            <el-form-item v-if="selectedNode.data.operation === 'upload'" label="目标路径">
              <el-input
                v-model="selectedNode.data.target_path"
                placeholder="/path/to/save/file.ext"
              />
            </el-form-item>
            <el-form-item v-if="selectedNode.data.operation === 'download'" label="下载格式">
              <el-select v-model="selectedNode.data.download_format" placeholder="选择下载格式">
                <el-option label="Base64编码" value="base64" />
                <el-option label="文件路径" value="path" />
              </el-select>
            </el-form-item>
          </template>
          
          <el-form-item>
            <div class="config-actions">
              <el-button type="primary" @click="handleSaveNode">保存配置</el-button>
              <el-button @click="handleCopyNode">复制节点</el-button>
              <el-button type="danger" @click="handleDeleteNode">删除节点</el-button>
            </div>
          </el-form-item>
            </el-form>
          </el-tab-pane>
          
          <!-- 数据流转可视化 -->
          <el-tab-pane label="数据流" name="dataflow">
            <!-- 上游数据源 -->
            <el-card shadow="never" class="dataflow-card" style="margin-bottom: 15px;">
              <template #header>
                <div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
                  <span>📥 输入数据源</span>
                  <el-tag size="small" type="info">{{ upstreamEdges.length }} 个上游节点</el-tag>
                </div>
              </template>
              
              <div v-if="upstreamEdges.length === 0" class="empty-state">
                <el-empty description="暂无上游节点" :image-size="80" />
              </div>
              
              <!-- 上游节点列表 -->
              <div v-for="edge in upstreamEdges" :key="edge.id" class="upstream-item" style="margin-bottom: 15px; padding: 12px; border: 1px solid #e4e7ed; border-radius: 4px;">
                <div class="node-info" style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
                  <el-icon><ArrowLeft /></el-icon>
                  <span class="node-name" style="font-weight: 500;">{{ getNodeLabel(edge.source) }}</span>
                  <el-tag size="small" :type="getNodeTypeColor(edge.source)">
                    {{ getNodeType(edge.source) }}
                  </el-tag>
                </div>
                
                <!-- 可用变量列表 -->
                <div class="variables-list" v-if="getUpstreamVariables(edge.source).length > 0">
                  <div class="var-group-title" style="font-size: 12px; color: #909399; margin-bottom: 8px;">可用变量：</div>
                  <div style="display: flex; flex-wrap: wrap; gap: 6px;">
                    <el-tag
                      v-for="varItem in getUpstreamVariables(edge.source)"
                      :key="varItem.name"
                      class="variable-item"
                      size="small"
                      :type="varItem.type === 'object' ? 'warning' : varItem.type === 'array' ? 'success' : 'info'"
                      style="cursor: pointer;"
                      @click="insertVariableFromDataflow(varItem.name)"
                    >
                      {{ varItem.name }}
                      <el-tooltip v-if="varItem.description" :content="varItem.description" placement="top">
                        <el-icon style="margin-left: 4px;"><InfoFilled /></el-icon>
                      </el-tooltip>
                    </el-tag>
                  </div>
                </div>
                
                <!-- 数据预览（如果有执行记录） -->
                <el-collapse v-if="hasUpstreamExecutionData(edge.source)" style="margin-top: 10px;">
                  <el-collapse-item title="数据预览" name="preview">
                    <div style="margin-top: 8px;">
                      <el-select
                        :model-value="upstreamExecutionDataMap[edge.source]?.executionId"
                        @update:model-value="(value) => updateUpstreamExecutionId(edge.source, value)"
                        placeholder="选择执行记录"
                        size="small"
                        style="width: 100%; margin-bottom: 8px;"
                        @change="loadUpstreamNodeData(edge.source)"
                        clearable
                      >
                        <el-option
                          v-for="exec in recentExecutions"
                          :key="exec.id"
                          :label="`${formatExecutionTime(exec.created_at)} - ${exec.status}`"
                          :value="exec.id"
                        />
                      </el-select>
                      
                      <div v-if="upstreamExecutionDataMap[edge.source]?.data">
                        <el-card shadow="never" style="margin-bottom: 8px;">
                          <template #header>
                            <span style="font-size: 12px;">📤 输出数据</span>
                          </template>
                          <el-scrollbar height="150px">
                            <pre style="margin: 0; padding: 8px; font-size: 11px; line-height: 1.4; background: #f5f7fa; border-radius: 4px;">{{ formatJSON(upstreamExecutionDataMap[edge.source].data.output) }}</pre>
                          </el-scrollbar>
                        </el-card>
                        <div style="font-size: 11px; color: #909399; text-align: right;">
                          执行时间: {{ upstreamExecutionDataMap[edge.source].data.duration || '未知' }}ms
                        </div>
                      </div>
                      <div v-else-if="upstreamExecutionDataMap[edge.source]?.loading" style="text-align: center; padding: 20px;">
                        <el-icon class="is-loading"><Loading /></el-icon>
                        <span style="margin-left: 8px; color: #909399;">加载中...</span>
                      </div>
                    </div>
                  </el-collapse-item>
                </el-collapse>
              </div>
            </el-card>
            
            <!-- 当前节点输出 -->
            <el-card shadow="never" class="dataflow-card">
              <template #header>
                <div class="card-header">
                  <span>📤 输出数据</span>
                </div>
              </template>
              
              <!-- 输出字段说明 -->
              <div class="output-fields" v-if="getOutputFields(selectedNode?.type || '').length > 0">
                <div class="var-group-title" style="font-size: 12px; color: #909399; margin-bottom: 8px;">输出字段：</div>
                <div
                  v-for="field in getOutputFields(selectedNode?.type || '')"
                  :key="field.name"
                  class="output-field"
                  style="padding: 8px; margin-bottom: 8px; background: #f5f7fa; border-radius: 4px;"
                >
                  <div style="display: flex; align-items: center; gap: 8px;">
                    <span class="field-name" style="font-weight: 500; color: #409eff;">{{ field.name }}</span>
                    <el-tag size="small" type="info">{{ field.type }}</el-tag>
                  </div>
                  <div class="field-desc" style="font-size: 12px; color: #909399; margin-top: 4px;">{{ field.description }}</div>
                </div>
              </div>
              <div v-else class="empty-state">
                <el-empty description="无输出字段说明" :image-size="60" />
              </div>
              
              <!-- 下游节点 -->
              <div v-if="downstreamNodes.length > 0" class="downstream-section" style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #e4e7ed;">
                <div class="section-title" style="font-size: 12px; color: #909399; margin-bottom: 8px;">下游节点：</div>
                <div
                  v-for="node in downstreamNodes"
                  :key="node.id"
                  class="downstream-item"
                  style="display: flex; align-items: center; gap: 8px; padding: 6px; margin-bottom: 4px;"
                >
                  <el-icon><ArrowRight /></el-icon>
                  <span>{{ node.data.label }}</span>
                  <el-tag size="small" type="info">{{ node.type }}</el-tag>
                </div>
              </div>
            </el-card>
          </el-tab-pane>
          
          <!-- 记忆信息展示（仅 Cache 节点） -->
          <el-tab-pane label="记忆信息" name="memory" v-if="selectedNode?.type === 'cache'">
            <el-card shadow="never" style="margin-bottom: 15px;">
              <template #header>
                <div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
                  <span>💾 记忆内容</span>
                  <el-button size="small" @click="refreshMemory" :loading="memoryLoading">
                    <el-icon><Refresh /></el-icon>
                    刷新
                  </el-button>
                </div>
              </template>
              
              <!-- 记忆键 -->
              <el-form-item label="记忆键">
                <el-input
                  :model-value="memoryKey"
                  placeholder="user_memory_{user_id}"
                  readonly
                />
                <el-tag size="small" style="margin-left: 8px;" :type="memoryStatus === '存在' ? 'success' : 'info'">
                  {{ memoryStatus }}
                </el-tag>
              </el-form-item>
              
              <!-- 对话历史 -->
              <el-form-item label="对话历史">
                <el-tag
                  v-if="memoryData?.conversation_history"
                  type="info"
                >
                  {{ memoryData.conversation_history.length }} 条记录
                </el-tag>
                <el-tag v-else type="info">无记录</el-tag>
                <el-button
                  v-if="memoryData?.conversation_history && memoryData.conversation_history.length > 0"
                  size="small"
                  text
                  @click="showConversationHistory = true"
                  style="margin-left: 8px;"
                >
                  查看详情
                </el-button>
              </el-form-item>
              
              <!-- 用户画像 -->
              <el-form-item label="用户画像">
                <el-tag
                  v-if="memoryData?.user_profile"
                  type="success"
                >
                  {{ Object.keys(memoryData.user_profile).length }} 个字段
                </el-tag>
                <el-tag v-else type="info">无数据</el-tag>
                <el-button
                  v-if="memoryData?.user_profile && Object.keys(memoryData.user_profile).length > 0"
                  size="small"
                  text
                  @click="showUserProfile = true"
                  style="margin-left: 8px;"
                >
                  查看详情
                </el-button>
              </el-form-item>
              
              <!-- TTL 信息 -->
              <el-form-item label="过期时间" v-if="selectedNode.data.ttl">
                <el-tag type="warning">
                  {{ ttlInfo }}
                </el-tag>
              </el-form-item>
            </el-card>
            
            <!-- 记忆操作 -->
            <el-card shadow="never">
              <template #header>
                <span>🔧 记忆操作</span>
              </template>
              
              <el-button-group>
                <el-button size="small" @click="testMemoryQuery">测试查询</el-button>
                <el-button size="small" @click="clearMemory">清空记忆</el-button>
                <el-button size="small" type="danger" @click="deleteMemory">删除记忆</el-button>
              </el-button-group>
            </el-card>
            
            <!-- 对话历史详情对话框 -->
            <el-dialog v-model="showConversationHistory" title="对话历史" width="70%">
              <el-scrollbar height="400px">
                <div v-for="(msg, index) in memoryData?.conversation_history" :key="index" style="margin-bottom: 15px; padding: 12px; border: 1px solid #e4e7ed; border-radius: 4px;">
                  <div style="font-weight: 500; margin-bottom: 8px;">
                    <el-tag size="small" :type="msg.role === 'user' ? 'primary' : 'success'">{{ msg.role === 'user' ? '用户' : '助手' }}</el-tag>
                    <span style="margin-left: 8px; color: #909399; font-size: 12px;">{{ msg.timestamp || '未知时间' }}</span>
                  </div>
                  <div style="white-space: pre-wrap;">{{ msg.content }}</div>
                </div>
              </el-scrollbar>
            </el-dialog>
            
            <!-- 用户画像详情对话框 -->
            <el-dialog v-model="showUserProfile" title="用户画像" width="60%">
              <el-descriptions :column="2" border>
                <el-descriptions-item
                  v-for="(value, key) in memoryData?.user_profile"
                  :key="key"
                  :label="key"
                >
                  {{ typeof value === 'object' ? JSON.stringify(value, null, 2) : value }}
                </el-descriptions-item>
              </el-descriptions>
            </el-dialog>
          </el-tab-pane>
          
          <!-- 执行数据预览 -->
          <el-tab-pane label="数据预览" name="preview">
            <el-card shadow="never" style="margin-bottom: 15px;">
              <template #header>
                <div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
                  <span>📊 执行数据</span>
                  <el-select 
                    v-model="selectedExecutionId"
                    placeholder="选择执行记录"
                    size="small"
                    style="width: 200px;"
                    @change="loadNodeExecutionData"
                    clearable
                  >
                    <el-option
                      v-for="exec in recentExecutions"
                      :key="exec.id"
                      :label="`${formatExecutionTime(exec.created_at)} - ${exec.status}`"
                      :value="exec.id"
                    />
                  </el-select>
                </div>
              </template>
              
              <div v-if="!selectedExecutionId" class="empty-state">
                <el-empty description="请选择执行记录查看数据" :image-size="80" />
              </div>
              
              <div v-else>
                <!-- 输入数据 -->
                <el-card shadow="never" style="margin-bottom: 15px;">
                  <template #header>
                    <div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
                      <span>📥 输入数据</span>
                      <el-button
                        text
                        size="small"
                        @click="copyToClipboard(executionData?.input)"
                        title="复制"
                      >
                        <el-icon><DocumentCopy /></el-icon>
                      </el-button>
                    </div>
                  </template>
                  <el-scrollbar height="200px" v-if="executionData?.input">
                    <pre class="data-viewer" style="margin: 0; padding: 12px; font-size: 12px; line-height: 1.5;">{{ formatJSON(executionData.input) }}</pre>
                  </el-scrollbar>
                  <div v-else class="empty-state">
                    <el-empty description="无输入数据" :image-size="60" />
                  </div>
                </el-card>
                
                <!-- 输出数据 -->
                <el-card shadow="never">
                  <template #header>
                    <div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
                      <span>📤 输出数据</span>
                      <el-button
                        text
                        size="small"
                        @click="copyToClipboard(executionData?.output)"
                        title="复制"
                      >
                        <el-icon><DocumentCopy /></el-icon>
                      </el-button>
                    </div>
                  </template>
                  <el-scrollbar height="200px" v-if="executionData?.output">
                    <pre class="data-viewer" style="margin: 0; padding: 12px; font-size: 12px; line-height: 1.5;">{{ formatJSON(executionData.output) }}</pre>
                  </el-scrollbar>
                  <div v-else class="empty-state">
                    <el-empty description="无输出数据" :image-size="60" />
                  </div>
                </el-card>
                
                <!-- 执行信息 -->
                <el-descriptions :column="2" border style="margin-top: 15px;" v-if="executionData">
                  <el-descriptions-item label="执行时间">
                    {{ executionData.duration ? `${executionData.duration}ms` : '未知' }}
                  </el-descriptions-item>
                  <el-descriptions-item label="开始时间">
                    {{ executionData.start_time ? formatExecutionTime(executionData.start_time) : '未知' }}
                  </el-descriptions-item>
                  <el-descriptions-item label="完成时间">
                    {{ executionData.complete_time ? formatExecutionTime(executionData.complete_time) : '未知' }}
                  </el-descriptions-item>
                  <!-- 缓存命中情况（Cache节点或输出中包含cache_hit） -->
                  <el-descriptions-item 
                    v-if="selectedNode?.type === 'cache' || executionData?.output?.cache_hit !== undefined"
                    label="缓存命中"
                  >
                    <el-tag :type="executionData?.output?.cache_hit ? 'success' : 'info'" size="small">
                      {{ executionData?.output?.cache_hit ? '✅ 命中' : '❌ 未命中' }}
                    </el-tag>
                    <span v-if="executionData?.output?.cache_hit" style="margin-left: 8px; font-size: 12px; color: #909399;">
                      (从缓存读取)
                    </span>
                    <span v-else style="margin-left: 8px; font-size: 12px; color: #909399;">
                      (执行计算)
                    </span>
                  </el-descriptions-item>
                </el-descriptions>
                
                <!-- Cache节点额外信息 -->
                <el-card v-if="selectedNode?.type === 'cache' && executionData?.output" shadow="never" style="margin-top: 15px;">
                  <template #header>
                    <span style="font-size: 13px;">💾 缓存信息</span>
                  </template>
                  <el-descriptions :column="1" border size="small">
                    <el-descriptions-item label="缓存键">
                      <code style="font-size: 11px;">{{ executionData.output.key || '未知' }}</code>
                    </el-descriptions-item>
                    <el-descriptions-item label="缓存状态">
                      <el-tag :type="executionData.output.cache_hit ? 'success' : 'warning'" size="small">
                        {{ executionData.output.cache_hit ? '已缓存' : '未缓存' }}
                      </el-tag>
                    </el-descriptions-item>
                    <el-descriptions-item v-if="executionData.output.memory" label="记忆数据">
                      <el-tag type="info" size="small">
                        {{ Object.keys(executionData.output.memory || {}).length }} 个字段
                      </el-tag>
                      <el-button
                        size="small"
                        text
                        @click="showCacheMemoryDetail = true; cacheMemoryDetail = executionData.output.memory"
                        style="margin-left: 8px;"
                      >
                        查看详情
                      </el-button>
                    </el-descriptions-item>
                  </el-descriptions>
                </el-card>
                
                <!-- 缓存记忆详情对话框 -->
                <el-dialog v-model="showCacheMemoryDetail" title="缓存记忆详情" width="70%">
                  <el-scrollbar height="400px">
                    <pre style="margin: 0; padding: 12px; background: #f5f7fa; border-radius: 4px; font-size: 12px;">{{ formatJSON(cacheMemoryDetail) }}</pre>
                  </el-scrollbar>
                </el-dialog>
              </div>
            </el-card>
          </el-tab-pane>
          
          <!-- 高级配置 -->
          <el-tab-pane label="高级" name="advanced">
            <el-form :model="selectedNode.data" label-width="100px">
              <!-- 通用高级配置 -->
              <el-form-item label="超时时间(秒)" v-if="hasTimeoutConfig">
                <el-input-number v-model="selectedNode.data.timeout" :min="1" :max="300" />
                <span style="margin-left: 8px; color: #909399; font-size: 12px;">默认30秒</span>
              </el-form-item>
              
              <el-form-item label="重试次数" v-if="hasRetryConfig">
                <el-input-number v-model="selectedNode.data.retry_count" :min="0" :max="10" />
                <span style="margin-left: 8px; color: #909399; font-size: 12px;">失败时重试次数</span>
              </el-form-item>
              
              <el-form-item label="重试延迟(ms)" v-if="hasRetryConfig && selectedNode.data.retry_count > 0">
                <el-input-number v-model="selectedNode.data.retry_delay" :min="100" :max="10000" :step="100" />
              </el-form-item>
              
              <!-- HTTP节点高级配置 -->
              <template v-if="selectedNode.type === 'http'">
                <el-form-item label="SSL验证">
                  <el-switch v-model="selectedNode.data.verify_ssl" />
                </el-form-item>
                <el-form-item label="跟随重定向">
                  <el-switch v-model="selectedNode.data.follow_redirects" />
                </el-form-item>
              </template>
              
              <!-- LLM节点高级配置 -->
              <template v-if="selectedNode.type === 'llm' || selectedNode.type === 'template'">
                <el-form-item label="流式输出">
                  <el-switch v-model="selectedNode.data.stream" />
                </el-form-item>
                <el-form-item label="Top P">
                  <el-input-number v-model="selectedNode.data.top_p" :min="0" :max="1" :step="0.1" :precision="2" />
                </el-form-item>
                <el-form-item label="频率惩罚">
                  <el-input-number v-model="selectedNode.data.frequency_penalty" :min="-2" :max="2" :step="0.1" :precision="1" />
                </el-form-item>
                <el-form-item label="存在惩罚">
                  <el-input-number v-model="selectedNode.data.presence_penalty" :min="-2" :max="2" :step="0.1" :precision="1" />
                </el-form-item>
              </template>
              
              <!-- 缓存节点高级配置 -->
              <template v-if="selectedNode.type === 'cache'">
                <el-form-item label="缓存后端">
                  <el-select v-model="selectedNode.data.backend">
                    <el-option label="内存" value="memory" />
                    <el-option label="Redis" value="redis" />
                  </el-select>
                </el-form-item>
                <el-form-item label="Redis连接" v-if="selectedNode.data.backend === 'redis'">
                  <el-input v-model="selectedNode.data.redis_url" placeholder="redis://localhost:6379/0" />
                </el-form-item>
              </template>
              
              <!-- 错误处理节点高级配置 -->
              <template v-if="selectedNode.type === 'error_handler'">
                <el-form-item label="错误处理工作流">
                  <el-input v-model="selectedNode.data.error_handler_workflow" placeholder="错误处理工作流ID（可选）" />
                </el-form-item>
                <el-form-item label="错误通知">
                  <el-switch v-model="selectedNode.data.error_notify" />
                </el-form-item>
              </template>
              
              <!-- 批处理节点高级配置 -->
              <template v-if="selectedNode.type === 'batch'">
                <el-form-item label="等待完成">
                  <el-switch v-model="selectedNode.data.wait_for_completion" />
                </el-form-item>
                <el-form-item label="并发数" v-if="selectedNode.data.wait_for_completion">
                  <el-input-number v-model="selectedNode.data.concurrency" :min="1" :max="10" />
                </el-form-item>
              </template>
              
              <!-- 向量数据库节点高级配置 -->
              <template v-if="selectedNode.type === 'vector_db'">
                <el-form-item label="相似度阈值">
                  <el-input-number v-model="selectedNode.data.similarity_threshold" :min="0" :max="1" :step="0.1" :precision="2" />
                </el-form-item>
                <el-form-item label="距离度量">
                  <el-select v-model="selectedNode.data.distance_metric">
                    <el-option label="余弦相似度" value="cosine" />
                    <el-option label="欧氏距离" value="euclidean" />
                    <el-option label="点积" value="dot" />
                  </el-select>
                </el-form-item>
              </template>
            </el-form>
          </el-tab-pane>
          
          <!-- 工具配置（仅LLM节点） -->
          <el-tab-pane label="工具" name="tools" v-if="selectedNode.type === 'llm' || selectedNode.type === 'template'">
            <el-form :model="selectedNode.data" label-width="120px">
              <el-form-item>
                <template #label>
                  <span>启用工具调用</span>
                  <el-tooltip content="启用后，LLM可以根据需要自动调用预定义的工具" placement="right">
                    <el-icon style="margin-left: 4px; cursor: help;"><QuestionFilled /></el-icon>
                  </el-tooltip>
                </template>
                <el-switch
                  v-model="selectedNode.data.enable_tools"
                  @change="handleToolsToggle"
                />
                <span style="margin-left: 8px; color: #909399; font-size: 12px;">
                  允许LLM调用工具来获取信息或执行操作
                </span>
              </el-form-item>
              
              <div v-if="selectedNode.data.enable_tools" style="margin-top: 20px;">
                <el-form-item label="选择工具">
                  <el-select
                    v-model="selectedNode.data.tools"
                    multiple
                    placeholder="选择可用工具"
                    style="width: 100%"
                    filterable
                    @change="handleToolsChange"
                  >
                    <el-option-group
                      v-for="group in toolGroups"
                      :key="group.label"
                      :label="group.label"
                    >
                      <el-option
                        v-for="tool in group.tools"
                        :key="tool.name"
                        :label="tool.name"
                        :value="tool.name"
                      >
                        <div style="display: flex; flex-direction: column;">
                          <span style="font-weight: 500;">{{ tool.name }}</span>
                          <span style="font-size: 12px; color: #909399; margin-top: 2px;">
                            {{ tool.description }}
                          </span>
                        </div>
                      </el-option>
                    </el-option-group>
                  </el-select>
                  <div style="margin-top: 8px; color: #909399; font-size: 12px;">
                    已选择 {{ (selectedNode.data.tools || []).length }} 个工具
                  </div>
                </el-form-item>
                
                <!-- 显示选中工具的详细信息 -->
                <div v-if="selectedNode.data.tools && selectedNode.data.tools.length > 0" style="margin-top: 20px;">
                  <el-divider content-position="left">工具详情</el-divider>
                  <div v-for="toolName in selectedNode.data.tools" :key="toolName" style="margin-bottom: 15px;">
                    <el-card shadow="hover" style="border: 1px solid #e4e7ed;">
                      <template #header>
                        <div style="display: flex; justify-content: space-between; align-items: center;">
                          <span style="font-weight: 500;">{{ toolName }}</span>
                          <el-button
                            type="danger"
                            size="small"
                            text
                            @click="removeTool(toolName)"
                          >
                            移除
                          </el-button>
                        </div>
                      </template>
                      <div v-if="getToolSchema(toolName)">
                        <div style="margin-bottom: 8px;">
                          <strong>描述：</strong>
                          <span style="color: #606266;">{{ getToolSchema(toolName).function?.description || '无描述' }}</span>
                        </div>
                        <div v-if="getToolSchema(toolName).function?.parameters">
                          <strong>参数：</strong>
                          <el-collapse style="margin-top: 8px;">
                            <el-collapse-item>
                              <template #title>
                                <span style="font-size: 12px;">查看参数定义</span>
                              </template>
                              <pre style="margin: 0; padding: 8px; background: #f5f7fa; border-radius: 4px; font-size: 11px; overflow-x: auto;">{{ JSON.stringify(getToolSchema(toolName).function.parameters, null, 2) }}</pre>
                            </el-collapse-item>
                          </el-collapse>
                        </div>
                      </div>
                      <div v-else style="color: #909399; font-size: 12px;">
                        工具信息加载中...
                      </div>
                    </el-card>
                  </div>
                </div>
                
                <el-alert
                  v-if="!selectedNode.data.tools || selectedNode.data.tools.length === 0"
                  type="warning"
                  :closable="false"
                  show-icon
                  style="margin-top: 15px;"
                >
                  <template #title>
                    <span style="font-size: 12px;">请至少选择一个工具</span>
                  </template>
                </el-alert>
              </div>
              
              <el-alert
                v-else
                type="info"
                :closable="false"
                show-icon
                style="margin-top: 15px;"
              >
                <template #title>
                  <div style="font-size: 12px;">
                    <p style="margin: 0 0 8px 0;">工具调用功能允许LLM自动调用预定义的工具来：</p>
                    <ul style="margin: 0; padding-left: 20px;">
                      <li>发送HTTP请求获取数据</li>
                      <li>读取文件内容</li>
                      <li>执行其他自定义操作</li>
                    </ul>
                    <p style="margin: 8px 0 0 0;">启用后，LLM会根据用户需求自动选择合适的工具。</p>
                  </div>
                </template>
              </el-alert>
            </el-form>
          </el-tab-pane>
          
          <!-- 配置助手 -->
          <el-tab-pane label="配置助手" name="assistant">
            <el-card shadow="never" style="margin-bottom: 15px;">
              <template #header>
                <div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
                  <span>🎯 快速配置</span>
                </div>
              </template>
              
              <!-- 配置模式选择 -->
              <el-radio-group v-model="configAssistantMode" style="width: 100%; margin-bottom: 15px;">
                <el-radio-button label="simple">简单模式</el-radio-button>
                <el-radio-button label="template">模板模式</el-radio-button>
                <el-radio-button label="wizard">向导模式</el-radio-button>
              </el-radio-group>
              
              <!-- 简单模式 -->
              <div v-if="configAssistantMode === 'simple'" class="config-wizard">
                <el-alert
                  type="info"
                  :closable="false"
                  show-icon
                  style="margin-bottom: 15px;"
                >
                  <template #title>
                    <div style="font-size: 12px;">
                      选择常用场景，快速配置节点
                    </div>
                  </template>
                </el-alert>
                
                <div class="scenario-list">
                  <div
                    v-for="scenario in getScenarios(selectedNode?.type || '')"
                    :key="scenario.id"
                    class="scenario-item"
                    @click="selectScenario(scenario)"
                  >
                    <div class="scenario-icon">
                      <el-icon :size="24"><component :is="getScenarioIcon(scenario.icon)" /></el-icon>
                    </div>
                    <div class="scenario-info">
                      <div class="scenario-name">{{ scenario.name }}</div>
                      <div class="scenario-desc">{{ scenario.description }}</div>
                    </div>
                    <el-button size="small" type="primary" text>应用</el-button>
                  </div>
                </div>
              </div>
              
              <!-- 模板模式 -->
              <div v-else-if="configAssistantMode === 'template'" class="template-selector">
                <div style="display: flex; gap: 10px; margin-bottom: 15px;">
                  <el-input
                    v-model="configTemplateSearchKeyword"
                    placeholder="搜索模板..."
                    size="small"
                    clearable
                    style="flex: 1;"
                  >
                    <template #prefix>
                      <el-icon><Search /></el-icon>
                    </template>
                  </el-input>
                  <el-select
                    v-model="templateCategoryFilter"
                    placeholder="分类"
                    size="small"
                    style="width: 120px;"
                    clearable
                  >
                    <el-option label="全部" value="" />
                    <el-option label="常用" value="common" />
                    <el-option label="AI" value="ai" />
                    <el-option label="数据处理" value="data" />
                    <el-option label="网络" value="network" />
                  </el-select>
                </div>
                
                <el-scrollbar height="400px">
                  <div class="template-list">
                    <div
                      v-for="template in filteredConfigTemplates"
                      :key="template.id"
                      class="template-item"
                      :class="{ 'is-favorite': template.isFavorite }"
                    >
                      <div class="template-header">
                        <div class="template-title">
                          <span class="template-name">{{ template.name }}</span>
                          <el-tag size="small" type="info" style="margin-left: 8px;">{{ template.category }}</el-tag>
                        </div>
                        <div class="template-actions">
                          <el-button
                            size="small"
                            text
                            @click="toggleTemplateFavorite(template.id)"
                          >
                            <el-icon>
                              <Star v-if="template.isFavorite" style="color: #f7ba2a;" />
                              <Star v-else />
                            </el-icon>
                          </el-button>
                        </div>
                      </div>
                      <div class="template-desc">{{ template.description }}</div>
                      <div class="template-preview">
                        <pre>{{ getTemplatePreview(template) }}</pre>
                      </div>
                      <div class="template-footer">
                        <el-button size="small" type="primary" @click="applyConfigTemplate(template)">
                          应用模板
                        </el-button>
                        <el-button size="small" @click="viewTemplateDetail(template)">
                          查看详情
                        </el-button>
                        <el-button size="small" @click="exportTemplate(template)">
                          导出
                        </el-button>
                      </div>
                    </div>
                  </div>
                </el-scrollbar>
              </div>
              
              <!-- 向导模式 -->
              <div v-else-if="configAssistantMode === 'wizard'" class="wizard-mode">
                <el-steps :active="wizardStep" simple style="margin-bottom: 20px;">
                  <el-step title="选择场景" />
                  <el-step title="配置参数" />
                  <el-step title="完成" />
                </el-steps>
                
                <!-- 场景选择 -->
                <div v-if="wizardStep === 0" class="wizard-content">
                  <div class="scenario-list">
                    <div
                      v-for="scenario in getScenarios(selectedNode?.type || '')"
                      :key="scenario.id"
                      class="scenario-item"
                      @click="selectWizardScenario(scenario)"
                    >
                      <div class="scenario-icon">
                        <el-icon :size="24"><component :is="getScenarioIcon(scenario.icon)" /></el-icon>
                      </div>
                      <div class="scenario-info">
                        <div class="scenario-name">{{ scenario.name }}</div>
                        <div class="scenario-desc">{{ scenario.description }}</div>
                      </div>
                    </div>
                  </div>
                </div>
                
                <!-- 参数配置 -->
                <div v-if="wizardStep === 1" class="wizard-content">
                  <el-form :model="wizardConfig" label-width="120px">
                    <el-form-item
                      v-for="field in selectedWizardScenario?.fields || []"
                      :key="field.name"
                      :label="field.label"
                    >
                      <el-input
                        v-if="field.type === 'text'"
                        v-model="wizardConfig[field.name]"
                        :placeholder="field.placeholder"
                      />
                      <el-input
                        v-else-if="field.type === 'textarea'"
                        v-model="wizardConfig[field.name]"
                        type="textarea"
                        :rows="3"
                        :placeholder="field.placeholder"
                      />
                      <el-select
                        v-else-if="field.type === 'select'"
                        v-model="wizardConfig[field.name]"
                        :placeholder="field.placeholder"
                      >
                        <el-option
                          v-for="opt in field.options"
                          :key="opt.value"
                          :label="opt.label"
                          :value="opt.value"
                        />
                      </el-select>
                      <el-input-number
                        v-else-if="field.type === 'number'"
                        v-model="wizardConfig[field.name]"
                        :min="field.min"
                        :max="field.max"
                        :step="field.step"
                      />
                    </el-form-item>
                  </el-form>
                  
                  <div style="margin-top: 20px; text-align: right;">
                    <el-button @click="wizardStep = 0">上一步</el-button>
                    <el-button type="primary" @click="applyWizardConfig">完成</el-button>
                  </div>
                </div>
                
                <!-- 完成 -->
                <div v-if="wizardStep === 2" class="wizard-content">
                  <el-result
                    icon="success"
                    title="配置完成"
                    sub-title="节点配置已应用，您可以在基础标签页中查看和调整"
                  >
                    <template #extra>
                      <el-button type="primary" @click="wizardStep = 0">重新配置</el-button>
                    </template>
                  </el-result>
                </div>
              </div>
            </el-card>
            
            <!-- 模板管理工具栏 -->
            <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #e4e7ed;">
              <el-button-group>
                <el-button size="small" @click="templateImportVisible = true">
                  <el-icon><Upload /></el-icon>
                  导入模板
                </el-button>
                <el-button size="small" @click="saveCurrentAsTemplate">
                  <el-icon><DocumentAdd /></el-icon>
                  保存为模板
                </el-button>
              </el-button-group>
            </div>
            
            <!-- 模板详情对话框 -->
            <el-dialog v-model="templateDetailVisible" title="模板详情" width="60%">
              <div v-if="selectedTemplate">
                <h3>{{ selectedTemplate.name }}</h3>
                <p style="color: #909399; margin-bottom: 15px;">{{ selectedTemplate.description }}</p>
                <el-divider />
                <h4>配置预览：</h4>
                <el-scrollbar height="300px">
                  <pre style="margin: 0; padding: 12px; background: #f5f7fa; border-radius: 4px;">{{ formatJSON(selectedTemplate.config) }}</pre>
                </el-scrollbar>
                <div style="margin-top: 15px; text-align: right;">
                  <el-button @click="templateDetailVisible = false">关闭</el-button>
                  <el-button @click="exportTemplate(selectedTemplate)">导出模板</el-button>
                  <el-button type="primary" @click="applyConfigTemplate(selectedTemplate)">应用模板</el-button>
                </div>
              </div>
            </el-dialog>
            
            <!-- 模板导入对话框 -->
            <el-dialog v-model="templateImportVisible" title="导入模板" width="50%">
              <el-upload
                class="template-upload"
                drag
                :auto-upload="false"
                :on-change="handleTemplateImport"
                :file-list="[]"
                accept=".json"
              >
                <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
                <div class="el-upload__text">
                  将模板文件拖到此处，或<em>点击上传</em>
                </div>
                <template #tip>
                  <div class="el-upload__tip">
                    只能上传JSON格式的模板文件
                  </div>
                </template>
              </el-upload>
              
              <el-divider>或直接粘贴JSON</el-divider>
              
              <el-input
                v-model="templateImportJson"
                type="textarea"
                :rows="10"
                placeholder="粘贴模板JSON内容..."
              />
              
              <div style="margin-top: 15px; text-align: right;">
                <el-button @click="templateImportVisible = false">取消</el-button>
                <el-button type="primary" @click="importTemplateFromJson">导入</el-button>
              </div>
            </el-dialog>
          </el-tab-pane>
          
          <!-- 节点测试 -->
          <el-tab-pane label="测试" name="test">
            <div class="node-test-section">
          <el-form label-width="100px">
            <!-- 测试用例管理 -->
            <el-form-item label="测试用例">
              <div class="test-case-bar">
                <el-select
                  v-model="selectedTestCaseId"
                  placeholder="选择用例"
                  size="small"
                  style="width: 180px;"
                >
                  <el-option
                    v-for="c in currentNodeTestCases"
                    :key="c.id"
                    :label="c.name"
                    :value="c.id"
                  />
                </el-select>
                <el-button-group style="margin-left: 8px;">
                  <el-button size="small" @click="saveCurrentTestCase">
                    <el-icon><DocumentCopy /></el-icon>
                    保存
                  </el-button>
                  <el-button size="small" @click="applySelectedTestCase" :disabled="!selectedTestCaseId">
                    <el-icon><Download /></el-icon>
                    应用
                  </el-button>
                  <el-button size="small" type="danger" @click="deleteSelectedTestCase" :disabled="!selectedTestCaseId">
                    <el-icon><Delete /></el-icon>
                    删除
                  </el-button>
                </el-button-group>
              </div>
            </el-form-item>

            <!-- 测试输入区域 -->
            <el-form-item label="测试输入">
              <div class="test-input-toolbar">
                <el-button-group size="small">
                  <el-button @click="fillFromUpstream" :disabled="!hasUpstreamNodes">
                    <el-icon><Download /></el-icon>
                    从上游填充
                  </el-button>
                  <el-button @click="formatTestInput">
                    <el-icon><Sort /></el-icon>
                    格式化
                  </el-button>
                  <el-button @click="clearTestInput">
                    <el-icon><Delete /></el-icon>
                    清空
                  </el-button>
                </el-button-group>
                <el-select
                  v-model="testInputTemplate"
                  size="small"
                  placeholder="快速模板"
                  style="width: 150px; margin-left: 8px;"
                  @change="applyTestInputTemplate"
                >
                  <el-option label="空对象" value="empty" />
                  <el-option label="基础输入" value="basic" />
                  <el-option label="完整示例" value="full" />
                  <el-option v-if="selectedNode?.type === 'llm'" label="LLM输入" value="llm" />
                  <el-option v-if="selectedNode?.type === 'http'" label="HTTP输入" value="http" />
                  <el-option v-if="selectedNode?.type === 'json'" label="JSON输入" value="json" />
                </el-select>
              </div>
              <el-input
                v-model="nodeTestInput"
                type="textarea"
                :rows="6"
                placeholder='请输入JSON格式的测试输入数据，例如：{"input": "测试输入", "query": "测试查询"}'
                :class="{ 'input-error': testInputError }"
              />
              <div class="test-input-hint">
                <el-icon><InfoFilled /></el-icon>
                <span>输入数据将作为节点的输入参数</span>
                <span v-if="testInputError" class="error-text">{{ testInputError }}</span>
              </div>
            </el-form-item>
            
            <!-- 测试操作按钮 -->
            <el-form-item>
              <el-button 
                type="success" 
                @click="handleTestNode" 
                :loading="testingNode"
                :disabled="!selectedNode || testInputError"
                style="width: 100%"
              >
                <el-icon><VideoPlay /></el-icon>
                {{ testingNode ? '测试中...' : '运行测试' }}
              </el-button>
            </el-form-item>
            
            <!-- 测试结果区域 -->
            <el-form-item v-if="nodeTestResult" label="测试输出">
              <div class="test-result-header">
                <div class="test-status-group">
                  <span class="test-status" :class="nodeTestResult.status">
                    <el-icon v-if="nodeTestResult.status === 'success'"><CircleCheck /></el-icon>
                    <el-icon v-else><CircleClose /></el-icon>
                    {{ nodeTestResult.status === 'success' ? '测试成功' : '测试失败' }}
                  </span>
                  <span class="test-time" v-if="nodeTestResult.execution_time">
                    <el-icon><Timer /></el-icon>
                    {{ nodeTestResult.execution_time }}ms
                  </span>
                </div>
                <div class="test-result-actions" v-if="nodeTestResult.status === 'success'">
                  <el-button size="small" text @click="copyTestResult">
                    <el-icon><DocumentCopy /></el-icon>
                    复制
                  </el-button>
                  <el-button size="small" text @click="downloadTestResult">
                    <el-icon><Download /></el-icon>
                    导出
                  </el-button>
                </div>
              </div>
              
              <!-- JSON格式化展示 -->
              <div class="test-result-content">
                <el-scrollbar height="300px">
                  <pre class="json-viewer" v-if="nodeTestResult.status === 'success'">{{ formattedTestOutput }}</pre>
                  <div v-else class="test-error-detail">
                    <el-alert
                      type="error"
                      :title="nodeTestResult.error_message || '测试失败'"
                      :closable="false"
                      show-icon
                    >
                      <template #default>
                        <div v-if="nodeTestResult.error_trace" class="error-trace">
                          <details>
                            <summary>错误详情</summary>
                            <pre>{{ nodeTestResult.error_trace }}</pre>
                          </details>
                        </div>
                      </template>
                    </el-alert>
                  </div>
                </el-scrollbar>
              </div>
              
              <!-- 测试统计信息 -->
              <div v-if="nodeTestResult.status === 'success' && nodeTestResult.output" class="test-stats">
                <el-tag size="small" type="info">
                  输出类型: {{ getOutputType(nodeTestResult.output) }}
                </el-tag>
                <el-tag size="small" type="info" v-if="typeof nodeTestResult.output === 'object'">
                  字段数: {{ Object.keys(nodeTestResult.output).length }}
                </el-tag>
                <el-tag size="small" type="info" v-if="typeof nodeTestResult.output === 'string'">
                  长度: {{ nodeTestResult.output.length }} 字符
                </el-tag>
              </div>
            </el-form-item>
          </el-form>
            </div>
          </el-tab-pane>
        </el-tabs>
      </div>
    </div>
    
    <!-- 运行对话框 -->
    <el-dialog
      v-model="runDialogVisible"
      title="运行工作流"
      width="600px"
      :close-on-click-modal="false"
    >
      <el-form :model="runForm" label-width="100px">
        <el-form-item label="执行参数">
          <el-alert
            type="info"
            :closable="false"
            show-icon
            style="margin-bottom: 10px;"
          >
            <template #title>
              <div style="font-size: 12px;">
                输入JSON格式的执行参数，这些参数将作为工作流的初始输入数据。<br/>
                例如: {"input": "你好", "value": 10, "status": "active"}
              </div>
            </template>
          </el-alert>
          <el-input
            v-model="runForm.inputData"
            type="textarea"
            :rows="8"
            placeholder='{"input": "你好", "value": 10}'
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="runDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="handleConfirmRun" :loading="running">
          开始运行
        </el-button>
      </template>
    </el-dialog>
    
    <!-- 工作流模板应用对话框 -->
    <el-dialog
      v-model="templateDialogVisible"
      title="应用工作流模板"
      width="800px"
      :close-on-click-modal="false"
    >
      <div v-loading="loadingWorkflowTemplates">
        <el-input
          v-model="templateSearchKeyword"
          placeholder="搜索模板..."
          clearable
          style="margin-bottom: 15px;"
        >
          <template #prefix>
            <el-icon><Search /></el-icon>
          </template>
        </el-input>
        
        <div class="template-list">
          <div
            v-for="template in filteredTemplates"
            :key="template.id"
            class="template-item"
            @click="handleSelectTemplate(template)"
          >
            <div class="template-header">
              <h4>{{ template.name }}</h4>
              <el-tag v-if="template.is_featured" type="warning" size="small">精选</el-tag>
            </div>
            <div class="template-description">
              {{ template.description || '无描述' }}
            </div>
            <div class="template-meta">
              <span>节点数: {{ getTemplateNodeCount(template) }}</span>
              <span>使用次数: {{ template.use_count || 0 }}</span>
              <span v-if="template.rating_avg">评分: {{ template.rating_avg.toFixed(1) }} ⭐</span>
            </div>
          </div>
          
          <div v-if="filteredTemplates.length === 0" class="empty-templates">
            <el-empty description="暂无可用模板" />
          </div>
        </div>
      </div>
      
      <template #footer>
        <el-button @click="templateDialogVisible = false">取消</el-button>
      </template>
    </el-dialog>
    
    <!-- 节点执行详情面板 -->
    <NodeExecutionDetail
      v-model:visible="nodeDetailVisible"
      :node-id="selectedNode?.id"
      :node-label="selectedNode?.data?.label || selectedNode?.id"
      :node-type="selectedNode?.type"
      :execution-id="currentExecutionId"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, markRaw, watch, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { Controls } from '@vue-flow/controls'
import { MiniMap } from '@vue-flow/minimap'
import type { Node, Edge, NodeClickEvent, EdgeClickEvent, Connection, Viewport } from '@vue-flow/core'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Check, Warning, ZoomIn, ZoomOut, FullScreen, DocumentCopy, User, VideoPlay, InfoFilled, WarningFilled, Rank, ArrowDown, Sort, Grid, Operation, Document, Search, Timer, Box, Edit, Picture, Download, Delete, CircleCheck, CircleClose, Aim, ArrowLeft, ArrowRight, Refresh, Loading, Star, ChatDotRound, Connection as ConnectionIcon, Setting, DataAnalysis, Link, Upload, UploadFilled, DocumentAdd, QuestionFilled } from '@element-plus/icons-vue'
import { useWorkflowStore } from '@/stores/workflow'
import api from '@/api'
import type { WorkflowNode, WorkflowEdge } from '@/types'
import { StartNode, LLMNode, ConditionNode, EndNode, DefaultNode } from './NodeTypes'
import { useCollaboration } from '@/composables/useCollaboration'
import NodeExecutionDetail from './NodeExecutionDetail.vue'

const props = defineProps<{
  workflowId?: string
  agentId?: string
  initialNodes?: any[]
  initialEdges?: any[]
  executionStatus?: any  // 执行状态，包含当前执行的节点信息
}>()

const emit = defineEmits<{
  save: [workflow: { nodes: WorkflowNode[]; edges: WorkflowEdge[] }]
  'node-test': [data: { node: any; result: any; input: any }]
}>()

const router = useRouter()
const workflowStore = useWorkflowStore()
const canvasRef = ref<HTMLElement>()
const selectedNode = ref<Node | null>(null)
const selectedEdge = ref<Edge | null>(null)
const draggedNodeType = ref<any>(null)
const testingNode = ref(false)
const nodeDetailVisible = ref(false)
const currentExecutionId = ref<string | null>(null)
const snapEnabled = ref(true)
const showSwimlanes = ref(false)
const laneCount = ref(3)

// 节点复制相关
const copiedNode = ref<Node | null>(null)

// 节点测试相关
const nodeTestInput = ref('{}')
const nodeTestOutput = ref('')
const nodeTestResult = ref<any>(null)
const testInputTemplate = ref<string>('')
const testInputError = ref<string>('')
const selectedTestCaseId = ref<string>('')
const nodeTestCases = ref<Record<string, { id: string; name: string; data: string }[]>>({})
const testCaseStorageKey = computed(() => `workflow_node_test_cases_${props.workflowId || 'default'}`)
const currentNodeTestCases = computed(() => {
  const id = selectedNode.value?.id
  if (!id) return []
  return nodeTestCases.value[id] || []
})

// 格式化测试输出
const formattedTestOutput = computed(() => {
  if (!nodeTestResult.value || nodeTestResult.value.status !== 'success') return ''
  try {
    const output = nodeTestResult.value.output
    if (typeof output === 'string') {
      // 尝试解析为JSON
      try {
        const parsed = JSON.parse(output)
        return JSON.stringify(parsed, null, 2)
      } catch {
        return output
      }
    } else if (typeof output === 'object' && output !== null) {
      return JSON.stringify(output, null, 2)
    } else {
      return String(output)
    }
  } catch {
    return String(nodeTestResult.value.output)
  }
})

// 检查是否有上游节点
const hasUpstreamNodes = computed(() => {
  if (!selectedNode.value) return false
  return edges.value.some(e => e.target === selectedNode.value?.id)
})

// 节点模板相关
const nodeTemplates = ref<any[]>([])
const loadingTemplates = ref(false)

// 加载节点模板列表
const loadNodeTemplates = async () => {
  loadingTemplates.value = true
  try {
    const response = await api.get('/api/v1/node-templates', {
      params: {
        limit: 100  // 加载前100个模板
      }
    })
    nodeTemplates.value = response.data
  } catch (error: any) {
    console.error('加载节点模板失败:', error)
    // 不显示错误消息，因为这是后台加载
  } finally {
    loadingTemplates.value = false
  }
}

// 处理模板选择
const handleTemplateChange = async (templateId: string) => {
  if (!templateId || !selectedNode.value) return
  
  try {
    // 获取模板详情
    const response = await api.get(`/api/v1/node-templates/${templateId}`)
    const template = response.data
    
    // 应用模板配置到节点
    if (selectedNode.value.data) {
      selectedNode.value.data.prompt = template.prompt
      selectedNode.value.data.provider = template.provider
      selectedNode.value.data.model = template.model
      selectedNode.value.data.temperature = parseFloat(template.temperature) || 0.7
      selectedNode.value.data.max_tokens = template.max_tokens || 1500
      selectedNode.value.data.template_id = templateId
      
      // 如果有变量定义，可以在这里处理
      if (template.variables && template.variables.length > 0) {
        // 可以保存变量定义供后续使用
        selectedNode.value.data.template_variables = template.variables
      }
    }
    
    // 增加模板使用次数
    try {
      await api.post(`/api/v1/node-templates/${templateId}/use`)
    } catch (e) {
      // 忽略使用次数更新失败
    }
    
    ElMessage.success(`已应用模板: ${template.name}`)
  } catch (error: any) {
    ElMessage.error(error.response?.data?.detail || '加载模板失败')
  }
}

// 管理模板
const handleManageTemplates = () => {
  router.push({ name: 'node-templates' })
}

// 工作流模板相关
const templateDialogVisible = ref(false)
const workflowTemplates = ref<any[]>([])
const templateSearchKeyword = ref('')
const loadingWorkflowTemplates = ref(false)

// 加载工作流模板列表
const loadWorkflowTemplates = async () => {
  loadingWorkflowTemplates.value = true
  try {
    // 优先从模板市场获取
    try {
      const response = await api.get('/api/v1/template-market', {
        params: {
          limit: 50,
          sort_by: 'use_count',
          sort_order: 'desc'
        }
      })
      workflowTemplates.value = response.data || []
    } catch (e) {
      // 如果模板市场失败，尝试从工作流模板API获取
      const response = await api.get('/api/v1/workflows/templates')
      workflowTemplates.value = response.data || []
    }
  } catch (error: any) {
    console.error('加载工作流模板失败:', error)
    ElMessage.error('加载模板列表失败')
    workflowTemplates.value = []
  } finally {
    loadingWorkflowTemplates.value = false
  }
}

// 过滤模板
const filteredTemplates = computed(() => {
  if (!templateSearchKeyword.value) {
    return workflowTemplates.value
  }
  const keyword = templateSearchKeyword.value.toLowerCase()
  return workflowTemplates.value.filter(template => 
    template.name?.toLowerCase().includes(keyword) ||
    template.description?.toLowerCase().includes(keyword) ||
    template.category?.toLowerCase().includes(keyword)
  )
})

// 获取模板节点数
const getTemplateNodeCount = (template: any) => {
  if (template.nodes && Array.isArray(template.nodes)) {
    return template.nodes.length
  }
  if (template.workflow_config?.nodes) {
    return template.workflow_config.nodes.length
  }
  return 0
}

// 打开模板对话框
const handleApplyTemplate = async () => {
  templateDialogVisible.value = true
  if (workflowTemplates.value.length === 0) {
    await loadWorkflowTemplates()
  }
}

// 选择并应用模板
const handleSelectTemplate = async (template: any) => {
  try {
    // 获取模板详情
    let templateData: any
    try {
      const response = await api.get(`/api/v1/template-market/${template.id}`)
      templateData = response.data
    } catch (e) {
      // 如果模板市场API失败，尝试从工作流模板API获取
      const response = await api.get(`/api/v1/workflows/templates/${template.id}`)
      templateData = response.data
    }
    
    if (!templateData) {
      ElMessage.error('获取模板详情失败')
      return
    }
    
    // 获取模板的节点和边
    let templateNodes = templateData.nodes || templateData.workflow_config?.nodes || []
    let templateEdges = templateData.edges || templateData.workflow_config?.edges || []
    
    if (!templateNodes || templateNodes.length === 0) {
      ElMessage.warning('模板中没有节点')
      return
    }
    
    // 生成节点ID映射（避免ID冲突）
    const nodeIdMapping: Record<string, string> = {}
    const timestamp = Date.now()
    
    templateNodes.forEach((node: any, index: number) => {
      const oldId = node.id
      const newId = `${node.type || 'node'}_${timestamp}_${index}`
      nodeIdMapping[oldId] = newId
      node.id = newId
    })
    
    // 更新边的源节点和目标节点ID
    templateEdges.forEach((edge: any) => {
      if (edge.source && nodeIdMapping[edge.source]) {
        edge.source = nodeIdMapping[edge.source]
      }
      if (edge.target && nodeIdMapping[edge.target]) {
        edge.target = nodeIdMapping[edge.target]
      }
      // 生成新的边ID
      edge.id = `edge_${edge.source}_${edge.target}_${timestamp}`
    })
    
    // 计算偏移量，将模板节点添加到画布右侧
    const existingNodes = nodes.value
    let offsetX = 100
    let offsetY = 100
    
    if (existingNodes.length > 0) {
      // 找到现有节点的最大X坐标
      const maxX = Math.max(...existingNodes.map(n => n.position.x))
      offsetX = maxX + 400  // 在现有节点右侧400px处开始
      
      // 找到现有节点的最小Y坐标
      const minY = Math.min(...existingNodes.map(n => n.position.y))
      offsetY = minY
    }
    
    // 调整节点位置
    templateNodes.forEach((node: any) => {
      node.position = {
        x: (node.position?.x || 0) + offsetX,
        y: (node.position?.y || 0) + offsetY
      }
    })
    
    // 转换为Vue Flow节点格式
    const vueFlowNodes = templateNodes.map((node: any) => ({
      id: node.id,
      type: node.type || 'default',
      position: node.position || { x: 0, y: 0 },
      data: node.data || { label: node.label || node.type }
    }))
    
    // 转换为Vue Flow边格式
    const vueFlowEdges = templateEdges.map((edge: any) => ({
      id: edge.id,
      source: edge.source,
      target: edge.target,
      sourceHandle: edge.sourceHandle || 'right',
      targetHandle: edge.targetHandle || 'left',
      type: 'bezier',
      animated: true,
      selectable: true,
      deletable: true,
      focusable: true,
      style: {
        stroke: '#409eff',
        strokeWidth: 2.5,
        strokeDasharray: '0'
      },
      markerEnd: {
        type: 'arrowclosed',
        color: '#409eff',
        width: 20,
        height: 20
      }
    }))
    
    // 添加到画布
    addNodes(vueFlowNodes)
    await nextTick()
    addEdges(vueFlowEdges)
    
    // 关闭对话框
    templateDialogVisible.value = false
    ElMessage.success(`已应用模板: ${template.name} (${templateNodes.length}个节点)`)
    
    // 标记有变更
    hasChanges.value = true
    
    // 可选：自动布局新添加的节点
    await nextTick()
    setTimeout(() => {
      handleAutoLayout()
    }, 300)
    
  } catch (error: any) {
    console.error('应用模板失败:', error)
    ElMessage.error(error.response?.data?.detail || '应用模板失败')
  }
}

// 执行状态相关的节点高亮
const executedNodeIds = ref<Set<string>>(new Set())
const runningNodeId = ref<string | null>(null)
const failedNodeIds = ref<Set<string>>(new Set())
const testingAnimation = ref(false)
const testAnimationTimer = ref<number | null>(null)

// 运行对话框
const runDialogVisible = ref(false)
const running = ref(false)
const runForm = ref({
  inputData: '{}'
})

// 使用useVueFlow获取节点和边的响应式引用
const vueFlowInstance = useVueFlow()
const { 
  nodes, 
  edges, 
  addNodes, 
  addEdges, 
  removeNodes, 
  removeEdges, 
  updateNode,
  updateEdge,
  screenToFlowCoordinate,
  zoomIn: vueFlowZoomIn,
  zoomOut: vueFlowZoomOut,
  setViewport,
  getViewport,
  fitView
} = vueFlowInstance

// 保存状态
const saving = ref(false)
const hasChanges = ref(false)
const lastSavedData = ref<string>('')
const savingInterval = ref<number | null>(null)

// 撤销/重做状态
type WorkflowSnapshot = {
  nodes: Node[]
  edges: Edge[]
  selectedNodeId: string | null
  selectedEdgeId: string | null
  viewport: Viewport | null
}
const history = ref<WorkflowSnapshot[]>([])
const redoStack = ref<WorkflowSnapshot[]>([])
const isRestoringHistory = ref(false)
const historyInitialized = ref(false)
const HISTORY_LIMIT = 50

const transientNodeDataKeys = ['executionStatus', 'executionClass', 'errorMessage', 'errorType']
const sanitizeNodeData = (data: Record<string, any> = {}) => {
  const cloned = JSON.parse(JSON.stringify(data || {}))
  transientNodeDataKeys.forEach(key => {
    if (key in cloned) {
      delete cloned[key]
    }
  })
  return cloned
}

const snapshotEquals = (a: WorkflowSnapshot, b: WorkflowSnapshot) => {
  const coreA = JSON.stringify({ nodes: a.nodes, edges: a.edges })
  const coreB = JSON.stringify({ nodes: b.nodes, edges: b.edges })
  return coreA === coreB && a.selectedNodeId === b.selectedNodeId && a.selectedEdgeId === b.selectedEdgeId
}

const createSnapshot = (): WorkflowSnapshot => {
  const viewport = typeof getViewport === 'function' ? getViewport() : null
  const nodesSnapshot = nodes.value.map(node => {
    const cloned = JSON.parse(JSON.stringify(node))
    cloned.data = sanitizeNodeData(node.data)
    return cloned
  })
  const edgesSnapshot = edges.value.map(edge => JSON.parse(JSON.stringify(edge)))
  return {
    nodes: nodesSnapshot,
    edges: edgesSnapshot,
    selectedNodeId: selectedNode.value?.id || null,
    selectedEdgeId: selectedEdge.value?.id || null,
    viewport: viewport ? { ...viewport } : null
  }
}

const resetHistory = () => {
  history.value = [createSnapshot()]
  redoStack.value = []
  historyInitialized.value = true
}

const pushHistory = (reason = 'update', { markDirty = true, force = false } = {}) => {
  if (isRestoringHistory.value) return
  if (!historyInitialized.value && !force) return
  const snapshot = createSnapshot()
  const last = history.value[history.value.length - 1]
  if (last && snapshotEquals(last, snapshot)) {
    return
  }
  history.value.push(snapshot)
  if (history.value.length > HISTORY_LIMIT) {
    history.value.shift()
  }
  if (markDirty) {
    hasChanges.value = true
  }
  redoStack.value = []
  console.debug('[WorkflowEditor] history push:', reason, 'size:', history.value.length)
}

const restoreSnapshot = async (snapshot: WorkflowSnapshot) => {
  isRestoringHistory.value = true
  nodes.value = snapshot.nodes.map(n => ({ ...n, data: sanitizeNodeData(n.data) }))
  edges.value = snapshot.edges.map(e => ({ ...e }))
  selectedNode.value = snapshot.selectedNodeId ? nodes.value.find(n => n.id === snapshot.selectedNodeId) || null : null
  selectedEdge.value = snapshot.selectedEdgeId ? edges.value.find(e => e.id === snapshot.selectedEdgeId) || null : null
  if (snapshot.viewport && typeof setViewport === 'function') {
    setViewport({ ...snapshot.viewport }, { duration: 0 })
  }
  await nextTick()
  checkChanges()
  isRestoringHistory.value = false
}

const undo = async () => {
  if (!historyInitialized.value || history.value.length <= 1) {
    ElMessage.info('没有可撤销的操作')
    return
  }
  const current = history.value.pop()
  const previous = history.value[history.value.length - 1]
  if (!current || !previous) {
    return
  }
  redoStack.value.push(current)
  await restoreSnapshot(previous)
}

const redo = async () => {
  if (!historyInitialized.value || redoStack.value.length === 0) {
    ElMessage.info('没有可重做的操作')
    return
  }
  const snapshot = redoStack.value.pop()
  if (!snapshot) return
  history.value.push(snapshot)
  if (history.value.length > HISTORY_LIMIT) {
    history.value.shift()
  }
  await restoreSnapshot(snapshot)
}

// 缩放状态
const currentZoom = ref(1)

// 协作功能
const collaborationEnabled = ref(false)
let collaboration: ReturnType<typeof useCollaboration> | null = null
if (props.workflowId) {
  collaboration = useCollaboration(props.workflowId)
  collaborationEnabled.value = true
}

const collaborationConnected = ref(false)
const onlineUsers = ref<any[]>([])
const currentUser = ref<any>(null)
const isProcessingRemoteOperation = ref(false) // 防止远程操作触发本地操作

// 自定义节点类型映射（使用markRaw避免响应式转换）
const customNodeTypes = {
  start: markRaw(StartNode),
  input: markRaw(DefaultNode),  // 输入节点使用默认节点（有输入和输出）
  llm: markRaw(LLMNode),
  template: markRaw(LLMNode),  // template也使用LLM节点
  condition: markRaw(ConditionNode),
  switch: markRaw(DefaultNode),  // Switch节点使用默认节点（支持多个输出handle）
  merge: markRaw(DefaultNode),  // Merge节点使用默认节点
  wait: markRaw(DefaultNode),  // Wait节点使用默认节点
  transform: markRaw(DefaultNode),  // 转换节点使用默认节点
  json: markRaw(DefaultNode),  // JSON处理节点使用默认节点
  text: markRaw(DefaultNode),  // 文本处理节点使用默认节点
  cache: markRaw(DefaultNode),  // 缓存节点使用默认节点
  vector_db: markRaw(DefaultNode),  // 向量数据库节点使用默认节点
  log: markRaw(DefaultNode),  // 日志节点使用默认节点
  error_handler: markRaw(DefaultNode),  // 错误处理节点使用默认节点
  csv: markRaw(DefaultNode),  // CSV处理节点使用默认节点
  object_storage: markRaw(DefaultNode),  // 对象存储节点使用默认节点
  slack: markRaw(DefaultNode),  // Slack节点使用默认节点
  dingtalk: markRaw(DefaultNode),  // 钉钉节点使用默认节点
  dingding: markRaw(DefaultNode),  // 钉钉节点别名
  wechat_work: markRaw(DefaultNode),  // 企业微信节点使用默认节点
  wecom: markRaw(DefaultNode),  // 企业微信节点别名
  sms: markRaw(DefaultNode),  // 短信节点使用默认节点
  pdf: markRaw(DefaultNode),  // PDF处理节点使用默认节点
  image: markRaw(DefaultNode),  // 图像处理节点使用默认节点
  excel: markRaw(DefaultNode),  // Excel处理节点使用默认节点
  subworkflow: markRaw(DefaultNode),  // 子工作流节点使用默认节点
  code: markRaw(DefaultNode),  // 代码执行节点使用默认节点
  oauth: markRaw(DefaultNode),  // OAuth节点使用默认节点
  validator: markRaw(DefaultNode),  // 数据验证节点使用默认节点
  batch: markRaw(DefaultNode),  // 批处理节点使用默认节点
  loop: markRaw(DefaultNode),  // 循环节点使用默认节点
  foreach: markRaw(DefaultNode),  // foreach也使用默认节点
  agent: markRaw(DefaultNode),  // Agent节点使用默认节点
  schedule: markRaw(DefaultNode),  // 定时任务节点使用默认节点
  delay: markRaw(DefaultNode),  // 延迟节点使用默认节点
  timer: markRaw(DefaultNode),  // 定时器节点使用默认节点
  webhook: markRaw(DefaultNode),  // Webhook节点使用默认节点
  email: markRaw(DefaultNode),  // 邮件节点使用默认节点
  mail: markRaw(DefaultNode),  // 邮件节点别名
  message_queue: markRaw(DefaultNode),  // 消息队列节点使用默认节点
  mq: markRaw(DefaultNode),  // 消息队列节点别名
  rabbitmq: markRaw(DefaultNode),  // RabbitMQ节点别名
  kafka: markRaw(DefaultNode),  // Kafka节点别名
  output: markRaw(DefaultNode),  // 输出节点使用默认节点
  end: markRaw(EndNode),
  default: markRaw(DefaultNode)
}

// 节点类型定义
const nodeTypes = [
  { type: 'start', label: '开始', icon: 'Play', category: '基础' },
  { type: 'input', label: '输入', icon: 'Upload', category: '基础' },
  { type: 'llm', label: 'LLM', icon: 'ChatDotRound', category: 'AI' },
  { type: 'template', label: '模板', icon: 'Document', category: 'AI' },
  { type: 'condition', label: '条件', icon: 'Switch', category: '逻辑' },
  { type: 'switch', label: 'Switch', icon: 'Operation', category: '逻辑' },
  { type: 'merge', label: 'Merge', icon: 'Connection', category: '逻辑' },
  { type: 'wait', label: '等待', icon: 'Timer', category: '逻辑' },
  { type: 'transform', label: '转换', icon: 'Refresh', category: '数据' },
  { type: 'json', label: 'JSON处理', icon: 'Document', category: '数据' },
  { type: 'text', label: '文本处理', icon: 'Edit', category: '数据' },
  { type: 'cache', label: '缓存', icon: 'Box', category: '数据' },
  { type: 'vector_db', label: '向量数据库', icon: 'Connection', category: 'AI' },
  { type: 'log', label: '日志', icon: 'Document', category: '系统' },
  { type: 'error_handler', label: '错误处理', icon: 'Warning', category: '逻辑' },
  { type: 'csv', label: 'CSV处理', icon: 'Document', category: '数据' },
  { type: 'object_storage', label: '对象存储', icon: 'Box', category: '数据' },
  { type: 'pdf', label: 'PDF处理', icon: 'Document', category: '数据' },
  { type: 'image', label: '图像处理', icon: 'Picture', category: '数据' },
  { type: 'excel', label: 'Excel处理', icon: 'Document', category: '数据' },
  { type: 'subworkflow', label: '子工作流', icon: 'Operation', category: '逻辑' },
  { type: 'code', label: '代码执行', icon: 'Edit', category: '逻辑' },
  { type: 'oauth', label: 'OAuth', icon: 'Document', category: '网络' },
  { type: 'validator', label: '数据验证', icon: 'Document', category: '数据' },
  { type: 'batch', label: '批处理', icon: 'Grid', category: '逻辑' },
  { type: 'loop', label: '循环', icon: 'RefreshRight', category: '逻辑' },
  { type: 'agent', label: 'Agent', icon: 'User', category: 'AI' },
  { type: 'http', label: 'HTTP请求', icon: 'Connection', category: '网络' },
  { type: 'database', label: '数据库', icon: 'Connection', category: '数据' },
  { type: 'file', label: '文件操作', icon: 'Document', category: '数据' },
  { type: 'schedule', label: '定时任务', icon: 'Clock', category: '系统' },
  { type: 'webhook', label: 'Webhook', icon: 'Link', category: '网络' },
  { type: 'email', label: '邮件', icon: 'Message', category: '通信' },
  { type: 'slack', label: 'Slack', icon: 'Message', category: '通信' },
  { type: 'dingtalk', label: '钉钉', icon: 'Message', category: '通信' },
  { type: 'wechat_work', label: '企业微信', icon: 'Message', category: '通信' },
  { type: 'sms', label: '短信', icon: 'Message', category: '通信' },
  { type: 'message_queue', label: '消息队列', icon: 'Connection', category: '通信' },
  { type: 'output', label: '输出', icon: 'Download', category: '基础' },
  { type: 'end', label: '结束', icon: 'CircleCheck', category: '基础' }
]

// 节点搜索和筛选相关
const nodeSearchKeyword = ref('')
const selectedCategory = ref('')
const canvasNodeSearchKeyword = ref('')

// 节点分类
const nodeCategories = computed(() => {
  const categories = new Set(nodeTypes.map(nt => nt.category))
  return Array.from(categories).map(cat => ({
    value: cat,
    label: cat
  }))
})

// 折叠控制
const toolboxActiveNames = ref<string[]>([])
watch(nodeCategories, (cats) => {
  toolboxActiveNames.value = cats.map(c => c.value)
}, { immediate: true })

// 分组节点（含搜索与可选分类过滤）
const groupedNodeTypes = computed(() => {
  const keyword = nodeSearchKeyword.value?.toLowerCase() || ''
  const categoryFilter = selectedCategory.value || ''

  return nodeCategories.value
    .filter(cat => !categoryFilter || cat.value === categoryFilter)
    .map(cat => {
      let items = nodeTypes.filter(nt => nt.category === cat.value)
      if (keyword) {
        items = items.filter(nt =>
          nt.label.toLowerCase().includes(keyword) ||
          nt.type.toLowerCase().includes(keyword) ||
          (nt.category && nt.category.toLowerCase().includes(keyword))
        )
      }
      return { category: cat.value, label: cat.label, items }
    })
})

// 过滤画布节点
const filteredCanvasNodes = computed(() => {
  if (!canvasNodeSearchKeyword.value) {
    return nodes.value
  }
  
  const keyword = canvasNodeSearchKeyword.value.toLowerCase()
  return nodes.value.filter(node => 
    (node.data?.label || '').toLowerCase().includes(keyword) ||
    node.id.toLowerCase().includes(keyword) ||
    node.type.toLowerCase().includes(keyword)
  )
})

// 图标映射对象
const iconMap: Record<string, any> = {
  Check, Warning, ZoomIn, ZoomOut, FullScreen, DocumentCopy, User, VideoPlay,
  InfoFilled, WarningFilled, Rank, ArrowDown, Sort, Grid, Operation, Document,
  Search, Timer, Box, Edit, Picture, Download, Delete, CircleCheck, CircleClose,
  Aim, ArrowLeft, ArrowRight, Refresh, Loading, Star, ChatDotRound,
  Connection: ConnectionIcon, // 使用别名避免与 Vue Flow 的 Connection 类型冲突
  Setting, DataAnalysis, Link, Upload, UploadFilled, DocumentAdd
}

// 获取节点图标
const getNodeIcon = (nodeType: string) => {
  const nodeTypeDef = nodeTypes.find(nt => nt.type === nodeType)
  const iconName = nodeTypeDef?.icon || 'Document'
  return iconMap[iconName] || Document
}

// 获取场景图标
const getScenarioIcon = (iconName: string) => {
  // 将 'Connection' 映射到 ConnectionIcon
  if (iconName === 'Connection') {
    return ConnectionIcon
  }
  return iconMap[iconName] || Document
}

// 折叠控制
const handleExpandAllGroups = () => {
  toolboxActiveNames.value = nodeCategories.value.map(c => c.value)
}
const handleCollapseAllGroups = () => {
  toolboxActiveNames.value = []
}

// 配置标签页
const configActiveTab = ref<string>('basic')

// 工具相关
const availableTools = ref<Array<{name: string, description: string, category?: string, function_schema?: any}>>([])
const toolSchemas = ref<Record<string, any>>({})
const toolGroups = computed(() => {
  const builtin: Array<{name: string, description: string, function_schema?: any}> = []
  const custom: Array<{name: string, description: string, category?: string, function_schema?: any}> = []
  
  availableTools.value.forEach(tool => {
    if (tool.category === 'builtin') {
      builtin.push(tool)
    } else {
      custom.push(tool)
    }
  })
  
  const groups: Array<{label: string, tools: Array<any>}> = []
  if (builtin.length > 0) {
    groups.push({ label: '内置工具', tools: builtin })
  }
  if (custom.length > 0) {
    groups.push({ label: '自定义工具', tools: custom })
  }
  return groups
})

// 加载工具列表
const loadTools = async () => {
  try {
    // 加载公开工具（不需要认证）
    let publicTools: any[] = []
    try {
      const response = await api.get('/api/v1/tools')
      publicTools = response.data || []
    } catch (error: any) {
      // 如果获取公开工具失败，只记录警告，不影响内置工具加载
      console.warn('加载公开工具失败:', error)
    }
    
    // 加载内置工具（不需要认证）
    let builtinTools: any[] = []
    try {
      const builtinResponse = await api.get('/api/v1/tools/builtin')
      builtinTools = builtinResponse.data || []
    } catch (error: any) {
      console.error('加载内置工具失败:', error)
      ElMessage.error('加载内置工具失败: ' + (error.response?.data?.detail || error.message || '请检查网络连接'))
      return
    }
    
    // 合并工具列表
    availableTools.value = [
      ...builtinTools.map((schema: any) => ({
        name: schema.function?.name || schema.name,
        description: schema.function?.description || schema.description || '',
        category: 'builtin',
        function_schema: schema
      })),
      ...publicTools.map((tool: any) => ({
        name: tool.name,
        description: tool.description,
        category: tool.category,
        function_schema: tool.function_schema
      }))
    ]
    
    // 构建工具schema映射
    availableTools.value.forEach(tool => {
      if (tool.function_schema) {
        toolSchemas.value[tool.name] = tool.function_schema
      }
    })
    
    console.log(`工具列表加载成功: ${availableTools.value.length} 个工具`)
  } catch (error: any) {
    console.error('加载工具列表失败:', error)
    ElMessage.error('加载工具列表失败: ' + (error.response?.data?.detail || error.message || '未知错误'))
  }
}

// 获取工具schema
const getToolSchema = (toolName: string) => {
  return toolSchemas.value[toolName]
}

// 处理工具开关切换
const handleToolsToggle = (enabled: boolean) => {
  if (enabled && (!selectedNode.value.data.tools || selectedNode.value.data.tools.length === 0)) {
    // 如果启用但没有选择工具，初始化空数组
    if (!selectedNode.value.data.tools) {
      selectedNode.value.data.tools = []
    }
    // 加载工具列表（如果还没加载）
    if (availableTools.value.length === 0) {
      loadTools()
    }
  }
}

// 处理工具选择变化
const handleToolsChange = (tools: string[]) => {
  // 可以在这里添加额外的逻辑
  console.log('工具选择变化:', tools)
}

// 移除工具
const removeTool = (toolName: string) => {
  if (selectedNode.value.data.tools) {
    const index = selectedNode.value.data.tools.indexOf(toolName)
    if (index > -1) {
      selectedNode.value.data.tools.splice(index, 1)
    }
  }
}

// 可用变量与字符串字段（用于插入占位符）
const availableVariables = computed(() => {
  // 从上游节点输出动态推断可用变量
  if (!selectedNode.value) return ['input', 'text', 'route', 'USER_INPUT']
  
  const vars = new Set<string>(['input', 'text', 'route', 'USER_INPUT', 'query', 'message', 'content'])
  
  // 查找上游节点
  const upstreamEdges = edges.value.filter(e => e.target === selectedNode.value?.id)
  upstreamEdges.forEach(edge => {
    const upstreamNode = nodes.value.find(n => n.id === edge.source)
    if (upstreamNode) {
      // 根据上游节点类型推断可能的输出字段
      const nodeType = upstreamNode.type
      if (nodeType === 'llm' || nodeType === 'template') {
        vars.add('output')
        vars.add('response')
      } else if (nodeType === 'http') {
        vars.add('response')
        vars.add('data')
        vars.add('body')
      } else if (nodeType === 'json') {
        vars.add('data')
        vars.add('result')
      } else if (nodeType === 'transform') {
        vars.add('result')
        // 从mapping中提取目标字段
        const mapping = upstreamNode.data?.mapping || {}
        Object.keys(mapping).forEach(key => vars.add(key))
      } else if (nodeType === 'code') {
        vars.add('result')
        vars.add('output')
      } else if (nodeType === 'vector_db') {
        vars.add('results')
        vars.add('similarity')
      } else if (nodeType === 'cache') {
        vars.add('value')
        vars.add('cached')
      }
      
      // 从上游节点的data中提取可能的输出字段
      const upstreamData = upstreamNode.data || {}
      Object.keys(upstreamData).forEach(key => {
        if (typeof upstreamData[key] === 'string' && key !== 'label') {
          vars.add(key)
        }
      })
    }
  })
  
  return Array.from(vars).sort()
})

const laneOverlayStyle = computed(() => {
  if (!showSwimlanes.value) return {}
  const count = Math.max(1, laneCount.value || 1)
  return {
    backgroundImage: 'linear-gradient(90deg, rgba(64,158,255,0.06) 0, rgba(64,158,255,0.06) 50%, transparent 50%, transparent 100%)',
    backgroundSize: `${100 / count}% 100%`,
  }
})

const variableInsertField = ref<string>('')
const variableToInsert = ref<string>('')
const availableStringFields = computed(() => {
  const data = selectedNode.value?.data || {}
  return Object.keys(data).filter(k => typeof (data as any)[k] === 'string')
})

// 判断节点是否有超时配置
const hasTimeoutConfig = computed(() => {
  const nodeType = selectedNode.value?.type
  return ['http', 'webhook', 'database', 'file', 'object_storage', 'slack', 'dingtalk', 'wechat_work', 'sms'].includes(nodeType || '')
})

// 判断节点是否有重试配置
const hasRetryConfig = computed(() => {
  const nodeType = selectedNode.value?.type
  return ['http', 'webhook', 'database', 'file', 'object_storage', 'llm', 'template'].includes(nodeType || '')
})

const insertVariable = (field: string, variable: string) => {
  if (!selectedNode.value || !field || !variable) return
  const val = selectedNode.value.data?.[field]
  const newVal = (val || '') + `{${variable}}`
  selectedNode.value.data[field] = newVal
}

// 从数据流面板插入变量
const insertVariableFromDataflow = (variable: string) => {
  // 找到第一个可编辑的字符串字段
  const stringFields = availableStringFields.value
  if (stringFields.length > 0) {
    insertVariable(stringFields[0], variable)
  }
}

// 处理提示词输入
const handlePromptInput = () => {
  if (!selectedNode.value || !promptTextareaRef.value) return
  
  const textarea = promptTextareaRef.value.$el?.querySelector('textarea')
  if (!textarea) return
  
  const value = selectedNode.value.data.prompt || ''
  const cursorPos = textarea.selectionStart
  
  // 检查是否输入了 {{
  const beforeCursor = value.substring(0, cursorPos)
  const lastOpen = beforeCursor.lastIndexOf('{{')
  const lastClose = beforeCursor.lastIndexOf('}}')
  
  // 如果找到了 {{ 且没有对应的 }}
  if (lastOpen !== -1 && (lastClose === -1 || lastClose < lastOpen)) {
    // 提取搜索文本（{{ 之后的内容）
    const searchText = beforeCursor.substring(lastOpen + 2)
    
    // 检查是否已经输入了 }}，如果输入了就不显示自动补全
    if (!searchText.includes('}}')) {
      autocompleteSearchText.value = searchText
      autocompleteTriggerPosition.value = lastOpen
      showVariableAutocomplete.value = true
      autocompleteSelectedIndex.value = 0
      
      // 计算下拉框位置
      nextTick(() => {
        updateAutocompletePosition(textarea, cursorPos)
      })
    } else {
      showVariableAutocomplete.value = false
    }
  } else {
    showVariableAutocomplete.value = false
  }
}

// 更新自动补全下拉框位置
const updateAutocompletePosition = (textarea: HTMLTextAreaElement, cursorPos: number) => {
  if (!textarea || !autocompleteDropdownRef.value) return
  
  // 计算光标在文本中的位置（行和列）
  const text = textarea.value.substring(0, cursorPos)
  const lines = text.split('\n')
  const lineIndex = lines.length - 1
  const colIndex = lines[lines.length - 1].length
  
  // 获取 textarea 的位置和样式
  const rect = textarea.getBoundingClientRect()
  const lineHeight = parseInt(getComputedStyle(textarea).lineHeight) || 20
  const paddingTop = parseInt(getComputedStyle(textarea).paddingTop) || 0
  const paddingLeft = parseInt(getComputedStyle(textarea).paddingLeft) || 0
  
  // 计算光标位置
  const top = rect.top + paddingTop + (lineIndex * lineHeight) + lineHeight + 5
  const left = rect.left + paddingLeft + (colIndex * 8) // 假设每个字符宽度约8px
  
  autocompletePosition.value = {
    top: `${top}px`,
    left: `${left}px`
  }
}

// 处理提示词键盘事件
const handlePromptKeydown = (event: KeyboardEvent) => {
  if (!showVariableAutocomplete.value) return
  
  if (event.key === 'ArrowDown') {
    event.preventDefault()
    autocompleteSelectedIndex.value = Math.min(
      autocompleteSelectedIndex.value + 1,
      filteredAutocompleteVariables.value.length - 1
    )
    scrollToSelectedItem()
  } else if (event.key === 'ArrowUp') {
    event.preventDefault()
    autocompleteSelectedIndex.value = Math.max(autocompleteSelectedIndex.value - 1, 0)
    scrollToSelectedItem()
  } else if (event.key === 'Enter' || event.key === 'Tab') {
    event.preventDefault()
    if (filteredAutocompleteVariables.value.length > 0) {
      selectAutocompleteVariable(
        filteredAutocompleteVariables.value[autocompleteSelectedIndex.value].name
      )
    }
  } else if (event.key === 'Escape') {
    event.preventDefault()
    showVariableAutocomplete.value = false
  }
}

// 滚动到选中的项
const scrollToSelectedItem = () => {
  nextTick(() => {
    if (!autocompleteDropdownRef.value) return
    const items = autocompleteDropdownRef.value.querySelectorAll('.autocomplete-item')
    const selectedItem = items[autocompleteSelectedIndex.value] as HTMLElement
    if (selectedItem) {
      selectedItem.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
    }
  })
}

// 选择自动补全变量
const selectAutocompleteVariable = (variableName: string) => {
  if (!selectedNode.value || !promptTextareaRef.value) return
  
  const textarea = promptTextareaRef.value.$el?.querySelector('textarea')
  if (!textarea) return
  
  const value = selectedNode.value.data.prompt || ''
  const cursorPos = textarea.selectionStart
  
  // 替换 {{ 到光标位置的内容
  const beforeCursor = value.substring(0, cursorPos)
  const afterCursor = value.substring(cursorPos)
  const lastOpen = beforeCursor.lastIndexOf('{{')
  
  if (lastOpen !== -1) {
    const newValue = 
      value.substring(0, lastOpen) + 
      `{{${variableName}}}` + 
      afterCursor
    
    selectedNode.value.data.prompt = newValue
    
    // 设置光标位置到 }} 之后
    nextTick(() => {
      const newCursorPos = lastOpen + `{{${variableName}}}`.length
      textarea.setSelectionRange(newCursorPos, newCursorPos)
      textarea.focus()
    })
  }
  
  showVariableAutocomplete.value = false
}

// 处理提示词失去焦点
const handlePromptBlur = (event: FocusEvent) => {
  // 延迟隐藏，以便点击下拉框时不会立即关闭
  setTimeout(() => {
    if (!autocompleteDropdownRef.value?.contains(event.relatedTarget as Node)) {
      showVariableAutocomplete.value = false
    }
  }, 200)
}

// 获取变量类型标签
const getVarTypeTag = (type: string) => {
  const typeMap: Record<string, string> = {
    'string': 'info',
    'number': 'warning',
    'boolean': 'success',
    'object': 'warning',
    'array': 'success',
    'any': 'info'
  }
  return typeMap[type] || 'info'
}

// 变量面板展开状态
const variablePanelActive = ref<string[]>([])

// 变量自动补全相关
const promptTextareaRef = ref<any>(null)
const autocompleteDropdownRef = ref<HTMLElement | null>(null)
const showVariableAutocomplete = ref(false)
const autocompletePosition = ref({ top: '0px', left: '0px' })
const autocompleteSelectedIndex = ref(0)
const autocompleteSearchText = ref('')
const autocompleteTriggerPosition = ref(0) // 触发位置（光标位置）

// 所有可用变量（用于自动补全）
const allAutocompleteVariables = computed(() => {
  const vars: Array<{ name: string; type: string; description: string; group: string }> = []
  
  // 基础变量
  basicVariables.value.forEach(name => {
    vars.push({
      name,
      type: 'string',
      description: '基础变量',
      group: '基础变量'
    })
  })
  
  // 上游变量
  upstreamVariablesList.value.forEach(item => {
    vars.push({
      ...item,
      group: '上游节点变量'
    })
  })
  
  // 记忆变量
  memoryVariablesList.value.forEach(item => {
    vars.push({
      ...item,
      group: '记忆变量'
    })
  })
  
  return vars
})

// 过滤后的自动补全变量
const filteredAutocompleteVariables = computed(() => {
  if (!autocompleteSearchText.value) {
    return allAutocompleteVariables.value.slice(0, 10) // 限制显示数量
  }
  
  const search = autocompleteSearchText.value.toLowerCase()
  return allAutocompleteVariables.value.filter(v => 
    v.name.toLowerCase().includes(search)
  ).slice(0, 10)
})

// 基础变量
const basicVariables = computed(() => {
  return ['input', 'text', 'route', 'USER_INPUT', 'query', 'message', 'content']
})

// 上游变量列表（带类型和描述）
const upstreamVariablesList = computed(() => {
  if (!selectedNode.value) return []
  const vars: Array<{ name: string; type: string; description: string }> = []
  
  const upstreamEdgesList = edges.value.filter(e => e.target === selectedNode.value?.id)
  upstreamEdgesList.forEach(edge => {
    const upstreamNode = nodes.value.find(n => n.id === edge.source)
    if (upstreamNode) {
      const nodeVars = getUpstreamVariables(edge.source)
      vars.push(...nodeVars)
    }
  })
  
  // 去重
  const uniqueVars = new Map()
  vars.forEach(v => {
    if (!uniqueVars.has(v.name)) {
      uniqueVars.set(v.name, v)
    }
  })
  
  return Array.from(uniqueVars.values())
})

// 记忆变量列表
const memoryVariablesList = computed(() => {
  if (!selectedNode.value) return []
  const vars: Array<{ name: string; type: string; description: string }> = []
  
  // 检查上游是否有 cache 节点
  const upstreamEdgesList = edges.value.filter(e => e.target === selectedNode.value?.id)
  upstreamEdgesList.forEach(edge => {
    const upstreamNode = nodes.value.find(n => n.id === edge.source)
    if (upstreamNode?.type === 'cache') {
      vars.push(
        { name: 'memory', type: 'object', description: '完整的记忆数据对象' },
        { name: 'memory.conversation_history', type: 'array', description: '对话历史记录' },
        { name: 'memory.user_profile', type: 'object', description: '用户画像信息' },
        { name: 'memory.context', type: 'object', description: '上下文信息' }
      )
    }
  })
  
  return vars
})

// 上游边
const upstreamEdges = computed(() => {
  if (!selectedNode.value) return []
  return edges.value.filter(e => e.target === selectedNode.value?.id)
})

// 下游节点
const downstreamNodes = computed(() => {
  if (!selectedNode.value) return []
  const downstreamEdges = edges.value.filter(e => e.source === selectedNode.value?.id)
  return downstreamEdges.map(edge => {
    return nodes.value.find(n => n.id === edge.target)
  }).filter(Boolean) as WorkflowNode[]
})

// 获取节点标签
const getNodeLabel = (nodeId: string) => {
  const node = nodes.value.find(n => n.id === nodeId)
  return node?.data?.label || nodeId
}

// 获取节点类型
const getNodeType = (nodeId: string) => {
  const node = nodes.value.find(n => n.id === nodeId)
  return node?.type || 'unknown'
}

// 获取节点类型颜色
const getNodeTypeColor = (nodeId: string) => {
  const nodeType = getNodeType(nodeId)
  const colorMap: Record<string, string> = {
    'llm': 'primary',
    'cache': 'success',
    'transform': 'warning',
    'http': 'danger',
    'switch': 'info',
    'merge': 'info',
    'start': 'success',
    'end': 'danger'
  }
  return colorMap[nodeType] || 'info'
}

// 获取上游节点的变量
const getUpstreamVariables = (nodeId: string): Array<{ name: string; type: string; description: string }> => {
  const node = nodes.value.find(n => n.id === nodeId)
  if (!node) return []
  
  const variables: Array<{ name: string; type: string; description: string }> = []
  
  switch (node.type) {
    case 'llm':
    case 'template':
      variables.push(
        { name: 'output', type: 'string', description: 'LLM生成的回复内容' },
        { name: 'response', type: 'string', description: '完整的响应内容' }
      )
      break
    case 'http':
      variables.push(
        { name: 'response', type: 'object', description: 'HTTP响应对象' },
        { name: 'data', type: 'any', description: '响应数据' },
        { name: 'body', type: 'string', description: '响应体内容' },
        { name: 'status', type: 'number', description: 'HTTP状态码' }
      )
      break
    case 'json':
      variables.push(
        { name: 'data', type: 'any', description: '解析后的JSON数据' },
        { name: 'result', type: 'any', description: '提取的结果' }
      )
      break
    case 'transform':
      variables.push({ name: 'result', type: 'any', description: '转换后的结果' })
      // 从mapping中提取目标字段
      const mapping = node.data?.mapping || {}
      Object.keys(mapping).forEach(key => {
        variables.push({ name: key, type: 'any', description: `映射字段: ${key}` })
      })
      break
    case 'code':
      variables.push(
        { name: 'result', type: 'any', description: '代码执行结果' },
        { name: 'output', type: 'any', description: '代码输出' }
      )
      break
    case 'vector_db':
      variables.push(
        { name: 'results', type: 'array', description: '搜索结果列表' },
        { name: 'similarity', type: 'number', description: '相似度分数' }
      )
      break
    case 'cache':
      variables.push(
        { name: 'value', type: 'any', description: '缓存的值' },
        { name: 'cached', type: 'boolean', description: '是否命中缓存' },
        { name: 'memory', type: 'object', description: '记忆数据对象' },
        { name: 'conversation_history', type: 'array', description: '对话历史' },
        { name: 'user_profile', type: 'object', description: '用户画像' }
      )
      break
  }
  
  return variables
}

// 获取节点输出字段
const getOutputFields = (nodeType: string): Array<{ name: string; type: string; description: string }> => {
  const outputFieldsMap: Record<string, Array<{ name: string; type: string; description: string }>> = {
    'llm': [
      { name: 'output', type: 'string', description: 'LLM生成的文本回复' },
      { name: 'response', type: 'object', description: '完整的LLM响应对象' }
    ],
    'cache': [
      { name: 'value', type: 'any', description: '缓存的值' },
      { name: 'cached', type: 'boolean', description: '是否命中缓存' }
    ],
    'transform': [
      { name: 'result', type: 'any', description: '转换后的数据' }
    ],
    'http': [
      { name: 'response', type: 'object', description: 'HTTP响应对象' },
      { name: 'data', type: 'any', description: '响应数据' },
      { name: 'status', type: 'number', description: 'HTTP状态码' }
    ],
    'switch': [
      { name: 'route', type: 'string', description: '路由到的分支名称' }
    ],
    'merge': [
      { name: 'result', type: 'object', description: '合并后的数据' }
    ]
  }
  
  return outputFieldsMap[nodeType] || []
}

// 记忆相关数据和方法
const memoryData = ref<any>(null)
const memoryLoading = ref(false)
const showConversationHistory = ref(false)
const showUserProfile = ref(false)

// 记忆键
const memoryKey = computed(() => {
  if (!selectedNode.value || selectedNode.value.type !== 'cache') return ''
  return selectedNode.value.data?.key || ''
})

// 记忆状态
const memoryStatus = computed(() => {
  if (!memoryData.value) return '不存在'
  return '存在'
})

// TTL 信息
const ttlInfo = computed(() => {
  if (!selectedNode.value || !selectedNode.value.data?.ttl) return '未设置'
  const ttl = selectedNode.value.data.ttl
  if (ttl >= 86400) {
    return `${Math.floor(ttl / 86400)} 天`
  } else if (ttl >= 3600) {
    return `${Math.floor(ttl / 3600)} 小时`
  } else if (ttl >= 60) {
    return `${Math.floor(ttl / 60)} 分钟`
  } else {
    return `${ttl} 秒`
  }
})

// 刷新记忆
const refreshMemory = async () => {
  if (!selectedNode.value || selectedNode.value.type !== 'cache') return
  
  const key = memoryKey.value
  if (!key) {
    ElMessage.warning('记忆键未设置')
    return
  }
  
  memoryLoading.value = true
  try {
    // 调用API获取记忆数据
    const response = await api.get(`/api/v1/execution-logs/cache/${encodeURIComponent(key)}`)
    if (response.data && response.data.value) {
      memoryData.value = response.data.value
      ElMessage.success('记忆数据已刷新')
    } else {
      memoryData.value = null
      ElMessage.info('记忆数据不存在')
    }
  } catch (error: any) {
    if (error.response?.status === 404) {
      memoryData.value = null
      ElMessage.info('记忆数据不存在')
    } else {
      ElMessage.error('获取记忆数据失败: ' + (error.response?.data?.detail || error.message || '未知错误'))
    }
  } finally {
    memoryLoading.value = false
  }
}

// 测试记忆查询
const testMemoryQuery = () => {
  ElMessage.info('测试查询功能开发中...')
}

// 清空记忆
const clearMemory = () => {
  ElMessageBox.confirm('确定要清空记忆吗？此操作不可恢复。', '确认清空', {
    type: 'warning'
  }).then(async () => {
    const key = memoryKey.value
    if (!key) {
      ElMessage.warning('记忆键未设置')
      return
    }
    
    try {
      // 这里可以调用API清空记忆，或者直接删除
      await deleteMemory()
      ElMessage.success('记忆已清空')
    } catch (error: any) {
      ElMessage.error('清空记忆失败: ' + (error.response?.data?.detail || error.message || '未知错误'))
    }
  }).catch(() => {})
}

// 删除记忆
const deleteMemory = async () => {
  const key = memoryKey.value
  if (!key) {
    ElMessage.warning('记忆键未设置')
    return
  }
  
  ElMessageBox.confirm('确定要删除记忆吗？此操作不可恢复。', '确认删除', {
    type: 'warning'
  }).then(async () => {
    try {
      await api.delete(`/api/v1/execution-logs/cache/${encodeURIComponent(key)}`)
      ElMessage.success('记忆已删除')
      memoryData.value = null
    } catch (error: any) {
      ElMessage.error('删除记忆失败: ' + (error.response?.data?.detail || error.message || '未知错误'))
    }
  }).catch(() => {})
}

// 执行数据预览相关
const selectedExecutionId = ref<string>('')
const executionData = ref<any>(null)
const recentExecutions = ref<any[]>([])

// 缓存记忆详情
const showCacheMemoryDetail = ref(false)
const cacheMemoryDetail = ref<any>(null)

// 配置助手相关
const configAssistantMode = ref<'simple' | 'template' | 'wizard'>('simple')
const wizardStep = ref(0)
const selectedWizardScenario = ref<any>(null)
const wizardConfig = ref<Record<string, any>>({})
const configTemplateSearchKeyword = ref('')
const templateCategoryFilter = ref('')
const templateDetailVisible = ref(false)
const selectedTemplate = ref<any>(null)

// 获取场景列表
const getScenarios = (nodeType: string) => {
  const scenarios: Array<{
    id: string
    name: string
    description: string
    icon: string
    fields?: Array<{
      name: string
      label: string
      type: string
      placeholder?: string
      options?: Array<{ label: string; value: any }>
      min?: number
      max?: number
      step?: number
    }>
    config?: Record<string, any>
  }> = []
  
  switch (nodeType) {
    case 'llm':
      scenarios.push(
        {
          id: 'llm_summary',
          name: '文本总结',
          description: '总结长文本内容',
          icon: 'Document',
          fields: [
            { name: 'prompt', label: '提示词', type: 'textarea', placeholder: '请总结以下内容：{{input}}' },
            { name: 'max_tokens', label: '最大Token数', type: 'number', min: 100, max: 4000, step: 100 }
          ],
          config: {
            provider: 'deepseek',
            model: 'deepseek-chat',
            prompt: '请总结以下内容，100字以内：{{input}}',
            temperature: 0.5,
            max_tokens: 500
          }
        },
        {
          id: 'llm_translate',
          name: '文本翻译',
          description: '翻译文本内容',
          icon: 'Connection',
          fields: [
            { name: 'target_lang', label: '目标语言', type: 'select', options: [
              { label: '英语', value: 'English' },
              { label: '中文', value: 'Chinese' },
              { label: '日语', value: 'Japanese' }
            ]}
          ],
          config: {
            provider: 'deepseek',
            model: 'deepseek-chat',
            prompt: '请把下列内容翻译成{{target_lang}}：{{input}}',
            temperature: 0.3,
            max_tokens: 1000
          }
        },
        {
          id: 'llm_extract',
          name: '信息提取',
          description: '从文本中提取结构化信息',
          icon: 'DataAnalysis',
          config: {
            provider: 'deepseek',
            model: 'deepseek-chat',
            prompt: '请从以下文本中提取关键信息，以JSON格式返回：{{input}}',
            temperature: 0.2,
            max_tokens: 1000
          }
        },
        {
          id: 'llm_classify',
          name: '文本分类',
          description: '对文本进行分类',
          icon: 'Sort',
          config: {
            provider: 'deepseek',
            model: 'deepseek-chat',
            prompt: '请对以下文本进行分类：{{input}}',
            temperature: 0.1,
            max_tokens: 200
          }
        }
      )
      break
    case 'http':
      scenarios.push(
        {
          id: 'http_api_call',
          name: 'API调用',
          description: '调用外部API',
          icon: 'Link',
          fields: [
            { name: 'url', label: 'API地址', type: 'text', placeholder: 'https://api.example.com/data' },
            { name: 'method', label: '请求方法', type: 'select', options: [
              { label: 'GET', value: 'GET' },
              { label: 'POST', value: 'POST' },
              { label: 'PUT', value: 'PUT' },
              { label: 'DELETE', value: 'DELETE' }
            ]}
          ],
          config: {
            method: 'GET',
            url: 'https://api.example.com/data',
            headers: '{"Content-Type": "application/json"}',
            timeout: 30
          }
        }
      )
      break
    case 'cache':
      scenarios.push(
        {
          id: 'cache_memory',
          name: '记忆存储',
          description: '存储用户记忆数据',
          icon: 'Box',
          fields: [
            { name: 'key', label: '记忆键', type: 'text', placeholder: 'user_memory_{user_id}' },
            { name: 'ttl', label: '过期时间(秒)', type: 'number', min: 60, max: 86400, step: 60 }
          ],
          config: {
            operation: 'set',
            key: 'user_memory_{user_id}',
            ttl: 3600,
            backend: 'redis'
          }
        },
        {
          id: 'cache_query',
          name: '记忆查询',
          description: '查询用户记忆数据',
          icon: 'Search',
          config: {
            operation: 'get',
            key: 'user_memory_{user_id}',
            backend: 'redis'
          }
        }
      )
      break
  }
  
  return scenarios
}

// 选择场景（简单模式）
const selectScenario = (scenario: any) => {
  if (scenario.config && selectedNode.value) {
    Object.assign(selectedNode.value.data, scenario.config)
    ElMessage.success(`已应用场景：${scenario.name}`)
  }
}

// 选择向导场景
const selectWizardScenario = (scenario: any) => {
  selectedWizardScenario.value = scenario
  wizardConfig.value = {}
  wizardStep.value = 1
}

// 应用向导配置
const applyWizardConfig = () => {
  if (!selectedNode.value || !selectedWizardScenario.value) return
  
  // 合并基础配置和向导配置
  const finalConfig = {
    ...selectedWizardScenario.value.config,
    ...wizardConfig.value
  }
  
  Object.assign(selectedNode.value.data, finalConfig)
  wizardStep.value = 2
  ElMessage.success('配置已应用')
}

// 配置模板列表
const configTemplates = ref<Array<{
  id: string
  name: string
  description: string
  category: string
  nodeType: string
  config: Record<string, any>
  isFavorite?: boolean
}>>([
  {
    id: 'llm_summary_template',
    name: 'LLM文本总结模板',
    description: '用于总结长文本的LLM配置模板',
    category: 'ai',
    nodeType: 'llm',
    config: {
      provider: 'deepseek',
      model: 'deepseek-chat',
      prompt: '请总结以下内容，100字以内：{{input}}',
      temperature: 0.5,
      max_tokens: 500
    }
  },
  {
    id: 'llm_translate_template',
    name: 'LLM翻译模板',
    description: '用于翻译文本的LLM配置模板',
    category: 'ai',
    nodeType: 'llm',
    config: {
      provider: 'deepseek',
      model: 'deepseek-chat',
      prompt: '请把下列内容翻译成英文：{{input}}',
      temperature: 0.3,
      max_tokens: 1000
    }
  },
  {
    id: 'http_get_template',
    name: 'HTTP GET请求模板',
    description: '用于GET请求的HTTP节点配置',
    category: 'network',
    nodeType: 'http',
    config: {
      method: 'GET',
      url: 'https://api.example.com/data',
      headers: '{}',
      timeout: 30
    }
  },
  {
    id: 'cache_memory_template',
    name: '缓存记忆模板',
    description: '用于存储用户记忆的缓存配置',
    category: 'data',
    nodeType: 'cache',
    config: {
      operation: 'set',
      key: 'user_memory_{user_id}',
      ttl: 3600,
      backend: 'redis'
    }
  }
])

// 过滤配置模板
const filteredConfigTemplates = computed(() => {
  let result = configTemplates.value.filter(t => 
    t.nodeType === selectedNode.value?.type
  )
  
  if (configTemplateSearchKeyword.value) {
    const keyword = configTemplateSearchKeyword.value.toLowerCase()
    result = result.filter(t => 
      t.name.toLowerCase().includes(keyword) ||
      t.description.toLowerCase().includes(keyword)
    )
  }
  
  if (templateCategoryFilter.value) {
    result = result.filter(t => t.category === templateCategoryFilter.value)
  }
  
  return result
})

// 获取模板预览
const getTemplatePreview = (template: any) => {
  return JSON.stringify(template.config, null, 2)
}

// 应用配置模板
const applyConfigTemplate = (template: any) => {
  if (!selectedNode.value) return
  
  Object.assign(selectedNode.value.data, template.config)
  templateDetailVisible.value = false
  ElMessage.success(`已应用模板：${template.name}`)
}

// 查看模板详情
const viewTemplateDetail = (template: any) => {
  selectedTemplate.value = template
  templateDetailVisible.value = true
}

// 切换模板收藏
const toggleTemplateFavorite = (templateId: string) => {
  const template = configTemplates.value.find(t => t.id === templateId)
  if (template) {
    template.isFavorite = !template.isFavorite
    // 保存到localStorage
    saveTemplatesToStorage()
    ElMessage.success(template.isFavorite ? '已收藏' : '已取消收藏')
  }
}

// 导出模板
const exportTemplate = (template: any) => {
  try {
    const dataStr = JSON.stringify(template, null, 2)
    const dataBlob = new Blob([dataStr], { type: 'application/json' })
    const url = URL.createObjectURL(dataBlob)
    const link = document.createElement('a')
    link.href = url
    link.download = `${template.name.replace(/\s+/g, '_')}.json`
    link.click()
    URL.revokeObjectURL(url)
    ElMessage.success('模板已导出')
  } catch (error) {
    ElMessage.error('导出失败')
  }
}

// 模板导入相关
const templateImportVisible = ref(false)
const templateImportJson = ref('')

// 处理模板导入
const handleTemplateImport = (file: any) => {
  const reader = new FileReader()
  reader.onload = (e) => {
    try {
      const template = JSON.parse(e.target?.result as string)
      importTemplate(template)
    } catch (error) {
      ElMessage.error('模板文件格式错误')
    }
  }
  reader.readAsText(file.raw)
}

// 从JSON导入模板
const importTemplateFromJson = () => {
  if (!templateImportJson.value.trim()) {
    ElMessage.warning('请输入模板JSON内容')
    return
  }
  
  try {
    const template = JSON.parse(templateImportJson.value)
    importTemplate(template)
    templateImportJson.value = ''
  } catch (error) {
    ElMessage.error('JSON格式错误')
  }
}

// 导入模板
const importTemplate = (template: any) => {
  // 验证模板格式
  if (!template.name || !template.config || !template.nodeType) {
    ElMessage.error('模板格式不完整')
    return
  }
  
  // 检查是否已存在
  const exists = configTemplates.value.find(t => t.id === template.id)
  if (exists) {
    ElMessageBox.confirm('模板已存在，是否覆盖？', '确认', {
      type: 'warning'
    }).then(() => {
      Object.assign(exists, template)
      saveTemplatesToStorage()
      ElMessage.success('模板已更新')
      templateImportVisible.value = false
    }).catch(() => {})
  } else {
    // 生成新ID
    template.id = template.id || `template_${Date.now()}`
    configTemplates.value.push(template)
    saveTemplatesToStorage()
    ElMessage.success('模板已导入')
    templateImportVisible.value = false
  }
}

// 保存当前配置为模板
const saveCurrentAsTemplate = async () => {
  if (!selectedNode.value) {
    ElMessage.warning('请先选择节点')
    return
  }
  
  try {
    const { value: name } = await ElMessageBox.prompt('请输入模板名称', '保存模板', {
      inputValue: `${selectedNode.value.type}_template_${Date.now()}`,
      inputValidator: (value) => {
        if (!value || value.trim() === '') {
          return '模板名称不能为空'
        }
        return true
      }
    })
    
    const { value: description } = await ElMessageBox.prompt('请输入模板描述', '保存模板', {
      inputValue: `${selectedNode.value.data?.label || selectedNode.value.type} 节点配置模板`
    })
    
    const newTemplate = {
      id: `template_${Date.now()}`,
      name: name.trim(),
      description: description.trim() || '',
      category: 'custom',
      nodeType: selectedNode.value.type,
      config: { ...selectedNode.value.data },
      isFavorite: false
    }
    
    configTemplates.value.push(newTemplate)
    saveTemplatesToStorage()
    ElMessage.success('模板已保存')
  } catch (error) {
    // 用户取消
  }
}

// 保存模板到localStorage
const saveTemplatesToStorage = () => {
  try {
    localStorage.setItem('node_config_templates', JSON.stringify(configTemplates.value))
  } catch (error) {
    console.error('保存模板失败:', error)
  }
}

// 从localStorage加载模板
const loadTemplatesFromStorage = () => {
  try {
    const stored = localStorage.getItem('node_config_templates')
    if (stored) {
      const storedTemplates = JSON.parse(stored)
      // 合并存储的模板和默认模板
      storedTemplates.forEach((stored: any) => {
        const exists = configTemplates.value.find(t => t.id === stored.id)
        if (!exists) {
          configTemplates.value.push(stored)
        } else {
          // 更新收藏状态
          exists.isFavorite = stored.isFavorite
        }
      })
    }
  } catch (error) {
    console.error('加载模板失败:', error)
  }
}


// 上游节点执行数据映射
const upstreamExecutionDataMap = ref<Record<string, {
  executionId?: string
  data?: any
  loading?: boolean
}>>({})

// 检查是否有上游节点的执行数据
const hasUpstreamExecutionData = (nodeId: string) => {
  return recentExecutions.value.length > 0
}

// 更新上游节点执行ID
const updateUpstreamExecutionId = (nodeId: string, executionId: string | undefined) => {
  if (!upstreamExecutionDataMap.value[nodeId]) {
    upstreamExecutionDataMap.value[nodeId] = {}
  }
  upstreamExecutionDataMap.value[nodeId].executionId = executionId
  if (executionId) {
    loadUpstreamNodeData(nodeId)
  } else {
    upstreamExecutionDataMap.value[nodeId].data = null
  }
}

// 加载上游节点数据
const loadUpstreamNodeData = async (nodeId: string) => {
  const executionId = upstreamExecutionDataMap.value[nodeId]?.executionId
  if (!executionId) {
    if (!upstreamExecutionDataMap.value[nodeId]) {
      upstreamExecutionDataMap.value[nodeId] = {}
    }
    upstreamExecutionDataMap.value[nodeId].data = null
    return
  }
  
  if (!upstreamExecutionDataMap.value[nodeId]) {
    upstreamExecutionDataMap.value[nodeId] = {}
  }
  
  upstreamExecutionDataMap.value[nodeId] = {
    ...upstreamExecutionDataMap.value[nodeId],
    loading: true
  }
  
  try {
    const response = await api.get(
      `/api/v1/execution-logs/executions/${executionId}/nodes/${nodeId}/data`
    )
    upstreamExecutionDataMap.value[nodeId] = {
      executionId,
      data: response.data,
      loading: false
    }
  } catch (error: any) {
    console.error('加载上游节点数据失败:', error)
    upstreamExecutionDataMap.value[nodeId] = {
      executionId,
      data: null,
      loading: false
    }
  }
}

// 加载最近的执行记录
const loadRecentExecutions = async () => {
  if (!selectedNode.value) return
  
  try {
    // 获取当前工作流或智能体的ID
    const workflowId = props.workflowId
    const agentId = props.agentId
    
    if (!workflowId && !agentId) return
    
    const response = await api.get('/api/v1/executions', {
      params: {
        workflow_id: workflowId || undefined,
        agent_id: agentId || undefined,
        limit: 10,
        skip: 0
      }
    })
    
    recentExecutions.value = response.data || []
  } catch (error: any) {
    console.error('加载执行记录失败:', error)
  }
}

// 加载节点执行数据
const loadNodeExecutionData = async () => {
  if (!selectedExecutionId.value || !selectedNode.value) {
    executionData.value = null
    return
  }
  
  try {
    const response = await api.get(
      `/api/v1/execution-logs/executions/${selectedExecutionId.value}/nodes/${selectedNode.value.id}/data`
    )
    executionData.value = response.data
  } catch (error: any) {
    console.error('加载节点执行数据失败:', error)
    ElMessage.warning('无法加载执行数据: ' + (error.response?.data?.detail || error.message || '未知错误'))
    executionData.value = null
  }
}

// 格式化执行时间
const formatExecutionTime = (time: string | Date) => {
  if (!time) return '未知'
  const date = typeof time === 'string' ? new Date(time) : time
  return date.toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
  })
}

// 格式化JSON
const formatJSON = (data: any) => {
  if (!data) return ''
  try {
    return JSON.stringify(data, null, 2)
  } catch {
    return String(data)
  }
}

// 复制到剪贴板
const copyToClipboard = async (data: any) => {
  if (!data) {
    ElMessage.warning('没有可复制的内容')
    return
  }
  
  try {
    const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2)
    await navigator.clipboard.writeText(text)
    ElMessage.success('已复制到剪贴板')
  } catch (error) {
    ElMessage.error('复制失败')
  }
}

// 监听节点选择变化，加载执行记录
watch(selectedNode, (newNode) => {
  if (newNode) {
    loadRecentExecutions()
  } else {
    recentExecutions.value = []
    selectedExecutionId.value = ''
    executionData.value = null
  }
}, { immediate: true })

// 快速模板
const templateSelection = ref<string>('')
const applyTemplate = () => {
  if (!selectedNode.value) return
  const t = selectedNode.value.type
  if (!templateSelection.value) return
  // HTTP模板
  if (t === 'http') {
    if (templateSelection.value === 'http_get') {
      selectedNode.value.data.method = 'GET'
      selectedNode.value.data.url = 'https://httpbin.org/get'
      selectedNode.value.data.headers = '{}'
      selectedNode.value.data.timeout = 10
    } else if (templateSelection.value === 'http_post_json') {
      selectedNode.value.data.method = 'POST'
      selectedNode.value.data.url = 'https://httpbin.org/post'
      selectedNode.value.data.headers = '{ "Content-Type": "application/json" }'
      selectedNode.value.data.body = '{ "text": "{text}" }'
      selectedNode.value.data.timeout = 10
    }
  }
  // LLM模板
  if (t === 'llm') {
    if (templateSelection.value === 'llm_summary') {
      selectedNode.value.data.provider = selectedNode.value.data.provider || 'deepseek'
      selectedNode.value.data.model = selectedNode.value.data.model || 'deepseek-chat'
      selectedNode.value.data.prompt = '请总结以下内容，100字以内：{text}'
      selectedNode.value.data.temperature = 0.5
    } else if (templateSelection.value === 'llm_translate') {
      selectedNode.value.data.prompt = '请把下列内容翻译成英文：{text}'
      selectedNode.value.data.temperature = 0.3
    }
  }
  // OAuth模板
  if (t === 'oauth') {
    if (templateSelection.value === 'oauth_google') {
      selectedNode.value.data.provider = 'google'
      selectedNode.value.data.scopes = ['openid', 'profile', 'email']
    } else if (templateSelection.value === 'oauth_github') {
      selectedNode.value.data.provider = 'github'
      selectedNode.value.data.scopes = ['read:user', 'user:email']
    }
  }
  // JSON处理模板
  if (t === 'json') {
    if (templateSelection.value === 'json_parse') {
      selectedNode.value.data.operation = 'parse'
      selectedNode.value.data.path = ''
    } else if (templateSelection.value === 'json_extract') {
      selectedNode.value.data.operation = 'extract'
      selectedNode.value.data.path = '$.data.items[*]'
    }
  }
  // 文本处理模板
  if (t === 'text') {
    if (templateSelection.value === 'text_split') {
      selectedNode.value.data.operation = 'split'
      selectedNode.value.data.delimiter = '\n'
    } else if (templateSelection.value === 'text_format') {
      selectedNode.value.data.operation = 'format'
      selectedNode.value.data.template = 'Hello {name}, your value is {value}'
    }
  }
  // 缓存模板
  if (t === 'cache') {
    if (templateSelection.value === 'cache_get') {
      selectedNode.value.data.operation = 'get'
      selectedNode.value.data.key = 'cache_{key}'
      selectedNode.value.data.ttl = 3600
    } else if (templateSelection.value === 'cache_set') {
      selectedNode.value.data.operation = 'set'
      selectedNode.value.data.key = 'cache_{key}'
      selectedNode.value.data.ttl = 3600
    }
  }
  // 向量数据库模板
  if (t === 'vector_db') {
    if (templateSelection.value === 'vector_search') {
      selectedNode.value.data.operation = 'search'
      selectedNode.value.data.collection = 'documents'
      selectedNode.value.data.top_k = 5
    } else if (templateSelection.value === 'vector_upsert') {
      selectedNode.value.data.operation = 'upsert'
      selectedNode.value.data.collection = 'documents'
    }
  }
  // HTTP扩展模板
  if (t === 'http') {
    if (templateSelection.value === 'http_put') {
      selectedNode.value.data.method = 'PUT'
      selectedNode.value.data.url = 'https://httpbin.org/put'
      selectedNode.value.data.headers = '{ "Content-Type": "application/json" }'
      selectedNode.value.data.body = '{ "id": "{id}", "data": "{data}" }'
      selectedNode.value.data.timeout = 10
    } else if (templateSelection.value === 'http_delete') {
      selectedNode.value.data.method = 'DELETE'
      selectedNode.value.data.url = 'https://httpbin.org/delete'
      selectedNode.value.data.headers = '{}'
      selectedNode.value.data.timeout = 10
    }
  }
  // LLM扩展模板
  if (t === 'llm') {
    if (templateSelection.value === 'llm_extract') {
      selectedNode.value.data.provider = selectedNode.value.data.provider || 'deepseek'
      selectedNode.value.data.model = selectedNode.value.data.model || 'deepseek-chat'
      selectedNode.value.data.prompt = '请从以下文本中提取关键信息（JSON格式）：{text}'
      selectedNode.value.data.temperature = 0.3
    } else if (templateSelection.value === 'llm_classify') {
      selectedNode.value.data.provider = selectedNode.value.data.provider || 'deepseek'
      selectedNode.value.data.model = selectedNode.value.data.model || 'deepseek-chat'
      selectedNode.value.data.prompt = '请将以下内容分类为：正面/中性/负面。内容：{text}'
      selectedNode.value.data.temperature = 0.2
    }
  }
  // 通信节点模板
  if (t === 'slack' && templateSelection.value === 'slack_send') {
    selectedNode.value.data.operation = 'send_message'
    selectedNode.value.data.channel = '#general'
    selectedNode.value.data.message = '通知：{message}'
  }
  if (t === 'dingtalk' && templateSelection.value === 'dingtalk_send') {
    selectedNode.value.data.operation = 'send_message'
    selectedNode.value.data.message = '通知：{message}'
  }
  if (t === 'wechat_work' && templateSelection.value === 'wechat_send') {
    selectedNode.value.data.operation = 'send_message'
    selectedNode.value.data.message = '通知：{message}'
  }
  
  templateSelection.value = ''
  ElMessage.success('模板已应用')
}

// 定位到节点
const handleFocusNode = (nodeId: string) => {
  const node = nodes.value.find(n => n.id === nodeId)
  if (!node) return
  
  // 选中节点
  selectedNode.value = node
  
  // 定位到节点（居中显示）
  const viewport = getViewport()
  if (viewport) {
    const nodeX = node.position.x
    const nodeY = node.position.y
    
    // 计算视口中心位置
    const canvasElement = document.querySelector('.editor-canvas')
    if (canvasElement) {
      const rect = canvasElement.getBoundingClientRect()
      const centerX = rect.width / 2
      const centerY = rect.height / 2
      
      // 设置视口，使节点居中
      setViewport({
        x: centerX - nodeX * viewport.zoom,
        y: centerY - nodeY * viewport.zoom,
        zoom: viewport.zoom
      }, { duration: 300 })
    }
  }
  
  ElMessage.success(`已定位到节点: ${node.data?.label || node.id}`)
}

// 定位所有节点（适应视图）
const handleFocusAllNodes = () => {
  if (nodes.value.length === 0) {
    ElMessage.warning('画布中没有节点')
    return
  }
  
  // 使用Vue Flow的fitView功能
  if (fitView) {
    fitView({ padding: 0.2, duration: 300 })
    ElMessage.success('已定位所有节点')
  } else {
    // 手动计算并设置视口
    const minX = Math.min(...nodes.value.map(n => n.position.x))
    const maxX = Math.max(...nodes.value.map(n => n.position.x))
    const minY = Math.min(...nodes.value.map(n => n.position.y))
    const maxY = Math.max(...nodes.value.map(n => n.position.y))
    
    const centerX = (minX + maxX) / 2
    const centerY = (minY + maxY) / 2
    const width = maxX - minX
    const height = maxY - minY
    
    const canvasElement = document.querySelector('.editor-canvas')
    if (canvasElement) {
      const rect = canvasElement.getBoundingClientRect()
      const canvasWidth = rect.width
      const canvasHeight = rect.height
      
      // 计算合适的缩放比例
      const scaleX = canvasWidth / (width + 200)
      const scaleY = canvasHeight / (height + 200)
      const zoom = Math.min(scaleX, scaleY, 1.2)
      
      setViewport({
        x: canvasWidth / 2 - centerX * zoom,
        y: canvasHeight / 2 - centerY * zoom,
        zoom
      }, { duration: 300 })
      
      ElMessage.success('已定位所有节点')
    }
  }
}


// 拖拽开始
const handleDragStart = (event: DragEvent, nodeType: any) => {
  draggedNodeType.value = nodeType
  if (event.dataTransfer) {
    event.dataTransfer.effectAllowed = 'copy'
    // 存储节点类型信息到dataTransfer
    event.dataTransfer.setData('text/plain', nodeType.type)
  }
  console.log('开始拖拽节点:', nodeType.type)
}

// 拖拽进入
const handleDragEnter = (event: DragEvent) => {
  event.preventDefault()
  if (event.dataTransfer) {
    event.dataTransfer.dropEffect = 'copy'
  }
}

// 拖拽悬停
const handleDragOver = (event: DragEvent) => {
  event.preventDefault()
  event.stopPropagation()
  if (event.dataTransfer) {
    event.dataTransfer.dropEffect = 'copy'
  }
}

// 拖拽放置
const handleDrop = (event: DragEvent) => {
  event.preventDefault()
  event.stopPropagation()
  
  console.log('Drop事件触发', { draggedNodeType: draggedNodeType.value, canvasRef: !!canvasRef.value })
  
  if (!draggedNodeType.value) {
    // 尝试从dataTransfer获取
    const nodeTypeStr = event.dataTransfer?.getData('text/plain')
    if (nodeTypeStr) {
      const foundType = nodeTypes.find(nt => nt.type === nodeTypeStr)
      if (foundType) {
        draggedNodeType.value = foundType
      }
    }
  }
  
  if (!draggedNodeType.value || !canvasRef.value) {
    console.warn('无法创建节点：缺少节点类型或画布引用')
    draggedNodeType.value = null
    return
  }
  
  // 获取画布相对于视口的位置
  const rect = canvasRef.value.getBoundingClientRect()
  const clientX = event.clientX
  const clientY = event.clientY
  
  // 计算相对于画布的坐标
  const relativeX = clientX - rect.left
  const relativeY = clientY - rect.top
  
  // 尝试使用Vue Flow的坐标转换
  let position = { x: relativeX, y: relativeY }
  try {
    // 获取Vue Flow实例
    const flowInstance = vueFlowInstance
    if (flowInstance && typeof flowInstance.screenToFlowCoordinate === 'function') {
      const flowPos = flowInstance.screenToFlowCoordinate({
        x: clientX,
        y: clientY
      })
      position = flowPos
      console.log('使用Vue Flow坐标转换:', position)
    } else {
      // 如果没有坐标转换函数，使用相对坐标
      // 减去一些偏移量以补偿Vue Flow的视口
      position = { x: relativeX - 100, y: relativeY - 100 }
      console.log('使用相对坐标:', position)
    }
  } catch (e) {
    console.error('坐标转换失败:', e)
    position = { x: relativeX - 100, y: relativeY - 100 }
  }
  
  // 创建新节点
  const nodeType = draggedNodeType.value.type || 'default'
  const isLLMNode = nodeType === 'llm' || nodeType === 'agent' || nodeType === 'template'
  const isTemplateNode = nodeType === 'template'
  const isScheduleNode = nodeType === 'schedule' || nodeType === 'delay' || nodeType === 'timer'
  const isWebhookNode = nodeType === 'webhook'
  const isEmailNode = nodeType === 'email' || nodeType === 'mail'
  const isMQNode = nodeType === 'message_queue' || nodeType === 'mq' || nodeType === 'rabbitmq' || nodeType === 'kafka'
  
  const newNode: Node = {
    id: `node_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
    type: nodeType,
    position: position,
    data: {
      label: draggedNodeType.value.label,
      prompt: '',
      model: 'gpt-3.5-turbo',
      condition: '',
      // LLM节点默认配置
      ...(isLLMNode && !isTemplateNode ? {
        provider: 'deepseek',
        model: 'deepseek-chat',
        prompt: '请处理用户请求。',
        temperature: 0.5,
        max_tokens: 1500,
        enable_tools: false,
        tools: []
      } : {}),
      // 模板节点默认配置
      ...(isTemplateNode ? {
        provider: 'deepseek',
        model: 'deepseek-chat',
        prompt: '',
        temperature: 0.7,
        max_tokens: 1500,
        template_id: null,
        enable_tools: false,
        tools: []
      } : {}),
      // 定时任务节点默认配置
      ...(isScheduleNode ? {
        delay_type: 'fixed',
        delay_value: 5,
        delay_unit: 'seconds'
      } : {}),
      // Webhook节点默认配置
      ...(isWebhookNode ? {
        method: 'POST',
        url: '',
        headers: '{}',
        body: '',
        timeout: 30
      } : {}),
      // 邮件节点默认配置
      ...(isEmailNode ? {
        smtp_host: 'smtp.example.com',
        smtp_port: 587,
        smtp_user: '',
        smtp_password: '',
        use_tls: true,
        from_email: '',
        to_email: '',
        cc_email: '',
        bcc_email: '',
        subject: '',
        body: '',
        body_type: 'text',
        attachments: []
      } : {}),
      // Switch节点默认配置
      ...(nodeType === 'switch' ? {
        field: 'status',
        cases: {},
        default: 'default'
      } : {}),
      // Merge节点默认配置
      ...(nodeType === 'merge' ? {
        mode: 'merge_all',
        strategy: 'array'
      } : {}),
      // Wait节点默认配置
      ...(nodeType === 'wait' ? {
        wait_type: 'condition',
        condition: '',
        timeout: 300,
        poll_interval: 5
      } : {}),
      // JSON处理节点默认配置
      ...(nodeType === 'json' ? {
        operation: 'parse',
        path: '',
        schema: {}
      } : {}),
      // 文本处理节点默认配置
      ...(nodeType === 'text' ? {
        operation: 'split',
        delimiter: '\n',
        regex: '',
        template: ''
      } : {}),
      // 缓存节点默认配置
      ...(nodeType === 'cache' ? {
        operation: 'get',
        key: '',
        ttl: 3600,
        backend: 'memory'
      } : {}),
      // 向量数据库节点默认配置
      ...(nodeType === 'vector_db' ? {
        operation: 'search',
        collection: 'default',
        query_vector: '',
        top_k: 5
      } : {}),
      // 日志节点默认配置
      ...(nodeType === 'log' ? {
        level: 'info',
        message: '节点执行',
        include_data: true
      } : {}),
      // 错误处理节点默认配置
      ...(nodeType === 'error_handler' ? {
        retry_count: 3,
        retry_delay: 1000,
        on_error: 'notify',
        error_handler_workflow: ''
      } : {}),
      // CSV处理节点默认配置
      ...(nodeType === 'csv' ? {
        operation: 'parse',
        delimiter: ',',
        headers: true,
        encoding: 'utf-8'
      } : {}),
      // 对象存储节点默认配置
      ...(nodeType === 'object_storage' ? {
        provider: 's3',
        operation: 'upload',
        bucket: '',
        key: '',
        file: ''
      } : {}),
      // Slack节点默认配置
      ...(nodeType === 'slack' ? {
        operation: 'send_message',
        token: '',
        channel: '',
        message: '',
        attachments: []
      } : {}),
      // 钉钉节点默认配置
      ...(nodeType === 'dingtalk' || nodeType === 'dingding' ? {
        operation: 'send_message',
        webhook_url: '',
        access_token: '',
        chat_id: '',
        message: ''
      } : {}),
      // 企业微信节点默认配置
      ...(nodeType === 'wechat_work' || nodeType === 'wecom' ? {
        operation: 'send_message',
        corp_id: '',
        corp_secret: '',
        agent_id: '',
        chat_id: '',
        message: ''
      } : {}),
      // 短信节点默认配置
      ...(nodeType === 'sms' ? {
        provider: 'aliyun',
        operation: 'send',
        phone: '',
        template: '',
        sign: '',
        access_key: '',
        access_secret: ''
      } : {}),
      // PDF处理节点默认配置
      ...(nodeType === 'pdf' ? {
        operation: 'extract_text',
        pages: '',
        template: ''
      } : {}),
      // 图像处理节点默认配置
      ...(nodeType === 'image' ? {
        operation: 'resize',
        width: 800,
        height: 600,
        format: 'png'
      } : {}),
      // Excel处理节点默认配置
      ...(nodeType === 'excel' ? {
        operation: 'read',
        sheet: 'Sheet1',
        range: '',
        format: 'xlsx'
      } : {}),
      // 子工作流节点默认配置
      ...(nodeType === 'subworkflow' ? {
        workflow_id: '',
        input_mapping: {}
      } : {}),
      // 代码执行节点默认配置
      ...(nodeType === 'code' ? {
        language: 'python',
        code: "result = input_data",
        timeout: 30
      } : {}),
      // OAuth节点默认配置
      ...(nodeType === 'oauth' ? {
        provider: 'google',
        client_id: '',
        client_secret: '',
        scopes: []
      } : {}),
      // 数据验证节点默认配置
      ...(nodeType === 'validator' ? {
        schema: {},
        on_error: 'reject'
      } : {}),
      // 批处理节点默认配置
      ...(nodeType === 'batch' ? {
        batch_size: 100,
        mode: 'split',
        wait_for_completion: true
      } : {}),
      // 结束节点默认配置
      ...(nodeType === 'end' || nodeType === 'output' ? {
        output_format: 'text'  // 默认纯文本格式，适合对话场景
      } : {}),
      // 消息队列节点默认配置
      ...(isMQNode ? {
        queue_type: 'rabbitmq',
        host: 'localhost',
        port: 5672,
        username: 'guest',
        password: 'guest',
        exchange: '',
        routing_key: '',
        queue_name: '',
        topic: '',
        bootstrap_servers: 'localhost:9092',
        message: ''
      } : {})
    }
  }
  
  console.log('添加节点:', newNode)
  addNodes([newNode])
  pushHistory('add node')
  hasChanges.value = true
  draggedNodeType.value = null
}

// 节点点击
const onNodeClick = (event: NodeClickEvent) => {
  selectedNode.value = event.node
  selectedEdge.value = null  // 清除选中的边
  // 清除所有边的选中状态
  edges.value.forEach(e => {
    if (e.selected) {
      e.selected = false
      e.style = { ...e.style, stroke: '#409eff' } // 恢复默认颜色
    }
  })
  
  // 重置节点测试数据
  nodeTestInput.value = getDefaultTestInput(event.node.type)
  nodeTestOutput.value = ''
  nodeTestResult.value = null
  selectedTestCaseId.value = ''
}

// 节点双击 - 显示执行详情
const onNodeDoubleClick = (event: NodeClickEvent) => {
  // 阻止双击时的默认行为（如缩放）
  event.event?.preventDefault?.()
  
  if (currentExecutionId.value) {
    // 确保选中节点
    selectedNode.value = event.node
    nodeDetailVisible.value = true
  } else {
    ElMessage.info('暂无执行记录，请先执行工作流')
  }
}

// 判断是否为定时任务节点类型（计算属性）
const isScheduleNodeSelected = computed(() => {
  return selectedNode.value && (selectedNode.value.type === 'schedule' || selectedNode.value.type === 'delay' || selectedNode.value.type === 'timer')
})

// 获取延迟提示文本（计算属性）
const delayAlertTitle = computed(() => {
  if (!selectedNode.value || !selectedNode.value.data) {
    return ''
  }
  const delayValue = selectedNode.value.data.delay_value || 0
  const delayUnit = selectedNode.value.data.delay_unit || 'seconds'
  let unit = '秒'
  if (delayUnit === 'minutes') {
    unit = '分钟'
  } else if (delayUnit === 'hours') {
    unit = '小时'
  }
  return '将在执行时延迟 ' + String(delayValue) + ' ' + unit
})

// 获取默认测试输入（根据节点类型）
const getDefaultTestInput = (nodeType: string): string => {
  const defaults: Record<string, any> = {
    'start': {},
    'input': { text: '测试文本', input: '测试输入' },
    'llm': { input: '请总结以下内容', text: '这是一段需要处理的文本内容...' },
    'template': { input: '请处理用户请求', context: '相关上下文' },
    'condition': { value: 10, threshold: 5, status: 'active' },
    'switch': { status: 'success', route: 'default', value: 'test' },
    'merge': { branch1: { data: 'data1' }, branch2: { data: 'data2' } },
    'wait': { condition: 'ready', status: 'pending' },
    'transform': { data: { name: '测试', value: 100 }, items: [{ id: 1, name: 'item1' }] },
    'json': { json: '{"data": {"items": [{"id": 1}]}}', path: '$.data.items[*]' },
    'text': { text: '这是需要处理的文本\n包含多行内容', delimiter: '\n' },
    'cache': { key: 'test_key', value: 'test_value' },
    'vector_db': { query: '搜索查询', query_vector: '[0.1, 0.2, 0.3]' },
    'http': { url: 'https://api.example.com/data', method: 'GET', params: { page: 1 } },
    'database': { query: 'SELECT * FROM table', params: {} },
    'file': { file_path: '/path/to/file.txt', content: '文件内容' },
    'webhook': { method: 'POST', body: { event: 'test' } },
    'email': { to: 'test@example.com', subject: '测试邮件', body: '邮件内容' },
    'slack': { channel: '#general', message: '测试消息' },
    'dingtalk': { message: '测试消息', chat_id: 'chat123' },
    'wechat_work': { message: '测试消息', chat_id: 'chat123' },
    'sms': { phone: '13800138000', template: '验证码：{code}', code: '123456' },
    'log': { message: '测试日志', level: 'info' },
    'error_handler': { error: { message: '测试错误', code: 'TEST_ERROR' } },
    'csv': { csv_data: 'name,age\nJohn,30\nJane,25', delimiter: ',' },
    'object_storage': { bucket: 'my-bucket', key: 'files/test.txt', file: 'base64data' },
    'pdf': { pdf_data: 'base64encodedpdf', pages: '1-10' },
    'image': { image_data: 'base64encodedimage', width: 800, height: 600 },
    'excel': { excel_data: 'base64encodedexcel', sheet: 'Sheet1' },
    'subworkflow': { workflow_id: 'workflow123', input_mapping: { param1: 'value1' } },
    'code': { input_data: { value: 10 }, code: 'return input_data["value"] * 2' },
    'oauth': { provider: 'google', scopes: ['openid', 'profile'] },
    'validator': { data: { name: 'test', age: 25 }, schema: {} },
    'batch': { items: [{ id: 1 }, { id: 2 }, { id: 3 }], batch_size: 2 },
    'end': { result: '测试结果', output: '最终输出' },
    'output': { message: '测试消息', data: { test: 'value' } }
  }
  
  const defaultData = defaults[nodeType] || { input: '测试输入', data: { test: 'value' } }
  return JSON.stringify(defaultData, null, 2)
}

// 从上游节点填充测试数据
const fillFromUpstream = () => {
  if (!selectedNode.value) return
  
  const upstreamEdges = edges.value.filter(e => e.target === selectedNode.value?.id)
  if (upstreamEdges.length === 0) {
    ElMessage.warning('当前节点没有上游节点')
    return
  }
  
  // 获取第一个上游节点的示例输出
  const upstreamNode = nodes.value.find(n => n.id === upstreamEdges[0].source)
  if (!upstreamNode) return
  
  // 根据上游节点类型生成示例数据
  const exampleData = generateExampleDataForNode(upstreamNode)
  nodeTestInput.value = JSON.stringify(exampleData, null, 2)
  testInputError.value = ''
  ElMessage.success('已从上游节点填充测试数据')
}

// 根据节点类型生成示例数据
const generateExampleDataForNode = (node: Node): any => {
  const nodeType = node.type
  const nodeData = node.data || {}
  
  switch (nodeType) {
    case 'llm':
    case 'template':
      return {
        output: '这是LLM生成的回复内容',
        response: '这是完整的响应',
        tokens: 100
      }
    case 'http':
      return {
        response: {
          status: 200,
          data: { result: 'success', items: [{ id: 1, name: '测试' }] },
          headers: {}
        },
        body: { result: 'success' }
      }
    case 'json':
      return {
        data: { items: [{ id: 1, value: 'test' }], total: 1 },
        result: { extracted: 'value' }
      }
    case 'transform':
      return {
        result: nodeData.mapping ? Object.keys(nodeData.mapping).reduce((acc, key) => {
          acc[key] = `示例值_${key}`
          return acc
        }, {} as any) : { transformed: 'data' }
      }
    case 'code':
      return {
        output: { result: '代码执行结果' },
        result: '处理后的数据'
      }
    case 'vector_db':
      return {
        results: [
          { id: '1', text: '相关文档1', similarity: 0.95 },
          { id: '2', text: '相关文档2', similarity: 0.88 }
        ],
        similarity: 0.95
      }
    case 'cache':
      return {
        value: '缓存的值',
        cached: true
      }
    case 'switch':
      return {
        status: 'success',
        route: 'default'
      }
    case 'merge':
      return {
        merged: [{ branch1: 'data1' }, { branch2: 'data2' }]
      }
    default:
      return {
        output: '节点输出',
        data: { test: 'value' }
      }
  }
}

// 格式化测试输入
const formatTestInput = () => {
  try {
    const parsed = JSON.parse(nodeTestInput.value || '{}')
    nodeTestInput.value = JSON.stringify(parsed, null, 2)
    testInputError.value = ''
    ElMessage.success('格式化成功')
  } catch (error: any) {
    testInputError.value = 'JSON格式错误: ' + error.message
    ElMessage.error('JSON格式错误，无法格式化')
  }
}

// 清空测试输入
const clearTestInput = () => {
  nodeTestInput.value = '{}'
  testInputError.value = ''
}

// 应用测试输入模板
const applyTestInputTemplate = (template: string) => {
  if (!selectedNode.value) return
  
  let templateData: any = {}
  
  switch (template) {
    case 'empty':
      templateData = {}
      break
    case 'basic':
      templateData = { input: '测试输入' }
      break
    case 'full':
      templateData = {
        input: '测试输入',
        query: '测试查询',
        data: { test: 'value', number: 123 },
        options: { enabled: true }
      }
      break
    case 'llm':
      templateData = {
        input: '请总结以下内容',
        text: '这是一段需要总结的长文本内容...',
        context: '相关上下文信息'
      }
      break
    case 'http':
      templateData = {
        url: 'https://api.example.com/data',
        method: 'GET',
        params: { page: 1, limit: 10 }
      }
      break
    case 'json':
      templateData = {
        json: '{"data": {"items": [{"id": 1, "name": "测试"}]}}',
        path: '$.data.items[*]'
      }
      break
  }
  
  nodeTestInput.value = JSON.stringify(templateData, null, 2)
  testInputTemplate.value = ''
  testInputError.value = ''
}

// 复制测试结果
const copyTestResult = () => {
  if (!nodeTestResult.value || nodeTestResult.value.status !== 'success') return
  
  const text = formattedTestOutput.value || String(nodeTestResult.value.output)
  navigator.clipboard.writeText(text).then(() => {
    ElMessage.success('已复制到剪贴板')
  }).catch(() => {
    ElMessage.error('复制失败')
  })
}

// 导出测试结果
const downloadTestResult = () => {
  if (!nodeTestResult.value || nodeTestResult.value.status !== 'success') return
  
  const data = {
    node_id: selectedNode.value?.id,
    node_type: selectedNode.value?.type,
    node_name: selectedNode.value?.data?.label,
    test_input: JSON.parse(nodeTestInput.value || '{}'),
    test_output: nodeTestResult.value.output,
    execution_time: nodeTestResult.value.execution_time,
    timestamp: new Date().toISOString()
  }
  
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = `test_result_${selectedNode.value?.id}_${Date.now()}.json`
  a.click()
  URL.revokeObjectURL(url)
  ElMessage.success('测试结果已导出')
}

// 获取输出类型
const getOutputType = (output: any): string => {
  if (output === null) return 'null'
  if (Array.isArray(output)) return 'array'
  if (typeof output === 'object') return 'object'
  return typeof output
}

// 验证测试输入
watch(nodeTestInput, (value) => {
  if (!value || value.trim() === '') {
    testInputError.value = ''
    return
  }
  
  try {
    JSON.parse(value)
    testInputError.value = ''
  } catch (error: any) {
    testInputError.value = 'JSON格式错误: ' + error.message
  }
})

// 测试用例存储
const persistTestCases = () => {
  localStorage.setItem(testCaseStorageKey.value, JSON.stringify(nodeTestCases.value))
}

const loadTestCases = () => {
  try {
    const stored = localStorage.getItem(testCaseStorageKey.value)
    if (stored) {
      nodeTestCases.value = JSON.parse(stored)
    }
  } catch (error) {
    console.warn('加载测试用例失败', error)
  }
}

const saveCurrentTestCase = async () => {
  if (!selectedNode.value) {
    ElMessage.warning('请先选择节点')
    return
  }
  try {
    JSON.parse(nodeTestInput.value || '{}')
  } catch (error: any) {
    ElMessage.error('输入数据不是有效的JSON，无法保存用例')
    return
  }
  try {
    const { value } = await ElMessageBox.prompt('请输入测试用例名称', '保存测试用例', {
      inputValue: selectedNode.value.data?.label || '测试用例'
    })
    const nodeId = selectedNode.value.id
    const cases = nodeTestCases.value[nodeId] || []
    const newCase = {
      id: `case_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
      name: value,
      data: nodeTestInput.value
    }
    nodeTestCases.value[nodeId] = [...cases, newCase]
    selectedTestCaseId.value = newCase.id
    persistTestCases()
    ElMessage.success('测试用例已保存')
  } catch (error) {
    // 用户取消不提示
  }
}

const applySelectedTestCase = () => {
  if (!selectedNode.value || !selectedTestCaseId.value) return
  const nodeId = selectedNode.value.id
  const cases = nodeTestCases.value[nodeId] || []
  const c = cases.find(i => i.id === selectedTestCaseId.value)
  if (c) {
    nodeTestInput.value = c.data
    testInputError.value = ''
    ElMessage.success('已应用测试用例')
  }
}

const deleteSelectedTestCase = () => {
  if (!selectedNode.value || !selectedTestCaseId.value) return
  const nodeId = selectedNode.value.id
  const cases = nodeTestCases.value[nodeId] || []
  const next = cases.filter(i => i.id !== selectedTestCaseId.value)
  nodeTestCases.value[nodeId] = next
  selectedTestCaseId.value = ''
  persistTestCases()
  ElMessage.success('已删除测试用例')
}

// 边点击
const onEdgeClick = (event: EdgeClickEvent) => {
  console.log('Edge clicked:', event.edge)
  
  // 选中边，取消选中节点
  selectedEdge.value = event.edge
  selectedNode.value = null
  
  // 设置边的选中状态
  const edge = edges.value.find(e => e.id === event.edge.id)
  if (edge) {
    edge.selected = true
    console.log('Edge selected:', edge.id, 'selected:', edge.selected)
  }
  
  // 清除其他边的选中状态
  edges.value.forEach(e => {
    if (e.id !== event.edge.id) {
      e.selected = false
    }
  })
}

// 画布点击
const onPaneClick = () => {
  selectedNode.value = null
  selectedEdge.value = null
}

// 节点变化
const onNodesChange = (changes: any[]) => {
  // 处理节点变化
  console.log('Nodes changed:', changes)
  if (isRestoringHistory.value) {
    return
  }
  const hasStructuralChange = changes.some(change => change.type !== 'select' && (!('dragging' in change) || change.dragging === false))
  if (hasStructuralChange && !isProcessingRemoteOperation.value) {
    pushHistory('nodes change')
    hasChanges.value = true
  }
  
  // 如果正在处理远程操作，不发送协作消息
  if (isProcessingRemoteOperation.value) {
    return
  }
  
  // 发送协作操作
  if (collaboration && collaborationConnected.value) {
    for (const change of changes) {
      if (change.type === 'add' && change.item) {
        // 节点添加
        collaboration.sendOperation({
          type: 'node_add',
          data: {
            node: change.item
          }
        })
      } else if (change.type === 'remove' && change.id) {
        // 节点删除
        collaboration.sendOperation({
          type: 'node_delete',
          data: {
            node_id: change.id
          }
        })
      } else if (change.type === 'position' && change.id && change.position) {
        // 节点移动
        collaboration.sendOperation({
          type: 'node_move',
          data: {
            node_id: change.id,
            position: change.position
          }
        })
      }
    }
  }
}

// 边变化
const onEdgesChange = (changes: any[]) => {
  // 处理边变化
  console.log('Edges changed:', changes)
  if (isRestoringHistory.value) {
    return
  }
  const hasStructuralChange = changes.some(change => change.type === 'add' || change.type === 'remove')
  if (hasStructuralChange && !isProcessingRemoteOperation.value) {
    pushHistory('edges change')
    hasChanges.value = true
  }
  
  // 处理边的选中状态变化
  for (const change of changes) {
    if (change.type === 'select') {
      if (change.selected) {
        // 边被选中
        const edge = edges.value.find(e => e.id === change.id)
        if (edge) {
          selectedEdge.value = edge
          selectedNode.value = null  // 清除节点选中
        }
      } else {
        // 边取消选中
        if (selectedEdge.value?.id === change.id) {
          selectedEdge.value = null
        }
      }
    }
  }
  
  // 如果正在处理远程操作，不发送协作消息
  if (isProcessingRemoteOperation.value) {
    return
  }
  
  // 发送协作操作
  if (collaboration && collaborationConnected.value) {
    for (const change of changes) {
      if (change.type === 'add' && change.item) {
        // 边添加
        collaboration.sendOperation({
          type: 'edge_add',
          data: {
            edge: change.item
          }
        })
      } else if (change.type === 'remove' && change.id) {
        // 边删除
        collaboration.sendOperation({
          type: 'edge_delete',
          data: {
            edge_id: change.id
          }
        })
      }
    }
  }
}

// 连接验证函数 - 允许所有方向的连接（包括左右）
const isValidConnection = (connection: Connection) => {
  console.log('验证连接:', connection)
  
  // 基本验证：必须有源和目标
  if (!connection.source || !connection.target) {
    return false
  }
  
  // 不能连接到自身
  if (connection.source === connection.target) {
    return false
  }
  
  // 检查是否已经存在相同的连接
  const existingEdge = edges.value.find(
    e => e.source === connection.source && 
         e.target === connection.target &&
         (e.sourceHandle === connection.sourceHandle || (!e.sourceHandle && !connection.sourceHandle)) &&
         (e.targetHandle === connection.targetHandle || (!e.targetHandle && !connection.targetHandle))
  )
  
  if (existingEdge) {
    return false
  }
  
  // 允许所有方向的连接（上下左右都可以）
  // 不限制连接点的位置，允许任意方向连接
  return true
}

// 连接开始
const onConnectStart = (event: any) => {
  console.log('开始连接:', event)
}

// 连接结束
const onConnectEnd = (event: any) => {
  console.log('结束连接:', event)
}

// 连接节点
const onConnect = (connection: Connection) => {
  console.log('连接节点:', connection)
  if (connection.source && connection.target) {
    // 检查是否连接到自身
    if (connection.source === connection.target) {
      ElMessage.warning('不能连接到自身')
      return
    }
    
    // 检查是否已经存在相同的连接
    const existingEdge = edges.value.find(
      e => e.source === connection.source && 
           e.target === connection.target &&
           (e.sourceHandle === connection.sourceHandle || (!e.sourceHandle && !connection.sourceHandle)) &&
           (e.targetHandle === connection.targetHandle || (!e.targetHandle && !connection.targetHandle))
    )
    
    if (existingEdge) {
      ElMessage.warning('连接已存在')
      return
    }
    
    // 清除选中的边
    selectedEdge.value = null
    
    const newEdge: Edge = {
      id: `edge_${connection.source}_${connection.target}_${Date.now()}`,
      source: connection.source,
      target: connection.target,
      sourceHandle: connection.sourceHandle || undefined,
      targetHandle: connection.targetHandle || undefined,
      type: 'bezier',  // 使用贝塞尔曲线（平滑曲线）
      animated: true,
      selectable: true,
      deletable: true,
      focusable: true,
      style: {
        stroke: '#409eff',
        strokeWidth: 2.5,
        strokeDasharray: '0'
      },
      markerEnd: {
        type: 'arrowclosed',
        color: '#409eff',
        width: 20,
        height: 20
      }
    }
    addEdges([newEdge])
    pushHistory('connect nodes')
    hasChanges.value = true
    ElMessage.success('节点已连接')
    
    // 发送协作操作
    if (collaboration && collaborationConnected.value && !isProcessingRemoteOperation.value) {
      collaboration.sendOperation({
        type: 'edge_add',
        data: {
          edge: newEdge
        }
      })
    }
  }
}

// 保存节点配置
const handleSaveNode = async () => {
  if (selectedNode.value) {
    updateNode(selectedNode.value.id, selectedNode.value)
    
    // 保存节点配置后，自动保存工作流，确保配置持久化
    try {
      const workflow = checkChanges()
      if (hasChanges.value) {
        emit('save', workflow)
        lastSavedData.value = JSON.stringify(workflow)
        hasChanges.value = false
      }
      ElMessage.success('节点配置已保存并同步到工作流')
    } catch (error: any) {
      ElMessage.warning('节点配置已更新，但工作流保存失败，请手动保存')
    }
    
    // 发送协作操作
    if (collaboration && collaborationConnected.value && !isProcessingRemoteOperation.value) {
      collaboration.sendOperation({
        type: 'node_update',
        data: {
          node_id: selectedNode.value.id,
          data: selectedNode.value.data
        }
      })
    }
  }
}

// 测试节点
const handleTestNode = async () => {
  if (!selectedNode.value) {
    ElMessage.warning('请先选择一个节点')
    return
  }
  
  // 解析输入数据
  let inputData: any = {}
  try {
    inputData = JSON.parse(nodeTestInput.value || '{}')
  } catch (error) {
    ElMessage.error('输入数据格式错误，请输入有效的JSON格式')
    return
  }
  
  testingNode.value = true
  nodeTestOutput.value = ''
  nodeTestResult.value = null
  
  try {
    // 准备节点数据
    const nodeData = {
      id: selectedNode.value.id,
      type: selectedNode.value.type,
      data: selectedNode.value.data
    }
    
    // 调用节点测试API
    const response = await api.post('/api/v1/nodes/test', {
      node: nodeData,
      input_data: inputData
    })
    
    const result = response.data
    nodeTestResult.value = result
    
    // 格式化输出数据
    if (result.status === 'success') {
      if (result.output === null || result.output === undefined) {
        nodeTestOutput.value = 'null'
      } else {
        // 如果输出是字符串，直接显示；如果是对象，格式化为JSON
        if (typeof result.output === 'string') {
          nodeTestOutput.value = result.output
        } else {
          nodeTestOutput.value = JSON.stringify(result.output, null, 2)
        }
      }
    } else if (result.status === 'error') {
      nodeTestOutput.value = `错误: ${result.error_message || '未知错误'}`
    }
    
    // 触发节点测试事件，让父组件在预览面板中显示结果
    emit('node-test', {
      node: selectedNode.value,
      result: result,
      input: inputData
    })
    
    if (result.status === 'success') {
      ElMessage.success(`节点测试成功 (${result.execution_time}ms)`)
    } else {
      ElMessage.error(`节点测试失败: ${result.error_message || '未知错误'}`)
    }
    
  } catch (error: any) {
    console.error('测试节点失败:', error)
    const errorMessage = error.response?.data?.detail || error.message || '测试失败'
    ElMessage.error(errorMessage)
    
    // 显示错误信息
    nodeTestResult.value = {
      status: 'error',
      error_message: errorMessage
    }
    nodeTestOutput.value = `错误: ${errorMessage}`
    
    // 即使失败也触发事件，显示错误信息
    emit('node-test', {
      node: selectedNode.value,
      result: {
        status: 'error',
        error_message: errorMessage
      },
      input: inputData
    })
  } finally {
    testingNode.value = false
  }
}

// 处理远程协作操作
const handleRemoteOperation = (operation: any) => {
  if (!operation || !operation.data) return
  
  isProcessingRemoteOperation.value = true
  
  try {
    switch (operation.type) {
      case 'node_add':
        if (operation.data.node) {
          addNodes([operation.data.node])
        }
        break
      
      case 'node_delete':
        if (operation.data.node_id) {
          const nodeToRemove = nodes.value.find(n => n.id === operation.data.node_id)
          if (nodeToRemove) {
            removeNodes([nodeToRemove])
          }
        }
        break
      
      case 'node_move':
        if (operation.data.node_id && operation.data.position) {
          const nodeToMove = nodes.value.find(n => n.id === operation.data.node_id)
          if (nodeToMove) {
            nodeToMove.position = operation.data.position
          }
        }
        break
      
      case 'node_update':
        if (operation.data.node_id && operation.data.data) {
          const nodeToUpdate = nodes.value.find(n => n.id === operation.data.node_id)
          if (nodeToUpdate) {
            nodeToUpdate.data = { ...nodeToUpdate.data, ...operation.data.data }
          }
        }
        break
      
      case 'edge_add':
        if (operation.data.edge) {
          addEdges([operation.data.edge])
        }
        break
      
      case 'edge_delete':
        if (operation.data.edge_id) {
          const edgeToRemove = edges.value.find(e => e.id === operation.data.edge_id)
          if (edgeToRemove) {
            removeEdges([edgeToRemove])
          }
        }
        break
    }
  } finally {
    // 延迟重置标志，避免立即触发本地操作
    setTimeout(() => {
      isProcessingRemoteOperation.value = false
    }, 100)
  }
}

// 复制节点
const handleCopyNode = () => {
  if (selectedNode.value) {
    // 深拷贝节点
    const nodeCopy = JSON.parse(JSON.stringify(selectedNode.value))
    copiedNode.value = nodeCopy
    ElMessage.success('节点已复制，按Ctrl+V粘贴或点击粘贴按钮')
  }
}

// 粘贴节点（从按钮）
const handlePasteNodeFromButton = () => {
  if (copiedNode.value) {
    // 计算画布中心位置
    const centerX = 400
    const centerY = 300
    pasteNodeAtPosition(centerX, centerY)
  }
}

// 粘贴节点到指定位置
const pasteNodeAtPosition = (x: number, y: number) => {
  if (!copiedNode.value) return
  
  // 生成新的节点ID
  const newId = `${copiedNode.value.type}-${Date.now()}`
  const newNode: Node = {
    ...copiedNode.value,
    id: newId,
    position: {
      x: x,
      y: y
    },
    data: {
      ...copiedNode.value.data,
      label: `${copiedNode.value.data?.label || copiedNode.value.type} (副本)`
    }
  }
  
  addNodes([newNode])
  selectedNode.value = newNode
  copiedNode.value = null
  ElMessage.success('节点已粘贴')
}

// 缩放控制
const zoomIn = () => {
  vueFlowZoomIn()
}

const zoomOut = () => {
  vueFlowZoomOut()
}

const resetZoom = () => {
  setViewport({ x: 0, y: 0, zoom: 1 })
}

// 视口变化监听
const onViewportChange = (viewport: Viewport) => {
  currentZoom.value = viewport.zoom
}

// 键盘快捷键支持
const handleKeyDown = (event: KeyboardEvent) => {
  // 如果焦点在输入框等元素上，不处理
  const target = event.target as HTMLElement
  if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
    return
  }

  const isCtrlOrMeta = event.ctrlKey || event.metaKey
  const key = typeof event.key === 'string' ? event.key.toLowerCase() : ''

  // Ctrl+Z 撤销 / Ctrl+Shift+Z 或 Ctrl+Y 重做
  if (isCtrlOrMeta && key === 'z') {
    event.preventDefault()
    if (event.shiftKey) {
      redo()
    } else {
      undo()
    }
    return
  }
  if (isCtrlOrMeta && key === 'y') {
    event.preventDefault()
    redo()
    return
  }
  
  // Ctrl+C 复制
  if (isCtrlOrMeta && key === 'c' && selectedNode.value) {
    handleCopyNode()
    event.preventDefault()
  }
  // Ctrl+V 粘贴
  if (isCtrlOrMeta && key === 'v' && copiedNode.value) {
    handlePasteNodeFromButton()
    event.preventDefault()
  }
  // Delete 或 Backspace 删除节点或边
  if (event.key === 'Delete' || event.key === 'Backspace') {
    console.log('Delete key pressed, selectedEdge:', selectedEdge.value, 'selectedNode:', selectedNode.value)
    
    // 优先删除选中的边
    if (selectedEdge.value) {
      console.log('Deleting edge:', selectedEdge.value.id)
      handleDeleteEdge()
      event.preventDefault()
      return
    }
    
    // 然后删除选中的节点
    if (selectedNode.value) {
      console.log('Deleting node:', selectedNode.value.id)
      handleDeleteNode()
      event.preventDefault()
      return
    }
    
    // 如果没有选中的边或节点，尝试删除 Vue Flow 中选中的元素
    const selectedEdges = edges.value.filter(e => e.selected)
    if (selectedEdges.length > 0) {
      console.log('Deleting selected edges:', selectedEdges.map(e => e.id))
      removeEdges(selectedEdges)
      selectedEdge.value = null
      ElMessage.success('连接已删除')
      event.preventDefault()
      return
    }
  }
  // Ctrl+S 保存
  if (isCtrlOrMeta && key === 's') {
    event.preventDefault()
    handleSave()
  }
  // Ctrl+0 重置缩放
  if (isCtrlOrMeta && key === '0') {
    event.preventDefault()
    resetZoom()
  }
  // Ctrl+Plus 放大
  if (isCtrlOrMeta && (key === '+' || key === '=')) {
    event.preventDefault()
    zoomIn()
  }
  // Ctrl+Minus 缩小
  if (isCtrlOrMeta && key === '-') {
    event.preventDefault()
    zoomOut()
  }
  // Ctrl+L 自动布局
  if (isCtrlOrMeta && key === 'l') {
    event.preventDefault()
    handleAutoLayout()
  }
}

// 删除节点（优化版）
const handleDeleteNode = () => {
  if (selectedNode.value) {
    const nodeId = selectedNode.value.id
    const nodeType = selectedNode.value.type
    
    // 检查是否是开始或结束节点
    if (nodeType === 'start' || nodeType === 'end') {
      ElMessage.warning('不能删除开始或结束节点')
      return
    }
    
    // 删除与该节点相关的所有边
    const relatedEdges = edges.value.filter(
      edge => edge.source === nodeId || edge.target === nodeId
    )
    if (relatedEdges.length > 0) {
      removeEdges(relatedEdges)
    }
    
    // 删除节点
    removeNodes([selectedNode.value])
    selectedNode.value = null
    ElMessage.success('节点已删除')
    
    // 发送协作操作
    if (collaboration && collaborationConnected.value && !isProcessingRemoteOperation.value) {
      collaboration.sendOperation({
        type: 'node_delete',
        data: {
          node_id: nodeId
        }
      })
    }
  }
}

// 删除边
const handleDeleteEdge = () => {
  if (selectedEdge.value) {
    const edgeId = selectedEdge.value.id
    const edgeToRemove = edges.value.find(e => e.id === edgeId)
    
    if (edgeToRemove) {
      // 删除边
      removeEdges([edgeToRemove])
      selectedEdge.value = null
      ElMessage.success('连接已删除')
      
      // 发送协作操作
      if (collaboration && collaborationConnected.value && !isProcessingRemoteOperation.value) {
        collaboration.sendOperation({
          type: 'edge_delete',
          data: {
            edge_id: edgeId
          }
        })
      }
    }
  }
}

// 检查是否有变更
const checkChanges = () => {
  const workflow = {
    nodes: nodes.value.map(node => ({
      id: node.id,
      type: node.type || 'default',
      position: node.position,
      data: node.data
    })),
    edges: edges.value.map(edge => ({
      id: edge.id,
      source: edge.source,
      target: edge.target,
      sourceHandle: edge.sourceHandle,
      targetHandle: edge.targetHandle
    }))
  }
  const currentData = JSON.stringify(workflow)
  hasChanges.value = currentData !== lastSavedData.value
  return workflow
}

// 保存工作流
const handleSave = async () => {
  if (saving.value) return
  
  const workflow = checkChanges()
  
  if (!hasChanges.value && lastSavedData.value) {
    ElMessage.info('没有需要保存的更改')
    return
  }
  
  saving.value = true
  try {
    await new Promise(resolve => setTimeout(resolve, 300)) // 模拟保存延迟
    
    emit('save', workflow)
    lastSavedData.value = JSON.stringify(workflow)
    hasChanges.value = false
    ElMessage.success('工作流已保存')
  } catch (error: any) {
    ElMessage.error(error.response?.data?.detail || '保存失败')
  } finally {
    saving.value = false
  }
}

// 自动保存（可选）
const enableAutoSave = () => {
  if (savingInterval.value) return
  
  savingInterval.value = window.setInterval(() => {
    if (hasChanges.value && !saving.value) {
      // 静默保存，不显示提示
      const workflow = checkChanges()
      if (hasChanges.value) {
        emit('save', workflow)
        lastSavedData.value = JSON.stringify(workflow)
        hasChanges.value = false
        console.log('自动保存完成')
      }
    }
  }, 30000) // 每30秒自动保存一次
}

const disableAutoSave = () => {
  if (savingInterval.value) {
    clearInterval(savingInterval.value)
    savingInterval.value = null
  }
}

// 运行工作流
const handleRun = async () => {
  // 检查是否有工作流ID
  if (!props.workflowId) {
    ElMessage.warning('请先保存工作流后再运行')
    return
  }
  
  // 检查工作流是否有节点
  if (nodes.value.length === 0) {
    ElMessage.warning('工作流不能为空，请先添加节点')
    return
  }
  
  // 打开运行对话框
  runForm.value.inputData = '{}'
  runDialogVisible.value = true
}

// 确认运行
const handleConfirmRun = async () => {
  if (!props.workflowId) {
    ElMessage.error('工作流ID不存在')
    return
  }
  
  // 解析输入数据
  let inputData: any = {}
  try {
    inputData = JSON.parse(runForm.value.inputData || '{}')
  } catch (error) {
    ElMessage.error('输入参数格式错误，请输入有效的JSON格式')
    return
  }
  
  running.value = true
  try {
    // 调用执行API
    const response = await api.post(`/api/v1/workflows/${props.workflowId}/execute`, inputData)
    const execution = response.data
    
    ElMessage.success('工作流执行已启动')
    runDialogVisible.value = false
    
    // 跳转到执行详情页
    router.push(`/executions/${execution.id}`)
  } catch (error: any) {
    console.error('执行工作流失败:', error)
    const errorMessage = error.response?.data?.message || error.message || '执行工作流失败'
    ElMessage.error(errorMessage)
  } finally {
    running.value = false
  }
}

// 清空画布
const handleClear = () => {
  removeNodes(nodes.value)
  removeEdges(edges.value)
  selectedNode.value = null
  selectedEdge.value = null
  ElMessage.success('画布已清空')
}

// 节点对齐功能
const handleAlignNodes = (command: string) => {
  // 获取选中的节点（支持多选）
  const selectedNodes = nodes.value.filter(node => node.selected)
  
  if (selectedNodes.length < 2) {
    ElMessage.warning('请至少选择2个节点进行对齐')
    return
  }
  
  // 计算对齐基准值
  let baseValue: number
  // 默认节点尺寸（根据节点类型可能有不同）
  const defaultNodeWidth = 150
  const defaultNodeHeight = 50
  
  const positions = selectedNodes.map(node => {
    // 尝试从DOM获取实际尺寸，如果获取不到则使用默认值
    let width = defaultNodeWidth
    let height = defaultNodeHeight
    
    // 可以通过vueFlowInstance获取节点尺寸
    try {
      const nodeElement = document.querySelector(`[data-id="${node.id}"]`)
      if (nodeElement) {
        const rect = nodeElement.getBoundingClientRect()
        width = rect.width || defaultNodeWidth
        height = rect.height || defaultNodeHeight
      }
    } catch (e) {
      // 如果获取失败，使用默认值
    }
    
    return {
      id: node.id,
      x: node.position.x,
      y: node.position.y,
      width,
      height
    }
  })
  
  switch (command) {
    case 'left':
      // 左对齐：以最左边的节点为基准
      baseValue = Math.min(...positions.map(p => p.x))
      positions.forEach(pos => {
        updateNode(pos.id, { position: { x: baseValue, y: pos.y } })
      })
      ElMessage.success('节点已左对齐')
      break
      
    case 'right':
      // 右对齐：以最右边的节点为基准
      baseValue = Math.max(...positions.map(p => p.x + p.width))
      positions.forEach(pos => {
        updateNode(pos.id, { position: { x: baseValue - pos.width, y: pos.y } })
      })
      ElMessage.success('节点已右对齐')
      break
      
    case 'top':
      // 上对齐：以最上边的节点为基准
      baseValue = Math.min(...positions.map(p => p.y))
      positions.forEach(pos => {
        updateNode(pos.id, { position: { x: pos.x, y: baseValue } })
      })
      ElMessage.success('节点已上对齐')
      break
      
    case 'bottom':
      // 下对齐：以最下边的节点为基准
      baseValue = Math.max(...positions.map(p => p.y + p.height))
      positions.forEach(pos => {
        updateNode(pos.id, { position: { x: pos.x, y: baseValue - pos.height } })
      })
      ElMessage.success('节点已下对齐')
      break
      
    case 'center-h':
      // 水平居中：以所有节点的中心点为基准
      const minX = Math.min(...positions.map(p => p.x))
      const maxX = Math.max(...positions.map(p => p.x + p.width))
      const centerX = (minX + maxX) / 2
      positions.forEach(pos => {
        updateNode(pos.id, { position: { x: centerX - pos.width / 2, y: pos.y } })
      })
      ElMessage.success('节点已水平居中')
      break
      
    case 'center-v':
      // 垂直居中：以所有节点的中心点为基准
      const minY = Math.min(...positions.map(p => p.y))
      const maxY = Math.max(...positions.map(p => p.y + p.height))
      const centerY = (minY + maxY) / 2
      positions.forEach(pos => {
        updateNode(pos.id, { position: { x: pos.x, y: centerY - pos.height / 2 } })
      })
      ElMessage.success('节点已垂直居中')
      break
      
    case 'distribute-h':
      // 水平分布：均匀分布节点
      const sortedByX = [...positions].sort((a, b) => a.x - b.x)
      const totalWidth = sortedByX[sortedByX.length - 1].x + sortedByX[sortedByX.length - 1].width - sortedByX[0].x
      const spacing = totalWidth / (sortedByX.length - 1)
      let currentX = sortedByX[0].x
      sortedByX.forEach((pos, index) => {
        if (index > 0) {
          currentX = sortedByX[index - 1].x + sortedByX[index - 1].width + spacing - pos.width
        }
        updateNode(pos.id, { position: { x: currentX, y: pos.y } })
      })
      ElMessage.success('节点已水平分布')
      break
      
    case 'distribute-v':
      // 垂直分布：均匀分布节点
      const sortedByY = [...positions].sort((a, b) => a.y - b.y)
      const totalHeight = sortedByY[sortedByY.length - 1].y + sortedByY[sortedByY.length - 1].height - sortedByY[0].y
      const vSpacing = totalHeight / (sortedByY.length - 1)
      let currentY = sortedByY[0].y
      sortedByY.forEach((pos, index) => {
        if (index > 0) {
          currentY = sortedByY[index - 1].y + sortedByY[index - 1].height + vSpacing - pos.height
        }
        updateNode(pos.id, { position: { x: pos.x, y: currentY } })
      })
      ElMessage.success('节点已垂直分布')
      break
  }
  
  // 标记有变更
  hasChanges.value = true
}

// 自动布局功能（基于DAG的层次布局算法）
const handleAutoLayout = async () => {
  if (nodes.value.length === 0) {
    ElMessage.warning('画布中没有节点')
    return
  }
  
  // 找到开始节点
  const startNode = nodes.value.find(n => n.type === 'start')
  if (!startNode) {
    ElMessage.warning('未找到开始节点，无法进行自动布局')
    return
  }
  
  // 构建邻接表（有向图）
  const graph: Record<string, string[]> = {}
  const inDegree: Record<string, number> = {}
  
  // 初始化
  nodes.value.forEach(node => {
    graph[node.id] = []
    inDegree[node.id] = 0
  })
  
  // 构建图
  edges.value.forEach(edge => {
    if (graph[edge.source] && !graph[edge.source].includes(edge.target)) {
      graph[edge.source].push(edge.target)
      inDegree[edge.target] = (inDegree[edge.target] || 0) + 1
    }
  })
  
  // 拓扑排序，将节点分层
  const layers: string[][] = []
  const visited = new Set<string>()
  const queue: string[] = []
  
  // 找到所有入度为0的节点（开始节点）
  Object.keys(inDegree).forEach(nodeId => {
    if (inDegree[nodeId] === 0) {
      queue.push(nodeId)
    }
  })
  
  // 如果没有入度为0的节点，使用开始节点
  if (queue.length === 0 && startNode) {
    queue.push(startNode.id)
  }
  
  // 分层遍历
  while (queue.length > 0) {
    const layer: string[] = []
    const layerSize = queue.length
    
    for (let i = 0; i < layerSize; i++) {
      const nodeId = queue.shift()!
      if (visited.has(nodeId)) continue
      
      visited.add(nodeId)
      layer.push(nodeId)
      
      // 处理该节点的所有出边
      const neighbors = graph[nodeId] || []
      neighbors.forEach(neighborId => {
        inDegree[neighborId] = (inDegree[neighborId] || 0) - 1
        if (inDegree[neighborId] === 0 && !visited.has(neighborId)) {
          queue.push(neighborId)
        }
      })
    }
    
    if (layer.length > 0) {
      layers.push(layer)
    }
  }
  
  // 处理未访问的节点（可能是孤立节点）
  nodes.value.forEach(node => {
    if (!visited.has(node.id)) {
      if (layers.length === 0) {
        layers.push([node.id])
      } else {
        layers[layers.length - 1].push(node.id)
      }
    }
  })
  
  // 布局参数
  const nodeWidth = 200
  const nodeHeight = 80
  const horizontalSpacing = 280  // 水平间距（增大间距，避免节点重叠）
  const verticalSpacing = 180    // 垂直间距（增大间距，使布局更清晰）
  const startX = 100
  const startY = 100
  
  // 计算每层的布局（水平居中）
  layers.forEach((layer, layerIndex) => {
    const layerY = startY + layerIndex * verticalSpacing
    const layerWidth = (layer.length - 1) * horizontalSpacing
    const layerStartX = startX - layerWidth / 2
    
    layer.forEach((nodeId, nodeIndex) => {
      const nodeX = layerStartX + nodeIndex * horizontalSpacing
      updateNode(nodeId, {
        position: { x: nodeX, y: layerY }
      })
    })
  })
  
  // 自动调整视口，使所有节点可见
  await nextTick()
  setTimeout(() => {
    try {
      const allNodes = nodes.value
      if (allNodes.length > 0) {
        const minX = Math.min(...allNodes.map(n => n.position.x))
        const maxX = Math.max(...allNodes.map(n => n.position.x + nodeWidth))
        const minY = Math.min(...allNodes.map(n => n.position.y))
        const maxY = Math.max(...allNodes.map(n => n.position.y + nodeHeight))
        
        const centerX = (minX + maxX) / 2
        const centerY = (minY + maxY) / 2
        const width = maxX - minX
        const height = maxY - minY
        
        // 计算合适的缩放比例
        const viewport = getViewport()
        if (viewport) {
          const scale = Math.min(
            (viewport.zoom * 800) / width,
            (viewport.zoom * 600) / height,
            1.2  // 最大缩放不超过1.2
          )
          
          setViewport({
            x: -centerX * scale + 400,
            y: -centerY * scale + 300,
            zoom: scale
          }, { duration: 300 })
        }
      }
    } catch (e) {
      console.warn('自动调整视口失败:', e)
    }
  }, 100)
  
  ElMessage.success(`自动布局完成，共 ${layers.length} 层，${nodes.value.length} 个节点`)
  hasChanges.value = true
}

// 测试动画 - 依次执行所有节点，展示完整的工作流动画效果
const handleTestAnimation = () => {
  // 清除之前的测试状态
  if (testAnimationTimer.value) {
    clearTimeout(testAnimationTimer.value)
    testAnimationTimer.value = null
  }
  
  // 找到第一个开始节点
  const startNode = nodes.value.find(n => n.type === 'start' || n.data?.type === 'start' || n.id.startsWith('start'))
  
  if (!startNode) {
    ElMessage.warning('未找到开始节点，请先添加一个开始节点')
    return
  }
  
  console.log('[rjb] 🧪 开始测试动画，节点ID:', startNode.id)
  
  // 清除所有节点的执行状态
  nodes.value.forEach(node => {
    const nodeClass = (node.class || '').replace(/\b(executing|executed|failed)\b/g, '').trim()
    if (nodeClass !== node.class) {
      updateNode(node.id, { 
        class: nodeClass, 
        data: { ...node.data, executionStatus: undefined, executionClass: undefined }
      })
    }
  })
  
  // 清除所有边的执行状态
  edges.value.forEach(edge => {
    const edgeClass = (edge.class || '').replace(/\b(edge-executing|edge-executed)\b/g, '').trim()
    if (edgeClass !== edge.class) {
      if (updateEdge) {
        updateEdge(edge.id, { 
          class: edgeClass,
          style: {
            ...edge.style,
            stroke: '#409eff',
            strokeWidth: 2.5
          },
          animated: false
        })
      } else {
        edge.class = edgeClass
        edge.animated = false
      }
    }
  })
  
  // 使用广度优先搜索（BFS）找到所有节点的执行顺序
  const getExecutionOrder = (startNodeId: string): string[] => {
    const visited = new Set<string>()
    const order: string[] = []
    const queue: string[] = [startNodeId]
    
    visited.add(startNodeId)
    
    while (queue.length > 0) {
      const currentNodeId = queue.shift()!
      order.push(currentNodeId)
      
      // 找到所有从当前节点出发的边
      const outgoingEdges = edges.value.filter(e => e.source === currentNodeId)
      for (const edge of outgoingEdges) {
        if (!visited.has(edge.target)) {
          visited.add(edge.target)
          queue.push(edge.target)
        }
      }
    }
    
    // 如果还有未访问的节点（可能是孤立的），也加入顺序
    nodes.value.forEach(node => {
      if (!visited.has(node.id)) {
        order.push(node.id)
      }
    })
    
    return order
  }
  
  const executionOrder = getExecutionOrder(startNode.id)
  console.log('[rjb] 🧪 节点执行顺序:', executionOrder)
  
  if (executionOrder.length === 0) {
    ElMessage.warning('未找到可执行的节点')
    return
  }
  
  testingAnimation.value = true
  
  // 依次执行每个节点
  const executeNodeAnimation = (nodeIndex: number) => {
    if (nodeIndex >= executionOrder.length) {
      // 所有节点执行完成
      testingAnimation.value = false
      testAnimationTimer.value = null
      ElMessage.success(`动画测试完成！共执行了 ${executionOrder.length} 个节点`)
      return
    }
    
    const nodeId = executionOrder[nodeIndex]
    const node = nodes.value.find(n => n.id === nodeId)
    
    if (!node) {
      // 如果节点不存在，跳过
      executeNodeAnimation(nodeIndex + 1)
      return
    }
    
    console.log(`[rjb] 🧪 [${nodeIndex + 1}/${executionOrder.length}] 开始执行节点:`, nodeId)
    
    // 第一步：标记为执行中
    const nodeClass = ((node.class || '').replace(/\b(executing|executed|failed)\b/g, '').trim() + ' executing').trim()
    const nodeData = { ...node.data, executionStatus: 'executing', executionClass: 'executing' }
    updateNode(node.id, { 
      class: nodeClass,
      data: nodeData
    })
    console.log('[rjb] 🧪 ✅ 标记节点为执行中:', nodeId)
    
    // 高亮连接到当前节点的边（从上游节点来的边）
    edges.value.forEach(edge => {
      if (edge.target === nodeId) {
        const edgeClass = ((edge.class || '').replace(/\b(edge-executing|edge-executed)\b/g, '').trim() + ' edge-executing').trim()
        try {
          if (updateEdge) {
            updateEdge(edge.id, {
              class: edgeClass,
              style: {
                ...edge.style,
                stroke: '#409eff',
                strokeWidth: 3.5,
                strokeDasharray: '8,4'
              },
              animated: true
            })
          } else {
            edge.class = edgeClass
            edge.style = {
              ...edge.style,
              stroke: '#409eff',
              strokeWidth: 3.5,
              strokeDasharray: '8,4'
            }
            edge.animated = true
          }
        } catch (e) {
          console.warn('[rjb] Failed to update edge:', e)
        }
      }
    })
    
    // 第二步：1.5秒后标记为已完成，然后执行下一个节点
    testAnimationTimer.value = window.setTimeout(() => {
      const executedNodeClass = ((node.class || '').replace(/\b(executing|executed|failed)\b/g, '').trim() + ' executed').trim()
      const executedNodeData = { ...node.data, executionStatus: 'executed', executionClass: 'executed' }
      updateNode(node.id, { 
        class: executedNodeClass,
        data: executedNodeData
      })
      console.log('[rjb] 🧪 ✅ 标记节点为已完成:', nodeId)
      
      // 更新连接到当前节点的边为已完成状态
      edges.value.forEach(edge => {
        if (edge.target === nodeId) {
          const edgeClass = ((edge.class || '').replace(/\b(edge-executing|edge-executed)\b/g, '').trim() + ' edge-executed').trim()
          try {
            if (updateEdge) {
              updateEdge(edge.id, {
                class: edgeClass,
                style: {
                  ...edge.style,
                  stroke: '#67c23a',
                  strokeWidth: 3,
                  strokeDasharray: '0'
                },
                animated: false
              })
            } else {
              edge.class = edgeClass
              edge.style = {
                ...edge.style,
                stroke: '#67c23a',
                strokeWidth: 3,
                strokeDasharray: '0'
              }
              edge.animated = false
            }
          } catch (e) {
            console.warn('[rjb] Failed to update edge:', e)
          }
        }
      })
      
      // 继续执行下一个节点
      executeNodeAnimation(nodeIndex + 1)
    }, 1500) // 每个节点执行1.5秒
  }
  
  // 开始执行第一个节点
  executeNodeAnimation(0)
}

// 监听节点和边的变化，检查是否有变更
watch([nodes, edges], () => {
  if (lastSavedData.value) {
    checkChanges()
  }
}, { deep: true })

// 监听初始数据变化（用于异步加载）
watch(
  () => props.initialNodes,
  async (newNodes) => {
    // 如果数据从空变为有数据，且当前画布为空，则加载
    if (newNodes && newNodes.length > 0 && nodes.value.length === 0) {
      console.log('[WorkflowEditor] 检测到初始节点数据变化，加载节点:', newNodes.length)
      const vueFlowNodes = newNodes.map((node: any) => ({
        id: node.id,
        type: node.type,
        position: node.position || { x: 0, y: 0 },
        data: node.data || { label: node.label || node.type }
      }))
      addNodes(vueFlowNodes)
      await nextTick()
    }
  },
  { immediate: true }
)

watch(
  () => props.initialEdges,
  async (newEdges) => {
    if (newEdges && newEdges.length > 0 && edges.value.length === 0) {
      console.log('[WorkflowEditor] 检测到初始连接数据变化，加载连接:', newEdges.length)
      const vueFlowEdges = newEdges.map((edge: any) => ({
        id: edge.id,
        source: edge.source,
        target: edge.target,
        sourceHandle: edge.sourceHandle,
        targetHandle: edge.targetHandle,
        selectable: true,
        deletable: true,
        focusable: true,
        type: edge.type || 'bezier',
        animated: true,
        style: {
          stroke: '#409eff',
          strokeWidth: 2.5,
          strokeDasharray: '0'
        },
        markerEnd: {
          type: 'arrowclosed',
          color: '#409eff',
          width: 20,
          height: 20
        }
      }))
      addEdges(vueFlowEdges)
    }
  },
  { immediate: true }
)

// 监听执行状态变化，更新节点样式
watch(() => props.executionStatus, (newStatus, oldStatus) => {
  console.log('[rjb] WorkflowEditor received executionStatus:', JSON.stringify(newStatus, null, 2))
  console.log('[rjb] Current nodes:', nodes.value.map(n => ({ id: n.id, type: n.type, class: n.class })))
  console.log('[rjb] All node IDs:', nodes.value.map(n => n.id))
  
  // 提取execution_id
  if (newStatus && newStatus.execution_id) {
    currentExecutionId.value = newStatus.execution_id
  }
  
  // 使用 nextTick 确保 DOM 更新
  nextTick(() => {
    // 清除所有节点的执行状态
    nodes.value.forEach(node => {
      if (node.data) {
        delete node.data.executionStatus
        delete node.data.executionClass
      }
      // 同时清除 class
      let nodeClass = (node.class || '').replace(/\b(executing|executed|failed)\b/g, '').trim()
      if (nodeClass !== node.class) {
        updateNode(node.id, { class: nodeClass, data: { ...node.data } })
      }
    })
    
    // 如果没有执行状态，直接返回
    if (!newStatus) {
      console.log('[rjb] No execution status, clearing all node states')
      return
    }
    
    const { current_node, executed_nodes, failed_nodes } = newStatus
    console.log('[rjb] Execution status - current:', current_node, 'executed:', executed_nodes, 'failed:', failed_nodes)
    console.log('[rjb] Execution status full:', JSON.stringify(newStatus, null, 2))
    
    // 清除所有边的执行状态
    edges.value.forEach(edge => {
      if (edge.data) {
        delete edge.data.executionStatus
      }
      if (edge.class) {
        edge.class = edge.class.replace(/\b(edge-executing|edge-executed)\b/g, '').trim()
      }
    })
    
    // 更新正在执行的节点
    if (current_node && current_node.node_id) {
      const node = nodes.value.find(n => n.id === current_node.node_id)
      if (node) {
        const nodeClass = ((node.class || '').replace(/\b(executing|executed|failed)\b/g, '').trim() + ' executing').trim()
        const nodeData = { ...node.data, executionStatus: 'executing', executionClass: 'executing' }
        updateNode(node.id, { 
          class: nodeClass,
          data: nodeData
        })
        console.log('[rjb] ✅ Marking node as executing:', current_node.node_id, 'class:', nodeClass, 'nodeData:', nodeData)
        
        // 高亮连接到正在执行节点的边
        edges.value.forEach(edge => {
          if (edge.target === current_node.node_id) {
            const edgeClass = ((edge.class || '').replace(/\b(edge-executing|edge-executed)\b/g, '').trim() + ' edge-executing').trim()
            try {
              if (updateEdge) {
                updateEdge(edge.id, {
                  class: edgeClass,
                  style: {
                    ...edge.style,
                    stroke: '#409eff',
                    strokeWidth: 3.5,
                    strokeDasharray: '8,4'
                  },
                  animated: true
                })
              } else {
                // 如果updateEdge不存在，直接修改（兼容旧版本）
                edge.class = edgeClass
                edge.style = {
                  ...edge.style,
                  stroke: '#409eff',
                  strokeWidth: 3.5,
                  strokeDasharray: '8,4'
                }
                edge.animated = true
              }
            } catch (e) {
              console.warn('[rjb] Failed to update edge:', e)
              // 直接修改作为后备方案
              edge.class = edgeClass
              edge.style = {
                ...edge.style,
                stroke: '#409eff',
                strokeWidth: 3.5,
                strokeDasharray: '8,4'
              }
              edge.animated = true
            }
          }
        })
      } else {
        console.warn('[rjb] ❌ Node not found:', current_node.node_id, 'available nodes:', nodes.value.map(n => n.id))
      }
    }
    
    // 更新已完成的节点
    if (executed_nodes && Array.isArray(executed_nodes)) {
      executed_nodes.forEach((executedNode: any) => {
        if (executedNode.node_id) {
          const node = nodes.value.find(n => n.id === executedNode.node_id)
          if (node) {
            const nodeClass = ((node.class || '').replace(/\b(executing|executed|failed)\b/g, '').trim() + ' executed').trim()
            const nodeData = { ...node.data, executionStatus: 'executed', executionClass: 'executed' }
            updateNode(node.id, { 
              class: nodeClass,
              data: nodeData
            })
            console.log('[rjb] ✅ Marking node as executed:', executedNode.node_id, 'class:', nodeClass)
            
            // 高亮连接到已执行节点的边
            edges.value.forEach(edge => {
              if (edge.target === executedNode.node_id) {
                const edgeClass = ((edge.class || '').replace(/\b(edge-executing|edge-executed)\b/g, '').trim() + ' edge-executed').trim()
                try {
                  updateEdge(edge.id, {
                    class: edgeClass,
                    style: {
                      ...edge.style,
                      stroke: '#67c23a',
                      strokeWidth: 3,
                      strokeDasharray: '0'
                    },
                    animated: false
                  })
                } catch (e) {
                  console.warn('[rjb] Failed to update edge:', e)
                  // 如果updateEdge不存在，直接修改（兼容旧版本）
                  edge.class = edgeClass
                  edge.style = {
                    ...edge.style,
                    stroke: '#67c23a',
                    strokeWidth: 3,
                    strokeDasharray: '0'
                  }
                  edge.animated = false
                }
              }
            })
          } else {
            console.warn('[rjb] ❌ Executed node not found:', executedNode.node_id)
          }
        }
      })
    }
    
    // 更新失败的节点（包含错误信息）
    if (failed_nodes && Array.isArray(failed_nodes)) {
      failed_nodes.forEach((failedNode: any) => {
        if (failedNode.node_id) {
          const node = nodes.value.find(n => n.id === failedNode.node_id)
          if (node) {
            const nodeClass = ((node.class || '').replace(/\b(executing|executed|failed)\b/g, '').trim() + ' failed').trim()
            const nodeData = { 
              ...node.data, 
              executionStatus: 'failed', 
              executionClass: 'failed',
              errorMessage: failedNode.error_message || '执行失败',
              errorType: failedNode.error_type
            }
            updateNode(node.id, { 
              class: nodeClass,
              data: nodeData
            })
            console.log('[rjb] ✅ Marking node as failed:', failedNode.node_id, 'error:', failedNode.error_message)
          } else {
            console.warn('[rjb] ❌ Failed node not found:', failedNode.node_id)
          }
        }
      })
    }
  })
}, { deep: true, immediate: true })

// 加载工作流数据
onMounted(async () => {
  // 注册键盘事件监听
  window.addEventListener('keydown', handleKeyDown)
  
  // 启用自动保存
  enableAutoSave()
  
  // 加载节点模板列表
  loadNodeTemplates()
  // 加载测试用例
  loadTestCases()
  // 加载配置模板
  loadTemplatesFromStorage()
  // 加载工具列表
  loadTools()
  
  // 初始化视口
  const viewport = getViewport()
  if (viewport) {
    currentZoom.value = viewport.zoom
  }
  
  // 加载初始数据的函数
  const loadInitialData = async () => {
    // 如果已经有节点，不重复加载
    if (nodes.value.length > 0) {
      console.log('[WorkflowEditor] 节点已存在，跳过加载')
      return
    }
    
    // 加载节点
    if (props.initialNodes && props.initialNodes.length > 0) {
      console.log('[WorkflowEditor] 加载初始节点:', props.initialNodes.length, '个节点', props.initialNodes)
      const vueFlowNodes = props.initialNodes.map((node: any) => ({
        id: node.id,
        type: node.type,
        position: node.position || { x: 0, y: 0 },
        data: node.data || { label: node.label || node.type }
      }))
      addNodes(vueFlowNodes)
      console.log('[WorkflowEditor] 已添加节点到画布:', vueFlowNodes.length)
    }
    
    // 等待节点添加完成后再添加连接
    await nextTick()
    
    // 加载连接
    if (props.initialEdges && props.initialEdges.length > 0) {
      console.log('[WorkflowEditor] 加载初始连接:', props.initialEdges.length, '个连接', props.initialEdges)
      const vueFlowEdges = props.initialEdges.map((edge: any) => ({
        id: edge.id,
        source: edge.source,
        target: edge.target,
        sourceHandle: edge.sourceHandle,
        targetHandle: edge.targetHandle,
        selectable: true,
        deletable: true,
        focusable: true,
        type: edge.type || 'bezier',
        animated: true,
        style: {
          stroke: '#409eff',
          strokeWidth: 2.5,
          strokeDasharray: '0'
        },
        markerEnd: {
          type: 'arrowclosed',
          color: '#409eff',
          width: 20,
          height: 20
        }
      }))
      addEdges(vueFlowEdges)
      console.log('[WorkflowEditor] 已添加连接到画布:', vueFlowEdges.length)
    }
    
    // 保存初始状态
    await nextTick()
    if (props.initialNodes && props.initialNodes.length > 0) {
      const workflowData = {
        nodes: nodes.value,
        edges: edges.value
      }
      lastSavedData.value = JSON.stringify(workflowData)
      hasChanges.value = false
    }
  }
  
  // 立即尝试加载
  await loadInitialData()
  
  // 如果提供了 workflowId，从后端加载工作流数据
  if (props.workflowId) {
    try {
      const workflow = await workflowStore.fetchWorkflow(props.workflowId)
      if (workflow) {
        // 转换工作流数据为Vue Flow格式
        const vueFlowNodes = workflow.nodes.map((node: any) => ({
          id: node.id,
          type: node.type,
          position: node.position,
          data: node.data
        }))
        const vueFlowEdges = workflow.edges.map((edge: any) => ({
          id: edge.id,
          source: edge.source,
          target: edge.target,
          sourceHandle: edge.sourceHandle,
          targetHandle: edge.targetHandle,
          type: edge.type || 'bezier',
          animated: true,
          selectable: true,
          deletable: true,
          focusable: true,
          style: {
            stroke: '#409eff',
            strokeWidth: 2.5,
            strokeDasharray: '0'
          },
          markerEnd: {
            type: 'arrowclosed',
            color: '#409eff',
            width: 20,
            height: 20
          }
        }))
        
        addNodes(vueFlowNodes)
        addEdges(vueFlowEdges)
        
        // 保存初始状态
        await nextTick()
        const workflowData = {
          nodes: vueFlowNodes,
          edges: vueFlowEdges
        }
        lastSavedData.value = JSON.stringify(workflowData)
        hasChanges.value = false
      }
    } catch (error) {
      console.error('加载工作流失败:', error)
    }
    
    // 建立协作连接
    if (collaboration) {
      try {
        collaboration.connect()
        
        // 监听协作状态
        watch(() => collaboration?.connected, (connected) => {
          collaborationConnected.value = connected || false
        }, { immediate: true })
        
        // 监听在线用户
        watch(() => collaboration?.onlineUsers, (users) => {
          onlineUsers.value = users || []
        }, { immediate: true, deep: true })
        
        // 监听当前用户
        watch(() => collaboration?.currentUser, (user) => {
          currentUser.value = user || null
        }, { immediate: true })
        
        // 监听远程操作
        collaboration.onOperation((operation) => {
          // 只处理其他用户的操作
          if (operation.user_id !== currentUser.value?.user_id) {
            handleRemoteOperation(operation)
          }
        })
      } catch (error) {
        console.error('建立协作连接失败:', error)
        ElMessage.warning('协作功能不可用')
      }
    }
  }
})

onUnmounted(() => {
  // 清理键盘事件监听
  window.removeEventListener('keydown', handleKeyDown)
  // 禁用自动保存
  disableAutoSave()
  
  // 清理测试动画定时器
  if (testAnimationTimer.value) {
    clearTimeout(testAnimationTimer.value)
    testAnimationTimer.value = null
  }
  
  // 断开协作连接
  if (collaboration) {
    collaboration.disconnect()
  }
  
  // 如果有未保存的更改，提示用户
  if (hasChanges.value) {
    ElMessageBox.confirm(
      '工作流有未保存的更改，确定要离开吗？',
      '提示',
      {
        confirmButtonText: '离开',
        cancelButtonText: '取消',
        type: 'warning'
      }
    ).catch(() => {
      // 用户取消，不做任何操作
    })
  }
})
</script>

<style scoped>
.workflow-editor {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.editor-toolbar {
  padding: 8px 12px;
  background: #f5f5f5;
  border-bottom: 1px solid #ddd;
  display: flex;
  gap: 10px;
  flex-shrink: 0;
}

.editor-container {
  flex: 1;
  display: flex;
  overflow: hidden;
  min-height: 0;
}

.node-toolbox {
  width: 220px;
  min-width: 220px;
  background: #fff;
  border-right: 1px solid #ddd;
  padding: 10px;
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
}

.node-toolbox h3 {
  margin: 0 0 10px 0;
  font-size: 14px;
  font-weight: 600;
  color: #333;
}

.node-search {
  margin-bottom: 10px;
}

.node-filters {
  margin-bottom: 10px;
}

.node-filters :deep(.el-radio-group) {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.node-filters :deep(.el-radio-button__inner) {
  padding: 4px 8px;
  font-size: 12px;
}

.node-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
  min-height: 0;
}

.node-item {
  padding: 8px 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  cursor: move;
  display: flex;
  align-items: center;
  gap: 8px;
  transition: all 0.2s;
  font-size: 13px;
  user-select: none;
  -webkit-user-drag: element;
}

.node-item:hover {
  background: #f0f0f0;
  border-color: #409eff;
  transform: translateX(2px);
}

.node-item:active {
  opacity: 0.7;
}

.empty-nodes {
  padding: 20px 0;
  text-align: center;
}

/* 画布节点搜索区域 */
.canvas-node-search {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid #e4e7ed;
}

.canvas-node-search .search-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}

.canvas-node-search h4 {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: #303133;
}

.canvas-node-list {
  max-height: 200px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.canvas-node-item {
  padding: 6px 8px;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: all 0.2s;
  font-size: 12px;
}

.canvas-node-item:hover {
  background: #f5f7fa;
  border-color: #409eff;
}

.canvas-node-item.is-selected {
  background: #ecf5ff;
  border-color: #409eff;
  font-weight: 500;
}

.canvas-node-item .node-name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.empty-canvas-nodes {
  padding: 10px 0;
  text-align: center;
}

.editor-canvas {
  flex: 1;
  position: relative;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

.editor-canvas :deep(.vue-flow) {
  width: 100%;
  height: 100%;
}

.editor-canvas :deep(.vue-flow__viewport) {
  width: 100%;
  height: 100%;
}

.editor-canvas :deep(.vue-flow__container) {
  width: 100%;
  height: 100%;
}

.config-panel {
  width: 420px;
  min-width: 380px;
  background: #fff;
  border-left: 1px solid #ddd;
  padding: 15px 20px 15px 15px; /* 右侧预留滚动条空间，避免按钮被遮挡 */
  overflow-y: auto;
  overflow-x: hidden;
  flex-shrink: 0;
  box-sizing: border-box;
}

.config-panel h3 {
  margin: 0 0 15px 0;
  font-size: 16px;
}

.node-test-section {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid #e4e7ed;
}

.test-input-hint {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 4px;
  font-size: 12px;
  color: #909399;
}

.test-result-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
  padding: 8px;
  background: #f5f7fa;
  border-radius: 4px;
}

.test-status {
  font-weight: 500;
  font-size: 13px;
}

.test-status.success {
  color: #67c23a;
}

.test-status.error {
  color: #f56c6c;
}

.test-time {
  font-size: 12px;
  color: #909399;
}

/* 数据流相关样式 */
.dataflow-card {
  margin-bottom: 15px;
}

.upstream-item {
  margin-bottom: 15px;
  padding: 12px;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  transition: all 0.2s;
}

.upstream-item:hover {
  border-color: #409eff;
  background: #f5f7fa;
}

.variable-suggestions {
  padding: 8px 0;
}

.var-group {
  margin-bottom: 12px;
}

.var-group:last-child {
  margin-bottom: 0;
}

.group-title {
  font-size: 12px;
  font-weight: 500;
  color: #606266;
  margin-bottom: 8px;
}

.var-items {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.var-tag {
  cursor: pointer;
  transition: all 0.2s;
}

.var-tag:hover {
  transform: scale(1.05);
}

.output-field {
  padding: 8px;
  margin-bottom: 8px;
  background: #f5f7fa;
  border-radius: 4px;
}

.downstream-section {
  margin-top: 15px;
  padding-top: 15px;
  border-top: 1px solid #e4e7ed;
}

.downstream-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px;
  margin-bottom: 4px;
}

.empty-state {
  padding: 20px;
  text-align: center;
}

/* 变量自动补全样式 */
.prompt-input-wrapper {
  position: relative;
}

.variable-autocomplete-dropdown {
  position: fixed;
  z-index: 3000;
  background: #fff;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  max-height: 300px;
  overflow-y: auto;
  min-width: 300px;
  max-width: 500px;
}

.autocomplete-item {
  padding: 8px 12px;
  cursor: pointer;
  border-bottom: 1px solid #f5f7fa;
  transition: background-color 0.2s;
}

.autocomplete-item:last-child {
  border-bottom: none;
}

.autocomplete-item:hover,
.autocomplete-item.is-active {
  background-color: #f5f7fa;
}

.var-item-name {
  display: flex;
  align-items: center;
  font-size: 13px;
  font-weight: 500;
  color: #303133;
  margin-bottom: 4px;
}

.var-item-desc {
  font-size: 12px;
  color: #909399;
  line-height: 1.4;
}

.autocomplete-empty {
  padding: 12px;
  text-align: center;
  color: #909399;
  font-size: 12px;
}

/* 配置助手样式 */
.config-wizard {
  padding: 10px 0;
}

.scenario-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.scenario-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s;
}

.scenario-item:hover {
  border-color: #409eff;
  background: #f5f7fa;
}

.scenario-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: #ecf5ff;
  border-radius: 4px;
  color: #409eff;
}

.scenario-info {
  flex: 1;
}

.scenario-name {
  font-weight: 500;
  color: #303133;
  margin-bottom: 4px;
}

.scenario-desc {
  font-size: 12px;
  color: #909399;
}

.template-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.template-item {
  padding: 12px;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  transition: all 0.2s;
}

.template-item.is-favorite {
  border-color: #f7ba2a;
  background: #fef9e6;
}

.template-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}

.template-title {
  display: flex;
  align-items: center;
}

.template-name {
  font-weight: 500;
  color: #303133;
}

.template-desc {
  font-size: 12px;
  color: #909399;
  margin-bottom: 8px;
}

.template-preview {
  margin: 8px 0;
  padding: 8px;
  background: #f5f7fa;
  border-radius: 4px;
  max-height: 100px;
  overflow: hidden;
}

.template-preview pre {
  margin: 0;
  font-size: 11px;
  line-height: 1.4;
  color: #606266;
}

.template-footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 8px;
}

.wizard-content {
  padding: 10px 0;
}

.wizard-content .scenario-list {
  max-height: 400px;
  overflow-y: auto;
}

.test-error {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 8px;
  padding: 8px;
  background: #fef0f0;
  border: 1px solid #fde2e2;
  border-radius: 4px;
  color: #f56c6c;
  font-size: 12px;
}

/* 节点执行状态动画 */
@keyframes pulse {
  0%, 100% {
    opacity: 1;
    box-shadow: 0 0 15px rgba(64, 158, 255, 0.8);
  }
  50% {
    opacity: 0.8;
    box-shadow: 0 0 25px rgba(64, 158, 255, 1);
  }
}

.custom-node {
  padding: 8px 16px;
  border-radius: 6px;
  text-align: center;
  font-size: 13px;
  min-width: 100px;
  border: 2px solid transparent;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  transition: all 0.3s ease-in-out;
}


.start-node {
  background: #67c23a;
  color: white;
}

.llm-node {
  background: #409eff;
  color: white;
}

.condition-node {
  background: #e6a23c;
  color: white;
}

.end-node {
  background: #f56c6c;
  color: white;
}

.collaboration-users {
  display: flex;
  align-items: center;
}

.online-users-list {
  padding: 8px 0;
}

.user-item {
  display: flex;
  align-items: center;
  padding: 6px 0;
  gap: 8px;
}

.user-color {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  display: inline-block;
}
</style>

<style>
/* 节点执行状态样式 - 全局样式，确保能应用到 Vue Flow 节点 */
.custom-node.executing {
  border: 3px solid #409eff !important;
  box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.5), 0 0 20px rgba(64, 158, 255, 0.8) !important;
  animation: pulse-blue 1.5s infinite !important;
  transform: scale(1.05) !important;
  z-index: 3000 !important; /* Element Plus 模态框 z-index 通常是 2000-2100，我们设置更高 */
  position: relative !important;
}

.custom-node.executed,
.vue-flow__node.executed .custom-node,
.vue-flow__node .custom-node.executed {
  border: 3px solid #67c23a !important;
  box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.5), 0 2px 8px rgba(103, 194, 58, 0.3) !important;
  z-index: 2999 !important; /* 比执行中的节点稍低，但仍在模态框之上 */
  position: relative !important;
}

.custom-node.failed,
.vue-flow__node.failed .custom-node,
.vue-flow__node .custom-node.failed {
  border: 3px solid #f56c6c !important;
  box-shadow: 0 0 0 3px rgba(245, 108, 108, 0.5), 0 2px 8px rgba(245, 108, 108, 0.3) !important;
  z-index: 2999 !important; /* 比执行中的节点稍低，但仍在模态框之上 */
  position: relative !important;
}

/* 确保 Vue Flow 节点容器也能应用样式 */
.vue-flow__node .custom-node.executing,
.vue-flow__node .custom-node.executed,
.vue-flow__node .custom-node.failed {
  border: 3px solid !important;
  box-shadow: 0 0 0 3px !important;
}

.vue-flow__node .custom-node.executing {
  border-color: #409eff !important;
  box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.5), 0 0 20px rgba(64, 158, 255, 0.8) !important;
  animation: pulse-blue 1.5s infinite, node-pulse 2s ease-in-out infinite !important;
  transform: scale(1.05) !important;
  z-index: 3000 !important;
  position: relative !important;
}

.vue-flow__node .custom-node.executed {
  border-color: #67c23a !important;
  box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.5), 0 2px 8px rgba(103, 194, 58, 0.3) !important;
  z-index: 2999 !important;
  position: relative !important;
}

.vue-flow__node .custom-node.failed {
  border-color: #f56c6c !important;
  box-shadow: 0 0 0 3px rgba(245, 108, 108, 0.5), 0 2px 8px rgba(245, 108, 108, 0.3) !important;
  z-index: 2999 !important;
  position: relative !important;
}

@keyframes pulse-blue {
  0% {
    box-shadow: 0 0 0 0 rgba(64, 158, 255, 0.5), 0 0 20px rgba(64, 158, 255, 0.8);
  }
  70% {
    box-shadow: 0 0 0 10px rgba(64, 158, 255, 0), 0 0 30px rgba(64, 158, 255, 1);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(64, 158, 255, 0), 0 0 20px rgba(64, 158, 255, 0.8);
  }
}

/* 旋转动画 - 用于加载指示器 */
@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

/* 增强执行状态的动画效果 */
.custom-node.executing {
  animation: pulse-blue 1.5s infinite, node-pulse 2s ease-in-out infinite !important;
}

@keyframes node-pulse {
  0%, 100% {
    transform: scale(1.05);
  }
  50% {
    transform: scale(1.08);
  }
}

/* 执行成功时的闪烁效果 */
.custom-node.executed {
  animation: success-flash 0.5s ease-out !important;
}

@keyframes success-flash {
  0% {
    box-shadow: 0 0 0 0 rgba(103, 194, 58, 0.5), 0 0 20px rgba(103, 194, 58, 0.8);
  }
  50% {
    box-shadow: 0 0 0 8px rgba(103, 194, 58, 0.3), 0 0 30px rgba(103, 194, 58, 1);
  }
  100% {
    box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.5), 0 2px 8px rgba(103, 194, 58, 0.3);
  }
}

/* 执行失败时的闪烁效果 */
.custom-node.failed {
  animation: error-shake 0.5s ease-out !important;
}

@keyframes error-shake {
  0%, 100% {
    transform: translateX(0);
  }
  10%, 30%, 50%, 70%, 90% {
    transform: translateX(-3px);
  }
  20%, 40%, 60%, 80% {
    transform: translateX(3px);
  }
}

/* 边的执行状态样式 */
.vue-flow__edge.edge-executing .vue-flow__edge-path {
  stroke: #409eff !important;
  stroke-width: 3.5 !important;
  stroke-dasharray: 8,4 !important;
  animation: edge-flow 1.5s linear infinite !important;
}

.vue-flow__edge.edge-executed .vue-flow__edge-path {
  stroke: #67c23a !important;
  stroke-width: 3 !important;
}

@keyframes edge-flow {
  0% {
    stroke-dashoffset: 0;
  }
  100% {
    stroke-dashoffset: 12;
  }
}

/* 全局样式 - 优化边的选中效果（类似 Dify） */
.vue-flow__edge.selected .vue-flow__edge-path {
  stroke: #67c23a !important;
  stroke-width: 3.5 !important;
}

.vue-flow__edge.selected .vue-flow__edge-text {
  fill: #67c23a !important;
}

.vue-flow__edge:hover .vue-flow__edge-path {
  stroke-width: 3 !important;
  cursor: pointer;
}

/* 连接点样式优化（类似 Dify） */
.vue-flow__handle {
  width: 10px !important;
  height: 10px !important;
  border: 2px solid #fff !important;
  background: #409eff !important;
  transition: all 0.2s ease !important;
}

.vue-flow__handle:hover {
  width: 14px !important;
  height: 14px !important;
  background: #67c23a !important;
  box-shadow: 0 0 0 4px rgba(64, 158, 255, 0.2) !important;
}

.vue-flow__handle-connecting {
  background: #67c23a !important;
  box-shadow: 0 0 0 4px rgba(103, 194, 58, 0.3) !important;
}

.vue-flow__handle-valid {
  background: #67c23a !important;
}

.vue-flow__connection-line {
  stroke: #409eff !important;
  stroke-width: 2.5 !important;
  stroke-dasharray: 5,5 !important;
}

/* 工作流模板对话框样式 */
.template-list {
  max-height: 500px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.template-item {
  padding: 15px;
  border: 1px solid #e4e7ed;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.3s;
  background: #fff;
}

.template-item:hover {
  border-color: #409eff;
  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
  transform: translateY(-2px);
}

.template-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}

.template-header h4 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: #303133;
}

.template-description {
  color: #606266;
  font-size: 14px;
  line-height: 1.5;
  margin-bottom: 10px;
}

.template-meta {
  display: flex;
  gap: 15px;
  font-size: 12px;
  color: #909399;
}

.template-meta span {
  display: flex;
  align-items: center;
}

.empty-templates {
  padding: 40px 0;
  text-align: center;
}

/* 节点测试区域样式 */
.test-input-toolbar {
  display: flex;
  align-items: center;
  margin-bottom: 8px;
  gap: 8px;
}

.test-input-hint {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: 12px;
  color: #909399;
}

.test-input-hint .error-text {
  color: #f56c6c;
  margin-left: 8px;
}

.input-error textarea {
  border-color: #f56c6c !important;
}

.test-result-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
  padding: 8px 12px;
  background: #f5f7fa;
  border-radius: 4px;
}

.test-status-group {
  display: flex;
  align-items: center;
  gap: 12px;
}

.test-status {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 600;
  font-size: 14px;
}

.test-status.success {
  color: #67c23a;
}

.test-status.error {
  color: #f56c6c;
}

.test-time {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: #909399;
}

.test-result-actions {
  display: flex;
  gap: 8px;
}

.test-result-content {
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  overflow: hidden;
  margin-top: 8px;
}

.json-viewer {
  margin: 0;
  padding: 12px;
  background: #fafafa;
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
  font-size: 13px;
  line-height: 1.6;
  color: #303133;
  overflow-x: auto;
  white-space: pre;
}

.test-error-detail {
  padding: 12px;
}

.error-trace {
  margin-top: 12px;
  font-size: 12px;
}

.error-trace details {
  cursor: pointer;
}

.error-trace summary {
  margin-bottom: 8px;
  color: #606266;
  font-weight: 500;
}

.error-trace pre {
  margin: 8px 0 0 0;
  padding: 8px;
  background: #f5f7fa;
  border-radius: 4px;
  font-size: 11px;
  color: #f56c6c;
  overflow-x: auto;
}

.test-stats {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  flex-wrap: wrap;
}

/* 配置按钮布局 */
.config-actions {
  display: flex;
  gap: 8px;
  width: 100%;
  flex-wrap: wrap;
}

.config-actions .el-button {
  flex: 1 1 0;
  min-width: 100px;
}

/* 快速模板 & 变量插入 */
.quick-actions {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 12px;
  margin-bottom: 16px;
  background: #f8f9fb;
  border: 1px solid #ebeef5;
  border-radius: 8px;
}

.quick-item {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.quick-label {
  font-weight: 600;
  color: #303133;
}

.test-case-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.swimlane-overlay {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.lane-config {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: 8px;
}
</style>
