@vibe-forge/client 0.2.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/LICENSE +21 -0
  2. package/cli.cjs +6 -0
  3. package/index.html +27 -0
  4. package/package.json +42 -0
  5. package/src/App.tsx +174 -0
  6. package/src/api.ts +241 -0
  7. package/src/components/ArchiveView.scss +168 -0
  8. package/src/components/ArchiveView.tsx +299 -0
  9. package/src/components/AutomationView/AutomationView.scss +26 -0
  10. package/src/components/AutomationView/RuleFormPanel.scss +129 -0
  11. package/src/components/AutomationView/RuleFormPanel.tsx +257 -0
  12. package/src/components/AutomationView/RuleSidebar.scss +219 -0
  13. package/src/components/AutomationView/RuleSidebar.tsx +258 -0
  14. package/src/components/AutomationView/RunHistoryPanel.scss +286 -0
  15. package/src/components/AutomationView/RunHistoryPanel.tsx +320 -0
  16. package/src/components/AutomationView/TaskList.scss +128 -0
  17. package/src/components/AutomationView/TaskList.tsx +79 -0
  18. package/src/components/AutomationView/TriggerList.scss +153 -0
  19. package/src/components/AutomationView/TriggerList.tsx +217 -0
  20. package/src/components/AutomationView/index.tsx +228 -0
  21. package/src/components/AutomationView/types.ts +21 -0
  22. package/src/components/Chat.scss +89 -0
  23. package/src/components/Chat.tsx +92 -0
  24. package/src/components/ConfigView.scss +185 -0
  25. package/src/components/ConfigView.tsx +258 -0
  26. package/src/components/NavRail.scss +71 -0
  27. package/src/components/NavRail.tsx +188 -0
  28. package/src/components/Sidebar.scss +112 -0
  29. package/src/components/Sidebar.tsx +291 -0
  30. package/src/components/chat/ChatHeader.scss +401 -0
  31. package/src/components/chat/ChatHeader.tsx +342 -0
  32. package/src/components/chat/ChatHistoryView.tsx +122 -0
  33. package/src/components/chat/ChatSettingsView.tsx +22 -0
  34. package/src/components/chat/ChatTimelineView.scss +53 -0
  35. package/src/components/chat/ChatTimelineView.tsx +158 -0
  36. package/src/components/chat/CodeBlock.scss +87 -0
  37. package/src/components/chat/CodeBlock.tsx +179 -0
  38. package/src/components/chat/CompletionMenu.scss +70 -0
  39. package/src/components/chat/CompletionMenu.tsx +58 -0
  40. package/src/components/chat/CurrentTodoList.scss +217 -0
  41. package/src/components/chat/CurrentTodoList.tsx +103 -0
  42. package/src/components/chat/MarkdownContent.tsx +43 -0
  43. package/src/components/chat/MessageFooter.tsx +48 -0
  44. package/src/components/chat/MessageItem.scss +251 -0
  45. package/src/components/chat/MessageItem.tsx +78 -0
  46. package/src/components/chat/NewSessionGuide.scss +186 -0
  47. package/src/components/chat/NewSessionGuide.tsx +167 -0
  48. package/src/components/chat/Sender.scss +367 -0
  49. package/src/components/chat/Sender.tsx +541 -0
  50. package/src/components/chat/SessionTimelinePanel/EventList.scss +58 -0
  51. package/src/components/chat/SessionTimelinePanel/EventList.tsx +212 -0
  52. package/src/components/chat/SessionTimelinePanel/gantt.ts +177 -0
  53. package/src/components/chat/SessionTimelinePanel/git-graph.ts +518 -0
  54. package/src/components/chat/SessionTimelinePanel/index.scss +28 -0
  55. package/src/components/chat/SessionTimelinePanel/index.tsx +121 -0
  56. package/src/components/chat/SessionTimelinePanel/mermaid.ts +4 -0
  57. package/src/components/chat/SessionTimelinePanel/types.ts +64 -0
  58. package/src/components/chat/SessionTimelinePanel/utils.ts +20 -0
  59. package/src/components/chat/ThinkingStatus.scss +70 -0
  60. package/src/components/chat/ThinkingStatus.tsx +13 -0
  61. package/src/components/chat/ToolCallBox.scss +137 -0
  62. package/src/components/chat/ToolCallBox.tsx +55 -0
  63. package/src/components/chat/ToolGroup.scss +154 -0
  64. package/src/components/chat/ToolGroup.tsx +102 -0
  65. package/src/components/chat/ToolRenderer.tsx +45 -0
  66. package/src/components/chat/messageUtils.ts +171 -0
  67. package/src/components/chat/safeSerialize.ts +84 -0
  68. package/src/components/chat/tools/DefaultTool.tsx +63 -0
  69. package/src/components/chat/tools/adapter-claude/BashTool.scss +71 -0
  70. package/src/components/chat/tools/adapter-claude/BashTool.tsx +82 -0
  71. package/src/components/chat/tools/adapter-claude/GlobTool.scss +88 -0
  72. package/src/components/chat/tools/adapter-claude/GlobTool.tsx +85 -0
  73. package/src/components/chat/tools/adapter-claude/GrepTool.scss +96 -0
  74. package/src/components/chat/tools/adapter-claude/GrepTool.tsx +114 -0
  75. package/src/components/chat/tools/adapter-claude/LSTool.scss +85 -0
  76. package/src/components/chat/tools/adapter-claude/LSTool.tsx +94 -0
  77. package/src/components/chat/tools/adapter-claude/ReadTool.scss +57 -0
  78. package/src/components/chat/tools/adapter-claude/ReadTool.tsx +87 -0
  79. package/src/components/chat/tools/adapter-claude/TodoTool.scss +78 -0
  80. package/src/components/chat/tools/adapter-claude/TodoTool.tsx +60 -0
  81. package/src/components/chat/tools/adapter-claude/WriteTool.scss +92 -0
  82. package/src/components/chat/tools/adapter-claude/WriteTool.tsx +86 -0
  83. package/src/components/chat/tools/adapter-claude/components/FileList.scss +65 -0
  84. package/src/components/chat/tools/adapter-claude/components/FileList.tsx +185 -0
  85. package/src/components/chat/tools/adapter-claude/index.ts +28 -0
  86. package/src/components/chat/tools/defineToolRender.ts +28 -0
  87. package/src/components/chat/tools/task/GetTaskInfoTool.scss +50 -0
  88. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +88 -0
  89. package/src/components/chat/tools/task/ListTasksTool.scss +56 -0
  90. package/src/components/chat/tools/task/ListTasksTool.tsx +83 -0
  91. package/src/components/chat/tools/task/StartTasksTool.scss +56 -0
  92. package/src/components/chat/tools/task/StartTasksTool.tsx +96 -0
  93. package/src/components/chat/tools/task/components/TaskToolCard.scss +127 -0
  94. package/src/components/chat/tools/task/components/TaskToolCard.tsx +177 -0
  95. package/src/components/chat/tools/task/index.ts +15 -0
  96. package/src/components/chat/useChatModels.tsx +206 -0
  97. package/src/components/chat/useChatSession.ts +370 -0
  98. package/src/components/config/ConfigAboutSection.scss +111 -0
  99. package/src/components/config/ConfigAboutSection.tsx +86 -0
  100. package/src/components/config/ConfigDisplayValue.scss +22 -0
  101. package/src/components/config/ConfigDisplayValue.tsx +62 -0
  102. package/src/components/config/ConfigEditors.scss +65 -0
  103. package/src/components/config/ConfigEditors.tsx +98 -0
  104. package/src/components/config/ConfigFieldRow.scss +97 -0
  105. package/src/components/config/ConfigFieldRow.tsx +36 -0
  106. package/src/components/config/ConfigSectionForm.scss +94 -0
  107. package/src/components/config/ConfigSectionForm.tsx +436 -0
  108. package/src/components/config/ConfigSectionPanel.tsx +67 -0
  109. package/src/components/config/ConfigShortcutInput.scss +11 -0
  110. package/src/components/config/ConfigShortcutInput.tsx +52 -0
  111. package/src/components/config/ConfigSourceSwitch.tsx +57 -0
  112. package/src/components/config/configSchema.ts +319 -0
  113. package/src/components/config/configUtils.ts +83 -0
  114. package/src/components/config/index.tsx +5 -0
  115. package/src/components/config/recordEditors/BooleanRecordEditor.scss +1 -0
  116. package/src/components/config/recordEditors/BooleanRecordEditor.tsx +75 -0
  117. package/src/components/config/recordEditors/KeyValueEditor.scss +1 -0
  118. package/src/components/config/recordEditors/KeyValueEditor.tsx +97 -0
  119. package/src/components/config/recordEditors/McpServersRecordEditor.scss +1 -0
  120. package/src/components/config/recordEditors/McpServersRecordEditor.tsx +258 -0
  121. package/src/components/config/recordEditors/ModelServicesRecordEditor.scss +1 -0
  122. package/src/components/config/recordEditors/ModelServicesRecordEditor.tsx +233 -0
  123. package/src/components/config/recordEditors/RecordEditors.scss +117 -0
  124. package/src/components/config/recordEditors/RecordJsonEditor.scss +1 -0
  125. package/src/components/config/recordEditors/RecordJsonEditor.tsx +113 -0
  126. package/src/components/config/recordEditors/index.tsx +5 -0
  127. package/src/components/knowledge-base/KnowledgeBaseView.scss +19 -0
  128. package/src/components/knowledge-base/KnowledgeBaseView.tsx +186 -0
  129. package/src/components/knowledge-base/components/ActionButton.scss +5 -0
  130. package/src/components/knowledge-base/components/ActionButton.tsx +9 -0
  131. package/src/components/knowledge-base/components/EmptyState.scss +19 -0
  132. package/src/components/knowledge-base/components/EmptyState.tsx +42 -0
  133. package/src/components/knowledge-base/components/EntitiesTab.scss +5 -0
  134. package/src/components/knowledge-base/components/EntitiesTab.tsx +80 -0
  135. package/src/components/knowledge-base/components/EntityItem.scss +82 -0
  136. package/src/components/knowledge-base/components/EntityItem.tsx +79 -0
  137. package/src/components/knowledge-base/components/EntityList.scss +5 -0
  138. package/src/components/knowledge-base/components/EntityList.tsx +70 -0
  139. package/src/components/knowledge-base/components/FilterBar.scss +21 -0
  140. package/src/components/knowledge-base/components/FilterBar.tsx +51 -0
  141. package/src/components/knowledge-base/components/FlowsTab.scss +5 -0
  142. package/src/components/knowledge-base/components/FlowsTab.tsx +80 -0
  143. package/src/components/knowledge-base/components/KnowledgeBaseHeader.scss +27 -0
  144. package/src/components/knowledge-base/components/KnowledgeBaseHeader.tsx +29 -0
  145. package/src/components/knowledge-base/components/KnowledgeList.scss +19 -0
  146. package/src/components/knowledge-base/components/KnowledgeList.tsx +19 -0
  147. package/src/components/knowledge-base/components/LoadingState.scss +5 -0
  148. package/src/components/knowledge-base/components/LoadingState.tsx +11 -0
  149. package/src/components/knowledge-base/components/MetaList.scss +19 -0
  150. package/src/components/knowledge-base/components/MetaList.tsx +18 -0
  151. package/src/components/knowledge-base/components/RulesTab.scss +5 -0
  152. package/src/components/knowledge-base/components/RulesTab.tsx +49 -0
  153. package/src/components/knowledge-base/components/SectionHeader.scss +22 -0
  154. package/src/components/knowledge-base/components/SectionHeader.tsx +21 -0
  155. package/src/components/knowledge-base/components/SkillsTab.scss +5 -0
  156. package/src/components/knowledge-base/components/SkillsTab.tsx +49 -0
  157. package/src/components/knowledge-base/components/SpecItem.scss +138 -0
  158. package/src/components/knowledge-base/components/SpecItem.tsx +131 -0
  159. package/src/components/knowledge-base/components/SpecList.scss +5 -0
  160. package/src/components/knowledge-base/components/SpecList.tsx +70 -0
  161. package/src/components/knowledge-base/components/TabContent.scss +8 -0
  162. package/src/components/knowledge-base/components/TabContent.tsx +17 -0
  163. package/src/components/knowledge-base/components/TabLabel.scss +10 -0
  164. package/src/components/knowledge-base/components/TabLabel.tsx +15 -0
  165. package/src/components/knowledge-base/index.tsx +1 -0
  166. package/src/components/sidebar/SessionItem.scss +256 -0
  167. package/src/components/sidebar/SessionItem.tsx +265 -0
  168. package/src/components/sidebar/SessionList.scss +92 -0
  169. package/src/components/sidebar/SessionList.tsx +166 -0
  170. package/src/components/sidebar/SidebarHeader.scss +79 -0
  171. package/src/components/sidebar/SidebarHeader.tsx +128 -0
  172. package/src/connectionManager.ts +172 -0
  173. package/src/hooks/useGlobalShortcut.ts +26 -0
  174. package/src/hooks/useQueryParams.ts +54 -0
  175. package/src/i18n.ts +22 -0
  176. package/src/main.tsx +41 -0
  177. package/src/resources/locales/en.json +765 -0
  178. package/src/resources/locales/zh.json +766 -0
  179. package/src/store/index.ts +23 -0
  180. package/src/styles/global.scss +100 -0
  181. package/src/utils/shortcutUtils.ts +88 -0
  182. package/src/vite-env.d.ts +12 -0
  183. package/src/ws.ts +33 -0
  184. package/vite.config.ts +26 -0
