@vibe-forge/client 0.10.1 → 0.11.1
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/dist/assets/{arc-C1rWFTer.js → arc-CSepokz3.js} +1 -1
- package/dist/assets/{blockDiagram-c4efeb88-DlZ9x70F.js → blockDiagram-c4efeb88-D0ARcoNf.js} +1 -1
- package/dist/assets/{c4Diagram-c83219d4-BKKxi__y.js → c4Diagram-c83219d4-BysYF9kP.js} +1 -1
- package/dist/assets/channel-CeKPk6Nd.js +1 -0
- package/dist/assets/{classDiagram-beda092f-CVGPySZq.js → classDiagram-beda092f-BG1GhIOL.js} +1 -1
- package/dist/assets/{classDiagram-v2-2358418a-7kp8GVVj.js → classDiagram-v2-2358418a-Dd08uGSH.js} +1 -1
- package/dist/assets/clone-CrkD2PuD.js +1 -0
- package/dist/assets/{createText-1719965b-Dykv8kT9.js → createText-1719965b-CigPEIEn.js} +1 -1
- package/dist/assets/{cssMode-B59COYVW.js → cssMode-MjflyEfm.js} +1 -1
- package/dist/assets/{edges-96097737-CkZ1ZBro.js → edges-96097737-DuTBJJRv.js} +1 -1
- package/dist/assets/{erDiagram-0228fc6a-281ADcRp.js → erDiagram-0228fc6a-Cp1bL7Y7.js} +1 -1
- package/dist/assets/{flowDb-c6c81e3f-BQjX_flP.js → flowDb-c6c81e3f-BfKbhiq5.js} +1 -1
- package/dist/assets/{flowDiagram-50d868cf-DMHZTjES.js → flowDiagram-50d868cf-m7gGc3PK.js} +1 -1
- package/dist/assets/flowDiagram-v2-4f6560a1-4ZU4bdp1.js +1 -0
- package/dist/assets/{flowchart-elk-definition-6af322e1-CI3yz4z8.js → flowchart-elk-definition-6af322e1-EVeTDRRK.js} +1 -1
- package/dist/assets/{freemarker2-DWnWjibn.js → freemarker2-Bb3-QAIN.js} +1 -1
- package/dist/assets/{ganttDiagram-a2739b55-B3IING9L.js → ganttDiagram-a2739b55-DslB2U0R.js} +1 -1
- package/dist/assets/{gitGraphDiagram-82fe8481-CnArIr_T.js → gitGraphDiagram-82fe8481-C-KFWMXL.js} +1 -1
- package/dist/assets/{graph-BZ1F0Yve.js → graph-CukaUc0o.js} +1 -1
- package/dist/assets/{handlebars-C1QH9qTz.js → handlebars-C4le-2Y6.js} +1 -1
- package/dist/assets/{html-D1NkqHjC.js → html-CjNiRs5S.js} +1 -1
- package/dist/assets/{htmlMode-DAZCE_rA.js → htmlMode-B73_3-We.js} +1 -1
- package/dist/assets/{index-5325376f-Da9zSHjA.js → index-5325376f-CVISZFPw.js} +1 -1
- package/dist/assets/{index-C0vjF3D0.js → index-BZosmb5_.js} +336 -336
- package/dist/assets/index-C1oh0w9H.css +32 -0
- package/dist/assets/{infoDiagram-8eee0895-DYbFvRM7.js → infoDiagram-8eee0895-DoirLE1K.js} +1 -1
- package/dist/assets/{javascript-CoMjGRHa.js → javascript-BDjnqJFP.js} +1 -1
- package/dist/assets/{journeyDiagram-c64418c1-Boebox0b.js → journeyDiagram-c64418c1-Ckn-p2CM.js} +1 -1
- package/dist/assets/{jsonMode-D__gAvuz.js → jsonMode-C-ftOc5j.js} +1 -1
- package/dist/assets/{layout-CTcHNbHp.js → layout-Z7yUG7hB.js} +1 -1
- package/dist/assets/{line-4AwinCz2.js → line-DPG_cfAy.js} +1 -1
- package/dist/assets/{linear-CeSMLzJW.js → linear--GSeVfMi.js} +1 -1
- package/dist/assets/{liquid-DZF6egdE.js → liquid-COiLZ9py.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-6K4lv5S2.js → lspLanguageFeatures-DGmhryFq.js} +1 -1
- package/dist/assets/{mdx-Cnt4ka6w.js → mdx-BpL87Gej.js} +1 -1
- package/dist/assets/{mermaid.core-B0yG5s4D.js → mermaid.core-Cg1CCDo6.js} +4 -4
- package/dist/assets/{mindmap-definition-8da855dc-KJEvXMKj.js → mindmap-definition-8da855dc-CKDof1lD.js} +1 -1
- package/dist/assets/{pieDiagram-a8764435-17nFAXPJ.js → pieDiagram-a8764435-DwvCaZVE.js} +1 -1
- package/dist/assets/{python-DA3TtjDv.js → python-63dBmWV_.js} +1 -1
- package/dist/assets/{quadrantDiagram-1e28029f-Dt4vubi-.js → quadrantDiagram-1e28029f-CkzYBQpy.js} +1 -1
- package/dist/assets/{razor-CWDJgvX_.js → razor-C50tBqEZ.js} +1 -1
- package/dist/assets/{requirementDiagram-08caed73-H6aDyDK-.js → requirementDiagram-08caed73-Brgdjqf4.js} +1 -1
- package/dist/assets/{sankeyDiagram-a04cb91d-DxsVtbjI.js → sankeyDiagram-a04cb91d-CGkYexrs.js} +1 -1
- package/dist/assets/{sequenceDiagram-c5b8d532-BHa148XJ.js → sequenceDiagram-c5b8d532-D0wE-_J8.js} +1 -1
- package/dist/assets/{stateDiagram-1ecb1508-DgwBm8LO.js → stateDiagram-1ecb1508-BYb3NCXZ.js} +1 -1
- package/dist/assets/{stateDiagram-v2-c2b004d7-BK7IQLVc.js → stateDiagram-v2-c2b004d7-DrPqi4Pt.js} +1 -1
- package/dist/assets/{styles-b4e223ce-DzW27Bc-.js → styles-b4e223ce-DD66TIO4.js} +1 -1
- package/dist/assets/{styles-ca3715f6-Dex2GiLT.js → styles-ca3715f6-iy02LHIV.js} +1 -1
- package/dist/assets/{styles-d45a18b0-B6fGtDKS.js → styles-d45a18b0-BgqAgJyW.js} +1 -1
- package/dist/assets/{svgDrawCommon-b86b1483-B4HYgfV5.js → svgDrawCommon-b86b1483-CDq7ugnw.js} +1 -1
- package/dist/assets/{timeline-definition-faaaa080--QSbWb25.js → timeline-definition-faaaa080-DzcLLjK0.js} +1 -1
- package/dist/assets/{tsMode-ZM7ocZCH.js → tsMode-BFRFI4ct.js} +1 -1
- package/dist/assets/{typescript-CKWDmBCc.js → typescript-CBZQRAPv.js} +1 -1
- package/dist/assets/{xml-DuEUAzPi.js → xml-BpWm6upt.js} +1 -1
- package/dist/assets/{xychartDiagram-f5964ef8-D09Zkv2K.js → xychartDiagram-f5964ef8-zBN8FmLQ.js} +1 -1
- package/dist/assets/{yaml-DL7QPRYk.js → yaml-CqbJPiIP.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +10 -10
- package/src/api/git.ts +78 -0
- package/src/api.ts +24 -0
- package/src/components/chat/ChatHeader.tsx +4 -0
- package/src/components/chat/ChatHistoryView.tsx +22 -13
- package/src/components/chat/git-controls/BranchSwitcherDropdown.tsx +157 -0
- package/src/components/chat/git-controls/ChatGitControls.scss +616 -0
- package/src/components/chat/git-controls/ChatGitControls.tsx +151 -0
- package/src/components/chat/git-controls/GitCommitModal.tsx +199 -0
- package/src/components/chat/git-controls/GitCommitModalParts.tsx +151 -0
- package/src/components/chat/git-controls/GitOperationsDropdown.tsx +123 -0
- package/src/components/chat/git-controls/GitPushModal.tsx +106 -0
- package/src/components/chat/git-controls/GitWorktreeDropdown.tsx +68 -0
- package/src/components/chat/git-controls/git-branch-utils.ts +88 -0
- package/src/components/chat/git-controls/git-commit-utils.ts +79 -0
- package/src/components/chat/git-controls/git-mutation-utils.ts +69 -0
- package/src/components/chat/git-controls/git-operation-utils.ts +98 -0
- package/src/components/chat/git-controls/git-worktree-utils.ts +49 -0
- package/src/components/chat/git-controls/use-chat-git-commit.ts +185 -0
- package/src/components/chat/git-controls/use-chat-git-controls.ts +200 -0
- package/src/components/chat/git-controls/use-chat-git-push-state.ts +19 -0
- package/src/components/chat/git-controls/use-chat-git-worktrees.ts +39 -0
- package/src/components/chat/messages/MessageStatusNotice.scss +163 -0
- package/src/components/chat/messages/MessageStatusNotice.tsx +48 -0
- package/src/components/chat/messages/build-chat-history-status-notices.ts +138 -0
- package/src/components/chat/sender/@components/sender-body/SenderBody.tsx +0 -24
- package/src/components/chat/sender/@core/build-sender-controller-result.ts +0 -6
- package/src/components/chat/sender/@hooks/use-sender-controller.ts +0 -2
- package/src/components/chat/sender/@types/sender-props.ts +0 -3
- package/src/components/chat/sender/Sender.scss +0 -58
- package/src/components/chat/sender/Sender.tsx +0 -2
- package/src/components/chat/tools/DefaultTool.tsx +84 -208
- package/src/components/chat/tools/adapter-claude/ClaudeEditDiff.tsx +30 -0
- package/src/components/chat/tools/adapter-claude/GenericClaudeTool.scss +128 -0
- package/src/components/chat/tools/adapter-claude/GenericClaudeTool.tsx +119 -0
- package/src/components/chat/tools/adapter-claude/claude-tool-edit-builders.ts +109 -0
- package/src/components/chat/tools/adapter-claude/claude-tool-field-sections.tsx +83 -0
- package/src/components/chat/tools/adapter-claude/claude-tool-operation-builders.ts +135 -0
- package/src/components/chat/tools/adapter-claude/claude-tool-presentation.ts +61 -0
- package/src/components/chat/tools/adapter-claude/claude-tool-shared.ts +185 -0
- package/src/components/chat/tools/adapter-claude/claude-tool-summary.ts +76 -0
- package/src/components/chat/tools/adapter-claude/claude-tool-system-builders.ts +125 -0
- package/src/components/chat/tools/adapter-claude/claude-tool-task-builders.ts +148 -0
- package/src/components/chat/tools/adapter-claude/index.ts +24 -15
- package/src/components/chat/tools/core/ToolCallBox.scss +362 -36
- package/src/components/chat/tools/core/ToolCallBox.tsx +35 -13
- package/src/components/chat/tools/core/ToolDiffViewer.scss +138 -0
- package/src/components/chat/tools/core/ToolDiffViewer.tsx +180 -0
- package/src/components/chat/tools/core/ToolGroup.scss +52 -74
- package/src/components/chat/tools/core/ToolGroup.tsx +25 -40
- package/src/components/chat/tools/core/ToolRenderer.tsx +3 -3
- package/src/components/chat/tools/core/ToolResultContent.tsx +66 -0
- package/src/components/chat/tools/core/ToolSummaryHeader.tsx +67 -0
- package/src/components/chat/tools/core/generic-tool-presentation.ts +661 -0
- package/src/components/chat/tools/core/tool-content-presence.ts +57 -0
- package/src/components/chat/tools/core/tool-display.ts +203 -0
- package/src/components/chat/tools/core/tool-field-sections.tsx +132 -0
- package/src/components/chat/tools/core/tool-result-content-utils.ts +171 -0
- package/src/components/chat/tools/core/tool-summary.ts +206 -0
- package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +59 -53
- package/src/components/chat/tools/task/GetTaskInfoTool.tsx +26 -9
- package/src/components/chat/tools/task/ListTasksTool.tsx +22 -9
- package/src/components/chat/tools/task/StartTasksTool.tsx +22 -9
- package/src/hooks/chat/interaction-state.ts +29 -9
- package/src/hooks/chat/session-view-cache.ts +80 -0
- package/src/hooks/chat/use-chat-scroll.ts +2 -2
- package/src/hooks/chat/use-chat-session-messages.ts +139 -39
- package/src/hooks/chat/use-chat-session.ts +2 -2
- package/src/resources/locales/en.json +149 -0
- package/src/resources/locales/zh.json +149 -0
- package/src/routes/ChatRoute.tsx +24 -27
- package/src/utils/strip-ansi.ts +26 -0
- package/dist/assets/channel-F1aqMANO.js +0 -1
- package/dist/assets/clone-B-GCuXNo.js +0 -1
- package/dist/assets/flowDiagram-v2-4f6560a1-C5FzdVl1.js +0 -1
- package/dist/assets/index-vzEbM21t.css +0 -32
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
|
|
3
|
+
import type { ChatMessageContent } from '@vibe-forge/core'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
buildClaudeToolPresentation,
|
|
7
|
+
getClaudeToolBaseName,
|
|
8
|
+
isClaudeToolName
|
|
9
|
+
} from '../adapter-claude/claude-tool-presentation'
|
|
10
|
+
import { getClaudeToolSummaryText } from '../adapter-claude/claude-tool-summary'
|
|
11
|
+
import { buildGenericToolPresentation } from './generic-tool-presentation'
|
|
12
|
+
|
|
13
|
+
export type ToolUseItem = Extract<ChatMessageContent, { type: 'tool_use' }>
|
|
14
|
+
type Translate = (key: string, options?: Record<string, unknown>) => string
|
|
15
|
+
|
|
16
|
+
const HUMANIZED_SEGMENT_SEPARATOR = /[_:-]+/g
|
|
17
|
+
const GENERIC_TOOL_NAMESPACE_PREFIXES = new Set(['adapter', 'agent', 'mcp', 'plugin', 'tool'])
|
|
18
|
+
|
|
19
|
+
const humanizeToolSegment = (value: string) =>
|
|
20
|
+
value
|
|
21
|
+
.replace(HUMANIZED_SEGMENT_SEPARATOR, ' ')
|
|
22
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
23
|
+
.trim()
|
|
24
|
+
|
|
25
|
+
const getToolNameSegments = (name: string) => (
|
|
26
|
+
name.includes('__') ? name.split('__').filter(Boolean) : name.split(':').filter(Boolean)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
export const formatToolName = (name: string) => {
|
|
30
|
+
if (name.startsWith('mcp__ChromeDevtools__')) {
|
|
31
|
+
return name.replace('mcp__ChromeDevtools__', '')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const namespaceSegments = name.includes('__') ? name.split('__').filter(Boolean) : []
|
|
35
|
+
const lastSegment = namespaceSegments.length > 0
|
|
36
|
+
? namespaceSegments[namespaceSegments.length - 1]
|
|
37
|
+
: name.split(':').pop() ?? name
|
|
38
|
+
return humanizeToolSegment(lastSegment)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const getToolInputPreview = (input: unknown) => {
|
|
42
|
+
if (input == null || typeof input !== 'object' || Array.isArray(input)) {
|
|
43
|
+
return undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const record = input as Record<string, unknown>
|
|
47
|
+
const preferredKeys = [
|
|
48
|
+
'file_path',
|
|
49
|
+
'path',
|
|
50
|
+
'url',
|
|
51
|
+
'query',
|
|
52
|
+
'pattern',
|
|
53
|
+
'command',
|
|
54
|
+
'selector',
|
|
55
|
+
'taskId',
|
|
56
|
+
'subject',
|
|
57
|
+
'skill',
|
|
58
|
+
'title'
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
for (const key of preferredKeys) {
|
|
62
|
+
const value = record[key]
|
|
63
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
64
|
+
return value.trim().split('\n')[0]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const value of Object.values(record)) {
|
|
69
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
70
|
+
return value.trim().split('\n')[0]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getToolSummaryText(item: ToolUseItem, t: Translate) {
|
|
78
|
+
if (isClaudeToolName(item.name)) {
|
|
79
|
+
return getClaudeToolSummaryText(item.name, item.input, t)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const presentation = buildGenericToolPresentation(item.name, item.input)
|
|
83
|
+
const displayName = presentation.titleKey != null
|
|
84
|
+
? t(presentation.titleKey, { defaultValue: presentation.fallbackTitle })
|
|
85
|
+
: presentation.fallbackTitle
|
|
86
|
+
const preview = presentation.primary ?? getToolInputPreview(item.input)
|
|
87
|
+
return preview != null && preview !== '' ? `${displayName} ${preview}` : displayName
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getToolTitleText(item: ToolUseItem, t: Translate) {
|
|
91
|
+
if (isClaudeToolName(item.name)) {
|
|
92
|
+
const presentation = buildClaudeToolPresentation(item.name, item.input)
|
|
93
|
+
return t(presentation.titleKey, { defaultValue: presentation.fallbackTitle })
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const presentation = buildGenericToolPresentation(item.name, item.input)
|
|
97
|
+
return presentation.titleKey != null
|
|
98
|
+
? t(presentation.titleKey, { defaultValue: presentation.fallbackTitle })
|
|
99
|
+
: presentation.fallbackTitle
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getToolPrimaryText(item: ToolUseItem) {
|
|
103
|
+
if (isClaudeToolName(item.name)) {
|
|
104
|
+
return buildClaudeToolPresentation(item.name, item.input).primary
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return buildGenericToolPresentation(item.name, item.input).primary ?? getToolInputPreview(item.input)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const getToolNamespaceLabel = (name: string) => {
|
|
111
|
+
const namespaceSegments = getToolNameSegments(name)
|
|
112
|
+
.slice(0, -1)
|
|
113
|
+
.filter((segment, index) => !(index === 0 && GENERIC_TOOL_NAMESPACE_PREFIXES.has(segment.toLowerCase())))
|
|
114
|
+
.map(humanizeToolSegment)
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
|
|
117
|
+
return namespaceSegments.length > 0 ? namespaceSegments.join(' ') : undefined
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const getToolQualifiedLabel = (name: string, fallbackLabel: string) => {
|
|
121
|
+
const namespaceLabel = getToolNamespaceLabel(name)
|
|
122
|
+
return namespaceLabel != null && namespaceLabel !== ''
|
|
123
|
+
? `${namespaceLabel} ${fallbackLabel}`
|
|
124
|
+
: fallbackLabel
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface ToolGroupDescriptor {
|
|
128
|
+
key: string
|
|
129
|
+
label: string
|
|
130
|
+
qualifiedLabel: string
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getToolGroupDescriptor(item: ToolUseItem, t: Translate): ToolGroupDescriptor {
|
|
134
|
+
if (isClaudeToolName(item.name)) {
|
|
135
|
+
const baseName = getClaudeToolBaseName(item.name)
|
|
136
|
+
const presentation = buildClaudeToolPresentation(item.name, item.input)
|
|
137
|
+
const label = t(presentation.titleKey, { defaultValue: presentation.fallbackTitle })
|
|
138
|
+
return {
|
|
139
|
+
key: `claude:${baseName}`,
|
|
140
|
+
label,
|
|
141
|
+
qualifiedLabel: label
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const presentation = buildGenericToolPresentation(item.name, item.input)
|
|
146
|
+
const label = presentation.titleKey != null
|
|
147
|
+
? t(presentation.titleKey, { defaultValue: presentation.fallbackTitle })
|
|
148
|
+
: presentation.fallbackTitle
|
|
149
|
+
return {
|
|
150
|
+
key: item.name,
|
|
151
|
+
label,
|
|
152
|
+
qualifiedLabel: getToolQualifiedLabel(item.name, label)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function getToolGroupSummaryText(
|
|
157
|
+
items: Array<{
|
|
158
|
+
item: ToolUseItem
|
|
159
|
+
}>,
|
|
160
|
+
t: Translate
|
|
161
|
+
) {
|
|
162
|
+
const groupedTools = new Map<string, { label: string; qualifiedLabel: string; count: number }>()
|
|
163
|
+
|
|
164
|
+
for (const { item } of items) {
|
|
165
|
+
const descriptor = getToolGroupDescriptor(item, t)
|
|
166
|
+
const label = descriptor.label
|
|
167
|
+
if (label === '') continue
|
|
168
|
+
|
|
169
|
+
const current = groupedTools.get(descriptor.key)
|
|
170
|
+
if (current != null) {
|
|
171
|
+
current.count += 1
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
groupedTools.set(descriptor.key, {
|
|
176
|
+
label,
|
|
177
|
+
qualifiedLabel: descriptor.qualifiedLabel,
|
|
178
|
+
count: 1
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const groups = Array.from(groupedTools.values())
|
|
183
|
+
const labelCounts = groups.reduce((map, group) => {
|
|
184
|
+
map.set(group.label, (map.get(group.label) ?? 0) + 1)
|
|
185
|
+
return map
|
|
186
|
+
}, new Map<string, number>())
|
|
187
|
+
|
|
188
|
+
if (groups.length === 0) {
|
|
189
|
+
return t('chat.usedTools', { count: items.length })
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const summaries = groups.map((group) => {
|
|
193
|
+
const name = (labelCounts.get(group.label) ?? 0) > 1 ? group.qualifiedLabel : group.label
|
|
194
|
+
return {
|
|
195
|
+
count: group.count,
|
|
196
|
+
text: t('chat.tools.groupSummaryCount', { name, count: group.count })
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const visible = summaries.slice(0, 2).map(summary => summary.text)
|
|
201
|
+
const hiddenCallCount = summaries.slice(2).reduce((count, summary) => count + summary.count, 0)
|
|
202
|
+
|
|
203
|
+
return hiddenCallCount > 0
|
|
204
|
+
? [...visible, t('chat.tools.groupSummaryMoreCount', { count: hiddenCallCount })].join(' · ')
|
|
205
|
+
: visible.join(' · ')
|
|
206
|
+
}
|
|
@@ -1,75 +1,81 @@
|
|
|
1
1
|
import './ChromeDevtoolsTool.scss'
|
|
2
|
+
import { Tooltip } from 'antd'
|
|
2
3
|
import React from 'react'
|
|
3
4
|
import { useTranslation } from 'react-i18next'
|
|
4
5
|
|
|
5
6
|
import { CodeBlock } from '#~/components/CodeBlock'
|
|
6
7
|
import { safeJsonStringify } from '#~/utils/safe-serialize'
|
|
7
8
|
import { ToolCallBox } from '../core/ToolCallBox'
|
|
9
|
+
import { ToolResultContent } from '../core/ToolResultContent'
|
|
10
|
+
import { ToolSummaryHeader } from '../core/ToolSummaryHeader'
|
|
11
|
+
import { hasMeaningfulToolValue } from '../core/tool-content-presence'
|
|
12
|
+
import { TOOL_TOOLTIP_PROPS, getToolSectionIcon, getToolTargetPresentation } from '../core/tool-display'
|
|
13
|
+
import { getToolPrimaryText, getToolTitleText } from '../core/tool-summary'
|
|
8
14
|
import { defineToolRender } from '../defineToolRender'
|
|
9
15
|
|
|
10
|
-
const formatToolName = (name: string) => {
|
|
11
|
-
if (name.startsWith('mcp__ChromeDevtools__')) {
|
|
12
|
-
return name.replace('mcp__ChromeDevtools__', '')
|
|
13
|
-
}
|
|
14
|
-
return name
|
|
15
|
-
}
|
|
16
|
-
|
|
17
16
|
export const ChromeDevtoolsTool = defineToolRender(({ item, resultItem }) => {
|
|
18
17
|
const { t } = useTranslation()
|
|
19
|
-
const displayName = formatToolName(item.name)
|
|
20
18
|
const input = item.input != null ? item.input : {}
|
|
19
|
+
const hasCallDetails = hasMeaningfulToolValue(input)
|
|
20
|
+
const hasResultDetails = resultItem != null && hasMeaningfulToolValue(resultItem.content)
|
|
21
|
+
const hasDetails = hasCallDetails || hasResultDetails
|
|
22
|
+
const titleText = getToolTitleText(item, t)
|
|
23
|
+
const targetPresentation = getToolTargetPresentation(getToolPrimaryText(item))
|
|
24
|
+
const errorMeta = resultItem?.is_error === true
|
|
25
|
+
? (
|
|
26
|
+
<span className='tool-status tool-status--error'>
|
|
27
|
+
<span className='material-symbols-rounded'>error</span>
|
|
28
|
+
</span>
|
|
29
|
+
)
|
|
30
|
+
: undefined
|
|
21
31
|
|
|
22
32
|
return (
|
|
23
|
-
<div className='tool-group chrome-devtools-tool'>
|
|
33
|
+
<div className='tool-group tool-group--compact chrome-devtools-tool'>
|
|
24
34
|
<ToolCallBox
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
content={
|
|
55
|
-
<div className='tool-content'>
|
|
56
|
-
{typeof resultItem.content === 'string'
|
|
57
|
-
? (
|
|
35
|
+
variant='inline'
|
|
36
|
+
defaultExpanded={false}
|
|
37
|
+
collapsible={hasDetails}
|
|
38
|
+
header={({ isExpanded, isCollapsible }) => (
|
|
39
|
+
<ToolSummaryHeader
|
|
40
|
+
icon={<i className='devicon-chrome-plain colored' />}
|
|
41
|
+
title={titleText}
|
|
42
|
+
target={targetPresentation.text}
|
|
43
|
+
targetTitle={targetPresentation.title}
|
|
44
|
+
targetMonospace={targetPresentation.monospace}
|
|
45
|
+
expanded={isExpanded}
|
|
46
|
+
collapsible={isCollapsible}
|
|
47
|
+
meta={errorMeta}
|
|
48
|
+
metaTitle={errorMeta == null ? undefined : t('chat.result')}
|
|
49
|
+
/>
|
|
50
|
+
)}
|
|
51
|
+
content={hasDetails
|
|
52
|
+
? (
|
|
53
|
+
<div className='tool-detail-sections'>
|
|
54
|
+
{hasCallDetails && (
|
|
55
|
+
<div className='tool-detail-section'>
|
|
56
|
+
<div className='tool-detail-section__header'>
|
|
57
|
+
<Tooltip title={t('chat.tools.call')} {...TOOL_TOOLTIP_PROPS}>
|
|
58
|
+
<span className='tool-detail-section__icon material-symbols-rounded'>
|
|
59
|
+
{getToolSectionIcon('call')}
|
|
60
|
+
</span>
|
|
61
|
+
</Tooltip>
|
|
62
|
+
</div>
|
|
58
63
|
<CodeBlock
|
|
59
|
-
code={
|
|
60
|
-
lang='text'
|
|
61
|
-
/>
|
|
62
|
-
)
|
|
63
|
-
: (
|
|
64
|
-
<CodeBlock
|
|
65
|
-
code={safeJsonStringify(resultItem.content, 2)}
|
|
64
|
+
code={safeJsonStringify(input, 2)}
|
|
66
65
|
lang='json'
|
|
66
|
+
hideHeader={true}
|
|
67
67
|
/>
|
|
68
|
-
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
{hasResultDetails && resultItem != null && (
|
|
71
|
+
<div className='tool-detail-section'>
|
|
72
|
+
<ToolResultContent content={resultItem.content} />
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
69
75
|
</div>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
)
|
|
77
|
+
: null}
|
|
78
|
+
/>
|
|
73
79
|
</div>
|
|
74
80
|
)
|
|
75
81
|
})
|
|
@@ -5,6 +5,8 @@ import React, { useMemo } from 'react'
|
|
|
5
5
|
import { useTranslation } from 'react-i18next'
|
|
6
6
|
|
|
7
7
|
import { ToolCallBox } from '../core/ToolCallBox'
|
|
8
|
+
import { ToolSummaryHeader } from '../core/ToolSummaryHeader'
|
|
9
|
+
import { getToolTargetPresentation } from '../core/tool-display'
|
|
8
10
|
import { defineToolRender } from '../defineToolRender'
|
|
9
11
|
import { TaskRow } from './components/TaskRow'
|
|
10
12
|
|
|
@@ -53,18 +55,33 @@ export const GetTaskInfoTool = defineToolRender(({ item, resultItem }) => {
|
|
|
53
55
|
})()
|
|
54
56
|
: []
|
|
55
57
|
const titleFallback = t('chat.tools.task')
|
|
58
|
+
const taskIdPresentation = getToolTargetPresentation(taskResult?.taskId ?? inputTaskId)
|
|
59
|
+
const errorMeta = resultItem?.is_error === true
|
|
60
|
+
? (
|
|
61
|
+
<span className='tool-status tool-status--error'>
|
|
62
|
+
<span className='material-symbols-rounded'>error</span>
|
|
63
|
+
</span>
|
|
64
|
+
)
|
|
65
|
+
: undefined
|
|
56
66
|
|
|
57
67
|
return (
|
|
58
|
-
<div className='tool-group get-task-info-tool'>
|
|
68
|
+
<div className='tool-group tool-group--compact get-task-info-tool'>
|
|
59
69
|
<ToolCallBox
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<span className='
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
variant='inline'
|
|
71
|
+
defaultExpanded={false}
|
|
72
|
+
header={({ isExpanded, isCollapsible }) => (
|
|
73
|
+
<ToolSummaryHeader
|
|
74
|
+
icon={<span className='material-symbols-rounded'>info</span>}
|
|
75
|
+
title={t('chat.tools.getTaskInfo')}
|
|
76
|
+
target={taskIdPresentation.text}
|
|
77
|
+
targetTitle={taskIdPresentation.title}
|
|
78
|
+
targetMonospace={taskIdPresentation.monospace}
|
|
79
|
+
expanded={isExpanded}
|
|
80
|
+
collapsible={isCollapsible}
|
|
81
|
+
meta={errorMeta}
|
|
82
|
+
metaTitle={errorMeta == null ? undefined : t('chat.result')}
|
|
83
|
+
/>
|
|
84
|
+
)}
|
|
68
85
|
content={
|
|
69
86
|
<div className='tool-content'>
|
|
70
87
|
{taskResult
|
|
@@ -4,6 +4,7 @@ import React, { useMemo } from 'react'
|
|
|
4
4
|
import { useTranslation } from 'react-i18next'
|
|
5
5
|
|
|
6
6
|
import { ToolCallBox } from '../core/ToolCallBox'
|
|
7
|
+
import { ToolSummaryHeader } from '../core/ToolSummaryHeader'
|
|
7
8
|
import { defineToolRender } from '../defineToolRender'
|
|
8
9
|
import { TaskRow } from './components/TaskRow'
|
|
9
10
|
|
|
@@ -29,18 +30,30 @@ export const ListTasksTool = defineToolRender(({ resultItem }) => {
|
|
|
29
30
|
const text = resultItem.content[0].text.trim()
|
|
30
31
|
return JSON.parse(text) as TaskResult[]
|
|
31
32
|
}, [resultItem?.content])
|
|
33
|
+
const errorMeta = resultItem?.is_error === true
|
|
34
|
+
? (
|
|
35
|
+
<span className='tool-status tool-status--error'>
|
|
36
|
+
<span className='material-symbols-rounded'>error</span>
|
|
37
|
+
</span>
|
|
38
|
+
)
|
|
39
|
+
: undefined
|
|
32
40
|
|
|
33
41
|
return (
|
|
34
|
-
<div className='tool-group list-tasks-tool'>
|
|
42
|
+
<div className='tool-group tool-group--compact list-tasks-tool'>
|
|
35
43
|
<ToolCallBox
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<span className='
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
variant='inline'
|
|
45
|
+
defaultExpanded={false}
|
|
46
|
+
header={({ isExpanded, isCollapsible }) => (
|
|
47
|
+
<ToolSummaryHeader
|
|
48
|
+
icon={<span className='material-symbols-rounded'>list_alt</span>}
|
|
49
|
+
title={t('chat.tools.listTasks')}
|
|
50
|
+
target={t('chat.tools.taskCount', { count: taskResults.length })}
|
|
51
|
+
expanded={isExpanded}
|
|
52
|
+
collapsible={isCollapsible}
|
|
53
|
+
meta={errorMeta}
|
|
54
|
+
metaTitle={errorMeta == null ? undefined : t('chat.result')}
|
|
55
|
+
/>
|
|
56
|
+
)}
|
|
44
57
|
content={
|
|
45
58
|
<div className='tool-content'>
|
|
46
59
|
<div className='list-tasks-tool__list'>
|
|
@@ -5,6 +5,7 @@ import React, { useMemo } from 'react'
|
|
|
5
5
|
import { useTranslation } from 'react-i18next'
|
|
6
6
|
|
|
7
7
|
import { ToolCallBox } from '../core/ToolCallBox'
|
|
8
|
+
import { ToolSummaryHeader } from '../core/ToolSummaryHeader'
|
|
8
9
|
import { defineToolRender } from '../defineToolRender'
|
|
9
10
|
import { TaskRow } from './components/TaskRow'
|
|
10
11
|
|
|
@@ -39,18 +40,30 @@ export const StartTasksTool = defineToolRender(({ item, resultItem }) => {
|
|
|
39
40
|
}, [resultItem?.content])
|
|
40
41
|
|
|
41
42
|
const { taskResults } = parsedResult
|
|
43
|
+
const errorMeta = resultItem?.is_error === true
|
|
44
|
+
? (
|
|
45
|
+
<span className='tool-status tool-status--error'>
|
|
46
|
+
<span className='material-symbols-rounded'>error</span>
|
|
47
|
+
</span>
|
|
48
|
+
)
|
|
49
|
+
: undefined
|
|
42
50
|
|
|
43
51
|
return (
|
|
44
|
-
<div className='tool-group start-tasks-tool'>
|
|
52
|
+
<div className='tool-group tool-group--compact start-tasks-tool'>
|
|
45
53
|
<ToolCallBox
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<span className='
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
variant='inline'
|
|
55
|
+
defaultExpanded={false}
|
|
56
|
+
header={({ isExpanded, isCollapsible }) => (
|
|
57
|
+
<ToolSummaryHeader
|
|
58
|
+
icon={<span className='material-symbols-rounded'>playlist_add</span>}
|
|
59
|
+
title={t('chat.tools.startTasks')}
|
|
60
|
+
target={t('chat.tools.taskCount', { count: tasks.length })}
|
|
61
|
+
expanded={isExpanded}
|
|
62
|
+
collapsible={isCollapsible}
|
|
63
|
+
meta={errorMeta}
|
|
64
|
+
metaTitle={errorMeta == null ? undefined : t('chat.result')}
|
|
65
|
+
/>
|
|
66
|
+
)}
|
|
54
67
|
content={
|
|
55
68
|
<div className='tool-content'>
|
|
56
69
|
<div className='start-tasks-tool__list'>
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { AskUserQuestionParams, Session, WSEvent } from '@vibe-forge/core'
|
|
2
2
|
|
|
3
|
+
import { stripAnsi } from '#~/utils/strip-ansi'
|
|
4
|
+
|
|
3
5
|
export interface InteractionRequestState {
|
|
4
6
|
id: string
|
|
5
7
|
payload: AskUserQuestionParams
|
|
6
8
|
}
|
|
7
9
|
|
|
8
|
-
export interface
|
|
10
|
+
export interface ChatErrorState {
|
|
9
11
|
kind: 'connection' | 'session'
|
|
10
12
|
message: string
|
|
13
|
+
code?: string
|
|
14
|
+
reason?: 'error' | 'closed'
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
export interface FatalSessionErrorState {
|
|
@@ -15,6 +19,8 @@ export interface FatalSessionErrorState {
|
|
|
15
19
|
code?: string
|
|
16
20
|
}
|
|
17
21
|
|
|
22
|
+
const normalizeErrorMessage = (value: string) => stripAnsi(value).trim()
|
|
23
|
+
|
|
18
24
|
export const getFatalSessionError = (event: WSEvent): FatalSessionErrorState | null => {
|
|
19
25
|
if (event?.type !== 'error') {
|
|
20
26
|
return null
|
|
@@ -24,20 +30,34 @@ export const getFatalSessionError = (event: WSEvent): FatalSessionErrorState | n
|
|
|
24
30
|
return null
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
const code = event.data != null && typeof event.data === 'object' &&
|
|
34
|
+
'code' in event.data &&
|
|
35
|
+
typeof event.data.code === 'string' &&
|
|
36
|
+
event.data.code.trim() !== ''
|
|
37
|
+
? event.data.code
|
|
38
|
+
: undefined
|
|
39
|
+
|
|
27
40
|
if (event.data != null && typeof event.data === 'object' && 'message' in event.data) {
|
|
28
41
|
const message = event.data.message
|
|
29
|
-
if (typeof message === 'string'
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
if (typeof message === 'string') {
|
|
43
|
+
const normalizedMessage = normalizeErrorMessage(message)
|
|
44
|
+
if (normalizedMessage !== '') {
|
|
45
|
+
return {
|
|
46
|
+
message: normalizedMessage,
|
|
47
|
+
code
|
|
48
|
+
}
|
|
35
49
|
}
|
|
36
50
|
}
|
|
37
51
|
}
|
|
38
52
|
|
|
39
|
-
if (typeof event.message === 'string'
|
|
40
|
-
|
|
53
|
+
if (typeof event.message === 'string') {
|
|
54
|
+
const normalizedMessage = normalizeErrorMessage(event.message)
|
|
55
|
+
if (normalizedMessage !== '') {
|
|
56
|
+
return {
|
|
57
|
+
message: normalizedMessage,
|
|
58
|
+
code
|
|
59
|
+
}
|
|
60
|
+
}
|
|
41
61
|
}
|
|
42
62
|
|
|
43
63
|
return null
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ChatMessage } from '@vibe-forge/core'
|
|
2
|
+
import type { SessionInfo } from '@vibe-forge/types'
|
|
3
|
+
|
|
4
|
+
import type { ChatErrorState, InteractionRequestState } from './interaction-state'
|
|
5
|
+
|
|
6
|
+
export interface ChatSessionViewSnapshot {
|
|
7
|
+
messages: ChatMessage[]
|
|
8
|
+
sessionInfo: SessionInfo | null
|
|
9
|
+
errorState: ChatErrorState | null
|
|
10
|
+
interactionRequest: InteractionRequestState | null
|
|
11
|
+
isHydrated: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const MAX_CHAT_SESSION_VIEW_SNAPSHOTS = 20
|
|
15
|
+
|
|
16
|
+
export const createChatSessionViewSnapshot = (
|
|
17
|
+
value?: Partial<ChatSessionViewSnapshot>
|
|
18
|
+
): ChatSessionViewSnapshot => ({
|
|
19
|
+
messages: value?.messages ?? [],
|
|
20
|
+
sessionInfo: value?.sessionInfo ?? null,
|
|
21
|
+
errorState: value?.errorState ?? null,
|
|
22
|
+
interactionRequest: value?.interactionRequest ?? null,
|
|
23
|
+
isHydrated: value?.isHydrated ?? false
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export const mergeChatSessionViewSnapshot = (
|
|
27
|
+
current: ChatSessionViewSnapshot | undefined,
|
|
28
|
+
patch: Partial<ChatSessionViewSnapshot>
|
|
29
|
+
): ChatSessionViewSnapshot => {
|
|
30
|
+
return createChatSessionViewSnapshot({
|
|
31
|
+
...createChatSessionViewSnapshot(current),
|
|
32
|
+
...patch
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const restoreChatSessionViewSnapshot = (snapshot?: ChatSessionViewSnapshot) => {
|
|
37
|
+
const resolved = createChatSessionViewSnapshot(snapshot)
|
|
38
|
+
const restorable = resolved.isHydrated === true
|
|
39
|
+
? resolved
|
|
40
|
+
: createChatSessionViewSnapshot()
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
messages: restorable.messages,
|
|
44
|
+
sessionInfo: restorable.sessionInfo,
|
|
45
|
+
errorState: restorable.errorState,
|
|
46
|
+
interactionRequest: restorable.interactionRequest,
|
|
47
|
+
isReady: restorable.isHydrated
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const setChatSessionViewSnapshot = (
|
|
52
|
+
cache: Map<string, ChatSessionViewSnapshot>,
|
|
53
|
+
sessionId: string,
|
|
54
|
+
patch: Partial<ChatSessionViewSnapshot>
|
|
55
|
+
) => {
|
|
56
|
+
const next = mergeChatSessionViewSnapshot(cache.get(sessionId), patch)
|
|
57
|
+
|
|
58
|
+
if (cache.has(sessionId)) {
|
|
59
|
+
cache.delete(sessionId)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
cache.set(sessionId, next)
|
|
63
|
+
|
|
64
|
+
while (cache.size > MAX_CHAT_SESSION_VIEW_SNAPSHOTS) {
|
|
65
|
+
const oldestSessionId = cache.keys().next().value
|
|
66
|
+
if (oldestSessionId == null) {
|
|
67
|
+
break
|
|
68
|
+
}
|
|
69
|
+
cache.delete(oldestSessionId)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return next
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const deleteChatSessionViewSnapshot = (
|
|
76
|
+
cache: Map<string, ChatSessionViewSnapshot>,
|
|
77
|
+
sessionId: string
|
|
78
|
+
) => {
|
|
79
|
+
cache.delete(sessionId)
|
|
80
|
+
}
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
|
|
2
2
|
|
|
3
3
|
const SCROLL_THRESHOLD = 80
|
|
4
4
|
|
|
5
|
-
export function useChatScroll({
|
|
5
|
+
export function useChatScroll({ contentVersion }: { contentVersion: number }) {
|
|
6
6
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
|
7
7
|
const messagesContainerRef = useRef<HTMLDivElement>(null)
|
|
8
8
|
const messagesContentRef = useRef<HTMLDivElement>(null)
|
|
@@ -39,7 +39,7 @@ export function useChatScroll({ messagesLength }: { messagesLength: number }) {
|
|
|
39
39
|
|
|
40
40
|
useEffect(() => {
|
|
41
41
|
updateScrollState()
|
|
42
|
-
}, [
|
|
42
|
+
}, [contentVersion, updateScrollState])
|
|
43
43
|
|
|
44
44
|
return {
|
|
45
45
|
messagesEndRef,
|