@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,12 +1,15 @@
1
- import React, { useMemo } from 'react'
1
+ import React, { useEffect, useMemo, useRef } from 'react'
2
2
 
3
- import type { AskUserQuestionParams, ChatMessage, Session, SessionInfo } from '@vibe-forge/core'
3
+ import type { AskUserQuestionParams, ChatMessage, ChatMessageContent, Session, SessionInfo } from '@vibe-forge/core'
4
4
  import { CurrentTodoList } from './CurrentTodoList'
5
- import { MessageItem } from './MessageItem'
5
+ import { MessageItem } from './Messages/MessageItem'
6
6
  import { NewSessionGuide } from './NewSessionGuide'
7
- import { Sender } from './Sender'
8
- import { ToolGroup } from './ToolGroup'
9
- import { processMessages } from './messageUtils'
7
+ import { Sender } from './Sender/Sender'
8
+ import { ToolGroup } from './tools/core/ToolGroup'
9
+ import { processMessages } from './Messages/message-utils'
10
+ import type { PermissionMode } from '#~/hooks/chat/use-chat-permission-mode'
11
+ import { useChatScroll } from '#~/hooks/chat/use-chat-scroll'
12
+ import { useChatSessionActions } from '#~/hooks/chat/use-chat-session-actions'
10
13
 
