@vibe-forge/client 0.3.0 → 0.4.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 (158) hide show
  1. package/cli.cjs +1 -1
  2. package/dist/assets/{arc-CwMXUVsq.js → arc-DgIxeTMg.js} +1 -1
  3. package/dist/assets/{blockDiagram-c4efeb88-CGxJV7KJ.js → blockDiagram-c4efeb88-CEAob3X9.js} +1 -1
  4. package/dist/assets/{c4Diagram-c83219d4-BKhin7cY.js → c4Diagram-c83219d4-DwIxpDKd.js} +1 -1
  5. package/dist/assets/channel-DhtnrNJ6.js +1 -0
  6. package/dist/assets/{classDiagram-beda092f-BASmn22R.js → classDiagram-beda092f-Cz1q8u_0.js} +1 -1
  7. package/dist/assets/{classDiagram-v2-2358418a-BUk9rNBX.js → classDiagram-v2-2358418a-CImgTuwd.js} +1 -1
  8. package/dist/assets/clone-7bHB6YkC.js +1 -0
  9. package/dist/assets/{createText-1719965b-2XqnWjQY.js → createText-1719965b-C1_HJcCc.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-BU8qStzd.js} +1 -1
  15. package/dist/assets/{erDiagram-0228fc6a-CCR2or72.js → erDiagram-0228fc6a-DNA1Fz2L.js} +1 -1
  16. package/dist/assets/{flowDb-c6c81e3f-B72HWT9x.js → flowDb-c6c81e3f-DjiCStMN.js} +1 -1
  17. package/dist/assets/{flowDiagram-50d868cf-WOi0KARY.js → flowDiagram-50d868cf-CSDi0-RD.js} +1 -1
  18. package/dist/assets/flowDiagram-v2-4f6560a1-_13Sz5Wh.js +1 -0
  19. package/dist/assets/{flowchart-elk-definition-6af322e1-i_Yd0LCE.js → flowchart-elk-definition-6af322e1-DrhIMas7.js} +1 -1
  20. package/dist/assets/{ganttDiagram-a2739b55-CFH9zF14.js → ganttDiagram-a2739b55-CTZnUP5z.js} +1 -1
  21. package/dist/assets/{gitGraphDiagram-82fe8481-DglKfMze.js → gitGraphDiagram-82fe8481-COOW7jTi.js} +1 -1
  22. package/dist/assets/{graph-BKbBNGPf.js → graph-CIkpD4Kx.js} +1 -1
  23. package/dist/assets/{index-5325376f-BK7F9nSl.js → index-5325376f-aVVRRTIu.js} +1 -1
  24. package/dist/assets/index-D1giUI7r.css +1 -0
  25. package/dist/assets/index-DRSI_ZIL.js +514 -0
  26. package/dist/assets/{infoDiagram-8eee0895-BLFL77_D.js → infoDiagram-8eee0895-DQpZ1LVD.js} +1 -1
  27. package/dist/assets/{journeyDiagram-c64418c1-CS9XctDL.js → journeyDiagram-c64418c1-DoKguIuk.js} +1 -1
  28. package/dist/assets/{layout-By3JZZGt.js → layout-Tnmha8Nh.js} +1 -1
  29. package/dist/assets/{line-9GUsXbwv.js → line-BQR2SOyl.js} +1 -1
  30. package/dist/assets/{linear-DzGV4E9N.js → linear-DlG0eemV.js} +1 -1
  31. package/dist/assets/{mermaid.core-CG3Ib42Q.js → mermaid.core-BnwYO0He.js} +6 -6
  32. package/dist/assets/{mindmap-definition-8da855dc-WQ3LPKJU.js → mindmap-definition-8da855dc-BllYwDID.js} +1 -1
  33. package/dist/assets/{pieDiagram-a8764435-DHVIUZiN.js → pieDiagram-a8764435-DwCkhPVc.js} +1 -1
  34. package/dist/assets/{quadrantDiagram-1e28029f-C3G9Ye8-.js → quadrantDiagram-1e28029f-c40GKTU0.js} +1 -1
  35. package/dist/assets/{requirementDiagram-08caed73-C9ES1D5G.js → requirementDiagram-08caed73-DnQp2Tk6.js} +1 -1
  36. package/dist/assets/{sankeyDiagram-a04cb91d-B4BKXclQ.js → sankeyDiagram-a04cb91d-CnJrs13b.js} +1 -1
  37. package/dist/assets/{sequenceDiagram-c5b8d532-DrgEb25G.js → sequenceDiagram-c5b8d532-1YBwnpKu.js} +1 -1
  38. package/dist/assets/{stateDiagram-1ecb1508-CF1XWARJ.js → stateDiagram-1ecb1508-BFBxQ6Fh.js} +1 -1
  39. package/dist/assets/{stateDiagram-v2-c2b004d7-IO3i3yXv.js → stateDiagram-v2-c2b004d7-Dmechvv2.js} +1 -1
  40. package/dist/assets/{styles-b4e223ce-DACN9aSc.js → styles-b4e223ce-DWWfWX8O.js} +1 -1
  41. package/dist/assets/{styles-ca3715f6-bekm2WLP.js → styles-ca3715f6-CKKvZxaU.js} +1 -1
  42. package/dist/assets/{styles-d45a18b0-OzTDVBb8.js → styles-d45a18b0-dKMOUh9p.js} +1 -1
  43. package/dist/assets/{svgDrawCommon-b86b1483-BWroJerr.js → svgDrawCommon-b86b1483-CBgjChPM.js} +1 -1
  44. package/dist/assets/{timeline-definition-faaaa080-CCfRNigO.js → timeline-definition-faaaa080-NCt-HHmb.js} +1 -1
  45. package/dist/assets/{xychartDiagram-f5964ef8-C3cbfVqN.js → xychartDiagram-f5964ef8-BJhXS4dG.js} +1 -1
  46. package/dist/index.html +2 -7
  47. package/index.html +0 -5
  48. package/package.json +11 -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 +82 -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 +37 -29
  74. package/src/components/{chat/CodeBlock.tsx → CodeBlock.tsx} +3 -1
  75. package/src/components/ConfigView.tsx +7 -0
  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 +89 -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} +80 -0
  86. package/src/components/chat/{Sender.tsx → Sender/Sender.tsx} +161 -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/channelDefinitions.ts +6 -0
  122. package/src/components/config/configSchema.ts +10 -1
  123. package/src/components/config/recordEditors/ChannelRecordEditor.scss +1 -0
  124. package/src/components/config/recordEditors/ChannelRecordEditor.tsx +397 -0
  125. package/src/components/config/recordEditors/index.tsx +1 -0
  126. package/src/components/knowledge-base/components/RuleItem.tsx +1 -1
  127. package/src/components/knowledge-base/components/SpecItem.tsx +1 -1
  128. package/src/hooks/chat/use-chat-interaction.ts +26 -0
  129. package/src/{components/chat/useChatModels.tsx → hooks/chat/use-chat-models.tsx} +46 -15
  130. package/src/hooks/chat/use-chat-permission-mode.ts +47 -0
  131. package/src/hooks/chat/use-chat-scroll.ts +51 -0
  132. package/src/hooks/chat/use-chat-session-actions.ts +147 -0
  133. package/src/hooks/chat/use-chat-session-messages.ts +250 -0
  134. package/src/hooks/chat/use-chat-session.ts +57 -0
  135. package/src/hooks/chat/use-chat-view.ts +39 -0
  136. package/src/main.tsx +10 -13
  137. package/src/resources/locales/en.json +66 -0
  138. package/src/resources/locales/zh.json +66 -0
  139. package/src/runtime-config.ts +52 -0
  140. package/src/vite-env.d.ts +11 -0
  141. package/src/ws.ts +5 -3
  142. package/vite.config.ts +12 -4
  143. package/dist/assets/channel-jbCEHqbG.js +0 -1
  144. package/dist/assets/clone-CCRKqS4L.js +0 -1
  145. package/dist/assets/flowDiagram-v2-4f6560a1-Baslbgn4.js +0 -1
  146. package/dist/assets/index-B0qfCb1G.css +0 -1
  147. package/dist/assets/index-CNo75dYr.js +0 -497
  148. package/src/components/chat/ToolCallBox.scss +0 -137
  149. package/src/components/chat/useChatSession.ts +0 -370
  150. /package/src/components/{chat/CodeBlock.scss → CodeBlock.scss} +0 -0
  151. /package/src/components/chat/{MessageFooter.tsx → Messages/MessageFooter.tsx} +0 -0
  152. /package/src/components/chat/{CompletionMenu.scss → Sender/CompletionMenu.scss} +0 -0
  153. /package/src/components/chat/{CompletionMenu.tsx → Sender/CompletionMenu.tsx} +0 -0
  154. /package/src/components/chat/{ThinkingStatus.scss → Sender/ThinkingStatus.scss} +0 -0
  155. /package/src/components/chat/{ThinkingStatus.tsx → Sender/ThinkingStatus.tsx} +0 -0
  156. /package/src/components/chat/{ToolCallBox.tsx → tools/core/ToolCallBox.tsx} +0 -0
  157. /package/src/components/chat/{ToolGroup.scss → tools/core/ToolGroup.scss} +0 -0
  158. /package/src/{components/chat/safeSerialize.ts → utils/safe-serialize.ts} +0 -0
