@vibe-forge/client 0.3.0 → 0.5.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 (163) hide show
  1. package/cli.cjs +2 -1
  2. package/dist/assets/{arc-CwMXUVsq.js → arc-C4ymrcSQ.js} +1 -1
  3. package/dist/assets/{blockDiagram-c4efeb88-CGxJV7KJ.js → blockDiagram-c4efeb88-CeB7-kgP.js} +1 -1
  4. package/dist/assets/{c4Diagram-c83219d4-BKhin7cY.js → c4Diagram-c83219d4-C935Im8S.js} +1 -1
  5. package/dist/assets/channel-84s1ACzD.js +1 -0
  6. package/dist/assets/{classDiagram-beda092f-BASmn22R.js → classDiagram-beda092f-B9IV13KI.js} +1 -1
  7. package/dist/assets/{classDiagram-v2-2358418a-BUk9rNBX.js → classDiagram-v2-2358418a-CXF_K4fE.js} +1 -1
  8. package/dist/assets/clone-B2E8tddE.js +1 -0
  9. package/dist/assets/{createText-1719965b-2XqnWjQY.js → createText-1719965b-DwX8iC5F.js} +1 -1
  10. package/dist/assets/devicon-BWlTeAUU.woff +0 -0
  11. package/dist/assets/devicon-CirD-cQx.ttf +0 -0
  12. package/dist/assets/devicon-Dg8iWy0i.svg +1211 -0
  13. package/dist/assets/devicon-TqfHp33-.eot +0 -0
  14. package/dist/assets/{edges-96097737-B7e32Jeg.js → edges-96097737-9P1uH1RE.js} +1 -1
  15. package/dist/assets/{erDiagram-0228fc6a-CCR2or72.js → erDiagram-0228fc6a-ixeGTFvg.js} +1 -1
  16. package/dist/assets/{flowDb-c6c81e3f-B72HWT9x.js → flowDb-c6c81e3f-G1gSTTBI.js} +1 -1
  17. package/dist/assets/{flowDiagram-50d868cf-WOi0KARY.js → flowDiagram-50d868cf-CzrG99nD.js} +1 -1
  18. package/dist/assets/flowDiagram-v2-4f6560a1-CJfJYbME.js +1 -0
  19. package/dist/assets/{flowchart-elk-definition-6af322e1-i_Yd0LCE.js → flowchart-elk-definition-6af322e1-sFCoysWa.js} +1 -1
  20. package/dist/assets/{ganttDiagram-a2739b55-CFH9zF14.js → ganttDiagram-a2739b55-Ccsk_Lru.js} +1 -1
  21. package/dist/assets/{gitGraphDiagram-82fe8481-DglKfMze.js → gitGraphDiagram-82fe8481-CwathJ6H.js} +1 -1
  22. package/dist/assets/{graph-BKbBNGPf.js → graph-DRCU-8Rz.js} +1 -1
  23. package/dist/assets/{index-5325376f-BK7F9nSl.js → index-5325376f-Bq-fg2i_.js} +1 -1
  24. package/dist/assets/index-CHMuZ5-1.css +1 -0
  25. package/dist/assets/index-cGZvDhhU.js +542 -0
  26. package/dist/assets/{infoDiagram-8eee0895-BLFL77_D.js → infoDiagram-8eee0895-JBcUkJ6T.js} +1 -1
  27. package/dist/assets/{journeyDiagram-c64418c1-CS9XctDL.js → journeyDiagram-c64418c1-DsdQU-R8.js} +1 -1
  28. package/dist/assets/{layout-By3JZZGt.js → layout-s0slG1OL.js} +1 -1
  29. package/dist/assets/{line-9GUsXbwv.js → line-CymFqgW6.js} +1 -1
  30. package/dist/assets/{linear-DzGV4E9N.js → linear-lDQVZ6aQ.js} +1 -1
  31. package/dist/assets/{mermaid.core-CG3Ib42Q.js → mermaid.core-Cmlqga_E.js} +6 -6
  32. package/dist/assets/{mindmap-definition-8da855dc-WQ3LPKJU.js → mindmap-definition-8da855dc-CqqTDJn_.js} +1 -1
  33. package/dist/assets/{pieDiagram-a8764435-DHVIUZiN.js → pieDiagram-a8764435-BL2Ajx7Z.js} +1 -1
  34. package/dist/assets/{quadrantDiagram-1e28029f-C3G9Ye8-.js → quadrantDiagram-1e28029f-ClL_3ASt.js} +1 -1
  35. package/dist/assets/{requirementDiagram-08caed73-C9ES1D5G.js → requirementDiagram-08caed73-CB1RgE3K.js} +1 -1
  36. package/dist/assets/{sankeyDiagram-a04cb91d-B4BKXclQ.js → sankeyDiagram-a04cb91d-tgleEYiD.js} +1 -1
  37. package/dist/assets/{sequenceDiagram-c5b8d532-DrgEb25G.js → sequenceDiagram-c5b8d532-DlatQT5R.js} +1 -1
  38. package/dist/assets/{stateDiagram-1ecb1508-CF1XWARJ.js → stateDiagram-1ecb1508-B--MLqRs.js} +1 -1
  39. package/dist/assets/{stateDiagram-v2-c2b004d7-IO3i3yXv.js → stateDiagram-v2-c2b004d7-CRMZ6Dpx.js} +1 -1
  40. package/dist/assets/{styles-b4e223ce-DACN9aSc.js → styles-b4e223ce-CPiYHfUz.js} +1 -1
  41. package/dist/assets/{styles-ca3715f6-bekm2WLP.js → styles-ca3715f6-B9UKPAzX.js} +1 -1
  42. package/dist/assets/{styles-d45a18b0-OzTDVBb8.js → styles-d45a18b0-BC1Ak1So.js} +1 -1
  43. package/dist/assets/{svgDrawCommon-b86b1483-BWroJerr.js → svgDrawCommon-b86b1483-DV8R0g-n.js} +1 -1
  44. package/dist/assets/{timeline-definition-faaaa080-CCfRNigO.js → timeline-definition-faaaa080-CiqGS5DC.js} +1 -1
  45. package/dist/assets/{xychartDiagram-f5964ef8-C3cbfVqN.js → xychartDiagram-f5964ef8-h6VSD3GE.js} +1 -1
  46. package/dist/index.html +2 -7
  47. package/index.html +0 -5
  48. package/package.json +12 -6
  49. package/src/App.tsx +2 -0
  50. package/src/api/README.md +26 -0
  51. package/src/api/automation.ts +88 -0
  52. package/src/api/base.ts +54 -0
  53. package/src/api/benchmark.ts +45 -0
  54. package/src/api/config.ts +24 -0
  55. package/src/api/knowledge.ts +72 -0
  56. package/src/api/projects.ts +15 -0
  57. package/src/api/sessions.ts +84 -0
  58. package/src/api/types.ts +20 -0
  59. package/src/api.ts +44 -269
  60. package/src/components/AutomationView/AutomationView.scss +5 -1
  61. package/src/components/AutomationView/RuleFormPanel.tsx +3 -2
  62. package/src/components/AutomationView/TaskList.scss +4 -6
  63. package/src/components/AutomationView/TaskList.tsx +2 -1
  64. package/src/components/AutomationView/TriggerList.scss +4 -1
  65. package/src/components/BenchmarkView/BenchmarkCasePanel.scss +267 -0
  66. package/src/components/BenchmarkView/BenchmarkCasePanel.tsx +309 -0
  67. package/src/components/BenchmarkView/BenchmarkSidebar.scss +182 -0
  68. package/src/components/BenchmarkView/BenchmarkSidebar.tsx +262 -0
  69. package/src/components/BenchmarkView/BenchmarkView.scss +78 -0
  70. package/src/components/BenchmarkView/index.tsx +197 -0
  71. package/src/components/BenchmarkView/types.ts +10 -0
  72. package/src/components/BenchmarkView/utils.ts +21 -0
  73. package/src/components/Chat.tsx +43 -29
  74. package/src/components/{chat/CodeBlock.tsx → CodeBlock.tsx} +3 -1
  75. package/src/components/ConfigView.tsx +32 -25
  76. package/src/components/{chat/MarkdownContent.tsx → MarkdownContent.tsx} +1 -1
  77. package/src/components/NavRail.tsx +7 -0
  78. package/src/components/chat/ChatHeader.scss +37 -19
  79. package/src/components/chat/ChatHeader.tsx +6 -9
  80. package/src/components/chat/ChatHistoryView.tsx +99 -45
  81. package/src/components/chat/CurrentTodoList.tsx +10 -9
  82. package/src/components/chat/{MessageItem.scss → Messages/MessageItem.scss} +14 -0
  83. package/src/components/chat/{MessageItem.tsx → Messages/MessageItem.tsx} +30 -8
  84. package/src/components/chat/{messageUtils.ts → Messages/message-utils.ts} +1 -1
  85. package/src/components/chat/{Sender.scss → Sender/Sender.scss} +146 -3
  86. package/src/components/chat/{Sender.tsx → Sender/Sender.tsx} +183 -5
  87. package/src/components/chat/tools/DefaultTool.tsx +184 -21
  88. package/src/components/chat/tools/adapter-claude/BashTool.scss +67 -51
  89. package/src/components/chat/tools/adapter-claude/BashTool.tsx +83 -49
  90. package/src/components/chat/tools/adapter-claude/GlobTool.scss +0 -79
  91. package/src/components/chat/tools/adapter-claude/GlobTool.tsx +16 -36
  92. package/src/components/chat/tools/adapter-claude/GrepTool.scss +0 -87
  93. package/src/components/chat/tools/adapter-claude/GrepTool.tsx +22 -41
  94. package/src/components/chat/tools/adapter-claude/LSTool.scss +0 -79
  95. package/src/components/chat/tools/adapter-claude/LSTool.tsx +15 -15
  96. package/src/components/chat/tools/adapter-claude/ReadTool.scss +0 -55
  97. package/src/components/chat/tools/adapter-claude/ReadTool.tsx +20 -42
  98. package/src/components/chat/tools/adapter-claude/TodoTool.scss +8 -23
  99. package/src/components/chat/tools/adapter-claude/TodoTool.tsx +24 -11
  100. package/src/components/chat/tools/adapter-claude/WriteTool.scss +21 -69
  101. package/src/components/chat/tools/adapter-claude/WriteTool.tsx +22 -58
  102. package/src/components/chat/tools/adapter-claude/index.ts +4 -10
  103. package/src/components/chat/tools/adapter-claude/utils.ts +54 -0
  104. package/src/components/chat/tools/core/ToolCallBox.scss +356 -0
  105. package/src/components/chat/{ToolGroup.tsx → tools/core/ToolGroup.tsx} +26 -7
  106. package/src/components/chat/{ToolRenderer.tsx → tools/core/ToolRenderer.tsx} +6 -4
  107. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.scss +11 -0
  108. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +75 -0
  109. package/src/components/chat/tools/plugin-chrome-devtools/index.ts +45 -0
  110. package/src/components/chat/tools/task/GetTaskInfoTool.scss +2 -27
  111. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +48 -38
  112. package/src/components/chat/tools/task/ListTasksTool.scss +3 -28
  113. package/src/components/chat/tools/task/ListTasksTool.tsx +11 -8
  114. package/src/components/chat/tools/task/StartTasksTool.scss +3 -28
  115. package/src/components/chat/tools/task/StartTasksTool.tsx +14 -17
  116. package/src/components/chat/tools/task/components/TaskRow.scss +105 -0
  117. package/src/components/chat/tools/task/components/TaskRow.tsx +163 -0
  118. package/src/components/chat/tools/task/components/TaskToolCard.scss +15 -15
  119. package/src/components/chat/tools/task/components/TaskToolCard.tsx +8 -6
  120. package/src/components/config/ConfigSectionForm.tsx +12 -1
  121. package/src/components/config/ConfigSourceSwitch.tsx +12 -34
  122. package/src/components/config/channelDefinitions.ts +6 -0
  123. package/src/components/config/configSchema.ts +10 -1
  124. package/src/components/config/recordEditors/ChannelRecordEditor.scss +1 -0
  125. package/src/components/config/recordEditors/ChannelRecordEditor.tsx +397 -0
  126. package/src/components/config/recordEditors/index.tsx +1 -0
  127. package/src/components/knowledge-base/components/RuleItem.tsx +1 -1
  128. package/src/components/knowledge-base/components/SpecItem.tsx +1 -1
  129. package/src/components/sidebar/SessionItem.scss +17 -0
  130. package/src/components/sidebar/SessionItem.tsx +21 -13
  131. package/src/hooks/chat/use-chat-adapter.ts +81 -0
  132. package/src/hooks/chat/use-chat-interaction.ts +26 -0
  133. package/src/{components/chat/useChatModels.tsx → hooks/chat/use-chat-models.tsx} +117 -22
  134. package/src/hooks/chat/use-chat-permission-mode.ts +47 -0
  135. package/src/hooks/chat/use-chat-scroll.ts +51 -0
  136. package/src/hooks/chat/use-chat-session-actions.ts +153 -0
  137. package/src/hooks/chat/use-chat-session-messages.ts +262 -0
  138. package/src/hooks/chat/use-chat-session.ts +63 -0
  139. package/src/hooks/chat/use-chat-view.ts +39 -0
  140. package/src/main.tsx +10 -13
  141. package/src/resources/adapters.ts +20 -0
  142. package/src/resources/locales/en.json +66 -0
  143. package/src/resources/locales/zh.json +66 -0
  144. package/src/runtime-config.ts +52 -0
  145. package/src/vite-env.d.ts +11 -0
  146. package/src/ws.ts +5 -3
  147. package/vite.config.ts +12 -4
  148. package/dist/assets/channel-jbCEHqbG.js +0 -1
  149. package/dist/assets/clone-CCRKqS4L.js +0 -1
  150. package/dist/assets/flowDiagram-v2-4f6560a1-Baslbgn4.js +0 -1
  151. package/dist/assets/index-B0qfCb1G.css +0 -1
  152. package/dist/assets/index-CNo75dYr.js +0 -497
  153. package/src/components/chat/ToolCallBox.scss +0 -137
  154. package/src/components/chat/useChatSession.ts +0 -370
  155. /package/src/components/{chat/CodeBlock.scss → CodeBlock.scss} +0 -0
  156. /package/src/components/chat/{MessageFooter.tsx → Messages/MessageFooter.tsx} +0 -0
  157. /package/src/components/chat/{CompletionMenu.scss → Sender/CompletionMenu.scss} +0 -0
  158. /package/src/components/chat/{CompletionMenu.tsx → Sender/CompletionMenu.tsx} +0 -0
  159. /package/src/components/chat/{ThinkingStatus.scss → Sender/ThinkingStatus.scss} +0 -0
  160. /package/src/components/chat/{ThinkingStatus.tsx → Sender/ThinkingStatus.tsx} +0 -0
  161. /package/src/components/chat/{ToolCallBox.tsx → tools/core/ToolCallBox.tsx} +0 -0
  162. /package/src/components/chat/{ToolGroup.scss → tools/core/ToolGroup.scss} +0 -0
  163. /package/src/{components/chat/safeSerialize.ts → utils/safe-serialize.ts} +0 -0
