daios007 commited on
Commit
9eb1c55
·
1 Parent(s): ab13230
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .DS_Store +0 -0
  2. README.md +30 -12
  3. app/agents/AgentFilters.tsx +80 -0
  4. app/agents/AgentList.tsx +204 -0
  5. app/agents/create/AgentBuilder.tsx +150 -0
  6. app/agents/create/BasicSettings.tsx +168 -0
  7. app/agents/create/KnowledgeBase.tsx +511 -0
  8. app/agents/create/ModelConfig.tsx +111 -0
  9. app/agents/create/TestPanel.tsx +194 -0
  10. app/agents/create/WorkflowConfig.tsx +313 -0
  11. app/agents/create/page.tsx +27 -0
  12. app/agents/page.tsx +40 -0
  13. app/dashboard/ActivityFeed.tsx +67 -0
  14. app/dashboard/DashboardStats.tsx +63 -0
  15. app/dashboard/QuickActions.tsx +64 -0
  16. app/dashboard/RecentAgents.tsx +89 -0
  17. app/dashboard/page.tsx +41 -0
  18. app/globals.css +4 -0
  19. app/knowledge/KnowledgeFilters.tsx +121 -0
  20. app/knowledge/KnowledgeLibrary.tsx +383 -0
  21. app/knowledge/create/KnowledgeBuilder.tsx +549 -0
  22. app/knowledge/create/page.tsx +26 -0
  23. app/knowledge/page.tsx +47 -0
  24. app/layout.tsx +41 -0
  25. app/not-found.tsx +9 -0
  26. app/page.tsx +145 -0
  27. app/workflows/WorkflowFilters.tsx +80 -0
  28. app/workflows/WorkflowList.tsx +265 -0
  29. app/workflows/[id]/analytics/ExecutionHistory.tsx +230 -0
  30. app/workflows/[id]/analytics/NodeAnalytics.tsx +126 -0
  31. app/workflows/[id]/analytics/PerformanceChart.tsx +80 -0
  32. app/workflows/[id]/analytics/WorkflowAnalytics.tsx +232 -0
  33. app/workflows/[id]/analytics/page.tsx +34 -0
  34. app/workflows/create/NodeLibrary.tsx +159 -0
  35. app/workflows/create/WorkflowBuilder.tsx +141 -0
  36. app/workflows/create/WorkflowCanvas.tsx +232 -0
  37. app/workflows/create/WorkflowSettings.tsx +338 -0
  38. app/workflows/create/page.tsx +26 -0
  39. app/workflows/page.tsx +52 -0
  40. app/workflows/templates/TemplateFilters.tsx +101 -0
  41. app/workflows/templates/TemplateLibrary.tsx +290 -0
  42. app/workflows/templates/page.tsx +53 -0
  43. components/Header.tsx +80 -0
  44. components/Sidebar.tsx +49 -0
  45. eslint.config.mjs +21 -0
  46. index.html +0 -20
  47. next.config.ts +13 -0
  48. package.json +28 -0
  49. postcss.config.mjs +8 -0
  50. style.css +0 -28
.DS_Store ADDED
Binary file (6.15 kB). View file
 
