@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
@@ -1,137 +0,0 @@
1
- .tool-group {
2
- margin: 0;
3
- border: 1px solid var(--border-color);
4
- border-radius: 6px;
5
- overflow: hidden;
6
- background-color: var(--bg-color);
7
-
8
- .tool-call-box {
9
- border: none;
10
- margin: 0;
11
- border-radius: 0;
12
-
13
- &.result {
14
- background-color: transparent;
15
- }
16
- }
17
-
18
- .tool-call-header {
19
- padding: 8px 12px;
20
- height: 36px;
21
- box-sizing: border-box;
22
- cursor: pointer;
23
- user-select: none;
24
- display: flex;
25
- align-items: center;
26
- justify-content: space-between;
27
- gap: 8px;
28
- font-size: 13px;
29
- font-weight: 500;
30
- color: var(--text-color);
31
- background-color: var(--tag-bg);
32
- transition: text-shadow .4s ease;
33
-
34
- &.collapsed {
35
- cursor: default;
36
- }
37
-
38
- .tool-call-header-main {
39
- display: flex;
40
- align-items: center;
41
- gap: 6px;
42
- flex: 1;
43
- min-width: 0;
44
- }
45
-
46
- &:hover {
47
- text-shadow: 0 0 .5px currentColor;
48
- }
49
-
50
- .material-symbols-rounded {
51
- font-size: 18px;
52
- color: var(--sub-text-color);
53
-
54
- &.expand-icon {
55
- font-size: 16px;
56
- }
57
- }
58
- }
59
-
60
- .tool-result-header {
61
- padding: 8px 12px;
62
- height: 36px;
63
- box-sizing: border-box;
64
- cursor: pointer;
65
- user-select: none;
66
- border-top: 1px solid var(--border-color);
67
- display: flex;
68
- align-items: center;
69
- gap: 8px;
70
- font-size: 13px;
71
- font-weight: 500;
72
- color: #059669;
73
- background-color: var(--tag-bg);
74
-
75
- &.error {
76
- color: #dc2626;
77
- .material-symbols-rounded {
78
- color: #dc2626;
79
- }
80
- }
81
-
82
- &:hover {
83
- opacity: .8;
84
- }
85
-
86
- .material-symbols-rounded {
87
- font-size: 18px;
88
- color: #059669;
89
- }
90
- }
91
-
92
- .tool-call-box:first-child {
93
- .tool-result-header {
94
- border-top: none;
95
- }
96
- }
97
-
98
- .tool-call-box.expanded {
99
- .tool-call-header {
100
- border-bottom: 1px solid var(--border-color);
101
- }
102
- .tool-result-header {
103
- border-bottom: none;
104
- }
105
- }
106
-
107
- .tool-call-box.collapsed {
108
- .tool-call-header {
109
- border-bottom: none;
110
- }
111
- .tool-result-header {
112
- border-bottom: none;
113
- }
114
- }
115
-
116
- .tool-call-body {
117
- padding: 0;
118
- overflow: hidden;
119
- transition: all .2s ease-in-out;
120
- background-color: var(--bg-color);
121
-
122
- .tool-call-box.result & {
123
- background-color: var(--bg-color);
124
- border-top: 1px dashed var(--border-color);
125
- }
126
-
127
- .tool-content {
128
- padding: 0;
129
-
130
- // Override code block wrapper styles
131
- .code-block-wrapper {
132
- border: none;
133
- border-radius: 0;
134
- }
135
- }
136
- }
137
- }
@@ -1,370 +0,0 @@
1
- import { App } from 'antd'
2
- import { useCallback, useEffect, useRef, 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 { AskUserQuestionParams, ChatMessage, Session, SessionInfo, WSEvent } from '@vibe-forge/core'
8
- import { useQueryParams } from '#~/hooks/useQueryParams.js'
9
- import { createSession, getSessionMessages } from '../../api'
10
- import { connectionManager } from '../../connectionManager'
11
- import type { ChatHeaderView } from './ChatHeader'
12
-
13
- const normalizeView = (value: string): ChatHeaderView => {
14
- if (value === 'timeline' || value === 'settings' || value === 'history') {
15
- return value
16
- }
17
- return 'history'
18
- }
19
-
20
- export function useChatSession({
21
- session,
22
- selectedModel,
23
- hasAvailableModels
24
- }: {
25
- session?: Session
26
- selectedModel?: string
27
- hasAvailableModels: boolean
28
- }) {
29
- const { message } = App.useApp()
30
- const { t } = useTranslation()
31
- const navigate = useNavigate()
32
- const [messages, setMessages] = useState<ChatMessage[]>([])
33
- const [sessionInfo, setSessionInfo] = useState<SessionInfo | null>(null)
34
- const [interactionRequest, setInteractionRequest] = useState<{ id: string; payload: AskUserQuestionParams } | null>(
35
- null
36
- )
37
- const [isCreating, setIsCreating] = useState(false)
38
- const [isReady, setIsReady] = useState(false)
39
- const { values: queryValues, update: updateQuery } = useQueryParams<{ view: string }>({
40
- keys: ['view'],
41
- defaults: {
42
- view: 'history'
43
- },
44
- omit: {
45
- view: (value) => value === 'history'
46
- }
47
- })
48
- const activeView = normalizeView(queryValues.view)
49
- const setActiveView = useCallback((view: ChatHeaderView) => {
50
- updateQuery({ view })
51
- }, [updateQuery])
52
- const messagesEndRef = useRef<HTMLDivElement>(null)
53
- const messagesContainerRef = useRef<HTMLDivElement>(null)
54
- const wasAtBottom = useRef<boolean>(true)
55
- const isInitialLoadRef = useRef<boolean>(true)
56
- const lastConnectedModelRef = useRef<string | undefined>(undefined)
57
- const [showScrollBottom, setShowScrollBottom] = useState(false)
58
- const { mutate } = useSWRConfig()
59
-
60
- const isThinking = isCreating || session?.status === 'running'
61
-
62
- const scrollToBottom = (behavior: ScrollBehavior = 'smooth') => {
63
- setTimeout(() => {
64
- if (messagesContainerRef.current) {
65
- messagesContainerRef.current.scrollTo({
66
- top: messagesContainerRef.current.scrollHeight,
67
- behavior
68
- })
69
- }
70
- }, 50)
71
- }
72
-
73
- useEffect(() => {
74
- const container = messagesContainerRef.current
75
- if (!container) return
76
-
77
- const handleScroll = () => {
78
- const { scrollTop, scrollHeight, clientHeight } = container
79
- const atBottom = scrollHeight - scrollTop <= clientHeight + 100
80
- wasAtBottom.current = atBottom
81
- setShowScrollBottom(!atBottom && scrollHeight > clientHeight)
82
- }
83
-
84
- container.addEventListener('scroll', handleScroll)
85
- return () => container.removeEventListener('scroll', handleScroll)
86
- }, [])
87
-
88
- useEffect(() => {
89
- if (isInitialLoadRef.current && messages.length > 0) {
90
- scrollToBottom('auto')
91
- const timer = setTimeout(() => {
92
- setIsReady(true)
93
- isInitialLoadRef.current = false
94
- }, 50)
95
- return () => clearTimeout(timer)
96
- } else if (wasAtBottom.current) {
97
- scrollToBottom('smooth')
98
- }
99
- }, [messages])
100
-
101
- useEffect(() => {
102
- if (session?.id == null || session.id === '') {
103
- setMessages([])
104
- setSessionInfo(null)
105
- setIsReady(true)
106
- lastConnectedModelRef.current = undefined
107
- return
108
- }
109
-
110
- setMessages([])
111
- setSessionInfo(null)
112
- setIsReady(false)
113
- isInitialLoadRef.current = true
114
-
115
- let isDisposed = false
116
-
117
- const fetchHistory = async () => {
118
- try {
119
- const res = await getSessionMessages(session.id)
120
- if (isDisposed) return
121
- const events = res.messages as WSEvent[]
122
-
123
- if (res.session) {
124
- const updatedSession = res.session
125
- void mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
126
- if (prev?.sessions == null) return prev
127
- const newSessions = prev.sessions.map((s: Session) =>
128
- s.id === updatedSession.id ? { ...s, ...updatedSession } : s
129
- )
130
- return { ...prev, sessions: newSessions }
131
- }, false)
132
- }
133
-
134
- if (res.interaction) {
135
- setInteractionRequest(res.interaction)
136
- } else {
137
- setInteractionRequest(null)
138
- }
139
-
140
- let currentMessages: ChatMessage[] = []
141
- let currentSessionInfo: SessionInfo | null = null
142
-
143
- for (const data of events) {
144
- if (data.type === 'message') {
145
- const exists = currentMessages.find((msg) => msg.id === data.message.id)
146
- if (exists != null) {
147
- currentMessages = currentMessages.map((msg) => (msg.id === data.message.id ? data.message : msg))
148
- } else {
149
- currentMessages.push(data.message)
150
- }
151
- } else if (data.type === 'session_info') {
152
- if (data.info != null && data.info.type !== 'summary') {
153
- currentSessionInfo = data.info
154
- }
155
- } else if (data.type === 'tool_result') {
156
- currentMessages = currentMessages.map((msg) => {
157
- if (msg.toolCall != null && msg.toolCall.id === data.toolCallId) {
158
- return {
159
- ...msg,
160
- toolCall: {
161
- ...msg.toolCall,
162
- status: data.isError === true ? 'error' : 'success',
163
- output: data.output
164
- }
165
- }
166
- }
167
- return msg
168
- })
169
- }
170
- }
171
-
172
- setMessages(currentMessages)
173
- setSessionInfo(currentSessionInfo)
174
-
175
- setTimeout(() => {
176
- if (isDisposed) return
177
- setIsReady(true)
178
- isInitialLoadRef.current = false
179
- }, 100)
180
- } catch (err) {
181
- console.error('Failed to fetch history messages:', err)
182
- }
183
- }
184
-
185
- void fetchHistory()
186
-
187
- let cleanup: (() => void) | undefined
188
- const normalizedModel = selectedModel ?? ''
189
- const modelChanged = selectedModel != null
190
- && lastConnectedModelRef.current != null
191
- && normalizedModel !== lastConnectedModelRef.current
192
- && session?.status !== 'running'
193
- if (modelChanged) {
194
- connectionManager.send(session.id, { type: 'terminate_session' })
195
- connectionManager.close(session.id)
196
- }
197
- lastConnectedModelRef.current = normalizedModel
198
-
199
- const timer = setTimeout(() => {
200
- if (isDisposed) return
201
-
202
- cleanup = connectionManager.connect(session.id, {
203
- onOpen() {
204
- },
205
- onMessage(data: WSEvent) {
206
- if (isDisposed) return
207
- if (data.type === 'error') {
208
- void message.error(data.message)
209
- } else if (data.type === 'session_updated') {
210
- void mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
211
- if (prev?.sessions == null) return prev
212
- const updatedSession = data.session as Session | { id: string; isDeleted: boolean }
213
-
214
- if ('isDeleted' in updatedSession && updatedSession.isDeleted) {
215
- return {
216
- ...prev,
217
- sessions: prev.sessions.filter((s: Session) => s.id !== updatedSession.id)
218
- }
219
- }
220
-
221
- const typedUpdatedSession = updatedSession as Session
222
- const newSessions = prev.sessions.map((s: Session) =>
223
- s.id === typedUpdatedSession.id ? { ...s, ...typedUpdatedSession } : s
224
- )
225
-
226
- if (
227
- !newSessions.some((s: Session) => s.id === typedUpdatedSession.id) && !('isDeleted' in updatedSession)
228
- ) {
229
- newSessions.unshift(typedUpdatedSession)
230
- }
231
-
232
- return { ...prev, sessions: newSessions }
233
- }, false)
234
- } else if (data.type === 'message') {
235
- setMessages((m) => {
236
- const exists = m.find((msg) => msg.id === data.message.id)
237
- if (exists != null) {
238
- return m.map((msg) => (msg.id === data.message.id ? data.message : msg))
239
- }
240
- return [...m, data.message]
241
- })
242
- } else if (data.type === 'session_info') {
243
- if (data.info != null && data.info.type === 'summary') {
244
- void mutate('/api/sessions')
245
- } else {
246
- setSessionInfo(data.info ?? null)
247
- if (isInitialLoadRef.current) {
248
- setTimeout(() => {
249
- if (isDisposed) return
250
- if (isInitialLoadRef.current) {
251
- setIsReady(true)
252
- isInitialLoadRef.current = false
253
- }
254
- }, 100)
255
- }
256
- }
257
- } else if (data.type === 'tool_result') {
258
- setMessages((m) => {
259
- return m.map((msg) => {
260
- if (msg.toolCall != null && msg.toolCall.id === data.toolCallId) {
261
- return {
262
- ...msg,
263
- toolCall: {
264
- ...msg.toolCall,
265
- status: data.isError === true ? 'error' : 'success',
266
- output: data.output
267
- }
268
- }
269
- }
270
- return msg
271
- })
272
- })
273
- } else if (data.type === 'interaction_request') {
274
- setInteractionRequest({ id: data.id, payload: data.payload })
275
- }
276
- },
277
- onClose() {
278
- }
279
- }, selectedModel ? { model: selectedModel } : undefined)
280
- }, modelChanged ? 200 : 100)
281
-
282
- return () => {
283
- isDisposed = true
284
- clearTimeout(timer)
285
- cleanup?.()
286
- }
287
- }, [selectedModel, session?.id, session?.status, mutate])
288
-
289
- useEffect(() => {
290
- if (activeView !== queryValues.view) {
291
- updateQuery({ view: activeView })
292
- }
293
- }, [activeView, queryValues.view, updateQuery])
294
-
295
- const send = async (text: string) => {
296
- if (text.trim() === '' || isThinking) return
297
- if (!hasAvailableModels) {
298
- void message.warning(t('chat.modelConfigRequired'))
299
- return
300
- }
301
-
302
- if (!session?.id) {
303
- setIsCreating(true)
304
- try {
305
- const { session: newSession } = await createSession(undefined, text.trim(), selectedModel)
306
-
307
- await mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
308
- if (!prev?.sessions) return { sessions: [newSession] }
309
- return {
310
- ...prev,
311
- sessions: [newSession, ...prev.sessions]
312
- }
313
- }, false)
314
-
315
- void navigate(`/session/${newSession.id}`)
316
- } catch (err) {
317
- console.error(err)
318
- setIsCreating(false)
319
- void message.error('Failed to create session')
320
- }
321
- return
322
- }
323
-
324
- connectionManager.send(session.id, {
325
- type: 'user_message',
326
- text: text.trim()
327
- })
328
- }
329
-
330
- const interrupt = () => {
331
- if (!session?.id || isThinking === false) return
332
- connectionManager.send(session.id, {
333
- type: 'interrupt'
334
- })
335
- }
336
-
337
- const clearMessages = () => {
338
- setMessages([])
339
- void message.success('Messages cleared')
340
- }
341
-
342
- const handleInteractionResponse = (id: string, data: string | string[]) => {
343
- if (!session?.id) return
344
- connectionManager.send(session.id, {
345
- type: 'interaction_response',
346
- id,
347
- data
348
- })
349
- setInteractionRequest(null)
350
- }
351
-
352
- return {
353
- messages,
354
- sessionInfo,
355
- interactionRequest,
356
- isCreating,
357
- isReady,
358
- isThinking,
359
- activeView,
360
- setActiveView,
361
- messagesEndRef,
362
- messagesContainerRef,
363
- showScrollBottom,
364
- scrollToBottom,
365
- send,
366
- interrupt,
367
- clearMessages,
368
- handleInteractionResponse
369
- }
370
- }