@@ -0,0 +1,262 @@
1
+ import { App } from 'antd'
2
+ import { useEffect, useRef, useState } from 'react'
3
+ import { useSWRConfig } from 'swr'
4
+
5
+ import { getSessionMessages } from '#~/api.js'
6
+ import { connectionManager } from '#~/connectionManager.js'
7
+ import type { AskUserQuestionParams, ChatMessage, Session, SessionInfo, WSEvent } from '@vibe-forge/core'
8
+ import type { PermissionMode } from './use-chat-permission-mode'
9
+
10
+ const applyMessageEvent = (currentMessages: ChatMessage[], data: WSEvent) => {
11
+ if (data.type !== 'message') return currentMessages
12
+ const exists = currentMessages.find((msg) => msg.id === data.message.id)
13
+ if (exists != null) {
14
+ return currentMessages.map((msg) => (msg.id === data.message.id ? data.message : msg))
15
+ }
16
+ return [...currentMessages, data.message]
17
+ }
18
+
19
+ const applyToolResultEvent = (currentMessages: ChatMessage[], data: WSEvent): ChatMessage[] => {
20
+ if (data.type !== 'tool_result') return currentMessages
21
+ const status = data.isError === true ? 'error' : 'success'
22
+ return currentMessages.map((msg) => {
23
+ if (msg.toolCall != null && msg.toolCall.id === data.toolCallId) {
24
+ return {
25
+ ...msg,
26
+ toolCall: {
27
+ ...msg.toolCall,
28
+ status,
29
+ output: data.output
30
+ }
31
+ }
32
+ }
33
+ return msg
34
+ })
35
+ }
36
+
37
+ export function useChatSessionMessages({
38
+ session,
39
+ modelForQuery,
40
+ permissionMode,
41
+ adapter,
42
+ setInteractionRequest
43
+ }: {
44
+ session?: Session
45
+ modelForQuery?: string
46
+ permissionMode: PermissionMode
47
+ adapter?: string
48
+ setInteractionRequest: (value: { id: string; payload: AskUserQuestionParams } | null) => void
49
+ }) {
50
+ const { message } = App.useApp()
51
+ const { mutate } = useSWRConfig()
52
+ const [messages, setMessages] = useState<ChatMessage[]>([])
53
+ const [sessionInfo, setSessionInfo] = useState<SessionInfo | null>(null)
54
+ const [isReady, setIsReady] = useState(false)
55
+ const isInitialLoadRef = useRef<boolean>(true)
56
+ const lastConnectedModelRef = useRef<string | undefined>(undefined)
57
+ const lastConnectedPermissionModeRef = useRef<string | undefined>(undefined)
58
+ const lastConnectedAdapterRef = useRef<string | undefined>(undefined)
59
+
60
+ useEffect(() => {
61
+ setMessages([])
62
+ setSessionInfo(null)
63
+ setIsReady(false)
64
+ setInteractionRequest(null)
65
+ isInitialLoadRef.current = true
66
+
67
+ if (session?.id == null || session.id === '') {
68
+ setIsReady(true)
69
+ lastConnectedModelRef.current = undefined
70
+ lastConnectedPermissionModeRef.current = undefined
71
+ return
72
+ }
73
+
74
+ let isDisposed = false
75
+
76
+ const fetchHistory = async () => {
77
+ try {
78
+ const res = await getSessionMessages(session.id)
79
+ if (isDisposed) return
80
+ const events = res.messages as WSEvent[]
81
+
82
+ if (res.session) {
83
+ const updatedSession = res.session
84
+ void mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
85
+ if (prev?.sessions == null) return prev
86
+ const newSessions = prev.sessions.map((s: Session) =>
87
+ s.id === updatedSession.id ? { ...s, ...updatedSession } : s
88
+ )
89
+ return { ...prev, sessions: newSessions }
90
+ }, false)
91
+ }
92
+
93
+ if (res.interaction) {
94
+ setInteractionRequest(res.interaction)
95
+ }
96
+
97
+ let currentMessages: ChatMessage[] = []
98
+ let currentSessionInfo: SessionInfo | null = null
99
+
100
+ for (const data of events) {
101
+ currentMessages = applyMessageEvent(currentMessages, data)
102
+ currentMessages = applyToolResultEvent(currentMessages, data)
103
+ if (data.type === 'session_info') {
104
+ if (data.info != null && data.info.type !== 'summary') {
105
+ currentSessionInfo = data.info
106
+ }
107
+ }
108
+ }
109
+
110
+ setMessages(currentMessages)
111
+ setSessionInfo(currentSessionInfo)
112
+
113
+ setTimeout(() => {
114
+ if (isDisposed) return
115
+ setIsReady(true)
116
+ isInitialLoadRef.current = false
117
+ }, 100)
118
+ } catch (err) {
119
+ console.error('Failed to fetch history messages:', err)
120
+ }
121
+ }
122
+
123
+ void fetchHistory()
124
+
125
+ return () => {
126
+ isDisposed = true
127
+ }
128
+ }, [mutate, session?.id, setInteractionRequest])
129
+
130
+ useEffect(() => {
131
+ if (session?.id == null || session.id === '') {
132
+ return
133
+ }
134
+
135
+ let isDisposed = false
136
+ let cleanup: (() => void) | undefined
137
+ const normalizedModel = modelForQuery ?? ''
138
+ const modelChanged = modelForQuery != null &&
139
+ lastConnectedModelRef.current != null &&
140
+ normalizedModel !== lastConnectedModelRef.current &&
141
+ session?.status !== 'running'
142
+ const normalizedPermissionMode = permissionMode ?? ''
143
+ const permissionModeChanged = permissionMode != null &&
144
+ lastConnectedPermissionModeRef.current != null &&
145
+ normalizedPermissionMode !== lastConnectedPermissionModeRef.current &&
146
+ session?.status !== 'running'
147
+ const normalizedAdapter = adapter ?? ''
148
+ const adapterChanged = adapter != null &&
149
+ lastConnectedAdapterRef.current != null &&
150
+ normalizedAdapter !== lastConnectedAdapterRef.current &&
151
+ session?.status !== 'running'
152
+ if (modelChanged || permissionModeChanged || adapterChanged) {
153
+ connectionManager.send(session.id, { type: 'terminate_session' })
154
+ connectionManager.close(session.id)
155
+ }
156
+ lastConnectedModelRef.current = normalizedModel
157
+ lastConnectedPermissionModeRef.current = normalizedPermissionMode
158
+ lastConnectedAdapterRef.current = normalizedAdapter
159
+
160
+ const timer = setTimeout(() => {
161
+ if (isDisposed) return
162
+
163
+ const connectionParams: Record<string, string> = {}
164
+ if (modelForQuery) {
165
+ connectionParams.model = modelForQuery
166
+ }
167
+ if (permissionMode) {
168
+ connectionParams.permissionMode = permissionMode
169
+ }
170
+ if (adapter) {
171
+ connectionParams.adapter = adapter
172
+ }
173
+
174
+ cleanup = connectionManager.connect(session.id, {
175
+ onOpen() {
176
+ },
177
+ onMessage(data: WSEvent) {
178
+ if (isDisposed) return
179
+ if (data.type === 'error') {
180
+ void message.error(data.message)
181
+ return
182
+ }
183
+
184
+ if (data.type === 'session_updated') {
185
+ void mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
186
+ if (prev?.sessions == null) return prev
187
+ const updatedSession = data.session as Session | { id: string; isDeleted: boolean }
188
+
189
+ if ('isDeleted' in updatedSession && updatedSession.isDeleted) {
190
+ return {
191
+ ...prev,
192
+ sessions: prev.sessions.filter((s: Session) => s.id !== updatedSession.id)
193
+ }
194
+ }
195
+
196
+ const typedUpdatedSession = updatedSession as Session
197
+ const newSessions = prev.sessions.map((s: Session) =>
198
+ s.id === typedUpdatedSession.id ? { ...s, ...typedUpdatedSession } : s
199
+ )
200
+
201
+ if (
202
+ !newSessions.some((s: Session) => s.id === typedUpdatedSession.id) && !('isDeleted' in updatedSession)
203
+ ) {
204
+ newSessions.unshift(typedUpdatedSession)
205
+ }
206
+
207
+ return { ...prev, sessions: newSessions }
208
+ }, false)
209
+ return
210
+ }
211
+
212
+ if (data.type === 'message') {
213
+ setMessages((current) => applyMessageEvent(current, data))
214
+ return
215
+ }
216
+
217
+ if (data.type === 'session_info') {
218
+ if (data.info != null && data.info.type === 'summary') {
219
+ void mutate('/api/sessions')
220
+ } else {
221
+ setSessionInfo(data.info ?? null)
222
+ if (isInitialLoadRef.current) {
223
+ setTimeout(() => {
224
+ if (isDisposed) return
225
+ if (isInitialLoadRef.current) {
226
+ setIsReady(true)
227
+ isInitialLoadRef.current = false
228
+ }
229
+ }, 100)
230
+ }
231
+ }
232
+ return
233
+ }
234
+
235
+ if (data.type === 'tool_result') {
236
+ setMessages((current) => applyToolResultEvent(current, data))
237
+ return
238
+ }
239
+
240
+ if (data.type === 'interaction_request') {
241
+ setInteractionRequest({ id: data.id, payload: data.payload })
242
+ }
243
+ },
244
+ onClose() {
245
+ }
246
+ }, Object.keys(connectionParams).length > 0 ? connectionParams : undefined)
247
+ }, modelChanged ? 200 : 100)
248
+
249
+ return () => {
250
+ isDisposed = true
251
+ clearTimeout(timer)
252
+ cleanup?.()
253
+ }
254
+ }, [adapter, message, modelForQuery, mutate, permissionMode, session?.id, session?.status, setInteractionRequest])
255
+
256
+ return {
257
+ messages,
258
+ setMessages,
259
+ sessionInfo,
260
+ isReady
261
+ }
262
+ }
@@ -0,0 +1,63 @@
1
+ import { useTranslation } from 'react-i18next'
2
+
3
+ import type { Session } from '@vibe-forge/core'
4
+ import { useChatAdapter } from './use-chat-adapter'
5
+ import { useChatInteraction } from './use-chat-interaction'
6
+ import { useChatModels } from './use-chat-models'
7
+ import { useChatPermissionMode } from './use-chat-permission-mode'
8
+ import { useChatSessionMessages } from './use-chat-session-messages'
9
+ import { useChatView } from './use-chat-view'
10
+
11
+ export function useChatSession({
12
+ session
13
+ }: {
14
+ session?: Session
15
+ }) {
16
+ const { t } = useTranslation()
17
+ const { selectedAdapter, setSelectedAdapter, adapterOptions } = useChatAdapter()
18
+ const {
19
+ selectedModel,
20
+ selectedModelWithService,
21
+ setSelectedModel,
22
+ modelOptions,
23
+ hasAvailableModels
24
+ } = useChatModels({ selectedAdapter })
25
+ const { permissionMode, setPermissionMode, permissionModeOptions } = useChatPermissionMode()
26
+ const { activeView, setActiveView } = useChatView()
27
+ const { interactionRequest, setInteractionRequest, handleInteractionResponse } = useChatInteraction({
28
+ sessionId: session?.id
29
+ })
30
+ const { messages, setMessages, sessionInfo, isReady } = useChatSessionMessages({
31
+ session,
32
+ modelForQuery: selectedModelWithService,
33
+ permissionMode,
34
+ adapter: selectedAdapter,
35
+ setInteractionRequest
36
+ })
37
+ const isThinking = session?.status === 'running'
38
+
39
+ return {
40
+ messages,
41
+ sessionInfo,
42
+ interactionRequest,
43
+ isReady,
44
+ isThinking,
45
+ activeView,
46
+ setActiveView,
47
+ handleInteractionResponse,
48
+ setMessages,
49
+ placeholder: !session?.id ? t('chat.newSessionPlaceholder') : undefined,
50
+ modelOptions,
51
+ selectedModel,
52
+ modelForQuery: selectedModelWithService,
53
+ setSelectedModel,
54
+ permissionMode,
55
+ setPermissionMode,
56
+ permissionModeOptions,
57
+ selectedAdapter,
58
+ setSelectedAdapter,
59
+ adapterOptions,
60
+ hasAvailableModels,
61
+ modelUnavailable: !hasAvailableModels
62
+ }
63
+ }
@@ -0,0 +1,39 @@
1
+ import { useCallback, useEffect } from 'react'
2
+
3
+ import { useQueryParams } from '#~/hooks/useQueryParams.js'
4
+ import type { ChatHeaderView } from '#~/components/chat/ChatHeader.js'
5
+
6
+ const normalizeView = (value: string): ChatHeaderView => {
7
+ if (value === 'timeline' || value === 'settings' || value === 'history') {
8
+ return value
9
+ }
10
+ return 'history'
11
+ }
12
+
13
+ export function useChatView() {
14
+ const { values: queryValues, update: updateQuery } = useQueryParams<{ view: string }>({
15
+ keys: ['view'],
16
+ defaults: {
17
+ view: 'history'
18
+ },
19
+ omit: {
20
+ view: (value) => value === 'history'
21
+ }
22
+ })
23
+
24
+ const activeView = normalizeView(queryValues.view)
25
+ const setActiveView = useCallback((view: ChatHeaderView) => {
26
+ updateQuery({ view })
27
+ }, [updateQuery])
28
+
29
+ useEffect(() => {
30
+ if (activeView !== queryValues.view) {
31
+ updateQuery({ view: activeView })
32
+ }
33
+ }, [activeView, queryValues.view, updateQuery])
34
+
35
+ return {
36
+ activeView,
37
+ setActiveView
38
+ }
39
+ }
package/src/main.tsx CHANGED
@@ -1,3 +1,5 @@
1
+ import 'devicon/devicon.min.css'
2
+
1
3
  import './styles/global.scss'