@@ -0,0 +1,320 @@
1
+ import './RunHistoryPanel.scss'
2
+
3
+ import { Button, Empty, Input, Select, Table } from 'antd'
4
+ import type { ColumnsType } from 'antd/es/table'
5
+ import dayjs from 'dayjs'
6
+ import React, { useMemo } from 'react'
7
+ import { useTranslation } from 'react-i18next'
8
+
9
+ import type { AutomationRule, AutomationRun } from '#~/api.js'
10
+
11
+ interface RunHistoryPanelProps {
12
+ rule: AutomationRule | null
13
+ runs: AutomationRun[]
14
+ runQuery: string
15
+ statusFilter: string
16
+ timeFilter: string
17
+ sortOrder: string
18
+ onEditRule: (rule: AutomationRule) => void
19
+ onRunQueryChange: (value: string) => void
20
+ onStatusFilterChange: (value: string) => void
21
+ onTimeFilterChange: (value: string) => void
22
+ onSortOrderChange: (value: string) => void
23
+ }
24
+
25
+ export function RunHistoryPanel({
26
+ rule,
27
+ runs,
28
+ runQuery,
29
+ statusFilter,
30
+ timeFilter,
31
+ sortOrder,
32
+ onEditRule,
33
+ onRunQueryChange,
34
+ onStatusFilterChange,
35
+ onTimeFilterChange,
36
+ onSortOrderChange
37
+ }: RunHistoryPanelProps) {
38
+ const { t } = useTranslation()
39
+
40
+ const filteredRuns = useMemo(() => {
41
+ const keyword = runQuery.trim().toLowerCase()
42
+ const now = Date.now()
43
+ const timeLimitMap: Record<string, number | null> = {
44
+ all: null,
45
+ '24h': 24 * 60 * 60 * 1000,
46
+ '7d': 7 * 24 * 60 * 60 * 1000,
47
+ '30d': 30 * 24 * 60 * 60 * 1000
48
+ }
49
+ const timeLimit = timeLimitMap[timeFilter] ?? null
50
+ const base = runs.filter((run) => {
51
+ if (statusFilter !== 'all' && (run.status ?? 'unknown') !== statusFilter) return false
52
+ if (timeLimit != null && now - run.runAt > timeLimit) return false
53
+ if (!keyword) return true
54
+ const text = [
55
+ run.taskTitle,
56
+ run.title,
57
+ run.lastMessage,
58
+ run.lastUserMessage,
59
+ run.sessionId
60
+ ].filter(Boolean).join(' ').toLowerCase()
61
+ return text.includes(keyword)
62
+ })
63
+ return base.sort((a, b) => {
64
+ if (sortOrder === 'asc') return a.runAt - b.runAt
65
+ return b.runAt - a.runAt
66
+ })
67
+ }, [runQuery, runs, sortOrder, statusFilter, timeFilter])
68
+
69
+ const columns: ColumnsType<AutomationRun> = useMemo(() => [
70
+ {
71
+ title: t('automation.runTime'),
72
+ dataIndex: 'runAt',
73
+ key: 'runAt',
74
+ render: (value: number) => dayjs(value).format('YYYY-MM-DD HH:mm')
75
+ },
76
+ {
77
+ title: t('automation.taskTitle'),
78
+ dataIndex: 'taskTitle',
79
+ key: 'taskTitle',
80
+ render: (_value: string | null | undefined, run) => (
81
+ <div className='automation-view__task-cell'>
82
+ <a className='automation-view__task-link' href={`/session/${run.sessionId}`}>
83
+ <span className='material-symbols-rounded automation-view__task-icon'>task_alt</span>
84
+ <span className='automation-view__task-text'>{run.taskTitle ?? t('automation.taskUnknown')}</span>
85
+ {run.status
86
+ ? (
87
+ <span className='automation-view__status' data-status={run.status}>
88
+ <span className='material-symbols-rounded automation-view__status-icon'>fiber_manual_record</span>
89
+ {t(`common.status.${run.status}`, run.status)}
90
+ </span>
91
+ )
92
+ : (
93
+ <span className='automation-view__status'>-</span>
94
+ )}
95
+ </a>
96
+ </div>
97
+ )
98
+ },
99
+ {
100
+ title: t('automation.runSummary'),
101
+ dataIndex: 'lastMessage',
102
+ key: 'summary',
103
+ render: (_value: string | undefined, run) => {
104
+ const text = run.title ?? run.lastMessage ?? run.lastUserMessage ?? '-'
105
+ return <span className='automation-view__run-summary'>{text}</span>
106
+ }
107
+ }
108
+ ], [t])
109
+
110
+ const recentRuns = useMemo(() => {
111
+ if (runs.length === 0) return []
112
+ const sorted = [...runs].sort((a, b) => b.runAt - a.runAt)
113
+ const latestSessionId = sorted[0]?.sessionId
114
+ if (!latestSessionId) return sorted.slice(0, 1)
115
+ return sorted.filter((run) => run.sessionId === latestSessionId)
116
+ }, [runs])
117
+
118
+ if (!rule) {
119
+ return (
120
+ <div className='automation-view__empty'>
121
+ <Empty description={t('automation.selectRule')} />
122
+ </div>
123
+ )
124
+ }
125
+
126
+ const triggerLabels = (rule.triggers ?? []).map((trigger) => {
127
+ if (trigger.type === 'interval') {
128
+ const minutes = trigger.intervalMs ? Math.max(1, Math.round(trigger.intervalMs / 60000)) : 1
129
+ return t('automation.intervalEvery', { minutes })
130
+ }
131
+ if (trigger.type === 'cron') {
132
+ const expression = trigger.cronExpression?.trim() || '-'
133
+ return t('automation.cronExpression', { expression })
134
+ }
135
+ return t('automation.webhookTrigger')
136
+ })
137
+ const taskLabels = (rule.tasks ?? []).map((task, index) => (
138
+ task.title || t('automation.taskDefaultTitle', { index: index + 1 })
139
+ ))
140
+
141
+ return (
142
+ <div className='automation-view__content'>
143
+ <div className='automation-view__detail'>
144
+ <div className='automation-view__detail-header'>
145
+ <div className='automation-view__detail-title'>
146
+ <h3 className='automation-view__content-text'>{rule.name}</h3>
147
+ <span className='automation-view__detail-id'>{rule.id}</span>
148
+ </div>
149
+ <Button
150
+ className='automation-view__icon-button automation-view__icon-button--edit'
151
+ type='text'
152
+ onClick={() => onEditRule(rule)}
153
+ >
154
+ <span className='material-symbols-rounded automation-view__action-icon'>edit</span>
155
+ </Button>
156
+ </div>
157
+ <div className='automation-view__detail-description'>
158
+ <span className='material-symbols-rounded automation-view__meta-icon'>description</span>
159
+ <span>{rule.description || t('automation.noDescription')}</span>
160
+ </div>
161
+ <div className='automation-view__detail-body'>
162
+ <div className='automation-view__detail-left'>
163
+ <div className='automation-view__detail-row'>
164
+ <span className='material-symbols-rounded automation-view__meta-icon'>event</span>
165
+ <span className='automation-view__detail-label'>{t('automation.createdAt')}</span>
166
+ <span className='automation-view__detail-value'>
167
+ {dayjs(rule.createdAt).format('YYYY-MM-DD HH:mm')}
168
+ </span>
169
+ </div>
170
+ <div className='automation-view__detail-row'>
171
+ <span className='material-symbols-rounded automation-view__meta-icon'>toggle_on</span>
172
+ <span className='automation-view__detail-label'>{t('automation.ruleStatus')}</span>
173
+ <span className='automation-view__detail-value' data-status={rule.enabled ? 'enabled' : 'disabled'}>
174
+ {rule.enabled ? t('automation.enabledOn') : t('automation.enabledOff')}
175
+ </span>
176
+ </div>
177
+ <div className='automation-view__detail-list'>
178
+ <div className='automation-view__detail-row'>
179
+ <span className='material-symbols-rounded automation-view__meta-icon'>update</span>
180
+ <span className='automation-view__detail-label'>{t('automation.lastRunLabel')}</span>
181
+ </div>
182
+ <div className='automation-view__run-list'>
183
+ {recentRuns.length > 0
184
+ ? recentRuns.map((run) => (
185
+ <div key={run.id} className='automation-view__run-item'>
186
+ <a
187
+ className='automation-view__run-link'
188
+ href={`/session/${run.sessionId}?tag=${
189
+ encodeURIComponent(`automation:${rule.id}:${rule.name}`)
190
+ }`}
191
+ target='_blank'
192
+ rel='noreferrer'
193
+ >
194
+ {run.taskTitle ?? t('automation.taskUnknown')}
195
+ </a>
196
+ <span className='automation-view__run-time'>
197
+ {dayjs(run.runAt).format('YYYY-MM-DD HH:mm')}
198
+ </span>
199
+ </div>
200
+ ))
201
+ : <span className='automation-view__detail-value'>{t('automation.noRunYet')}</span>}
202
+ </div>
203
+ </div>
204
+ {rule.lastSessionId && (
205
+ <div className='automation-view__detail-row'>
206
+ <span className='material-symbols-rounded automation-view__meta-icon'>open_in_new</span>
207
+ <a
208
+ className='automation-view__detail-label automation-view__detail-link'
209
+ href={`/session/${rule.lastSessionId}?tag=${
210
+ encodeURIComponent(`automation:${rule.id}:${rule.name}`)
211
+ }`}
212
+ target='_blank'
213
+ rel='noreferrer'
214
+ >
215
+ {t('automation.relatedSession')}
216
+ </a>
217
+ </div>
218
+ )}
219
+ </div>
220
+ <div className='automation-view__detail-right'>
221
+ <div className='automation-view__detail-section'>
222
+ <div className='automation-view__detail-section-title'>
223
+ <span className='material-symbols-rounded automation-view__meta-icon'>bolt</span>
224
+ {t('automation.sectionTriggers')}
225
+ </div>
226
+ <div className='automation-view__detail-chips'>
227
+ {triggerLabels.length
228
+ ? triggerLabels.map((label, index) => (
229
+ <span key={`${label}-${index}`} className='automation-view__detail-chip'>
230
+ {label}
231
+ </span>
232
+ ))
233
+ : <span className='automation-view__detail-placeholder'>{t('automation.noTriggers')}</span>}
234
+ </div>
235
+ </div>
236
+ <div className='automation-view__detail-section'>
237
+ <div className='automation-view__detail-section-title'>
238
+ <span className='material-symbols-rounded automation-view__meta-icon'>task</span>
239
+ {t('automation.sectionTasks')}
240
+ </div>
241
+ <div className='automation-view__detail-chips'>
242
+ {taskLabels.length
243
+ ? taskLabels.map((label, index) => (
244
+ <span key={`${label}-${index}`} className='automation-view__detail-chip'>
245
+ {label}
246
+ </span>
247
+ ))
248
+ : <span className='automation-view__detail-placeholder'>{t('automation.noTasks')}</span>}
249
+ </div>
250
+ </div>
251
+ </div>
252
+ </div>
253
+ </div>
254
+
255
+ <div className='automation-view__content-header'>
256
+ <div className='automation-view__content-title'>
257
+ <span className='material-symbols-rounded automation-view__content-icon'>history</span>
258
+ <h3 className='automation-view__content-text'>{t('automation.runHistory')}</h3>
259
+ </div>
260
+ </div>
261
+ <div className='automation-view__run-filters'>
262
+ <Input
263
+ value={runQuery}
264
+ onChange={(event) => onRunQueryChange(event.target.value)}
265
+ placeholder={t('automation.runSearch')}
266
+ className='automation-view__run-search'
267
+ allowClear
268
+ />
269
+ <Select
270
+ value={statusFilter}
271
+ onChange={onStatusFilterChange}
272
+ className='automation-view__run-select'
273
+ options={[
274
+ { label: t('automation.statusAll'), value: 'all' },
275
+ { label: t('common.status.running'), value: 'running' },
276
+ { label: t('common.status.completed'), value: 'completed' },
277
+ { label: t('common.status.failed'), value: 'failed' },
278
+ { label: t('common.status.terminated'), value: 'terminated' },
279
+ { label: t('common.status.waiting_input'), value: 'waiting_input' }
280
+ ]}
281
+ />
282
+ <Select
283
+ value={timeFilter}
284
+ onChange={onTimeFilterChange}
285
+ className='automation-view__run-select'
286
+ options={[
287
+ { label: t('automation.timeAll'), value: 'all' },
288
+ { label: t('automation.time24h'), value: '24h' },
289
+ { label: t('automation.time7d'), value: '7d' },
290
+ { label: t('automation.time30d'), value: '30d' }
291
+ ]}
292
+ />
293
+ <Select
294
+ value={sortOrder}
295
+ onChange={onSortOrderChange}
296
+ className='automation-view__run-select'
297
+ options={[
298
+ { label: t('automation.sortDesc'), value: 'desc' },
299
+ { label: t('automation.sortAsc'), value: 'asc' }
300
+ ]}
301
+ />
302
+ </div>
303
+ {filteredRuns.length === 0
304
+ ? (
305
+ <div className='automation-view__empty'>
306
+ <Empty description={t('automation.noRuns')} />
307
+ </div>
308
+ )
309
+ : (
310
+ <Table
311
+ rowKey='id'
312
+ className='automation-view__run-table'
313
+ columns={columns}
314
+ dataSource={filteredRuns}
315
+ pagination={{ pageSize: 8, hideOnSinglePage: true }}
316
+ />
317
+ )}
318
+ </div>
319
+ )
320
+ }
@@ -0,0 +1,128 @@
1
+ .automation-view {
2
+ &__list {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 12px;
6
+
7
+ &--horizontal {
8
+ gap: 10px;
9
+
10
+ .automation-view__list-item {
11
+ flex: 0 0 360px;
12
+ min-width: 360px;
13
+ }
14
+ }
15
+
16
+ &-scroll {
17
+ display: flex;
18
+ gap: 12px;
19
+ overflow-x: auto;
20
+ padding-bottom: 4px;
21
+
22
+ .automation-view__list-item {
23
+ min-width: 340px;
24
+ flex: 0 0 340px;
25
+ }
26
+ }
27
+ }
28
+
29
+ &__list-item {
30
+ position: relative;
31
+ border: 1px solid var(--border-color);
32
+ border-radius: 12px;
33
+ padding: 12px;
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: 12px;
37
+ background: var(--tag-bg);
38
+ }
39
+
40
+ &__list-header {
41
+ display: flex;
42
+ align-items: flex-end;
43
+ gap: 12px;
44
+ justify-content: space-between;
45
+ }
46
+
47
+ &__section-header {
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: space-between;
51
+ gap: 12px;
52
+ }
53
+
54
+ &__section-heading {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: 12px;
58
+ }
59
+
60
+ &__section-actions {
61
+ display: inline-flex;
62
+ align-items: center;
63
+ gap: 8px;
64
+ }
65
+
66
+ &__section-count {
67
+ font-size: 12px;
68
+ color: var(--sub-text-color);
69
+ }
70
+
71
+ &__form-title {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 8px;
75
+ font-weight: 600;
76
+ color: var(--text-color);
77
+ }
78
+
79
+ &__form-icon {
80
+ font-size: 18px;
81
+ color: var(--sub-text-color);
82
+ }
83
+
84
+ &__form-desc {
85
+ font-size: 12px;
86
+ color: var(--sub-text-color);
87
+ margin-bottom: 12px;
88
+ }
89
+
90
+ &__icon-button {
91
+ display: inline-flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ width: 32px;
95
+ height: 32px;
96
+ padding: 0;
97
+ }
98
+
99
+ &__action-icon {
100
+ font-size: 18px;
101
+ }
102
+
103
+ &__button-icon {
104
+ font-size: 16px;
105
+ margin-right: 4px;
106
+ }
107
+
108
+ &__remove-button {
109
+ position: absolute;
110
+ top: 8px;
111
+ right: 8px;
112
+ width: 32px;
113
+ height: 32px;
114
+ padding: 0;
115
+ }
116
+
117
+ &__task-index {
118
+ display: inline-flex;
119
+ align-items: center;
120
+ gap: 6px;
121
+ font-size: 12px;
122
+ color: var(--sub-text-color);
123
+ }
124
+
125
+ &__task-index-icon {
126
+ font-size: 14px;
127
+ }
128
+ }
@@ -0,0 +1,79 @@
1
+ import './TaskList.scss'
2
+
3
+ import { Button, Form, Input, Tooltip } from 'antd'
4
+ import React from 'react'
5
+ import { useTranslation } from 'react-i18next'
6
+
7
+ export function TaskList() {
8
+ const { t } = useTranslation()
9
+
10
+ return (
11
+ <Form.List name='tasks'>
12
+ {(fields, { add, remove }) => (
13
+ <div className='automation-view__list automation-view__list--horizontal'>
14
+ <div className='automation-view__section-header'>
15
+ <div className='automation-view__section-heading'>
16
+ <div className='automation-view__form-title'>
17
+ <span className='material-symbols-rounded automation-view__form-icon'>task</span>
18
+ {t('automation.sectionTasks')}
19
+ </div>
20
+ <span className='automation-view__section-count'>
21
+ {t('automation.taskCount', { count: fields.length })}
22
+ </span>
23
+ </div>
24
+ <div className='automation-view__section-actions'>
25
+ <Tooltip title={t('automation.addTask')}>
26
+ <Button
27
+ className='automation-view__icon-button'
28
+ type='text'
29
+ onClick={() => add({
30
+ title: t('automation.taskDefaultTitle', { index: fields.length + 1 }),
31
+ prompt: ''
32
+ })}
33
+ >
34
+ <span className='material-symbols-rounded automation-view__action-icon'>add</span>
35
+ </Button>
36
+ </Tooltip>
37
+ </div>
38
+ </div>
39
+ <div className='automation-view__form-desc'>{t('automation.taskAll')}</div>
40
+ <div className='automation-view__list-scroll'>
41
+ {fields.map((field, index) => (
42
+ <div key={field.key} className='automation-view__list-item'>
43
+ <Form.Item name={[field.name, 'id']} hidden>
44
+ <Input />
45
+ </Form.Item>
46
+ <Tooltip title={t('automation.remove')}>
47
+ <Button
48
+ className='automation-view__remove-button'
49
+ danger
50
+ onClick={() => remove(field.name)}
51
+ disabled={fields.length <= 1}
52
+ >
53
+ <span className='material-symbols-rounded automation-view__button-icon'>close</span>
54
+ </Button>
55
+ </Tooltip>
56
+ <div className='automation-view__list-header'>
57
+ <Form.Item name={[field.name, 'title']} label={t('automation.taskTitle')}>
58
+ <Input placeholder={t('automation.taskTitlePlaceholder')} />
59
+ </Form.Item>
60
+ </div>
61
+ <Form.Item
62
+ name={[field.name, 'prompt']}
63
+ label={t('automation.prompt')}
64
+ rules={[{ required: true, message: t('automation.promptRequired') }]}
65
+ >
66
+ <Input.TextArea rows={3} />
67
+ </Form.Item>
68
+ <div className='automation-view__task-index'>
69
+ <span className='material-symbols-rounded automation-view__task-index-icon'>format_list_numbered</span>
70
+ {t('automation.taskIndex', { index: index + 1 })}
71
+ </div>
72
+ </div>
73
+ ))}
74
+ </div>
75
+ </div>
76
+ )}
77
+ </Form.List>
78
+ )
79
+ }
@@ -0,0 +1,153 @@
1
+ .automation-view {
2
+ &__list {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 12px;
6
+
7
+ &--horizontal {
8
+ gap: 10px;
9
+
10
+ .automation-view__list-item {
11
+ flex: 0 0 360px;
12
+ min-width: 360px;
13
+ }
14
+ }
15
+
16
+ &-scroll {
17
+ display: flex;
18
+ gap: 12px;
19
+ overflow-x: auto;
20
+ padding-bottom: 4px;
21
+
22
+ .automation-view__list-item {
23
+ min-width: 340px;
24
+ flex: 0 0 340px;
25
+ }
26
+ }
27
+ }
28
+
29
+ &__list-item {
30
+ position: relative;
31
+ border: 1px solid var(--border-color);
32
+ border-radius: 12px;
33
+ padding: 12px;
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: 12px;
37
+ background: var(--tag-bg);
38
+ }
39
+
40
+ &__list-header {
41
+ display: flex;
42
+ align-items: flex-end;
43
+ gap: 12px;
44
+ justify-content: space-between;
45
+ }
46
+
47
+ &__section-header {
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: space-between;
51
+ gap: 12px;
52
+ }
53
+
54
+ &__section-heading {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: 12px;
58
+ }
59
+
60
+ &__section-actions {
61
+ display: inline-flex;
62
+ align-items: center;
63
+ gap: 8px;
64
+ }
65
+
66
+ &__section-count {
67
+ font-size: 12px;
68
+ color: var(--sub-text-color);
69
+ }
70
+
71
+ &__form-title {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 8px;
75
+ font-weight: 600;
76
+ color: var(--text-color);
77
+ }
78
+
79
+ &__form-icon {
80
+ font-size: 18px;
81
+ color: var(--sub-text-color);
82
+ }
83
+
84
+ &__form-desc {
85
+ font-size: 12px;
86
+ color: var(--sub-text-color);
87
+ margin-bottom: 12px;
88
+ }
89
+
90
+ &__icon-button {
91
+ display: inline-flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ width: 32px;
95
+ height: 32px;
96
+ padding: 0;
97
+ }
98
+
99
+ &__action-icon {
100
+ font-size: 18px;
101
+ }
102
+
103
+ &__remove-button {
104
+ position: absolute;
105
+ top: 8px;
106
+ right: 8px;
107
+ width: 32px;
108
+ height: 32px;
109
+ padding: 0;
110
+ }
111
+
112
+ &__input-number {
113
+ width: 100%;
114
+ }
115
+
116
+ &__cron-section {
117
+ display: flex;
118
+ flex-direction: column;
119
+ gap: 12px;
120
+ }
121
+
122
+ &__cron-hint {
123
+ color: var(--sub-text-color);
124
+ font-size: 12px;
125
+ }
126
+
127
+ &__cron-weekly {
128
+ display: grid;
129
+ grid-template-columns: 1fr 1fr;
130
+ gap: 12px;
131
+ }
132
+
133
+ &__webhook {
134
+ display: flex;
135
+ flex-direction: column;
136
+ gap: 8px;
137
+ }
138
+
139
+ &__webhook-row {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: space-between;
143
+ gap: 8px;
144
+ padding: 8px 12px;
145
+ border-radius: 8px;
146
+ background: var(--code-bg);
147
+ color: var(--sub-text-color);
148
+ }
149
+
150
+ &__webhook-label {
151
+ font-size: 12px;
152
+ }
153
+ }