README.md CHANGED
@@ -1,12 +1,30 @@
1
- ---
2
- title: test1
3
- emoji: 🐳
4
- colorFrom: blue
5
- colorTo: gray
6
- sdk: static
7
- pinned: false
8
- tags:
9
- - deepsite
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
app/agents/AgentFilters.tsx ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ export default function AgentFilters() {
6
+ const [activeFilter, setActiveFilter] = useState('all');
7
+ const [searchTerm, setSearchTerm] = useState('');
8
+
9
+ const filters = [
10
+ { key: 'all', label: '全部', count: 24 },
11
+ { key: 'active', label: '运行中', count: 18 },
12
+ { key: 'paused', label: '暂停', count: 4 },
13
+ { key: 'draft', label: '草稿', count: 2 }
14
+ ];
15
+
16
+ const types = [
17
+ { key: 'all', label: '全部类型' },
18
+ { key: 'chat', label: '对话型' },
19
+ { key: 'analysis', label: '分析型' },
20
+ { key: 'generation', label: '生成型' },
21
+ { key: 'workflow', label: '工作流型' }
22
+ ];
23
+
24
+ return (
25
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
26
+ <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
27
+ <div className="flex items-center space-x-4">
28
+ {filters.map((filter) => (
29
+ <button
30
+ key={filter.key}
31
+ onClick={() => setActiveFilter(filter.key)}
32
+ className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors cursor-pointer whitespace-nowrap ${
33
+ activeFilter === filter.key
34
+ ? 'bg-blue-100 text-blue-700'
35
+ : 'text-gray-600 hover:bg-gray-100'
36
+ }`}
37
+ >
38
+ {filter.label} ({filter.count})
39
+ </button>
40
+ ))}
41
+ </div>
42
+
43
+ <div className="flex items-center space-x-4">
44
+ <div className="relative">
45
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
46
+ <div className="w-4 h-4 flex items-center justify-center">
47
+ <i className="ri-search-line text-gray-400"></i>
48
+ </div>
49
+ </div>
50
+ <input
51
+ type="text"
52
+ placeholder="搜索Agent..."
53
+ value={searchTerm}
54
+ onChange={(e) => setSearchTerm(e.target.value)}
55
+ className="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
56
+ />
57
+ </div>
58
+
59
+ <div className="relative">
60
+ <button className="px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap">
61
+ 类型筛选
62
+ <div className="w-4 h-4 flex items-center justify-center inline-block ml-2">
63
+ <i className="ri-arrow-down-s-line"></i>
64
+ </div>
65
+ </button>
66
+ </div>
67
+
68
+ <div className="relative">
69
+ <button className="px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap">
70
+ 排序
71
+ <div className="w-4 h-4 flex items-center justify-center inline-block ml-2">
72
+ <i className="ri-arrow-down-s-line"></i>
73
+ </div>
74
+ </button>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ );
80
+ }
app/agents/AgentList.tsx ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import Link from 'next/link';
5
+
6
+ export default function AgentList() {
7
+ const [selectedAgents, setSelectedAgents] = useState<number[]>([]);
8
+
9
+ const agents = [
10
+ {
11
+ id: 1,
12
+ name: '智能客服助手',
13
+ description: '处理客户咨询和问题解答的智能助手',
14
+ type: '对话型',
15
+ status: '运行中',
16
+ version: 'v2.1',
17
+ created: '2024-01-15',
18
+ lastUsed: '2分钟前',
19
+ calls: 2845,
20
+ avatar: '🤖',
21
+ tags: ['客服', '问答', '多轮对话']
22
+ },
23
+ {
24
+ id: 2,
25
+ name: '文档分析器',
26
+ description: '自动分析和提取文档关键信息',
27
+ type: '分析型',
28
+ status: '运行中',
29
+ version: 'v1.8',
30
+ created: '2024-01-10',
31
+ lastUsed: '15分钟前',
32
+ calls: 1256,
33
+ avatar: '📄',
34
+ tags: ['文档', '分析', 'OCR']
35
+ },
36
+ {
37
+ id: 3,
38
+ name: '代码生成器',
39
+ description: '根据需求自动生成代码片段',
40
+ type: '生成型',
41
+ status: '暂停',
42
+ version: 'v1.5',
43
+ created: '2024-01-08',
44
+ lastUsed: '1小时前',
45
+ calls: 892,
46
+ avatar: '💻',
47
+ tags: ['编程', '代码', '自动化']
48
+ },
49
+ {
50
+ id: 4,
51
+ name: '数据可视化',
52
+ description: '将数据转换为图表和可视化报告',
53
+ type: '图表型',
54
+ status: '运行中',
55
+ version: 'v2.0',
56
+ created: '2024-01-05',
57
+ lastUsed: '30分钟前',
58
+ calls: 674,
59
+ avatar: '📊',
60
+ tags: ['数据', '图表', '报告']
61
+ },
62
+ {
63
+ id: 5,
64
+ name: '邮件智能助手',
65
+ description: '自动分类和回复邮件',
66
+ type: '对话型',
67
+ status: '运行中',
68
+ version: 'v1.3',
69
+ created: '2024-01-03',
70
+ lastUsed: '45分钟前',
71
+ calls: 458,
72
+ avatar: '📧',
73
+ tags: ['邮件', '自动回复', '分类']
74
+ },
75
+ {
76
+ id: 6,
77
+ name: '语音转文字',
78
+ description: '实时语音识别和转录',
79
+ type: '转换型',
80
+ status: '运行中',
81
+ version: 'v1.7',
82
+ created: '2024-01-01',
83
+ lastUsed: '1小时前',
84
+ calls: 325,
85
+ avatar: '🎤',
86
+ tags: ['语音', '转录', '实时']
87
+ }
88
+ ];
89
+
90
+ const handleSelectAgent = (agentId: number) => {
91
+ setSelectedAgents(prev =>
92
+ prev.includes(agentId)
93
+ ? prev.filter(id => id !== agentId)
94
+ : [...prev, agentId]
95
+ );
96
+ };
97
+
98
+ const handleSelectAll = () => {
99
+ setSelectedAgents(selectedAgents.length === agents.length ? [] : agents.map(a => a.id));
100
+ };
101
+
102
+ return (
103
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200">
104
+ <div className="p-6 border-b border-gray-200">
105
+ <div className="flex items-center justify-between">
106
+ <div className="flex items-center space-x-4">
107
+ <input
108
+ type="checkbox"
109
+ checked={selectedAgents.length === agents.length}
110
+ onChange={handleSelectAll}
111
+ className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
112
+ />
113
+ <span className="text-sm text-gray-600">
114
+ {selectedAgents.length > 0 ? `已选择 ${selectedAgents.length} 个Agent` : '全选'}
115
+ </span>
116
+ </div>
117
+
118
+ {selectedAgents.length > 0 && (
119
+ <div className="flex items-center space-x-3">
120
+ <button className="px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer whitespace-nowrap">
121
+ 批量启动
122
+ </button>
123
+ <button className="px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer whitespace-nowrap">
124
+ 批量暂停
125
+ </button>
126
+ <button className="px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-50 rounded-lg transition-colors cursor-pointer whitespace-nowrap">
127
+ 批量删除
128
+ </button>
129
+ </div>
130
+ )}
131
+ </div>
132
+ </div>
133
+
134
+ <div className="divide-y divide-gray-200">
135
+ {agents.map((agent) => (
136
+ <div key={agent.id} className="p-6 hover:bg-gray-50 transition-colors">
137
+ <div className="flex items-center justify-between">
138
+ <div className="flex items-center space-x-4">
139
+ <input
140
+ type="checkbox"
141
+ checked={selectedAgents.includes(agent.id)}
142
+ onChange={() => handleSelectAgent(agent.id)}
143
+ className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
144
+ />
145
+ <div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center">
146
+ <span className="text-xl">{agent.avatar}</span>
147
+ </div>
148
+ <div className="flex-1">
149
+ <div className="flex items-center space-x-3">
150
+ <h3 className="text-lg font-semibold text-gray-900">{agent.name}</h3>
151
+ <span className="text-sm text-gray-500">{agent.version}</span>
152
+ <span className={`px-2 py-1 text-xs rounded-full ${
153
+ agent.status === '运行中'
154
+ ? 'bg-green-100 text-green-800'
155
+ : 'bg-yellow-100 text-yellow-800'
156
+ }`}>
157
+ {agent.status}
158
+ </span>
159
+ </div>
160
+ <p className="text-gray-600 mt-1">{agent.description}</p>
161
+ <div className="flex items-center space-x-4 mt-3">
162
+ <span className="text-sm text-gray-500">类型: {agent.type}</span>
163
+ <span className="text-sm text-gray-500">调用: {agent.calls.toLocaleString()}</span>
164
+ <span className="text-sm text-gray-500">最后使用: {agent.lastUsed}</span>
165
+ </div>
166
+ <div className="flex items-center space-x-2 mt-3">
167
+ {agent.tags.map((tag, index) => (
168
+ <span key={index} className="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded-full">
169
+ {tag}
170
+ </span>
171
+ ))}
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ <div className="flex items-center space-x-3">
177
+ <Link href={`/agents/${agent.id}/analytics`} className="p-2 text-gray-400 hover:text-blue-600 transition-colors cursor-pointer">
178
+ <div className="w-5 h-5 flex items-center justify-center">
179
+ <i className="ri-bar-chart-line"></i>
180
+ </div>
181
+ </Link>
182
+ <Link href={`/agents/${agent.id}/edit`} className="p-2 text-gray-400 hover:text-blue-600 transition-colors cursor-pointer">
183
+ <div className="w-5 h-5 flex items-center justify-center">
184
+ <i className="ri-edit-line"></i>
185
+ </div>
186
+ </Link>
187
+ <button className="p-2 text-gray-400 hover:text-blue-600 transition-colors cursor-pointer">
188
+ <div className="w-5 h-5 flex items-center justify-center">
189
+ <i className="ri-settings-line"></i>
190
+ </div>
191
+ </button>
192
+ <button className="p-2 text-gray-400 hover:text-red-600 transition-colors cursor-pointer">
193
+ <div className="w-5 h-5 flex items-center justify-center">
194
+ <i className="ri-delete-bin-line"></i>
195
+ </div>
196
+ </button>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ ))}
201
+ </div>
202
+ </div>
203
+ );
204
+ }
app/agents/create/AgentBuilder.tsx ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import BasicSettings from './BasicSettings';
6
+ import ModelConfig from './ModelConfig';
7
+ import WorkflowConfig from './WorkflowConfig';
8
+ import KnowledgeBase from './KnowledgeBase';
9
+ import TestPanel from './TestPanel';
10
+
11
+ export default function AgentBuilder() {
12
+ const router = useRouter();
13
+ const [currentStep, setCurrentStep] = useState(0);
14
+ const [agentData, setAgentData] = useState({
15
+ name: '',
16
+ description: '',
17
+ type: 'chat',
18
+ avatar: '🤖',
19
+ model: 'gpt-4',
20
+ temperature: 0.7,
21
+ maxTokens: 2000,
22
+ systemPrompt: '',
23
+ workflows: [],
24
+ knowledgeBase: [],
25
+ tags: []
26
+ });
27
+
28
+ const steps = [
29
+ { id: 0, name: '基本设置', icon: 'ri-settings-line' },
30
+ { id: 1, name: '模型配置', icon: 'ri-cpu-line' },
31
+ { id: 2, name: '工作流配置', icon: 'ri-flow-chart' },
32
+ { id: 3, name: '知识库', icon: 'ri-book-line' },
33
+ { id: 4, name: '测试调试', icon: 'ri-play-line' }
34
+ ];
35
+
36
+ const handleNext = () => {
37
+ if (currentStep < steps.length - 1) {
38
+ setCurrentStep(currentStep + 1);
39
+ }
40
+ };
41
+
42
+ const handlePrev = () => {
43
+ if (currentStep > 0) {
44
+ setCurrentStep(currentStep - 1);
45
+ }
46
+ };
47
+
48
+ const handleSave = () => {
49
+ console.log('保存Agent:', agentData);
50
+ router.push('/agents');
51
+ };
52
+
53
+ const handleDeploy = () => {
54
+ console.log('部署Agent:', agentData);
55
+ router.push('/agents');
56
+ };
57
+
58
+ const renderStepContent = () => {
59
+ switch (currentStep) {
60
+ case 0:
61
+ return <BasicSettings data={agentData} onChange={setAgentData} />;
62
+ case 1:
63
+ return <ModelConfig data={agentData} onChange={setAgentData} />;
64
+ case 2:
65
+ return <WorkflowConfig data={agentData} onChange={setAgentData} />;
66
+ case 3:
67
+ return <KnowledgeBase data={agentData} onChange={setAgentData} />;
68
+ case 4:
69
+ return <TestPanel data={agentData} />;
70
+ default:
71
+ return null;
72
+ }
73
+ };
74
+
75
+ return (
76
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200">
77
+ {/* 步骤导航 */}
78
+ <div className="p-6 border-b border-gray-200">
79
+ <div className="flex items-center justify-between">
80
+ {steps.map((step, index) => (
81
+ <div key={step.id} className="flex items-center">
82
+ <div className={`flex items-center space-x-3 px-4 py-2 rounded-lg cursor-pointer transition-colors ${
83
+ currentStep === index
84
+ ? 'bg-blue-100 text-blue-700'
85
+ : currentStep > index
86
+ ? 'bg-green-100 text-green-700'
87
+ : 'text-gray-500'
88
+ }`} onClick={() => setCurrentStep(index)}>
89
+ <div className="w-6 h-6 flex items-center justify-center">
90
+ <i className={step.icon}></i>
91
+ </div>
92
+ <span className="font-medium">{step.name}</span>
93
+ </div>
94
+ {index < steps.length - 1 && (
95
+ <div className="mx-4 w-8 h-px bg-gray-300"></div>
96
+ )}
97
+ </div>
98
+ ))}
99
+ </div>
100
+ </div>
101
+
102
+ {/* 步骤内容 */}
103
+ <div className="p-8">
104
+ {renderStepContent()}
105
+ </div>
106
+
107
+ {/* 底部操作按钮 */}
108
+ <div className="p-6 border-t border-gray-200 flex items-center justify-between">
109
+ <div className="flex space-x-3">
110
+ {currentStep > 0 && (
111
+ <button
112
+ onClick={handlePrev}
113
+ className="px-6 py-3 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap"
114
+ >
115
+ 上一步
116
+ </button>
117
+ )}
118
+ </div>
119
+
120
+ <div className="flex space-x-3">
121
+ <button
122
+ onClick={handleSave}
123
+ className="px-6 py-3 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap"
124
+ >
125
+ 保存草稿
126
+ </button>
127
+
128
+ {currentStep < steps.length - 1 ? (
129
+ <button
130
+ onClick={handleNext}
131
+ className="bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap"
132
+ >
133
+ 下一步
134
+ </button>
135
+ ) : (
136
+ <button
137
+ onClick={handleDeploy}
138
+ className="bg-green-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-green-700 transition-colors cursor-pointer whitespace-nowrap"
139
+ >
140
+ <div className="w-5 h-5 flex items-center justify-center inline-block mr-2">
141
+ <i className="ri-rocket-line"></i>
142
+ </div>
143
+ 立即部署
144
+ </button>
145
+ )}
146
+ </div>
147
+ </div>
148
+ </div>
149
+ );
150
+ }
app/agents/create/BasicSettings.tsx ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ 'use client';
3
+
4
+ import { useState } from 'react';
5
+
6
+ interface BasicSettingsProps {
7
+ data: any;
8
+ onChange: (data: any) => void;
9
+ }
10
+
11
+ export default function BasicSettings({ data, onChange }: BasicSettingsProps) {
12
+ const [tags, setTags] = useState(data.tags || []);
13
+ const [newTag, setNewTag] = useState('');
14
+
15
+ const agentTypes = [
16
+ { value: 'chat', label: '对话型Agent', desc: '专注于多轮对话和问答' },
17
+ { value: 'analysis', label: '分析型Agent', desc: '数据分析和报告生成' },
18
+ { value: 'generation', label: '生成型Agent', desc: '内容创作和代码生成' },
19
+ { value: 'workflow', label: '工作流型Agent', desc: '复杂任务自动化处理' }
20
+ ];
21
+
22
+ const avatars = ['🤖', '🎯', '📊', '💻', '📝', '🔍', '⚡', '🚀', '💡', '🎨'];
23
+
24
+ const handleInputChange = (field: string, value: any) => {
25
+ onChange({ ...data, [field]: value });
26
+ };
27
+
28
+ const addTag = () => {
29
+ if (newTag && !tags.includes(newTag)) {
30
+ const updatedTags = [...tags, newTag];
31
+ setTags(updatedTags);
32
+ onChange({ ...data, tags: updatedTags });
33
+ setNewTag('');
34
+ }
35
+ };
36
+
37
+ const removeTag = (tagToRemove: string) => {
38
+ const updatedTags = tags.filter(tag => tag !== tagToRemove);
39
+ setTags(updatedTags);
40
+ onChange({ ...data, tags: updatedTags });
41
+ };
42
+
43
+ return (
44
+ <div className="space-y-8">
45
+ <div>
46
+ <h3 className="text-lg font-semibold text-gray-900 mb-6">基本信息</h3>
47
+
48
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
49
+ <div className="space-y-6">
50
+ <div>
51
+ <label className="block text-sm font-medium text-gray-700 mb-2">Agent名称</label>
52
+ <input
53
+ type="text"
54
+ placeholder="输入Agent名称"
55
+ value={data.name}
56
+ onChange={(e) => handleInputChange('name', e.target.value)}
57
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
58
+ />
59
+ </div>
60
+
61
+ <div>
62
+ <label className="block text-sm font-medium text-gray-700 mb-2">描述</label>
63
+ <textarea
64
+ placeholder="描述Agent的功能和用途"
65
+ rows={4}
66
+ maxLength={500}
67
+ value={data.description}
68
+ onChange={(e) => handleInputChange('description', e.target.value)}
69
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm resize-none"
70
+ />
71
+ <div className="text-xs text-gray-500 mt-1">{data.description.length}/500</div>
72
+ </div>
73
+
74
+ <div>
75
+ <label className="block text-sm font-medium text-gray-700 mb-2">标签</label>
76
+ <div className="flex flex-wrap gap-2 mb-3">
77
+ {tags.map((tag, index) => (
78
+ <span
79
+ key={index}
80
+ className="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 text-sm rounded-full"
81
+ >
82
+ {tag}
83
+ <button
84
+ onClick={() => removeTag(tag)}
85
+ className="ml-2 w-4 h-4 flex items-center justify-center hover:bg-blue-200 rounded-full cursor-pointer"
86
+ >
87
+ <i className="ri-close-line text-xs"></i>
88
+ </button>
89
+ </span>
90
+ ))}
91
+ </div>
92
+ <div className="flex space-x-2">
93
+ <input
94
+ type="text"
95
+ placeholder="添加标签"
96
+ value={newTag}
97
+ onChange={(e) => setNewTag(e.target.value)}
98
+ onKeyPress={(e) => e.key === 'Enter' && addTag()}
99
+ className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
100
+ />
101
+ <button
102
+ onClick={addTag}
103
+ className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap text-sm"
104
+ >
105
+ 添加
106
+ </button>
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <div className="space-y-6">
112
+ <div>
113
+ <label className="block text-sm font-medium text-gray-700 mb-2">选择头像</label>
114
+ <div className="grid grid-cols-5 gap-3">
115
+ {avatars.map((avatar) => (
116
+ <button
117
+ key={avatar}
118
+ onClick={() => handleInputChange('avatar', avatar)}
119
+ className={`w-12 h-12 rounded-xl flex items-center justify-center text-xl cursor-pointer transition-colors ${
120
+ data.avatar === avatar
121
+ ? 'bg-blue-100 border-2 border-blue-500'
122
+ : 'bg-gray-100 hover:bg-gray-200 border-2 border-transparent'
123
+ }`}
124
+ >
125
+ {avatar}
126
+ </button>
127
+ ))}
128
+ </div>
129
+ </div>
130
+
131
+ <div>
132
+ <label className="block text-sm font-medium text-gray-700 mb-2">Agent类型</label>
133
+ <div className="space-y-3">
134
+ {agentTypes.map((type) => (
135
+ <div
136
+ key={type.value}
137
+ onClick={() => handleInputChange('type', type.value)}
138
+ className={`p-4 border rounded-lg cursor-pointer transition-colors ${
139
+ data.type === type.value
140
+ ? 'border-blue-500 bg-blue-50'
141
+ : 'border-gray-300 hover:border-gray-400'
142
+ }`}
143
+ >
144
+ <div className="flex items-center justify-between">
145
+ <div>
146
+ <h4 className="font-medium text-gray-900">{type.label}</h4>
147
+ <p className="text-sm text-gray-600 mt-1">{type.desc}</p>
148
+ </div>
149
+ <div className={`w-4 h-4 rounded-full border-2 ${
150
+ data.type === type.value
151
+ ? 'border-blue-500 bg-blue-500'
152
+ : 'border-gray-300'
153
+ }`}>
154
+ {data.type === type.value && (
155
+ <div className="w-2 h-2 bg-white rounded-full mx-auto mt-0.5"></div>
156
+ )}
157
+ </div>
158
+ </div>
159
+ </div>
160
+ ))}
161
+ </div>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ );
168
+ }
app/agents/create/KnowledgeBase.tsx ADDED
@@ -0,0 +1,511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ 'use client';
3
+
4
+ import { useState } from 'react';
5
+
6
+ interface KnowledgeBaseProps {
7
+ data: any;
8
+ onChange: (data: any) => void;
9
+ }
10
+
11
+ export default function KnowledgeBase({ data, onChange }: KnowledgeBaseProps) {
12
+ const [uploadedFiles, setUploadedFiles] = useState([
13
+ { id: 1, name: '产品手册.pdf', size: '2.4 MB', status: 'uploaded', type: 'pdf', chunks: 45, lastModified: '2024-01-15' },
14
+ { id: 2, name: '常见问题FAQ.docx', size: '856 KB', status: 'uploaded', type: 'docx', chunks: 23, lastModified: '2024-01-14' },
15
+ { id: 3, name: '技术文档.md', size: '1.2 MB', status: 'uploaded', type: 'md', chunks: 67, lastModified: '2024-01-13' },
16
+ ]);
17
+
18
+ const [webSources, setWebSources] = useState([
19
+ { id: 1, url: 'https://docs.example.com', title: '官方文档', status: 'crawled', pages: 15, lastUpdate: '2024-01-15' },
20
+ { id: 2, url: 'https://help.example.com', title: '帮助中心', status: 'crawling', pages: 8, lastUpdate: '2024-01-14' }
21
+ ]);
22
+
23
+ const [manualContent, setManualContent] = useState('');
24
+ const [newUrl, setNewUrl] = useState('');
25
+ const [activeTab, setActiveTab] = useState('files');
26
+ const [processingSettings, setProcessingSettings] = useState({
27
+ autoChunk: true,
28
+ smartDedup: true,
29
+ generateSummary: false,
30
+ chunkSize: 1000,
31
+ overlap: 200
32
+ });
33
+
34
+ const knowledgeBases = [
35
+ { id: 'kb-001', name: '通用知识库', description: '包含基础常识和通用信息', items: 1240, isDefault: true },
36
+ { id: 'kb-002', name: '技术文档库', description: '技术相关的文档和资料', items: 567, isDefault: false },
37
+ { id: 'kb-003', name: '产品说明库', description: '产品相关的说明和手册', items: 892, isDefault: false },
38
+ { id: 'kb-004', name: '客服问答库', description: '客服常见问题和解答', items: 345, isDefault: false }
39
+ ];
40
+
41
+ const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState(['kb-001']);
42
+
43
+ const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
44
+ const files = Array.from(event.target.files || []);
45
+ const newFiles = files.map((file, index) => ({
46
+ id: uploadedFiles.length + index + 1,
47
+ name: file.name,
48
+ size: `${(file.size / 1024 / 1024).toFixed(1)} MB`,
49
+ status: 'uploading',
50
+ type: file.name.split('.').pop() || 'unknown',
51
+ chunks: 0,
52
+ lastModified: new Date().toISOString().split('T')[0]
53
+ }));
54
+
55
+ setUploadedFiles([...uploadedFiles, ...newFiles]);
56
+
57
+ setTimeout(() => {
58
+ setUploadedFiles(prev => prev.map(file =>
59
+ newFiles.some(nf => nf.id === file.id)
60
+ ? { ...file, status: 'uploaded', chunks: Math.floor(Math.random() * 50) + 10 }
61
+ : file
62
+ ));
63
+ }, 2000);
64
+ };
65
+
66
+ const removeFile = (fileId: number) => {
67
+ setUploadedFiles(prev => prev.filter(file => file.id !== fileId));
68
+ };
69
+
70
+ const addWebSource = () => {
71
+ if (newUrl) {
72
+ const newSource = {
73
+ id: webSources.length + 1,
74
+ url: newUrl,
75
+ title: '正在获取标题...',
76
+ status: 'crawling',
77
+ pages: 0,
78
+ lastUpdate: new Date().toISOString().split('T')[0]
79
+ };
80
+ setWebSources([...webSources, newSource]);
81
+ setNewUrl('');
82
+
83
+ setTimeout(() => {
84
+ setWebSources(prev => prev.map(source =>
85
+ source.id === newSource.id
86
+ ? { ...source, status: 'crawled', title: `${newUrl.split('/')[2]} - 文档`, pages: Math.floor(Math.random() * 20) + 5 }
87
+ : source
88
+ ));
89
+ }, 3000);
90
+ }
91
+ };
92
+
93
+ const removeWebSource = (sourceId: number) => {
94
+ setWebSources(prev => prev.filter(source => source.id !== sourceId));
95
+ };
96
+
97
+ const toggleKnowledgeBase = (kbId: string) => {
98
+ setSelectedKnowledgeBases(prev =>
99
+ prev.includes(kbId)
100
+ ? prev.filter(id => id !== kbId)
101
+ : [...prev, kbId]
102
+ );
103
+ };
104
+
105
+ const addManualContent = () => {
106
+ if (manualContent.trim()) {
107
+ const newFile = {
108
+ id: uploadedFiles.length + 1,
109
+ name: `手动内容_${new Date().toLocaleTimeString()}`,
110
+ size: `${Math.ceil(manualContent.length / 1024)} KB`,
111
+ status: 'uploaded',
112
+ type: 'text',
113
+ chunks: Math.ceil(manualContent.length / processingSettings.chunkSize),
114
+ lastModified: new Date().toISOString().split('T')[0]
115
+ };
116
+ setUploadedFiles([...uploadedFiles, newFile]);
117
+ setManualContent('');
118
+ }
119
+ };
120
+
121
+ return (
122
+ <div className="space-y-8">
123
+ <div>
124
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">知识库配置</h3>
125
+ <p className="text-gray-600 mb-6">为Agent配置知识来源,提升回答的准确性和专业性</p>
126
+
127
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
128
+ <div className="lg:col-span-2 space-y-6">
129
+ {/* 选择现有知识库 */}
130
+ <div className="bg-white border border-gray-200 rounded-lg p-6">
131
+ <h4 className="font-medium text-gray-900 mb-4">选择现有知识库</h4>
132
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
133
+ {knowledgeBases.map((kb) => (
134
+ <div
135
+ key={kb.id}
136
+ onClick={() => toggleKnowledgeBase(kb.id)}
137
+ className={`p-4 border rounded-lg cursor-pointer transition-all ${
138
+ selectedKnowledgeBases.includes(kb.id)
139
+ ? 'border-blue-500 bg-blue-50'
140
+ : 'border-gray-200 hover:border-gray-300'
141
+ }`}
142
+ >
143
+ <div className="flex items-start justify-between">
144
+ <div className="flex-1">
145
+ <div className="flex items-center space-x-2">
146
+ <h5 className="font-medium text-gray-900">{kb.name}</h5>
147
+ {kb.isDefault && (
148
+ <span className="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full">默认</span>
149
+ )}
150
+ </div>
151
+ <p className="text-sm text-gray-600 mt-1">{kb.description}</p>
152
+ <p className="text-xs text-gray-500 mt-2">{kb.items.toLocaleString()} 条记录</p>
153
+ </div>
154
+ <div className={`w-5 h-5 rounded border-2 flex items-center justify-center ${
155
+ selectedKnowledgeBases.includes(kb.id)
156
+ ? 'border-blue-500 bg-blue-500'
157
+ : 'border-gray-300'
158
+ }`}>
159
+ {selectedKnowledgeBases.includes(kb.id) && (
160
+ <i className="ri-check-line text-white text-xs"></i>
161
+ )}
162
+ </div>
163
+ </div>
164
+ </div>
165
+ ))}
166
+ </div>
167
+ </div>
168
+
169
+ {/* 添加新内容 */}
170
+ <div className="bg-white border border-gray-200 rounded-lg">
171
+ <div className="border-b border-gray-200">
172
+ <div className="flex space-x-0">
173
+ {[
174
+ { key: 'files', label: '文件上传', icon: 'ri-file-line' },
175
+ { key: 'web', label: '网页抓取', icon: 'ri-global-line' },
176
+ { key: 'manual', label: '手动输入', icon: 'ri-edit-line' }
177
+ ].map((tab) => (
178
+ <button
179
+ key={tab.key}
180
+ onClick={() => setActiveTab(tab.key)}
181
+ className={`px-6 py-4 text-sm font-medium border-b-2 transition-colors ${
182
+ activeTab === tab.key
183
+ ? 'border-blue-500 text-blue-600 bg-blue-50'
184
+ : 'border-transparent text-gray-500 hover:text-gray-700'
185
+ }`}
186
+ >
187
+ <div className="w-4 h-4 flex items-center justify-center inline-block mr-2">
188
+ <i className={tab.icon}></i>
189
+ </div>
190
+ {tab.label}
191
+ </button>
192
+ ))}
193
+ </div>
194
+ </div>
195
+
196
+ <div className="p-6">
197
+ {activeTab === 'files' && (
198
+ <div className="space-y-4">
199
+ <div className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-gray-400 transition-colors">
200
+ <div className="w-12 h-12 bg-gray-100 rounded-lg flex items-center justify-center mx-auto mb-4">
201
+ <i className="ri-upload-cloud-line text-xl text-gray-400"></i>
202
+ </div>
203
+ <p className="text-gray-600 mb-2">拖拽文件到此处,或者</p>
204
+ <label className="inline-block px-4 py-2 bg-blue-600 text-white rounded-lg cursor-pointer hover:bg-blue-700 transition-colors text-sm">
205
+ 选择文件
206
+ <input
207
+ type="file"
208
+ multiple
209
+ accept=".pdf,.doc,.docx,.txt,.md,.csv,.xlsx"
210
+ onChange={handleFileUpload}
211
+ className="hidden"
212
+ />
213
+ </label>
214
+ <p className="text-xs text-gray-500 mt-2">支持 PDF, DOC, DOCX, TXT, MD, CSV, XLSX 格式,最大 50MB</p>
215
+ </div>
216
+ </div>
217
+ )}
218
+
219
+ {activeTab === 'web' && (
220
+ <div className="space-y-4">
221
+ <div className="flex space-x-2">
222
+ <input
223
+ type="url"
224
+ placeholder="输入要抓取的网址(如:https://docs.example.com)"
225
+ value={newUrl}
226
+ onChange={(e) => setNewUrl(e.target.value)}
227
+ className="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
228
+ />
229
+ <button
230
+ onClick={addWebSource}
231
+ className="px-4 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap text-sm"
232
+ >
233
+ 开始抓取
234
+ </button>
235
+ </div>
236
+ <div className="text-xs text-gray-500">
237
+ 支持抓取文档站点、博客文章、产品页面等,自动提取文本内容
238
+ </div>
239
+ </div>
240
+ )}
241
+
242
+ {activeTab === 'manual' && (
243
+ <div className="space-y-4">
244
+ <textarea
245
+ placeholder="直接输入要添加到知识库的内容...&#10;&#10;例如:&#10;• 产品使用说明&#10;• 常见问题解答&#10;• 操作步骤指南&#10;• 专业知识条目"
246
+ rows={8}
247
+ maxLength={500}
248
+ value={manualContent}
249
+ onChange={(e) => setManualContent(e.target.value)}
250
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm resize-none"
251
+ />
252
+ <div className="flex items-center justify-between">
253
+ <div className="text-xs text-gray-500">{manualContent.length}/500 字符</div>
254
+ <button
255
+ onClick={addManualContent}
256
+ disabled={!manualContent.trim()}
257
+ className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 transition-colors cursor-pointer whitespace-nowrap text-sm"
258
+ >
259
+ 添加内容
260
+ </button>
261
+ </div>
262
+ </div>
263
+ )}
264
+ </div>
265
+ </div>
266
+ </div>
267
+
268
+ <div className="space-y-6">
269
+ {/* 已添加内容统计 */}
270
+ <div className="bg-white border border-gray-200 rounded-lg p-4">
271
+ <h4 className="font-medium text-gray-900 mb-3">内容统计</h4>
272
+ <div className="space-y-3">
273
+ <div className="flex items-center justify-between">
274
+ <span className="text-sm text-gray-600">文档数量</span>
275
+ <span className="font-medium text-gray-900">{uploadedFiles.length}</span>
276
+ </div>
277
+ <div className="flex items-center justify-between">
278
+ <span className="text-sm text-gray-600">网页来源</span>
279
+ <span className="font-medium text-gray-900">{webSources.length}</span>
280
+ </div>
281
+ <div className="flex items-center justify-between">
282
+ <span className="text-sm text-gray-600">总分块数</span>
283
+ <span className="font-medium text-gray-900">{uploadedFiles.reduce((sum, file) => sum + file.chunks, 0)}</span>
284
+ </div>
285
+ <div className="flex items-center justify-between">
286
+ <span className="text-sm text-gray-600">知识库</span>
287
+ <span className="font-medium text-gray-900">{selectedKnowledgeBases.length} 个</span>
288
+ </div>
289
+ </div>
290
+ </div>
291
+
292
+ {/* 处理设置 */}
293
+ <div className="bg-white border border-gray-200 rounded-lg p-4">
294
+ <h4 className="font-medium text-gray-900 mb-3">处理设置</h4>
295
+ <div className="space-y-4">
296
+ <div className="flex items-center justify-between">
297
+ <span className="text-sm text-gray-700">自动分块处理</span>
298
+ <div
299
+ onClick={() => setProcessingSettings(prev => ({...prev, autoChunk: !prev.autoChunk}))}
300
+ className={`w-10 h-6 rounded-full relative cursor-pointer transition-colors ${
301
+ processingSettings.autoChunk ? 'bg-blue-600' : 'bg-gray-300'
302
+ }`}
303
+ >
304
+ <div className={`w-4 h-4 bg-white rounded-full absolute top-1 transition-transform ${
305
+ processingSettings.autoChunk ? 'right-1' : 'left-1'
306
+ }`}></div>
307
+ </div>
308
+ </div>
309
+
310
+ <div className="flex items-center justify-between">
311
+ <span className="text-sm text-gray-700">智能去重</span>
312
+ <div
313
+ onClick={() => setProcessingSettings(prev => ({...prev, smartDedup: !prev.smartDedup}))}
314
+ className={`w-10 h-6 rounded-full relative cursor-pointer transition-colors ${
315
+ processingSettings.smartDedup ? 'bg-blue-600' : 'bg-gray-300'
316
+ }`}
317
+ >
318
+ <div className={`w-4 h-4 bg-white rounded-full absolute top-1 transition-transform ${
319
+ processingSettings.smartDedup ? 'right-1' : 'left-1'
320
+ }`}></div>
321
+ </div>
322
+ </div>
323
+
324
+ <div className="flex items-center justify-between">
325
+ <span className="text-sm text-gray-700">生成摘要</span>
326
+ <div
327
+ onClick={() => setProcessingSettings(prev => ({...prev, generateSummary: !prev.generateSummary}))}
328
+ className={`w-10 h-6 rounded-full relative cursor-pointer transition-colors ${
329
+ processingSettings.generateSummary ? 'bg-blue-600' : 'bg-gray-300'
330
+ }`}
331
+ >
332
+ <div className={`w-4 h-4 bg-white rounded-full absolute top-1 transition-transform ${
333
+ processingSettings.generateSummary ? 'right-1' : 'left-1'
334
+ }`}></div>
335
+ </div>
336
+ </div>
337
+
338
+ <div className="pt-2 border-t border-gray-200">
339
+ <div className="mb-3">
340
+ <label className="block text-sm text-gray-700 mb-1">
341
+ 分块大小: {processingSettings.chunkSize} 字符
342
+ </label>
343
+ <input
344
+ type="range"
345
+ min="500"
346
+ max="2000"
347
+ step="100"
348
+ value={processingSettings.chunkSize}
349
+ onChange={(e) => setProcessingSettings(prev => ({...prev, chunkSize: parseInt(e.target.value)}))}
350
+ className="w-full h-1 bg-gray-200 rounded-lg appearance-none cursor-pointer"
351
+ />
352
+ </div>
353
+
354
+ <div>
355
+ <label className="block text-sm text-gray-700 mb-1">
356
+ 重叠字符: {processingSettings.overlap}
357
+ </label>
358
+ <input
359
+ type="range"
360
+ min="0"
361
+ max="500"
362
+ step="50"
363
+ value={processingSettings.overlap}
364
+ onChange={(e) => setProcessingSettings(prev => ({...prev, overlap: parseInt(e.target.value)}))}
365
+ className="w-full h-1 bg-gray-200 rounded-lg appearance-none cursor-pointer"
366
+ />
367
+ </div>
368
+ </div>
369
+ </div>
370
+ </div>
371
+
372
+ {/* 使用提示 */}
373
+ <div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
374
+ <div className="flex items-start space-x-3">
375
+ <div className="w-5 h-5 flex items-center justify-center mt-0.5">
376
+ <i className="ri-lightbulb-line text-blue-600"></i>
377
+ </div>
378
+ <div>
379
+ <h4 className="font-medium text-blue-900 mb-2">优化建议</h4>
380
+ <ul className="text-sm text-blue-800 space-y-1">
381
+ <li>• 上传结构化的文档获得更好效果</li>
382
+ <li>• 定期更新知识库内容</li>
383
+ <li>• 避免重复和冗余信息</li>
384
+ <li>• 使用标准的文档格式</li>
385
+ </ul>
386
+ </div>
387
+ </div>
388
+ </div>
389
+ </div>
390
+ </div>
391
+
392
+ {/* 已上传文件列表 */}
393
+ {uploadedFiles.length > 0 && (
394
+ <div className="mt-8">
395
+ <h4 className="font-medium text-gray-900 mb-4">已添加的文件</h4>
396
+ <div className="bg-white border border-gray-200 rounded-lg overflow-hidden">
397
+ <div className="divide-y divide-gray-200">
398
+ {uploadedFiles.map((file) => (
399
+ <div key={file.id} className="p-4 hover:bg-gray-50 transition-colors">
400
+ <div className="flex items-center justify-between">
401
+ <div className="flex items-center space-x-4">
402
+ <div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
403
+ <i className={`${
404
+ file.type === 'pdf' ? 'ri-file-pdf-line text-red-600' :
405
+ file.type === 'docx' ? 'ri-file-word-line text-blue-600' :
406
+ file.type === 'md' ? 'ri-markdown-line text-purple-600' :
407
+ 'ri-file-text-line text-gray-600'
408
+ }`}></i>
409
+ </div>
410
+ <div className="flex-1">
411
+ <div className="flex items-center space-x-2">
412
+ <p className="font-medium text-gray-900">{file.name}</p>
413
+ {file.status === 'uploading' ? (
414
+ <span className="px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded-full">处理中</span>
415
+ ) : (
416
+ <span className="px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full">已完成</span>
417
+ )}
418
+ </div>
419
+ <div className="flex items-center space-x-4 mt-1 text-sm text-gray-500">
420
+ <span>{file.size}</span>
421
+ <span>{file.chunks} 个分块</span>
422
+ <span>修改于 {file.lastModified}</span>
423
+ </div>
424
+ </div>
425
+ </div>
426
+ <div className="flex items-center space-x-2">
427
+ {file.status === 'uploading' ? (
428
+ <div className="w-4 h-4 border-2 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
429
+ ) : (
430
+ <button className="p-1 text-gray-400 hover:text-blue-600 transition-colors cursor-pointer">
431
+ <div className="w-4 h-4 flex items-center justify-center">
432
+ <i className="ri-eye-line"></i>
433
+ </div>
434
+ </button>
435
+ )}
436
+ <button
437
+ onClick={() => removeFile(file.id)}
438
+ className="p-1 text-gray-400 hover:text-red-600 transition-colors cursor-pointer"
439
+ >
440
+ <div className="w-4 h-4 flex items-center justify-center">
441
+ <i className="ri-delete-bin-line"></i>
442
+ </div>
443
+ </button>
444
+ </div>
445
+ </div>
446
+ </div>
447
+ ))}
448
+ </div>
449
+ </div>
450
+ </div>
451
+ )}
452
+
453
+ {/* 网页来源列表 */}
454
+ {webSources.length > 0 && (
455
+ <div className="mt-8">
456
+ <h4 className="font-medium text-gray-900 mb-4">网页来源</h4>
457
+ <div className="bg-white border border-gray-200 rounded-lg overflow-hidden">
458
+ <div className="divide-y divide-gray-200">
459
+ {webSources.map((source) => (
460
+ <div key={source.id} className="p-4 hover:bg-gray-50 transition-colors">
461
+ <div className="flex items-center justify-between">
462
+ <div className="flex items-center space-x-4">
463
+ <div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
464
+ <i className="ri-global-line text-green-600"></i>
465
+ </div>
466
+ <div className="flex-1">
467
+ <div className="flex items-center space-x-2">
468
+ <p className="font-medium text-gray-900">{source.title}</p>
469
+ {source.status === 'crawling' ? (
470
+ <span className="px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded-full">抓取中</span>
471
+ ) : (
472
+ <span className="px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full">已完成</span>
473
+ )}
474
+ </div>
475
+ <div className="flex items-center space-x-4 mt-1 text-sm text-gray-500">
476
+ <span>{source.url}</span>
477
+ <span>{source.pages} 个页面</span>
478
+ <span>更新于 {source.lastUpdate}</span>
479
+ </div>
480
+ </div>
481
+ </div>
482
+ <div className="flex items-center space-x-2">
483
+ {source.status === 'crawling' ? (
484
+ <div className="w-4 h-4 border-2 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
485
+ ) : (
486
+ <button className="p-1 text-gray-400 hover:text-blue-600 transition-colors cursor-pointer">
487
+ <div className="w-4 h-4 flex items-center justify-center">
488
+ <i className="ri-refresh-line"></i>
489
+ </div>
490
+ </button>
491
+ )}
492
+ <button
493
+ onClick={() => removeWebSource(source.id)}
494
+ className="p-1 text-gray-400 hover:text-red-600 transition-colors cursor-pointer"
495
+ >
496
+ <div className="w-4 h-4 flex items-center justify-center">
497
+ <i className="ri-delete-bin-line"></i>
498
+ </div>
499
+ </button>
500
+ </div>
501
+ </div>
502
+ </div>
503
+ ))}
504
+ </div>
505
+ </div>
506
+ </div>
507
+ )}
508
+ </div>
509
+ </div>
510
+ );
511
+ }
app/agents/create/ModelConfig.tsx ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ 'use client';
3
+
4
+ interface ModelConfigProps {
5
+ data: any;
6
+ onChange: (data: any) => void;
7
+ }
8
+
9
+ export default function ModelConfig({ data, onChange }: ModelConfigProps) {
10
+ const models = [
11
+ { id: 'gpt-4', name: 'GPT-4', desc: '最先进的语言模型,适合复杂任务', cost: '高' },
12
+ { id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo', desc: '性价比最优,响应快速', cost: '中' },
13
+ { id: 'claude-3', name: 'Claude-3', desc: '擅长分析和推理任务', cost: '中' },
14
+ { id: 'gemini-pro', name: 'Gemini Pro', desc: '多模态能力强', cost: '中' }
15
+ ];
16
+
17
+ const handleInputChange = (field: string, value: any) => {
18
+ onChange({ ...data, [field]: value });
19
+ };
20
+
21
+ return (
22
+ <div className="space-y-8">
23
+ <div>
24
+ <h3 className="text-lg font-semibold text-gray-900 mb-6">模型配置</h3>
25
+
26
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
27
+ <div className="space-y-6">
28
+ <div>
29
+ <label className="block text-sm font-medium text-gray-700 mb-3">选择基础模型</label>
30
+ <div className="space-y-3">
31
+ {models.map((model) => (
32
+ <div
33
+ key={model.id}
34
+ onClick={() => handleInputChange('model', model.id)}
35
+ className={`p-4 border rounded-lg cursor-pointer transition-colors ${
36
+ data.model === model.id
37
+ ? 'border-blue-500 bg-blue-50'
38
+ : 'border-gray-300 hover:border-gray-400'
39
+ }`}
40
+ >
41
+ <div className="flex items-center justify-between">
42
+ <div>
43
+ <h4 className="font-medium text-gray-900">{model.name}</h4>
44
+ <p className="text-sm text-gray-600 mt-1">{model.desc}</p>
45
+ </div>
46
+ <div className="text-right">
47
+ <span className={`px-2 py-1 text-xs rounded-full ${
48
+ model.cost === '高' ? 'bg-red-100 text-red-800' : 'bg-yellow-100 text-yellow-800'
49
+ }`}>
50
+ 成本{model.cost}
51
+ </span>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ ))}
56
+ </div>
57
+ </div>
58
+ </div>
59
+
60
+ <div className="space-y-6">
61
+ <div>
62
+ <label className="block text-sm font-medium text-gray-700 mb-2">
63
+ 创造性 (Temperature): {data.temperature}
64
+ </label>
65
+ <input
66
+ type="range"
67
+ min="0"
68
+ max="2"
69
+ step="0.1"
70
+ value={data.temperature}
71
+ onChange={(e) => handleInputChange('temperature', parseFloat(e.target.value))}
72
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
73
+ />
74
+ <div className="flex justify-between text-xs text-gray-500 mt-1">
75
+ <span>保守</span>
76
+ <span>创新</span>
77
+ </div>
78
+ </div>
79
+
80
+ <div>
81
+ <label className="block text-sm font-medium text-gray-700 mb-2">最大输出长度</label>
82
+ <input
83
+ type="number"
84
+ min="100"
85
+ max="4000"
86
+ step="100"
87
+ value={data.maxTokens}
88
+ onChange={(e) => handleInputChange('maxTokens', parseInt(e.target.value))}
89
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
90
+ />
91
+ <p className="text-xs text-gray-500 mt-1">建议范围: 100-4000 tokens</p>
92
+ </div>
93
+
94
+ <div>
95
+ <label className="block text-sm font-medium text-gray-700 mb-2">系统提示词</label>
96
+ <textarea
97
+ placeholder="定义Agent的角色、行为和回答风格..."
98
+ rows={8}
99
+ maxLength={500}
100
+ value={data.systemPrompt}
101
+ onChange={(e) => handleInputChange('systemPrompt', e.target.value)}
102
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm resize-none"
103
+ />
104
+ <div className="text-xs text-gray-500 mt-1">{data.systemPrompt.length}/500</div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ );
111
+ }
app/agents/create/TestPanel.tsx ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ 'use client';
3
+
4
+ import { useState } from 'react';
5
+
6
+ interface TestPanelProps {
7
+ data: any;
8
+ }
9
+
10
+ export default function TestPanel({ data }: TestPanelProps) {
11
+ const [messages, setMessages] = useState([
12
+ {
13
+ id: 1,
14
+ role: 'assistant',
15
+ content: `你好!我是${data.name || 'AI Assistant'}。我已经准备好为您提供帮助了。请告诉我您需要什么协助?`,
16
+ timestamp: new Date()
17
+ }
18
+ ]);
19
+ const [inputMessage, setInputMessage] = useState('');
20
+ const [isLoading, setIsLoading] = useState(false);
21
+
22
+ const sendMessage = async () => {
23
+ if (!inputMessage.trim()) return;
24
+
25
+ const userMessage = {
26
+ id: messages.length + 1,
27
+ role: 'user',
28
+ content: inputMessage,
29
+ timestamp: new Date()
30
+ };
31
+
32
+ setMessages(prev => [...prev, userMessage]);
33
+ setInputMessage('');
34
+ setIsLoading(true);
35
+
36
+ setTimeout(() => {
37
+ const aiResponse = {
38
+ id: messages.length + 2,
39
+ role: 'assistant',
40
+ content: `这是一个测试响应。在实际部署后,我将根据您配置的模型和知识库来回答问题。当前配置:模型 ${data.model},温度 ${data.temperature}。`,
41
+ timestamp: new Date()
42
+ };
43
+
44
+ setMessages(prev => [...prev, aiResponse]);
45
+ setIsLoading(false);
46
+ }, 1500);
47
+ };
48
+
49
+ const clearChat = () => {
50
+ setMessages([{
51
+ id: 1,
52
+ role: 'assistant',
53
+ content: `你好!我是${data.name || 'AI Assistant'}。我已经准备好为您提供帮助了。请告诉我您需要什么协助?`,
54
+ timestamp: new Date()
55
+ }]);
56
+ };
57
+
58
+ return (
59
+ <div className="space-y-8">
60
+ <div>
61
+ <h3 className="text-lg font-semibold text-gray-900 mb-6">测试对话</h3>
62
+
63
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
64
+ <div className="lg:col-span-2">
65
+ <div className="bg-gray-50 rounded-xl border border-gray-200 h-96 flex flex-col">
66
+ <div className="p-4 border-b border-gray-200 flex items-center justify-between">
67
+ <div className="flex items-center space-x-3">
68
+ <div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
69
+ <span className="text-sm">{data.avatar}</span>
70
+ </div>
71
+ <div>
72
+ <h4 className="font-medium text-gray-900">{data.name || '未命名Agent'}</h4>
73
+ <p className="text-xs text-gray-500">测试模式</p>
74
+ </div>
75
+ </div>
76
+ <button
77
+ onClick={clearChat}
78
+ className="px-3 py-2 text-sm text-gray-600 hover:bg-gray-200 rounded-lg transition-colors cursor-pointer"
79
+ >
80
+ 清空对话
81
+ </button>
82
+ </div>
83
+
84
+ <div className="flex-1 p-4 overflow-y-auto space-y-4">
85
+ {messages.map((message) => (
86
+ <div key={message.id} className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}>
87
+ <div className={`max-w-xs lg:max-w-md px-4 py-2 rounded-lg ${
88
+ message.role === 'user'
89
+ ? 'bg-blue-600 text-white'
90
+ : 'bg-white border border-gray-200 text-gray-900'
91
+ }`}>
92
+ <p className="text-sm">{message.content}</p>
93
+ <p className={`text-xs mt-1 ${
94
+ message.role === 'user' ? 'text-blue-100' : 'text-gray-500'
95
+ }`}>
96
+ {message.timestamp.toLocaleTimeString()}
97
+ </p>
98
+ </div>
99
+ </div>
100
+ ))}
101
+
102
+ {isLoading && (
103
+ <div className="flex justify-start">
104
+ <div className="bg-white border border-gray-200 rounded-lg px-4 py-2">
105
+ <div className="flex items-center space-x-2">
106
+ <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
107
+ <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
108
+ <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ )}
113
+ </div>
114
+
115
+ <div className="p-4 border-t border-gray-200">
116
+ <div className="flex space-x-2">
117
+ <input
118
+ type="text"
119
+ placeholder="输入测试消息..."
120
+ value={inputMessage}
121
+ onChange={(e) => setInputMessage(e.target.value)}
122
+ onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
123
+ className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
124
+ />
125
+ <button
126
+ onClick={sendMessage}
127
+ disabled={isLoading || !inputMessage.trim()}
128
+ className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors cursor-pointer whitespace-nowrap"
129
+ >
130
+ <div className="w-5 h-5 flex items-center justify-center">
131
+ <i className="ri-send-plane-line"></i>
132
+ </div>
133
+ </button>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ <div className="space-y-6">
140
+ <div className="bg-white border border-gray-200 rounded-lg p-4">
141
+ <h4 className="font-medium text-gray-900 mb-3">配置预览</h4>
142
+ <div className="space-y-2 text-sm">
143
+ <div className="flex justify-between">
144
+ <span className="text-gray-600">名称:</span>
145
+ <span className="text-gray-900">{data.name || '未设置'}</span>
146
+ </div>
147
+ <div className="flex justify-between">
148
+ <span className="text-gray-600">类型:</span>
149
+ <span className="text-gray-900">{data.type}</span>
150
+ </div>
151
+ <div className="flex justify-between">
152
+ <span className="text-gray-600">模型:</span>
153
+ <span className="text-gray-900">{data.model}</span>
154
+ </div>
155
+ <div className="flex justify-between">
156
+ <span className="text-gray-600">创造性:</span>
157
+ <span className="text-gray-900">{data.temperature}</span>
158
+ </div>
159
+ <div className="flex justify-between">
160
+ <span className="text-gray-600">最大长度:</span>
161
+ <span className="text-gray-900">{data.maxTokens}</span>
162
+ </div>
163
+ </div>
164
+ </div>
165
+
166
+ <div className="bg-white border border-gray-200 rounded-lg p-4">
167
+ <h4 className="font-medium text-gray-900 mb-3">测试建议</h4>
168
+ <div className="space-y-2">
169
+ <button
170
+ onClick={() => setInputMessage('你好,请介绍一下自己')}
171
+ className="w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 rounded cursor-pointer"
172
+ >
173
+ 基础问候测试
174
+ </button>
175
+ <button
176
+ onClick={() => setInputMessage('请帮我分析一下这个问题')}
177
+ className="w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 rounded cursor-pointer"
178
+ >
179
+ 功能测试
180
+ </button>
181
+ <button
182
+ onClick={() => setInputMessage('你能做什么?有什么限制吗?')}
183
+ className="w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 rounded cursor-pointer"
184
+ >
185
+ 能力边界测试
186
+ </button>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ );
194
+ }
app/agents/create/WorkflowConfig.tsx ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ interface WorkflowConfigProps {
6
+ data: any;
7
+ onChange: (data: any) => void;
8
+ }
9
+
10
+ export default function WorkflowConfig({ data, onChange }: WorkflowConfigProps) {
11
+ const [selectedWorkflows, setSelectedWorkflows] = useState(data.workflows || []);
12
+ const [searchTerm, setSearchTerm] = useState('');
13
+ const [filterCategory, setFilterCategory] = useState('all');
14
+
15
+ const availableWorkflows = [
16
+ {
17
+ id: 'wf-001',
18
+ name: '数据分析流程',
19
+ description: '自动化数据收集、清洗和分析',
20
+ category: 'data-processing',
21
+ icon: '📊',
22
+ status: 'active',
23
+ lastUsed: '2024-01-15',
24
+ executions: 156
25
+ },
26
+ {
27
+ id: 'wf-002',
28
+ name: '内容审核工作流',
29
+ description: '文本内容智能审核和分类',
30
+ category: 'content-moderation',
31
+ icon: '🔍',
32
+ status: 'active',
33
+ lastUsed: '2024-01-14',
34
+ executions: 89
35
+ },
36
+ {
37
+ id: 'wf-003',
38
+ name: '客服自动回复',
39
+ description: '基于知识库的智能客服响应',
40
+ category: 'customer-service',
41
+ icon: '💬',
42
+ status: 'active',
43
+ lastUsed: '2024-01-13',
44
+ executions: 234
45
+ },
46
+ {
47
+ id: 'wf-004',
48
+ name: '报告生成器',
49
+ description: '自动生成业务分析报告',
50
+ category: 'report-generation',
51
+ icon: '📄',
52
+ status: 'active',
53
+ lastUsed: '2024-01-12',
54
+ executions: 67
55
+ },
56
+ {
57
+ id: 'wf-005',
58
+ name: '邮件处理流程',
59
+ description: '邮件分类和自动回复',
60
+ category: 'email-automation',
61
+ icon: '📧',
62
+ status: 'active',
63
+ lastUsed: '2024-01-11',
64
+ executions: 145
65
+ },
66
+ {
67
+ id: 'wf-006',
68
+ name: '社交媒体监控',
69
+ description: '社交媒体内容监控和分析',
70
+ category: 'social-media',
71
+ icon: '📱',
72
+ status: 'active',
73
+ lastUsed: '2024-01-10',
74
+ executions: 78
75
+ }
76
+ ];
77
+
78
+ const categories = [
79
+ { key: 'all', label: '全部分类', count: availableWorkflows.length },
80
+ { key: 'data-processing', label: '数据处理', count: 1 },
81
+ { key: 'content-moderation', label: '内容审核', count: 1 },
82
+ { key: 'customer-service', label: '客服服务', count: 1 },
83
+ { key: 'report-generation', label: '报告生成', count: 1 },
84
+ { key: 'email-automation', label: '邮件自动化', count: 1 },
85
+ { key: 'social-media', label: '社交媒体', count: 1 }
86
+ ];
87
+
88
+ const filteredWorkflows = availableWorkflows.filter(workflow => {
89
+ const matchesSearch = workflow.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
90
+ workflow.description.toLowerCase().includes(searchTerm.toLowerCase());
91
+ const matchesCategory = filterCategory === 'all' || workflow.category === filterCategory;
92
+ return matchesSearch && matchesCategory;
93
+ });
94
+
95
+ const handleWorkflowToggle = (workflowId: string) => {
96
+ const isSelected = selectedWorkflows.includes(workflowId);
97
+ const updatedWorkflows = isSelected
98
+ ? selectedWorkflows.filter(id => id !== workflowId)
99
+ : [...selectedWorkflows, workflowId];
100
+
101
+ setSelectedWorkflows(updatedWorkflows);
102
+ onChange({ ...data, workflows: updatedWorkflows });
103
+ };
104
+
105
+ const handleSelectAll = () => {
106
+ const allIds = filteredWorkflows.map(wf => wf.id);
107
+ setSelectedWorkflows(allIds);
108
+ onChange({ ...data, workflows: allIds });
109
+ };
110
+
111
+ const handleDeselectAll = () => {
112
+ setSelectedWorkflows([]);
113
+ onChange({ ...data, workflows: [] });
114
+ };
115
+
116
+ return (
117
+ <div className="space-y-8">
118
+ <div>
119
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">工作流配置</h3>
120
+ <p className="text-gray-600 mb-6">选择Agent可以调用的工作流程,增强自动化处理能力</p>
121
+
122
+ <div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
123
+ <div className="lg:col-span-3 space-y-6">
124
+ {/* 搜索和筛选 */}
125
+ <div className="bg-white border border-gray-200 rounded-lg p-4">
126
+ <div className="flex flex-col sm:flex-row gap-4">
127
+ <div className="flex-1 relative">
128
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
129
+ <div className="w-4 h-4 flex items-center justify-center">
130
+ <i className="ri-search-line text-gray-400"></i>
131
+ </div>
132
+ </div>
133
+ <input
134
+ type="text"
135
+ placeholder="搜索工作流..."
136
+ value={searchTerm}
137
+ onChange={(e) => setSearchTerm(e.target.value)}
138
+ className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
139
+ />
140
+ </div>
141
+ <select
142
+ value={filterCategory}
143
+ onChange={(e) => setFilterCategory(e.target.value)}
144
+ className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm pr-8"
145
+ >
146
+ {categories.map(category => (
147
+ <option key={category.key} value={category.key}>
148
+ {category.label} ({category.count})
149
+ </option>
150
+ ))}
151
+ </select>
152
+ </div>
153
+
154
+ <div className="flex items-center justify-between mt-4">
155
+ <div className="text-sm text-gray-600">
156
+ 已选择 {selectedWorkflows.length} 个工作流
157
+ </div>
158
+ <div className="flex space-x-2">
159
+ <button
160
+ onClick={handleSelectAll}
161
+ className="px-3 py-1 text-sm text-blue-600 hover:bg-blue-50 rounded cursor-pointer"
162
+ >
163
+ 全选
164
+ </button>
165
+ <button
166
+ onClick={handleDeselectAll}
167
+ className="px-3 py-1 text-sm text-gray-600 hover:bg-gray-50 rounded cursor-pointer"
168
+ >
169
+ 清空
170
+ </button>
171
+ </div>
172
+ </div>
173
+ </div>
174
+
175
+ {/* 工作流列表 */}
176
+ <div className="space-y-4">
177
+ {filteredWorkflows.map((workflow) => (
178
+ <div
179
+ key={workflow.id}
180
+ className={`bg-white border rounded-lg p-4 cursor-pointer transition-all ${
181
+ selectedWorkflows.includes(workflow.id)
182
+ ? 'border-blue-500 bg-blue-50'
183
+ : 'border-gray-200 hover:border-gray-300'
184
+ }`}
185
+ onClick={() => handleWorkflowToggle(workflow.id)}
186
+ >
187
+ <div className="flex items-center justify-between">
188
+ <div className="flex items-center space-x-4">
189
+ <div className="w-12 h-12 bg-gray-100 rounded-lg flex items-center justify-center text-xl">
190
+ {workflow.icon}
191
+ </div>
192
+ <div className="flex-1">
193
+ <div className="flex items-center space-x-2">
194
+ <h4 className="font-medium text-gray-900">{workflow.name}</h4>
195
+ <span className="px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full">
196
+ {workflow.status === 'active' ? '活跃' : '暂停'}
197
+ </span>
198
+ </div>
199
+ <p className="text-sm text-gray-600 mt-1">{workflow.description}</p>
200
+ <div className="flex items-center space-x-4 mt-2 text-xs text-gray-500">
201
+ <span>执行次数: {workflow.executions}</span>
202
+ <span>最后使用: {workflow.lastUsed}</span>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ <div className="flex items-center space-x-3">
207
+ <button
208
+ onClick={(e) => {
209
+ e.stopPropagation();
210
+ alert('工作流详情功能开发中...');
211
+ }}
212
+ className="p-2 text-gray-400 hover:text-gray-600 rounded-lg transition-colors cursor-pointer"
213
+ >
214
+ <div className="w-4 h-4 flex items-center justify-center">
215
+ <i className="ri-eye-line"></i>
216
+ </div>
217
+ </button>
218
+ <div className={`w-5 h-5 rounded border-2 flex items-center justify-center ${
219
+ selectedWorkflows.includes(workflow.id)
220
+ ? 'border-blue-500 bg-blue-500'
221
+ : 'border-gray-300'
222
+ }`}>
223
+ {selectedWorkflows.includes(workflow.id) && (
224
+ <i className="ri-check-line text-white text-xs"></i>
225
+ )}
226
+ </div>
227
+ </div>
228
+ </div>
229
+ </div>
230
+ ))}
231
+ </div>
232
+
233
+ {filteredWorkflows.length === 0 && (
234
+ <div className="text-center py-12">
235
+ <div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
236
+ <i className="ri-search-line text-2xl text-gray-400"></i>
237
+ </div>
238
+ <p className="text-gray-500">没有找到匹配的工作流</p>
239
+ </div>
240
+ )}
241
+ </div>
242
+
243
+ <div className="space-y-6">
244
+ <div className="bg-white border border-gray-200 rounded-lg p-4">
245
+ <h4 className="font-medium text-gray-900 mb-3">已选择的工作流</h4>
246
+ {selectedWorkflows.length === 0 ? (
247
+ <p className="text-sm text-gray-500">还未选择任何工作流</p>
248
+ ) : (
249
+ <div className="space-y-2">
250
+ {selectedWorkflows.map((workflowId) => {
251
+ const workflow = availableWorkflows.find(w => w.id === workflowId);
252
+ return workflow ? (
253
+ <div key={workflowId} className="flex items-center justify-between py-2 px-3 bg-gray-50 rounded">
254
+ <div className="flex items-center space-x-2">
255
+ <span className="text-sm">{workflow.icon}</span>
256
+ <span className="text-sm font-medium text-gray-900">{workflow.name}</span>
257
+ </div>
258
+ <button
259
+ onClick={() => handleWorkflowToggle(workflowId)}
260
+ className="w-4 h-4 flex items-center justify-center text-gray-400 hover:text-red-600 cursor-pointer"
261
+ >
262
+ <i className="ri-close-line text-xs"></i>
263
+ </button>
264
+ </div>
265
+ ) : null;
266
+ })}
267
+ </div>
268
+ )}
269
+ </div>
270
+
271
+ <div className="bg-white border border-gray-200 rounded-lg p-4">
272
+ <h4 className="font-medium text-gray-900 mb-3">工作流配置</h4>
273
+ <div className="space-y-3">
274
+ <div className="flex items-center justify-between">
275
+ <span className="text-sm text-gray-700">自动触发</span>
276
+ <div className="w-10 h-6 bg-blue-600 rounded-full relative cursor-pointer">
277
+ <div className="w-4 h-4 bg-white rounded-full absolute right-1 top-1"></div>
278
+ </div>
279
+ </div>
280
+ <div className="flex items-center justify-between">
281
+ <span className="text-sm text-gray-700">并行执行</span>
282
+ <div className="w-10 h-6 bg-gray-300 rounded-full relative cursor-pointer">
283
+ <div className="w-4 h-4 bg-white rounded-full absolute left-1 top-1"></div>
284
+ </div>
285
+ </div>
286
+ <div className="flex items-center justify-between">
287
+ <span className="text-sm text-gray-700">错误重试</span>
288
+ <div className="w-10 h-6 bg-blue-600 rounded-full relative cursor-pointer">
289
+ <div className="w-4 h-4 bg-white rounded-full absolute right-1 top-1"></div>
290
+ </div>
291
+ </div>
292
+ </div>
293
+ </div>
294
+
295
+ <div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
296
+ <div className="flex items-start space-x-3">
297
+ <div className="w-5 h-5 flex items-center justify-center mt-0.5">
298
+ <i className="ri-information-line text-blue-600"></i>
299
+ </div>
300
+ <div>
301
+ <h4 className="font-medium text-blue-900 mb-1">使用提示</h4>
302
+ <p className="text-sm text-blue-800">
303
+ 选择的工作流将与Agent集成,用户可以通过对话触发相应的自动化流程。建议选择与Agent功能相关的工作流。
304
+ </p>
305
+ </div>
306
+ </div>
307
+ </div>
308
+ </div>
309
+ </div>
310
+ </div>
311
+ </div>
312
+ );
313
+ }
app/agents/create/page.tsx ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ 'use client';
3
+
4
+ import Header from '@/components/Header';
5
+ import Sidebar from '@/components/Sidebar';
6
+ import AgentBuilder from './AgentBuilder';
7
+
8
+ export default function CreateAgentPage() {
9
+ return (
10
+ <div className="min-h-screen bg-gray-50">
11
+ <Header />
12
+ <div className="flex">
13
+ <Sidebar />
14
+ <main className="flex-1 p-8">
15
+ <div className="max-w-7xl mx-auto">
16
+ <div className="mb-8">
17
+ <h1 className="text-3xl font-bold text-gray-900 mb-2">创建新Agent</h1>
18
+ <p className="text-gray-600">配置和定制您的AI Agent功能</p>
19
+ </div>
20
+
21
+ <AgentBuilder />
22
+ </div>
23
+ </main>
24
+ </div>
25
+ </div>
26
+ );
27
+ }
app/agents/page.tsx ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ 'use client';
3
+
4
+ import Header from '@/components/Header';
5
+ import Sidebar from '@/components/Sidebar';
6
+ import AgentList from './AgentList';
7
+ import AgentFilters from './AgentFilters';
8
+ import Link from 'next/link';
9
+
10
+ export default function AgentsPage() {
11
+ return (
12
+ <div className="min-h-screen bg-gray-50">
13
+ <Header />
14
+ <div className="flex">
15
+ <Sidebar />
16
+ <main className="flex-1 p-8">
17
+ <div className="max-w-7xl mx-auto">
18
+ <div className="mb-8">
19
+ <div className="flex items-center justify-between">
20
+ <div>
21
+ <h1 className="text-3xl font-bold text-gray-900 mb-2">Agent管理</h1>
22
+ <p className="text-gray-600">创建、管理和部署您的AI Agent</p>
23
+ </div>
24
+ <Link href="/agents/create" className="bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap">
25
+ <div className="w-5 h-5 flex items-center justify-center inline-block mr-2">
26
+ <i className="ri-add-line"></i>
27
+ </div>
28
+ 创建Agent
29
+ </Link>
30
+ </div>
31
+ </div>
32
+
33
+ <AgentFilters />
34
+ <AgentList />
35
+ </div>
36
+ </main>
37
+ </div>
38
+ </div>
39
+ );
40
+ }
app/dashboard/ActivityFeed.tsx ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ export default function ActivityFeed() {
4
+ const activities = [
5
+ {
6
+ id: 1,
7
+ type: 'create',
8
+ title: '创建了新的Agent',
9
+ description: '智能客服助手 v2.0',
10
+ time: '2小时前',
11
+ icon: 'ri-add-circle-line',
12
+ color: 'blue'
13
+ },
14
+ {
15
+ id: 2,
16
+ type: 'update',
17
+ title: '更新了工作流',
18
+ description: '文档处理流程优化',
19
+ time: '4小时前',
20
+ icon: 'ri-edit-line',
21
+ color: 'green'
22
+ },
23
+ {
24
+ id: 3,
25
+ type: 'deploy',
26
+ title: '部署了Agent',
27
+ description: '代码生成器已上线',
28
+ time: '6小时前',
29
+ icon: 'ri-rocket-line',
30
+ color: 'purple'
31
+ },
32
+ {
33
+ id: 4,
34
+ type: 'upload',
35
+ title: '上传了知识库',
36
+ description: '产品文档已更新',
37
+ time: '8小时前',
38
+ icon: 'ri-upload-line',
39
+ color: 'orange'
40
+ }
41
+ ];
42
+
43
+ return (
44
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200">
45
+ <div className="p-6 border-b border-gray-200">
46
+ <h2 className="text-lg font-semibold text-gray-900">最新动态</h2>
47
+ </div>
48
+
49
+ <div className="p-6">
50
+ <div className="space-y-4">
51
+ {activities.map((activity) => (
52
+ <div key={activity.id} className="flex items-start space-x-4">
53
+ <div className={`w-8 h-8 rounded-full flex items-center justify-center bg-${activity.color}-100 mt-1`}>
54
+ <i className={`${activity.icon} text-sm text-${activity.color}-600`}></i>
55
+ </div>
56
+ <div className="flex-1">
57
+ <h4 className="font-medium text-gray-900">{activity.title}</h4>
58
+ <p className="text-sm text-gray-600 mt-1">{activity.description}</p>
59
+ <p className="text-xs text-gray-500 mt-2">{activity.time}</p>
60
+ </div>
61
+ </div>
62
+ ))}
63
+ </div>
64
+ </div>
65
+ </div>
66
+ );
67
+ }
app/dashboard/DashboardStats.tsx ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ export default function DashboardStats() {
4
+ const stats = [
5
+ {
6
+ title: '活跃Agent',
7
+ value: '12',
8
+ change: '+2.5%',
9
+ trend: 'up',
10
+ icon: 'ri-robot-line',
11
+ color: 'blue'
12
+ },
13
+ {
14
+ title: '今日调用',
15
+ value: '2,845',
16
+ change: '+12.3%',
17
+ trend: 'up',
18
+ icon: 'ri-rocket-line',
19
+ color: 'green'
20
+ },
21
+ {
22
+ title: '工作流运行',
23
+ value: '156',
24
+ change: '+8.7%',
25
+ trend: 'up',
26
+ icon: 'ri-flow-chart',
27
+ color: 'purple'
28
+ },
29
+ {
30
+ title: '知识库文档',
31
+ value: '234',
32
+ change: '+5.2%',
33
+ trend: 'up',
34
+ icon: 'ri-database-line',
35
+ color: 'orange'
36
+ }
37
+ ];
38
+
39
+ return (
40
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
41
+ {stats.map((stat, index) => (
42
+ <div key={index} className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
43
+ <div className="flex items-center justify-between">
44
+ <div>
45
+ <p className="text-sm font-medium text-gray-600">{stat.title}</p>
46
+ <p className="text-2xl font-bold text-gray-900 mt-1">{stat.value}</p>
47
+ </div>
48
+ <div className={`w-12 h-12 rounded-lg flex items-center justify-center bg-${stat.color}-100`}>
49
+ <i className={`${stat.icon} text-xl text-${stat.color}-600`}></i>
50
+ </div>
51
+ </div>
52
+ <div className="mt-4 flex items-center">
53
+ <div className="w-4 h-4 flex items-center justify-center">
54
+ <i className="ri-arrow-up-line text-green-500 text-sm"></i>
55
+ </div>
56
+ <span className="text-sm text-green-600 ml-1">{stat.change}</span>
57
+ <span className="text-sm text-gray-500 ml-2">较上周</span>
58
+ </div>
59
+ </div>
60
+ ))}
61
+ </div>
62
+ );
63
+ }
app/dashboard/QuickActions.tsx ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+
5
+ export default function QuickActions() {
6
+ const actions = [
7
+ {
8
+ title: '创建Agent',
9
+ description: '从零开始构建新的AI Agent',
10
+ icon: 'ri-add-circle-line',
11
+ href: '/agents/create',
12
+ color: 'blue'
13
+ },
14
+ {
15
+ title: '导入模板',
16
+ description: '使用预设模板快速开始',
17
+ icon: 'ri-download-line',
18
+ href: '/templates',
19
+ color: 'green'
20
+ },
21
+ {
22
+ title: '创建工作流',
23
+ description: '设计复杂的自动化流程',
24
+ icon: 'ri-flow-chart',
25
+ href: '/workflows/create',
26
+ color: 'purple'
27
+ },
28
+ {
29
+ title: '上传知识库',
30
+ description: '添加文档和数据源',
31
+ icon: 'ri-upload-line',
32
+ href: '/knowledge/upload',
33
+ color: 'orange'
34
+ }
35
+ ];
36
+
37
+ return (
38
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200">
39
+ <div className="p-6 border-b border-gray-200">
40
+ <h2 className="text-lg font-semibold text-gray-900">快速操作</h2>
41
+ </div>
42
+
43
+ <div className="p-6">
44
+ <div className="space-y-4">
45
+ {actions.map((action, index) => (
46
+ <Link
47
+ key={index}
48
+ href={action.href}
49
+ className="flex items-center space-x-4 p-4 hover:bg-gray-50 rounded-lg cursor-pointer transition-colors"
50
+ >
51
+ <div className={`w-10 h-10 rounded-lg flex items-center justify-center bg-${action.color}-100`}>
52
+ <i className={`${action.icon} text-lg text-${action.color}-600`}></i>
53
+ </div>
54
+ <div>
55
+ <h3 className="font-medium text-gray-900">{action.title}</h3>
56
+ <p className="text-sm text-gray-500">{action.description}</p>
57
+ </div>
58
+ </Link>
59
+ ))}
60
+ </div>
61
+ </div>
62
+ </div>
63
+ );
64
+ }
app/dashboard/RecentAgents.tsx ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+
5
+ export default function RecentAgents() {
6
+ const agents = [
7
+ {
8
+ id: 1,
9
+ name: '智能客服助手',
10
+ type: '对话型',
11
+ status: '运行中',
12
+ lastUsed: '2分钟前',
13
+ calls: 245,
14
+ avatar: '🤖'
15
+ },
16
+ {
17
+ id: 2,
18
+ name: '文档分析器',
19
+ type: '分析型',
20
+ status: '运行中',
21
+ lastUsed: '15分钟前',
22
+ calls: 128,
23
+ avatar: '📄'
24
+ },
25
+ {
26
+ id: 3,
27
+ name: '代码生成器',
28
+ type: '生成型',
29
+ status: '暂停',
30
+ lastUsed: '1小时前',
31
+ calls: 89,
32
+ avatar: '💻'
33
+ },
34
+ {
35
+ id: 4,
36
+ name: '数据可视化',
37
+ type: '图表型',
38
+ status: '运行中',
39
+ lastUsed: '30分钟前',
40
+ calls: 67,
41
+ avatar: '📊'
42
+ }
43
+ ];
44
+
45
+ return (
46
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200">
47
+ <div className="p-6 border-b border-gray-200">
48
+ <div className="flex items-center justify-between">
49
+ <h2 className="text-lg font-semibold text-gray-900">最近使用的Agent</h2>
50
+ <Link href="/agents" className="text-blue-600 hover:text-blue-700 text-sm font-medium cursor-pointer">
51
+ 查看全部
52
+ </Link>
53
+ </div>
54
+ </div>
55
+
56
+ <div className="p-6">
57
+ <div className="space-y-4">
58
+ {agents.map((agent) => (
59
+ <div key={agent.id} className="flex items-center justify-between p-4 hover:bg-gray-50 rounded-lg cursor-pointer">
60
+ <div className="flex items-center space-x-4">
61
+ <div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
62
+ <span className="text-lg">{agent.avatar}</span>
63
+ </div>
64
+ <div>
65
+ <h3 className="font-medium text-gray-900">{agent.name}</h3>
66
+ <div className="flex items-center space-x-4 mt-1">
67
+ <span className="text-sm text-gray-500">{agent.type}</span>
68
+ <span className={`px-2 py-1 text-xs rounded-full ${
69
+ agent.status === '运行中'
70
+ ? 'bg-green-100 text-green-800'
71
+ : 'bg-yellow-100 text-yellow-800'
72
+ }`}>
73
+ {agent.status}
74
+ </span>
75
+ </div>
76
+ </div>
77
+ </div>
78
+
79
+ <div className="text-right">
80
+ <p className="text-sm font-medium text-gray-900">{agent.calls} 次调用</p>
81
+ <p className="text-xs text-gray-500">{agent.lastUsed}</p>
82
+ </div>
83
+ </div>
84
+ ))}
85
+ </div>
86
+ </div>
87
+ </div>
88
+ );
89
+ }
app/dashboard/page.tsx ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import Header from '@/components/Header';
4
+ import Sidebar from '@/components/Sidebar';
5
+ import DashboardStats from './DashboardStats';
6
+ import RecentAgents from './RecentAgents';
7
+ import ActivityFeed from './ActivityFeed';
8
+ import QuickActions from './QuickActions';
9
+
10
+ export default function DashboardPage() {
11
+ return (
12
+ <div className="min-h-screen bg-gray-50">
13
+ <Header />
14
+ <div className="flex">
15
+ <Sidebar />
16
+ <main className="flex-1 p-8">
17
+ <div className="max-w-7xl mx-auto">
18
+ <div className="mb-8">
19
+ <h1 className="text-3xl font-bold text-gray-900 mb-2">工作台</h1>
20
+ <p className="text-gray-600">管理您的AI Agent应用和工作流</p>
21
+ </div>
22
+
23
+ <DashboardStats />
24
+
25
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-8">
26
+ <div className="lg:col-span-2">
27
+ <RecentAgents />
28
+ </div>
29
+ <div>
30
+ <QuickActions />
31
+ <div className="mt-8">
32
+ <ActivityFeed />
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </main>
38
+ </div>
39
+ </div>
40
+ );
41
+ }
app/globals.css ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ @import url('https://cdnjs.cloudflare.com/ajax/libs/remixicon/4.5.0/remixicon.min.css');
2
+ @tailwind base;
3
+ @tailwind components;
4
+ @tailwind utilities;
app/knowledge/KnowledgeFilters.tsx ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ export default function KnowledgeFilters() {
6
+ const [activeCategory, setActiveCategory] = useState('all');
7
+ const [activeStatus, setActiveStatus] = useState('all');
8
+ const [searchTerm, setSearchTerm] = useState('');
9
+ const [sortBy, setSortBy] = useState('lastUpdated');
10
+
11
+ const categories = [
12
+ { key: 'all', label: '全部分类', count: 6 },
13
+ { key: 'general', label: '通用', count: 1 },
14
+ { key: 'tech', label: '技术', count: 1 },
15
+ { key: 'product', label: '产品', count: 1 },
16
+ { key: 'service', label: '客服', count: 1 },
17
+ { key: 'legal', label: '法务', count: 1 },
18
+ { key: 'sales', label: '销售', count: 1 }
19
+ ];
20
+
21
+ const statuses = [
22
+ { key: 'all', label: '全部状态' },
23
+ { key: 'active', label: '运行中' },
24
+ { key: 'inactive', label: '已停用' },
25
+ { key: 'updating', label: '更新中' }
26
+ ];
27
+
28
+ const sortOptions = [
29
+ { key: 'lastUpdated', label: '最后更新' },
30
+ { key: 'name', label: '名称' },
31
+ { key: 'items', label: '内容数量' },
32
+ { key: 'usage', label: '使用次数' },
33
+ { key: 'size', label: '文件大小' }
34
+ ];
35
+
36
+ return (
37
+ <div className="bg-white border border-gray-200 rounded-lg p-6 mb-6">
38
+ <div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
39
+ {/* 搜索 */}
40
+ <div className="lg:col-span-1">
41
+ <div className="relative">
42
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
43
+ <div className="w-4 h-4 flex items-center justify-center">
44
+ <i className="ri-search-line text-gray-400"></i>
45
+ </div>
46
+ </div>
47
+ <input
48
+ type="text"
49
+ placeholder="搜索知识库..."
50
+ value={searchTerm}
51
+ onChange={(e) => setSearchTerm(e.target.value)}
52
+ className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
53
+ />
54
+ </div>
55
+ </div>
56
+
57
+ {/* 分类筛选 */}
58
+ <div className="lg:col-span-1">
59
+ <select
60
+ value={activeCategory}
61
+ onChange={(e) => setActiveCategory(e.target.value)}
62
+ className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm bg-white pr-8"
63
+ >
64
+ {categories.map((category) => (
65
+ <option key={category.key} value={category.key}>
66
+ {category.label} ({category.count})
67
+ </option>
68
+ ))}
69
+ </select>
70
+ </div>
71
+
72
+ {/* 状态筛选 */}
73
+ <div className="lg:col-span-1">
74
+ <select
75
+ value={activeStatus}
76
+ onChange={(e) => setActiveStatus(e.target.value)}
77
+ className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm bg-white pr-8"
78
+ >
79
+ {statuses.map((status) => (
80
+ <option key={status.key} value={status.key}>
81
+ {status.label}
82
+ </option>
83
+ ))}
84
+ </select>
85
+ </div>
86
+
87
+ {/* 排序 */}
88
+ <div className="lg:col-span-1">
89
+ <select
90
+ value={sortBy}
91
+ onChange={(e) => setSortBy(e.target.value)}
92
+ className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm bg-white pr-8"
93
+ >
94
+ {sortOptions.map((option) => (
95
+ <option key={option.key} value={option.key}>
96
+ 按{option.label}排序
97
+ </option>
98
+ ))}
99
+ </select>
100
+ </div>
101
+ </div>
102
+
103
+ {/* 快速标签 */}
104
+ <div className="mt-4 pt-4 border-t border-gray-200">
105
+ <div className="flex items-center space-x-2">
106
+ <span className="text-sm text-gray-600">快速筛选:</span>
107
+ <div className="flex flex-wrap gap-2">
108
+ {['默认知识库', '高使用率', '最近更新', '大容量', '技术相关'].map((tag) => (
109
+ <button
110
+ key={tag}
111
+ className="px-3 py-1 bg-gray-100 text-gray-700 text-sm rounded-full hover:bg-gray-200 transition-colors cursor-pointer whitespace-nowrap"
112
+ >
113
+ {tag}
114
+ </button>
115
+ ))}
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ );
121
+ }
app/knowledge/KnowledgeLibrary.tsx ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import Link from 'next/link';
5
+
6
+ export default function KnowledgeLibrary() {
7
+ const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
8
+ const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState<string[]>([]);
9
+
10
+ const knowledgeBases = [
11
+ {
12
+ id: 'kb-001',
13
+ name: '通用知识库',
14
+ description: '包含基础常识和通用信息,适用于大多数场景的问答需求',
15
+ category: '通用',
16
+ items: 1240,
17
+ size: '15.6 MB',
18
+ status: 'active',
19
+ lastUpdated: '2024-01-15',
20
+ creator: '系统默认',
21
+ isDefault: true,
22
+ usage: 856,
23
+ tags: ['通用', '基础', '默认']
24
+ },
25
+ {
26
+ id: 'kb-002',
27
+ name: '技术文档库',
28
+ description: '收录了各种技术文档、API说明和开发指南,为技术问题提供专业解答',
29
+ category: '技术',
30
+ items: 567,
31
+ size: '8.3 MB',
32
+ status: 'active',
33
+ lastUpdated: '2024-01-14',
34
+ creator: '张工程师',
35
+ isDefault: false,
36
+ usage: 423,
37
+ tags: ['技术', 'API', '开发']
38
+ },
39
+ {
40
+ id: 'kb-003',
41
+ name: '产品说明库',
42
+ description: '产品功能介绍、使用手册和操作指南,帮助用户更好地了解产品特性',
43
+ category: '产品',
44
+ items: 892,
45
+ size: '12.1 MB',
46
+ status: 'active',
47
+ lastUpdated: '2024-01-13',
48
+ creator: '李产品经理',
49
+ isDefault: false,
50
+ usage: 678,
51
+ tags: ['产品', '手册', '功能']
52
+ },
53
+ {
54
+ id: 'kb-004',
55
+ name: '客服问答库',
56
+ description: '客服常见问题和标准解答,提升客户服务效率和质量',
57
+ category: '客服',
58
+ items: 345,
59
+ size: '4.2 MB',
60
+ status: 'active',
61
+ lastUpdated: '2024-01-12',
62
+ creator: '王客服主管',
63
+ isDefault: false,
64
+ usage: 234,
65
+ tags: ['客服', '问答', 'FAQ']
66
+ },
67
+ {
68
+ id: 'kb-005',
69
+ name: '法律法规库',
70
+ description: '相关法律条文、政策解读和合规要求,确保业务合规运营',
71
+ category: '法务',
72
+ items: 178,
73
+ size: '6.8 MB',
74
+ status: 'active',
75
+ lastUpdated: '2024-01-11',
76
+ creator: '赵法务',
77
+ isDefault: false,
78
+ usage: 89,
79
+ tags: ['法律', '合规', '政策']
80
+ },
81
+ {
82
+ id: 'kb-006',
83
+ name: '销售话术库',
84
+ description: '销售技巧、话术模板和客户案例,提升销售团队的专业水平',
85
+ category: '销售',
86
+ items: 456,
87
+ size: '5.9 MB',
88
+ status: 'inactive',
89
+ lastUpdated: '2024-01-10',
90
+ creator: '陈销售总监',
91
+ isDefault: false,
92
+ usage: 156,
93
+ tags: ['销售', '话术', '案例']
94
+ }
95
+ ];
96
+
97
+ const toggleSelection = (id: string) => {
98
+ setSelectedKnowledgeBases(prev =>
99
+ prev.includes(id)
100
+ ? prev.filter(kbId => kbId !== id)
101
+ : [...prev, id]
102
+ );
103
+ };
104
+
105
+ const selectAll = () => {
106
+ setSelectedKnowledgeBases(knowledgeBases.map(kb => kb.id));
107
+ };
108
+
109
+ const clearSelection = () => {
110
+ setSelectedKnowledgeBases([]);
111
+ };
112
+
113
+ const getStatusColor = (status: string) => {
114
+ switch (status) {
115
+ case 'active':
116
+ return 'bg-green-100 text-green-800';
117
+ case 'inactive':
118
+ return 'bg-red-100 text-red-800';
119
+ case 'updating':
120
+ return 'bg-yellow-100 text-yellow-800';
121
+ default:
122
+ return 'bg-gray-100 text-gray-800';
123
+ }
124
+ };
125
+
126
+ const getStatusText = (status: string) => {
127
+ switch (status) {
128
+ case 'active':
129
+ return '运行中';
130
+ case 'inactive':
131
+ return '已停用';
132
+ case 'updating':
133
+ return '更新中';
134
+ default:
135
+ return '未知';
136
+ }
137
+ };
138
+
139
+ return (
140
+ <div className="space-y-6">
141
+ {/* 操作栏 */}
142
+ <div className="bg-white border border-gray-200 rounded-lg p-4">
143
+ <div className="flex items-center justify-between">
144
+ <div className="flex items-center space-x-4">
145
+ <div className="flex items-center space-x-2">
146
+ <input
147
+ type="checkbox"
148
+ checked={selectedKnowledgeBases.length === knowledgeBases.length}
149
+ onChange={(e) => e.target.checked ? selectAll() : clearSelection()}
150
+ className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
151
+ />
152
+ <span className="text-sm text-gray-600">
153
+ {selectedKnowledgeBases.length > 0
154
+ ? `已选择 ${selectedKnowledgeBases.length} 个知识库`
155
+ : '全选'
156
+ }
157
+ </span>
158
+ </div>
159
+
160
+ {selectedKnowledgeBases.length > 0 && (
161
+ <div className="flex items-center space-x-2">
162
+ <button className="px-3 py-1 text-sm bg-blue-100 text-blue-700 rounded hover:bg-blue-200 transition-colors cursor-pointer whitespace-nowrap">
163
+ 批量启用
164
+ </button>
165
+ <button className="px-3 py-1 text-sm bg-red-100 text-red-700 rounded hover:bg-red-200 transition-colors cursor-pointer whitespace-nowrap">
166
+ 批量停用
167
+ </button>
168
+ <button className="px-3 py-1 text-sm bg-gray-100 text-gray-700 rounded hover:bg-gray-200 transition-colors cursor-pointer whitespace-nowrap">
169
+ 批量删除
170
+ </button>
171
+ </div>
172
+ )}
173
+ </div>
174
+
175
+ <div className="flex items-center space-x-2">
176
+ <button
177
+ onClick={() => setViewMode('grid')}
178
+ className={`p-2 rounded ${viewMode === 'grid' ? 'bg-blue-100 text-blue-600' : 'text-gray-400 hover:text-gray-600'} transition-colors cursor-pointer`}
179
+ >
180
+ <div className="w-4 h-4 flex items-center justify-center">
181
+ <i className="ri-layout-grid-line"></i>
182
+ </div>
183
+ </button>
184
+ <button
185
+ onClick={() => setViewMode('list')}
186
+ className={`p-2 rounded ${viewMode === 'list' ? 'bg-blue-100 text-blue-600' : 'text-gray-400 hover:text-gray-600'} transition-colors cursor-pointer`}
187
+ >
188
+ <div className="w-4 h-4 flex items-center justify-center">
189
+ <i className="ri-list-unordered"></i>
190
+ </div>
191
+ </button>
192
+ </div>
193
+ </div>
194
+ </div>
195
+
196
+ {/* 知识库列表 */}
197
+ {viewMode === 'grid' ? (
198
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
199
+ {knowledgeBases.map((kb) => (
200
+ <div key={kb.id} className="bg-white border border-gray-200 rounded-lg hover:shadow-md transition-all duration-200">
201
+ <div className="p-6">
202
+ <div className="flex items-start justify-between mb-4">
203
+ <div className="flex items-center space-x-3">
204
+ <input
205
+ type="checkbox"
206
+ checked={selectedKnowledgeBases.includes(kb.id)}
207
+ onChange={() => toggleSelection(kb.id)}
208
+ className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
209
+ />
210
+ <div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
211
+ <i className="ri-book-line text-xl text-white"></i>
212
+ </div>
213
+ <div className="flex-1">
214
+ <div className="flex items-center space-x-2">
215
+ <h3 className="font-semibold text-gray-900">{kb.name}</h3>
216
+ {kb.isDefault && (
217
+ <span className="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full">默认</span>
218
+ )}
219
+ </div>
220
+ <span className={`inline-block px-2 py-1 text-xs rounded-full mt-1 ${getStatusColor(kb.status)}`}>
221
+ {getStatusText(kb.status)}
222
+ </span>
223
+ </div>
224
+ </div>
225
+
226
+ <div className="relative">
227
+ <button className="p-2 text-gray-400 hover:text-gray-600 transition-colors cursor-pointer">
228
+ <div className="w-4 h-4 flex items-center justify-center">
229
+ <i className="ri-more-line"></i>
230
+ </div>
231
+ </button>
232
+ </div>
233
+ </div>
234
+
235
+ <p className="text-gray-600 text-sm mb-4 line-clamp-3">{kb.description}</p>
236
+
237
+ <div className="space-y-3 mb-4">
238
+ <div className="flex items-center justify-between text-sm">
239
+ <span className="text-gray-500">内容条目</span>
240
+ <span className="font-medium text-gray-900">{kb.items.toLocaleString()}</span>
241
+ </div>
242
+ <div className="flex items-center justify-between text-sm">
243
+ <span className="text-gray-500">存储大小</span>
244
+ <span className="font-medium text-gray-900">{kb.size}</span>
245
+ </div>
246
+ <div className="flex items-center justify-between text-sm">
247
+ <span className="text-gray-500">使用次数</span>
248
+ <span className="font-medium text-gray-900">{kb.usage.toLocaleString()}</span>
249
+ </div>
250
+ <div className="flex items-center justify-between text-sm">
251
+ <span className="text-gray-500">最后更新</span>
252
+ <span className="font-medium text-gray-900">{kb.lastUpdated}</span>
253
+ </div>
254
+ </div>
255
+
256
+ <div className="flex flex-wrap gap-1 mb-4">
257
+ {kb.tags.map((tag, index) => (
258
+ <span key={index} className="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded">
259
+ {tag}
260
+ </span>
261
+ ))}
262
+ </div>
263
+
264
+ <div className="flex items-center justify-between pt-4 border-t border-gray-200">
265
+ <div className="flex items-center space-x-2">
266
+ <div className="w-6 h-6 bg-gray-200 rounded-full flex items-center justify-center">
267
+ <i className="ri-user-line text-xs text-gray-600"></i>
268
+ </div>
269
+ <span className="text-xs text-gray-500">{kb.creator}</span>
270
+ </div>
271
+
272
+ <div className="flex items-center space-x-2">
273
+ <Link
274
+ href={`/knowledge/${kb.id}`}
275
+ className="px-3 py-1 bg-blue-100 text-blue-700 text-xs rounded hover:bg-blue-200 transition-colors cursor-pointer whitespace-nowrap"
276
+ >
277
+ 查看详情
278
+ </Link>
279
+ </div>
280
+ </div>
281
+ </div>
282
+ </div>
283
+ ))}
284
+ </div>
285
+ ) : (
286
+ <div className="bg-white border border-gray-200 rounded-lg overflow-hidden">
287
+ <div className="overflow-x-auto">
288
+ <table className="w-full">
289
+ <thead className="bg-gray-50 border-b border-gray-200">
290
+ <tr>
291
+ <th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
292
+ <input
293
+ type="checkbox"
294
+ checked={selectedKnowledgeBases.length === knowledgeBases.length}
295
+ onChange={(e) => e.target.checked ? selectAll() : clearSelection()}
296
+ className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
297
+ />
298
+ </th>
299
+ <th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">名称</th>
300
+ <th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">分类</th>
301
+ <th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">条目数</th>
302
+ <th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">大小</th>
303
+ <th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">使用次数</th>
304
+ <th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
305
+ <th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">最后更新</th>
306
+ <th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
307
+ </tr>
308
+ </thead>
309
+ <tbody className="bg-white divide-y divide-gray-200">
310
+ {knowledgeBases.map((kb) => (
311
+ <tr key={kb.id} className="hover:bg-gray-50 transition-colors">
312
+ <td className="px-6 py-4 whitespace-nowrap">
313
+ <input
314
+ type="checkbox"
315
+ checked={selectedKnowledgeBases.includes(kb.id)}
316
+ onChange={() => toggleSelection(kb.id)}
317
+ className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
318
+ />
319
+ </td>
320
+ <td className="px-6 py-4 whitespace-nowrap">
321
+ <div className="flex items-center">
322
+ <div className="w-10 h-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center mr-3">
323
+ <i className="ri-book-line text-white"></i>
324
+ </div>
325
+ <div>
326
+ <div className="flex items-center space-x-2">
327
+ <div className="text-sm font-medium text-gray-900">{kb.name}</div>
328
+ {kb.isDefault && (
329
+ <span className="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full">默认</span>
330
+ )}
331
+ </div>
332
+ <div className="text-sm text-gray-500 max-w-xs truncate">{kb.description}</div>
333
+ </div>
334
+ </div>
335
+ </td>
336
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{kb.category}</td>
337
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{kb.items.toLocaleString()}</td>
338
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{kb.size}</td>
339
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{kb.usage.toLocaleString()}</td>
340
+ <td className="px-6 py-4 whitespace-nowrap">
341
+ <span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getStatusColor(kb.status)}`}>
342
+ {getStatusText(kb.status)}
343
+ </span>
344
+ </td>
345
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{kb.lastUpdated}</td>
346
+ <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
347
+ <div className="flex items-center space-x-3">
348
+ <Link
349
+ href={`/knowledge/${kb.id}`}
350
+ className="text-blue-600 hover:text-blue-900 cursor-pointer"
351
+ >
352
+ 查看
353
+ </Link>
354
+ <button className="text-gray-600 hover:text-gray-900 cursor-pointer">编辑</button>
355
+ <button className="text-red-600 hover:text-red-900 cursor-pointer">删除</button>
356
+ </div>
357
+ </td>
358
+ </tr>
359
+ ))}
360
+ </tbody>
361
+ </table>
362
+ </div>
363
+ </div>
364
+ )}
365
+
366
+ {/* 分页 */}
367
+ <div className="flex items-center justify-between">
368
+ <div className="text-sm text-gray-500">
369
+ 共 {knowledgeBases.length} 个知识库
370
+ </div>
371
+ <div className="flex items-center space-x-2">
372
+ <button className="px-3 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap text-sm">
373
+ 上一页
374
+ </button>
375
+ <span className="px-3 py-2 bg-blue-600 text-white rounded-lg text-sm">1</span>
376
+ <button className="px-3 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap text-sm">
377
+ 下一页
378
+ </button>
379
+ </div>
380
+ </div>
381
+ </div>
382
+ );
383
+ }
app/knowledge/create/KnowledgeBuilder.tsx ADDED
@@ -0,0 +1,549 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+
6
+ export default function KnowledgeBuilder() {
7
+ const router = useRouter();
8
+ const [currentStep, setCurrentStep] = useState(0);
9
+ const [knowledgeData, setKnowledgeData] = useState({
10
+ name: '',
11
+ description: '',
12
+ category: 'general',
13
+ isPublic: false,
14
+ tags: [],
15
+ files: [],
16
+ webSources: [],
17
+ manualContent: '',
18
+ processingSettings: {
19
+ autoChunk: true,
20
+ smartDedup: true,
21
+ generateSummary: false,
22
+ chunkSize: 1000,
23
+ overlap: 200
24
+ }
25
+ });
26
+
27
+ const [uploadedFiles, setUploadedFiles] = useState([]);
28
+ const [webSources, setWebSources] = useState([]);
29
+ const [newUrl, setNewUrl] = useState('');
30
+ const [activeTab, setActiveTab] = useState('files');
31
+
32
+ const steps = [
33
+ { id: 0, name: '基本信息', icon: 'ri-information-line' },
34
+ { id: 1, name: '内容添加', icon: 'ri-file-add-line' },
35
+ { id: 2, name: '处理配置', icon: 'ri-settings-3-line' },
36
+ { id: 3, name: '预览测试', icon: 'ri-eye-line' }
37
+ ];
38
+
39
+ const categories = [
40
+ { key: 'general', label: '通用知识', icon: 'ri-book-line' },
41
+ { key: 'tech', label: '技术文档', icon: 'ri-code-line' },
42
+ { key: 'product', label: '产品说明', icon: 'ri-product-hunt-line' },
43
+ { key: 'service', label: '客服问答', icon: 'ri-customer-service-line' },
44
+ { key: 'legal', label: '法务合规', icon: 'ri-scales-line' },
45
+ { key: 'sales', label: '销售材料', icon: 'ri-line-chart-line' }
46
+ ];
47
+
48
+ const handleNext = () => {
49
+ if (currentStep < steps.length - 1) {
50
+ setCurrentStep(currentStep + 1);
51
+ }
52
+ };
53
+
54
+ const handlePrev = () => {
55
+ if (currentStep > 0) {
56
+ setCurrentStep(currentStep - 1);
57
+ }
58
+ };
59
+
60
+ const handleSave = () => {
61
+ console.log('保存知识库:', knowledgeData);
62
+ router.push('/knowledge');
63
+ };
64
+
65
+ const handlePublish = () => {
66
+ console.log('发布知识库:', knowledgeData);
67
+ router.push('/knowledge');
68
+ };
69
+
70
+ const handleFileUpload = (event) => {
71
+ const files = Array.from(event.target.files || []);
72
+ const newFiles = files.map((file, index) => ({
73
+ id: uploadedFiles.length + index + 1,
74
+ name: file.name,
75
+ size: `${(file.size / 1024 / 1024).toFixed(1)} MB`,
76
+ status: 'uploading',
77
+ type: file.name.split('.').pop() || 'unknown',
78
+ chunks: 0,
79
+ lastModified: new Date().toISOString().split('T')[0]
80
+ }));
81
+
82
+ setUploadedFiles([...uploadedFiles, ...newFiles]);
83
+
84
+ setTimeout(() => {
85
+ setUploadedFiles(prev => prev.map(file =>
86
+ newFiles.some(nf => nf.id === file.id)
87
+ ? { ...file, status: 'uploaded', chunks: Math.floor(Math.random() * 50) + 10 }
88
+ : file
89
+ ));
90
+ }, 2000);
91
+ };
92
+
93
+ const addWebSource = () => {
94
+ if (newUrl) {
95
+ const newSource = {
96
+ id: webSources.length + 1,
97
+ url: newUrl,
98
+ title: '正在获取标题...',
99
+ status: 'crawling',
100
+ pages: 0,
101
+ lastUpdate: new Date().toISOString().split('T')[0]
102
+ };
103
+ setWebSources([...webSources, newSource]);
104
+ setNewUrl('');
105
+
106
+ setTimeout(() => {
107
+ setWebSources(prev => prev.map(source =>
108
+ source.id === newSource.id
109
+ ? { ...source, status: 'crawled', title: `${newUrl.split('/')[2]} - 文档`, pages: Math.floor(Math.random() * 20) + 5 }
110
+ : source
111
+ ));
112
+ }, 3000);
113
+ }
114
+ };
115
+
116
+ const renderStepContent = () => {
117
+ switch (currentStep) {
118
+ case 0:
119
+ return (
120
+ <div className="space-y-6">
121
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
122
+ <div className="space-y-6">
123
+ <div>
124
+ <label className="block text-sm font-medium text-gray-700 mb-2">知识库名称</label>
125
+ <input
126
+ type="text"
127
+ placeholder="输入知识库名称..."
128
+ value={knowledgeData.name}
129
+ onChange={(e) => setKnowledgeData({...knowledgeData, name: e.target.value})}
130
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
131
+ />
132
+ </div>
133
+
134
+ <div>
135
+ <label className="block text-sm font-medium text-gray-700 mb-2">描述说明</label>
136
+ <textarea
137
+ placeholder="描述知识库的用途和内容范围..."
138
+ rows={4}
139
+ maxLength={500}
140
+ value={knowledgeData.description}
141
+ onChange={(e) => setKnowledgeData({...knowledgeData, description: e.target.value})}
142
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none"
143
+ />
144
+ <div className="text-xs text-gray-500 mt-1">{knowledgeData.description.length}/500 字符</div>
145
+ </div>
146
+
147
+ <div>
148
+ <label className="block text-sm font-medium text-gray-700 mb-3">知识库分类</label>
149
+ <div className="grid grid-cols-2 gap-3">
150
+ {categories.map((category) => (
151
+ <div
152
+ key={category.key}
153
+ onClick={() => setKnowledgeData({...knowledgeData, category: category.key})}
154
+ className={`p-4 border rounded-lg cursor-pointer transition-all ${
155
+ knowledgeData.category === category.key
156
+ ? 'border-blue-500 bg-blue-50'
157
+ : 'border-gray-200 hover:border-gray-300'
158
+ }`}
159
+ >
160
+ <div className="flex items-center space-x-3">
161
+ <div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
162
+ <i className={`${category.icon} text-blue-600`}></i>
163
+ </div>
164
+ <span className="font-medium text-gray-900">{category.label}</span>
165
+ </div>
166
+ </div>
167
+ ))}
168
+ </div>
169
+ </div>
170
+ </div>
171
+
172
+ <div className="space-y-6">
173
+ <div className="bg-blue-50 border border-blue-200 rounded-lg p-6">
174
+ <div className="flex items-start space-x-3">
175
+ <div className="w-6 h-6 flex items-center justify-center mt-0.5">
176
+ <i className="ri-lightbulb-line text-blue-600"></i>
177
+ </div>
178
+ <div>
179
+ <h4 className="font-medium text-blue-900 mb-2">创建建议</h4>
180
+ <ul className="text-sm text-blue-800 space-y-1">
181
+ <li>• 选择描述性强的名称,便于后续查找</li>
182
+ <li>• 详细描述知识库的应用场景</li>
183
+ <li>• 根据内容类型选择合适的分类</li>
184
+ <li>• 考虑知识库的长期维护和更新</li>
185
+ </ul>
186
+ </div>
187
+ </div>
188
+ </div>
189
+
190
+ <div className="border border-gray-200 rounded-lg p-6">
191
+ <h4 className="font-medium text-gray-900 mb-4">权限设置</h4>
192
+ <div className="space-y-4">
193
+ <div className="flex items-center justify-between">
194
+ <div>
195
+ <div className="font-medium text-gray-900">公开知识库</div>
196
+ <div className="text-sm text-gray-500">其他用户可以查看和使用此知识库</div>
197
+ </div>
198
+ <div
199
+ onClick={() => setKnowledgeData({...knowledgeData, isPublic: !knowledgeData.isPublic})}
200
+ className={`w-10 h-6 rounded-full relative cursor-pointer transition-colors ${
201
+ knowledgeData.isPublic ? 'bg-blue-600' : 'bg-gray-300'
202
+ }`}
203
+ >
204
+ <div className={`w-4 h-4 bg-white rounded-full absolute top-1 transition-transform ${
205
+ knowledgeData.isPublic ? 'right-1' : 'left-1'
206
+ }`}></div>
207
+ </div>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ );
215
+
216
+ case 1:
217
+ return (
218
+ <div className="space-y-6">
219
+ <div className="bg-white border border-gray-200 rounded-lg">
220
+ <div className="border-b border-gray-200">
221
+ <div className="flex space-x-0">
222
+ {[
223
+ { key: 'files', label: '文件上传', icon: 'ri-file-line' },
224
+ { key: 'web', label: '网页抓取', icon: 'ri-global-line' },
225
+ { key: 'manual', label: '手动输入', icon: 'ri-edit-line' }
226
+ ].map((tab) => (
227
+ <button
228
+ key={tab.key}
229
+ onClick={() => setActiveTab(tab.key)}
230
+ className={`px-6 py-4 text-sm font-medium border-b-2 transition-colors ${
231
+ activeTab === tab.key
232
+ ? 'border-blue-500 text-blue-600 bg-blue-50'
233
+ : 'border-transparent text-gray-500 hover:text-gray-700'
234
+ }`}
235
+ >
236
+ <div className="w-4 h-4 flex items-center justify-center inline-block mr-2">
237
+ <i className={tab.icon}></i>
238
+ </div>
239
+ {tab.label}
240
+ </button>
241
+ ))}
242
+ </div>
243
+ </div>
244
+
245
+ <div className="p-6">
246
+ {activeTab === 'files' && (
247
+ <div className="space-y-4">
248
+ <div className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-gray-400 transition-colors">
249
+ <div className="w-16 h-16 bg-gray-100 rounded-lg flex items-center justify-center mx-auto mb-4">
250
+ <i className="ri-upload-cloud-line text-2xl text-gray-400"></i>
251
+ </div>
252
+ <p className="text-lg text-gray-600 mb-2">拖拽文件到此处,或者</p>
253
+ <label className="inline-block px-6 py-3 bg-blue-600 text-white rounded-lg cursor-pointer hover:bg-blue-700 transition-colors">
254
+ 选择文件
255
+ <input
256
+ type="file"
257
+ multiple
258
+ accept=".pdf,.doc,.docx,.txt,.md,.csv,.xlsx"
259
+ onChange={handleFileUpload}
260
+ className="hidden"
261
+ />
262
+ </label>
263
+ <p className="text-sm text-gray-500 mt-4">支持 PDF、DOC、DOCX、TXT、MD、CSV、XLSX 格式,单文件最大 50MB</p>
264
+ </div>
265
+ </div>
266
+ )}
267
+
268
+ {activeTab === 'web' && (
269
+ <div className="space-y-6">
270
+ <div className="flex space-x-3">
271
+ <input
272
+ type="url"
273
+ placeholder="输入要抓取的网址(如:https://docs.example.com)"
274
+ value={newUrl}
275
+ onChange={(e) => setNewUrl(e.target.value)}
276
+ className="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
277
+ />
278
+ <button
279
+ onClick={addWebSource}
280
+ className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap"
281
+ >
282
+ 开始抓取
283
+ </button>
284
+ </div>
285
+ <div className="text-sm text-gray-500 space-y-1">
286
+ <p>• 支持抓取文档站点、博客文章、产品页面等</p>
287
+ <p>• 自动识别并提取页面文本内容</p>
288
+ <p>• 支持批量抓取同站点多个页面</p>
289
+ </div>
290
+ </div>
291
+ )}
292
+
293
+ {activeTab === 'manual' && (
294
+ <div className="space-y-4">
295
+ <textarea
296
+ placeholder="直接输入要添加到知识库的内容...&#10;&#10;您可以添加:&#10;• 产品使用说明和操作指南&#10;• 常见问题及详细解答&#10;• 专业术语和概念解释&#10;• 业务流程和规范文档"
297
+ rows={12}
298
+ maxLength={500}
299
+ value={knowledgeData.manualContent}
300
+ onChange={(e) => setKnowledgeData({...knowledgeData, manualContent: e.target.value})}
301
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none"
302
+ />
303
+ <div className="flex items-center justify-between">
304
+ <div className="text-sm text-gray-500">{knowledgeData.manualContent.length}/500 字符</div>
305
+ <button className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap">
306
+ 添加内容
307
+ </button>
308
+ </div>
309
+ </div>
310
+ )}
311
+ </div>
312
+ </div>
313
+ </div>
314
+ );
315
+
316
+ case 2:
317
+ return (
318
+ <div className="space-y-6">
319
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
320
+ <div className="bg-white border border-gray-200 rounded-lg p-6">
321
+ <h4 className="font-medium text-gray-900 mb-4">处理选项</h4>
322
+ <div className="space-y-4">
323
+ <div className="flex items-center justify-between">
324
+ <div>
325
+ <span className="font-medium text-gray-900">自动分块处理</span>
326
+ <p className="text-sm text-gray-500">将长文档自动分割为合适大小的片段</p>
327
+ </div>
328
+ <div
329
+ onClick={() => setKnowledgeData(prev => ({
330
+ ...prev,
331
+ processingSettings: {...prev.processingSettings, autoChunk: !prev.processingSettings.autoChunk}
332
+ }))}
333
+ className={`w-10 h-6 rounded-full relative cursor-pointer transition-colors ${
334
+ knowledgeData.processingSettings.autoChunk ? 'bg-blue-600' : 'bg-gray-300'
335
+ }`}
336
+ >
337
+ <div className={`w-4 h-4 bg-white rounded-full absolute top-1 transition-transform ${
338
+ knowledgeData.processingSettings.autoChunk ? 'right-1' : 'left-1'
339
+ }`}></div>
340
+ </div>
341
+ </div>
342
+
343
+ <div className="flex items-center justify-between">
344
+ <div>
345
+ <span className="font-medium text-gray-900">智能去重</span>
346
+ <p className="text-sm text-gray-500">自动识别并合并重复或相似内容</p>
347
+ </div>
348
+ <div
349
+ onClick={() => setKnowledgeData(prev => ({
350
+ ...prev,
351
+ processingSettings: {...prev.processingSettings, smartDedup: !prev.processingSettings.smartDedup}
352
+ }))}
353
+ className={`w-10 h-6 rounded-full relative cursor-pointer transition-colors ${
354
+ knowledgeData.processingSettings.smartDedup ? 'bg-blue-600' : 'bg-gray-300'
355
+ }`}
356
+ >
357
+ <div className={`w-4 h-4 bg-white rounded-full absolute top-1 transition-transform ${
358
+ knowledgeData.processingSettings.smartDedup ? 'right-1' : 'left-1'
359
+ }`}></div>
360
+ </div>
361
+ </div>
362
+
363
+ <div className="flex items-center justify-between">
364
+ <div>
365
+ <span className="font-medium text-gray-900">生成摘要</span>
366
+ <p className="text-sm text-gray-500">为每个内容片段生成简要摘要</p>
367
+ </div>
368
+ <div
369
+ onClick={() => setKnowledgeData(prev => ({
370
+ ...prev,
371
+ processingSettings: {...prev.processingSettings, generateSummary: !prev.processingSettings.generateSummary}
372
+ }))}
373
+ className={`w-10 h-6 rounded-full relative cursor-pointer transition-colors ${
374
+ knowledgeData.processingSettings.generateSummary ? 'bg-blue-600' : 'bg-gray-300'
375
+ }`}
376
+ >
377
+ <div className={`w-4 h-4 bg-white rounded-full absolute top-1 transition-transform ${
378
+ knowledgeData.processingSettings.generateSummary ? 'right-1' : 'left-1'
379
+ }`}></div>
380
+ </div>
381
+ </div>
382
+ </div>
383
+ </div>
384
+
385
+ <div className="bg-white border border-gray-200 rounded-lg p-6">
386
+ <h4 className="font-medium text-gray-900 mb-4">参数设置</h4>
387
+ <div className="space-y-6">
388
+ <div>
389
+ <label className="block text-sm font-medium text-gray-700 mb-2">
390
+ 分块大小: {knowledgeData.processingSettings.chunkSize} 字符
391
+ </label>
392
+ <input
393
+ type="range"
394
+ min="500"
395
+ max="2000"
396
+ step="100"
397
+ value={knowledgeData.processingSettings.chunkSize}
398
+ onChange={(e) => setKnowledgeData(prev => ({
399
+ ...prev,
400
+ processingSettings: {...prev.processingSettings, chunkSize: parseInt(e.target.value)}
401
+ }))}
402
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
403
+ />
404
+ <div className="flex justify-between text-xs text-gray-500 mt-1">
405
+ <span>500</span>
406
+ <span>2000</span>
407
+ </div>
408
+ </div>
409
+
410
+ <div>
411
+ <label className="block text-sm font-medium text-gray-700 mb-2">
412
+ 重叠字符: {knowledgeData.processingSettings.overlap}
413
+ </label>
414
+ <input
415
+ type="range"
416
+ min="0"
417
+ max="500"
418
+ step="50"
419
+ value={knowledgeData.processingSettings.overlap}
420
+ onChange={(e) => setKnowledgeData(prev => ({
421
+ ...prev,
422
+ processingSettings: {...prev.processingSettings, overlap: parseInt(e.target.value)}
423
+ }))}
424
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
425
+ />
426
+ <div className="flex justify-between text-xs text-gray-500 mt-1">
427
+ <span>0</span>
428
+ <span>500</span>
429
+ </div>
430
+ </div>
431
+ </div>
432
+ </div>
433
+ </div>
434
+ </div>
435
+ );
436
+
437
+ case 3:
438
+ return (
439
+ <div className="space-y-6">
440
+ <div className="bg-white border border-gray-200 rounded-lg p-6">
441
+ <h4 className="font-medium text-gray-900 mb-4">知识库预览</h4>
442
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
443
+ <div className="space-y-4">
444
+ <div className="p-4 bg-gray-50 rounded-lg">
445
+ <div className="text-sm text-gray-600 mb-2">基本信息</div>
446
+ <div className="space-y-2">
447
+ <div><span className="font-medium">名称:</span>{knowledgeData.name || '未设置'}</div>
448
+ <div><span className="font-medium">分类:</span>{categories.find(c => c.key === knowledgeData.category)?.label}</div>
449
+ <div><span className="font-medium">描述:</span>{knowledgeData.description || '无描述'}</div>
450
+ </div>
451
+ </div>
452
+ </div>
453
+
454
+ <div className="space-y-4">
455
+ <div className="p-4 bg-gray-50 rounded-lg">
456
+ <div className="text-sm text-gray-600 mb-2">内容统计</div>
457
+ <div className="space-y-2">
458
+ <div><span className="font-medium">文档数量:</span>{uploadedFiles.length} 个</div>
459
+ <div><span className="font-medium">网页来源:</span>{webSources.length} 个</div>
460
+ <div><span className="font-medium">总分块数:</span>{uploadedFiles.reduce((sum, file) => sum + file.chunks, 0)} 个</div>
461
+ </div>
462
+ </div>
463
+ </div>
464
+ </div>
465
+ </div>
466
+ </div>
467
+ );
468
+
469
+ default:
470
+ return null;
471
+ }
472
+ };
473
+
474
+ return (
475
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200">
476
+ {/* 步骤导航 */}
477
+ <div className="p-6 border-b border-gray-200">
478
+ <div className="flex items-center justify-between">
479
+ {steps.map((step, index) => (
480
+ <div key={step.id} className="flex items-center">
481
+ <div className={`flex items-center space-x-3 px-4 py-2 rounded-lg cursor-pointer transition-colors ${
482
+ currentStep === index
483
+ ? 'bg-blue-100 text-blue-700'
484
+ : currentStep > index
485
+ ? 'bg-green-100 text-green-700'
486
+ : 'text-gray-500'
487
+ }`} onClick={() => setCurrentStep(index)}>
488
+ <div className="w-6 h-6 flex items-center justify-center">
489
+ <i className={step.icon}></i>
490
+ </div>
491
+ <span className="font-medium">{step.name}</span>
492
+ </div>
493
+ {index < steps.length - 1 && (
494
+ <div className="mx-4 w-8 h-px bg-gray-300"></div>
495
+ )}
496
+ </div>
497
+ ))}
498
+ </div>
499
+ </div>
500
+
501
+ {/* 步骤内容 */}
502
+ <div className="p-8">
503
+ {renderStepContent()}
504
+ </div>
505
+
506
+ {/* 底部操作按钮 */}
507
+ <div className="p-6 border-t border-gray-200 flex items-center justify-between">
508
+ <div className="flex space-x-3">
509
+ {currentStep > 0 && (
510
+ <button
511
+ onClick={handlePrev}
512
+ className="px-6 py-3 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap"
513
+ >
514
+ 上一步
515
+ </button>
516
+ )}
517
+ </div>
518
+
519
+ <div className="flex space-x-3">
520
+ <button
521
+ onClick={handleSave}
522
+ className="px-6 py-3 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap"
523
+ >
524
+ 保存草稿
525
+ </button>
526
+
527
+ {currentStep < steps.length - 1 ? (
528
+ <button
529
+ onClick={handleNext}
530
+ className="bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap"
531
+ >
532
+ 下一步
533
+ </button>
534
+ ) : (
535
+ <button
536
+ onClick={handlePublish}
537
+ className="bg-green-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-green-700 transition-colors cursor-pointer whitespace-nowrap"
538
+ >
539
+ <div className="w-5 h-5 flex items-center justify-center inline-block mr-2">
540
+ <i className="ri-check-line"></i>
541
+ </div>
542
+ 创建知识库
543
+ </button>
544
+ )}
545
+ </div>
546
+ </div>
547
+ </div>
548
+ );
549
+ }
app/knowledge/create/page.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import Header from '@/components/Header';
4
+ import Sidebar from '@/components/Sidebar';
5
+ import KnowledgeBuilder from './KnowledgeBuilder';
6
+
7
+ export default function CreateKnowledgePage() {
8
+ return (
9
+ <div className="min-h-screen bg-gray-50">
10
+ <Header />
11
+ <div className="flex">
12
+ <Sidebar />
13
+ <main className="flex-1 p-8">
14
+ <div className="max-w-7xl mx-auto">
15
+ <div className="mb-8">
16
+ <h1 className="text-3xl font-bold text-gray-900 mb-2">创建知识库</h1>
17
+ <p className="text-gray-600">构建专业的知识体系,为AI提供准确的信息支持</p>
18
+ </div>
19
+
20
+ <KnowledgeBuilder />
21
+ </div>
22
+ </main>
23
+ </div>
24
+ </div>
25
+ );
26
+ }
app/knowledge/page.tsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import Header from '@/components/Header';
4
+ import Sidebar from '@/components/Sidebar';
5
+ import KnowledgeLibrary from './KnowledgeLibrary';
6
+ import KnowledgeFilters from './KnowledgeFilters';
7
+ import Link from 'next/link';
8
+
9
+ export default function KnowledgePage() {
10
+ return (
11
+ <div className="min-h-screen bg-gray-50">
12
+ <Header />
13
+ <div className="flex">
14
+ <Sidebar />
15
+ <main className="flex-1 p-8">
16
+ <div className="max-w-7xl mx-auto">
17
+ <div className="mb-8">
18
+ <div className="flex items-center justify-between">
19
+ <div>
20
+ <h1 className="text-3xl font-bold text-gray-900 mb-2">知识库管理</h1>
21
+ <p className="text-gray-600">管理和组织您的知识内容,为AI提供专业知识支持</p>
22
+ </div>
23
+ <div className="flex space-x-3">
24
+ <Link href="/knowledge/create" className="border border-gray-300 text-gray-700 px-6 py-3 rounded-lg font-medium hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap">
25
+ <div className="w-5 h-5 flex items-center justify-center inline-block mr-2">
26
+ <i className="ri-upload-line"></i>
27
+ </div>
28
+ 批量导入
29
+ </Link>
30
+ <Link href="/knowledge/create" className="bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap">
31
+ <div className="w-5 h-5 flex items-center justify-center inline-block mr-2">
32
+ <i className="ri-add-line"></i>
33
+ </div>
34
+ 新建知识库
35
+ </Link>
36
+ </div>
37
+ </div>
38
+ </div>
39
+
40
+ <KnowledgeFilters />
41
+ <KnowledgeLibrary />
42
+ </div>
43
+ </main>
44
+ </div>
45
+ </div>
46
+ );
47
+ }
app/layout.tsx ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import { Geist, Geist_Mono, Pacifico } from "next/font/google";
3
+ import "./globals.css";
4
+
5
+ const pacifico = Pacifico({
6
+ weight: '400',
7
+ subsets: ['latin'],
8
+ display: 'swap',
9
+ variable: '--font-pacifico',
10
+ })
11
+
12
+ const geistSans = Geist({
13
+ variable: "--font-geist-sans",
14
+ subsets: ["latin"],
15
+ });
16
+
17
+ const geistMono = Geist_Mono({
18
+ variable: "--font-geist-mono",
19
+ subsets: ["latin"],
20
+ });
21
+
22
+ export const metadata: Metadata = {
23
+ title: "Readdy Site",
24
+ description: "Generated by Readdy",
25
+ };
26
+
27
+ export default function RootLayout({
28
+ children,
29
+ }: Readonly<{
30
+ children: React.ReactNode;
31
+ }>) {
32
+ return (
33
+ <html lang="en" suppressHydrationWarning={true}>
34
+ <body
35
+ className={`${geistSans.variable} ${geistMono.variable} ${pacifico.variable} antialiased`}
36
+ >
37
+ {children}
38
+ </body>
39
+ </html>
40
+ );
41
+ }
app/not-found.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ export default function NotFound() {
2
+ return (
3
+ <div className="flex flex-col items-center justify-center h-screen text-center px-4">
4
+ <h1 className="text-5xl md:text-5xl font-semibold text-gray-100">404</h1>
5
+ <h1 className="text-2xl md:text-3xl font-semibold mt-6">This page has not been generated</h1>
6
+ <p className="mt-4 text-xl md:text-2xl text-gray-500">Tell me what you would like on this page</p>
7
+ </div>
8
+ );
9
+ }
app/page.tsx ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+
5
+ export default function Home() {
6
+ return (
7
+ <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
8
+ <div className="relative">
9
+ <div
10
+ className="absolute inset-0 bg-cover bg-center opacity-10"
11
+ style={{
12
+ backgroundImage: `url('https://readdy.ai/api/search-image?query=Modern%20AI%20technology%20workspace%20with%20holographic%20displays%2C%20futuristic%20interface%20elements%2C%20clean%20minimalist%20design%2C%20soft%20blue%20and%20purple%20lighting%2C%20high-tech%20environment%2C%20digital%20screens%20showing%20data%20visualizations%2C%20sleek%20modern%20architecture%2C%20professional%20technology%20atmosphere%2C%20ultra-modern%20setting&width=1920&height=1080&seq=hero-bg&orientation=landscape')`
13
+ }}
14
+ />
15
+
16
+ <div className="relative z-10">
17
+ <header className="px-6 py-8">
18
+ <div className="max-w-7xl mx-auto">
19
+ <div className="flex items-center justify-between">
20
+ <div className="text-3xl font-bold text-blue-600" style={{ fontFamily: 'Pacifico, serif' }}>
21
+ AI Agent Studio
22
+ </div>
23
+ <div className="flex items-center space-x-6">
24
+ <Link href="/dashboard" className="text-gray-700 hover:text-blue-600 transition-colors">
25
+ 产品介绍
26
+ </Link>
27
+ <Link href="/templates" className="text-gray-700 hover:text-blue-600 transition-colors">
28
+ 模板市场
29
+ </Link>
30
+ <Link href="/dashboard" className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap">
31
+ 立即体验
32
+ </Link>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </header>
37
+
38
+ <section className="px-6 py-20">
39
+ <div className="max-w-7xl mx-auto">
40
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
41
+ <div>
42
+ <h1 className="text-5xl font-bold text-gray-900 mb-6">
43
+ 构建智能Agent
44
+ <br />
45
+ <span className="text-blue-600">无需编程</span>
46
+ </h1>
47
+ <p className="text-xl text-gray-600 mb-8 leading-relaxed">
48
+ 通过可视化界面快速创建、部署和管理AI Agent应用。支持多种Agent类型,丰富的模板库,让人工智能触手可及。
49
+ </p>
50
+ <div className="flex items-center space-x-4">
51
+ <Link href="/dashboard" className="bg-blue-600 text-white px-8 py-4 rounded-lg text-lg font-semibold hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap">
52
+ 开始创建
53
+ </Link>
54
+ <Link href="/templates" className="border border-gray-300 text-gray-700 px-8 py-4 rounded-lg text-lg font-semibold hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap">
55
+ 浏览模板
56
+ </Link>
57
+ </div>
58
+ </div>
59
+
60
+ <div className="relative">
61
+ <img
62
+ src="https://readdy.ai/api/search-image?query=Professional%20AI%20dashboard%20interface%20mockup%20showing%20agent%20management%20panels%2C%20workflow%20diagrams%2C%20data%20analytics%20charts%2C%20modern%20UI%20design%20with%20blue%20accent%20colors%2C%20clean%20interface%20elements%2C%20desktop%20application%20screenshot%2C%20sophisticated%20technology%20platform%2C%20user-friendly%20design%2C%20comprehensive%20control%20panel%20layout&width=600&height=400&seq=hero-image&orientation=landscape"
63
+ alt="AI Agent Studio Platform"
64
+ className="rounded-xl shadow-2xl object-cover w-full h-96"
65
+ />
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </section>
70
+
71
+ <section className="px-6 py-20 bg-white/80 backdrop-blur-sm">
72
+ <div className="max-w-7xl mx-auto">
73
+ <div className="text-center mb-16">
74
+ <h2 className="text-3xl font-bold text-gray-900 mb-4">强大的功能特性</h2>
75
+ <p className="text-xl text-gray-600">一站式AI Agent开发平台</p>
76
+ </div>
77
+
78
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
79
+ <div className="bg-white p-8 rounded-xl shadow-sm border border-gray-200">
80
+ <div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center mb-6">
81
+ <i className="ri-robot-line text-2xl text-blue-600"></i>
82
+ </div>
83
+ <h3 className="text-xl font-semibold text-gray-900 mb-4">可视化Agent构建</h3>
84
+ <p className="text-gray-600">拖拽式界面设计,无需编程知识即可创建复杂的AI Agent应用</p>
85
+ </div>
86
+
87
+ <div className="bg-white p-8 rounded-xl shadow-sm border border-gray-200">
88
+ <div className="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center mb-6">
89
+ <i className="ri-flow-chart text-2xl text-green-600"></i>
90
+ </div>
91
+ <h3 className="text-xl font-semibold text-gray-900 mb-4">智能工作流</h3>
92
+ <p className="text-gray-600">设计复杂的自动化工作流,实现多Agent协同工作</p>
93
+ </div>
94
+
95
+ <div className="bg-white p-8 rounded-xl shadow-sm border border-gray-200">
96
+ <div className="w-12 h-12 bg-purple-100 rounded-xl flex items-center justify-center mb-6">
97
+ <i className="ri-database-line text-2xl text-purple-600"></i>
98
+ </div>
99
+ <h3 className="text-xl font-semibold text-gray-900 mb-4">知识库管理</h3>
100
+ <p className="text-gray-600">上传文档、数据源,构建专业的知识库为Agent提供支持</p>
101
+ </div>
102
+
103
+ <div className="bg-white p-8 rounded-xl shadow-sm border border-gray-200">
104
+ <div className="w-12 h-12 bg-orange-100 rounded-xl flex items-center justify-center mb-6">
105
+ <i className="ri-store-line text-2xl text-orange-600"></i>
106
+ </div>
107
+ <h3 className="text-xl font-semibold text-gray-900 mb-4">模板市场</h3>
108
+ <p className="text-gray-600">丰富的预设模板库,快速启动各种场景的Agent应用</p>
109
+ </div>
110
+
111
+ <div className="bg-white p-8 rounded-xl shadow-sm border border-gray-200">
112
+ <div className="w-12 h-12 bg-red-100 rounded-xl flex items-center justify-center mb-6">
113
+ <i className="ri-bar-chart-line text-2xl text-red-600"></i>
114
+ </div>
115
+ <h3 className="text-xl font-semibold text-gray-900 mb-4">数据分析</h3>
116
+ <p className="text-gray-600">实时监控Agent性能,深入分析使用数据和效果指标</p>
117
+ </div>
118
+
119
+ <div className="bg-white p-8 rounded-xl shadow-sm border border-gray-200">
120
+ <div className="w-12 h-12 bg-indigo-100 rounded-xl flex items-center justify-center mb-6">
121
+ <i className="ri-cloud-line text-2xl text-indigo-600"></i>
122
+ </div>
123
+ <h3 className="text-xl font-semibold text-gray-900 mb-4">云端部署</h3>
124
+ <p className="text-gray-600">一键部署到云端,支持高并发访问和弹性扩展</p>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </section>
129
+
130
+ <section className="px-6 py-20">
131
+ <div className="max-w-4xl mx-auto text-center">
132
+ <h2 className="text-3xl font-bold text-gray-900 mb-8">准备开始您的AI之旅?</h2>
133
+ <p className="text-xl text-gray-600 mb-8">
134
+ 立即体验AI Agent Studio,无需编程经验即可构建强大的智能应用
135
+ </p>
136
+ <Link href="/dashboard" className="bg-blue-600 text-white px-8 py-4 rounded-lg text-lg font-semibold hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap">
137
+ 免费开始使用
138
+ </Link>
139
+ </div>
140
+ </section>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ );
145
+ }
app/workflows/WorkflowFilters.tsx ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ export default function WorkflowFilters() {
6
+ const [activeFilter, setActiveFilter] = useState('all');
7
+ const [searchTerm, setSearchTerm] = useState('');
8
+
9
+ const filters = [
10
+ { key: 'all', label: '全部', count: 32 },
11
+ { key: 'active', label: '运行中', count: 24 },
12
+ { key: 'paused', label: '暂停', count: 5 },
13
+ { key: 'draft', label: '草稿', count: 3 }
14
+ ];
15
+
16
+ const categories = [
17
+ { key: 'all', label: '全部类型' },
18
+ { key: 'data-processing', label: '数据处理' },
19
+ { key: 'content-creation', label: '内容创作' },
20
+ { key: 'automation', label: '任务自动化' },
21
+ { key: 'integration', label: '系统集成' }
22
+ ];
23
+
24
+ return (
25
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
26
+ <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
27
+ <div className="flex items-center space-x-4">
28
+ {filters.map((filter) => (
29
+ <button
30
+ key={filter.key}
31
+ onClick={() => setActiveFilter(filter.key)}
32
+ className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors cursor-pointer whitespace-nowrap ${
33
+ activeFilter === filter.key
34
+ ? 'bg-blue-100 text-blue-700'
35
+ : 'text-gray-600 hover:bg-gray-100'
36
+ }`}
37
+ >
38
+ {filter.label} ({filter.count})
39
+ </button>
40
+ ))}
41
+ </div>
42
+
43
+ <div className="flex items-center space-x-4">
44
+ <div className="relative">
45
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
46
+ <div className="w-4 h-4 flex items-center justify-center">
47
+ <i className="ri-search-line text-gray-400"></i>
48
+ </div>
49
+ </div>
50
+ <input
51
+ type="text"
52
+ placeholder="搜索工作流..."
53
+ value={searchTerm}
54
+ onChange={(e) => setSearchTerm(e.target.value)}
55
+ className="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
56
+ />
57
+ </div>
58
+
59
+ <div className="relative">
60
+ <button className="px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap">
61
+ 类型筛选
62
+ <div className="w-4 h-4 flex items-center justify-center inline-block ml-2">
63
+ <i className="ri-arrow-down-s-line"></i>
64
+ </div>
65
+ </button>
66
+ </div>
67
+
68
+ <div className="relative">
69
+ <button className="px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap">
70
+ 执行状态
71
+ <div className="w-4 h-4 flex items-center justify-center inline-block ml-2">
72
+ <i className="ri-arrow-down-s-line"></i>
73
+ </div>
74
+ </button>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ );
80
+ }
app/workflows/WorkflowList.tsx ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import Link from 'next/link';
5
+
6
+ export default function WorkflowList() {
7
+ const [selectedWorkflows, setSelectedWorkflows] = useState<number[]>([]);
8
+
9
+ const workflows = [
10
+ {
11
+ id: 1,
12
+ name: '客户数据处理流程',
13
+ description: '自动处理客户数据,包括清洗、分类和分析',
14
+ category: '数据处理',
15
+ status: '运行中',
16
+ version: 'v3.2',
17
+ created: '2024-01-20',
18
+ lastRun: '2分钟前',
19
+ executions: 1245,
20
+ successRate: 98.5,
21
+ avgRuntime: '3.2分钟',
22
+ icon: '📊',
23
+ tags: ['数据清洗', 'ETL', '自动化'],
24
+ nodes: 8,
25
+ triggers: ['定时触发', 'API调用']
26
+ },
27
+ {
28
+ id: 2,
29
+ name: '内容生成与发布',
30
+ description: '根据关键词自动生成文章并发布到多个平台',
31
+ category: '内容创作',
32
+ status: '运行中',
33
+ version: 'v2.1',
34
+ created: '2024-01-18',
35
+ lastRun: '15分钟前',
36
+ executions: 892,
37
+ successRate: 96.2,
38
+ avgRuntime: '5.8分钟',
39
+ icon: '✍️',
40
+ tags: ['内容生成', 'SEO', '多平台'],
41
+ nodes: 12,
42
+ triggers: ['手动触发', '定时触发']
43
+ },
44
+ {
45
+ id: 3,
46
+ name: '邮件智能分类回复',
47
+ description: '自动分类邮件并生成智能回复',
48
+ category: '任务自动化',
49
+ status: '暂停',
50
+ version: 'v1.8',
51
+ created: '2024-01-15',
52
+ lastRun: '2小时前',
53
+ executions: 2156,
54
+ successRate: 94.8,
55
+ avgRuntime: '1.5分钟',
56
+ icon: '📧',
57
+ tags: ['邮件处理', 'NLP', '自动回复'],
58
+ nodes: 6,
59
+ triggers: ['邮件触发', '定时检查']
60
+ },
61
+ {
62
+ id: 4,
63
+ name: '订单处理自动化',
64
+ description: '从下单到发货的完整自动化处理流程',
65
+ category: '系统集成',
66
+ status: '运行中',
67
+ version: 'v4.0',
68
+ created: '2024-01-12',
69
+ lastRun: '30分钟前',
70
+ executions: 3245,
71
+ successRate: 99.1,
72
+ avgRuntime: '2.1分钟',
73
+ icon: '📦',
74
+ tags: ['订单处理', 'ERP集成', '库存管理'],
75
+ nodes: 15,
76
+ triggers: ['订单事件', 'Webhook']
77
+ },
78
+ {
79
+ id: 5,
80
+ name: '社交媒体监控',
81
+ description: '监控品牌提及并自动生成报告',
82
+ category: '数据处理',
83
+ status: '运行中',
84
+ version: 'v2.5',
85
+ created: '2024-01-10',
86
+ lastRun: '1小时前',
87
+ executions: 678,
88
+ successRate: 97.3,
89
+ avgRuntime: '4.2分钟',
90
+ icon: '📱',
91
+ tags: ['社媒监控', '情感分析', '报告生成'],
92
+ nodes: 10,
93
+ triggers: ['定时触发', '关键词匹配']
94
+ },
95
+ {
96
+ id: 6,
97
+ name: '财务报表生成',
98
+ description: '自动收集财务数据并生成可视化报表',
99
+ category: '数据处理',
100
+ status: '运行中',
101
+ version: 'v1.9',
102
+ created: '2024-01-08',
103
+ lastRun: '45分钟前',
104
+ executions: 456,
105
+ successRate: 99.8,
106
+ avgRuntime: '6.5分钟',
107
+ icon: '💰',
108
+ tags: ['财务分析', '数据可视化', 'BI'],
109
+ nodes: 11,
110
+ triggers: ['定时触发', '手动执行']
111
+ }
112
+ ];
113
+
114
+ const handleSelectWorkflow = (workflowId: number) => {
115
+ setSelectedWorkflows(prev =>
116
+ prev.includes(workflowId)
117
+ ? prev.filter(id => id !== workflowId)
118
+ : [...prev, workflowId]
119
+ );
120
+ };
121
+
122
+ const handleSelectAll = () => {
123
+ setSelectedWorkflows(selectedWorkflows.length === workflows.length ? [] : workflows.map(w => w.id));
124
+ };
125
+
126
+ return (
127
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200">
128
+ <div className="p-6 border-b border-gray-200">
129
+ <div className="flex items-center justify-between">
130
+ <div className="flex items-center space-x-4">
131
+ <input
132
+ type="checkbox"
133
+ checked={selectedWorkflows.length === workflows.length}
134
+ onChange={handleSelectAll}
135
+ className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
136
+ />
137
+ <span className="text-sm text-gray-600">
138
+ {selectedWorkflows.length > 0 ? `已选择 ${selectedWorkflows.length} 个工作流` : '全选'}
139
+ </span>
140
+ </div>
141
+
142
+ {selectedWorkflows.length > 0 && (
143
+ <div className="flex items-center space-x-3">
144
+ <button className="px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer whitespace-nowrap">
145
+ 批量启动
146
+ </button>
147
+ <button className="px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer whitespace-nowrap">
148
+ 批量暂停
149
+ </button>
150
+ <button className="px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-50 rounded-lg transition-colors cursor-pointer whitespace-nowrap">
151
+ 批量删除
152
+ </button>
153
+ </div>
154
+ )}
155
+ </div>
156
+ </div>
157
+
158
+ <div className="divide-y divide-gray-200">
159
+ {workflows.map((workflow) => (
160
+ <div key={workflow.id} className="p-6 hover:bg-gray-50 transition-colors">
161
+ <div className="flex items-start justify-between">
162
+ <div className="flex items-start space-x-4 flex-1">
163
+ <input
164
+ type="checkbox"
165
+ checked={selectedWorkflows.includes(workflow.id)}
166
+ onChange={() => handleSelectWorkflow(workflow.id)}
167
+ className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500 mt-1"
168
+ />
169
+
170
+ <div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center">
171
+ <span className="text-xl">{workflow.icon}</span>
172
+ </div>
173
+
174
+ <div className="flex-1">
175
+ <div className="flex items-center space-x-3 mb-2">
176
+ <h3 className="text-lg font-semibold text-gray-900">{workflow.name}</h3>
177
+ <span className="text-sm text-gray-500">{workflow.version}</span>
178
+ <span className={`px-2 py-1 text-xs rounded-full ${
179
+ workflow.status === '运行中'
180
+ ? 'bg-green-100 text-green-800'
181
+ : 'bg-yellow-100 text-yellow-800'
182
+ }`}>
183
+ {workflow.status}
184
+ </span>
185
+ </div>
186
+
187
+ <p className="text-gray-600 mb-3">{workflow.description}</p>
188
+
189
+ <div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-3">
190
+ <div>
191
+ <span className="text-sm text-gray-500">类型</span>
192
+ <p className="text-sm font-medium text-gray-900">{workflow.category}</p>
193
+ </div>
194
+ <div>
195
+ <span className="text-sm text-gray-500">节点数</span>
196
+ <p className="text-sm font-medium text-gray-900">{workflow.nodes} 个</p>
197
+ </div>
198
+ <div>
199
+ <span className="text-sm text-gray-500">成功率</span>
200
+ <p className="text-sm font-medium text-green-600">{workflow.successRate}%</p>
201
+ </div>
202
+ <div>
203
+ <span className="text-sm text-gray-500">平均运行时间</span>
204
+ <p className="text-sm font-medium text-gray-900">{workflow.avgRuntime}</p>
205
+ </div>
206
+ </div>
207
+
208
+ <div className="flex items-center space-x-4 mb-3">
209
+ <span className="text-sm text-gray-500">执行次数: {workflow.executions.toLocaleString()}</span>
210
+ <span className="text-sm text-gray-500">最后运行: {workflow.lastRun}</span>
211
+ </div>
212
+
213
+ <div className="flex items-center space-x-2 mb-3">
214
+ <span className="text-sm text-gray-500">触发方式:</span>
215
+ {workflow.triggers.map((trigger, index) => (
216
+ <span key={index} className="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded-full">
217
+ {trigger}
218
+ </span>
219
+ ))}
220
+ </div>
221
+
222
+ <div className="flex items-center space-x-2">
223
+ {workflow.tags.map((tag, index) => (
224
+ <span key={index} className="px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-full">
225
+ {tag}
226
+ </span>
227
+ ))}
228
+ </div>
229
+ </div>
230
+ </div>
231
+
232
+ <div className="flex items-center space-x-3">
233
+ <Link href={`/workflows/${workflow.id}/analytics`} className="p-2 text-gray-400 hover:text-blue-600 transition-colors cursor-pointer">
234
+ <div className="w-5 h-5 flex items-center justify-center">
235
+ <i className="ri-bar-chart-line"></i>
236
+ </div>
237
+ </Link>
238
+ <button className="p-2 text-gray-400 hover:text-green-600 transition-colors cursor-pointer">
239
+ <div className="w-5 h-5 flex items-center justify-center">
240
+ <i className="ri-play-line"></i>
241
+ </div>
242
+ </button>
243
+ <Link href={`/workflows/${workflow.id}/edit`} className="p-2 text-gray-400 hover:text-blue-600 transition-colors cursor-pointer">
244
+ <div className="w-5 h-5 flex items-center justify-center">
245
+ <i className="ri-edit-line"></i>
246
+ </div>
247
+ </Link>
248
+ <button className="p-2 text-gray-400 hover:text-blue-600 transition-colors cursor-pointer">
249
+ <div className="w-5 h-5 flex items-center justify-center">
250
+ <i className="ri-settings-line"></i>
251
+ </div>
252
+ </button>
253
+ <button className="p-2 text-gray-400 hover:text-red-600 transition-colors cursor-pointer">
254
+ <div className="w-5 h-5 flex items-center justify-center">
255
+ <i className="ri-delete-bin-line"></i>
256
+ </div>
257
+ </button>
258
+ </div>
259
+ </div>
260
+ </div>
261
+ ))}
262
+ </div>
263
+ </div>
264
+ );
265
+ }
app/workflows/[id]/analytics/ExecutionHistory.tsx ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ interface ExecutionHistoryProps {
4
+ workflowId: string;
5
+ }
6
+
7
+ export default function ExecutionHistory({ workflowId }: ExecutionHistoryProps) {
8
+ const executions = [
9
+ {
10
+ id: 1,
11
+ startTime: '2024-01-20 14:32:15',
12
+ endTime: '2024-01-20 14:35:28',
13
+ duration: '3分13秒',
14
+ status: 'success',
15
+ trigger: '定时触发',
16
+ processedRecords: 156,
17
+ errors: 0
18
+ },
19
+ {
20
+ id: 2,
21
+ startTime: '2024-01-20 14:02:45',
22
+ endTime: '2024-01-20 14:05:52',
23
+ duration: '3分7秒',
24
+ status: 'success',
25
+ trigger: 'API调用',
26
+ processedRecords: 142,
27
+ errors: 0
28
+ },
29
+ {
30
+ id: 3,
31
+ startTime: '2024-01-20 13:32:10',
32
+ endTime: '2024-01-20 13:35:45',
33
+ duration: '3分35秒',
34
+ status: 'warning',
35
+ trigger: '定时触发',
36
+ processedRecords: 134,
37
+ errors: 2
38
+ },
39
+ {
40
+ id: 4,
41
+ startTime: '2024-01-20 13:02:38',
42
+ endTime: '2024-01-20 13:05:12',
43
+ duration: '2分34秒',
44
+ status: 'success',
45
+ trigger: '手动执行',
46
+ processedRecords: 98,
47
+ errors: 0
48
+ },
49
+ {
50
+ id: 5,
51
+ startTime: '2024-01-20 12:32:22',
52
+ endTime: '2024-01-20 12:38:15',
53
+ duration: '5分53秒',
54
+ status: 'error',
55
+ trigger: '定时触发',
56
+ processedRecords: 0,
57
+ errors: 1
58
+ },
59
+ {
60
+ id: 6,
61
+ startTime: '2024-01-20 12:02:01',
62
+ endTime: '2024-01-20 12:04:58',
63
+ duration: '2分57秒',
64
+ status: 'success',
65
+ trigger: 'API调用',
66
+ processedRecords: 189,
67
+ errors: 0
68
+ }
69
+ ];
70
+
71
+ const getStatusColor = (status: string) => {
72
+ switch (status) {
73
+ case 'success':
74
+ return 'bg-green-100 text-green-800';
75
+ case 'warning':
76
+ return 'bg-yellow-100 text-yellow-800';
77
+ case 'error':
78
+ return 'bg-red-100 text-red-800';
79
+ case 'running':
80
+ return 'bg-blue-100 text-blue-800';
81
+ default:
82
+ return 'bg-gray-100 text-gray-800';
83
+ }
84
+ };
85
+
86
+ const getStatusText = (status: string) => {
87
+ switch (status) {
88
+ case 'success':
89
+ return '成功';
90
+ case 'warning':
91
+ return '警告';
92
+ case 'error':
93
+ return '失败';
94
+ case 'running':
95
+ return '运行中';
96
+ default:
97
+ return '未知';
98
+ }
99
+ };
100
+
101
+ const getStatusIcon = (status: string) => {
102
+ switch (status) {
103
+ case 'success':
104
+ return 'ri-check-line';
105
+ case 'warning':
106
+ return 'ri-alert-line';
107
+ case 'error':
108
+ return 'ri-close-line';
109
+ case 'running':
110
+ return 'ri-loader-line';
111
+ default:
112
+ return 'ri-question-line';
113
+ }
114
+ };
115
+
116
+ return (
117
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200">
118
+ <div className="p-6 border-b border-gray-200">
119
+ <div className="flex items-center justify-between">
120
+ <h3 className="text-lg font-semibold text-gray-900">执行历史</h3>
121
+ <div className="flex items-center space-x-3">
122
+ <button className="text-sm text-gray-600 hover:text-gray-900 cursor-pointer">
123
+ 导出日志
124
+ </button>
125
+ <button className="text-sm text-blue-600 hover:text-blue-800 cursor-pointer">
126
+ 刷新
127
+ </button>
128
+ </div>
129
+ </div>
130
+ </div>
131
+
132
+ <div className="overflow-x-auto">
133
+ <table className="w-full">
134
+ <thead className="bg-gray-50">
135
+ <tr>
136
+ <th className="text-left py-3 px-6 text-xs font-medium text-gray-500 uppercase tracking-wider">
137
+ 执行时间
138
+ </th>
139
+ <th className="text-left py-3 px-6 text-xs font-medium text-gray-500 uppercase tracking-wider">
140
+ 状态
141
+ </th>
142
+ <th className="text-left py-3 px-6 text-xs font-medium text-gray-500 uppercase tracking-wider">
143
+ 触发方式
144
+ </th>
145
+ <th className="text-left py-3 px-6 text-xs font-medium text-gray-500 uppercase tracking-wider">
146
+ 执行时长
147
+ </th>
148
+ <th className="text-left py-3 px-6 text-xs font-medium text-gray-500 uppercase tracking-wider">
149
+ 处理记录
150
+ </th>
151
+ <th className="text-left py-3 px-6 text-xs font-medium text-gray-500 uppercase tracking-wider">
152
+ 错误数
153
+ </th>
154
+ <th className="text-left py-3 px-6 text-xs font-medium text-gray-500 uppercase tracking-wider">
155
+ 操作
156
+ </th>
157
+ </tr>
158
+ </thead>
159
+ <tbody className="bg-white divide-y divide-gray-200">
160
+ {executions.map((execution) => (
161
+ <tr key={execution.id} className="hover:bg-gray-50">
162
+ <td className="py-4 px-6">
163
+ <div>
164
+ <p className="text-sm font-medium text-gray-900">{execution.startTime}</p>
165
+ <p className="text-xs text-gray-500">至 {execution.endTime.split(' ')[1]}</p>
166
+ </div>
167
+ </td>
168
+ <td className="py-4 px-6">
169
+ <div className="flex items-center space-x-2">
170
+ <div className={`flex items-center space-x-1 px-2 py-1 text-xs rounded-full ${getStatusColor(execution.status)}`}>
171
+ <i className={`${getStatusIcon(execution.status)} text-xs`}></i>
172
+ <span>{getStatusText(execution.status)}</span>
173
+ </div>
174
+ </div>
175
+ </td>
176
+ <td className="py-4 px-6">
177
+ <span className="text-sm text-gray-900">{execution.trigger}</span>
178
+ </td>
179
+ <td className="py-4 px-6">
180
+ <span className="text-sm text-gray-900">{execution.duration}</span>
181
+ </td>
182
+ <td className="py-4 px-6">
183
+ <span className="text-sm text-gray-900">{execution.processedRecords.toLocaleString()}</span>
184
+ </td>
185
+ <td className="py-4 px-6">
186
+ <span className={`text-sm ${execution.errors > 0 ? 'text-red-600' : 'text-gray-900'}`}>
187
+ {execution.errors}
188
+ </span>
189
+ </td>
190
+ <td className="py-4 px-6">
191
+ <div className="flex items-center space-x-2">
192
+ <button className="text-blue-600 hover:text-blue-800 text-sm cursor-pointer">
193
+ 查看详情
194
+ </button>
195
+ {execution.status === 'error' && (
196
+ <button className="text-orange-600 hover:text-orange-800 text-sm cursor-pointer">
197
+ 重试
198
+ </button>
199
+ )}
200
+ </div>
201
+ </td>
202
+ </tr>
203
+ ))}
204
+ </tbody>
205
+ </table>
206
+ </div>
207
+
208
+ <div className="p-6 border-t border-gray-200 flex items-center justify-between">
209
+ <span className="text-sm text-gray-700">显示 1-6 条,共 156 条记录</span>
210
+ <div className="flex items-center space-x-2">
211
+ <button className="px-3 py-1 border border-gray-300 rounded text-sm text-gray-600 hover:bg-gray-50 cursor-pointer">
212
+ 上一页
213
+ </button>
214
+ <button className="px-3 py-1 bg-blue-600 text-white rounded text-sm hover:bg-blue-700 cursor-pointer">
215
+ 1
216
+ </button>
217
+ <button className="px-3 py-1 border border-gray-300 rounded text-sm text-gray-600 hover:bg-gray-50 cursor-pointer">
218
+ 2
219
+ </button>
220
+ <button className="px-3 py-1 border border-gray-300 rounded text-sm text-gray-600 hover:bg-gray-50 cursor-pointer">
221
+ 3
222
+ </button>
223
+ <button className="px-3 py-1 border border-gray-300 rounded text-sm text-gray-600 hover:bg-gray-50 cursor-pointer">
224
+ 下一页
225
+ </button>
226
+ </div>
227
+ </div>
228
+ </div>
229
+ );
230
+ }
app/workflows/[id]/analytics/NodeAnalytics.tsx ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ interface NodeAnalyticsProps {
4
+ workflowId: string;
5
+ }
6
+
7
+ export default function NodeAnalytics({ workflowId }: NodeAnalyticsProps) {
8
+ const nodeData = [
9
+ {
10
+ id: 1,
11
+ name: '数据接收',
12
+ type: '触发器',
13
+ executions: 1245,
14
+ avgTime: '0.2s',
15
+ successRate: 100,
16
+ status: 'healthy'
17
+ },
18
+ {
19
+ id: 2,
20
+ name: '数据验证',
21
+ type: '条件判断',
22
+ executions: 1245,
23
+ avgTime: '0.8s',
24
+ successRate: 98.9,
25
+ status: 'healthy'
26
+ },
27
+ {
28
+ id: 3,
29
+ name: '数据清洗',
30
+ type: '数据处理',
31
+ executions: 1231,
32
+ avgTime: '1.5s',
33
+ successRate: 97.2,
34
+ status: 'warning'
35
+ },
36
+ {
37
+ id: 4,
38
+ name: 'AI分析',
39
+ type: 'AI节点',
40
+ executions: 1196,
41
+ avgTime: '2.1s',
42
+ successRate: 96.8,
43
+ status: 'warning'
44
+ },
45
+ {
46
+ id: 5,
47
+ name: '结果存储',
48
+ type: '数据库',
49
+ executions: 1158,
50
+ avgTime: '0.6s',
51
+ successRate: 99.5,
52
+ status: 'healthy'
53
+ }
54
+ ];
55
+
56
+ const getStatusColor = (status: string) => {
57
+ switch (status) {
58
+ case 'healthy':
59
+ return 'text-green-600 bg-green-100';
60
+ case 'warning':
61
+ return 'text-yellow-600 bg-yellow-100';
62
+ case 'error':
63
+ return 'text-red-600 bg-red-100';
64
+ default:
65
+ return 'text-gray-600 bg-gray-100';
66
+ }
67
+ };
68
+
69
+ return (
70
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
71
+ <div className="flex items-center justify-between mb-6">
72
+ <h3 className="text-lg font-semibold text-gray-900">节点性能分析</h3>
73
+ <button className="text-sm text-blue-600 hover:text-blue-800 cursor-pointer">
74
+ 查看详情
75
+ </button>
76
+ </div>
77
+
78
+ <div className="space-y-4">
79
+ {nodeData.map((node) => (
80
+ <div key={node.id} className="border border-gray-200 rounded-lg p-4">
81
+ <div className="flex items-center justify-between mb-3">
82
+ <div className="flex items-center space-x-3">
83
+ <div className="w-2 h-2 rounded-full bg-blue-500"></div>
84
+ <span className="font-medium text-gray-900">{node.name}</span>
85
+ <span className="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded">
86
+ {node.type}
87
+ </span>
88
+ </div>
89
+ <span className={`px-2 py-1 text-xs rounded-full ${getStatusColor(node.status)}`}>
90
+ {node.status === 'healthy' ? '正常' : '警告'}
91
+ </span>
92
+ </div>
93
+
94
+ <div className="grid grid-cols-3 gap-4">
95
+ <div>
96
+ <span className="text-xs text-gray-500">执行次数</span>
97
+ <p className="text-sm font-medium text-gray-900">{node.executions.toLocaleString()}</p>
98
+ </div>
99
+ <div>
100
+ <span className="text-xs text-gray-500">平均耗时</span>
101
+ <p className="text-sm font-medium text-gray-900">{node.avgTime}</p>
102
+ </div>
103
+ <div>
104
+ <span className="text-xs text-gray-500">成功率</span>
105
+ <p className="text-sm font-medium text-green-600">{node.successRate}%</p>
106
+ </div>
107
+ </div>
108
+
109
+ {/* 性能条 */}
110
+ <div className="mt-3">
111
+ <div className="w-full bg-gray-200 rounded-full h-1.5">
112
+ <div
113
+ className={`h-1.5 rounded-full ${
114
+ node.successRate >= 99 ? 'bg-green-500' :
115
+ node.successRate >= 95 ? 'bg-yellow-500' : 'bg-red-500'
116
+ }`}
117
+ style={{ width: `${node.successRate}%` }}
118
+ ></div>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ ))}
123
+ </div>
124
+ </div>
125
+ );
126
+ }
app/workflows/[id]/analytics/PerformanceChart.tsx ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line } from 'recharts';
4
+
5
+ interface PerformanceChartProps {
6
+ workflowId: string;
7
+ timeRange: string;
8
+ }
9
+
10
+ export default function PerformanceChart({ workflowId, timeRange }: PerformanceChartProps) {
11
+ const performanceData = [
12
+ { date: '01-01', executions: 45, successRate: 98.2, avgRuntime: 3.1 },
13
+ { date: '01-02', executions: 52, successRate: 97.8, avgRuntime: 3.3 },
14
+ { date: '01-03', executions: 48, successRate: 98.9, avgRuntime: 2.9 },
15
+ { date: '01-04', executions: 61, successRate: 98.1, avgRuntime: 3.4 },
16
+ { date: '01-05', executions: 55, successRate: 99.1, avgRuntime: 3.0 },
17
+ { date: '01-06', executions: 49, successRate: 97.5, avgRuntime: 3.6 },
18
+ { date: '01-07', executions: 58, successRate: 98.7, avgRuntime: 3.2 }
19
+ ];
20
+
21
+ return (
22
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
23
+ <div className="flex items-center justify-between mb-6">
24
+ <h3 className="text-lg font-semibold text-gray-900">执行趋势</h3>
25
+ <div className="flex items-center space-x-4">
26
+ <div className="flex items-center space-x-2">
27
+ <div className="w-3 h-3 bg-blue-500 rounded-full"></div>
28
+ <span className="text-sm text-gray-600">执行次数</span>
29
+ </div>
30
+ <div className="flex items-center space-x-2">
31
+ <div className="w-3 h-3 bg-green-500 rounded-full"></div>
32
+ <span className="text-sm text-gray-600">成功率</span>
33
+ </div>
34
+ </div>
35
+ </div>
36
+
37
+ <div className="h-80">
38
+ <ResponsiveContainer width="100%" height="100%">
39
+ <AreaChart data={performanceData}>
40
+ <defs>
41
+ <linearGradient id="colorExecutions" x1="0" y1="0" x2="0" y2="1">
42
+ <stop offset="5%" stopColor="#3B82F6" stopOpacity={0.8}/>
43
+ <stop offset="95%" stopColor="#3B82F6" stopOpacity={0.1}/>
44
+ </linearGradient>
45
+ </defs>
46
+ <CartesianGrid strokeDasharray="3 3" stroke="#E5E7EB" />
47
+ <XAxis
48
+ dataKey="date"
49
+ stroke="#6B7280"
50
+ fontSize={12}
51
+ tick={{ fill: '#6B7280' }}
52
+ />
53
+ <YAxis
54
+ stroke="#6B7280"
55
+ fontSize={12}
56
+ tick={{ fill: '#6B7280' }}
57
+ />
58
+ <Tooltip
59
+ contentStyle={{
60
+ backgroundColor: '#1F2937',
61
+ border: 'none',
62
+ borderRadius: '8px',
63
+ color: 'white'
64
+ }}
65
+ labelStyle={{ color: '#F3F4F6' }}
66
+ />
67
+ <Area
68
+ type="monotone"
69
+ dataKey="executions"
70
+ stroke="#3B82F6"
71
+ strokeWidth={2}
72
+ fillOpacity={1}
73
+ fill="url(#colorExecutions)"
74
+ />
75
+ </AreaChart>
76
+ </ResponsiveContainer>
77
+ </div>
78
+ </div>
79
+ );
80
+ }
app/workflows/[id]/analytics/WorkflowAnalytics.tsx ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import Link from 'next/link';
5
+ import PerformanceChart from './PerformanceChart';
6
+ import ExecutionHistory from './ExecutionHistory';
7
+ import NodeAnalytics from './NodeAnalytics';
8
+
9
+ interface WorkflowAnalyticsProps {
10
+ workflowId: string;
11
+ }
12
+
13
+ export default function WorkflowAnalytics({ workflowId }: WorkflowAnalyticsProps) {
14
+ const [timeRange, setTimeRange] = useState('7d');
15
+
16
+ const workflowData = {
17
+ '1': {
18
+ name: '客户数据处理流程',
19
+ description: '自动处理客户数据,包括清洗、分类和分析',
20
+ icon: '📊',
21
+ status: '运行中',
22
+ totalExecutions: 1245,
23
+ successRate: 98.5,
24
+ avgRuntime: 3.2,
25
+ totalRuntime: 4392,
26
+ errorCount: 19
27
+ },
28
+ '2': {
29
+ name: '内容生成与发布',
30
+ description: '根据关键词自动生成文章并发布到多个平台',
31
+ icon: '✍️',
32
+ status: '运行中',
33
+ totalExecutions: 892,
34
+ successRate: 96.2,
35
+ avgRuntime: 5.8,
36
+ totalRuntime: 5174,
37
+ errorCount: 34
38
+ },
39
+ '3': {
40
+ name: '邮件智能分类回复',
41
+ description: '自动分类邮件并生成智能回复',
42
+ icon: '📧',
43
+ status: '暂停',
44
+ totalExecutions: 2156,
45
+ successRate: 94.8,
46
+ avgRuntime: 1.5,
47
+ totalRuntime: 3234,
48
+ errorCount: 112
49
+ },
50
+ '4': {
51
+ name: '订单处理自动化',
52
+ description: '从下单到发货的完整自动化处理流程',
53
+ icon: '📦',
54
+ status: '运行中',
55
+ totalExecutions: 3245,
56
+ successRate: 99.1,
57
+ avgRuntime: 2.1,
58
+ totalRuntime: 6815,
59
+ errorCount: 29
60
+ },
61
+ '5': {
62
+ name: '社交媒体监控',
63
+ description: '监控品牌提及并自动生成报告',
64
+ icon: '📱',
65
+ status: '运行中',
66
+ totalExecutions: 678,
67
+ successRate: 97.3,
68
+ avgRuntime: 4.2,
69
+ totalRuntime: 2848,
70
+ errorCount: 18
71
+ },
72
+ '6': {
73
+ name: '财务报表生成',
74
+ description: '自动收集财务数据并生成可视化报表',
75
+ icon: '💰',
76
+ status: '运行中',
77
+ totalExecutions: 456,
78
+ successRate: 99.8,
79
+ avgRuntime: 6.5,
80
+ totalRuntime: 2964,
81
+ errorCount: 1
82
+ }
83
+ };
84
+
85
+ const workflow = workflowData[workflowId as keyof typeof workflowData] || workflowData['1'];
86
+
87
+ const timeRanges = [
88
+ { value: '1d', label: '最近1天' },
89
+ { value: '7d', label: '最近7天' },
90
+ { value: '30d', label: '最近30天' },
91
+ { value: '90d', label: '最近90天' }
92
+ ];
93
+
94
+ return (
95
+ <div className="space-y-8">
96
+ {/* 页面头部 */}
97
+ <div className="flex items-center justify-between">
98
+ <div className="flex items-center space-x-4">
99
+ <Link href="/workflows" className="p-2 text-gray-400 hover:text-blue-600 transition-colors cursor-pointer">
100
+ <div className="w-5 h-5 flex items-center justify-center">
101
+ <i className="ri-arrow-left-line"></i>
102
+ </div>
103
+ </Link>
104
+ <div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center">
105
+ <span className="text-xl">{workflow.icon}</span>
106
+ </div>
107
+ <div>
108
+ <h1 className="text-3xl font-bold text-gray-900">{workflow.name}</h1>
109
+ <p className="text-gray-600 mt-1">{workflow.description}</p>
110
+ </div>
111
+ </div>
112
+
113
+ <div className="flex items-center space-x-4">
114
+ <div className="flex bg-white rounded-lg border border-gray-200 p-1">
115
+ {timeRanges.map((range) => (
116
+ <button
117
+ key={range.value}
118
+ onClick={() => setTimeRange(range.value)}
119
+ className={`px-4 py-2 text-sm font-medium rounded-md transition-colors cursor-pointer whitespace-nowrap \${
120
+ timeRange === range.value
121
+ ? 'bg-blue-100 text-blue-700'
122
+ : 'text-gray-600 hover:text-gray-900'
123
+ }`}
124
+ >
125
+ {range.label}
126
+ </button>
127
+ ))}
128
+ </div>
129
+
130
+ <Link href={`/workflows/${workflowId}/edit`} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap">
131
+ <div className="w-4 h-4 flex items-center justify-center inline-block mr-2">
132
+ <i className="ri-edit-line"></i>
133
+ </div>
134
+ 编辑工作流
135
+ </Link>
136
+ </div>
137
+ </div>
138
+
139
+ {/* 核心指标卡片 */}
140
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
141
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
142
+ <div className="flex items-center justify-between">
143
+ <div>
144
+ <p className="text-sm font-medium text-gray-600">总执行次数</p>
145
+ <p className="text-2xl font-bold text-gray-900 mt-2">{workflow.totalExecutions.toLocaleString()}</p>
146
+ </div>
147
+ <div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center">
148
+ <i className="ri-play-line text-blue-600 text-xl"></i>
149
+ </div>
150
+ </div>
151
+ <div className="flex items-center mt-4">
152
+ <span className="text-sm text-green-600">+12.5%</span>
153
+ <span className="text-sm text-gray-500 ml-2">较上期</span>
154
+ </div>
155
+ </div>
156
+
157
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
158
+ <div className="flex items-center justify-between">
159
+ <div>
160
+ <p className="text-sm font-medium text-gray-600">成功率</p>
161
+ <p className="text-2xl font-bold text-green-600 mt-2">{workflow.successRate}%</p>
162
+ </div>
163
+ <div className="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center">
164
+ <i className="ri-check-line text-green-600 text-xl"></i>
165
+ </div>
166
+ </div>
167
+ <div className="flex items-center mt-4">
168
+ <span className="text-sm text-green-600">+2.1%</span>
169
+ <span className="text-sm text-gray-500 ml-2">较上期</span>
170
+ </div>
171
+ </div>
172
+
173
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
174
+ <div className="flex items-center justify-between">
175
+ <div>
176
+ <p className="text-sm font-medium text-gray-600">平均运行时间</p>
177
+ <p className="text-2xl font-bold text-gray-900 mt-2">{workflow.avgRuntime}分</p>
178
+ </div>
179
+ <div className="w-12 h-12 bg-orange-100 rounded-lg flex items-center justify-center">
180
+ <i className="ri-time-line text-orange-600 text-xl"></i>
181
+ </div>
182
+ </div>
183
+ <div className="flex items-center mt-4">
184
+ <span className="text-sm text-red-600">+0.3分</span>
185
+ <span className="text-sm text-gray-500 ml-2">较上期</span>
186
+ </div>
187
+ </div>
188
+
189
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
190
+ <div className="flex items-center justify-between">
191
+ <div>
192
+ <p className="text-sm font-medium text-gray-600">总运行时间</p>
193
+ <p className="text-2xl font-bold text-gray-900 mt-2">{workflow.totalRuntime}分</p>
194
+ </div>
195
+ <div className="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center">
196
+ <i className="ri-timer-line text-purple-600 text-xl"></i>
197
+ </div>
198
+ </div>
199
+ <div className="flex items-center mt-4">
200
+ <span className="text-sm text-green-600">-5.2%</span>
201
+ <span className="text-sm text-gray-500 ml-2">较上期</span>
202
+ </div>
203
+ </div>
204
+
205
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
206
+ <div className="flex items-center justify-between">
207
+ <div>
208
+ <p className="text-sm font-medium text-gray-600">错误次数</p>
209
+ <p className="text-2xl font-bold text-red-600 mt-2">{workflow.errorCount}</p>
210
+ </div>
211
+ <div className="w-12 h-12 bg-red-100 rounded-lg flex items-center justify-center">
212
+ <i className="ri-error-warning-line text-red-600 text-xl"></i>
213
+ </div>
214
+ </div>
215
+ <div className="flex items-center mt-4">
216
+ <span className="text-sm text-green-600">-18.3%</span>
217
+ <span className="text-sm text-gray-500 ml-2">较上期</span>
218
+ </div>
219
+ </div>
220
+ </div>
221
+
222
+ {/* 性能趋势图表 */}
223
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
224
+ <PerformanceChart workflowId={workflowId} timeRange={timeRange} />
225
+ <NodeAnalytics workflowId={workflowId} />
226
+ </div>
227
+
228
+ {/* 执行历史 */}
229
+ <ExecutionHistory workflowId={workflowId} />
230
+ </div>
231
+ );
232
+ }
app/workflows/[id]/analytics/page.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { Suspense } from 'react';
3
+ import Header from '@/components/Header';
4
+ import Sidebar from '@/components/Sidebar';
5
+ import WorkflowAnalytics from './WorkflowAnalytics';
6
+
7
+ export async function generateStaticParams() {
8
+ return [
9
+ { id: '1' },
10
+ { id: '2' },
11
+ { id: '3' },
12
+ { id: '4' },
13
+ { id: '5' },
14
+ { id: '6' },
15
+ ];
16
+ }
17
+
18
+ export default function WorkflowAnalyticsPage({ params }: { params: { id: string } }) {
19
+ return (
20
+ <div className="min-h-screen bg-gray-50">
21
+ <Header />
22
+ <div className="flex">
23
+ <Sidebar />
24
+ <main className="flex-1 p-8">
25
+ <div className="max-w-7xl mx-auto">
26
+ <Suspense fallback={<div className="text-center py-8">Loading...</div>}>
27
+ <WorkflowAnalytics workflowId={params.id} />
28
+ </Suspense>
29
+ </div>
30
+ </main>
31
+ </div>
32
+ </div>
33
+ );
34
+ }
app/workflows/create/NodeLibrary.tsx ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ export default function NodeLibrary() {
6
+ const [activeCategory, setActiveCategory] = useState('trigger');
7
+ const [searchTerm, setSearchTerm] = useState('');
8
+
9
+ const nodeCategories = [
10
+ { id: 'trigger', name: '触发器', icon: 'ri-play-line' },
11
+ { id: 'action', name: '动作', icon: 'ri-tools-line' },
12
+ { id: 'condition', name: '条件', icon: 'ri-question-line' },
13
+ { id: 'data', name: '数据', icon: 'ri-database-line' },
14
+ { id: 'ai', name: 'AI', icon: 'ri-brain-line' },
15
+ { id: 'integration', name: '集成', icon: 'ri-links-line' }
16
+ ];
17
+
18
+ const nodes = {
19
+ trigger: [
20
+ { id: 'schedule', name: '定时触发', icon: '⏰', desc: '按时间表执行' },
21
+ { id: 'webhook', name: 'Webhook', icon: '🔗', desc: 'HTTP请求触发' },
22
+ { id: 'file-upload', name: '文件上传', icon: '📁', desc: '文件上传时触发' },
23
+ { id: 'email', name: '邮件触发', icon: '📧', desc: '收到邮件时触发' },
24
+ { id: 'database', name: '数据库变更', icon: '🗄️', desc: '数据变更时触发' }
25
+ ],
26
+ action: [
27
+ { id: 'http-request', name: 'HTTP请求', icon: '🌐', desc: '发送HTTP请求' },
28
+ { id: 'send-email', name: '发送邮件', icon: '📮', desc: '发送电子邮件' },
29
+ { id: 'file-operation', name: '文件操作', icon: '📄', desc: '文件增删改查' },
30
+ { id: 'notification', name: '消息通知', icon: '🔔', desc: '发送通知消息' },
31
+ { id: 'data-transform', name: '数据转换', icon: '🔄', desc: '转换数据格式' }
32
+ ],
33
+ condition: [
34
+ { id: 'if-else', name: '条件判断', icon: '🤔', desc: '根据条件分支' },
35
+ { id: 'filter', name: '数据过滤', icon: '🔍', desc: '过滤数据项' },
36
+ { id: 'loop', name: '循环处理', icon: '🔁', desc: '循环执行操作' },
37
+ { id: 'delay', name: '延时等待', icon: '⏱️', desc: '延时一段时间' },
38
+ { id: 'merge', name: '数据合并', icon: '🔀', desc: '合并多个数据源' }
39
+ ],
40
+ data: [
41
+ { id: 'mysql', name: 'MySQL', icon: '🐬', desc: 'MySQL数据库' },
42
+ { id: 'mongodb', name: 'MongoDB', icon: '🍃', desc: 'MongoDB数据库' },
43
+ { id: 'redis', name: 'Redis', icon: '📦', desc: 'Redis缓存' },
44
+ { id: 'csv', name: 'CSV文件', icon: '📊', desc: 'CSV格式数据' },
45
+ { id: 'json', name: 'JSON数据', icon: '📋', desc: 'JSON格式数据' }
46
+ ],
47
+ ai: [
48
+ { id: 'text-generation', name: '文本生成', icon: '✍️', desc: 'AI文本生成' },
49
+ { id: 'text-analysis', name: '文本分析', icon: '🔎', desc: '文本内容分析' },
50
+ { id: 'translation', name: '文本翻译', icon: '🌍', desc: '多语言翻译' },
51
+ { id: 'sentiment', name: '情感分析', icon: '😊', desc: '情感倾向分析' },
52
+ { id: 'ocr', name: '图片识别', icon: '👁️', desc: 'OCR文字识别' }
53
+ ],
54
+ integration: [
55
+ { id: 'slack', name: 'Slack', icon: '💬', desc: 'Slack消息推送' },
56
+ { id: 'wechat', name: '微信', icon: '💚', desc: '微信消息推送' },
57
+ { id: 'dingtalk', name: '钉钉', icon: '📱', desc: '钉钉消息推送' },
58
+ { id: 'github', name: 'GitHub', icon: '🐙', desc: 'GitHub集成' },
59
+ { id: 'google-sheets', name: 'Google Sheets', icon: '📈', desc: 'Google表格' }
60
+ ]
61
+ };
62
+
63
+ const handleDragStart = (e: React.DragEvent, node: any) => {
64
+ e.dataTransfer.setData('application/json', JSON.stringify({
65
+ type: 'node',
66
+ nodeType: node.id,
67
+ ...node
68
+ }));
69
+ };
70
+
71
+ const filteredNodes = searchTerm
72
+ ? nodes[activeCategory as keyof typeof nodes]?.filter(node =>
73
+ node.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
74
+ node.desc.toLowerCase().includes(searchTerm.toLowerCase())
75
+ )
76
+ : nodes[activeCategory as keyof typeof nodes];
77
+
78
+ return (
79
+ <div className="h-full flex flex-col">
80
+ {/* 搜索框 */}
81
+ <div className="p-4 border-b border-gray-200">
82
+ <div className="relative">
83
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
84
+ <div className="w-4 h-4 flex items-center justify-center">
85
+ <i className="ri-search-line text-gray-400"></i>
86
+ </div>
87
+ </div>
88
+ <input
89
+ type="text"
90
+ placeholder="搜索节点..."
91
+ value={searchTerm}
92
+ onChange={(e) => setSearchTerm(e.target.value)}
93
+ 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"
94
+ />
95
+ </div>
96
+ </div>
97
+
98
+ {/* 分类标签 */}
99
+ <div className="p-4 border-b border-gray-200">
100
+ <div className="grid grid-cols-2 gap-2">
101
+ {nodeCategories.map((category) => (
102
+ <button
103
+ key={category.id}
104
+ onClick={() => setActiveCategory(category.id)}
105
+ className={`p-2 rounded-lg text-sm font-medium transition-colors cursor-pointer ${
106
+ activeCategory === category.id
107
+ ? 'bg-blue-100 text-blue-700'
108
+ : 'text-gray-600 hover:bg-gray-100'
109
+ }`}
110
+ >
111
+ <div className="flex items-center space-x-2">
112
+ <div className="w-4 h-4 flex items-center justify-center">
113
+ <i className={category.icon}></i>
114
+ </div>
115
+ <span>{category.name}</span>
116
+ </div>
117
+ </button>
118
+ ))}
119
+ </div>
120
+ </div>
121
+
122
+ {/* 节点列表 */}
123
+ <div className="flex-1 overflow-y-auto p-4">
124
+ <div className="space-y-2">
125
+ {filteredNodes?.map((node) => (
126
+ <div
127
+ key={node.id}
128
+ draggable
129
+ onDragStart={(e) => handleDragStart(e, node)}
130
+ className="p-3 bg-white border border-gray-200 rounded-lg hover:shadow-md transition-all cursor-move"
131
+ >
132
+ <div className="flex items-start space-x-3">
133
+ <div className="w-8 h-8 bg-gray-100 rounded-lg flex items-center justify-center flex-shrink-0">
134
+ <span className="text-sm">{node.icon}</span>
135
+ </div>
136
+ <div className="flex-1 min-w-0">
137
+ <h4 className="font-medium text-gray-900 text-sm">{node.name}</h4>
138
+ <p className="text-xs text-gray-500 mt-1 line-clamp-2">{node.desc}</p>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ ))}
143
+ </div>
144
+ </div>
145
+
146
+ {/* 模板快捷入口 */}
147
+ <div className="p-4 border-t border-gray-200">
148
+ <button className="w-full p-3 bg-blue-50 text-blue-700 rounded-lg hover:bg-blue-100 transition-colors cursor-pointer">
149
+ <div className="flex items-center justify-center space-x-2">
150
+ <div className="w-4 h-4 flex items-center justify-center">
151
+ <i className="ri-template-line"></i>
152
+ </div>
153
+ <span className="text-sm font-medium">使用模板</span>
154
+ </div>
155
+ </button>
156
+ </div>
157
+ </div>
158
+ );
159
+ }
app/workflows/create/WorkflowBuilder.tsx ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import WorkflowCanvas from './WorkflowCanvas';
5
+ import NodeLibrary from './NodeLibrary';
6
+ import WorkflowSettings from './WorkflowSettings';
7
+
8
+ export default function WorkflowBuilder() {
9
+ const [workflowData, setWorkflowData] = useState({
10
+ name: '',
11
+ description: '',
12
+ category: 'data-processing',
13
+ triggers: [],
14
+ nodes: [],
15
+ connections: []
16
+ });
17
+
18
+ const [selectedNode, setSelectedNode] = useState(null);
19
+ const [showSettings, setShowSettings] = useState(false);
20
+
21
+ const handleSave = () => {
22
+ console.log('保存工作流:', workflowData);
23
+ alert('工作流保存成功!');
24
+ };
25
+
26
+ const handleDeploy = () => {
27
+ console.log('部署工作流:', workflowData);
28
+ alert('工作流部署成功!');
29
+ };
30
+
31
+ const handleTest = () => {
32
+ console.log('测试工作流:', workflowData);
33
+ alert('开始测试工作流...');
34
+ };
35
+
36
+ return (
37
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200">
38
+ {/* 顶部工具栏 */}
39
+ <div className="p-6 border-b border-gray-200">
40
+ <div className="flex items-center justify-between">
41
+ <div className="flex items-center space-x-6">
42
+ <div>
43
+ <input
44
+ type="text"
45
+ placeholder="输入工作流名称"
46
+ value={workflowData.name}
47
+ onChange={(e) => setWorkflowData({...workflowData, name: e.target.value})}
48
+ className="text-xl font-semibold bg-transparent border-none focus:outline-none focus:ring-0 p-0 text-gray-900 placeholder-gray-500"
49
+ />
50
+ <p className="text-sm text-gray-500 mt-1">设计您的自动化流程</p>
51
+ </div>
52
+
53
+ <div className="flex items-center space-x-3">
54
+ <button
55
+ onClick={() => setShowSettings(!showSettings)}
56
+ className={`p-2 rounded-lg transition-colors cursor-pointer ${
57
+ showSettings ? 'bg-blue-100 text-blue-600' : 'text-gray-500 hover:bg-gray-100'
58
+ }`}
59
+ >
60
+ <div className="w-5 h-5 flex items-center justify-center">
61
+ <i className="ri-settings-line"></i>
62
+ </div>
63
+ </button>
64
+ <button className="p-2 text-gray-500 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer">
65
+ <div className="w-5 h-5 flex items-center justify-center">
66
+ <i className="ri-zoom-in-line"></i>
67
+ </div>
68
+ </button>
69
+ <button className="p-2 text-gray-500 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer">
70
+ <div className="w-5 h-5 flex items-center justify-center">
71
+ <i className="ri-zoom-out-line"></i>
72
+ </div>
73
+ </button>
74
+ <button className="p-2 text-gray-500 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer">
75
+ <div className="w-5 h-5 flex items-center justify-center">
76
+ <i className="ri-fullscreen-line"></i>
77
+ </div>
78
+ </button>
79
+ </div>
80
+ </div>
81
+
82
+ <div className="flex items-center space-x-3">
83
+ <button
84
+ onClick={handleTest}
85
+ className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap"
86
+ >
87
+ <div className="w-4 h-4 flex items-center justify-center inline-block mr-2">
88
+ <i className="ri-play-line"></i>
89
+ </div>
90
+ 测试运行
91
+ </button>
92
+ <button
93
+ onClick={handleSave}
94
+ className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap"
95
+ >
96
+ 保存草稿
97
+ </button>
98
+ <button
99
+ onClick={handleDeploy}
100
+ className="bg-blue-600 text-white px-6 py-2 rounded-lg font-medium hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap"
101
+ >
102
+ <div className="w-4 h-4 flex items-center justify-center inline-block mr-2">
103
+ <i className="ri-rocket-line"></i>
104
+ </div>
105
+ 部署
106
+ </button>
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ {/* 主内容区域 */}
112
+ <div className="flex h-screen">
113
+ {/* 左侧节点库 */}
114
+ <div className="w-80 border-r border-gray-200 bg-gray-50">
115
+ <NodeLibrary />
116
+ </div>
117
+
118
+ {/* 中间画布区域 */}
119
+ <div className="flex-1">
120
+ <WorkflowCanvas
121
+ nodes={workflowData.nodes}
122
+ connections={workflowData.connections}
123
+ onNodeSelect={setSelectedNode}
124
+ onUpdateWorkflow={setWorkflowData}
125
+ />
126
+ </div>
127
+
128
+ {/* 右侧设置面板 */}
129
+ {showSettings && (
130
+ <div className="w-80 border-l border-gray-200 bg-gray-50">
131
+ <WorkflowSettings
132
+ workflowData={workflowData}
133
+ selectedNode={selectedNode}
134
+ onUpdateWorkflow={setWorkflowData}
135
+ />
136
+ </div>
137
+ )}
138
+ </div>
139
+ </div>
140
+ );
141
+ }
app/workflows/create/WorkflowCanvas.tsx ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState, useRef } from 'react';
4
+
5
+ interface WorkflowCanvasProps {
6
+ nodes: any[];
7
+ connections: any[];
8
+ onNodeSelect: (node: any) => void;
9
+ onUpdateWorkflow: (data: any) => void;
10
+ }
11
+
12
+ export default function WorkflowCanvas({ nodes, connections, onNodeSelect, onUpdateWorkflow }: WorkflowCanvasProps) {
13
+ const canvasRef = useRef<HTMLDivElement>(null);
14
+ const [draggedNode, setDraggedNode] = useState(null);
15
+ const [selectedNode, setSelectedNode] = useState(null);
16
+ const [canvasNodes, setCanvasNodes] = useState([
17
+ {
18
+ id: 'start',
19
+ type: 'trigger',
20
+ name: '开始',
21
+ icon: '🚀',
22
+ x: 100,
23
+ y: 100,
24
+ isFixed: true
25
+ }
26
+ ]);
27
+
28
+ const handleDragOver = (e: React.DragEvent) => {
29
+ e.preventDefault();
30
+ };
31
+
32
+ const handleDrop = (e: React.DragEvent) => {
33
+ e.preventDefault();
34
+
35
+ const nodeData = JSON.parse(e.dataTransfer.getData('application/json'));
36
+ const rect = canvasRef.current?.getBoundingClientRect();
37
+
38
+ if (rect) {
39
+ const newNode = {
40
+ id: `node_${Date.now()}`,
41
+ type: nodeData.type,
42
+ name: nodeData.name,
43
+ icon: nodeData.icon,
44
+ nodeType: nodeData.nodeType,
45
+ x: e.clientX - rect.left - 50,
46
+ y: e.clientY - rect.top - 25,
47
+ config: {}
48
+ };
49
+
50
+ setCanvasNodes(prev => [...prev, newNode]);
51
+ }
52
+ };
53
+
54
+ const handleNodeClick = (node: any) => {
55
+ setSelectedNode(node);
56
+ onNodeSelect(node);
57
+ };
58
+
59
+ const handleNodeDrag = (nodeId: string, newX: number, newY: number) => {
60
+ setCanvasNodes(prev =>
61
+ prev.map(node =>
62
+ node.id === nodeId ? { ...node, x: newX, y: newY } : node
63
+ )
64
+ );
65
+ };
66
+
67
+ const deleteNode = (nodeId: string) => {
68
+ setCanvasNodes(prev => prev.filter(node => node.id !== nodeId && !node.isFixed));
69
+ if (selectedNode && selectedNode.id === nodeId) {
70
+ setSelectedNode(null);
71
+ onNodeSelect(null);
72
+ }
73
+ };
74
+
75
+ return (
76
+ <div
77
+ ref={canvasRef}
78
+ className="relative w-full h-full bg-gray-50 overflow-auto"
79
+ onDragOver={handleDragOver}
80
+ onDrop={handleDrop}
81
+ style={{
82
+ backgroundImage: 'radial-gradient(circle, #d1d5db 1px, transparent 1px)',
83
+ backgroundSize: '20px 20px'
84
+ }}
85
+ >
86
+ {/* 画布提示 */}
87
+ {canvasNodes.length <= 1 && (
88
+ <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
89
+ <div className="text-center text-gray-500">
90
+ <div className="w-16 h-16 bg-gray-200 rounded-full flex items-center justify-center mx-auto mb-4">
91
+ <div className="w-8 h-8 flex items-center justify-center">
92
+ <i className="ri-drag-drop-line text-2xl"></i>
93
+ </div>
94
+ </div>
95
+ <p className="text-lg font-medium mb-2">开始构建您的工作流</p>
96
+ <p className="text-sm">从左侧拖拽节点到画布上</p>
97
+ </div>
98
+ </div>
99
+ )}
100
+
101
+ {/* 渲染节点 */}
102
+ {canvasNodes.map((node) => (
103
+ <WorkflowNode
104
+ key={node.id}
105
+ node={node}
106
+ isSelected={selectedNode?.id === node.id}
107
+ onClick={() => handleNodeClick(node)}
108
+ onDrag={handleNodeDrag}
109
+ onDelete={deleteNode}
110
+ />
111
+ ))}
112
+
113
+ {/* 连接线 */}
114
+ <svg className="absolute inset-0 pointer-events-none">
115
+ {connections.map((connection, index) => {
116
+ const startNode = canvasNodes.find(n => n.id === connection.from);
117
+ const endNode = canvasNodes.find(n => n.id === connection.to);
118
+
119
+ if (startNode && endNode) {
120
+ return (
121
+ <line
122
+ key={index}
123
+ x1={startNode.x + 50}
124
+ y1={startNode.y + 25}
125
+ x2={endNode.x}
126
+ y2={endNode.y + 25}
127
+ stroke="#6b7280"
128
+ strokeWidth="2"
129
+ markerEnd="url(#arrowhead)"
130
+ />
131
+ );
132
+ }
133
+ return null;
134
+ })}
135
+
136
+ {/* 箭头定义 */}
137
+ <defs>
138
+ <marker
139
+ id="arrowhead"
140
+ markerWidth="10"
141
+ markerHeight="7"
142
+ refX="9"
143
+ refY="3.5"
144
+ orient="auto"
145
+ >
146
+ <polygon
147
+ points="0 0, 10 3.5, 0 7"
148
+ fill="#6b7280"
149
+ />
150
+ </marker>
151
+ </defs>
152
+ </svg>
153
+ </div>
154
+ );
155
+ }
156
+
157
+ // 工作流节点组件
158
+ function WorkflowNode({ node, isSelected, onClick, onDrag, onDelete }: any) {
159
+ const [isDragging, setIsDragging] = useState(false);
160
+ const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
161
+
162
+ const handleMouseDown = (e: React.MouseEvent) => {
163
+ if (node.isFixed) return;
164
+
165
+ setIsDragging(true);
166
+ setDragStart({
167
+ x: e.clientX - node.x,
168
+ y: e.clientY - node.y
169
+ });
170
+ };
171
+
172
+ const handleMouseMove = (e: React.MouseEvent) => {
173
+ if (!isDragging || node.isFixed) return;
174
+
175
+ onDrag(node.id, e.clientX - dragStart.x, e.clientY - dragStart.y);
176
+ };
177
+
178
+ const handleMouseUp = () => {
179
+ setIsDragging(false);
180
+ };
181
+
182
+ return (
183
+ <div
184
+ className={`absolute bg-white border-2 rounded-lg shadow-md cursor-pointer transition-all ${
185
+ isSelected ? 'border-blue-500 shadow-lg' : 'border-gray-200 hover:shadow-lg'
186
+ } ${isDragging ? 'cursor-move' : ''}`}
187
+ style={{
188
+ left: node.x,
189
+ top: node.y,
190
+ width: 100,
191
+ height: 50,
192
+ zIndex: isSelected ? 10 : 1
193
+ }}
194
+ onClick={onClick}
195
+ onMouseDown={handleMouseDown}
196
+ onMouseMove={handleMouseMove}
197
+ onMouseUp={handleMouseUp}
198
+ >
199
+ <div className="p-2 h-full flex flex-col items-center justify-center">
200
+ <div className="text-lg mb-1">{node.icon}</div>
201
+ <div className="text-xs font-medium text-gray-700 text-center line-clamp-1">
202
+ {node.name}
203
+ </div>
204
+ </div>
205
+
206
+ {/* 连接点 */}
207
+ {!node.isFixed && (
208
+ <>
209
+ <div className="absolute -left-2 top-1/2 transform -translate-y-1/2 w-4 h-4 bg-blue-500 rounded-full border-2 border-white cursor-pointer"></div>
210
+ <div className="absolute -right-2 top-1/2 transform -translate-y-1/2 w-4 h-4 bg-blue-500 rounded-full border-2 border-white cursor-pointer"></div>
211
+ </>
212
+ )}
213
+
214
+ {node.isFixed && (
215
+ <div className="absolute -right-2 top-1/2 transform -translate-y-1/2 w-4 h-4 bg-blue-500 rounded-full border-2 border-white cursor-pointer"></div>
216
+ )}
217
+
218
+ {/* 删除按钮 */}
219
+ {!node.isFixed && isSelected && (
220
+ <button
221
+ onClick={(e) => {
222
+ e.stopPropagation();
223
+ onDelete(node.id);
224
+ }}
225
+ className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center hover:bg-red-600 transition-colors cursor-pointer"
226
+ >
227
+ <i className="ri-close-line text-xs"></i>
228
+ </button>
229
+ )}
230
+ </div>
231
+ );
232
+ }
app/workflows/create/WorkflowSettings.tsx ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ interface WorkflowSettingsProps {
6
+ workflowData: any;
7
+ selectedNode: any;
8
+ onUpdateWorkflow: (data: any) => void;
9
+ }
10
+
11
+ export default function WorkflowSettings({ workflowData, selectedNode, onUpdateWorkflow }: WorkflowSettingsProps) {
12
+ const [activeTab, setActiveTab] = useState('general');
13
+
14
+ const tabs = [
15
+ { id: 'general', name: '基本设置', icon: 'ri-settings-line' },
16
+ { id: 'triggers', name: '触发器', icon: 'ri-play-line' },
17
+ { id: 'variables', name: '变量', icon: 'ri-code-line' },
18
+ { id: 'security', name: '权限', icon: 'ri-shield-line' }
19
+ ];
20
+
21
+ const categories = [
22
+ { value: 'data-processing', label: '数据处理' },
23
+ { value: 'content-creation', label: '内容创作' },
24
+ { value: 'automation', label: '任务自动化' },
25
+ { value: 'integration', label: '系统集成' }
26
+ ];
27
+
28
+ const handleInputChange = (field: string, value: any) => {
29
+ onUpdateWorkflow({ ...workflowData, [field]: value });
30
+ };
31
+
32
+ const renderGeneralSettings = () => (
33
+ <div className="space-y-6">
34
+ <div>
35
+ <label className="block text-sm font-medium text-gray-700 mb-2">工作流名称</label>
36
+ <input
37
+ type="text"
38
+ placeholder="输入工作流名称"
39
+ value={workflowData.name}
40
+ onChange={(e) => handleInputChange('name', e.target.value)}
41
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
42
+ />
43
+ </div>
44
+
45
+ <div>
46
+ <label className="block text-sm font-medium text-gray-700 mb-2">描述</label>
47
+ <textarea
48
+ placeholder="描述工作流的功能和用途"
49
+ rows={3}
50
+ maxLength={500}
51
+ value={workflowData.description}
52
+ onChange={(e) => handleInputChange('description', e.target.value)}
53
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm resize-none"
54
+ />
55
+ <div className="text-xs text-gray-500 mt-1">{workflowData.description.length}/500</div>
56
+ </div>
57
+
58
+ <div>
59
+ <label className="block text-sm font-medium text-gray-700 mb-2">分类</label>
60
+ <select
61
+ value={workflowData.category}
62
+ onChange={(e) => handleInputChange('category', e.target.value)}
63
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm pr-8"
64
+ >
65
+ {categories.map((category) => (
66
+ <option key={category.value} value={category.value}>
67
+ {category.label}
68
+ </option>
69
+ ))}
70
+ </select>
71
+ </div>
72
+
73
+ <div>
74
+ <label className="block text-sm font-medium text-gray-700 mb-3">执行设置</label>
75
+ <div className="space-y-3">
76
+ <div className="flex items-center justify-between">
77
+ <span className="text-sm text-gray-700">并行执行</span>
78
+ <div className="w-10 h-6 bg-gray-300 rounded-full relative cursor-pointer">
79
+ <div className="w-4 h-4 bg-white rounded-full absolute left-1 top-1"></div>
80
+ </div>
81
+ </div>
82
+ <div className="flex items-center justify-between">
83
+ <span className="text-sm text-gray-700">错误时停止</span>
84
+ <div className="w-10 h-6 bg-blue-600 rounded-full relative cursor-pointer">
85
+ <div className="w-4 h-4 bg-white rounded-full absolute right-1 top-1"></div>
86
+ </div>
87
+ </div>
88
+ <div className="flex items-center justify-between">
89
+ <span className="text-sm text-gray-700">保存执行日志</span>
90
+ <div className="w-10 h-6 bg-blue-600 rounded-full relative cursor-pointer">
91
+ <div className="w-4 h-4 bg-white rounded-full absolute right-1 top-1"></div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ );
98
+
99
+ const renderTriggerSettings = () => (
100
+ <div className="space-y-6">
101
+ <div>
102
+ <h4 className="font-medium text-gray-900 mb-3">触发条件</h4>
103
+ <div className="space-y-3">
104
+ <div className="p-3 border border-gray-200 rounded-lg">
105
+ <div className="flex items-center justify-between mb-2">
106
+ <span className="font-medium text-sm">定时触发</span>
107
+ <div className="w-8 h-5 bg-blue-600 rounded-full relative cursor-pointer">
108
+ <div className="w-3 h-3 bg-white rounded-full absolute right-1 top-1"></div>
109
+ </div>
110
+ </div>
111
+ <input
112
+ type="text"
113
+ placeholder="0 0 * * *"
114
+ className="w-full px-3 py-2 border border-gray-300 rounded text-sm"
115
+ />
116
+ <p className="text-xs text-gray-500 mt-1">Cron表达式格式</p>
117
+ </div>
118
+
119
+ <div className="p-3 border border-gray-200 rounded-lg">
120
+ <div className="flex items-center justify-between mb-2">
121
+ <span className="font-medium text-sm">Webhook触发</span>
122
+ <div className="w-8 h-5 bg-gray-300 rounded-full relative cursor-pointer">
123
+ <div className="w-3 h-3 bg-white rounded-full absolute left-1 top-1"></div>
124
+ </div>
125
+ </div>
126
+ <div className="text-xs text-gray-500">
127
+ URL: https://api.example.com/webhook/abc123
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ );
134
+
135
+ const renderVariableSettings = () => (
136
+ <div className="space-y-6">
137
+ <div>
138
+ <div className="flex items-center justify-between mb-3">
139
+ <h4 className="font-medium text-gray-900">环境变量</h4>
140
+ <button className="px-3 py-1 bg-blue-600 text-white rounded text-sm hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap">
141
+ 添加变量
142
+ </button>
143
+ </div>
144
+ <div className="space-y-2">
145
+ <div className="flex items-center space-x-2">
146
+ <input
147
+ type="text"
148
+ placeholder="变量名"
149
+ className="flex-1 px-3 py-2 border border-gray-300 rounded text-sm"
150
+ />
151
+ <input
152
+ type="text"
153
+ placeholder="变量值"
154
+ className="flex-1 px-3 py-2 border border-gray-300 rounded text-sm"
155
+ />
156
+ <button className="p-2 text-red-600 hover:bg-red-50 rounded cursor-pointer">
157
+ <div className="w-4 h-4 flex items-center justify-center">
158
+ <i className="ri-delete-bin-line"></i>
159
+ </div>
160
+ </button>
161
+ </div>
162
+ </div>
163
+ </div>
164
+
165
+ <div>
166
+ <h4 className="font-medium text-gray-900 mb-3">全局配置</h4>
167
+ <div className="space-y-3">
168
+ <div>
169
+ <label className="block text-sm text-gray-700 mb-1">超时时间(秒)</label>
170
+ <input
171
+ type="number"
172
+ defaultValue="300"
173
+ className="w-full px-3 py-2 border border-gray-300 rounded text-sm"
174
+ />
175
+ </div>
176
+ <div>
177
+ <label className="block text-sm text-gray-700 mb-1">重试次数</label>
178
+ <input
179
+ type="number"
180
+ defaultValue="3"
181
+ className="w-full px-3 py-2 border border-gray-300 rounded text-sm"
182
+ />
183
+ </div>
184
+ </div>
185
+ </div>
186
+ </div>
187
+ );
188
+
189
+ const renderSecuritySettings = () => (
190
+ <div className="space-y-6">
191
+ <div>
192
+ <h4 className="font-medium text-gray-900 mb-3">访问权限</h4>
193
+ <div className="space-y-3">
194
+ <div className="flex items-center justify-between">
195
+ <span className="text-sm text-gray-700">公开工作流</span>
196
+ <div className="w-10 h-6 bg-gray-300 rounded-full relative cursor-pointer">
197
+ <div className="w-4 h-4 bg-white rounded-full absolute left-1 top-1"></div>
198
+ </div>
199
+ </div>
200
+ <div className="flex items-center justify-between">
201
+ <span className="text-sm text-gray-700">需要API密钥</span>
202
+ <div className="w-10 h-6 bg-blue-600 rounded-full relative cursor-pointer">
203
+ <div className="w-4 h-4 bg-white rounded-full absolute right-1 top-1"></div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </div>
208
+
209
+ <div>
210
+ <h4 className="font-medium text-gray-900 mb-3">数据安全</h4>
211
+ <div className="space-y-3">
212
+ <div className="flex items-center justify-between">
213
+ <span className="text-sm text-gray-700">加密存储</span>
214
+ <div className="w-10 h-6 bg-blue-600 rounded-full relative cursor-pointer">
215
+ <div className="w-4 h-4 bg-white rounded-full absolute right-1 top-1"></div>
216
+ </div>
217
+ </div>
218
+ <div className="flex items-center justify-between">
219
+ <span className="text-sm text-gray-700">审计日志</span>
220
+ <div className="w-10 h-6 bg-blue-600 rounded-full relative cursor-pointer">
221
+ <div className="w-4 h-4 bg-white rounded-full absolute right-1 top-1"></div>
222
+ </div>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ </div>
227
+ );
228
+
229
+ const renderNodeSettings = () => {
230
+ if (!selectedNode) {
231
+ return (
232
+ <div className="flex items-center justify-center h-64 text-gray-500">
233
+ <div className="text-center">
234
+ <div className="w-16 h-16 bg-gray-200 rounded-full flex items-center justify-center mx-auto mb-4">
235
+ <div className="w-8 h-8 flex items-center justify-center">
236
+ <i className="ri-cursor-line text-2xl"></i>
237
+ </div>
238
+ </div>
239
+ <p className="font-medium">选择一个节点</p>
240
+ <p className="text-sm mt-1">点击画布上的节点来配置参数</p>
241
+ </div>
242
+ </div>
243
+ );
244
+ }
245
+
246
+ return (
247
+ <div className="space-y-6">
248
+ <div className="flex items-center space-x-3">
249
+ <div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
250
+ <span className="text-lg">{selectedNode.icon}</span>
251
+ </div>
252
+ <div>
253
+ <h3 className="font-semibold text-gray-900">{selectedNode.name}</h3>
254
+ <p className="text-sm text-gray-500">节点配置</p>
255
+ </div>
256
+ </div>
257
+
258
+ <div>
259
+ <label className="block text-sm font-medium text-gray-700 mb-2">节点名称</label>
260
+ <input
261
+ type="text"
262
+ defaultValue={selectedNode.name}
263
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
264
+ />
265
+ </div>
266
+
267
+ <div>
268
+ <label className="block text-sm font-medium text-gray-700 mb-2">描述</label>
269
+ <textarea
270
+ placeholder="节点功能描述"
271
+ rows={3}
272
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm resize-none"
273
+ />
274
+ </div>
275
+
276
+ {/* 根据节点类型显示不同的配置选项 */}
277
+ <div>
278
+ <h4 className="font-medium text-gray-900 mb-3">参数配置</h4>
279
+ <div className="space-y-3">
280
+ <div>
281
+ <label className="block text-sm text-gray-700 mb-1">输入参数</label>
282
+ <input
283
+ type="text"
284
+ placeholder="参数值"
285
+ className="w-full px-3 py-2 border border-gray-300 rounded text-sm"
286
+ />
287
+ </div>
288
+ <div>
289
+ <label className="block text-sm text-gray-700 mb-1">输出格式</label>
290
+ <select className="w-full px-3 py-2 border border-gray-300 rounded text-sm pr-8">
291
+ <option>JSON</option>
292
+ <option>XML</option>
293
+ <option>TEXT</option>
294
+ </select>
295
+ </div>
296
+ </div>
297
+ </div>
298
+ </div>
299
+ );
300
+ };
301
+
302
+ return (
303
+ <div className="h-full flex flex-col">
304
+ {/* 标签页 */}
305
+ <div className="p-4 border-b border-gray-200">
306
+ <div className="grid grid-cols-2 gap-1">
307
+ {tabs.map((tab) => (
308
+ <button
309
+ key={tab.id}
310
+ onClick={() => setActiveTab(tab.id)}
311
+ className={`p-2 rounded-lg text-xs font-medium transition-colors cursor-pointer ${
312
+ activeTab === tab.id
313
+ ? 'bg-blue-100 text-blue-700'
314
+ : 'text-gray-600 hover:bg-gray-100'
315
+ }`}
316
+ >
317
+ <div className="flex flex-col items-center space-y-1">
318
+ <div className="w-4 h-4 flex items-center justify-center">
319
+ <i className={tab.icon}></i>
320
+ </div>
321
+ <span>{tab.name}</span>
322
+ </div>
323
+ </button>
324
+ ))}
325
+ </div>
326
+ </div>
327
+
328
+ {/* 内容区域 */}
329
+ <div className="flex-1 overflow-y-auto p-4">
330
+ {selectedNode && activeTab === 'general' ? renderNodeSettings() :
331
+ activeTab === 'general' ? renderGeneralSettings() :
332
+ activeTab === 'triggers' ? renderTriggerSettings() :
333
+ activeTab === 'variables' ? renderVariableSettings() :
334
+ activeTab === 'security' ? renderSecuritySettings() : null}
335
+ </div>
336
+ </div>
337
+ );
338
+ }
app/workflows/create/page.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import Header from '@/components/Header';
4
+ import Sidebar from '@/components/Sidebar';
5
+ import WorkflowBuilder from './WorkflowBuilder';
6
+
7
+ export default function CreateWorkflowPage() {
8
+ return (
9
+ <div className="min-h-screen bg-gray-50">
10
+ <Header />
11
+ <div className="flex">
12
+ <Sidebar />
13
+ <main className="flex-1 p-8">
14
+ <div className="max-w-7xl mx-auto">
15
+ <div className="mb-8">
16
+ <h1 className="text-3xl font-bold text-gray-900 mb-2">创建新工作流</h1>
17
+ <p className="text-gray-600">设计和配置您的自动化工作流程</p>
18
+ </div>
19
+
20
+ <WorkflowBuilder />
21
+ </div>
22
+ </main>
23
+ </div>
24
+ </div>
25
+ );
26
+ }
app/workflows/page.tsx ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import Header from '@/components/Header';
4
+ import Sidebar from '@/components/Sidebar';
5
+ import WorkflowList from './WorkflowList';
6
+ import WorkflowFilters from './WorkflowFilters';
7
+ import Link from 'next/link';
8
+
9
+ export default function WorkflowsPage() {
10
+ return (
11
+ <div className="min-h-screen bg-gray-50">
12
+ <Header />
13
+ <div className="flex">
14
+ <Sidebar />
15
+ <main className="flex-1 p-8">
16
+ <div className="max-w-7xl mx-auto">
17
+ <div className="flex items-center justify-between mb-8">
18
+ <div>
19
+ <h1 className="text-3xl font-bold text-gray-900 mb-2">工作流管理</h1>
20
+ <p className="text-gray-600">创建和管理自动化工作流程</p>
21
+ </div>
22
+
23
+ <div className="flex items-center space-x-4">
24
+ <Link
25
+ href="/workflows/templates"
26
+ className="bg-white text-gray-700 border border-gray-300 px-6 py-3 rounded-lg font-medium hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap"
27
+ >
28
+ <div className="w-5 h-5 flex items-center justify-center inline-block mr-2">
29
+ <i className="ri-template-line"></i>
30
+ </div>
31
+ 模板库
32
+ </Link>
33
+ <Link
34
+ href="/workflows/create"
35
+ className="bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap"
36
+ >
37
+ <div className="w-5 h-5 flex items-center justify-center inline-block mr-2">
38
+ <i className="ri-add-line"></i>
39
+ </div>
40
+ 创建工作流
41
+ </Link>
42
+ </div>
43
+ </div>
44
+
45
+ <WorkflowFilters />
46
+ <WorkflowList />
47
+ </div>
48
+ </main>
49
+ </div>
50
+ </div>
51
+ );
52
+ }
app/workflows/templates/TemplateFilters.tsx ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ export default function TemplateFilters() {
6
+ const [activeCategory, setActiveCategory] = useState('all');
7
+ const [searchTerm, setSearchTerm] = useState('');
8
+
9
+ const categories = [
10
+ { key: 'all', label: '全部模板', count: 48 },
11
+ { key: 'data-processing', label: '数据处理', count: 16 },
12
+ { key: 'content-creation', label: '内容创作', count: 12 },
13
+ { key: 'automation', label: '任务自动化', count: 10 },
14
+ { key: 'integration', label: '系统集成', count: 8 },
15
+ { key: 'ai-workflow', label: 'AI工作流', count: 6 }
16
+ ];
17
+
18
+ const popularTags = [
19
+ 'API集成', '数据清洗', '内容生成', '邮件自动化', '报告生成',
20
+ '社交媒体', '客户服务', '电商运营', '财务管理', '项目管理'
21
+ ];
22
+
23
+ return (
24
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
25
+ <div className="flex flex-col gap-6">
26
+ {/* 分类筛选 */}
27
+ <div>
28
+ <h3 className="text-sm font-medium text-gray-900 mb-3">模板分类</h3>
29
+ <div className="flex flex-wrap gap-2">
30
+ {categories.map((category) => (
31
+ <button
32
+ key={category.key}
33
+ onClick={() => setActiveCategory(category.key)}
34
+ className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors cursor-pointer whitespace-nowrap ${
35
+ activeCategory === category.key
36
+ ? 'bg-blue-100 text-blue-700'
37
+ : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
38
+ }`}
39
+ >
40
+ {category.label} ({category.count})
41
+ </button>
42
+ ))}
43
+ </div>
44
+ </div>
45
+
46
+ {/* 搜索和排序 */}
47
+ <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
48
+ <div className="relative flex-1 max-w-md">
49
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
50
+ <div className="w-4 h-4 flex items-center justify-center">
51
+ <i className="ri-search-line text-gray-400"></i>
52
+ </div>
53
+ </div>
54
+ <input
55
+ type="text"
56
+ placeholder="搜索模板..."
57
+ value={searchTerm}
58
+ onChange={(e) => setSearchTerm(e.target.value)}
59
+ 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"
60
+ />
61
+ </div>
62
+
63
+ <div className="flex items-center space-x-3">
64
+ <div className="relative">
65
+ <button className="px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap">
66
+ 热门程度
67
+ <div className="w-4 h-4 flex items-center justify-center inline-block ml-2">
68
+ <i className="ri-arrow-down-s-line"></i>
69
+ </div>
70
+ </button>
71
+ </div>
72
+
73
+ <div className="relative">
74
+ <button className="px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer whitespace-nowrap">
75
+ 创建时间
76
+ <div className="w-4 h-4 flex items-center justify-center inline-block ml-2">
77
+ <i className="ri-arrow-down-s-line"></i>
78
+ </div>
79
+ </button>
80
+ </div>
81
+ </div>
82
+ </div>
83
+
84
+ {/* 热门标签 */}
85
+ <div>
86
+ <h3 className="text-sm font-medium text-gray-900 mb-3">热门标签</h3>
87
+ <div className="flex flex-wrap gap-2">
88
+ {popularTags.map((tag, index) => (
89
+ <button
90
+ key={index}
91
+ className="px-3 py-1 bg-gray-100 text-gray-700 text-sm rounded-full hover:bg-gray-200 transition-colors cursor-pointer whitespace-nowrap"
92
+ >
93
+ {tag}
94
+ </button>
95
+ ))}
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ );
101
+ }
app/workflows/templates/TemplateLibrary.tsx ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import Link from 'next/link';
5
+
6
+ export default function TemplateLibrary() {
7
+ const [selectedTemplate, setSelectedTemplate] = useState<number | null>(null);
8
+
9
+ const templates = [
10
+ {
11
+ id: 1,
12
+ name: '客户数据自动化处理',
13
+ description: '自动收集、清洗和分析客户数据,生成洞察报告',
14
+ category: '数据处理',
15
+ difficulty: '中级',
16
+ estimatedTime: '10-15分钟',
17
+ usageCount: 2156,
18
+ rating: 4.8,
19
+ tags: ['数据清洗', 'ETL', '客户分析', '报告生成'],
20
+ icon: '📊',
21
+ features: ['自动数据收集', '智能数据清洗', '实时分析', '可视化报表'],
22
+ preview: 'https://readdy.ai/api/search-image?query=modern%20data%20processing%20dashboard%20with%20clean%20charts%20and%20analytics%20visualization%2C%20blue%20and%20white%20interface%2C%20professional%20business%20intelligence%20design&width=400&height=240&seq=template_1&orientation=landscape'
23
+ },
24
+ {
25
+ id: 2,
26
+ name: 'AI内容创作助手',
27
+ description: '基于关键词自动生成高质量文章并发布到多个平台',
28
+ category: '内容创作',
29
+ difficulty: '简单',
30
+ estimatedTime: '5-10分钟',
31
+ usageCount: 1892,
32
+ rating: 4.9,
33
+ tags: ['内容生成', 'SEO优化', '多平台发布', 'AI写作'],
34
+ icon: '✍️',
35
+ features: ['AI智能写作', 'SEO优化', '一键发布', '内容调度'],
36
+ preview: 'https://readdy.ai/api/search-image?query=AI%20content%20creation%20interface%20with%20writing%20tools%20and%20publishing%20options%2C%20modern%20minimalist%20design%20with%20purple%20and%20blue%20accents&width=400&height=240&seq=template_2&orientation=landscape'
37
+ },
38
+ {
39
+ id: 3,
40
+ name: '邮件智能管理系统',
41
+ description: '自动分类邮件、生成智能回复并跟踪邮件状态',
42
+ category: '任务自动化',
43
+ difficulty: '中级',
44
+ estimatedTime: '8-12分钟',
45
+ usageCount: 1456,
46
+ rating: 4.7,
47
+ tags: ['邮件自动化', 'NLP处理', '智能回复', '状态跟踪'],
48
+ icon: '📧',
49
+ features: ['智能分类', '自动回复', '状态跟踪', '优先级管理'],
50
+ preview: 'https://readdy.ai/api/search-image?query=email%20management%20dashboard%20with%20organized%20inbox%2C%20smart%20filtering%20and%20automated%20responses%2C%20clean%20modern%20interface%20design&width=400&height=240&seq=template_3&orientation=landscape'
51
+ },
52
+ {
53
+ id: 4,
54
+ name: '电商订单自动化',
55
+ description: '从订单接收到发货的完整自动化处理流程',
56
+ category: '系统集成',
57
+ difficulty: '高级',
58
+ estimatedTime: '15-20分钟',
59
+ usageCount: 1234,
60
+ rating: 4.8,
61
+ tags: ['订单处理', 'ERP集成', '库存管理', '物流跟踪'],
62
+ icon: '📦',
63
+ features: ['订单自动处理', '库存同步', '物流跟踪', '异常处理'],
64
+ preview: 'https://readdy.ai/api/search-image?query=e-commerce%20order%20processing%20dashboard%20with%20inventory%20management%20and%20shipping%20tracking%2C%20professional%20blue%20and%20orange%20design&width=400&height=240&seq=template_4&orientation=landscape'
65
+ },
66
+ {
67
+ id: 5,
68
+ name: '社交媒体监控',
69
+ description: '监控品牌提及、分析情感倾向并生成营销洞察',
70
+ category: '数据处理',
71
+ difficulty: '中级',
72
+ estimatedTime: '12-18分钟',
73
+ usageCount: 987,
74
+ rating: 4.6,
75
+ tags: ['社媒监控', '情感分析', '品牌监测', '营销洞察'],
76
+ icon: '📱',
77
+ features: ['实时监控', '情感分析', '趋势预测', '竞品分析'],
78
+ preview: 'https://readdy.ai/api/search-image?query=social%20media%20monitoring%20dashboard%20with%20sentiment%20analysis%20charts%20and%20brand%20mention%20tracking%2C%20modern%20colorful%20interface&width=400&height=240&seq=template_5&orientation=landscape'
79
+ },
80
+ {
81
+ id: 6,
82
+ name: '财务报表自动化',
83
+ description: '自动收集财务数据,生成专业的财务报表和分析',
84
+ category: '数据处理',
85
+ difficulty: '高级',
86
+ estimatedTime: '20-25分钟',
87
+ usageCount: 756,
88
+ rating: 4.9,
89
+ tags: ['财务分析', '报表生成', '数据可视化', 'BI集成'],
90
+ icon: '💰',
91
+ features: ['多源数据整合', '自动报表', '财务分析', '预算跟踪'],
92
+ preview: 'https://readdy.ai/api/search-image?query=financial%20reporting%20dashboard%20with%20charts%20graphs%20and%20financial%20analytics%2C%20professional%20green%20and%20blue%20business%20interface&width=400&height=240&seq=template_6&orientation=landscape'
93
+ },
94
+ {
95
+ id: 7,
96
+ name: '客户服务自动化',
97
+ description: '智能客服机器人,自动回复客户询问并转接人工',
98
+ category: 'AI工作流',
99
+ difficulty: '中级',
100
+ estimatedTime: '10-15分钟',
101
+ usageCount: 1678,
102
+ rating: 4.7,
103
+ tags: ['智能客服', '自动回复', '工单管理', '人工转接'],
104
+ icon: '🤖',
105
+ features: ['智能对话', '多渠道支持', '工单管理', '知识库'],
106
+ preview: 'https://readdy.ai/api/search-image?query=customer%20service%20chatbot%20interface%20with%20conversation%20flow%20and%20automated%20responses%2C%20friendly%20blue%20and%20white%20design&width=400&height=240&seq=template_7&orientation=landscape'
107
+ },
108
+ {
109
+ id: 8,
110
+ name: '数据备份与同步',
111
+ description: '定期备份重要数据并同步到多个云存储平台',
112
+ category: '系统集成',
113
+ difficulty: '简单',
114
+ estimatedTime: '5-8分钟',
115
+ usageCount: 1345,
116
+ rating: 4.8,
117
+ tags: ['数据备份', '云同步', '定时任务', '安全存储'],
118
+ icon: '☁️',
119
+ features: ['定时备份', '多云同步', '数据加密', '恢复管理'],
120
+ preview: 'https://readdy.ai/api/search-image?query=cloud%20backup%20and%20sync%20dashboard%20with%20progress%20indicators%20and%20storage%20management%2C%20clean%20modern%20tech%20interface&width=400&height=240&seq=template_8&orientation=landscape'
121
+ },
122
+ {
123
+ id: 9,
124
+ name: '项目进度跟踪',
125
+ description: '自动跟踪项目进度,生成进度报告并发送提醒',
126
+ category: '任务自动化',
127
+ difficulty: '中级',
128
+ estimatedTime: '12-15分钟',
129
+ usageCount: 892,
130
+ rating: 4.6,
131
+ tags: ['项目管理', '进度跟踪', '报告生成', '提醒通知'],
132
+ icon: '📋',
133
+ features: ['进度监控', '里程碑跟踪', '团队协作', '风险预警'],
134
+ preview: 'https://readdy.ai/api/search-image?query=project%20management%20dashboard%20with%20progress%20tracking%20and%20milestone%20indicators%2C%20professional%20purple%20and%20blue%20interface&width=400&height=240&seq=template_9&orientation=landscape'
135
+ }
136
+ ];
137
+
138
+ const getDifficultyColor = (difficulty: string) => {
139
+ switch (difficulty) {
140
+ case '简单': return 'text-green-600 bg-green-100';
141
+ case '中级': return 'text-yellow-600 bg-yellow-100';
142
+ case '高级': return 'text-red-600 bg-red-100';
143
+ default: return 'text-gray-600 bg-gray-100';
144
+ }
145
+ };
146
+
147
+ const handleUseTemplate = (templateId: number) => {
148
+ console.log('使用模板:', templateId);
149
+ // 这里可以跳转到创建页面并预填充模板数据
150
+ };
151
+
152
+ return (
153
+ <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
154
+ {templates.map((template) => (
155
+ <div
156
+ key={template.id}
157
+ className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden hover:shadow-lg transition-shadow cursor-pointer"
158
+ onClick={() => setSelectedTemplate(selectedTemplate === template.id ? null : template.id)}
159
+ >
160
+ {/* 模板预览图 */}
161
+ <div className="relative h-48 bg-gray-100">
162
+ <img
163
+ src={template.preview}
164
+ alt={template.name}
165
+ className="w-full h-full object-cover object-top"
166
+ />
167
+ <div className="absolute top-3 right-3 bg-white rounded-full p-2 shadow-md">
168
+ <span className="text-xl">{template.icon}</span>
169
+ </div>
170
+ <div className="absolute bottom-3 left-3 flex items-center space-x-2">
171
+ <span className={`px-2 py-1 text-xs rounded-full ${getDifficultyColor(template.difficulty)}`}>
172
+ {template.difficulty}
173
+ </span>
174
+ <span className="px-2 py-1 text-xs bg-gray-900 text-white rounded-full">
175
+ {template.estimatedTime}
176
+ </span>
177
+ </div>
178
+ </div>
179
+
180
+ {/* 模板信息 */}
181
+ <div className="p-6">
182
+ <div className="flex items-start justify-between mb-3">
183
+ <div>
184
+ <h3 className="text-lg font-semibold text-gray-900 mb-1">{template.name}</h3>
185
+ <span className="text-sm text-blue-600 font-medium">{template.category}</span>
186
+ </div>
187
+ <div className="flex items-center space-x-1">
188
+ <div className="w-4 h-4 flex items-center justify-center">
189
+ <i className="ri-star-fill text-yellow-400"></i>
190
+ </div>
191
+ <span className="text-sm font-medium text-gray-700">{template.rating}</span>
192
+ </div>
193
+ </div>
194
+
195
+ <p className="text-gray-600 text-sm mb-4 leading-relaxed">
196
+ {template.description}
197
+ </p>
198
+
199
+ {/* 使用统计 */}
200
+ <div className="flex items-center text-sm text-gray-500 mb-4">
201
+ <div className="w-4 h-4 flex items-center justify-center mr-1">
202
+ <i className="ri-user-line"></i>
203
+ </div>
204
+ <span>{template.usageCount.toLocaleString()} 次使用</span>
205
+ </div>
206
+
207
+ {/* 标签 */}
208
+ <div className="flex flex-wrap gap-1 mb-4">
209
+ {template.tags.slice(0, 3).map((tag, index) => (
210
+ <span
211
+ key={index}
212
+ className="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded-full"
213
+ >
214
+ {tag}
215
+ </span>
216
+ ))}
217
+ {template.tags.length > 3 && (
218
+ <span className="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded-full">
219
+ +{template.tags.length - 3}
220
+ </span>
221
+ )}
222
+ </div>
223
+
224
+ {/* 展开的详细信息 */}
225
+ {selectedTemplate === template.id && (
226
+ <div className="border-t border-gray-200 pt-4 mt-4">
227
+ <div className="mb-4">
228
+ <h4 className="text-sm font-medium text-gray-900 mb-2">主要功能</h4>
229
+ <ul className="space-y-1">
230
+ {template.features.map((feature, index) => (
231
+ <li key={index} className="text-sm text-gray-600 flex items-center">
232
+ <div className="w-4 h-4 flex items-center justify-center mr-2">
233
+ <i className="ri-check-line text-green-500"></i>
234
+ </div>
235
+ {feature}
236
+ </li>
237
+ ))}
238
+ </ul>
239
+ </div>
240
+
241
+ <div className="mb-4">
242
+ <h4 className="text-sm font-medium text-gray-900 mb-2">所有标签</h4>
243
+ <div className="flex flex-wrap gap-1">
244
+ {template.tags.map((tag, index) => (
245
+ <span
246
+ key={index}
247
+ className="px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-full"
248
+ >
249
+ {tag}
250
+ </span>
251
+ ))}
252
+ </div>
253
+ </div>
254
+ </div>
255
+ )}
256
+
257
+ {/* 操作按钮 */}
258
+ <div className="flex items-center space-x-3">
259
+ <button
260
+ onClick={(e) => {
261
+ e.stopPropagation();
262
+ handleUseTemplate(template.id);
263
+ }}
264
+ className="flex-1 bg-blue-600 text-white py-2 px-4 rounded-lg font-medium hover:bg-blue-700 transition-colors whitespace-nowrap"
265
+ >
266
+ 使用模板
267
+ </button>
268
+ <button
269
+ onClick={(e) => e.stopPropagation()}
270
+ className="p-2 text-gray-400 hover:text-gray-600 transition-colors"
271
+ >
272
+ <div className="w-5 h-5 flex items-center justify-center">
273
+ <i className="ri-eye-line"></i>
274
+ </div>
275
+ </button>
276
+ <button
277
+ onClick={(e) => e.stopPropagation()}
278
+ className="p-2 text-gray-400 hover:text-red-600 transition-colors"
279
+ >
280
+ <div className="w-5 h-5 flex items-center justify-center">
281
+ <i className="ri-heart-line"></i>
282
+ </div>
283
+ </button>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ ))}
288
+ </div>
289
+ );
290
+ }
app/workflows/templates/page.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import Header from '@/components/Header';
4
+ import Sidebar from '@/components/Sidebar';
5
+ import TemplateLibrary from './TemplateLibrary';
6
+ import TemplateFilters from './TemplateFilters';
7
+ import Link from 'next/link';
8
+
9
+ export default function WorkflowTemplatesPage() {
10
+ return (
11
+ <div className="min-h-screen bg-gray-50">
12
+ <Header />
13
+ <div className="flex">
14
+ <Sidebar />
15
+ <main className="flex-1 p-8">
16
+ <div className="max-w-7xl mx-auto">
17
+ <div className="flex items-center justify-between mb-8">
18
+ <div>
19
+ <div className="flex items-center space-x-3 mb-2">
20
+ <Link
21
+ href="/workflows"
22
+ className="text-gray-500 hover:text-gray-700 transition-colors cursor-pointer"
23
+ >
24
+ <div className="w-5 h-5 flex items-center justify-center">
25
+ <i className="ri-arrow-left-line"></i>
26
+ </div>
27
+ </Link>
28
+ <h1 className="text-3xl font-bold text-gray-900">工作流模板库</h1>
29
+ </div>
30
+ <p className="text-gray-600">选择预设模板快速创建工作流</p>
31
+ </div>
32
+
33
+ <div className="flex items-center space-x-4">
34
+ <Link
35
+ href="/workflows/create"
36
+ className="bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap"
37
+ >
38
+ <div className="w-5 h-5 flex items-center justify-center inline-block mr-2">
39
+ <i className="ri-add-line"></i>
40
+ </div>
41
+ 自定义创建
42
+ </Link>
43
+ </div>
44
+ </div>
45
+
46
+ <TemplateFilters />
47
+ <TemplateLibrary />
48
+ </div>
49
+ </main>
50
+ </div>
51
+ </div>
52
+ );
53
+ }
components/Header.tsx ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import { useState } from 'react';
5
+
6
+ export default function Header() {
7
+ const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
8
+
9
+ return (
10
+ <header className="bg-white border-b border-gray-200 sticky top-0 z-50">
11
+ <div className="flex items-center justify-between px-6 py-4">
12
+ <div className="flex items-center space-x-8">
13
+ <Link href="/" className="text-2xl font-bold text-blue-600" style={{ fontFamily: 'Pacifico, serif' }}>
14
+ AI Agent Studio
15
+ </Link>
16
+ <nav className="hidden md:flex items-center space-x-6">
17
+ <Link href="/dashboard" className="text-gray-700 hover:text-blue-600 transition-colors">
18
+ 工作台
19
+ </Link>
20
+ <Link href="/agents" className="text-gray-700 hover:text-blue-600 transition-colors">
21
+ Agent管理
22
+ </Link>
23
+ <Link href="/workflows" className="text-gray-700 hover:text-blue-600 transition-colors">
24
+ 工作流
25
+ </Link>
26
+ <Link href="/knowledge" className="text-gray-700 hover:text-blue-600 transition-colors">
27
+ 知识库
28
+ </Link>
29
+ <Link href="/templates" className="text-gray-700 hover:text-blue-600 transition-colors">
30
+ 模板市场
31
+ </Link>
32
+ </nav>
33
+ </div>
34
+
35
+ <div className="flex items-center space-x-4">
36
+ <div className="relative">
37
+ <div className="w-8 h-8 flex items-center justify-center">
38
+ <i className="ri-notification-line text-gray-600 text-xl"></i>
39
+ </div>
40
+ <span className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full"></span>
41
+ </div>
42
+
43
+ <div className="relative">
44
+ <button
45
+ onClick={() => setIsUserMenuOpen(!isUserMenuOpen)}
46
+ className="flex items-center space-x-2 p-2 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer"
47
+ >
48
+ <div className="w-8 h-8 bg-blue-600 rounded-full flex items-center justify-center">
49
+ <span className="text-white text-sm font-medium">张</span>
50
+ </div>
51
+ <div className="w-4 h-4 flex items-center justify-center">
52
+ <i className="ri-arrow-down-s-line text-gray-600"></i>
53
+ </div>
54
+ </button>
55
+
56
+ {isUserMenuOpen && (
57
+ <div className="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border border-gray-200 py-2">
58
+ <div className="px-4 py-2 border-b border-gray-100">
59
+ <p className="text-sm font-medium text-gray-900">张三</p>
60
+ <p className="text-xs text-gray-500">zhang@example.com</p>
61
+ </div>
62
+ <Link href="/profile" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
63
+ 个人设置
64
+ </Link>
65
+ <Link href="/billing" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
66
+ 账单管理
67
+ </Link>
68
+ <div className="border-t border-gray-100 mt-2">
69
+ <button className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
70
+ 退出登录
71
+ </button>
72
+ </div>
73
+ </div>
74
+ )}
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </header>
79
+ );
80
+ }
components/Sidebar.tsx ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ 'use client';
3
+
4
+ import { useState } from 'react';
5
+ import Link from 'next/link';
6
+ import { usePathname } from 'next/navigation';
7
+
8
+ export default function Sidebar() {
9
+ const pathname = usePathname();
10
+ const [isCollapsed, setIsCollapsed] = useState(false);
11
+
12
+ const menuItems = [
13
+ { name: '工作台', href: '/dashboard', icon: 'ri-dashboard-line' },
14
+ { name: 'Agent管理', href: '/agents', icon: 'ri-robot-line' },
15
+ { name: '工作流', href: '/workflows', icon: 'ri-flow-chart' },
16
+ { name: '知识库', href: '/knowledge', icon: 'ri-book-line' },
17
+ { name: '模板市场', href: '/templates', icon: 'ri-store-line' },
18
+ { name: '数据分析', href: '/analytics', icon: 'ri-bar-chart-line' },
19
+ { name: '设置', href: '/settings', icon: 'ri-settings-line' }
20
+ ];
21
+
22
+ return (
23
+ <div className={`bg-white border-r border-gray-200 flex-shrink-0 transition-all duration-300 ${
24
+ isCollapsed ? 'w-16' : 'w-64'
25
+ }`}>
26
+ <div className="p-6">
27
+ <h2 className="text-lg font-semibold text-gray-900 mb-6">导航菜单</h2>
28
+ <nav className="space-y-2">
29
+ {menuItems.map((item) => (
30
+ <Link
31
+ key={item.href}
32
+ href={item.href}
33
+ className={`flex items-center space-x-3 px-4 py-3 rounded-lg transition-colors cursor-pointer ${
34
+ pathname === item.href
35
+ ? 'bg-blue-50 text-blue-600 border-l-4 border-blue-600'
36
+ : 'text-gray-700 hover:bg-gray-50'
37
+ }`}
38
+ >
39
+ <div className="w-5 h-5 flex items-center justify-center">
40
+ <i className={`${item.icon} text-lg`}></i>
41
+ </div>
42
+ <span className="font-medium">{item.name}</span>
43
+ </Link>
44
+ ))}
45
+ </nav>
46
+ </div>
47
+ </div>
48
+ );
49
+ }
eslint.config.mjs ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { dirname } from "path";
2
+ import { fileURLToPath } from "url";
3
+ import { FlatCompat } from "@eslint/eslintrc";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
8
+ const compat = new FlatCompat({
9
+ baseDirectory: __dirname,
10
+ });
11
+
12
+ const eslintConfig = [
13
+ ...compat.extends("next/core-web-vitals", "next/typescript"),
14
+ {
15
+ rules: {
16
+ "@typescript-eslint/no-explicit-any": "off"
17
+ }
18
+ }
19
+ ];
20
+
21
+ export default eslintConfig;
index.html DELETED
@@ -1,20 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>My app</title>
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <meta charset="utf-8">
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- </head>
9
- <body class="flex justify-center items-center h-screen overflow-hidden bg-white font-sans text-center px-6">
10
- <div class="w-full">
11
- <span class="text-xs rounded-full mb-2 inline-block px-2 py-1 border border-amber-500/15 bg-amber-500/15 text-amber-500">🔥 New version dropped!</span>
12
- <h1 class="text-4xl lg:text-6xl font-bold font-sans">
13
- <span class="text-2xl lg:text-4xl text-gray-400 block font-medium">I'm ready to work,</span>
14
- Ask me anything.
15
- </h1>
16
- </div>
17
- <img src="https://enzostvs-deepsite.hf.space/arrow.svg" class="absolute bottom-8 left-0 w-[100px] transform rotate-[30deg]" />
18
- <script></script>
19
- <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=daios007/test1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
20
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
next.config.ts ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ output: "export",
5
+ images: {
6
+ unoptimized: true,
7
+ },
8
+ typescript: {
9
+ // ignoreBuildErrors: true,
10
+ },
11
+ };
12
+
13
+ export default nextConfig;
package.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "next-app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "cross-env NODE_ENV=development next dev -H 0.0.0.0 -p 3000",
7
+ "build": "next build",
8
+ "lint": "next lint"
9
+ },
10
+ "dependencies": {
11
+ "@react-google-maps/api": "^2.19.3",
12
+ "next": "15.3.2",
13
+ "react": "^19.0.0",
14
+ "react-dom": "^19.0.0",
15
+ "recharts": "^3.0.2"
16
+ },
17
+ "devDependencies": {
18
+ "@tailwindcss/postcss": "^4",
19
+ "@types/node": "^20",
20
+ "@types/react": "^19",
21
+ "@types/react-dom": "^19",
22
+ "autoprefixer": "^10.4.21",
23
+ "cross-env": "^7.0.3",
24
+ "postcss": "^8.5.5",
25
+ "tailwindcss": "^3.4.17",
26
+ "typescript": "^5"
27
+ }
28
+ }
postcss.config.mjs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ const config = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ }
6
+ }
7
+
8
+ export default config;
style.css DELETED
@@ -1,28 +0,0 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
- }
5
-
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
- }
10
-
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
- }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }