@vibe-forge/client 0.4.0 → 0.6.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 (142) hide show
  1. package/AGENTS.md +37 -0
  2. package/cli.cjs +1 -0
  3. package/dist/assets/{arc-DgIxeTMg.js → arc-CMAHd5G3.js} +1 -1
  4. package/dist/assets/{blockDiagram-c4efeb88-CEAob3X9.js → blockDiagram-c4efeb88-DKww-VCP.js} +1 -1
  5. package/dist/assets/{c4Diagram-c83219d4-DwIxpDKd.js → c4Diagram-c83219d4-DKrjVHyY.js} +1 -1
  6. package/dist/assets/channel-Bi4g8rj9.js +1 -0
  7. package/dist/assets/{classDiagram-beda092f-Cz1q8u_0.js → classDiagram-beda092f-BXx5rdo3.js} +1 -1
  8. package/dist/assets/{classDiagram-v2-2358418a-CImgTuwd.js → classDiagram-v2-2358418a-CnR3WLsr.js} +1 -1
  9. package/dist/assets/clone-DPrpP2ky.js +1 -0
  10. package/dist/assets/{createText-1719965b-C1_HJcCc.js → createText-1719965b-CmOsl1W7.js} +1 -1
  11. package/dist/assets/{edges-96097737-BU8qStzd.js → edges-96097737-CQeQgpjD.js} +1 -1
  12. package/dist/assets/{erDiagram-0228fc6a-DNA1Fz2L.js → erDiagram-0228fc6a-ZUNB-ucF.js} +1 -1
  13. package/dist/assets/{flowDb-c6c81e3f-DjiCStMN.js → flowDb-c6c81e3f-DuuKeSLX.js} +1 -1
  14. package/dist/assets/{flowDiagram-50d868cf-CSDi0-RD.js → flowDiagram-50d868cf-Bc6n85yR.js} +1 -1
  15. package/dist/assets/flowDiagram-v2-4f6560a1-BZqaeqoh.js +1 -0
  16. package/dist/assets/{flowchart-elk-definition-6af322e1-DrhIMas7.js → flowchart-elk-definition-6af322e1-cAG5afW9.js} +1 -1
  17. package/dist/assets/{ganttDiagram-a2739b55-CTZnUP5z.js → ganttDiagram-a2739b55-Dp6xhY5I.js} +1 -1
  18. package/dist/assets/{gitGraphDiagram-82fe8481-COOW7jTi.js → gitGraphDiagram-82fe8481-MlIIRBdG.js} +1 -1
  19. package/dist/assets/{graph-CIkpD4Kx.js → graph-D7Es8jZ-.js} +1 -1
  20. package/dist/assets/{index-5325376f-aVVRRTIu.js → index-5325376f-DC18ottv.js} +1 -1
  21. package/dist/assets/index-D37AbgPQ.js +545 -0
  22. package/dist/assets/{index-D1giUI7r.css → index-fcJ9v94I.css} +1 -1
  23. package/dist/assets/{infoDiagram-8eee0895-DQpZ1LVD.js → infoDiagram-8eee0895-CXk21kFp.js} +1 -1
  24. package/dist/assets/{journeyDiagram-c64418c1-DoKguIuk.js → journeyDiagram-c64418c1-899BKBHL.js} +1 -1
  25. package/dist/assets/{layout-Tnmha8Nh.js → layout-DLaxdy48.js} +1 -1
  26. package/dist/assets/{line-BQR2SOyl.js → line-_lw5YbRM.js} +1 -1
  27. package/dist/assets/{linear-DlG0eemV.js → linear-D5iu84ui.js} +1 -1
  28. package/dist/assets/{mermaid.core-BnwYO0He.js → mermaid.core-C6sW3GFM.js} +4 -4
  29. package/dist/assets/{mindmap-definition-8da855dc-BllYwDID.js → mindmap-definition-8da855dc-BS9Xy9KN.js} +1 -1
  30. package/dist/assets/{pieDiagram-a8764435-DwCkhPVc.js → pieDiagram-a8764435-DZt9cEgs.js} +1 -1
  31. package/dist/assets/{quadrantDiagram-1e28029f-c40GKTU0.js → quadrantDiagram-1e28029f-BTIeHOgn.js} +1 -1
  32. package/dist/assets/{requirementDiagram-08caed73-DnQp2Tk6.js → requirementDiagram-08caed73-BHJAKD2g.js} +1 -1
  33. package/dist/assets/{sankeyDiagram-a04cb91d-CnJrs13b.js → sankeyDiagram-a04cb91d-DnAkVOK8.js} +1 -1
  34. package/dist/assets/{sequenceDiagram-c5b8d532-1YBwnpKu.js → sequenceDiagram-c5b8d532-92tE3oFv.js} +1 -1
  35. package/dist/assets/{stateDiagram-1ecb1508-BFBxQ6Fh.js → stateDiagram-1ecb1508-DG0ObiMg.js} +1 -1
  36. package/dist/assets/{stateDiagram-v2-c2b004d7-Dmechvv2.js → stateDiagram-v2-c2b004d7-BKoJx2ci.js} +1 -1
  37. package/dist/assets/{styles-b4e223ce-DWWfWX8O.js → styles-b4e223ce-Ba6G4ri9.js} +1 -1
  38. package/dist/assets/{styles-ca3715f6-CKKvZxaU.js → styles-ca3715f6-Bn6RIIVW.js} +1 -1
  39. package/dist/assets/{styles-d45a18b0-dKMOUh9p.js → styles-d45a18b0-_dELBUI6.js} +1 -1
  40. package/dist/assets/{svgDrawCommon-b86b1483-CBgjChPM.js → svgDrawCommon-b86b1483-CRK-ZoIs.js} +1 -1
  41. package/dist/assets/{timeline-definition-faaaa080-NCt-HHmb.js → timeline-definition-faaaa080-DvQ_RA_i.js} +1 -1
  42. package/dist/assets/{xychartDiagram-f5964ef8-BJhXS4dG.js → xychartDiagram-f5964ef8-CJxeDLfg.js} +1 -1
  43. package/dist/index.html +2 -2
  44. package/package.json +10 -7
  45. package/src/App.tsx +20 -168
  46. package/src/api/base.ts +116 -7
  47. package/src/api/sessions.ts +3 -1
  48. package/src/api.ts +3 -1
  49. package/src/components/ArchiveView.tsx +5 -5
  50. package/src/components/ConfigView.tsx +28 -28
  51. package/src/components/{AutomationView → automation-view}/index.tsx +10 -9
  52. package/src/components/{BenchmarkView → benchmark-view}/index.tsx +5 -4
  53. package/src/components/chat/ChatHeader.tsx +6 -6
  54. package/src/components/chat/ChatHistoryView.tsx +74 -16
  55. package/src/components/chat/ChatTimelineView.tsx +3 -3
  56. package/src/components/chat/CurrentTodoList.scss +56 -27
  57. package/src/components/chat/{Sender → sender}/Sender.scss +278 -85
  58. package/src/components/chat/{Sender → sender}/Sender.tsx +125 -41
  59. package/src/components/chat/tools/core/ToolGroup.tsx +1 -1
  60. package/src/components/config/ConfigSectionForm.tsx +1 -1
  61. package/src/components/config/ConfigSourceSwitch.tsx +12 -34
  62. package/src/components/config/channelDefinitions.ts +2 -2
  63. package/src/components/layout/AppShell.scss +19 -0
  64. package/src/components/layout/AppShell.tsx +45 -0
  65. package/src/components/sidebar/SessionItem.scss +17 -0
  66. package/src/components/sidebar/SessionItem.tsx +21 -13
  67. package/src/hooks/chat/model-selector.ts +150 -0
  68. package/src/hooks/chat/use-chat-adapter.ts +93 -0
  69. package/src/hooks/chat/use-chat-models.tsx +126 -57
  70. package/src/hooks/chat/use-chat-permission-mode.ts +14 -10
  71. package/src/hooks/chat/use-chat-session-actions.ts +22 -13
  72. package/src/hooks/chat/use-chat-session-messages.ts +62 -10
  73. package/src/hooks/chat/use-chat-session.ts +49 -2
  74. package/src/hooks/use-app-preferences.ts +41 -0
  75. package/src/hooks/use-session-subscription.ts +101 -0
  76. package/src/hooks/use-sidebar-navigation.ts +35 -0
  77. package/src/resources/adapters.ts +20 -0
  78. package/src/resources/locales/en.json +6 -0
  79. package/src/resources/locales/zh.json +6 -0
  80. package/src/routes/AppRoutes.tsx +22 -0
  81. package/src/routes/ArchiveRoute.tsx +5 -0
  82. package/src/routes/AutomationRoute.tsx +5 -0
  83. package/src/routes/BenchmarkRoute.tsx +5 -0
  84. package/src/{components/Chat.scss → routes/ChatRoute.scss} +35 -0
  85. package/src/routes/ChatRoute.tsx +132 -0
  86. package/src/routes/ConfigRoute.tsx +5 -0
  87. package/src/routes/KnowledgeRoute.tsx +5 -0
  88. package/dist/assets/channel-DhtnrNJ6.js +0 -1
  89. package/dist/assets/clone-7bHB6YkC.js +0 -1
  90. package/dist/assets/flowDiagram-v2-4f6560a1-_13Sz5Wh.js +0 -1
  91. package/dist/assets/index-DRSI_ZIL.js +0 -514
  92. package/src/components/Chat.tsx +0 -100
  93. package/src/components/{AutomationView → automation-view}/RuleFormPanel.scss +0 -0
  94. package/src/components/{AutomationView → automation-view}/RuleFormPanel.tsx +0 -0
  95. package/src/components/{AutomationView → automation-view}/RuleSidebar.scss +0 -0
  96. package/src/components/{AutomationView → automation-view}/RuleSidebar.tsx +0 -0
  97. package/src/components/{AutomationView → automation-view}/RunHistoryPanel.scss +0 -0
  98. package/src/components/{AutomationView → automation-view}/RunHistoryPanel.tsx +0 -0
  99. package/src/components/{AutomationView → automation-view}/TaskList.scss +0 -0
  100. package/src/components/{AutomationView → automation-view}/TaskList.tsx +0 -0
  101. package/src/components/{AutomationView → automation-view}/TriggerList.scss +0 -0
  102. package/src/components/{AutomationView → automation-view}/TriggerList.tsx +0 -0
  103. package/src/components/{AutomationView/AutomationView.scss → automation-view/index.scss} +0 -0
  104. package/src/components/{AutomationView → automation-view}/types.ts +0 -0
  105. package/src/components/{BenchmarkView → benchmark-view}/BenchmarkCasePanel.scss +0 -0
  106. package/src/components/{BenchmarkView → benchmark-view}/BenchmarkCasePanel.tsx +0 -0
  107. package/src/components/{BenchmarkView → benchmark-view}/BenchmarkSidebar.scss +0 -0
  108. package/src/components/{BenchmarkView → benchmark-view}/BenchmarkSidebar.tsx +0 -0
  109. package/src/components/{BenchmarkView → benchmark-view}/BenchmarkView.scss +0 -0
  110. package/src/components/{BenchmarkView → benchmark-view}/types.ts +0 -0
  111. package/src/components/{BenchmarkView → benchmark-view}/utils.ts +0 -0
  112. package/src/components/chat/{Messages → messages}/MessageFooter.tsx +0 -0
  113. package/src/components/chat/{Messages → messages}/MessageItem.scss +0 -0
  114. package/src/components/chat/{Messages → messages}/MessageItem.tsx +0 -0
  115. package/src/components/chat/{Messages → messages}/message-utils.ts +0 -0
  116. package/src/components/chat/{Sender → sender}/CompletionMenu.scss +0 -0
  117. package/src/components/chat/{Sender → sender}/CompletionMenu.tsx +0 -0
  118. package/src/components/chat/{Sender → sender}/ThinkingStatus.scss +0 -0
  119. package/src/components/chat/{Sender → sender}/ThinkingStatus.tsx +0 -0
  120. package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/EventList.scss +0 -0
  121. package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/EventList.tsx +0 -0
  122. package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/gantt.ts +0 -0
  123. package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/git-graph.ts +0 -0
  124. package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/index.scss +0 -0
  125. package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/index.tsx +0 -0
  126. package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/mermaid.ts +0 -0
  127. package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/types.ts +0 -0
  128. package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/utils.ts +0 -0
  129. package/src/components/config/{recordEditors → record-editors}/BooleanRecordEditor.scss +0 -0
  130. package/src/components/config/{recordEditors → record-editors}/BooleanRecordEditor.tsx +0 -0
  131. package/src/components/config/{recordEditors → record-editors}/ChannelRecordEditor.scss +0 -0
  132. package/src/components/config/{recordEditors → record-editors}/ChannelRecordEditor.tsx +33 -33
  133. /package/src/components/config/{recordEditors → record-editors}/KeyValueEditor.scss +0 -0
  134. /package/src/components/config/{recordEditors → record-editors}/KeyValueEditor.tsx +0 -0
  135. /package/src/components/config/{recordEditors → record-editors}/McpServersRecordEditor.scss +0 -0
  136. /package/src/components/config/{recordEditors → record-editors}/McpServersRecordEditor.tsx +0 -0
  137. /package/src/components/config/{recordEditors → record-editors}/ModelServicesRecordEditor.scss +0 -0
  138. /package/src/components/config/{recordEditors → record-editors}/ModelServicesRecordEditor.tsx +0 -0
  139. /package/src/components/config/{recordEditors → record-editors}/RecordEditors.scss +0 -0
  140. /package/src/components/config/{recordEditors → record-editors}/RecordJsonEditor.scss +0 -0
  141. /package/src/components/config/{recordEditors → record-editors}/RecordJsonEditor.tsx +0 -0
  142. /package/src/components/config/{recordEditors → record-editors}/index.tsx +0 -0
@@ -1,4 +1,4 @@
1
- import './AutomationView.scss'
1
+ import './index.scss'
2
2
 
3
3
  import { App } from 'antd'
4
4
  import React, { useCallback, useEffect, useMemo, useState } from 'react'
@@ -10,6 +10,7 @@ import type { AutomationRule, AutomationRun } from '#~/api.js'
10
10
  import {
11
11
  createAutomationRule,
12
12
  deleteAutomationRule,
13
+ getApiErrorMessage,
13
14
  listAutomationRules,
14
15
  listAutomationRuns,
15
16
  runAutomationRule,
@@ -127,8 +128,8 @@ export function AutomationView() {
127
128
  void mutateRuns()
128
129
  setPanelMode('view')
129
130
  }
130
- } catch {
131
- void message.error(t('automation.saveFailed'))
131
+ } catch (error) {
132
+ void message.error(getApiErrorMessage(error, t('automation.saveFailed')))
132
133
  } finally {
133
134
  setSubmitting(false)
134
135
  }
@@ -143,8 +144,8 @@ export function AutomationView() {
143
144
  }
144
145
  void mutate()
145
146
  void message.success(t('automation.deleted'))
146
- } catch {
147
- void message.error(t('automation.deleteFailed'))
147
+ } catch (error) {
148
+ void message.error(getApiErrorMessage(error, t('automation.deleteFailed')))
148
149
  }
149
150
  }, [message, mutate, selectedRuleId, t])
150
151
 
@@ -157,8 +158,8 @@ export function AutomationView() {
157
158
  void mutateRuns()
158
159
  void navigate(`/session/${nextSessionId}`)
159
160
  }
160
- } catch {
161
- void message.error(t('automation.runFailed'))
161
+ } catch (error) {
162
+ void message.error(getApiErrorMessage(error, t('automation.runFailed')))
162
163
  }
163
164
  }, [message, mutateRuns, navigate, t])
164
165
 
@@ -166,8 +167,8 @@ export function AutomationView() {
166
167
  try {
167
168
  await updateAutomationRule(rule.id, { enabled })
168
169
  void mutate()
169
- } catch {
170
- void message.error(t('automation.toggleFailed'))
170
+ } catch (error) {
171
+ void message.error(getApiErrorMessage(error, t('automation.toggleFailed')))
171
172
  }
172
173
  }, [message, mutate, t])
173
174
 
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'
6
6
  import useSWR from 'swr'
7
7
 
8
8
  import {
9
+ getApiErrorMessage,
9
10
  getBenchmarkResult,
10
11
  getBenchmarkRun,
11
12
  listBenchmarkCases,
@@ -88,8 +89,8 @@ export function BenchmarkView() {
88
89
  const res = await startBenchmarkRun({ category: item.category, title: item.title })
89
90
  setActiveRunId(res.run.runId)
90
91
  void message.success(t('benchmark.runStarted'))
91
- } catch {
92
- void message.error(t('benchmark.runFailed'))
92
+ } catch (error) {
93
+ void message.error(getApiErrorMessage(error, t('benchmark.runFailed')))
93
94
  }
94
95
  }, [message, t])
