@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,57 @@
|
|
|
1
|
+
import { toSerializable } from '#~/utils/safe-serialize'
|
|
2
|
+
|
|
3
|
+
import { getStringList, getStructuredBlocks } from './tool-result-content-utils'
|
|
4
|
+
|
|
5
|
+
const parseStructuredInput = (value: unknown) => {
|
|
6
|
+
if (typeof value !== 'string') {
|
|
7
|
+
return value
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const trimmed = value.trim()
|
|
11
|
+
if (
|
|
12
|
+
(trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
|
13
|
+
(trimmed.startsWith('[') && trimmed.endsWith(']'))
|
|
14
|
+
) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(trimmed)
|
|
17
|
+
} catch {
|
|
18
|
+
return value
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return value
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const hasMeaningfulToolValue = (value: unknown): boolean => {
|
|
26
|
+
const parsed = parseStructuredInput(toSerializable(value))
|
|
27
|
+
|
|
28
|
+
if (parsed == null) {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof parsed === 'string') {
|
|
33
|
+
return parsed.trim() !== ''
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof parsed === 'number' || typeof parsed === 'boolean') {
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (Array.isArray(parsed)) {
|
|
41
|
+
return parsed.some(item => hasMeaningfulToolValue(item))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof parsed !== 'object') {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (getStructuredBlocks(parsed) != null) {
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (getStringList(parsed) != null) {
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return Object.values(parsed as Record<string, unknown>).some(item => hasMeaningfulToolValue(item))
|
|
57
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
|
|
3
|
+
import { safeJsonStringify } from '#~/utils/safe-serialize'
|
|
4
|
+
|
|
5
|
+
export const TOOL_TOOLTIP_PROPS = {
|
|
6
|
+
arrow: false,
|
|
7
|
+
mouseEnterDelay: .2,
|
|
8
|
+
mouseLeaveDelay: .06
|
|
9
|
+
} as const
|
|
10
|
+
|
|
11
|
+
const PATH_TOOLTIP_MARKERS = ['apps', 'packages', 'src', 'scripts', '.ai', 'docs', 'tests', 'test', 'changelog']
|
|
12
|
+
const WINDOWS_PATH_RE = /^[a-z]:[\\/]+/i
|
|
13
|
+
const URL_RE = /^https?:\/\//i
|
|
14
|
+
|
|
15
|
+
const TOOL_FIELD_ICON_MAP: Record<string, string> = {
|
|
16
|
+
activeForm: 'description',
|
|
17
|
+
addBlockedBy: 'link_off',
|
|
18
|
+
addBlocks: 'block',
|
|
19
|
+
allowedDomains: 'travel_explore',
|
|
20
|
+
allowedPrompts: 'chat',
|
|
21
|
+
answers: 'fact_check',
|
|
22
|
+
args: 'segment',
|
|
23
|
+
blockedDomains: 'domain_disabled',
|
|
24
|
+
cellId: 'tag',
|
|
25
|
+
cellType: 'grid_view',
|
|
26
|
+
command: 'terminal',
|
|
27
|
+
content: 'notes',
|
|
28
|
+
contextLines: 'format_line_spacing',
|
|
29
|
+
cwd: 'folder_open',
|
|
30
|
+
changes: 'difference',
|
|
31
|
+
description: 'subject',
|
|
32
|
+
disableSandbox: 'shield',
|
|
33
|
+
details: 'info',
|
|
34
|
+
endLine: 'keyboard_double_arrow_down',
|
|
35
|
+
editMode: 'edit',
|
|
36
|
+
glob: 'data_object',
|
|
37
|
+
ignore: 'visibility_off',
|
|
38
|
+
ignoreCase: 'text_fields',
|
|
39
|
+
limit: 'filter_alt',
|
|
40
|
+
maxTurns: 'repeat',
|
|
41
|
+
maxOutputTokens: 'expand_content',
|
|
42
|
+
metadata: 'badge',
|
|
43
|
+
model: 'neurology',
|
|
44
|
+
mode: 'tune',
|
|
45
|
+
newSource: 'note_stack',
|
|
46
|
+
newString: 'edit_note',
|
|
47
|
+
oldString: 'history',
|
|
48
|
+
owner: 'person',
|
|
49
|
+
offset: 'format_indent_increase',
|
|
50
|
+
path: 'folder',
|
|
51
|
+
prompt: 'chat',
|
|
52
|
+
pushToRemote: 'upload',
|
|
53
|
+
questions: 'quiz',
|
|
54
|
+
remoteSession: 'devices',
|
|
55
|
+
remoteSessionUrl: 'link',
|
|
56
|
+
replaceAll: 'select_all',
|
|
57
|
+
resume: 'play_circle',
|
|
58
|
+
runInBackground: 'background_dot_large',
|
|
59
|
+
startLine: 'keyboard_double_arrow_up',
|
|
60
|
+
status: 'flag',
|
|
61
|
+
subagentType: 'hub',
|
|
62
|
+
subject: 'title',
|
|
63
|
+
timeout: 'timer',
|
|
64
|
+
todos: 'checklist',
|
|
65
|
+
workdir: 'folder_code',
|
|
66
|
+
yieldTimeMs: 'schedule'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const TOOL_FORMAT_ICON_MAP: Record<string, string> = {
|
|
70
|
+
code: 'code',
|
|
71
|
+
inline: 'label',
|
|
72
|
+
json: 'data_object',
|
|
73
|
+
list: 'format_list_bulleted',
|
|
74
|
+
questions: 'quiz',
|
|
75
|
+
text: 'article'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const getToolSectionIcon = (section: 'call' | 'details' | 'result') => {
|
|
79
|
+
if (section === 'call') return 'tune'
|
|
80
|
+
if (section === 'result') return 'assignment_turned_in'
|
|
81
|
+
return 'info'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const getToolFieldIcon = (labelKey: string, format: string) => {
|
|
85
|
+
const key = labelKey.split('.').pop() ?? labelKey
|
|
86
|
+
return TOOL_FIELD_ICON_MAP[key] ?? TOOL_FORMAT_ICON_MAP[format] ?? 'label'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const getToolValueText = (value: unknown) => {
|
|
90
|
+
if (typeof value === 'string') {
|
|
91
|
+
return value
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
95
|
+
return String(value)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return safeJsonStringify(value, 2)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const getToolInlineValueText = (value: unknown) => (
|
|
102
|
+
getToolValueText(value).replace(/\s+/g, ' ').trim()
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
export const shouldUseMonospaceTarget = (value: string | undefined) => {
|
|
106
|
+
if (value == null || value === '') {
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
value.startsWith('/') ||
|
|
112
|
+
value.startsWith('./') ||
|
|
113
|
+
value.startsWith('../') ||
|
|
114
|
+
value.startsWith('http://') ||
|
|
115
|
+
value.startsWith('https://') ||
|
|
116
|
+
value.startsWith('#') ||
|
|
117
|
+
value.includes('/')
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const normalizeToolPath = (value: string) => value.replace(/\\/g, '/')
|
|
122
|
+
|
|
123
|
+
const looksLikeToolPath = (value: string) => {
|
|
124
|
+
if (URL_RE.test(value)) {
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const normalized = normalizeToolPath(value)
|
|
129
|
+
if (normalized.startsWith('/') || normalized.startsWith('./') || normalized.startsWith('../')) {
|
|
130
|
+
return true
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (WINDOWS_PATH_RE.test(value)) {
|
|
134
|
+
return true
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!normalized.includes('/')) {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return !normalized.includes(' ')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const getPathLabel = (value: string) => {
|
|
145
|
+
const normalized = normalizeToolPath(value)
|
|
146
|
+
const segments = normalized.split('/').filter(Boolean)
|
|
147
|
+
return segments.at(-1) ?? normalized
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const getPathTooltip = (value: string) => {
|
|
151
|
+
const normalized = normalizeToolPath(value)
|
|
152
|
+
const segments = normalized.split('/').filter(Boolean)
|
|
153
|
+
const markerIndex = segments.findIndex(segment => PATH_TOOLTIP_MARKERS.includes(segment))
|
|
154
|
+
|
|
155
|
+
if (markerIndex >= 0) {
|
|
156
|
+
return segments.slice(markerIndex).join('/')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return normalized
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const getToolTargetPresentation = (value: string | undefined) => {
|
|
163
|
+
if (value == null || value.trim() === '') {
|
|
164
|
+
return {
|
|
165
|
+
text: undefined,
|
|
166
|
+
title: undefined,
|
|
167
|
+
monospace: false
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const normalized = value.trim()
|
|
172
|
+
|
|
173
|
+
if (looksLikeToolPath(normalized)) {
|
|
174
|
+
return {
|
|
175
|
+
text: getPathLabel(normalized),
|
|
176
|
+
title: getPathTooltip(normalized),
|
|
177
|
+
monospace: true
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (URL_RE.test(normalized)) {
|
|
182
|
+
try {
|
|
183
|
+
const url = new URL(normalized)
|
|
184
|
+
return {
|
|
185
|
+
text: `${url.hostname}${url.pathname}${url.search}${url.hash}`,
|
|
186
|
+
title: normalized,
|
|
187
|
+
monospace: false
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
return {
|
|
191
|
+
text: normalized,
|
|
192
|
+
title: normalized,
|
|
193
|
+
monospace: false
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
text: normalized,
|
|
200
|
+
title: undefined,
|
|
201
|
+
monospace: shouldUseMonospaceTarget(normalized)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Tooltip } from 'antd'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { CodeBlock } from '#~/components/CodeBlock'
|
|
5
|
+
import { safeJsonStringify } from '#~/utils/safe-serialize'
|
|
6
|
+
|
|
7
|
+
import { TOOL_TOOLTIP_PROPS, getToolFieldIcon, getToolInlineValueText, getToolValueText } from './tool-display'
|
|
8
|
+
|
|
9
|
+
type Translate = (key: string, options?: Record<string, unknown>) => string
|
|
10
|
+
|
|
11
|
+
export type ToolFieldFormat = 'inline' | 'text' | 'code' | 'list' | 'json' | 'questions'
|
|
12
|
+
|
|
13
|
+
export interface ToolFieldView {
|
|
14
|
+
labelKey: string
|
|
15
|
+
fallbackLabel: string
|
|
16
|
+
format: ToolFieldFormat
|
|
17
|
+
value: unknown
|
|
18
|
+
lang?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const getFieldKey = (field: ToolFieldView, index: number) => `${field.labelKey}-${index}`
|
|
22
|
+
|
|
23
|
+
const getSectionHeader = (icon: string, label: string) => (
|
|
24
|
+
<div className='tool-detail-section__header'>
|
|
25
|
+
<Tooltip title={label} {...TOOL_TOOLTIP_PROPS}>
|
|
26
|
+
<span className='tool-detail-section__icon material-symbols-rounded'>{icon}</span>
|
|
27
|
+
</Tooltip>
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export function ToolInlineFields({
|
|
32
|
+
fields,
|
|
33
|
+
t
|
|
34
|
+
}: {
|
|
35
|
+
fields: ToolFieldView[]
|
|
36
|
+
t: Translate
|
|
37
|
+
}) {
|
|
38
|
+
if (fields.length === 0) {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className='tool-inline-token-list tool-inline-token-list--standalone'
|
|
45
|
+
aria-label={t('chat.tools.fields.details')}
|
|
46
|
+
>
|
|
47
|
+
{fields.map((field, index) => {
|
|
48
|
+
const label = t(field.labelKey, { defaultValue: field.fallbackLabel })
|
|
49
|
+
const valueText = getToolInlineValueText(field.value)
|
|
50
|
+
return (
|
|
51
|
+
<Tooltip
|
|
52
|
+
key={getFieldKey(field, index)}
|
|
53
|
+
title={
|
|
54
|
+
<div className='tool-tooltip-content'>
|
|
55
|
+
<div className='tool-tooltip-content__title'>{label}</div>
|
|
56
|
+
<div className='tool-tooltip-content__value'>{getToolValueText(field.value)}</div>
|
|
57
|
+
</div>
|
|
58
|
+
}
|
|
59
|
+
{...TOOL_TOOLTIP_PROPS}
|
|
60
|
+
>
|
|
61
|
+
<div className='tool-inline-token'>
|
|
62
|
+
<span className='tool-inline-token__icon material-symbols-rounded'>
|
|
63
|
+
{getToolFieldIcon(field.labelKey, field.format)}
|
|
64
|
+
</span>
|
|
65
|
+
<span className='tool-inline-token__value'>{valueText}</span>
|
|
66
|
+
</div>
|
|
67
|
+
</Tooltip>
|
|
68
|
+
)
|
|
69
|
+
})}
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function renderToolBlockField(
|
|
75
|
+
field: ToolFieldView,
|
|
76
|
+
index: number,
|
|
77
|
+
t: Translate,
|
|
78
|
+
options: {
|
|
79
|
+
sectionClassName?: string
|
|
80
|
+
} = {}
|
|
81
|
+
) {
|
|
82
|
+
const label = t(field.labelKey, { defaultValue: field.fallbackLabel })
|
|
83
|
+
const sectionHeader = getSectionHeader(getToolFieldIcon(field.labelKey, field.format), label)
|
|
84
|
+
const sectionClassName = options.sectionClassName ?? 'tool-detail-section'
|
|
85
|
+
|
|
86
|
+
if (field.format === 'text') {
|
|
87
|
+
return (
|
|
88
|
+
<div className={sectionClassName} key={getFieldKey(field, index)}>
|
|
89
|
+
{sectionHeader}
|
|
90
|
+
<div className='tool-detail-section__text'>{String(field.value)}</div>
|
|
91
|
+
</div>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (field.format === 'code') {
|
|
96
|
+
return (
|
|
97
|
+
<div className={sectionClassName} key={getFieldKey(field, index)}>
|
|
98
|
+
{sectionHeader}
|
|
99
|
+
<CodeBlock
|
|
100
|
+
code={String(field.value)}
|
|
101
|
+
lang={field.lang ?? 'text'}
|
|
102
|
+
hideHeader={true}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (field.format === 'list') {
|
|
109
|
+
const items = Array.isArray(field.value) ? field.value.map(item => String(item)) : []
|
|
110
|
+
return (
|
|
111
|
+
<div className={sectionClassName} key={getFieldKey(field, index)}>
|
|
112
|
+
{sectionHeader}
|
|
113
|
+
<div className='tool-detail-list'>
|
|
114
|
+
{items.map(listItem => (
|
|
115
|
+
<div className='tool-detail-list-item' key={listItem}>{listItem}</div>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div className={sectionClassName} key={getFieldKey(field, index)}>
|
|
124
|
+
{sectionHeader}
|
|
125
|
+
<CodeBlock
|
|
126
|
+
code={safeJsonStringify(field.value, 2)}
|
|
127
|
+
lang='json'
|
|
128
|
+
hideHeader={true}
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { toSerializable } from '#~/utils/safe-serialize'
|
|
2
|
+
|
|
3
|
+
interface StructuredTextBlock {
|
|
4
|
+
type: 'text'
|
|
5
|
+
text: string
|
|
6
|
+
format: 'text' | 'markdown'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface StructuredImageBlock {
|
|
10
|
+
type: 'image'
|
|
11
|
+
src: string
|
|
12
|
+
alt?: string
|
|
13
|
+
title?: string
|
|
14
|
+
width?: number
|
|
15
|
+
height?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type StructuredBlock = StructuredTextBlock | StructuredImageBlock
|
|
19
|
+
|
|
20
|
+
const parseStructuredInput = (value: unknown) => {
|
|
21
|
+
if (typeof value !== 'string') {
|
|
22
|
+
return value
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const trimmed = value.trim()
|
|
26
|
+
if (
|
|
27
|
+
(trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
|
28
|
+
(trimmed.startsWith('[') && trimmed.endsWith(']'))
|
|
29
|
+
) {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(trimmed)
|
|
32
|
+
} catch {
|
|
33
|
+
return value
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return value
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const resolveImageSource = (value: Record<string, unknown>) => {
|
|
41
|
+
const directUrl = typeof value.url === 'string'
|
|
42
|
+
? value.url
|
|
43
|
+
: typeof value.src === 'string'
|
|
44
|
+
? value.src
|
|
45
|
+
: typeof value.image_url === 'string'
|
|
46
|
+
? value.image_url
|
|
47
|
+
: typeof value.imageUrl === 'string'
|
|
48
|
+
? value.imageUrl
|
|
49
|
+
: typeof value.dataUrl === 'string'
|
|
50
|
+
? value.dataUrl
|
|
51
|
+
: null
|
|
52
|
+
if (directUrl != null) {
|
|
53
|
+
return directUrl
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const source = value.source != null && typeof value.source === 'object'
|
|
57
|
+
? value.source as Record<string, unknown>
|
|
58
|
+
: null
|
|
59
|
+
const data = typeof value.data === 'string'
|
|
60
|
+
? value.data
|
|
61
|
+
: typeof value.base64 === 'string'
|
|
62
|
+
? value.base64
|
|
63
|
+
: source != null && typeof source.data === 'string'
|
|
64
|
+
? source.data
|
|
65
|
+
: null
|
|
66
|
+
if (data == null || data === '') {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const mimeType = typeof value.mimeType === 'string'
|
|
71
|
+
? value.mimeType
|
|
72
|
+
: typeof value.mime_type === 'string'
|
|
73
|
+
? value.mime_type
|
|
74
|
+
: source != null && typeof source.media_type === 'string'
|
|
75
|
+
? source.media_type
|
|
76
|
+
: source != null && typeof source.mimeType === 'string'
|
|
77
|
+
? source.mimeType
|
|
78
|
+
: source != null && typeof source.mime_type === 'string'
|
|
79
|
+
? source.mime_type
|
|
80
|
+
: 'image/png'
|
|
81
|
+
|
|
82
|
+
return `data:${mimeType};base64,${data}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const parseBlock = (value: unknown): StructuredBlock | null => {
|
|
86
|
+
if (value == null || typeof value !== 'object') {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const obj = value as Record<string, unknown>
|
|
91
|
+
const rawType = typeof obj.type === 'string' ? obj.type.toLowerCase() : ''
|
|
92
|
+
if (rawType === 'text' || rawType === 'markdown' || rawType === 'md') {
|
|
93
|
+
const text = typeof obj.text === 'string'
|
|
94
|
+
? obj.text
|
|
95
|
+
: typeof obj.content === 'string'
|
|
96
|
+
? obj.content
|
|
97
|
+
: null
|
|
98
|
+
if (text == null) {
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const rawFormat = typeof obj.format === 'string' ? obj.format.toLowerCase() : 'markdown'
|
|
103
|
+
return {
|
|
104
|
+
type: 'text',
|
|
105
|
+
text,
|
|
106
|
+
format: rawType === 'text' && (rawFormat === 'text' || rawFormat === 'plain') ? 'text' : 'markdown'
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (rawType !== 'image') {
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const src = resolveImageSource(obj)
|
|
115
|
+
if (src == null) {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
type: 'image',
|
|
121
|
+
src,
|
|
122
|
+
alt: typeof obj.alt === 'string' ? obj.alt : undefined,
|
|
123
|
+
title: typeof obj.title === 'string' ? obj.title : undefined,
|
|
124
|
+
width: typeof obj.width === 'number' ? obj.width : undefined,
|
|
125
|
+
height: typeof obj.height === 'number' ? obj.height : undefined
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const getStructuredBlocks = (value: unknown): StructuredBlock[] | null => {
|
|
130
|
+
const parsed = parseStructuredInput(toSerializable(value))
|
|
131
|
+
if (Array.isArray(parsed)) {
|
|
132
|
+
const blocks = parsed.map(parseBlock)
|
|
133
|
+
return blocks.every(Boolean) ? blocks as StructuredBlock[] : null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (parsed != null && typeof parsed === 'object') {
|
|
137
|
+
const container = parsed as Record<string, unknown>
|
|
138
|
+
const content = container.content ?? container.items ?? container.blocks
|
|
139
|
+
if (Array.isArray(content)) {
|
|
140
|
+
const blocks = content.map(parseBlock)
|
|
141
|
+
return blocks.every(Boolean) ? blocks as StructuredBlock[] : null
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const single = parseBlock(parsed)
|
|
146
|
+
return single != null ? [single] : null
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const getStringList = (value: unknown): string[] | null => {
|
|
150
|
+
const parsed = parseStructuredInput(toSerializable(value))
|
|
151
|
+
if (!Array.isArray(parsed) || !parsed.every(item => ['string', 'number', 'boolean'].includes(typeof item))) {
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return parsed.map(item => String(item))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const looksLikeMarkdown = (value: string) => {
|
|
159
|
+
const trimmed = value.trim()
|
|
160
|
+
if (trimmed === '') {
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
trimmed.startsWith('```') ||
|
|
166
|
+
/^(?:#{1,6}\s|[-*+]\s|>\s|\d+\.\s)/m.test(trimmed) ||
|
|
167
|
+
/\[[^\]]+\]\([^)]+\)/.test(trimmed) ||
|
|
168
|
+
/!\[[^\]]*\]\([^)]+\)/.test(trimmed) ||
|
|
169
|
+
/\|.+\|/.test(trimmed)
|
|
170
|
+
)
|
|
171
|
+
}
|