File size: 7,145 Bytes
9eb1c55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
'use client';

import { useState } from 'react';

export default function NodeLibrary() {
  const [activeCategory, setActiveCategory] = useState('trigger');
  const [searchTerm, setSearchTerm] = useState('');

  const nodeCategories = [
    { id: 'trigger', name: '触发器', icon: 'ri-play-line' },
    { id: 'action', name: '动作', icon: 'ri-tools-line' },
    { id: 'condition', name: '条件', icon: 'ri-question-line' },
    { id: 'data', name: '数据', icon: 'ri-database-line' },
    { id: 'ai', name: 'AI', icon: 'ri-brain-line' },
    { id: 'integration', name: '集成', icon: 'ri-links-line' }
  ];

  const nodes = {
    trigger: [
      { id: 'schedule', name: '定时触发', icon: '⏰', desc: '按时间表执行' },
      { id: 'webhook', name: 'Webhook', icon: '🔗', desc: 'HTTP请求触发' },
      { id: 'file-upload', name: '文件上传', icon: '📁', desc: '文件上传时触发' },
      { id: 'email', name: '邮件触发', icon: '📧', desc: '收到邮件时触发' },
      { id: 'database', name: '数据库变更', icon: '🗄️', desc: '数据变更时触发' }
    ],
    action: [
      { id: 'http-request', name: 'HTTP请求', icon: '🌐', desc: '发送HTTP请求' },
      { id: 'send-email', name: '发送邮件', icon: '📮', desc: '发送电子邮件' },
      { id: 'file-operation', name: '文件操作', icon: '📄', desc: '文件增删改查' },
      { id: 'notification', name: '消息通知', icon: '🔔', desc: '发送通知消息' },
      { id: 'data-transform', name: '数据转换', icon: '🔄', desc: '转换数据格式' }
    ],
    condition: [
      { id: 'if-else', name: '条件判断', icon: '🤔', desc: '根据条件分支' },
      { id: 'filter', name: '数据过滤', icon: '🔍', desc: '过滤数据项' },
      { id: 'loop', name: '循环处理', icon: '🔁', desc: '循环执行操作' },
      { id: 'delay', name: '延时等待', icon: '⏱️', desc: '延时一段时间' },
      { id: 'merge', name: '数据合并', icon: '🔀', desc: '合并多个数据源' }
    ],
    data: [
      { id: 'mysql', name: 'MySQL', icon: '🐬', desc: 'MySQL数据库' },
      { id: 'mongodb', name: 'MongoDB', icon: '🍃', desc: 'MongoDB数据库' },
      { id: 'redis', name: 'Redis', icon: '📦', desc: 'Redis缓存' },
      { id: 'csv', name: 'CSV文件', icon: '📊', desc: 'CSV格式数据' },
      { id: 'json', name: 'JSON数据', icon: '📋', desc: 'JSON格式数据' }
    ],
    ai: [
      { id: 'text-generation', name: '文本生成', icon: '✍️', desc: 'AI文本生成' },
      { id: 'text-analysis', name: '文本分析', icon: '🔎', desc: '文本内容分析' },
      { id: 'translation', name: '文本翻译', icon: '🌍', desc: '多语言翻译' },
      { id: 'sentiment', name: '情感分析', icon: '😊', desc: '情感倾向分析' },
      { id: 'ocr', name: '图片识别', icon: '👁️', desc: 'OCR文字识别' }
    ],
    integration: [
      { id: 'slack', name: 'Slack', icon: '💬', desc: 'Slack消息推送' },
      { id: 'wechat', name: '微信', icon: '💚', desc: '微信消息推送' },
      { id: 'dingtalk', name: '钉钉', icon: '📱', desc: '钉钉消息推送' },
      { id: 'github', name: 'GitHub', icon: '🐙', desc: 'GitHub集成' },
      { id: 'google-sheets', name: 'Google Sheets', icon: '📈', desc: 'Google表格' }
    ]
  };

  const handleDragStart = (e: React.DragEvent, node: any) => {
    e.dataTransfer.setData('application/json', JSON.stringify({
      type: 'node',
      nodeType: node.id,
      ...node
    }));
  };

  const filteredNodes = searchTerm 
    ? nodes[activeCategory as keyof typeof nodes]?.filter(node => 
        node.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
        node.desc.toLowerCase().includes(searchTerm.toLowerCase())
      )
    : nodes[activeCategory as keyof typeof nodes];

  return (
    <div className="h-full flex flex-col">
      {/* 搜索框 */}
      <div className="p-4 border-b border-gray-200">
        <div className="relative">
          <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
            <div className="w-4 h-4 flex items-center justify-center">
              <i className="ri-search-line text-gray-400"></i>
            </div>
          </div>
          <input
            type="text"
            placeholder="搜索节点..."
            value={searchTerm}
            onChange={(e) => setSearchTerm(e.target.value)}
            className="pl-10 pr-4 py-2 w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
          />
        </div>
      </div>

      {/* 分类标签 */}
      <div className="p-4 border-b border-gray-200">
        <div className="grid grid-cols-2 gap-2">
          {nodeCategories.map((category) => (
            <button
              key={category.id}
              onClick={() => setActiveCategory(category.id)}
              className={`p-2 rounded-lg text-sm font-medium transition-colors cursor-pointer ${
                activeCategory === category.id
                  ? 'bg-blue-100 text-blue-700'
                  : 'text-gray-600 hover:bg-gray-100'
              }`}
            >
              <div className="flex items-center space-x-2">
                <div className="w-4 h-4 flex items-center justify-center">
                  <i className={category.icon}></i>
                </div>
                <span>{category.name}</span>
              </div>
            </button>
          ))}
        </div>
      </div>

      {/* 节点列表 */}
      <div className="flex-1 overflow-y-auto p-4">
        <div className="space-y-2">
          {filteredNodes?.map((node) => (
            <div
              key={node.id}
              draggable
              onDragStart={(e) => handleDragStart(e, node)}
              className="p-3 bg-white border border-gray-200 rounded-lg hover:shadow-md transition-all cursor-move"
            >
              <div className="flex items-start space-x-3">
                <div className="w-8 h-8 bg-gray-100 rounded-lg flex items-center justify-center flex-shrink-0">
                  <span className="text-sm">{node.icon}</span>
                </div>
                <div className="flex-1 min-w-0">
                  <h4 className="font-medium text-gray-900 text-sm">{node.name}</h4>
                  <p className="text-xs text-gray-500 mt-1 line-clamp-2">{node.desc}</p>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* 模板快捷入口 */}
      <div className="p-4 border-t border-gray-200">
        <button className="w-full p-3 bg-blue-50 text-blue-700 rounded-lg hover:bg-blue-100 transition-colors cursor-pointer">
          <div className="flex items-center justify-center space-x-2">
            <div className="w-4 h-4 flex items-center justify-center">
              <i className="ri-template-line"></i>
            </div>
            <span className="text-sm font-medium">使用模板</span>
          </div>
        </button>
      </div>
    </div>
  );
}