@@ -0,0 +1,147 @@
1
+ import { App } from 'antd'
2
+ import { useCallback, useState } from 'react'
3
+ import { useTranslation } from 'react-i18next'
4
+ import { useNavigate } from 'react-router-dom'
5
+ import { useSWRConfig } from 'swr'
6
+
7
+ import type { ChatMessageContent, Session } from '@vibe-forge/core'
8
+ import { createSession } from '#~/api.js'
9
+ import { connectionManager } from '#~/connectionManager.js'
10
+ import type { PermissionMode } from './use-chat-permission-mode'
11
+
12
+ export function useChatSessionActions({
13
+ session,
14
+ modelForQuery,
15
+ hasAvailableModels,
16
+ permissionMode,
17
+ onClearMessages
18
+ }: {
19
+ session?: Session
20
+ modelForQuery?: string
21
+ hasAvailableModels: boolean
22
+ permissionMode: PermissionMode
23
+ onClearMessages: () => void
24
+ }) {
25
+ const { message } = App.useApp()
26
+ const { t } = useTranslation()
27
+ const navigate = useNavigate()
28
+ const { mutate } = useSWRConfig()
29
+ const [isCreating, setIsCreating] = useState(false)
30
+ const isThinking = isCreating || session?.status === 'running'
31
+
32
+ const send = useCallback(async (text: string) => {
33
+ if (text.trim() === '' || isThinking) return
34
+ if (!hasAvailableModels) {
35
+ void message.warning(t('chat.modelConfigRequired'))
36
+ return
37
+ }
38
+
39
+ if (!session?.id) {
40
+ setIsCreating(true)
41
+ try {
42
+ const { session: newSession } = await createSession(undefined, text.trim(), undefined, modelForQuery, {
43
+ permissionMode
44
+ })
45
+
46
+ await mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
47
+ if (!prev?.sessions) return { sessions: [newSession] }
48
+ return {
49
+ ...prev,
50
+ sessions: [newSession, ...prev.sessions]
51
+ }
52
+ }, false)
53
+
54
+ void navigate(`/session/${newSession.id}`)
55
+ } catch (err) {
56
+ console.error(err)
57
+ setIsCreating(false)
58
+ void message.error('Failed to create session')
59
+ }
60
+ return
61
+ }
62
+
63
+ connectionManager.send(session.id, {
64
+ type: 'user_message',
65
+ text: text.trim()
66
+ })
67
+ }, [
68
+ hasAvailableModels,
69
+ isThinking,
70
+ message,
71
+ mutate,
72
+ navigate,
73
+ permissionMode,
74
+ modelForQuery,
75
+ session?.id,
76
+ t
77
+ ])
78
+
79
+ const sendContent = useCallback(async (content: ChatMessageContent[]) => {
80
+ if (content.length === 0 || isThinking) return
81
+ if (!hasAvailableModels) {
82
+ void message.warning(t('chat.modelConfigRequired'))
83
+ return
84
+ }
85
+
86
+ if (!session?.id) {
87
+ setIsCreating(true)
88
+ try {
89
+ const { session: newSession } = await createSession(undefined, undefined, content, modelForQuery, {
90
+ permissionMode
91
+ })
92
+
93
+ await mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
94
+ if (!prev?.sessions) return { sessions: [newSession] }
95
+ return {
96
+ ...prev,
97
+ sessions: [newSession, ...prev.sessions]
98
+ }
99
+ }, false)
100
+
101
+ void navigate(`/session/${newSession.id}`)
102
+ setIsCreating(false)
103
+ } catch (err) {
104
+ console.error(err)
105
+ setIsCreating(false)
106
+ void message.error('Failed to create session')
107
+ }
108
+ return
109
+ }
110
+
111
+ connectionManager.send(session.id, {
112
+ type: 'user_message',
113
+ content
114
+ })
115
+ }, [
116
+ hasAvailableModels,
117
+ isThinking,
118
+ message,
119
+ mutate,
120
+ navigate,
121
+ permissionMode,
122
+ modelForQuery,
123
+ session?.id,
124
+ t
125
+ ])
126
+
127
+ const interrupt = useCallback(() => {
128
+ if (!session?.id || isThinking === false) return
129
+ connectionManager.send(session.id, {
130
+ type: 'interrupt'
131
+ })
132
+ }, [isThinking, session?.id])
133
+
134
+ const clearMessages = useCallback(() => {
135
+ onClearMessages()
136
+ void message.success('Messages cleared')
137
+ }, [message, onClearMessages])
138
+
139
+ return {
140
+ isCreating,
141
+ isThinking,
142
+ send,
143
+ sendContent,
144
+ interrupt,
145
+ clearMessages
146
+ }
147
+ }
@@ -0,0 +1,250 @@
1
+ import { App } from 'antd'
2
+ import { useEffect, useRef, useState } from 'react'
3
+ import { useSWRConfig } from 'swr'
4
+
5
+ import type { AskUserQuestionParams, ChatMessage, Session, SessionInfo, WSEvent } from '@vibe-forge/core'
6
+ import { getSessionMessages } from '#~/api.js'
7
+ import { connectionManager } from '#~/connectionManager.js'
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
+ setInteractionRequest
42
+ }: {
43
+ session?: Session
44
+ modelForQuery?: string
45
+ permissionMode: PermissionMode
46
+ setInteractionRequest: (value: { id: string; payload: AskUserQuestionParams } | null) => void
47
+ }) {
48
+ const { message } = App.useApp()
49
+ const { mutate } = useSWRConfig()
50
+ const [messages, setMessages] = useState<ChatMessage[]>([])
51
+ const [sessionInfo, setSessionInfo] = useState<SessionInfo | null>(null)
52
+ const [isReady, setIsReady] = useState(false)
53
+ const isInitialLoadRef = useRef<boolean>(true)
54
+ const lastConnectedModelRef = useRef<string | undefined>(undefined)
55
+ const lastConnectedPermissionModeRef = useRef<string | undefined>(undefined)
56
+
57
+ useEffect(() => {
58
+ setMessages([])
59
+ setSessionInfo(null)
60
+ setIsReady(false)
61
+ setInteractionRequest(null)
62
+ isInitialLoadRef.current = true
63
+
64
+ if (session?.id == null || session.id === '') {
65
+ setIsReady(true)
66
+ lastConnectedModelRef.current = undefined
67
+ lastConnectedPermissionModeRef.current = undefined
68
+ return
69
+ }
70
+
71
+ let isDisposed = false
72
+
73
+ const fetchHistory = async () => {
74
+ try {
75
+ const res = await getSessionMessages(session.id)
76
+ if (isDisposed) return
77
+ const events = res.messages as WSEvent[]
78
+
79
+ if (res.session) {
80
+ const updatedSession = res.session
81
+ void mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
82
+ if (prev?.sessions == null) return prev
83
+ const newSessions = prev.sessions.map((s: Session) =>
84
+ s.id === updatedSession.id ? { ...s, ...updatedSession } : s
85
+ )
86
+ return { ...prev, sessions: newSessions }
87
+ }, false)
88
+ }
89
+
90
+ if (res.interaction) {
91
+ setInteractionRequest(res.interaction)
92
+ }
93
+
94
+ let currentMessages: ChatMessage[] = []
95
+ let currentSessionInfo: SessionInfo | null = null
96
+
97
+ for (const data of events) {
98
+ currentMessages = applyMessageEvent(currentMessages, data)
99
+ currentMessages = applyToolResultEvent(currentMessages, data)
100
+ if (data.type === 'session_info') {
101
+ if (data.info != null && data.info.type !== 'summary') {
102
+ currentSessionInfo = data.info
103
+ }
104
+ }
105
+ }
106
+
107
+ setMessages(currentMessages)
108
+ setSessionInfo(currentSessionInfo)
109
+
110
+ setTimeout(() => {
111
+ if (isDisposed) return
112
+ setIsReady(true)
113
+ isInitialLoadRef.current = false
114
+ }, 100)
115
+ } catch (err) {
116
+ console.error('Failed to fetch history messages:', err)
117
+ }
118
+ }
119
+
120
+ void fetchHistory()
121
+
122
+ return () => {
123
+ isDisposed = true
124
+ }
125
+ }, [mutate, session?.id, setInteractionRequest])
126
+
127
+ useEffect(() => {
128
+ if (session?.id == null || session.id === '') {
129
+ return
130
+ }
131
+
132
+ let isDisposed = false
133
+ let cleanup: (() => void) | undefined
134
+ const normalizedModel = modelForQuery ?? ''
135
+ const modelChanged = modelForQuery != null &&
136
+ lastConnectedModelRef.current != null &&
137
+ normalizedModel !== lastConnectedModelRef.current &&
138
+ session?.status !== 'running'
139
+ const normalizedPermissionMode = permissionMode ?? ''
140
+ const permissionModeChanged = permissionMode != null &&
141
+ lastConnectedPermissionModeRef.current != null &&
142
+ normalizedPermissionMode !== lastConnectedPermissionModeRef.current &&
143
+ session?.status !== 'running'
144
+ if (modelChanged || permissionModeChanged) {
145
+ connectionManager.send(session.id, { type: 'terminate_session' })
146
+ connectionManager.close(session.id)
147
+ }
148
+ lastConnectedModelRef.current = normalizedModel
149
+ lastConnectedPermissionModeRef.current = normalizedPermissionMode
150
+
151
+ const timer = setTimeout(() => {
152
+ if (isDisposed) return
153
+
154
+ const connectionParams: Record<string, string> = {}
155
+ if (modelForQuery) {
156
+ connectionParams.model = modelForQuery
157
+ }
158
+ if (permissionMode) {
159
+ connectionParams.permissionMode = permissionMode
160
+ }
161
+
162
+ cleanup = connectionManager.connect(session.id, {
163
+ onOpen() {
164
+ },
165
+ onMessage(data: WSEvent) {
166
+ if (isDisposed) return
167
+ if (data.type === 'error') {
168
+ void message.error(data.message)
169
+ return
170
+ }
171
+
172
+ if (data.type === 'session_updated') {
173
+ void mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
174
+ if (prev?.sessions == null) return prev
175
+ const updatedSession = data.session as Session | { id: string; isDeleted: boolean }
176
+
177
+ if ('isDeleted' in updatedSession && updatedSession.isDeleted) {
178
+ return {
179
+ ...prev,
180
+ sessions: prev.sessions.filter((s: Session) => s.id !== updatedSession.id)
181
+ }
182
+ }
183
+
184
+ const typedUpdatedSession = updatedSession as Session
185
+ const newSessions = prev.sessions.map((s: Session) =>
186
+ s.id === typedUpdatedSession.id ? { ...s, ...typedUpdatedSession } : s
187
+ )
188
+
189
+ if (
190
+ !newSessions.some((s: Session) => s.id === typedUpdatedSession.id) && !('isDeleted' in updatedSession)
191
+ ) {
192
+ newSessions.unshift(typedUpdatedSession)
193
+ }
194
+
195
+ return { ...prev, sessions: newSessions }
196
+ }, false)
197
+ return
198
+ }
199
+
200
+ if (data.type === 'message') {
201
+ setMessages((current) => applyMessageEvent(current, data))
202
+ return
203
+ }
204
+
205
+ if (data.type === 'session_info') {
206
+ if (data.info != null && data.info.type === 'summary') {
207
+ void mutate('/api/sessions')
208
+ } else {
209
+ setSessionInfo(data.info ?? null)
210
+ if (isInitialLoadRef.current) {
211
+ setTimeout(() => {
212
+ if (isDisposed) return
213
+ if (isInitialLoadRef.current) {
214
+ setIsReady(true)
215
+ isInitialLoadRef.current = false
216
+ }
217
+ }, 100)
218
+ }
219
+ }
220
+ return
221
+ }
222
+
223
+ if (data.type === 'tool_result') {
224
+ setMessages((current) => applyToolResultEvent(current, data))
225
+ return
226
+ }
227
+
228
+ if (data.type === 'interaction_request') {
229
+ setInteractionRequest({ id: data.id, payload: data.payload })
230
+ }
231
+ },
232
+ onClose() {
233
+ }
234
+ }, Object.keys(connectionParams).length > 0 ? connectionParams : undefined)
235
+ }, modelChanged ? 200 : 100)
236
+
237
+ return () => {
238
+ isDisposed = true
239
+ clearTimeout(timer)
240
+ cleanup?.()
241
+ }
242
+ }, [message, modelForQuery, mutate, permissionMode, session?.id, session?.status, setInteractionRequest])
243
+
244
+ return {
245
+ messages,
246
+ setMessages,
247
+ sessionInfo,
248
+ isReady
249
+ }
250
+ }
@@ -0,0 +1,57 @@
1
+ import { useTranslation } from 'react-i18next'
2
+
3
+ import type { Session } from '@vibe-forge/core'
4
+ import { useChatInteraction } from './use-chat-interaction'
5
+ import { useChatModels } from './use-chat-models'
6
+ import { useChatPermissionMode } from './use-chat-permission-mode'
7
+ import { useChatSessionMessages } from './use-chat-session-messages'
8
+ import { useChatView } from './use-chat-view'
9
+
10
+ export function useChatSession({
11
+ session
12
+ }: {
13
+ session?: Session
14
+ }) {
15
+ const { t } = useTranslation()
16
+ const {
17
+ selectedModel,
18
+ selectedModelWithService,
19
+ setSelectedModel,
20
+ modelOptions,
21
+ hasAvailableModels
22
+ } = useChatModels()
23
+ const { permissionMode, setPermissionMode, permissionModeOptions } = useChatPermissionMode()
24
+ const { activeView, setActiveView } = useChatView()
25
+ const { interactionRequest, setInteractionRequest, handleInteractionResponse } = useChatInteraction({
26
+ sessionId: session?.id
27
+ })
28
+ const { messages, setMessages, sessionInfo, isReady } = useChatSessionMessages({
29
+ session,
30
+ modelForQuery: selectedModelWithService,
31
+ permissionMode,
32
+ setInteractionRequest
33
+ })
34
+ const isThinking = session?.status === 'running'
35
+
36
+ return {
37
+ messages,
38
+ sessionInfo,
39
+ interactionRequest,
40
+ isReady,
41
+ isThinking,
42
+ activeView,
43
+ setActiveView,
44
+ handleInteractionResponse,
45
+ setMessages,
46
+ placeholder: !session?.id ? t('chat.newSessionPlaceholder') : undefined,
47
+ modelOptions,
48
+ selectedModel,
49
+ modelForQuery: selectedModelWithService,
50
+ setSelectedModel,
51
+ permissionMode,
52
+ setPermissionMode,
53
+ permissionModeOptions,
54
+ hasAvailableModels,
55
+ modelUnavailable: !hasAvailableModels
56
+ }
57
+ }
@@ -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>
@@ -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",