2
4
  import './i18n'
3
5
 
@@ -8,30 +10,25 @@ import { createRoot } from 'react-dom/client'
8
10
  import { BrowserRouter } from 'react-router-dom'
9
11
  import { SWRConfig } from 'swr'
10
12
 
13
+ import { fetchApiJson } from '#~/api/base.js'
14
+ import { getClientBase } from '#~/runtime-config.js'
15
+
11
16
  import App from './App'
12
17
 
13
18
  const root = createRoot(document.getElementById('root')!)
19
+
20
+ const clientBase = getClientBase()
21
+
14
22
  root.render(
15
23
  <React.StrictMode>
16
24
  <ConfigProvider locale={zhCN} theme={{ token: { colorPrimary: '#000000' } }}>
17
25
  <AntdApp>
18
26
  <SWRConfig
19
27
  value={{
20
- fetcher: async (path: string) => {
21
- const serverHost = (import.meta.env.__VF_PROJECT_AI_SERVER_HOST__ != null &&
22
- import.meta.env.__VF_PROJECT_AI_SERVER_HOST__ !== '')
23
- ? import.meta.env.__VF_PROJECT_AI_SERVER_HOST__
24
- : window.location.hostname
25
- const serverPort = (import.meta.env.__VF_PROJECT_AI_SERVER_PORT__ != null &&
26
- import.meta.env.__VF_PROJECT_AI_SERVER_PORT__ !== '')
27
- ? import.meta.env.__VF_PROJECT_AI_SERVER_PORT__
28
- : '8787'
29
- const baseUrl = `http://${serverHost}:${serverPort}`
30
- return fetch(`${baseUrl}${path}`).then(async (r) => r.json() as Promise<unknown>)
31
- }
28
+ fetcher: async (path: string) => fetchApiJson<unknown>(path)
32
29
  }}
33
30
  >
34
- <BrowserRouter>
31
+ <BrowserRouter basename={clientBase}>
35
32
  <App />
36
33
  </BrowserRouter>
37
34
  </SWRConfig>
@@ -0,0 +1,20 @@
1
+ import { adapterDisplayName as claudeCodeDisplayName, adapterIcon as claudeCodeIcon } from '@vibe-forge/adapter-claude-code/icon'
2
+ import { adapterDisplayName as codexDisplayName, adapterIcon as codexIcon } from '@vibe-forge/adapter-codex/icon'
3
+
4
+ export const adapterDisplayMap = {
5
+ 'claude-code': {
6
+ title: claudeCodeDisplayName,
7
+ icon: claudeCodeIcon
8
+ },
9
+ codex: {
10
+ title: codexDisplayName,
11
+ icon: codexIcon
12
+ }
13
+ } as const
14
+
15
+ export const getAdapterDisplay = (adapterKey: string) => {
16
+ return adapterDisplayMap[adapterKey as keyof typeof adapterDisplayMap] ?? {
17
+ title: adapterKey,
18
+ icon: undefined
19
+ }
20
+ }
@@ -13,6 +13,7 @@
13
13
  "settings": "Settings",
14
14
  "language": "Language",
15
15
  "automation": "Automation",
16
+ "benchmark": "Benchmark",
16
17
  "delete": "Delete",
17
18
  "deleteSession": "Delete Session",
18
19
  "deleteSessionConfirm": "Are you sure you want to delete this session? This action cannot be undone.",
@@ -175,6 +176,58 @@
175
176
  "runStarted": "Task started",
176
177
  "runFailed": "Start failed"
177
178
  },
179
+ "benchmark": {
180
+ "title": "Benchmark",
181
+ "sidebarHint": "Choose a category and case, inspect the task goal, and launch a run.",
182
+ "category": "Category",
183
+ "categoryCount": "{{count}} categories",
184
+ "caseCount": "{{count}} cases",
185
+ "caseTree": "Case Tree",
186
+ "expandAll": "Expand All",
187
+ "collapseAll": "Collapse All",
188
+ "runSelected": "Run Selected ({{count}})",
189
+ "runAll": "Run All",
190
+ "caseId": "Case:",
191
+ "searchPlaceholder": "Search category, title, or summary",
192
+ "emptyCases": "No benchmark cases",
193
+ "noSummary": "No summary",
194
+ "configTitle": "Case Config",
195
+ "baseCommit": "Base Commit",
196
+ "setupCommand": "Setup Command",
197
+ "testCommand": "Test Command",
198
+ "timeoutSec": "Timeout (sec)",
199
+ "runState": "Run State",
200
+ "noActiveRun": "No active benchmark run",
201
+ "runStatus": "Status",
202
+ "progress": "Progress",
203
+ "lastMessage": "Last Message",
204
+ "taskGoal": "Task Goal",
205
+ "resultTitle": "Latest Result",
206
+ "noResult": "No result yet",
207
+ "finalScore": "Final Score",
208
+ "lastRunAt": "Last Run",
209
+ "testScore": "Test Score",
210
+ "goalScore": "Goal Score",
211
+ "referenceScore": "Reference Score",
212
+ "changedFiles": "Changed Files",
213
+ "noChangedFiles": "No changed files recorded",
214
+ "testExitCode": "Test Exit Code",
215
+ "durationMs": "Duration (ms)",
216
+ "issues": "Issues",
217
+ "noIssues": "No issues",
218
+ "concurrency": "Concurrency",
219
+ "runCategory": "Run Category",
220
+ "runCase": "Run Case",
221
+ "runStarted": "Benchmark started",
222
+ "categoryRunStarted": "Category benchmark started",
223
+ "runCompleted": "Benchmark completed",
224
+ "runFailed": "Benchmark run failed",
225
+ "status": {
226
+ "pass": "Pass",
227
+ "partial": "Partial",
228
+ "fail": "Fail"
229
+ }
230
+ },
178
231
  "knowledge": {
179
232
  "title": "Project Knowledge Base",
180
233
  "subtitle": "Manage skills, entities, flows, and rules for this project",
@@ -253,6 +306,9 @@
253
306
  "modelSearchPlaceholder": "Search models or services",
254
307
  "modelUnavailable": "No models available",
255
308
  "modelConfigRequired": "Add a model service in config before starting a session",
309
+ "imageTooLarge": "Image must be smaller than 5MB",
310
+ "imageReadFailed": "Failed to read image",
311
+ "imageNotSupportedInInteraction": "Images are not supported for this interaction",
256
312
  "modelGroupRecommended": "Recommended Models",
257
313
  "availableTools": "Available Tools",
258
314
  "toolsCount": "{{count}} tools",
@@ -351,6 +407,12 @@
351
407
  "todo": "Task Planning",
352
408
  "call": "call",
353
409
  "reading": "Reading file...",
410
+ "offset": "offset",
411
+ "limit": "limit",
412
+ "timeout": "timeout",
413
+ "runInBackground": "run in background",
414
+ "dangerouslyDisableSandbox": "sandbox disabled",
415
+ "viewCommand": "View command",
354
416
  "unknown": "Unknown tool"
355
417
  },
