@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,342 @@
1
+ import './ChatHeader.scss'
2
+
3
+ import type { SessionInfo } from '@vibe-forge/core'
4
+ import { App, Button, Dropdown, Radio } from 'antd'
5
+ import type { MenuProps } from 'antd'
6
+ import { useAtomValue } from 'jotai'
7
+ import { useEffect, useMemo, useRef, useState } from 'react'
8
+ import { useTranslation } from 'react-i18next'
9
+
10
+ import { deleteSession, updateSession } from '../../api'
11
+ import { isSidebarCollapsedAtom, isSidebarResizingAtom } from '../../store/index'
12
+ import { ConfigSectionPanel } from '../config'
13
+ import type { FieldSpec } from '../config/configSchema'
14
+
15
+ export type ChatHeaderView = 'history' | 'timeline' | 'settings'
16
+
17
+ export function ChatHeader({
18
+ sessionInfo,
19
+ sessionId,
20
+ sessionTitle,
21
+ isStarred,
22
+ isArchived,
23
+ tags,
24
+ lastMessage,
25
+ lastUserMessage,
26
+ activeView,
27
+ onViewChange
28
+ }: {
29
+ sessionInfo: SessionInfo | null
30
+ sessionId?: string
31
+ sessionTitle?: string
32
+ isStarred?: boolean
33
+ isArchived?: boolean
34
+ tags?: string[]
35
+ lastMessage?: string
36
+ lastUserMessage?: string
37
+ activeView: ChatHeaderView
38
+ onViewChange: (view: ChatHeaderView) => void
39
+ }) {
40
+ const { t } = useTranslation()
41
+ const { message } = App.useApp()
42
+ const isSidebarCollapsed = useAtomValue(isSidebarCollapsedAtom)
43
+ const isResizing = useAtomValue(isSidebarResizingAtom)
44
+ const [editTitle, setEditTitle] = useState('')
45
+
46
+ const summary = sessionInfo?.type === 'summary' ? sessionInfo.summary : null
47
+ const title = (sessionInfo?.type === 'init' ? sessionInfo.title : null) ?? sessionTitle
48
+ const cwd = sessionInfo?.type === 'init' ? sessionInfo.cwd : null
49
+ const displayTitle = (title != null && title !== '')
50
+ ? title
51
+ : (summary != null && summary !== '')
52
+ ? summary
53
+ : (lastUserMessage != null && lastUserMessage !== '')
54
+ ? lastUserMessage
55
+ : (lastMessage != null && lastMessage !== '')
56
+ ? lastMessage
57
+ : t('common.newChat')
58
+
59
+ useEffect(() => {
60
+ if (title != null && title !== '') {
61
+ setEditTitle(title)
62
+ }
63
+ }, [title])
64
+
65
+ const handleToggleStar = async () => {
66
+ if (sessionId == null || sessionId === '') return
67
+ try {
68
+ await updateSession(sessionId, { isStarred: !isStarred })
69
+ void message.success(isStarred ? t('common.unstarred') : t('common.starred'))
70
+ } catch (err) {
71
+ void message.error(t('common.operationFailed'))
72
+ }
73
+ }
74
+
75
+ const handleToggleArchive = async () => {
76
+ if (sessionId == null || sessionId === '') return
77
+ try {
78
+ await updateSession(sessionId, { isArchived: !isArchived })
79
+ void message.success(isArchived ? t('common.restored') : t('common.archived'))
80
+ } catch (err) {
81
+ void message.error(t('common.operationFailed'))
82
+ }
83
+ }
84
+
85
+ const moreItems: MenuProps['items'] = [
86
+ {
87
+ key: 'star',
88
+ label: isStarred ? t('common.unstar') : t('common.star'),
89
+ icon: <span className={`material-symbols-rounded ${isStarred ? 'is-filled' : ''}`} style={{ fontSize: '18px' }}>
90
+ {isStarred ? 'star' : 'star_border'}
91
+ </span>,
92
+ onClick: () => {
93
+ void handleToggleStar()
94
+ }
95
+ },
96
+ {
97
+ key: 'archive',
98
+ label: isArchived ? t('common.restore') : t('common.archive'),
99
+ icon: <span className='material-symbols-rounded' style={{ fontSize: '18px' }}>
100
+ {isArchived ? 'unarchive' : 'archive'}
101
+ </span>,
102
+ onClick: () => {
103
+ void handleToggleArchive()
104
+ }
105
+ }
106
+ ]
107
+
108
+ return (
109
+ <div className={`chat-header ${isSidebarCollapsed ? 'is-collapsed' : ''} ${isResizing ? 'is-resizing' : ''}`}>
110
+ <div style={{ display: 'flex', alignItems: 'center', gap: '12px', flex: 1, minWidth: 0 }}>
111
+ <div className='chat-header-info'>
112
+ <div className='chat-header-title'>
113
+ {displayTitle}
114
+ </div>
115
+ <div
116
+ className='chat-header-subtitle'
117
+ onDoubleClick={() => {
118
+ // eslint-disable-next-line no-console
119
+ console.log('Session Full Info:', {
120
+ sessionId,
121
+ sessionTitle,
122
+ isStarred,
123
+ isArchived,
124
+ tags,
125
+ lastMessage,
126
+ lastUserMessage,
127
+ sessionInfo
128
+ })
129
+ }}
130
+ style={{ cursor: 'pointer', userSelect: 'all' }}
131
+ >
132
+ {sessionId ?? t('chat.selectModel')}
133
+ </div>
134
+ </div>
135
+ </div>
136
+
137
+ <div className='chat-header-actions'>
138
+ <Radio.Group
139
+ className='chat-header-view-group'
140
+ value={activeView}
141
+ optionType='button'
142
+ buttonStyle='solid'
143
+ size='small'
144
+ onChange={(event) => {
145
+ onViewChange(event.target.value as ChatHeaderView)
146
+ }}
147
+ options={[
148
+ {
149
+ label: (
150
+ <span className='chat-header-view-option'>
151
+ <span className='material-symbols-rounded'>forum</span>
152
+ <span>{t('chat.viewHistory')}</span>
153
+ </span>
154
+ ),
155
+ value: 'history'
156
+ },
157
+ {
158
+ label: (
159
+ <span className='chat-header-view-option'>
160
+ <span className='material-symbols-rounded'>timeline</span>
161
+ <span>{t('chat.viewTimeline')}</span>
162
+ </span>
163
+ ),
164
+ value: 'timeline'
165
+ },
166
+ {
167
+ label: (
168
+ <span className='chat-header-view-option'>
169
+ <span className='material-symbols-rounded'>tune</span>
170
+ <span>{t('chat.viewSettings')}</span>
171
+ </span>
172
+ ),
173
+ value: 'settings'
174
+ }
175
+ ]}
176
+ />
177
+ <div className='chat-header-divider' />
178
+ <Dropdown menu={{ items: moreItems }} placement='bottomRight' trigger={['click']}>
179
+ <Button
180
+ type='text'
181
+ icon={<span className='material-symbols-rounded'>more_vert</span>}
182
+ />
183
+ </Dropdown>
184
+ </div>
185
+ </div>
186
+ )
187
+ }
188
+
189
+ export function SessionSettingsPanel({
190
+ sessionId,
191
+ initialTitle,
192
+ initialTags = [],
193
+ onClose
194
+ }: {
195
+ sessionId: string
196
+ initialTitle?: string
197
+ initialTags?: string[]
198
+ onClose: () => void
199
+ }) {
200
+ const { t } = useTranslation()
201
+ const { message, modal } = App.useApp()
202
+ const fields = useMemo<FieldSpec[]>(() => [
203
+ {
204
+ path: ['title'],
205
+ type: 'string',
206
+ defaultValue: '',
207
+ icon: 'edit_note',
208
+ labelKey: 'chat.title',
209
+ placeholderKey: 'chat.enterTitle'
210
+ },
211
+ {
212
+ path: ['tags'],
213
+ type: 'string[]',
214
+ defaultValue: [],
215
+ icon: 'sell',
216
+ labelKey: 'chat.tags'
217
+ }
218
+ ], [])
219
+ const initialValue = useMemo(() => ({
220
+ title: initialTitle ?? '',
221
+ tags: initialTags
222
+ }), [initialTags, initialTitle])
223
+ const [draft, setDraft] = useState(initialValue)
224
+ const draftsRef = useRef(initialValue)
225
+ const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
226
+ const savingRef = useRef(false)
227
+ const lastSavedRef = useRef<string | null>(null)
228
+
229
+ useEffect(() => {
230
+ setDraft(initialValue)
231
+ }, [initialValue])
232
+
233
+ useEffect(() => {
234
+ draftsRef.current = draft
235
+ }, [draft])
236
+
237
+ useEffect(() => {
238
+ return () => {
239
+ if (saveTimerRef.current) {
240
+ clearTimeout(saveTimerRef.current)
241
+ }
242
+ }
243
+ }, [])
244
+
245
+ const scheduleSave = (nextValue: { title: string; tags: string[] }) => {
246
+ const serialized = JSON.stringify(nextValue ?? {})
247
+ if (lastSavedRef.current === serialized) return
248
+ if (saveTimerRef.current) {
249
+ clearTimeout(saveTimerRef.current)
250
+ }
251
+ saveTimerRef.current = setTimeout(async () => {
252
+ if (savingRef.current) return
253
+ const currentValue = draftsRef.current
254
+ const currentSerialized = JSON.stringify(currentValue ?? {})
255
+ if (lastSavedRef.current === currentSerialized) return
256
+ savingRef.current = true
257
+ try {
258
+ await updateSession(sessionId, {
259
+ title: typeof currentValue.title === 'string' ? currentValue.title : '',
260
+ tags: Array.isArray(currentValue.tags) ? currentValue.tags : []
261
+ })
262
+ lastSavedRef.current = currentSerialized
263
+ } catch {
264
+ void message.error(t('common.operationFailed'))
265
+ } finally {
266
+ savingRef.current = false
267
+ }
268
+ }, 500)
269
+ }
270
+
271
+ const handleDraftChange = (nextValue: unknown) => {
272
+ const nextRecord = (nextValue != null && typeof nextValue === 'object')
273
+ ? nextValue as Record<string, unknown>
274
+ : {}
275
+ const nextDraft = {
276
+ title: typeof nextRecord.title === 'string' ? nextRecord.title : '',
277
+ tags: Array.isArray(nextRecord.tags)
278
+ ? nextRecord.tags.filter(item => typeof item === 'string')
279
+ : []
280
+ }
281
+ setDraft(nextDraft)
282
+ scheduleSave(nextDraft)
283
+ }
284
+
285
+ const handleDelete = () => {
286
+ modal.confirm({
287
+ title: t('common.deleteSession'),
288
+ content: t('common.deleteSessionConfirm'),
289
+ okText: t('common.delete'),
290
+ okType: 'danger',
291
+ cancelText: t('common.cancel'),
292
+ onOk: async () => {
293
+ try {
294
+ await deleteSession(sessionId)
295
+ void message.success(t('common.deleteSuccess'))
296
+ onClose()
297
+ } catch (err) {
298
+ void message.error(t('common.deleteFailed'))
299
+ }
300
+ }
301
+ })
302
+ }
303
+
304
+ return (
305
+ <div className='session-settings-drawer'>
306
+ <ConfigSectionPanel
307
+ sectionKey='session'
308
+ title={null}
309
+ icon={undefined}
310
+ fields={fields}
311
+ value={draft}
312
+ onChange={handleDraftChange}
313
+ mergedModelServices={{}}
314
+ mergedAdapters={{}}
315
+ selectedModelService={undefined}
316
+ t={t}
317
+ className='session-settings-drawer__form'
318
+ />
319
+
320
+ <div className='settings-footer'>
321
+ <div className='danger-zone'>
322
+ <div className='delete-session-row'>
323
+ <div className='delete-session-meta'>
324
+ <div className='delete-session-title'>{t('chat.deleteSessionTitle')}</div>
325
+ <div className='delete-session-desc'>{t('chat.deleteSessionDesc')}</div>
326
+ </div>
327
+ <Button
328
+ danger
329
+ type='primary'
330
+ size='middle'
331
+ icon={<span className='material-symbols-rounded'>delete_forever</span>}
332
+ onClick={handleDelete}
333
+ className='delete-session-btn'
334
+ >
335
+ {t('common.delete')}
336
+ </Button>
337
+ </div>
338
+ </div>
339
+ </div>
340
+ </div>
341
+ )
342
+ }
@@ -0,0 +1,122 @@
1
+ import React, { useMemo } from 'react'
2
+
3
+ import type { AskUserQuestionParams, ChatMessage, Session, SessionInfo } from '@vibe-forge/core'
4
+ import { CurrentTodoList } from './CurrentTodoList'
5
+ import { MessageItem } from './MessageItem'
6
+ import { NewSessionGuide } from './NewSessionGuide'
7
+ import { Sender } from './Sender'
8
+ import { ToolGroup } from './ToolGroup'
9
+ import { processMessages } from './messageUtils'
10
+
11
+ interface ModelSelectOption {
12
+ value: string
13
+ label: React.ReactNode
14
+ searchText: string
15
+ }
16
+
17
+ interface ModelSelectGroup {
18
+ label: React.ReactNode
19
+ options: ModelSelectOption[]
20
+ }
21
+
22
+ export function ChatHistoryView({
23
+ isReady,
24
+ messages,
25
+ session,
26
+ sessionInfo,
27
+ isCreating,
28
+ showScrollBottom,
29
+ messagesContainerRef,
30
+ messagesEndRef,
31
+ scrollToBottom,
32
+ interactionRequest,
33
+ onInteractionResponse,
34
+ onSend,
35
+ onInterrupt,
36
+ onClear,
37
+ placeholder,
38
+ modelOptions,
39
+ selectedModel,
40
+ onModelChange,
41
+ modelUnavailable
42
+ }: {
43
+ isReady: boolean
44
+ messages: ChatMessage[]
45
+ session?: Session
46
+ sessionInfo: SessionInfo | null
47
+ isCreating: boolean
48
+ showScrollBottom: boolean
49
+ messagesContainerRef: React.RefObject<HTMLDivElement>
50
+ messagesEndRef: React.RefObject<HTMLDivElement>
51
+ scrollToBottom: (behavior?: ScrollBehavior) => void
52
+ interactionRequest: { id: string; payload: AskUserQuestionParams } | null
53
+ onInteractionResponse: (id: string, data: string | string[]) => void
54
+ onSend: (text: string) => void
55
+ onInterrupt: () => void
56
+ onClear: () => void
57
+ placeholder?: string
58
+ modelOptions: ModelSelectGroup[]
59
+ selectedModel?: string
60
+ onModelChange: (model: string) => void
61
+ modelUnavailable: boolean
62
+ }) {
63
+ const renderItems = useMemo(() => processMessages(messages), [messages])
64
+
65
+ return (
66
+ <>
67
+ <div className={`chat-messages ${isReady ? 'ready' : ''}`} ref={messagesContainerRef}>
68
+ {renderItems.map((item, index) => {
69
+ if (item.type === 'message') {
70
+ return (
71
+ <MessageItem
72
+ key={item.message.id || index}
73
+ msg={item.message}
74
+ isFirstInGroup={item.isFirstInGroup}
75
+ />
76
+ )
77
+ } else if (item.type === 'tool-group') {
78
+ return (
79
+ <ToolGroup
80
+ key={item.id || `group-${index}`}
81
+ items={item.items}
82
+ footer={item.footer}
83
+ />
84
+ )
85
+ }
86
+ return null
87
+ })}
88
+ <div ref={messagesEndRef} />
89
+
90
+ {showScrollBottom && (
91
+ <div className='scroll-bottom-btn' onClick={() => scrollToBottom()}>
92
+ <span className='material-symbols-rounded'>arrow_downward</span>
93
+ </div>
94
+ )}
95
+ </div>
96
+
97
+ {!session?.id && (
98
+ <div className='new-session-guide-wrapper'>
99
+ <NewSessionGuide />
100
+ </div>
101
+ )}
102
+
103
+ <CurrentTodoList messages={messages} />
104
+ <div className='sender-container'>
105
+ <Sender
106
+ onSend={onSend}
107
+ sessionStatus={isCreating ? 'running' : session?.status}
108
+ onInterrupt={onInterrupt}
109
+ onClear={onClear}
110
+ sessionInfo={sessionInfo}
111
+ interactionRequest={interactionRequest}
112
+ onInteractionResponse={onInteractionResponse}
113
+ placeholder={placeholder}
114
+ modelOptions={modelOptions}
115
+ selectedModel={selectedModel}
116
+ onModelChange={onModelChange}
117
+ modelUnavailable={modelUnavailable}
118
+ />
119
+ </div>
120
+ </>
121
+ )
122
+ }
@@ -0,0 +1,22 @@
1
+ import type { Session } from '@vibe-forge/core'
2
+
3
+ import { SessionSettingsPanel } from './ChatHeader'
4
+
5
+ export function ChatSettingsView({
6
+ session,
7
+ onClose
8
+ }: {
9
+ session: Session
10
+ onClose: () => void
11
+ }) {
12
+ return (
13
+ <div className='chat-settings-panel'>
14
+ <SessionSettingsPanel
15
+ sessionId={session.id}
16
+ initialTitle={session.title}
17
+ initialTags={session.tags}
18
+ onClose={onClose}
19
+ />
20
+ </div>
21
+ )
22
+ }
@@ -0,0 +1,53 @@
1
+ .chat-timeline-view {
2
+ display: flex;
3
+ flex-direction: column;
4
+ height: 100%;
5
+ padding: 24px;
6
+ overflow: auto;
7
+ }
8
+
9
+ .session-timeline-section {
10
+ display: flex;
11
+ flex-direction: column;
12
+ min-height: 0;
13
+ overflow: scroll;
14
+ }
15
+
16
+ .session-timeline-section--fixed {
17
+ flex: 0 0 25%;
18
+ }
19
+
20
+ .session-timeline-section--flex {
21
+ flex: 1 1 auto;
22
+ }
23
+
24
+ .session-timeline-section--row {
25
+ flex-direction: row;
26
+ gap: 16px;
27
+ }
28
+
29
+ .session-timeline-column {
30
+ display: flex;
31
+ flex-direction: column;
32
+ min-height: 0;
33
+ overflow: hidden;
34
+ }
35
+
36
+ .session-timeline-column--graph {
37
+ overflow: auto;
38
+ }
39
+
40
+ .session-timeline-column--events {
41
+ flex: 1 1 150%;
42
+ }
43
+
44
+ .session-timeline-section:not(:last-child) {
45
+ border-bottom: 1px solid var(--sub-border-color);
46
+ }
47
+
48
+ .session-timeline-section > .session-timeline-panel,
49
+ .session-timeline-section > .session-timeline-event-table,
50
+ .session-timeline-column > .session-timeline-panel,
51
+ .session-timeline-column > .session-timeline-event-table {
52
+ flex: 1 1 auto;
53
+ }
@@ -0,0 +1,158 @@
1
+ import './ChatTimelineView.scss'
2
+
3
+ import React from 'react'
4
+
5
+ import type { ChatMessage } from '@vibe-forge/core'
6
+
7
+ import { SessionTimelinePanel } from './SessionTimelinePanel'
8
+ import { SessionTimelineEventList } from './SessionTimelinePanel/EventList'
9
+ import type { Task } from './SessionTimelinePanel/types'
10
+
11
+ const mockTimelineTask: Task = {
12
+ startTime: '09:40:00',
13
+ endTime: '11:30:48',
14
+ events: [
15
+ {
16
+ type: 'tool__StartTasks',
17
+ startTime: '09:41:02',
18
+ endTime: '10:10:25',
19
+ tasks: {
20
+ taskA: {
21
+ startTime: '09:41:10',
22
+ endTime: '09:55:02',
23
+ description: 'Task A'
24
+ },
25
+ taskB: {
26
+ startTime: '09:41:10',
27
+ endTime: '09:50:40',
28
+ description: 'Task B'
29
+ },
30
+ taskC: {
31
+ startTime: '09:41:10',
32
+ endTime: '10:10:20',
33
+ description: 'Task C',
34
+ events: [
35
+ {
36
+ type: 'tool__StartTasks',
37
+ startTime: '09:48:00',
38
+ endTime: '10:05:30',
39
+ tasks: {
40
+ taskC0: {
41
+ startTime: '09:49:10',
42
+ endTime: '10:05:25',
43
+ events: [
44
+ {
45
+ type: 'tool__AskUserQuestion',
46
+ startTime: '09:57:00',
47
+ endTime: '09:59:23'
48
+ },
49
+ {
50
+ type: 'user__Prompt',
51
+ startTime: '09:58:10',
52
+ endTime: '09:58:20'
53
+ }
54
+ ]
55
+ }
56
+ }
57
+ },
58
+ {
59
+ type: 'tool__Edit',
60
+ startTime: '09:57:10',
61
+ endTime: '09:59:20'
62
+ },
63
+ {
64
+ type: 'tool__ResumeTask',
65
+ startTime: '09:59:22',
66
+ endTime: '10:05:30'
67
+ }
68
+ ]
69
+ }
70
+ }
71
+ },
72
+ {
73
+ type: 'tool__StartTasks',
74
+ startTime: '10:41:02',
75
+ endTime: '11:10:25',
76
+ tasks: {
77
+ taskA1: {
78
+ startTime: '10:41:10',
79
+ endTime: '10:55:02',
80
+ description: 'Task A1'
81
+ },
82
+ taskB1: {
83
+ startTime: '10:41:10',
84
+ endTime: '10:50:40',
85
+ description: 'Task B1'
86
+ },
87
+ taskC1: {
88
+ startTime: '10:41:10',
89
+ endTime: '11:10:20',
90
+ description: 'Task C1',
91
+ events: [
92
+ {
93
+ type: 'tool__StartTasks',
94
+ startTime: '10:48:00',
95
+ endTime: '11:05:30',
96
+ tasks: {
97
+ taskC10: {
98
+ startTime: '10:49:10',
99
+ endTime: '11:05:25',
100
+ events: [
101
+ {
102
+ type: 'tool__AskUserQuestion',
103
+ startTime: '10:57:00',
104
+ endTime: '10:59:23'
105
+ },
106
+ {
107
+ type: 'user__Prompt',
108
+ startTime: '10:58:10',
109
+ endTime: '10:58:20'
110
+ }
111
+ ]
112
+ }
113
+ }
114
+ },
115
+ {
116
+ type: 'tool__Edit',
117
+ startTime: '10:57:10',
118
+ endTime: '10:59:20'
119
+ },
120
+ {
121
+ type: 'tool__ResumeTask',
122
+ startTime: '10:59:22',
123
+ endTime: '11:05:30'
124
+ }
125
+ ]
126
+ }
127
+ }
128
+ }
129
+ ]
130
+ }
131
+
132
+ export function ChatTimelineView({
133
+ messages
134
+ }: {
135
+ messages: ChatMessage[]
136
+ }) {
137
+ return (
138
+ <div className='chat-timeline-view'>
139
+ <section className='session-timeline-section session-timeline-section--fixed'>
140
+ <SessionTimelinePanel
141
+ task={mockTimelineTask}
142
+ viewMode='gantt'
143
+ />
144
+ </section>
145
+ <section className='session-timeline-section session-timeline-section--flex session-timeline-section--row'>
146
+ <div className='session-timeline-column session-timeline-column--graph'>
147
+ <SessionTimelinePanel
148
+ task={mockTimelineTask}
149
+ viewMode='git'
150
+ />
151
+ </div>
152
+ <div className='session-timeline-column session-timeline-column--events'>
153
+ <SessionTimelineEventList task={mockTimelineTask} />
154
+ </div>
155
+ </section>
156
+ </div>
157
+ )
158
+ }