95
96
 
@@ -98,8 +99,8 @@ export function BenchmarkView() {
98
99
  const res = await startBenchmarkRun({ category })
99
100
  setActiveRunId(res.run.runId)
100
101
  void message.success(t('benchmark.categoryRunStarted'))
101
- } catch {
102
- void message.error(t('benchmark.runFailed'))
102
+ } catch (error) {
103
+ void message.error(getApiErrorMessage(error, t('benchmark.runFailed')))
103
104
  }
104
105
  }, [message, t])
105
106
 
@@ -7,7 +7,7 @@ import { useAtomValue } from 'jotai'
7
7
  import { useEffect, useMemo, useRef, useState } from 'react'
8
8
  import { useTranslation } from 'react-i18next'
9
9
 
10
- import { deleteSession, updateSession } from '../../api'
10
+ import { deleteSession, getApiErrorMessage, updateSession } from '../../api'
11
11
  import { isSidebarCollapsedAtom, isSidebarResizingAtom } from '../../store/index'
12
12
  import { ConfigSectionPanel } from '../config'
13
13
  import type { FieldSpec } from '../config/configSchema'
@@ -68,7 +68,7 @@ export function ChatHeader({
68
68
  await updateSession(sessionId, { isStarred: !isStarred })
69
69
  void message.success(isStarred ? t('common.unstarred') : t('common.starred'))
70
70
  } catch (err) {
71
- void message.error(t('common.operationFailed'))
71
+ void message.error(getApiErrorMessage(err, t('common.operationFailed')))
72
72
  }
73
73
  }