356
418
  "sessionSettings": "Session Settings",
@@ -402,6 +464,7 @@
402
464
  "shortcutPlaceholder": "Press shortcut",
403
465
  "clearShortcut": "Clear shortcut",
404
466
  "newModelServiceName": "New model service name",
467
+ "newChannelName": "New channel name",
405
468
  "newAdapterName": "New adapter name",
406
469
  "newPluginName": "New plugin name",
407
470
  "newMcpServerName": "New MCP server name",
@@ -421,6 +484,8 @@
421
484
  "defaultModelPlaceholder": "Select model",
422
485
  "titlePlaceholder": "Enter title",
423
486
  "descriptionPlaceholder": "Enter description",
487
+ "channelType": "Channel Type",
488
+ "unknownChannelType": "Unknown type",
424
489
  "complexValue": "Complex value",
425
490
  "tagsPlaceholder": "Type and press Enter",
426
491
  "types": {
@@ -754,6 +819,7 @@
754
819
  "general": "General",
755
820
  "conversation": "Conversation",
756
821
  "modelServices": "Model Services",
822
+ "channels": "Channels",
757
823
  "adapters": "Adapters",
758
824
  "plugins": "Plugins",
759
825
  "mcp": "MCP",
@@ -13,6 +13,7 @@
13
13
  "settings": "设置",
14
14
  "language": "语言",
15
15
  "automation": "自动化任务",
16
+ "benchmark": "评测",
16
17
  "delete": "删除",
17
18
  "deleteSession": "删除会话",
18
19
  "deleteSessionConfirm": "确定要删除这个会话吗?此操作不可撤销。",
@@ -176,6 +177,58 @@
176
177
  "runStarted": "已启动任务",
177
178
  "runFailed": "启动失败"
178
179
  },