11
14
  interface ModelSelectOption {
12
15
  value: string
@@ -24,68 +27,112 @@ export function ChatHistoryView({
24
27
  messages,
25
28
  session,
26
29
  sessionInfo,
27
- isCreating,
28
- showScrollBottom,
29
- messagesContainerRef,
30
- messagesEndRef,
31
- scrollToBottom,
32
30
  interactionRequest,
33
31
  onInteractionResponse,
32
+ onClearMessages,
34
33
  onSend,
35
- onInterrupt,
36
- onClear,
34
+ onSendContent,
37
35
  placeholder,
38
36
  modelOptions,
39
37
  selectedModel,
38
+ modelForQuery,
40
39
  onModelChange,
41
- modelUnavailable
40
+ permissionMode,
41
+ permissionModeOptions,
42
+ onPermissionModeChange,
43
+ selectedAdapter,
44
+ adapterOptions,
45
+ onAdapterChange,
46
+ modelUnavailable,
47
+ hasAvailableModels
42
48
  }: {
43
49
  isReady: boolean
44
50
  messages: ChatMessage[]
45
51
  session?: Session
46
52
  sessionInfo: SessionInfo | null
47
- isCreating: boolean
48
- showScrollBottom: boolean
49
- messagesContainerRef: React.RefObject<HTMLDivElement>
50
- messagesEndRef: React.RefObject<HTMLDivElement>
51
- scrollToBottom: (behavior?: ScrollBehavior) => void
52
53
  interactionRequest: { id: string; payload: AskUserQuestionParams } | null
53
54
  onInteractionResponse: (id: string, data: string | string[]) => void
55
+ onClearMessages: () => void
54
56
  onSend: (text: string) => void
55
- onInterrupt: () => void
56
- onClear: () => void
57
+ onSendContent: (content: ChatMessageContent[]) => void
57
58
  placeholder?: string
58
59
  modelOptions: ModelSelectGroup[]
59
60
  selectedModel?: string
61
+ modelForQuery?: string
60
62
  onModelChange: (model: string) => void
63
+ permissionMode: PermissionMode
64
+ permissionModeOptions: Array<{ value: PermissionMode; label: React.ReactNode }>
65
+ onPermissionModeChange: (mode: PermissionMode) => void
66
+ selectedAdapter?: string
67
+ adapterOptions: Array<{ value: string; label: React.ReactNode }>
68
+ onAdapterChange: (adapter: string) => void
61
69
  modelUnavailable: boolean
70
+ hasAvailableModels: boolean
62
71
  }) {
72
+ const { messagesEndRef, messagesContainerRef, messagesContentRef, showScrollBottom, scrollToBottom } = useChatScroll({
73
+ messagesLength: messages.length
74
+ })
75
+ const { isCreating, send, sendContent, interrupt, clearMessages } = useChatSessionActions({
76
+ session,
77
+ modelForQuery,
78
+ hasAvailableModels,
79
+ permissionMode,
80
+ adapter: selectedAdapter,
81
+ onClearMessages
82
+ })
83
+ const initialScrollDoneRef = useRef(false)
84
+ const handleSend = async (text: string) => {
85
+ await send(text)
86
+ if (session?.id) {
87
+ onSend(text)
88
+ }
89
+ }
90
+ const handleSendContent = async (content: ChatMessageContent[]) => {
91
+ await sendContent(content)
92
+ onSendContent(content)
93
+ }
94
+ useEffect(() => {
95
+ initialScrollDoneRef.current = false
96
+ }, [session?.id])
97
+ useEffect(() => {
98
+ if (!initialScrollDoneRef.current && isReady) {
99
+ scrollToBottom('auto')
100
+ initialScrollDoneRef.current = true
101
+ }
102
+ }, [isReady, messages.length, scrollToBottom])
103
+ useEffect(() => {
104
+ if (!showScrollBottom) {
105
+ scrollToBottom('auto')
106
+ }
107
+ }, [messages.length, scrollToBottom, showScrollBottom])
63
108
  const renderItems = useMemo(() => processMessages(messages), [messages])
64
109
 
65
110
  return (
66
111
  <>
67
112
  <div className={`chat-messages ${isReady ? 'ready' : ''}`} ref={messagesContainerRef}>
68
- {renderItems.map((item, index) => {
69
- if (item.type === 'message') {
70
- return (
71
- <MessageItem
72
- key={item.message.id || index}
73
- msg={item.message}
74
- isFirstInGroup={item.isFirstInGroup}
75
- />
76
- )
77
- } else if (item.type === 'tool-group') {
78
- return (
79
- <ToolGroup
80
- key={item.id || `group-${index}`}
81
- items={item.items}
82
- footer={item.footer}
83
- />
84
- )
85
- }
86
- return null
87
- })}
88
- <div ref={messagesEndRef} />
113
+ <div className='chat-messages-content' ref={messagesContentRef}>
114
+ {renderItems.map((item, index) => {
115
+ if (item.type === 'message') {
116
+ return (
117
+ <MessageItem
118
+ key={item.message.id || index}
119
+ msg={item.message}
120
+ isFirstInGroup={item.isFirstInGroup}
121
+ />
122
+ )
123
+ } else if (item.type === 'tool-group') {
124
+ return (
125
+ <ToolGroup
126
+ key={item.id || `group-${index}`}
127
+ items={item.items}
128
+ footer={item.footer}
129
+ />
130
+ )
131
+ }
132
+ return null
133
+ })}
134
+ <div ref={messagesEndRef} />
135
+ </div>
89
136
 
90
137
  {showScrollBottom && (
91
138
  <div className='scroll-bottom-btn' onClick={() => scrollToBottom()}>
@@ -103,10 +150,11 @@ export function ChatHistoryView({
103
150
  <CurrentTodoList messages={messages} />
104
151
  <div className='sender-container'>
105
152
  <Sender
106
- onSend={onSend}
153
+ onSend={handleSend}
154
+ onSendContent={handleSendContent}
107
155
  sessionStatus={isCreating ? 'running' : session?.status}
108
- onInterrupt={onInterrupt}
109
- onClear={onClear}
156
+ onInterrupt={interrupt}
157
+ onClear={clearMessages}
110
158
  sessionInfo={sessionInfo}
111
159
  interactionRequest={interactionRequest}
112
160
  onInteractionResponse={onInteractionResponse}
@@ -114,6 +162,12 @@ export function ChatHistoryView({
114
162
  modelOptions={modelOptions}
115
163
  selectedModel={selectedModel}
116
164
  onModelChange={onModelChange}
165
+ permissionMode={permissionMode}
166
+ permissionModeOptions={permissionModeOptions}
167
+ onPermissionModeChange={onPermissionModeChange}
168
+ selectedAdapter={selectedAdapter}
169
+ adapterOptions={adapterOptions}
170
+ onAdapterChange={onAdapterChange}
117
171
  modelUnavailable={modelUnavailable}
118
172
  />
119
173
  </div>
@@ -1,14 +1,10 @@
1
1
  import './CurrentTodoList.scss'
2
- import type { ChatMessage } from '@vibe-forge/core'
3
2
  import React, { useState } from 'react'
4
3
  import { useTranslation } from 'react-i18next'
4
+ import type { ChatMessage } from '@vibe-forge/core'
5
+ import type { ToolInputs } from '@vibe-forge/core'
5
6
 
6
- interface TodoItem {
7
- id: string
8
- content: string
9
- status: 'pending' | 'in_progress' | 'completed'
10
- priority: string
11
- }
7
+ type TodoItem = ToolInputs['adapter:claude-code:TodoWrite']['todos'][number]
12
8
 
13
9
  export function CurrentTodoList({ messages }: { messages: ChatMessage[] }) {
14
10
  const { t } = useTranslation()
@@ -21,12 +17,17 @@ export function CurrentTodoList({ messages }: { messages: ChatMessage[] }) {
21
17
  const msg = messages[i]
22
18
  if (msg.role === 'assistant' && Array.isArray(msg.content)) {
23
19
  const todoUse = msg.content.find(c =>
24
- c != null && c.type === 'tool_use' && (c.name === 'TodoWrite' || c.name === 'todo_write')
20
+ c != null && c.type === 'tool_use' && (
21
+ c.name === 'TodoWrite' ||
22
+ c.name === 'todo_write' ||
23
+ c.name === 'adapter:claude-code:TodoWrite' ||
24
+ c.name === 'adapter:claude-code:todo_write'
25
+ )
25
26
  )
26
27
  if (
27
28
  todoUse != null && todoUse.type === 'tool_use' && todoUse.input != null && typeof todoUse.input === 'object'
28
29
  ) {
29
- const input = todoUse.input as { todos?: TodoItem[] }
30
+ const input = todoUse.input as Partial<ToolInputs['adapter:claude-code:TodoWrite']>
30
31
  if (Array.isArray(input.todos)) {
31
32
  latestTodos = input.todos
32
33
  break
@@ -122,6 +122,20 @@ html.dark {
122
122
  width: 100%;
123
123
  }
124
124
 
125
+ .message-image {
126
+ display: inline-block;
127
+ max-width: 360px;
128
+ border-radius: 10px;
129
+ overflow: hidden;
130
+ border: 1px solid var(--border-color);
131
+ }
132
+
133
+ .message-image img {
134
+ display: block;
135
+ max-width: 100%;
136
+ height: auto;
137
+ }
138
+
125
139
  .markdown-body {
126
140
  --border-color: #e5e7eb;
127
141
  --code-block-bg: #ffffff;
@@ -2,16 +2,18 @@ import './MessageItem.scss'
2
2
  import type { ChatMessage } from '@vibe-forge/core'
3
3
  import React from 'react'
4
4
  import { MessageFooter } from './MessageFooter'
5
- import { MarkdownContent } from './MarkdownContent'
6
- import { ToolRenderer } from './ToolRenderer'
5
+ import { MarkdownContent } from '#~/components/MarkdownContent'
6
+ import { ToolRenderer } from '../tools/core/ToolRenderer'
7
7
 
8
- export function MessageItem({
9
- msg,
10
- isFirstInGroup
11
- }: {
8
+ type MessageItemProps = {
12
9
  msg: ChatMessage
13
10
  isFirstInGroup: boolean
14
- }) {
11
+ }
12
+
13
+ function MessageItemComponent({
14
+ msg,
15
+ isFirstInGroup
16
+ }: MessageItemProps) {
15
17
  const isUser = msg.role === 'user'
16
18
 
17
19
  const renderContent = () => {
@@ -25,7 +27,7 @@ export function MessageItem({
25
27
 
26
28
  if (!Array.isArray(msg.content)) return null
27
29
 
28
- const hasContent = msg.content.some(c => c.type === 'text') || msg.toolCall != null
30
+ const hasContent = msg.content.some(c => c.type === 'text' || c.type === 'image') || msg.toolCall != null
29
31
  if (!hasContent) return null
30
32
 
31
33
  return (
@@ -36,6 +38,13 @@ export function MessageItem({
36
38
  <MarkdownContent key={i} content={item.text} />
37
39
  )
38
40
  }
41
+ if (item.type === 'image') {
42
+ return (
43
+ <a key={i} className='message-image' href={item.url} target='_blank' rel='noreferrer'>
44
+ <img src={item.url} alt={item.name ?? 'image'} />
45
+ </a>
46
+ )
47
+ }
39
48
  return null
40
49
  })}
41
50
  {msg.toolCall != null && (
@@ -76,3 +85,16 @@ export function MessageItem({
76
85
  </div>
77
86
  )
78
87
  }
88
+
89
+ const areMessageItemPropsEqual = (prev: MessageItemProps, next: MessageItemProps) => {
90
+ return prev.isFirstInGroup === next.isFirstInGroup
91
+ && prev.msg.id === next.msg.id
92
+ && prev.msg.role === next.msg.role
93
+ && prev.msg.createdAt === next.msg.createdAt
94
+ && prev.msg.model === next.msg.model
95
+ && prev.msg.content === next.msg.content
96
+ && prev.msg.toolCall === next.msg.toolCall
97
+ && prev.msg.usage === next.msg.usage
98
+ }
99
+
100
+ export const MessageItem = React.memo(MessageItemComponent, areMessageItemPropsEqual)
@@ -114,7 +114,7 @@ export function processMessages(messages: ChatMessage[]): ChatRenderItem[] {
114
114
  }
115
115
 
116
116
  for (const item of content) {
117
- if (item.type === 'text') {
117
+ if (item.type === 'text' || item.type === 'image') {
118
118
  flushTools()
119
119
  textParts.push(item)
120
120
  } else if (item.type === 'tool_use') {
@@ -39,6 +39,54 @@
39
39
  }
40
40
  }
41
41
 
42
+ .file-input-hidden {
43
+ display: none;
44
+ }
45
+
46
+ .pending-images {
47
+ display: flex;
48
+ gap: 8px;
49
+ padding: 6px 0;
50
+ flex-wrap: wrap;
51
+ }
52
+
53
+ .pending-image {
54
+ position: relative;
55
+ width: 56px;
56
+ height: 56px;
57
+ border-radius: 10px;
58
+ overflow: hidden;
59
+ border: 1px solid var(--border-color);
60
+ background-color: var(--bg-color);
61
+
62
+ img {
63
+ width: 100%;
64
+ height: 100%;
65
+ object-fit: cover;
66
+ display: block;
67
+ }
68
+ }
69
+
70
+ .pending-image-remove {
71
+ position: absolute;
72
+ top: 4px;
73
+ right: 4px;
74
+ width: 18px;
75
+ height: 18px;
76
+ border-radius: 9px;
77
+ background-color: rgba(0, 0, 0, .55);
78
+ color: #fff;
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ cursor: pointer;
83
+
84
+ .material-symbols-rounded {
85
+ font-size: 14px;
86
+ line-height: 1;
87
+ }
88
+ }
89
+
42
90
  .chat-input-toolbar {
43
91
  display: flex;
44
92
  justify-content: space-between;
@@ -219,9 +267,9 @@
219
267
  }
220
268
  }
221
269
 
222
- .model-select {
223
- min-width: 160px;
224
-
270
+ .adapter-select,
271
+ .model-select,
272
+ .permission-mode-select {
225
273
  .ant-select-selector {
226
274
  height: 28px !important;
227
275
  border-radius: 8px !important;
@@ -245,6 +293,101 @@
245
293
  }
246
294
  }
247
295
 
296
+ .adapter-select {
297
+ min-width: 100px;
298
+
299
+ .ant-select-selector {
300
+ padding-right: 24px !important;
301
+ }
302
+
303
+ .ant-select-selection-wrap {
304
+ display: flex;
305
+ align-items: center;
306
+ }
307
+
308
+ .ant-select-selection-item,
309
+ .ant-select-selection-placeholder {
310
+ display: inline-flex;
311
+ align-items: center;
312
+ min-width: 0;
313
+ }
314
+ }
315
+
316
+ .model-select {
317
+ min-width: 160px;
318
+
319
+ }
320
+
321
+ .permission-mode-select {
322
+ min-width: 120px;
323
+ }
324
+
325
+ .permission-mode-select-popup {
326
+ .ant-select-item-option-content {
327
+ font-size: 12px;
328
+ }
329
+ }
330
+
331
+ .adapter-select-popup {
332
+ .ant-select-item-option {
333
+ display: flex;
334
+ align-items: center;
335
+ }
336
+
337
+ .ant-select-item-option-content {
338
+ display: flex;
339
+ align-items: center;
340
+ flex: 1;
341
+ min-height: 20px;
342
+ font-size: 12px;
343
+ }
344
+
345
+ .adapter-option {
346
+ display: flex;
347
+ align-items: center;
348
+ gap: 6px;
349
+ width: 100%;
350
+ }
351
+
352
+ .adapter-option__icon {
353
+ width: 14px;
354
+ height: 14px;
355
+ object-fit: contain;
356
+ flex-shrink: 0;
357
+ }
358
+
359
+ .adapter-option__text {
360
+ display: flex;
361
+ align-items: center;
362
+ min-height: 14px;
363
+ line-height: 1;
364
+ }
365
+
366
+ }
367
+
368
+ .adapter-option {
369
+ display: inline-flex;
370
+ align-items: center;
371
+ gap: 6px;
372
+ min-width: 0;
373
+ }
374
+
375
+ .adapter-option__icon {
376
+ width: 14px;
377
+ height: 14px;
378
+ display: block;
379
+ object-fit: contain;
380
+ flex-shrink: 0;
381
+ }
382
+
383
+ .adapter-option__text {
384
+ display: inline-block;
385
+ min-width: 0;
386
+ overflow: hidden;
387
+ text-overflow: ellipsis;
388
+ white-space: nowrap;
389
+ }
390
+
248
391
  .model-select-popup {
249
392
  .ant-select-item-group {
250
393
  padding: 8px 8px 4px;