74
74
 
@@ -78,7 +78,7 @@ export function ChatHeader({
78
78
  await updateSession(sessionId, { isArchived: !isArchived })
79
79
  void message.success(isArchived ? t('common.restored') : t('common.archived'))
80
80
  } catch (err) {
81
- void message.error(t('common.operationFailed'))
81
+ void message.error(getApiErrorMessage(err, t('common.operationFailed')))
82
82
  }
83
83
  }
84
84
 
@@ -257,8 +257,8 @@ export function SessionSettingsPanel({
257
257
  tags: Array.isArray(currentValue.tags) ? currentValue.tags : []
258
258
  })
259
259
  lastSavedRef.current = currentSerialized
260
- } catch {
261
- void message.error(t('common.operationFailed'))
260
+ } catch (err) {
261
+ void message.error(getApiErrorMessage(err, t('common.operationFailed')))
262
262
  } finally {
263
263
  savingRef.current = false
264
264
  }
@@ -292,7 +292,7 @@ export function SessionSettingsPanel({
292
292
  void message.success(t('common.deleteSuccess'))
293
293
  onClose()
294
294
  } catch (err) {
295
- void message.error(t('common.deleteFailed'))
295
+ void message.error(getApiErrorMessage(err, t('common.deleteFailed')))
296
296
  }
297
297
  }
298
298
  })
@@ -1,20 +1,22 @@
1
1
  import React, { useEffect, useMemo, useRef } from 'react'
2
+ import { useTranslation } from 'react-i18next'
2
3
 
4
+ import type { PermissionMode } from '#~/hooks/chat/use-chat-permission-mode'
5
+ import { useChatScroll } from '#~/hooks/chat/use-chat-scroll'
6
+ import { useChatSessionActions } from '#~/hooks/chat/use-chat-session-actions'
3
7
  import type { AskUserQuestionParams, ChatMessage, ChatMessageContent, Session, SessionInfo } from '@vibe-forge/core'
4
8
  import { CurrentTodoList } from './CurrentTodoList'
5
- import { MessageItem } from './Messages/MessageItem'
9
+ import { MessageItem } from './messages/MessageItem'
10
+ import { processMessages } from './messages/message-utils'
6
11
  import { NewSessionGuide } from './NewSessionGuide'
7
- import { Sender } from './Sender/Sender'
12
+ import { Sender } from './sender/Sender'
8
13
  import { ToolGroup } from './tools/core/ToolGroup'
9
- import { processMessages } from './Messages/message-utils'
10
- import type { PermissionMode } from '#~/hooks/chat/use-chat-permission-mode'
11
- import { useChatScroll } from '#~/hooks/chat/use-chat-scroll'
12
- import { useChatSessionActions } from '#~/hooks/chat/use-chat-session-actions'
13
14
 
14
15
  interface ModelSelectOption {
15
16
  value: string
16
17
  label: React.ReactNode
17
18
  searchText: string
19
+ displayLabel: string
18
20
  }
19
21
 