180
+ "benchmark": {
181
+ "title": "评测",
182
+ "sidebarHint": "选择 category 和 case,查看任务目标并触发跑分。",
183
+ "category": "分类",
184
+ "categoryCount": "{{count}} 个分类",
185
+ "caseCount": "{{count}} 个用例",
186
+ "caseTree": "用例树",
187
+ "expandAll": "展开全部",
188
+ "collapseAll": "折叠全部",
189
+ "runSelected": "运行已选 ({{count}})",
190
+ "runAll": "运行全部",
191
+ "caseId": "用例:",
192
+ "searchPlaceholder": "搜索分类、标题或摘要",
193
+ "emptyCases": "暂无 benchmark 用例",
194
+ "noSummary": "暂无摘要",
195
+ "configTitle": "用例配置",
196
+ "baseCommit": "基线 Commit",
197
+ "setupCommand": "初始化命令",
198
+ "testCommand": "测试命令",
199
+ "timeoutSec": "超时(秒)",
200
+ "runState": "运行状态",
201
+ "noActiveRun": "当前没有运行中的 benchmark 任务",
202
+ "runStatus": "运行状态",
203
+ "progress": "进度",
204
+ "lastMessage": "最新消息",
205
+ "taskGoal": "任务目标",
206
+ "resultTitle": "最近结果",
207
+ "noResult": "暂无结果",
208
+ "finalScore": "总分",
209
+ "lastRunAt": "最近运行",
210
+ "testScore": "测试分",
211
+ "goalScore": "目标分",
212
+ "referenceScore": "参考分",
213
+ "changedFiles": "改动文件数",
214
+ "noChangedFiles": "暂无改动文件记录",
215
+ "testExitCode": "测试退出码",
216
+ "durationMs": "耗时(毫秒)",
217
+ "issues": "问题",
218
+ "noIssues": "暂无问题",
219
+ "concurrency": "并发",
220
+ "runCategory": "运行当前分类",
221
+ "runCase": "运行当前用例",
222
+ "runStarted": "Benchmark 已启动",
223
+ "categoryRunStarted": "分类跑分已启动",
224
+ "runCompleted": "Benchmark 已完成",
225
+ "runFailed": "Benchmark 运行失败",
226
+ "status": {
227
+ "pass": "通过",
228
+ "partial": "部分完成",
229
+ "fail": "失败"
230
+ }
231
+ },
179
232
  "knowledge": {
180
233
  "title": "项目知识库",
181
234
  "subtitle": "管理当前项目的技能、实体、流程与规则",
@@ -254,6 +307,9 @@
254
307
  "modelSearchPlaceholder": "搜索模型或服务",
255
308
  "modelUnavailable": "暂无可用模型",
256
309
  "modelConfigRequired": "请先在配置中添加模型服务后再开始会话",
310
+ "imageTooLarge": "图片大小不能超过 5MB",
311
+ "imageReadFailed": "图片读取失败",
312
+ "imageNotSupportedInInteraction": "当前交互不支持图片",
257
313
  "modelGroupRecommended": "推荐模型",
258
314
  "availableTools": "可用工具",
259
315
  "toolsCount": "{{count}} 个工具",
@@ -352,6 +408,12 @@
352
408
  "todo": "任务规划",
353
409
  "call": "调用",
354
410
  "reading": "正在读取文件...",
411
+ "offset": "offset",
412
+ "limit": "limit",
413
+ "timeout": "timeout",
414
+ "runInBackground": "后台执行",
415
+ "dangerouslyDisableSandbox": "禁用沙箱",
416
+ "viewCommand": "查看指令",
355
417
  "unknown": "未知工具"
356
418
  },
357
419
  "sessionSettings": "会话设置",
@@ -403,6 +465,7 @@
403
465
  "shortcutPlaceholder": "按下快捷键",
404
466
  "clearShortcut": "清除快捷键",
405
467
  "newModelServiceName": "新增模型服务名称",
468
+ "newChannelName": "新增频道名称",
406
469
  "newAdapterName": "新增适配器名称",
407
470
  "newPluginName": "新增插件名称",
408
471
  "newMcpServerName": "新增 MCP 服务名称",
@@ -422,6 +485,8 @@
422
485
  "defaultModelPlaceholder": "请选择模型",
423
486
  "titlePlaceholder": "请输入标题",
424
487
  "descriptionPlaceholder": "请输入描述",
488
+ "channelType": "频道类型",
489
+ "unknownChannelType": "未知类型",
425
490
  "complexValue": "复杂结构",
426
491
  "tagsPlaceholder": "输入后回车",
427
492
  "types": {
@@ -755,6 +820,7 @@
755
820
  "general": "通用",
756
821
  "conversation": "会话",
757
822
  "modelServices": "模型服务",
823
+ "channels": "频道",
758
824
  "adapters": "适配器",
759
825
  "plugins": "插件",
760
826
  "mcp": "MCP",