@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,217 @@
1
+ import './TriggerList.scss'
2
+
3
+ import { Button, Form, Input, InputNumber, Select, Tooltip } from 'antd'
4
+ import type { FormInstance } from 'antd'
5
+ import React from 'react'
6
+ import { useTranslation } from 'react-i18next'
7
+
8
+ import type { RuleFormValues } from './types'
9
+
10
+ type TriggerListProps = {
11
+ form: FormInstance<RuleFormValues>
12
+ updateWeeklyCron: (index: number, nextDay?: string, nextTime?: string) => void
13
+ getWebhookUrl: (triggerId?: string, webhookKey?: string) => string
14
+ }
15
+
16
+ export function TriggerList({ form, updateWeeklyCron, getWebhookUrl }: TriggerListProps) {
17
+ const { t } = useTranslation()
18
+
19
+ return (
20
+ <Form.List name='triggers'>
21
+ {(fields, { add, remove }) => (
22
+ <div className='automation-view__list automation-view__list--horizontal'>
23
+ <div className='automation-view__section-header'>
24
+ <div className='automation-view__section-heading'>
25
+ <div className='automation-view__form-title'>
26
+ <span className='material-symbols-rounded automation-view__form-icon'>bolt</span>
27
+ {t('automation.sectionTriggers')}
28
+ </div>
29
+ <span className='automation-view__section-count'>
30
+ {t('automation.triggerCount', { count: fields.length })}
31
+ </span>
32
+ </div>
33
+ <div className='automation-view__section-actions'>
34
+ <Tooltip title={t('automation.addTrigger')}>
35
+ <Button
36
+ className='automation-view__icon-button'
37
+ type='text'
38
+ onClick={() => add({
39
+ type: 'interval',
40
+ intervalMinutes: 30,
41
+ cronExpression: '',
42
+ weeklyDay: '1',
43
+ weeklyTime: '09:00'
44
+ })}
45
+ >
46
+ <span className='material-symbols-rounded automation-view__action-icon'>add</span>
47
+ </Button>
48
+ </Tooltip>
49
+ </div>
50
+ </div>
51
+ <div className='automation-view__form-desc'>{t('automation.triggerAny')}</div>
52
+ <div className='automation-view__list-scroll'>
53
+ {fields.map((field, index) => (
54
+ <Form.Item
55
+ key={field.key}
56
+ noStyle
57
+ shouldUpdate={(prevValues, nextValues) => {
58
+ const prevTrigger = prevValues.triggers?.[field.name]
59
+ const nextTrigger = nextValues.triggers?.[field.name]
60
+ return prevTrigger?.type !== nextTrigger?.type
61
+ || prevTrigger?.webhookKey !== nextTrigger?.webhookKey
62
+ || prevTrigger?.id !== nextTrigger?.id
63
+ }}
64
+ >
65
+ {(formInstance) => {
66
+ const triggerType = formInstance.getFieldValue(['triggers', field.name, 'type']) as RuleFormValues['triggers'][number]['type']
67
+ const triggerId = formInstance.getFieldValue(['triggers', field.name, 'id']) as string | undefined
68
+ const webhookKey = formInstance.getFieldValue(['triggers', field.name, 'webhookKey']) as string | undefined
69
+ const webhookUrl = getWebhookUrl(triggerId, webhookKey)
70
+ return (
71
+ <div className='automation-view__list-item'>
72
+ <Form.Item name={[field.name, 'id']} hidden>
73
+ <Input />
74
+ </Form.Item>
75
+ <Tooltip title={t('automation.remove')}>
76
+ <Button
77
+ className='automation-view__remove-button'
78
+ type='text'
79
+ danger
80
+ onClick={() => remove(field.name)}
81
+ disabled={fields.length <= 1}
82
+ >
83
+ <span className='material-symbols-rounded automation-view__action-icon'>close</span>
84
+ </Button>
85
+ </Tooltip>
86
+ <div className='automation-view__list-header'>
87
+ <Form.Item
88
+ name={[field.name, 'type']}
89
+ label={t('automation.ruleType')}
90
+ rules={[{ required: true, message: t('automation.ruleTypeRequired') }]}
91
+ >
92
+ <Select
93
+ options={[
94
+ { label: t('automation.typeInterval'), value: 'interval' },
95
+ { label: t('automation.typeWebhook'), value: 'webhook' },
96
+ { label: t('automation.typeCron'), value: 'cron' }
97
+ ]}
98
+ onChange={(value) => {
99
+ if (value === 'interval') {
100
+ form.setFieldValue(['triggers', field.name, 'intervalMinutes'], 30)
101
+ form.setFieldValue(['triggers', field.name, 'cronExpression'], '')
102
+ form.setFieldValue(['triggers', field.name, 'weeklyDay'], '1')
103
+ form.setFieldValue(['triggers', field.name, 'weeklyTime'], '09:00')
104
+ form.setFieldValue(['triggers', field.name, 'webhookKey'], '')
105
+ }
106
+ if (value === 'cron') {
107
+ form.setFieldValue(['triggers', field.name, 'cronExpression'], '')
108
+ form.setFieldValue(['triggers', field.name, 'weeklyDay'], '1')
109
+ form.setFieldValue(['triggers', field.name, 'weeklyTime'], '09:00')
110
+ form.setFieldValue(['triggers', field.name, 'intervalMinutes'], 30)
111
+ form.setFieldValue(['triggers', field.name, 'webhookKey'], '')
112
+ }
113
+ if (value === 'webhook') {
114
+ form.setFieldValue(['triggers', field.name, 'webhookKey'], '')
115
+ form.setFieldValue(['triggers', field.name, 'intervalMinutes'], 30)
116
+ form.setFieldValue(['triggers', field.name, 'cronExpression'], '')
117
+ form.setFieldValue(['triggers', field.name, 'weeklyDay'], '1')
118
+ form.setFieldValue(['triggers', field.name, 'weeklyTime'], '09:00')
119
+ }
120
+ }}
121
+ />
122
+ </Form.Item>
123
+ </div>
124
+
125
+ {triggerType === 'interval' && (
126
+ <Form.Item
127
+ name={[field.name, 'intervalMinutes']}
128
+ label={t('automation.intervalMinutes')}
129
+ rules={[{ required: true, message: t('automation.intervalRequired') }]}
130
+ >
131
+ <InputNumber min={1} className='automation-view__input-number' />
132
+ </Form.Item>
133
+ )}
134
+ {triggerType === 'cron' && (
135
+ <div className='automation-view__cron-section'>
136
+ <Form.Item
137
+ name={[field.name, 'cronExpression']}
138
+ label={t('automation.cronExpressionLabel')}
139
+ rules={[{ required: true, message: t('automation.cronExpressionRequired') }]}
140
+ >
141
+ <Input placeholder={t('automation.cronExpressionHint')} />
142
+ </Form.Item>
143
+ <Form.Item
144
+ name={[field.name, 'cronPreset']}
145
+ label={t('automation.cronPreset')}
146
+ >
147
+ <Select
148
+ options={[
149
+ { label: t('automation.cronPresetHourly'), value: '0 * * * *' },
150
+ { label: t('automation.cronPresetDaily9'), value: '0 9 * * *' },
151
+ { label: t('automation.cronPresetWeekday9'), value: '0 9 * * 1-5' },
152
+ { label: t('automation.cronPresetWeekend10'), value: '0 10 * * 6,0' }
153
+ ]}
154
+ onChange={(value) => form.setFieldValue(['triggers', field.name, 'cronExpression'], value)}
155
+ placeholder={t('automation.cronPresetHint')}
156
+ allowClear
157
+ />
158
+ </Form.Item>
159
+ <div className='automation-view__cron-hint'>{t('automation.cronInputHint')}</div>
160
+ <div className='automation-view__cron-weekly'>
161
+ <Form.Item name={[field.name, 'weeklyDay']} label={t('automation.weeklyDay')}>
162
+ <Select
163
+ options={[
164
+ { label: t('automation.weekdaySun'), value: '0' },
165
+ { label: t('automation.weekdayMon'), value: '1' },
166
+ { label: t('automation.weekdayTue'), value: '2' },
167
+ { label: t('automation.weekdayWed'), value: '3' },
168
+ { label: t('automation.weekdayThu'), value: '4' },
169
+ { label: t('automation.weekdayFri'), value: '5' },
170
+ { label: t('automation.weekdaySat'), value: '6' }
171
+ ]}
172
+ onChange={(value) => updateWeeklyCron(index, value)}
173
+ />
174
+ </Form.Item>
175
+ <Form.Item name={[field.name, 'weeklyTime']} label={t('automation.weeklyTime')}>
176
+ <Input
177
+ placeholder={t('automation.weeklyTimeHint')}
178
+ onChange={(event) => updateWeeklyCron(index, undefined, event.target.value)}
179
+ />
180
+ </Form.Item>
181
+ </div>
182
+ </div>
183
+ )}
184
+ {triggerType === 'webhook' && (
185
+ <div className='automation-view__webhook'>
186
+ <Form.Item name={[field.name, 'webhookKey']} label={t('automation.webhookKey')}>
187
+ <Input placeholder={t('automation.webhookKeyHint')} />
188
+ </Form.Item>
189
+ {webhookUrl && (
190
+ <div className='automation-view__webhook-row'>
191
+ <span className='automation-view__webhook-label'>{t('automation.webhookUrl')}</span>
192
+ <Tooltip title={t('automation.copy')}>
193
+ <Button
194
+ className='automation-view__icon-button'
195
+ type='text'
196
+ onClick={() => {
197
+ void navigator.clipboard.writeText(webhookUrl)
198
+ }}
199
+ >
200
+ <span className='material-symbols-rounded automation-view__action-icon'>content_copy</span>
201
+ </Button>
202
+ </Tooltip>
203
+ </div>
204
+ )}
205
+ </div>
206
+ )}
207
+ </div>
208
+ )
209
+ }}
210
+ </Form.Item>
211
+ ))}
212
+ </div>
213
+ </div>
214
+ )}
215
+ </Form.List>
216
+ )
217
+ }
@@ -0,0 +1,228 @@
1
+ import './AutomationView.scss'
2
+
3
+ import { App } from 'antd'
4
+ import React, { useCallback, useEffect, useMemo, useState } from 'react'
5
+ import { useTranslation } from 'react-i18next'
6
+ import { useNavigate } from 'react-router-dom'
7
+ import useSWR from 'swr'
8
+
9
+ import type { AutomationRule, AutomationRun } from '#~/api.js'
10
+ import {
11
+ createAutomationRule,
12
+ deleteAutomationRule,
13
+ listAutomationRules,
14
+ listAutomationRuns,
15
+ runAutomationRule,
16
+ updateAutomationRule
17
+ } from '#~/api.js'
18
+ import { useQueryParams } from '#~/hooks/useQueryParams.js'
19
+
20
+ import { RuleFormPanel } from './RuleFormPanel.js'
21
+ import { RuleSidebar } from './RuleSidebar.js'
22
+ import { RunHistoryPanel } from './RunHistoryPanel.js'
23
+
24
+ type PanelMode = 'view' | 'create' | 'edit'
25
+
26
+ interface AutomationQueryParams extends Record<string, string> {
27
+ rule: string
28
+ q: string
29
+ runQ: string
30
+ status: string
31
+ time: string
32
+ sort: string
33
+ }
34
+
35
+ export function AutomationView() {
36
+ const { t } = useTranslation()
37
+ const { message } = App.useApp()
38
+ const navigate = useNavigate()
39
+ const { data, mutate } = useSWR<{ rules: AutomationRule[] }>(
40
+ '/api/automation/rules',
41
+ listAutomationRules
42
+ )
43
+ const rules = data?.rules ?? []
44
+ const [panelMode, setPanelMode] = useState<PanelMode>('view')
45
+ const [submitting, setSubmitting] = useState(false)
46
+
47
+ const { values, update } = useQueryParams<AutomationQueryParams>({
48
+ keys: ['rule', 'q', 'runQ', 'status', 'time', 'sort'],
49
+ defaults: {
50
+ rule: '',
51
+ q: '',
52
+ runQ: '',
53
+ status: 'all',
54
+ time: 'all',
55
+ sort: 'desc'
56
+ },
57
+ omit: {
58
+ rule: (value: string) => value === '',
59
+ q: (value: string) => value === '',
60
+ runQ: (value: string) => value === '',
61
+ status: (value: string) => value === 'all',
62
+ time: (value: string) => value === 'all',
63
+ sort: (value: string) => value === 'desc'
64
+ }
65
+ })
66
+
67
+ const selectedRuleId = useMemo(() => {
68
+ const fromUrl = values.rule
69
+ if (fromUrl && rules.some(rule => rule.id === fromUrl)) return fromUrl
70
+ return rules[0]?.id ?? null
71
+ }, [rules, values.rule])
72
+
73
+ const selectedRule = useMemo(
74
+ () => rules.find(rule => rule.id === selectedRuleId) ?? null,
75
+ [rules, selectedRuleId]
76
+ )
77
+
78
+ const { data: runsData, mutate: mutateRuns } = useSWR<{ runs: AutomationRun[] }>(
79
+ selectedRuleId ? `/api/automation/rules/${selectedRuleId}/runs` : null,
80
+ () => listAutomationRuns(selectedRuleId ?? '')
81
+ )
82
+ const runs = runsData?.runs ?? []
83
+
84
+ useEffect(() => {
85
+ if (rules.length === 0) return
86
+ if (!values.rule || !rules.some(rule => rule.id === values.rule)) {
87
+ update({ rule: rules[0].id })
88
+ }
89
+ }, [rules, update, values.rule])
90
+
91
+ const handleSelectRule = useCallback((ruleId: string) => {
92
+ setPanelMode('view')
93
+ update({ rule: ruleId })
94
+ }, [update])
95
+
96
+ const handleCreateRule = useCallback(() => {
97
+ setPanelMode('create')
98
+ }, [])
99
+
100
+ const handleEditRule = useCallback((rule: AutomationRule) => {
101
+ setPanelMode('edit')
102
+ update({ rule: rule.id })
103
+ }, [update])
104
+
105
+ const handleCancelForm = useCallback(() => {
106
+ setPanelMode('view')
107
+ }, [])
108
+
109
+ const handleSubmit = useCallback(async (
110
+ payload: Partial<AutomationRule>,
111
+ immediateRun: boolean
112
+ ) => {
113
+ try {
114
+ setSubmitting(true)
115
+ if (panelMode === 'create') {
116
+ const res = await createAutomationRule({ ...payload, immediateRun })
117
+ await mutate()
118
+ if (res.rule?.id) {
119
+ update({ rule: res.rule.id })
120
+ }
121
+ setPanelMode('view')
122
+ return
123
+ }
124
+ if (panelMode === 'edit' && selectedRule) {
125
+ await updateAutomationRule(selectedRule.id, { ...payload, immediateRun })
126
+ await mutate()
127
+ void mutateRuns()
128
+ setPanelMode('view')
129
+ }
130
+ } catch {
131
+ void message.error(t('automation.saveFailed'))
132
+ } finally {
133
+ setSubmitting(false)
134
+ }
135
+ }, [panelMode, mutate, mutateRuns, message, selectedRule, t])
136
+
137
+ const handleDelete = useCallback(async (rule: AutomationRule) => {
138
+ try {
139
+ await deleteAutomationRule(rule.id)
140
+ if (selectedRuleId === rule.id) {
141
+ update({ rule: '' })
142
+ setPanelMode('view')
143
+ }
144
+ void mutate()
145
+ void message.success(t('automation.deleted'))
146
+ } catch {
147
+ void message.error(t('automation.deleteFailed'))
148
+ }
149
+ }, [message, mutate, selectedRuleId, t])
150
+
151
+ const handleRun = useCallback(async (rule: AutomationRule) => {
152
+ try {
153
+ const res = await runAutomationRule(rule.id)
154
+ const nextSessionId = res.sessionIds?.[0]
155
+ if (nextSessionId) {
156
+ void message.success(t('automation.runStarted'))
157
+ void mutateRuns()
158
+ void navigate(`/session/${nextSessionId}`)
159
+ }
160
+ } catch {
161
+ void message.error(t('automation.runFailed'))
162
+ }
163
+ }, [message, mutateRuns, navigate, t])
164
+
165
+ const handleToggle = useCallback(async (rule: AutomationRule, enabled: boolean) => {
166
+ try {
167
+ await updateAutomationRule(rule.id, { enabled })
168
+ void mutate()
169
+ } catch {
170
+ void message.error(t('automation.toggleFailed'))
171
+ }
172
+ }, [message, mutate, t])
173
+
174
+ return (
175
+ <div className='automation-view'>
176
+ <div className='automation-view__left'>
177
+ <RuleSidebar
178
+ rules={rules}
179
+ selectedRuleId={selectedRuleId}
180
+ query={values.q}
181
+ isCreating={panelMode === 'create'}
182
+ onCreate={handleCreateRule}
183
+ onSelect={handleSelectRule}
184
+ onRun={handleRun}
185
+ onDelete={handleDelete}
186
+ onToggle={handleToggle}
187
+ onQueryChange={(value: string) => update({ q: value })}
188
+ />
189
+ </div>
190
+ <div className='automation-view__divider' />
191
+ <div className='automation-view__right'>
192
+ {panelMode === 'create' && (
193
+ <RuleFormPanel
194
+ mode='create'
195
+ rule={null}
196
+ submitting={submitting}
197
+ onSubmit={handleSubmit}
198
+ onCancel={handleCancelForm}
199
+ />
200
+ )}
201
+ {panelMode === 'edit' && (
202
+ <RuleFormPanel
203
+ mode='edit'
204
+ rule={selectedRule}
205
+ submitting={submitting}
206
+ onSubmit={handleSubmit}
207
+ onCancel={handleCancelForm}
208
+ />
209
+ )}
210
+ {panelMode === 'view' && (
211
+ <RunHistoryPanel
212
+ rule={selectedRule}
213
+ runs={runs}
214
+ runQuery={values.runQ}
215
+ statusFilter={values.status}
216
+ timeFilter={values.time}
217
+ sortOrder={values.sort}
218
+ onEditRule={handleEditRule}
219
+ onRunQueryChange={(value: string) => update({ runQ: value })}
220
+ onStatusFilterChange={(value: string) => update({ status: value })}
221
+ onTimeFilterChange={(value: string) => update({ time: value })}
222
+ onSortOrderChange={(value: string) => update({ sort: value })}
223
+ />
224
+ )}
225
+ </div>
226
+ </div>
227
+ )
228
+ }
@@ -0,0 +1,21 @@
1
+ export type RuleFormValues = {
2
+ name: string
3
+ description?: string
4
+ enabled: boolean
5
+ immediateRun: boolean
6
+ triggers: Array<{
7
+ id?: string
8
+ type: 'interval' | 'webhook' | 'cron'
9
+ intervalMinutes?: number
10
+ webhookKey?: string
11
+ cronExpression?: string
12
+ cronPreset?: string
13
+ weeklyDay?: string
14
+ weeklyTime?: string
15
+ }>
16
+ tasks: Array<{
17
+ id?: string
18
+ title?: string
19
+ prompt: string
20
+ }>
21
+ }
@@ -0,0 +1,89 @@
1
+ .chat-container {
2
+ display: flex;
3
+ flex-direction: column;
4
+ height: 100%;
5
+ background-color: var(--bg-color);
6
+ position: relative;
7
+
8
+ &.is-new-session {
9
+ .chat-messages {
10
+ flex: 0;
11
+ height: 0;
12
+ padding: 0;
13
+ overflow: hidden;
14
+ opacity: 0;
15
+ }
16
+
17
+ .new-session-guide-wrapper {
18
+ flex: 1;
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ padding: 24px 24px 12px;
23
+ }
24
+ }
25
+ }
26
+
27
+ .new-session-guide-wrapper {
28
+ display: flex;
29
+ justify-content: center;
30
+ }
31
+
32
+ .chat-messages {
33
+ flex: 1;
34
+ overflow-y: auto;
35
+ padding: 12px 24px 0 24px;
36
+ position: relative;
37
+ opacity: 0;
38
+ background-color: var(--bg-color);
39
+ transition: .2s;
40
+
41
+ &.ready {
42
+ opacity: 1;
43
+ }
44
+ }
45
+
46
+ .sender-container {
47
+ flex: 0;
48
+ display: flex;
49
+ flex-direction: column;
50
+ justify-content: flex-end;
51
+ padding: 0 24px 24px;
52
+ transition:
53
+ flex .4s cubic-bezier(.4, 0, .2, 1),
54
+ padding-bottom .4s cubic-bezier(.4, 0, .2, 1);
55
+ }
56
+
57
+ .scroll-bottom-btn {
58
+ position: sticky;
59
+ bottom: 24px;
60
+ left: 100%;
61
+ transform: translateX(-40px);
62
+ width: 32px;
63
+ height: 32px;
64
+ border-radius: 50%;
65
+ background-color: var(--bg-color);
66
+ border: 1px solid var(--border-color);
67
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, .1);
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ cursor: pointer;
72
+ color: var(--sub-text-color, #6b7280);
73
+ z-index: 10;
74
+
75
+ &:hover {
76
+ background-color: var(--tag-hover-bg, #f9fafb);
77
+ color: var(--text-color);
78
+ }
79
+
80
+ .material-symbols-rounded {
81
+ font-size: 18px;
82
+ }
83
+ }
84
+
85
+ .chat-settings-panel {
86
+ flex: 1;
87
+ overflow: auto;
88
+ padding: 20px 24px 24px;
89
+ }
@@ -0,0 +1,92 @@
1
+ import './Chat.scss'
2
+
3
+ import { useTranslation } from 'react-i18next'
4
+
5
+ import type { Session } from '@vibe-forge/core'
6
+ import { ChatHeader } from './chat/ChatHeader.js'
7
+ import { ChatHistoryView } from './chat/ChatHistoryView.js'
8
+ import { ChatSettingsView } from './chat/ChatSettingsView.js'
9
+ import { ChatTimelineView } from './chat/ChatTimelineView.js'
10
+ import { useChatModels } from './chat/useChatModels.js'
11
+ import { useChatSession } from './chat/useChatSession.js'
12
+
13
+ export function Chat({
14
+ session
15
+ }: {
16
+ session?: Session
17
+ }) {
18
+ const { t } = useTranslation()
19
+ const { selectedModel, setSelectedModel, modelOptions, hasAvailableModels } = useChatModels()
20
+ const {
21
+ messages,
22
+ sessionInfo,
23
+ interactionRequest,
24
+ isCreating,
25
+ isReady,
26
+ isThinking,
27
+ activeView,
28
+ setActiveView,
29
+ messagesEndRef,
30
+ messagesContainerRef,
31
+ showScrollBottom,
32
+ scrollToBottom,
33
+ send,
34
+ interrupt,
35
+ clearMessages,
36
+ handleInteractionResponse
37
+ } = useChatSession({ session, selectedModel, hasAvailableModels })
38
+
39
+ return (
40
+ <div className={`chat-container ${isReady ? 'ready' : ''} ${!session?.id ? 'is-new-session' : ''}`}>
41
+ {session?.id && (
42
+ <ChatHeader
43
+ sessionInfo={sessionInfo}
44
+ sessionId={session?.id}
45
+ sessionTitle={session?.title}
46
+ isStarred={session?.isStarred}
47
+ isArchived={session?.isArchived}
48
+ tags={session?.tags}
49
+ lastMessage={session?.lastMessage}
50
+ lastUserMessage={session?.lastUserMessage}
51
+ activeView={activeView}
52
+ onViewChange={setActiveView}
53
+ />
54
+ )}
55
+
56
+ {activeView === 'history' && (
57
+ <ChatHistoryView
58
+ isReady={isReady}
59
+ messages={messages}
60
+ session={session}
61
+ sessionInfo={sessionInfo}
62
+ isCreating={isCreating}
63
+ showScrollBottom={showScrollBottom}
64
+ messagesContainerRef={messagesContainerRef}
65
+ messagesEndRef={messagesEndRef}
66
+ scrollToBottom={scrollToBottom}
67
+ interactionRequest={interactionRequest}
68
+ onInteractionResponse={handleInteractionResponse}
69
+ onSend={send}
70
+ onInterrupt={interrupt}
71
+ onClear={clearMessages}
72
+ placeholder={!session?.id ? t('chat.newSessionPlaceholder') : undefined}
73
+ modelOptions={modelOptions}
74
+ selectedModel={selectedModel}
75
+ onModelChange={setSelectedModel}
76
+ modelUnavailable={!hasAvailableModels}
77
+ />
78
+ )}
79
+
80
+ {activeView === 'timeline' && (
81
+ <ChatTimelineView messages={messages} isThinking={isThinking} />
82
+ )}
83
+
84
+ {activeView === 'settings' && session?.id && (
85
+ <ChatSettingsView
86
+ session={session}
87
+ onClose={() => setActiveView('history')}
88
+ />
89
+ )}
90
+ </div>
91
+ )
92
+ }