20
22
  interface ModelSelectGroup {
@@ -27,11 +29,12 @@ export function ChatHistoryView({
27
29
  messages,
28
30
  session,
29
31
  sessionInfo,
32
+ connectionError,
33
+ onRetryConnection,
30
34
  interactionRequest,
31
35
  onInteractionResponse,
36
+ setMessages,
32
37
  onClearMessages,
33
- onSend,
34
- onSendContent,
35
38
  placeholder,
36
39
  modelOptions,
37
40
  selectedModel,
@@ -40,6 +43,9 @@ export function ChatHistoryView({
40
43
  permissionMode,
41
44
  permissionModeOptions,
42
45
  onPermissionModeChange,
46
+ selectedAdapter,
47
+ adapterOptions,
48
+ onAdapterChange,
43
49
  modelUnavailable,
44
50
  hasAvailableModels
45
51
  }: {
@@ -47,11 +53,12 @@ export function ChatHistoryView({
47
53
  messages: ChatMessage[]
48
54
  session?: Session
49
55
  sessionInfo: SessionInfo | null
56
+ connectionError?: string | null
57
+ onRetryConnection: () => void
50
58
  interactionRequest: { id: string; payload: AskUserQuestionParams } | null
51
59
  onInteractionResponse: (id: string, data: string | string[]) => void
60
+ setMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>
52
61
  onClearMessages: () => void
53
- onSend: (text: string) => void
54
- onSendContent: (content: ChatMessageContent[]) => void
55
62
  placeholder?: string
56
63
  modelOptions: ModelSelectGroup[]
57
64
  selectedModel?: string
@@ -60,9 +67,13 @@ export function ChatHistoryView({
60
67
  permissionMode: PermissionMode
61
68
  permissionModeOptions: Array<{ value: PermissionMode; label: React.ReactNode }>
62
69
  onPermissionModeChange: (mode: PermissionMode) => void
70
+ selectedAdapter?: string
71
+ adapterOptions: Array<{ value: string; label: React.ReactNode }>
72
+ onAdapterChange: (adapter: string) => void
63
73
  modelUnavailable: boolean
64
74
  hasAvailableModels: boolean
65
75
  }) {
76
+ const { t } = useTranslation()
66
77
  const { messagesEndRef, messagesContainerRef, messagesContentRef, showScrollBottom, scrollToBottom } = useChatScroll({
67
78
  messagesLength: messages.length
68
79
  })
@@ -71,18 +82,53 @@ export function ChatHistoryView({
71
82
  modelForQuery,
72
83
  hasAvailableModels,
73
84
  permissionMode,
85
+ adapter: selectedAdapter,
74
86
  onClearMessages
75
87
  })
76
88
  const initialScrollDoneRef = useRef(false)
89
+ const buildUserMessage = (content: string | ChatMessageContent[]): ChatMessage => {
90
+ const id = globalThis.crypto?.randomUUID
91
+ ? globalThis.crypto.randomUUID()
92
+ : `local-${Date.now()}-${Math.random().toString(16).slice(2)}`
93
+ return {
94
+ id,
95
+ role: 'user' as const,
96
+ content,
97
+ createdAt: Date.now()
98
+ }
99
+ }
100
+
77
101
  const handleSend = async (text: string) => {
78
- await send(text)
79
- if (session?.id) {
80
- onSend(text)
102
+ if (!session?.id) {
103
+ const optimisticMessage = buildUserMessage(text)
104
+ setMessages((prev) => [...prev, optimisticMessage])
105
+ const didSend = await send(text)
106
+ if (!didSend) {
107
+ setMessages((prev) => prev.filter((message) => message.id !== optimisticMessage.id))
108
+ }
109
+ return
110
+ }
111
+
112
+ const didSend = await send(text)
113
+ if (didSend) {
114
+ setMessages((prev) => [...prev, buildUserMessage(text)])
81
115
  }
82
116
  }
83
117
  const handleSendContent = async (content: ChatMessageContent[]) => {
84
- await sendContent(content)
85
- onSendContent(content)
118
+ if (!session?.id) {
119
+ const optimisticMessage = buildUserMessage(content)
120
+ setMessages((prev) => [...prev, optimisticMessage])
121
+ const didSend = await sendContent(content)
122
+ if (!didSend) {
123
+ setMessages((prev) => prev.filter((message) => message.id !== optimisticMessage.id))
124
+ }
125
+ return
126
+ }
127
+
128
+ const didSend = await sendContent(content)
129
+ if (didSend) {
130
+ setMessages((prev) => [...prev, buildUserMessage(content)])
131
+ }
86
132
  }
87
133
  useEffect(() => {
88
134
  initialScrollDoneRef.current = false
@@ -104,6 +150,12 @@ export function ChatHistoryView({
104
150
  <>
105
151
  <div className={`chat-messages ${isReady ? 'ready' : ''}`} ref={messagesContainerRef}>
106
152
  <div className='chat-messages-content' ref={messagesContentRef}>
153
+ {!session?.id && isCreating && (
154
+ <div className='chat-pending-session-banner'>
155
+ <span className='material-symbols-rounded'>hourglass_top</span>
156
+ <span>{t('common.creatingChat')}</span>
157
+ </div>
158
+ )}
107
159
  {renderItems.map((item, index) => {
108
160
  if (item.type === 'message') {
109
161
  return (
@@ -134,7 +186,7 @@ export function ChatHistoryView({
134
186
  )}
135
187
  </div>
136
188
 
137
- {!session?.id && (
189
+ {!session?.id && messages.length === 0 && (
138
190
  <div className='new-session-guide-wrapper'>
139
191
  <NewSessionGuide />
140
192
  </div>
@@ -145,10 +197,13 @@ export function ChatHistoryView({
145
197
  <Sender
146
198
  onSend={handleSend}
147
199
  onSendContent={handleSendContent}
200
+ adapterLocked={session?.id != null}
148
201
  sessionStatus={isCreating ? 'running' : session?.status}
149
202
  onInterrupt={interrupt}
150
203
  onClear={clearMessages}
151
204
  sessionInfo={sessionInfo}
205
+ connectionError={connectionError}
206
+ onRetryConnection={onRetryConnection}
152
207
  interactionRequest={interactionRequest}
153
208
  onInteractionResponse={onInteractionResponse}
154
209
  placeholder={placeholder}
@@ -158,6 +213,9 @@ export function ChatHistoryView({
158
213
  permissionMode={permissionMode}
159
214
  permissionModeOptions={permissionModeOptions}
160
215
  onPermissionModeChange={onPermissionModeChange}
216
+ selectedAdapter={selectedAdapter}
217
+ adapterOptions={adapterOptions}
218
+ onAdapterChange={onAdapterChange}
161
219
  modelUnavailable={modelUnavailable}
162
220
  />
163
221
  </div>
@@ -4,9 +4,9 @@ import React from 'react'
4
4
 
5
5
  import type { ChatMessage } from '@vibe-forge/core'
6
6
 
7
- import { SessionTimelinePanel } from './SessionTimelinePanel'
8
- import { SessionTimelineEventList } from './SessionTimelinePanel/EventList'
9
- import type { Task } from './SessionTimelinePanel/types'
7
+ import { SessionTimelinePanel } from './session-timeline-panel'
8
+ import { SessionTimelineEventList } from './session-timeline-panel/EventList'
9
+ import type { Task } from './session-timeline-panel/types'
10
10
 
11
11
  const mockTimelineTask: Task = {
12
12
  startTime: '09:40:00',
@@ -1,4 +1,22 @@
1
1
  .current-todo-container {
2
+ --todo-panel-bg: var(--bg-color);
3
+ --todo-panel-muted-bg: var(--tag-bg);
4
+ --todo-panel-border: var(--border-color);
5
+ --todo-panel-hover-border: var(--primary-color);
6
+ --todo-panel-shadow: 0 2px 4px -1px rgba(0, 0, 0, .06);
7
+ --todo-overlay-shadow: 0 4px 12px rgba(0, 0, 0, .15);
8
+ --todo-track-bg: var(--tag-hover-bg);
9
+ --todo-text: var(--text-color);
10
+ --todo-sub-text: var(--sub-text-color);
11
+ --todo-pending-icon: var(--placeholder-color);
12
+ --todo-hover-bg: var(--tag-bg);
13
+ --todo-priority-high-bg: #fee2e2;
14
+ --todo-priority-high-text: #ef4444;
15
+ --todo-priority-medium-bg: #fef3c7;
16
+ --todo-priority-medium-text: #d97706;
17
+ --todo-priority-low-bg: #d1fae5;
18
+ --todo-priority-low-text: #10b981;
19
+
2
20
  position: relative;
3
21
  margin: 12px 24px 0 24px;
4
22
  z-index: 10;
@@ -9,31 +27,31 @@
9
27
  opacity: .6;
10
28
  .todo-progress-bar {
11
29
  cursor: default;
12
- background: #f9fafb;
30
+ background: var(--todo-panel-muted-bg);
13
31
  box-shadow: none;
14
32
  &:hover {
15
- border-color: #e5e7eb;
33
+ border-color: var(--todo-panel-border);
16
34
  }
17
35
  .progress-info .material-symbols-rounded {
18
- color: #9ca3af;
36
+ color: var(--todo-pending-icon);
19
37
  }
20
38
  }
21
39
  }
22
40
 
23
41
  .todo-progress-bar {
24
- background: white;
25
- border: 1px solid #e5e7eb;
42
+ background: var(--todo-panel-bg);
43
+ border: 1px solid var(--todo-panel-border);
26
44
  border-radius: 8px;
27
45
  padding: 8px 16px;
28
46
  display: flex;
29
47
  align-items: center;
30
48
  gap: 12px;
31
- box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .06);
49
+ box-shadow: var(--todo-panel-shadow);
32
50
  cursor: pointer;
33
51
  transition: all .2s ease;
34
52
 
35
53
  &:hover {
36
- border-color: #3b82f6;
54
+ border-color: var(--todo-panel-hover-border);
37
55
  }
38
56
 
39
57
  .progress-info {
@@ -44,26 +62,26 @@
44
62
 
45
63
  .material-symbols-rounded {
46
64
  font-size: 20px;
47
- color: #3b82f6;
65
+ color: var(--primary-color);
48
66
  }
49
67
 
50
68
  .text {
51
69
  font-size: 13px;
52
70
  font-weight: 500;
53
- color: #374151;
71
+ color: var(--todo-text);
54
72
  }
55
73
  }
56
74
 
57
75
  .progress-track {
58
76
  flex: 1;
59
77
  height: 6px;
60
- background: #f3f4f6;
78
+ background: var(--todo-track-bg);
61
79
  border-radius: 3px;
62
80
  overflow: hidden;
63
81
 
64
82
  .progress-fill {
65
83
  height: 100%;
66
- background: #3b82f6;
84
+ background: var(--primary-color);
67
85
  transition: width .3s ease;
68
86
  }
69
87
  }
@@ -73,12 +91,12 @@
73
91
  border: none;
74
92
  padding: 0;
75
93
  cursor: pointer;
76
- color: #9ca3af;
94
+ color: var(--todo-pending-icon);
77
95
  display: flex;
78
96
  align-items: center;
79
97
 
80
98
  &:hover {
81
- color: #374151;
99
+ color: var(--todo-text);
82
100
  }
83
101
  }
84
102
  }
@@ -88,10 +106,10 @@
88
106
  bottom: calc(100% + 8px);
89
107
  left: 0;
90
108
  right: 0;
91
- background: white;
92
- border: 1px solid #e5e7eb;
109
+ background: var(--todo-panel-bg);
110
+ border: 1px solid var(--todo-panel-border);
93
111
  border-radius: 8px;
94
- box-shadow: 0 4px 12px rgba(0, 0, 0, .15);
112
+ box-shadow: var(--todo-overlay-shadow);
95
113
  animation: slideUp .3s cubic-bezier(.4, 0, .2, 1);
96
114
  overflow: hidden;
97
115
  z-index: 100;
@@ -109,7 +127,7 @@
109
127
  transition: background-color .2s;
110
128
 
111
129
  &:hover {
112
- background-color: #f9fafb;
130
+ background-color: var(--todo-hover-bg);
113
131
  }
114
132
 
115
133
  .icon {
@@ -130,21 +148,21 @@
130
148
 
131
149
  &.status-in_progress {
132
150
  .icon {
133
- color: #3b82f6;
151
+ color: var(--primary-color);
134
152
  animation: spin 2s linear infinite;
135
153
  }
136
154
  .text {
137
- color: #111827;
155
+ color: var(--todo-text);
138
156
  font-weight: 500;
139
157
  }
140
158
  }
141
159
 
142
160
  &.status-pending {
143
161
  .icon {
144
- color: #9ca3af;
162
+ color: var(--todo-pending-icon);
145
163
  }
146
164
  .text {
147
- color: #4b5563;
165
+ color: var(--todo-sub-text);
148
166
  }
149
167
  }
150
168
 
@@ -167,16 +185,16 @@
167
185
  font-weight: 600;
168
186
 
169
187
  &.high {
170
- background: #fee2e2;
171
- color: #ef4444;
188
+ background: var(--todo-priority-high-bg);
189
+ color: var(--todo-priority-high-text);
172
190
  }
173
191
  &.medium {
174
- background: #fef3c7;
175
- color: #d97706;
192
+ background: var(--todo-priority-medium-bg);
193
+ color: var(--todo-priority-medium-text);
176
194
  }
177
195
  &.low {
178
- background: #d1fae5;
179
- color: #10b981;
196
+ background: var(--todo-priority-low-bg);
197
+ color: var(--todo-priority-low-text);
180
198
  }
181
199
  }
182
200
  }
@@ -185,6 +203,17 @@
185
203
  }
186
204
  }
187
205
 
206
+ html.dark .current-todo-container {
207
+ --todo-panel-shadow: 0 8px 24px rgba(0, 0, 0, .24);
208
+ --todo-overlay-shadow: 0 16px 36px rgba(0, 0, 0, .32);
209
+ --todo-priority-high-bg: rgba(248, 113, 113, .16);
210
+ --todo-priority-high-text: #fca5a5;
211
+ --todo-priority-medium-bg: rgba(245, 158, 11, .16);
212
+ --todo-priority-medium-text: #fcd34d;
213
+ --todo-priority-low-bg: rgba(45, 212, 191, .16);
214
+ --todo-priority-low-text: #5eead4;
215
+ }
216
+
188
217
  @keyframes slideDown {
189
218
  from {
190
219
  opacity: 0;