@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.
- package/LICENSE +21 -0
- package/cli.cjs +6 -0
- package/index.html +27 -0
- package/package.json +42 -0
- package/src/App.tsx +174 -0
- package/src/api.ts +241 -0
- package/src/components/ArchiveView.scss +168 -0
- package/src/components/ArchiveView.tsx +299 -0
- package/src/components/AutomationView/AutomationView.scss +26 -0
- package/src/components/AutomationView/RuleFormPanel.scss +129 -0
- package/src/components/AutomationView/RuleFormPanel.tsx +257 -0
- package/src/components/AutomationView/RuleSidebar.scss +219 -0
- package/src/components/AutomationView/RuleSidebar.tsx +258 -0
- package/src/components/AutomationView/RunHistoryPanel.scss +286 -0
- package/src/components/AutomationView/RunHistoryPanel.tsx +320 -0
- package/src/components/AutomationView/TaskList.scss +128 -0
- package/src/components/AutomationView/TaskList.tsx +79 -0
- package/src/components/AutomationView/TriggerList.scss +153 -0
- package/src/components/AutomationView/TriggerList.tsx +217 -0
- package/src/components/AutomationView/index.tsx +228 -0
- package/src/components/AutomationView/types.ts +21 -0
- package/src/components/Chat.scss +89 -0
- package/src/components/Chat.tsx +92 -0
- package/src/components/ConfigView.scss +185 -0
- package/src/components/ConfigView.tsx +258 -0
- package/src/components/NavRail.scss +71 -0
- package/src/components/NavRail.tsx +188 -0
- package/src/components/Sidebar.scss +112 -0
- package/src/components/Sidebar.tsx +291 -0
- package/src/components/chat/ChatHeader.scss +401 -0
- package/src/components/chat/ChatHeader.tsx +342 -0
- package/src/components/chat/ChatHistoryView.tsx +122 -0
- package/src/components/chat/ChatSettingsView.tsx +22 -0
- package/src/components/chat/ChatTimelineView.scss +53 -0
- package/src/components/chat/ChatTimelineView.tsx +158 -0
- package/src/components/chat/CodeBlock.scss +87 -0
- package/src/components/chat/CodeBlock.tsx +179 -0
- package/src/components/chat/CompletionMenu.scss +70 -0
- package/src/components/chat/CompletionMenu.tsx +58 -0
- package/src/components/chat/CurrentTodoList.scss +217 -0
- package/src/components/chat/CurrentTodoList.tsx +103 -0
- package/src/components/chat/MarkdownContent.tsx +43 -0
- package/src/components/chat/MessageFooter.tsx +48 -0
- package/src/components/chat/MessageItem.scss +251 -0
- package/src/components/chat/MessageItem.tsx +78 -0
- package/src/components/chat/NewSessionGuide.scss +186 -0
- package/src/components/chat/NewSessionGuide.tsx +167 -0
- package/src/components/chat/Sender.scss +367 -0
- package/src/components/chat/Sender.tsx +541 -0
- package/src/components/chat/SessionTimelinePanel/EventList.scss +58 -0
- package/src/components/chat/SessionTimelinePanel/EventList.tsx +212 -0
- package/src/components/chat/SessionTimelinePanel/gantt.ts +177 -0
- package/src/components/chat/SessionTimelinePanel/git-graph.ts +518 -0
- package/src/components/chat/SessionTimelinePanel/index.scss +28 -0
- package/src/components/chat/SessionTimelinePanel/index.tsx +121 -0
- package/src/components/chat/SessionTimelinePanel/mermaid.ts +4 -0
- package/src/components/chat/SessionTimelinePanel/types.ts +64 -0
- package/src/components/chat/SessionTimelinePanel/utils.ts +20 -0
- package/src/components/chat/ThinkingStatus.scss +70 -0
- package/src/components/chat/ThinkingStatus.tsx +13 -0
- package/src/components/chat/ToolCallBox.scss +137 -0
- package/src/components/chat/ToolCallBox.tsx +55 -0
- package/src/components/chat/ToolGroup.scss +154 -0
- package/src/components/chat/ToolGroup.tsx +102 -0
- package/src/components/chat/ToolRenderer.tsx +45 -0
- package/src/components/chat/messageUtils.ts +171 -0
- package/src/components/chat/safeSerialize.ts +84 -0
- package/src/components/chat/tools/DefaultTool.tsx +63 -0
- package/src/components/chat/tools/adapter-claude/BashTool.scss +71 -0
- package/src/components/chat/tools/adapter-claude/BashTool.tsx +82 -0
- package/src/components/chat/tools/adapter-claude/GlobTool.scss +88 -0
- package/src/components/chat/tools/adapter-claude/GlobTool.tsx +85 -0
- package/src/components/chat/tools/adapter-claude/GrepTool.scss +96 -0
- package/src/components/chat/tools/adapter-claude/GrepTool.tsx +114 -0
- package/src/components/chat/tools/adapter-claude/LSTool.scss +85 -0
- package/src/components/chat/tools/adapter-claude/LSTool.tsx +94 -0
- package/src/components/chat/tools/adapter-claude/ReadTool.scss +57 -0
- package/src/components/chat/tools/adapter-claude/ReadTool.tsx +87 -0
- package/src/components/chat/tools/adapter-claude/TodoTool.scss +78 -0
- package/src/components/chat/tools/adapter-claude/TodoTool.tsx +60 -0
- package/src/components/chat/tools/adapter-claude/WriteTool.scss +92 -0
- package/src/components/chat/tools/adapter-claude/WriteTool.tsx +86 -0
- package/src/components/chat/tools/adapter-claude/components/FileList.scss +65 -0
- package/src/components/chat/tools/adapter-claude/components/FileList.tsx +185 -0
- package/src/components/chat/tools/adapter-claude/index.ts +28 -0
- package/src/components/chat/tools/defineToolRender.ts +28 -0
- package/src/components/chat/tools/task/GetTaskInfoTool.scss +50 -0
- package/src/components/chat/tools/task/GetTaskInfoTool.tsx +88 -0
- package/src/components/chat/tools/task/ListTasksTool.scss +56 -0
- package/src/components/chat/tools/task/ListTasksTool.tsx +83 -0
- package/src/components/chat/tools/task/StartTasksTool.scss +56 -0
- package/src/components/chat/tools/task/StartTasksTool.tsx +96 -0
- package/src/components/chat/tools/task/components/TaskToolCard.scss +127 -0
- package/src/components/chat/tools/task/components/TaskToolCard.tsx +177 -0
- package/src/components/chat/tools/task/index.ts +15 -0
- package/src/components/chat/useChatModels.tsx +206 -0
- package/src/components/chat/useChatSession.ts +370 -0
- package/src/components/config/ConfigAboutSection.scss +111 -0
- package/src/components/config/ConfigAboutSection.tsx +86 -0
- package/src/components/config/ConfigDisplayValue.scss +22 -0
- package/src/components/config/ConfigDisplayValue.tsx +62 -0
- package/src/components/config/ConfigEditors.scss +65 -0
- package/src/components/config/ConfigEditors.tsx +98 -0
- package/src/components/config/ConfigFieldRow.scss +97 -0
- package/src/components/config/ConfigFieldRow.tsx +36 -0
- package/src/components/config/ConfigSectionForm.scss +94 -0
- package/src/components/config/ConfigSectionForm.tsx +436 -0
- package/src/components/config/ConfigSectionPanel.tsx +67 -0
- package/src/components/config/ConfigShortcutInput.scss +11 -0
- package/src/components/config/ConfigShortcutInput.tsx +52 -0
- package/src/components/config/ConfigSourceSwitch.tsx +57 -0
- package/src/components/config/configSchema.ts +319 -0
- package/src/components/config/configUtils.ts +83 -0
- package/src/components/config/index.tsx +5 -0
- package/src/components/config/recordEditors/BooleanRecordEditor.scss +1 -0
- package/src/components/config/recordEditors/BooleanRecordEditor.tsx +75 -0
- package/src/components/config/recordEditors/KeyValueEditor.scss +1 -0
- package/src/components/config/recordEditors/KeyValueEditor.tsx +97 -0
- package/src/components/config/recordEditors/McpServersRecordEditor.scss +1 -0
- package/src/components/config/recordEditors/McpServersRecordEditor.tsx +258 -0
- package/src/components/config/recordEditors/ModelServicesRecordEditor.scss +1 -0
- package/src/components/config/recordEditors/ModelServicesRecordEditor.tsx +233 -0
- package/src/components/config/recordEditors/RecordEditors.scss +117 -0
- package/src/components/config/recordEditors/RecordJsonEditor.scss +1 -0
- package/src/components/config/recordEditors/RecordJsonEditor.tsx +113 -0
- package/src/components/config/recordEditors/index.tsx +5 -0
- package/src/components/knowledge-base/KnowledgeBaseView.scss +19 -0
- package/src/components/knowledge-base/KnowledgeBaseView.tsx +186 -0
- package/src/components/knowledge-base/components/ActionButton.scss +5 -0
- package/src/components/knowledge-base/components/ActionButton.tsx +9 -0
- package/src/components/knowledge-base/components/EmptyState.scss +19 -0
- package/src/components/knowledge-base/components/EmptyState.tsx +42 -0
- package/src/components/knowledge-base/components/EntitiesTab.scss +5 -0
- package/src/components/knowledge-base/components/EntitiesTab.tsx +80 -0
- package/src/components/knowledge-base/components/EntityItem.scss +82 -0
- package/src/components/knowledge-base/components/EntityItem.tsx +79 -0
- package/src/components/knowledge-base/components/EntityList.scss +5 -0
- package/src/components/knowledge-base/components/EntityList.tsx +70 -0
- package/src/components/knowledge-base/components/FilterBar.scss +21 -0
- package/src/components/knowledge-base/components/FilterBar.tsx +51 -0
- package/src/components/knowledge-base/components/FlowsTab.scss +5 -0
- package/src/components/knowledge-base/components/FlowsTab.tsx +80 -0
- package/src/components/knowledge-base/components/KnowledgeBaseHeader.scss +27 -0
- package/src/components/knowledge-base/components/KnowledgeBaseHeader.tsx +29 -0
- package/src/components/knowledge-base/components/KnowledgeList.scss +19 -0
- package/src/components/knowledge-base/components/KnowledgeList.tsx +19 -0
- package/src/components/knowledge-base/components/LoadingState.scss +5 -0
- package/src/components/knowledge-base/components/LoadingState.tsx +11 -0
- package/src/components/knowledge-base/components/MetaList.scss +19 -0
- package/src/components/knowledge-base/components/MetaList.tsx +18 -0
- package/src/components/knowledge-base/components/RulesTab.scss +5 -0
- package/src/components/knowledge-base/components/RulesTab.tsx +49 -0
- package/src/components/knowledge-base/components/SectionHeader.scss +22 -0
- package/src/components/knowledge-base/components/SectionHeader.tsx +21 -0
- package/src/components/knowledge-base/components/SkillsTab.scss +5 -0
- package/src/components/knowledge-base/components/SkillsTab.tsx +49 -0
- package/src/components/knowledge-base/components/SpecItem.scss +138 -0
- package/src/components/knowledge-base/components/SpecItem.tsx +131 -0
- package/src/components/knowledge-base/components/SpecList.scss +5 -0
- package/src/components/knowledge-base/components/SpecList.tsx +70 -0
- package/src/components/knowledge-base/components/TabContent.scss +8 -0
- package/src/components/knowledge-base/components/TabContent.tsx +17 -0
- package/src/components/knowledge-base/components/TabLabel.scss +10 -0
- package/src/components/knowledge-base/components/TabLabel.tsx +15 -0
- package/src/components/knowledge-base/index.tsx +1 -0
- package/src/components/sidebar/SessionItem.scss +256 -0
- package/src/components/sidebar/SessionItem.tsx +265 -0
- package/src/components/sidebar/SessionList.scss +92 -0
- package/src/components/sidebar/SessionList.tsx +166 -0
- package/src/components/sidebar/SidebarHeader.scss +79 -0
- package/src/components/sidebar/SidebarHeader.tsx +128 -0
- package/src/connectionManager.ts +172 -0
- package/src/hooks/useGlobalShortcut.ts +26 -0
- package/src/hooks/useQueryParams.ts +54 -0
- package/src/i18n.ts +22 -0
- package/src/main.tsx +41 -0
- package/src/resources/locales/en.json +765 -0
- package/src/resources/locales/zh.json +766 -0
- package/src/store/index.ts +23 -0
- package/src/styles/global.scss +100 -0
- package/src/utils/shortcutUtils.ts +88 -0
- package/src/vite-env.d.ts +12 -0
- package/src/ws.ts +33 -0
- 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
|
+
}
|