@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.
- package/cli.cjs +2 -1
- package/dist/assets/{arc-CwMXUVsq.js → arc-C4ymrcSQ.js} +1 -1
- package/dist/assets/{blockDiagram-c4efeb88-CGxJV7KJ.js → blockDiagram-c4efeb88-CeB7-kgP.js} +1 -1
- package/dist/assets/{c4Diagram-c83219d4-BKhin7cY.js → c4Diagram-c83219d4-C935Im8S.js} +1 -1
- package/dist/assets/channel-84s1ACzD.js +1 -0
- package/dist/assets/{classDiagram-beda092f-BASmn22R.js → classDiagram-beda092f-B9IV13KI.js} +1 -1
- package/dist/assets/{classDiagram-v2-2358418a-BUk9rNBX.js → classDiagram-v2-2358418a-CXF_K4fE.js} +1 -1
- package/dist/assets/clone-B2E8tddE.js +1 -0
- package/dist/assets/{createText-1719965b-2XqnWjQY.js → createText-1719965b-DwX8iC5F.js} +1 -1
- package/dist/assets/devicon-BWlTeAUU.woff +0 -0
- package/dist/assets/devicon-CirD-cQx.ttf +0 -0
- package/dist/assets/devicon-Dg8iWy0i.svg +1211 -0
- package/dist/assets/devicon-TqfHp33-.eot +0 -0
- package/dist/assets/{edges-96097737-B7e32Jeg.js → edges-96097737-9P1uH1RE.js} +1 -1
- package/dist/assets/{erDiagram-0228fc6a-CCR2or72.js → erDiagram-0228fc6a-ixeGTFvg.js} +1 -1
- package/dist/assets/{flowDb-c6c81e3f-B72HWT9x.js → flowDb-c6c81e3f-G1gSTTBI.js} +1 -1
- package/dist/assets/{flowDiagram-50d868cf-WOi0KARY.js → flowDiagram-50d868cf-CzrG99nD.js} +1 -1
- package/dist/assets/flowDiagram-v2-4f6560a1-CJfJYbME.js +1 -0
- package/dist/assets/{flowchart-elk-definition-6af322e1-i_Yd0LCE.js → flowchart-elk-definition-6af322e1-sFCoysWa.js} +1 -1
- package/dist/assets/{ganttDiagram-a2739b55-CFH9zF14.js → ganttDiagram-a2739b55-Ccsk_Lru.js} +1 -1
- package/dist/assets/{gitGraphDiagram-82fe8481-DglKfMze.js → gitGraphDiagram-82fe8481-CwathJ6H.js} +1 -1
- package/dist/assets/{graph-BKbBNGPf.js → graph-DRCU-8Rz.js} +1 -1
- package/dist/assets/{index-5325376f-BK7F9nSl.js → index-5325376f-Bq-fg2i_.js} +1 -1
- package/dist/assets/index-CHMuZ5-1.css +1 -0
- package/dist/assets/index-cGZvDhhU.js +542 -0
- package/dist/assets/{infoDiagram-8eee0895-BLFL77_D.js → infoDiagram-8eee0895-JBcUkJ6T.js} +1 -1
- package/dist/assets/{journeyDiagram-c64418c1-CS9XctDL.js → journeyDiagram-c64418c1-DsdQU-R8.js} +1 -1
- package/dist/assets/{layout-By3JZZGt.js → layout-s0slG1OL.js} +1 -1
- package/dist/assets/{line-9GUsXbwv.js → line-CymFqgW6.js} +1 -1
- package/dist/assets/{linear-DzGV4E9N.js → linear-lDQVZ6aQ.js} +1 -1
- package/dist/assets/{mermaid.core-CG3Ib42Q.js → mermaid.core-Cmlqga_E.js} +6 -6
- package/dist/assets/{mindmap-definition-8da855dc-WQ3LPKJU.js → mindmap-definition-8da855dc-CqqTDJn_.js} +1 -1
- package/dist/assets/{pieDiagram-a8764435-DHVIUZiN.js → pieDiagram-a8764435-BL2Ajx7Z.js} +1 -1
- package/dist/assets/{quadrantDiagram-1e28029f-C3G9Ye8-.js → quadrantDiagram-1e28029f-ClL_3ASt.js} +1 -1
- package/dist/assets/{requirementDiagram-08caed73-C9ES1D5G.js → requirementDiagram-08caed73-CB1RgE3K.js} +1 -1
- package/dist/assets/{sankeyDiagram-a04cb91d-B4BKXclQ.js → sankeyDiagram-a04cb91d-tgleEYiD.js} +1 -1
- package/dist/assets/{sequenceDiagram-c5b8d532-DrgEb25G.js → sequenceDiagram-c5b8d532-DlatQT5R.js} +1 -1
- package/dist/assets/{stateDiagram-1ecb1508-CF1XWARJ.js → stateDiagram-1ecb1508-B--MLqRs.js} +1 -1
- package/dist/assets/{stateDiagram-v2-c2b004d7-IO3i3yXv.js → stateDiagram-v2-c2b004d7-CRMZ6Dpx.js} +1 -1
- package/dist/assets/{styles-b4e223ce-DACN9aSc.js → styles-b4e223ce-CPiYHfUz.js} +1 -1
- package/dist/assets/{styles-ca3715f6-bekm2WLP.js → styles-ca3715f6-B9UKPAzX.js} +1 -1
- package/dist/assets/{styles-d45a18b0-OzTDVBb8.js → styles-d45a18b0-BC1Ak1So.js} +1 -1
- package/dist/assets/{svgDrawCommon-b86b1483-BWroJerr.js → svgDrawCommon-b86b1483-DV8R0g-n.js} +1 -1
- package/dist/assets/{timeline-definition-faaaa080-CCfRNigO.js → timeline-definition-faaaa080-CiqGS5DC.js} +1 -1
- package/dist/assets/{xychartDiagram-f5964ef8-C3cbfVqN.js → xychartDiagram-f5964ef8-h6VSD3GE.js} +1 -1
- package/dist/index.html +2 -7
- package/index.html +0 -5
- package/package.json +12 -6
- package/src/App.tsx +2 -0
- package/src/api/README.md +26 -0
- package/src/api/automation.ts +88 -0
- package/src/api/base.ts +54 -0
- package/src/api/benchmark.ts +45 -0
- package/src/api/config.ts +24 -0
- package/src/api/knowledge.ts +72 -0
- package/src/api/projects.ts +15 -0
- package/src/api/sessions.ts +84 -0
- package/src/api/types.ts +20 -0
- package/src/api.ts +44 -269
- package/src/components/AutomationView/AutomationView.scss +5 -1
- package/src/components/AutomationView/RuleFormPanel.tsx +3 -2
- package/src/components/AutomationView/TaskList.scss +4 -6
- package/src/components/AutomationView/TaskList.tsx +2 -1
- package/src/components/AutomationView/TriggerList.scss +4 -1
- package/src/components/BenchmarkView/BenchmarkCasePanel.scss +267 -0
- package/src/components/BenchmarkView/BenchmarkCasePanel.tsx +309 -0
- package/src/components/BenchmarkView/BenchmarkSidebar.scss +182 -0
- package/src/components/BenchmarkView/BenchmarkSidebar.tsx +262 -0
- package/src/components/BenchmarkView/BenchmarkView.scss +78 -0
- package/src/components/BenchmarkView/index.tsx +197 -0
- package/src/components/BenchmarkView/types.ts +10 -0
- package/src/components/BenchmarkView/utils.ts +21 -0
- package/src/components/Chat.tsx +43 -29
- package/src/components/{chat/CodeBlock.tsx → CodeBlock.tsx} +3 -1
- package/src/components/ConfigView.tsx +32 -25
- package/src/components/{chat/MarkdownContent.tsx → MarkdownContent.tsx} +1 -1
- package/src/components/NavRail.tsx +7 -0
- package/src/components/chat/ChatHeader.scss +37 -19
- package/src/components/chat/ChatHeader.tsx +6 -9
- package/src/components/chat/ChatHistoryView.tsx +99 -45
- package/src/components/chat/CurrentTodoList.tsx +10 -9
- package/src/components/chat/{MessageItem.scss → Messages/MessageItem.scss} +14 -0
- package/src/components/chat/{MessageItem.tsx → Messages/MessageItem.tsx} +30 -8
- package/src/components/chat/{messageUtils.ts → Messages/message-utils.ts} +1 -1
- package/src/components/chat/{Sender.scss → Sender/Sender.scss} +146 -3
- package/src/components/chat/{Sender.tsx → Sender/Sender.tsx} +183 -5
- package/src/components/chat/tools/DefaultTool.tsx +184 -21
- package/src/components/chat/tools/adapter-claude/BashTool.scss +67 -51
- package/src/components/chat/tools/adapter-claude/BashTool.tsx +83 -49
- package/src/components/chat/tools/adapter-claude/GlobTool.scss +0 -79
- package/src/components/chat/tools/adapter-claude/GlobTool.tsx +16 -36
- package/src/components/chat/tools/adapter-claude/GrepTool.scss +0 -87
- package/src/components/chat/tools/adapter-claude/GrepTool.tsx +22 -41
- package/src/components/chat/tools/adapter-claude/LSTool.scss +0 -79
- package/src/components/chat/tools/adapter-claude/LSTool.tsx +15 -15
- package/src/components/chat/tools/adapter-claude/ReadTool.scss +0 -55
- package/src/components/chat/tools/adapter-claude/ReadTool.tsx +20 -42
- package/src/components/chat/tools/adapter-claude/TodoTool.scss +8 -23
- package/src/components/chat/tools/adapter-claude/TodoTool.tsx +24 -11
- package/src/components/chat/tools/adapter-claude/WriteTool.scss +21 -69
- package/src/components/chat/tools/adapter-claude/WriteTool.tsx +22 -58
- package/src/components/chat/tools/adapter-claude/index.ts +4 -10
- package/src/components/chat/tools/adapter-claude/utils.ts +54 -0
- package/src/components/chat/tools/core/ToolCallBox.scss +356 -0
- package/src/components/chat/{ToolGroup.tsx → tools/core/ToolGroup.tsx} +26 -7
- package/src/components/chat/{ToolRenderer.tsx → tools/core/ToolRenderer.tsx} +6 -4
- package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.scss +11 -0
- package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +75 -0
- package/src/components/chat/tools/plugin-chrome-devtools/index.ts +45 -0
- package/src/components/chat/tools/task/GetTaskInfoTool.scss +2 -27
- package/src/components/chat/tools/task/GetTaskInfoTool.tsx +48 -38
- package/src/components/chat/tools/task/ListTasksTool.scss +3 -28
- package/src/components/chat/tools/task/ListTasksTool.tsx +11 -8
- package/src/components/chat/tools/task/StartTasksTool.scss +3 -28
- package/src/components/chat/tools/task/StartTasksTool.tsx +14 -17
- package/src/components/chat/tools/task/components/TaskRow.scss +105 -0
- package/src/components/chat/tools/task/components/TaskRow.tsx +163 -0
- package/src/components/chat/tools/task/components/TaskToolCard.scss +15 -15
- package/src/components/chat/tools/task/components/TaskToolCard.tsx +8 -6
- package/src/components/config/ConfigSectionForm.tsx +12 -1
- package/src/components/config/ConfigSourceSwitch.tsx +12 -34
- package/src/components/config/channelDefinitions.ts +6 -0
- package/src/components/config/configSchema.ts +10 -1
- package/src/components/config/recordEditors/ChannelRecordEditor.scss +1 -0
- package/src/components/config/recordEditors/ChannelRecordEditor.tsx +397 -0
- package/src/components/config/recordEditors/index.tsx +1 -0
- package/src/components/knowledge-base/components/RuleItem.tsx +1 -1
- package/src/components/knowledge-base/components/SpecItem.tsx +1 -1
- package/src/components/sidebar/SessionItem.scss +17 -0
- package/src/components/sidebar/SessionItem.tsx +21 -13
- package/src/hooks/chat/use-chat-adapter.ts +81 -0
- package/src/hooks/chat/use-chat-interaction.ts +26 -0
- package/src/{components/chat/useChatModels.tsx → hooks/chat/use-chat-models.tsx} +117 -22
- package/src/hooks/chat/use-chat-permission-mode.ts +47 -0
- package/src/hooks/chat/use-chat-scroll.ts +51 -0
- package/src/hooks/chat/use-chat-session-actions.ts +153 -0
- package/src/hooks/chat/use-chat-session-messages.ts +262 -0
- package/src/hooks/chat/use-chat-session.ts +63 -0
- package/src/hooks/chat/use-chat-view.ts +39 -0
- package/src/main.tsx +10 -13
- package/src/resources/adapters.ts +20 -0
- package/src/resources/locales/en.json +66 -0
- package/src/resources/locales/zh.json +66 -0
- package/src/runtime-config.ts +52 -0
- package/src/vite-env.d.ts +11 -0
- package/src/ws.ts +5 -3
- package/vite.config.ts +12 -4
- package/dist/assets/channel-jbCEHqbG.js +0 -1
- package/dist/assets/clone-CCRKqS4L.js +0 -1
- package/dist/assets/flowDiagram-v2-4f6560a1-Baslbgn4.js +0 -1
- package/dist/assets/index-B0qfCb1G.css +0 -1
- package/dist/assets/index-CNo75dYr.js +0 -497
- package/src/components/chat/ToolCallBox.scss +0 -137
- package/src/components/chat/useChatSession.ts +0 -370
- /package/src/components/{chat/CodeBlock.scss → CodeBlock.scss} +0 -0
- /package/src/components/chat/{MessageFooter.tsx → Messages/MessageFooter.tsx} +0 -0
- /package/src/components/chat/{CompletionMenu.scss → Sender/CompletionMenu.scss} +0 -0
- /package/src/components/chat/{CompletionMenu.tsx → Sender/CompletionMenu.tsx} +0 -0
- /package/src/components/chat/{ThinkingStatus.scss → Sender/ThinkingStatus.scss} +0 -0
- /package/src/components/chat/{ThinkingStatus.tsx → Sender/ThinkingStatus.tsx} +0 -0
- /package/src/components/chat/{ToolCallBox.tsx → tools/core/ToolCallBox.tsx} +0 -0
- /package/src/components/chat/{ToolGroup.scss → tools/core/ToolGroup.scss} +0 -0
- /package/src/{components/chat/safeSerialize.ts → utils/safe-serialize.ts} +0 -0
|
@@ -6,11 +6,12 @@ import React, { useEffect, useRef, useState } from 'react'
|
|
|
6
6
|
import { useTranslation } from 'react-i18next'
|
|
7
7
|
import useSWR from 'swr'
|
|
8
8
|
|
|
9
|
-
import type { AskUserQuestionParams, SessionInfo, SessionStatus } from '@vibe-forge/core'
|
|
9
|
+
import type { AskUserQuestionParams, ChatMessageContent, SessionInfo, SessionStatus } from '@vibe-forge/core'
|
|
10
10
|
import type { CompletionItem } from './CompletionMenu'
|
|
11
|
+
import type { PermissionMode } from '#~/hooks/chat/use-chat-permission-mode'
|
|
11
12
|
import { CompletionMenu } from './CompletionMenu'
|
|
12
13
|
import { ThinkingStatus } from './ThinkingStatus'
|
|
13
|
-
import { isShortcutMatch } from '
|
|
14
|
+
import { isShortcutMatch } from '../../../utils/shortcutUtils'
|
|
14
15
|
|
|
15
16
|
const { TextArea } = Input
|
|
16
17
|
|
|
@@ -25,8 +26,17 @@ interface ModelSelectGroup {
|
|
|
25
26
|
options: ModelSelectOption[]
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
interface PendingImage {
|
|
30
|
+
id: string
|
|
31
|
+
url: string
|
|
32
|
+
name?: string
|
|
33
|
+
size?: number
|
|
34
|
+
mimeType?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
export function Sender({
|
|
29
38
|
onSend,
|
|
39
|
+
onSendContent,
|
|
30
40
|
sessionStatus,
|
|
31
41
|
onInterrupt,
|
|
32
42
|
onClear,
|
|
@@ -37,9 +47,16 @@ export function Sender({
|
|
|
37
47
|
modelOptions,
|
|
38
48
|
selectedModel,
|
|
39
49
|
onModelChange,
|
|
50
|
+
permissionMode,
|
|
51
|
+
permissionModeOptions,
|
|
52
|
+
onPermissionModeChange,
|
|
53
|
+
selectedAdapter,
|
|
54
|
+
adapterOptions,
|
|
55
|
+
onAdapterChange,
|
|
40
56
|
modelUnavailable
|
|
41
57
|
}: {
|
|
42
58
|
onSend: (text: string) => void
|
|
59
|
+
onSendContent: (content: ChatMessageContent[]) => void
|
|
43
60
|
sessionStatus?: SessionStatus
|
|
44
61
|
onInterrupt: () => void
|
|
45
62
|
onClear?: () => void
|
|
@@ -50,6 +67,12 @@ export function Sender({
|
|
|
50
67
|
modelOptions?: ModelSelectGroup[]
|
|
51
68
|
selectedModel?: string
|
|
52
69
|
onModelChange?: (model: string) => void
|
|
70
|
+
permissionMode: PermissionMode
|
|
71
|
+
permissionModeOptions: Array<{ value: PermissionMode; label: React.ReactNode }>
|
|
72
|
+
onPermissionModeChange: (mode: PermissionMode) => void
|
|
73
|
+
selectedAdapter?: string
|
|
74
|
+
adapterOptions?: Array<{ value: string; label: React.ReactNode }>
|
|
75
|
+
onAdapterChange?: (adapter: string) => void
|
|
53
76
|
modelUnavailable?: boolean
|
|
54
77
|
}) {
|
|
55
78
|
const { t } = useTranslation()
|
|
@@ -63,7 +86,9 @@ export function Sender({
|
|
|
63
86
|
const [showToolsList, setShowToolsList] = useState(false)
|
|
64
87
|
const textareaRef = useRef<TextAreaRef>(null)
|
|
65
88
|
const toolsRef = useRef<HTMLDivElement>(null)
|
|
89
|
+
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
66
90
|
const isMac = navigator.platform.includes('Mac')
|
|
91
|
+
const [pendingImages, setPendingImages] = useState<PendingImage[]>([])
|
|
67
92
|
|
|
68
93
|
const { data: configRes } = useSWR<{
|
|
69
94
|
sources?: {
|
|
@@ -96,8 +121,54 @@ export function Sender({
|
|
|
96
121
|
const [historyIndex, setHistoryIndex] = useState(-1)
|
|
97
122
|
const [draft, setDraft] = useState('')
|
|
98
123
|
|
|
124
|
+
const readFileAsDataUrl = (file: File) => {
|
|
125
|
+
return new Promise<string>((resolve, reject) => {
|
|
126
|
+
const reader = new FileReader()
|
|
127
|
+
reader.onload = () => {
|
|
128
|
+
resolve(typeof reader.result === 'string' ? reader.result : '')
|
|
129
|
+
}
|
|
130
|
+
reader.onerror = () => {
|
|
131
|
+
reject(new Error('read_failed'))
|
|
132
|
+
}
|
|
133
|
+
reader.readAsDataURL(file)
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const addImageFiles = async (files: File[]) => {
|
|
138
|
+
const maxSize = 5 * 1024 * 1024
|
|
139
|
+
for (const file of files) {
|
|
140
|
+
if (!file.type.startsWith('image/')) continue
|
|
141
|
+
if (file.size > maxSize) {
|
|
142
|
+
void message.error(t('chat.imageTooLarge'))
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const url = await readFileAsDataUrl(file)
|
|
147
|
+
if (url === '') {
|
|
148
|
+
void message.error(t('chat.imageReadFailed'))
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
setPendingImages(prev => [
|
|
152
|
+
...prev,
|
|
153
|
+
{
|
|
154
|
+
id: globalThis.crypto?.randomUUID
|
|
155
|
+
? globalThis.crypto.randomUUID()
|
|
156
|
+
: `img-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
157
|
+
url,
|
|
158
|
+
name: file.name,
|
|
159
|
+
size: file.size,
|
|
160
|
+
mimeType: file.type
|
|
161
|
+
}
|
|
162
|
+
])
|
|
163
|
+
} catch (err) {
|
|
164
|
+
void message.error(t('chat.imageReadFailed'))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
99
169
|
const handleSend = () => {
|
|
100
|
-
if (
|
|
170
|
+
if (isThinking) return
|
|
171
|
+
if (input.trim() === '' && pendingImages.length === 0) return
|
|
101
172
|
|
|
102
173
|
if (modelUnavailable) {
|
|
103
174
|
void message.warning(t('chat.modelConfigRequired'))
|
|
@@ -105,12 +176,31 @@ export function Sender({
|
|
|
105
176
|
}
|
|
106
177
|
|
|
107
178
|
if (interactionRequest != null && onInteractionResponse != null) {
|
|
179
|
+
if (pendingImages.length > 0) {
|
|
180
|
+
void message.warning(t('chat.imageNotSupportedInInteraction'))
|
|
181
|
+
return
|
|
182
|
+
}
|
|
108
183
|
onInteractionResponse(interactionRequest.id, input.trim())
|
|
109
184
|
setInput('')
|
|
110
185
|
return
|
|
111
186
|
}
|
|
112
187
|
|
|
113
|
-
|
|
188
|
+
if (pendingImages.length > 0) {
|
|
189
|
+
const content: ChatMessageContent[] = []
|
|
190
|
+
if (input.trim() !== '') {
|
|
191
|
+
content.push({ type: 'text', text: input.trim() })
|
|
192
|
+
}
|
|
193
|
+
content.push(...pendingImages.map(img => ({
|
|
194
|
+
type: 'image',
|
|
195
|
+
url: img.url,
|
|
196
|
+
name: img.name,
|
|
197
|
+
size: img.size,
|
|
198
|
+
mimeType: img.mimeType
|
|
199
|
+
})))
|
|
200
|
+
onSendContent(content)
|
|
201
|
+
} else {
|
|
202
|
+
onSend(input)
|
|
203
|
+
}
|
|
114
204
|
|
|
115
205
|
// Save to local storage history
|
|
116
206
|
try {
|
|
@@ -122,11 +212,48 @@ export function Sender({
|
|
|
122
212
|
}
|
|
123
213
|
|
|
124
214
|
setInput('')
|
|
215
|
+
setPendingImages([])
|
|
125
216
|
setDraft('')
|
|
126
217
|
setShowCompletion(false)
|
|
127
218
|
setHistoryIndex(-1)
|
|
128
219
|
}
|
|
129
220
|
|
|
221
|
+
const handleImageUpload = () => {
|
|
222
|
+
if (isThinking) return
|
|
223
|
+
if (modelUnavailable) {
|
|
224
|
+
void message.warning(t('chat.modelConfigRequired'))
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
if (interactionRequest != null) {
|
|
228
|
+
void message.warning(t('chat.imageNotSupportedInInteraction'))
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
fileInputRef.current?.click()
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const handleImageFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
235
|
+
const fileList = Array.from(event.target.files ?? [])
|
|
236
|
+
if (fileList.length === 0) return
|
|
237
|
+
await addImageFiles(fileList)
|
|
238
|
+
event.target.value = ''
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const handleRemovePendingImage = (id: string) => {
|
|
242
|
+
setPendingImages(prev => prev.filter(img => img.id !== id))
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const handlePaste = async (event: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
|
246
|
+
const items = Array.from(event.clipboardData?.items ?? [])
|
|
247
|
+
const files = items
|
|
248
|
+
.filter(item => item.kind === 'file' && item.type.startsWith('image/'))
|
|
249
|
+
.map(item => item.getAsFile())
|
|
250
|
+
.filter((file): file is File => file != null)
|
|
251
|
+
if (files.length > 0) {
|
|
252
|
+
event.preventDefault()
|
|
253
|
+
await addImageFiles(files)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
130
257
|
const clearInputValue = () => {
|
|
131
258
|
if (input === '') return
|
|
132
259
|
setInput('')
|
|
@@ -422,6 +549,18 @@ export function Sender({
|
|
|
422
549
|
{t('chat.modelConfigRequired')}
|
|
423
550
|
</div>
|
|
424
551
|
)}
|
|
552
|
+
{pendingImages.length > 0 && (
|
|
553
|
+
<div className='pending-images'>
|
|
554
|
+
{pendingImages.map(img => (
|
|
555
|
+
<div key={img.id} className='pending-image'>
|
|
556
|
+
<img src={img.url} alt={img.name ?? 'image'} />
|
|
557
|
+
<div className='pending-image-remove' onClick={() => handleRemovePendingImage(img.id)}>
|
|
558
|
+
<span className='material-symbols-rounded'>close</span>
|
|
559
|
+
</div>
|
|
560
|
+
</div>
|
|
561
|
+
))}
|
|
562
|
+
</div>
|
|
563
|
+
)}
|
|
425
564
|
{showCompletion && (
|
|
426
565
|
<CompletionMenu
|
|
427
566
|
items={completionItems}
|
|
@@ -437,12 +576,21 @@ export function Sender({
|
|
|
437
576
|
value={input}
|
|
438
577
|
onChange={handleInputChange}
|
|
439
578
|
onKeyDown={handleKeyDown}
|
|
579
|
+
onPaste={handlePaste}
|
|
440
580
|
autoSize={{ minRows: 1, maxRows: 10 }}
|
|
441
581
|
variant='borderless'
|
|
442
582
|
disabled={modelUnavailable}
|
|
443
583
|
/>
|
|
444
584
|
|
|
445
585
|
<div className='chat-input-toolbar'>
|
|
586
|
+
<input
|
|
587
|
+
ref={fileInputRef}
|
|
588
|
+
type='file'
|
|
589
|
+
accept='image/*'
|
|
590
|
+
multiple
|
|
591
|
+
onChange={handleImageFileChange}
|
|
592
|
+
className='file-input-hidden'
|
|
593
|
+
/>
|
|
446
594
|
<div className='toolbar-left'>
|
|
447
595
|
<Tooltip title='快捷指令'>
|
|
448
596
|
<span>
|
|
@@ -467,7 +615,7 @@ export function Sender({
|
|
|
467
615
|
</Tooltip>
|
|
468
616
|
<Tooltip title='上传图片'>
|
|
469
617
|
<span>
|
|
470
|
-
<div className='toolbar-btn' onClick={
|
|
618
|
+
<div className='toolbar-btn' onClick={handleImageUpload}>
|
|
471
619
|
<span className='material-symbols-rounded'>image</span>
|
|
472
620
|
</div>
|
|
473
621
|
</span>
|
|
@@ -504,6 +652,22 @@ export function Sender({
|
|
|
504
652
|
</div>
|
|
505
653
|
|
|
506
654
|
<div className='toolbar-right'>
|
|
655
|
+
{adapterOptions && adapterOptions.length > 1 && (
|
|
656
|
+
<Select
|
|
657
|
+
className='adapter-select'
|
|
658
|
+
classNames={{ popup: { root: 'adapter-select-popup' } }}
|
|
659
|
+
value={selectedAdapter}
|
|
660
|
+
options={adapterOptions}
|
|
661
|
+
showSearch={false}
|
|
662
|
+
allowClear={false}
|
|
663
|
+
disabled={modelUnavailable || isThinking}
|
|
664
|
+
onChange={(value) => onAdapterChange?.(value)}
|
|
665
|
+
placeholder={t('chat.adapterSelectPlaceholder', { defaultValue: 'Adapter' })}
|
|
666
|
+
optionLabelProp='label'
|
|
667
|
+
popupMatchSelectWidth={false}
|
|
668
|
+
/>
|
|
669
|
+
)}
|
|
670
|
+
|
|
507
671
|
<Select
|
|
508
672
|
className='model-select'
|
|
509
673
|
classNames={{ popup: { root: 'model-select-popup' } }}
|
|
@@ -522,6 +686,20 @@ export function Sender({
|
|
|
522
686
|
popupMatchSelectWidth={false}
|
|
523
687
|
/>
|
|
524
688
|
|
|
689
|
+
<Select
|
|
690
|
+
className='permission-mode-select'
|
|
691
|
+
classNames={{ popup: { root: 'permission-mode-select-popup' } }}
|
|
692
|
+
value={permissionMode}
|
|
693
|
+
options={permissionModeOptions}
|
|
694
|
+
showSearch={false}
|
|
695
|
+
allowClear={false}
|
|
696
|
+
disabled={modelUnavailable || isThinking}
|
|
697
|
+
onChange={(value) => onPermissionModeChange(value)}
|
|
698
|
+
placeholder='权限模式'
|
|
699
|
+
optionLabelProp='label'
|
|
700
|
+
popupMatchSelectWidth={false}
|
|
701
|
+
/>
|
|
702
|
+
|
|
525
703
|
<div
|
|
526
704
|
className={`chat-send-btn ${input.trim() !== '' && !modelUnavailable ? 'active' : ''} ${isThinking ? 'thinking' : ''} ${modelUnavailable ? 'disabled' : ''}`}
|
|
527
705
|
onClick={modelUnavailable ? undefined : (isThinking ? onInterrupt : handleSend)}
|
|
@@ -1,10 +1,172 @@
|
|
|
1
1
|
import type { ChatMessageContent } from '@vibe-forge/core'
|
|
2
|
-
import React from 'react'
|
|
3
2
|
import { useTranslation } from 'react-i18next'
|
|
4
|
-
import { CodeBlock } from '
|
|
5
|
-
import { MarkdownContent } from '
|
|
6
|
-
import { ToolCallBox } from '
|
|
7
|
-
import { safeJsonStringify } from '
|
|
3
|
+
import { CodeBlock } from '#~/components/CodeBlock'
|
|
4
|
+
import { MarkdownContent } from '#~/components/MarkdownContent'
|
|
5
|
+
import { ToolCallBox } from './core/ToolCallBox'
|
|
6
|
+
import { safeJsonStringify, toSerializable } from '#~/utils/safe-serialize'
|
|
7
|
+
|
|
8
|
+
interface StructuredTextBlock {
|
|
9
|
+
type: 'text'
|
|
10
|
+
text: string
|
|
11
|
+
format: 'text' | 'markdown'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface StructuredImageBlock {
|
|
15
|
+
type: 'image'
|
|
16
|
+
src: string
|
|
17
|
+
alt?: string
|
|
18
|
+
title?: string
|
|
19
|
+
width?: number
|
|
20
|
+
height?: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type StructuredBlock = StructuredTextBlock | StructuredImageBlock
|
|
24
|
+
|
|
25
|
+
function parseStructuredInput(value: unknown) {
|
|
26
|
+
if (typeof value !== 'string') {
|
|
27
|
+
return value
|
|
28
|
+
}
|
|
29
|
+
const trimmed = value.trim()
|
|
30
|
+
if (
|
|
31
|
+
(trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
|
32
|
+
(trimmed.startsWith('[') && trimmed.endsWith(']'))
|
|
33
|
+
) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(trimmed)
|
|
36
|
+
} catch {
|
|
37
|
+
return value
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return value
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveImageSource(value: Record<string, unknown>) {
|
|
44
|
+
const directUrl = typeof value.url === 'string'
|
|
45
|
+
? value.url
|
|
46
|
+
: typeof value.src === 'string'
|
|
47
|
+
? value.src
|
|
48
|
+
: typeof value.image_url === 'string'
|
|
49
|
+
? value.image_url
|
|
50
|
+
: typeof value.imageUrl === 'string'
|
|
51
|
+
? value.imageUrl
|
|
52
|
+
: typeof value.dataUrl === 'string'
|
|
53
|
+
? value.dataUrl
|
|
54
|
+
: null
|
|
55
|
+
if (directUrl) {
|
|
56
|
+
return directUrl
|
|
57
|
+
}
|
|
58
|
+
const source = value.source != null && typeof value.source === 'object'
|
|
59
|
+
? (value.source as Record<string, unknown>)
|
|
60
|
+
: null
|
|
61
|
+
const data = typeof value.data === 'string'
|
|
62
|
+
? value.data
|
|
63
|
+
: typeof value.base64 === 'string'
|
|
64
|
+
? value.base64
|
|
65
|
+
: source != null && typeof source.data === 'string'
|
|
66
|
+
? source.data
|
|
67
|
+
: null
|
|
68
|
+
if (!data) {
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
const mimeType = typeof value.mimeType === 'string'
|
|
72
|
+
? value.mimeType
|
|
73
|
+
: typeof value.mime_type === 'string'
|
|
74
|
+
? value.mime_type
|
|
75
|
+
: source != null && typeof source.media_type === 'string'
|
|
76
|
+
? source.media_type
|
|
77
|
+
: source != null && typeof source.mimeType === 'string'
|
|
78
|
+
? source.mimeType
|
|
79
|
+
: source != null && typeof source.mime_type === 'string'
|
|
80
|
+
? source.mime_type
|
|
81
|
+
: 'image/png'
|
|
82
|
+
return `data:${mimeType};base64,${data}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseBlock(value: unknown): StructuredBlock | null {
|
|
86
|
+
if (value == null || typeof value !== 'object') {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
const obj = value as Record<string, unknown>
|
|
90
|
+
const rawType = typeof obj.type === 'string' ? obj.type.toLowerCase() : ''
|
|
91
|
+
if (rawType === 'text' || rawType === 'markdown' || rawType === 'md') {
|
|
92
|
+
const text = typeof obj.text === 'string'
|
|
93
|
+
? obj.text
|
|
94
|
+
: typeof obj.content === 'string'
|
|
95
|
+
? obj.content
|
|
96
|
+
: null
|
|
97
|
+
if (text == null) {
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
const rawFormat = typeof obj.format === 'string' ? obj.format.toLowerCase() : 'markdown'
|
|
101
|
+
const format = rawType === 'text'
|
|
102
|
+
? (rawFormat === 'text' || rawFormat === 'plain' ? 'text' : 'markdown')
|
|
103
|
+
: 'markdown'
|
|
104
|
+
return { type: 'text', text, format }
|
|
105
|
+
}
|
|
106
|
+
if (rawType === 'image') {
|
|
107
|
+
const src = resolveImageSource(obj)
|
|
108
|
+
if (!src) {
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
const alt = typeof obj.alt === 'string' ? obj.alt : undefined
|
|
112
|
+
const title = typeof obj.title === 'string' ? obj.title : undefined
|
|
113
|
+
const width = typeof obj.width === 'number' ? obj.width : undefined
|
|
114
|
+
const height = typeof obj.height === 'number' ? obj.height : undefined
|
|
115
|
+
return { type: 'image', src, alt, title, width, height }
|
|
116
|
+
}
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getStructuredBlocks(value: unknown): StructuredBlock[] | null {
|
|
121
|
+
const serializable = toSerializable(value)
|
|
122
|
+
const parsed = parseStructuredInput(serializable)
|
|
123
|
+
if (Array.isArray(parsed)) {
|
|
124
|
+
const blocks = parsed.map(parseBlock)
|
|
125
|
+
return blocks.every(Boolean) ? (blocks as StructuredBlock[]) : null
|
|
126
|
+
}
|
|
127
|
+
if (parsed != null && typeof parsed === 'object') {
|
|
128
|
+
const container = parsed as Record<string, unknown>
|
|
129
|
+
const content = container.content ?? container.items ?? container.blocks
|
|
130
|
+
if (Array.isArray(content)) {
|
|
131
|
+
const blocks = content.map(parseBlock)
|
|
132
|
+
return blocks.every(Boolean) ? (blocks as StructuredBlock[]) : null
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const single = parseBlock(parsed)
|
|
136
|
+
return single ? [single] : null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function StructuredToolResult({ blocks }: { blocks: StructuredBlock[] }) {
|
|
140
|
+
return (
|
|
141
|
+
<div className='tool-result-structured'>
|
|
142
|
+
{blocks.map((block, index) => {
|
|
143
|
+
if (block.type === 'text') {
|
|
144
|
+
return (
|
|
145
|
+
<div className='tool-result-text' key={`text-${index}`}>
|
|
146
|
+
{block.format === 'markdown'
|
|
147
|
+
? <MarkdownContent content={block.text} />
|
|
148
|
+
: <div className='tool-result-text-content'>{block.text}</div>}
|
|
149
|
+
</div>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
return (
|
|
153
|
+
<div className='tool-result-image-wrapper' key={`image-${index}`}>
|
|
154
|
+
<img
|
|
155
|
+
className='tool-result-image'
|
|
156
|
+
src={block.src}
|
|
157
|
+
alt={block.alt ?? ''}
|
|
158
|
+
width={block.width}
|
|
159
|
+
height={block.height}
|
|
160
|
+
/>
|
|
161
|
+
{block.title != null && block.title.length > 0 && (
|
|
162
|
+
<div className='tool-result-image-caption'>{block.title}</div>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
)
|
|
166
|
+
})}
|
|
167
|
+
</div>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
8
170
|
|
|
9
171
|
export function DefaultTool({
|
|
10
172
|
item,
|
|
@@ -14,17 +176,16 @@ export function DefaultTool({
|
|
|
14
176
|
resultItem?: Extract<ChatMessageContent, { type: 'tool_result' }>
|
|
15
177
|
}) {
|
|
16
178
|
const { t } = useTranslation()
|
|
179
|
+
const structuredBlocks = resultItem != null ? getStructuredBlocks(resultItem.content) : null
|
|
17
180
|
return (
|
|
18
181
|
<div className='tool-group'>
|
|
19
182
|
<ToolCallBox
|
|
20
183
|
header={
|
|
21
|
-
|
|
22
|
-
<span className='material-symbols-rounded'
|
|
23
|
-
<span>{item.name}</span>
|
|
24
|
-
<span
|
|
25
|
-
|
|
26
|
-
</span>
|
|
27
|
-
</>
|
|
184
|
+
<div className='tool-header-content'>
|
|
185
|
+
<span className='material-symbols-rounded tool-header-icon'>build</span>
|
|
186
|
+
<span className='tool-header-title'>{item.name}</span>
|
|
187
|
+
<span className='tool-header-hint'>{t('chat.tools.call')}</span>
|
|
188
|
+
</div>
|
|
28
189
|
}
|
|
29
190
|
content={
|
|
30
191
|
<div className='tool-content'>
|
|
@@ -40,20 +201,22 @@ export function DefaultTool({
|
|
|
40
201
|
type='result'
|
|
41
202
|
isError={resultItem.is_error}
|
|
42
203
|
header={
|
|
43
|
-
|
|
44
|
-
<span className='material-symbols-rounded'
|
|
204
|
+
<div className='tool-header-content'>
|
|
205
|
+
<span className='material-symbols-rounded tool-header-icon'>
|
|
45
206
|
{resultItem.is_error === true ? 'error' : 'check_circle'}
|
|
46
207
|
</span>
|
|
47
|
-
<span>{t('chat.result')}</span>
|
|
48
|
-
|
|
208
|
+
<span className='tool-header-title'>{t('chat.result')}</span>
|
|
209
|
+
</div>
|
|
49
210
|
}
|
|
50
211
|
content={
|
|
51
212
|
<div className='tool-content'>
|
|
52
|
-
{
|
|
53
|
-
?
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
213
|
+
{structuredBlocks != null
|
|
214
|
+
? <StructuredToolResult blocks={structuredBlocks} />
|
|
215
|
+
: (typeof resultItem.content === 'string'
|
|
216
|
+
? (resultItem.content.startsWith('```')
|
|
217
|
+
? <MarkdownContent content={resultItem.content} />
|
|
218
|
+
: <CodeBlock code={resultItem.content} lang='text' />)
|
|
219
|
+
: <CodeBlock code={safeJsonStringify(resultItem.content, 2)} lang='json' />)}
|
|
57
220
|
</div>
|
|
58
221
|
}
|
|
59
222
|
/>
|
|
@@ -1,71 +1,87 @@
|
|
|
1
1
|
.tool-group.bash-tool {
|
|
2
|
-
.
|
|
2
|
+
.tool-call-header {
|
|
3
|
+
height: auto;
|
|
4
|
+
min-height: 32px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.bash-tool__header {
|
|
8
|
+
width: 100%;
|
|
9
|
+
min-width: 0;
|
|
3
10
|
display: flex;
|
|
4
11
|
align-items: center;
|
|
5
|
-
gap:
|
|
12
|
+
gap: 8px;
|
|
13
|
+
}
|
|
6
14
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
15
|
+
.bash-tool__icon {
|
|
16
|
+
font-size: 16px;
|
|
17
|
+
color: var(--sub-text-color);
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
width: 16px;
|
|
22
|
+
height: 16px;
|
|
23
|
+
flex-shrink: 0;
|
|
24
|
+
align-self: center;
|
|
25
|
+
}
|
|
10
26
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
27
|
+
.bash-tool__header-main {
|
|
28
|
+
flex: 1;
|
|
29
|
+
min-width: 0;
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
gap: 2px;
|
|
14
33
|
}
|
|
15
34
|
|
|
16
|
-
.bash-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
.bash-tool__command-row {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: 6px;
|
|
39
|
+
min-width: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.bash-tool__command-text {
|
|
43
|
+
flex: 1;
|
|
44
|
+
min-width: 0;
|
|
21
45
|
overflow: hidden;
|
|
22
46
|
text-overflow: ellipsis;
|
|
23
47
|
white-space: nowrap;
|
|
24
|
-
flex: 1;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.tool-reason {
|
|
28
|
-
margin: 0 0 4px 0;
|
|
29
48
|
font-size: 12px;
|
|
30
|
-
color: var(--
|
|
31
|
-
|
|
49
|
+
color: var(--text-color);
|
|
50
|
+
}
|
|
32
51
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
background-color: transparent;
|
|
37
|
-
padding: 0;
|
|
52
|
+
.bash-tool__command-text--clickable {
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
color: var(--text-color);
|
|
38
55
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
padding: 0 !important;
|
|
42
|
-
}
|
|
56
|
+
&:hover {
|
|
57
|
+
text-shadow: 0 0 .5px currentColor;
|
|
43
58
|
}
|
|
44
59
|
}
|
|
45
60
|
|
|
46
|
-
.bash-
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
.bash-tool__reason-row {
|
|
62
|
+
display: flex;
|
|
63
|
+
min-width: 0;
|
|
64
|
+
}
|
|
49
65
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
.bash-tool__reason-text {
|
|
67
|
+
min-width: 0;
|
|
68
|
+
overflow: hidden;
|
|
69
|
+
text-overflow: ellipsis;
|
|
70
|
+
white-space: nowrap;
|
|
71
|
+
}
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
.bash-tool__header-tags {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 6px;
|
|
77
|
+
flex-shrink: 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.bash-tool__command-detail {
|
|
81
|
+
margin-bottom: 6px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.tool-scroll {
|
|
85
|
+
max-height: 270px;
|
|
70
86
|
}
|
|
71
87
|
}
|