add diff viewer prefs and task session shortcut
This commit is contained in:
120
package-lock.json
generated
120
package-lock.json
generated
@@ -8,6 +8,7 @@
|
|||||||
"name": "@opencode-ai/client",
|
"name": "@opencode-ai/client",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@git-diff-view/solid": "^0.0.8",
|
||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
"@opencode-ai/sdk": "0.15.13",
|
"@opencode-ai/sdk": "0.15.13",
|
||||||
"@solidjs/router": "^0.13.0",
|
"@solidjs/router": "^0.13.0",
|
||||||
@@ -1096,6 +1097,46 @@
|
|||||||
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
|
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@git-diff-view/core": {
|
||||||
|
"version": "0.0.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/@git-diff-view/core/-/core-0.0.35.tgz",
|
||||||
|
"integrity": "sha512-cdH3BopR6AWUW+6hP78zGyryKxR9JkPgryd1JN78i5k+F9Eo4x/4S23ZF1VZnrpPlGLrSuYfiAZ0ho5m+pTuKg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@git-diff-view/lowlight": "^0.0.35",
|
||||||
|
"fast-diff": "^1.3.0",
|
||||||
|
"highlight.js": "^11.11.0",
|
||||||
|
"lowlight": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@git-diff-view/lowlight": {
|
||||||
|
"version": "0.0.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/@git-diff-view/lowlight/-/lowlight-0.0.35.tgz",
|
||||||
|
"integrity": "sha512-MVpOxrNn1oHVOTOWUjxLbbf1W4OtVHjj6CHxwJbBRg9ZWZdShBINjuEgHVMSGB6vZuHKfwruRfXw8XxV3aF8zw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"highlight.js": "^11.11.0",
|
||||||
|
"lowlight": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@git-diff-view/solid": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@git-diff-view/solid/-/solid-0.0.8.tgz",
|
||||||
|
"integrity": "sha512-MvZpyV5Gz0Axv2vvAlPpOmHtaJRUGBMoqXmvjIdZlUls0091QsglpE8bMbdRdEHuXodzxPDYyZrx3HCniMlGKw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@git-diff-view/core": "^0.0.35",
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"fast-diff": "^1.3.0",
|
||||||
|
"highlight.js": "^11.11.0",
|
||||||
|
"lowlight": "^3.3.0",
|
||||||
|
"reactivity-store": "^0.3.12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@internationalized/date": {
|
"node_modules/@internationalized/date": {
|
||||||
"version": "3.10.0",
|
"version": "3.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz",
|
||||||
@@ -2194,6 +2235,21 @@
|
|||||||
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/@vue/reactivity": {
|
||||||
|
"version": "3.5.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz",
|
||||||
|
"integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/shared": "3.5.24"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/shared": {
|
||||||
|
"version": "3.5.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz",
|
||||||
|
"integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@xmldom/xmldom": {
|
"node_modules/@xmldom/xmldom": {
|
||||||
"version": "0.8.11",
|
"version": "0.8.11",
|
||||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
|
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
|
||||||
@@ -3578,6 +3634,7 @@
|
|||||||
"integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==",
|
"integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-builder-lib": "24.13.3",
|
"app-builder-lib": "24.13.3",
|
||||||
"builder-util": "24.13.1",
|
"builder-util": "24.13.1",
|
||||||
@@ -4145,6 +4202,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-diff": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||||
@@ -4693,6 +4756,15 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/highlight.js": {
|
||||||
|
"version": "11.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
|
||||||
|
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hosted-git-info": {
|
"node_modules/hosted-git-info": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
|
||||||
@@ -5256,6 +5328,21 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lowlight": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
|
"highlight.js": "~11.11.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
@@ -6144,6 +6231,30 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react": {
|
||||||
|
"version": "19.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||||
|
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/reactivity-store": {
|
||||||
|
"version": "0.3.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/reactivity-store/-/reactivity-store-0.3.12.tgz",
|
||||||
|
"integrity": "sha512-Idz9EL4dFUtQbHySZQzckWOTUfqjdYpUtNW0iOysC32mG7IjiUGB77QrsyR5eAWBkRiS9JscF6A3fuQAIy+LrQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/reactivity": "~3.5.22",
|
||||||
|
"@vue/shared": "~3.5.22",
|
||||||
|
"use-sync-external-store": "^1.6.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
@@ -7282,6 +7393,15 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/utf8-byte-length": {
|
"node_modules/utf8-byte-length": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"package:linux": "electron-builder --linux"
|
"package:linux": "electron-builder --linux"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@git-diff-view/solid": "^0.0.8",
|
||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
"@opencode-ai/sdk": "0.15.13",
|
"@opencode-ai/sdk": "0.15.13",
|
||||||
"@solidjs/router": "^0.13.0",
|
"@solidjs/router": "^0.13.0",
|
||||||
|
|||||||
20
src/App.tsx
20
src/App.tsx
@@ -24,7 +24,7 @@ import {
|
|||||||
showFolderSelection,
|
showFolderSelection,
|
||||||
setShowFolderSelection,
|
setShowFolderSelection,
|
||||||
} from "./stores/ui"
|
} from "./stores/ui"
|
||||||
import { toggleShowThinkingBlocks, preferences, addRecentFolder } from "./stores/preferences"
|
import { toggleShowThinkingBlocks, preferences, addRecentFolder, setDiffViewMode } from "./stores/preferences"
|
||||||
import {
|
import {
|
||||||
createInstance,
|
createInstance,
|
||||||
instances,
|
instances,
|
||||||
@@ -752,6 +752,24 @@ const App: Component = () => {
|
|||||||
action: toggleShowThinkingBlocks,
|
action: toggleShowThinkingBlocks,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
commandRegistry.register({
|
||||||
|
id: "diff-view-split",
|
||||||
|
label: () => `${(preferences().diffViewMode || "split") === "split" ? "✓ " : ""}Use Split Diff View`,
|
||||||
|
description: "Display tool-call diffs side-by-side",
|
||||||
|
category: "System",
|
||||||
|
keywords: ["diff", "split", "view"],
|
||||||
|
action: () => setDiffViewMode("split"),
|
||||||
|
})
|
||||||
|
|
||||||
|
commandRegistry.register({
|
||||||
|
id: "diff-view-unified",
|
||||||
|
label: () => `${(preferences().diffViewMode || "split") === "unified" ? "✓ " : ""}Use Unified Diff View`,
|
||||||
|
description: "Display tool-call diffs inline",
|
||||||
|
category: "System",
|
||||||
|
keywords: ["diff", "unified", "view"],
|
||||||
|
action: () => setDiffViewMode("unified"),
|
||||||
|
})
|
||||||
|
|
||||||
commandRegistry.register({
|
commandRegistry.register({
|
||||||
id: "help",
|
id: "help",
|
||||||
label: "Show Help",
|
label: "Show Help",
|
||||||
|
|||||||
62
src/components/diff-viewer.tsx
Normal file
62
src/components/diff-viewer.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { createMemo, Show } from "solid-js"
|
||||||
|
import { DiffView, DiffModeEnum } from "@git-diff-view/solid"
|
||||||
|
import { getLanguageFromPath } from "../lib/markdown"
|
||||||
|
import { normalizeDiffText } from "../lib/diff-utils"
|
||||||
|
import type { DiffViewMode } from "../stores/preferences"
|
||||||
|
|
||||||
|
interface ToolCallDiffViewerProps {
|
||||||
|
diffText: string
|
||||||
|
filePath?: string
|
||||||
|
theme: "light" | "dark"
|
||||||
|
mode: DiffViewMode
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiffData = {
|
||||||
|
oldFile?: { fileName?: string | null; fileLang?: string | null; content?: string | null }
|
||||||
|
newFile?: { fileName?: string | null; fileLang?: string | null; content?: string | null }
|
||||||
|
hunks: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
||||||
|
const diffData = createMemo<DiffData | null>(() => {
|
||||||
|
const normalized = normalizeDiffText(props.diffText)
|
||||||
|
if (!normalized) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const language = getLanguageFromPath(props.filePath) || "text"
|
||||||
|
const fileName = props.filePath || "diff"
|
||||||
|
|
||||||
|
return {
|
||||||
|
oldFile: {
|
||||||
|
fileName,
|
||||||
|
fileLang: language,
|
||||||
|
},
|
||||||
|
newFile: {
|
||||||
|
fileName,
|
||||||
|
fileLang: language,
|
||||||
|
},
|
||||||
|
hunks: [normalized],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="tool-call-diff-viewer">
|
||||||
|
<Show
|
||||||
|
when={diffData()}
|
||||||
|
fallback={<pre class="tool-call-diff-fallback">{props.diffText}</pre>}
|
||||||
|
>
|
||||||
|
{(data) => (
|
||||||
|
<DiffView
|
||||||
|
data={data()}
|
||||||
|
diffViewMode={props.mode === "split" ? DiffModeEnum.Split : DiffModeEnum.Unified}
|
||||||
|
diffViewTheme={props.theme}
|
||||||
|
diffViewHighlight
|
||||||
|
diffViewWrap={false}
|
||||||
|
diffViewFontSize={13}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,12 +5,51 @@ import ToolCall from "./tool-call"
|
|||||||
import { sseManager } from "../lib/sse-manager"
|
import { sseManager } from "../lib/sse-manager"
|
||||||
import Kbd from "./kbd"
|
import Kbd from "./kbd"
|
||||||
import { preferences } from "../stores/preferences"
|
import { preferences } from "../stores/preferences"
|
||||||
import { providers, getSessionInfo, computeDisplayParts } from "../stores/sessions"
|
import {
|
||||||
|
providers,
|
||||||
|
getSessionInfo,
|
||||||
|
computeDisplayParts,
|
||||||
|
sessions,
|
||||||
|
setActiveSession,
|
||||||
|
setActiveParentSession,
|
||||||
|
} from "../stores/sessions"
|
||||||
|
import { setActiveInstanceId } from "../stores/instances"
|
||||||
|
|
||||||
const SCROLL_OFFSET = 64
|
const SCROLL_OFFSET = 64
|
||||||
|
|
||||||
|
interface TaskSessionLocation {
|
||||||
|
sessionId: string
|
||||||
|
instanceId: string
|
||||||
|
parentId: string | null
|
||||||
|
}
|
||||||
|
|
||||||
const messageScrollState = new Map<string, { scrollTop: number; autoScroll: boolean }>()
|
const messageScrollState = new Map<string, { scrollTop: number; autoScroll: boolean }>()
|
||||||
|
|
||||||
|
function findTaskSessionLocation(sessionId: string): TaskSessionLocation | null {
|
||||||
|
if (!sessionId) return null
|
||||||
|
const allSessions = sessions()
|
||||||
|
for (const [instanceId, sessionMap] of allSessions) {
|
||||||
|
const session = sessionMap?.get(sessionId)
|
||||||
|
if (session) {
|
||||||
|
return {
|
||||||
|
sessionId: session.id,
|
||||||
|
instanceId,
|
||||||
|
parentId: session.parentId ?? null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToTaskSession(location: TaskSessionLocation) {
|
||||||
|
setActiveInstanceId(location.instanceId)
|
||||||
|
const parentToActivate = location.parentId ?? location.sessionId
|
||||||
|
setActiveParentSession(location.instanceId, parentToActivate)
|
||||||
|
if (location.parentId) {
|
||||||
|
setActiveSession(location.instanceId, location.sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate session tokens and cost from messagesInfo (matches TUI logic)
|
// Calculate session tokens and cost from messagesInfo (matches TUI logic)
|
||||||
function calculateSessionInfo(messagesInfo?: Map<string, any>, instanceId?: string) {
|
function calculateSessionInfo(messagesInfo?: Map<string, any>, instanceId?: string) {
|
||||||
if (!messagesInfo || messagesInfo.size === 0)
|
if (!messagesInfo || messagesInfo.size === 0)
|
||||||
@@ -611,12 +650,36 @@ export default function MessageStream(props: MessageStreamProps) {
|
|||||||
|
|
||||||
const toolPart = item.toolPart
|
const toolPart = item.toolPart
|
||||||
|
|
||||||
|
const taskSessionId =
|
||||||
|
typeof toolPart?.state?.metadata?.sessionId === "string" ? toolPart.state.metadata.sessionId : ""
|
||||||
|
const taskLocation = taskSessionId ? findTaskSessionLocation(taskSessionId) : null
|
||||||
|
|
||||||
|
const handleGoToTaskSession = (event: Event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
if (!taskLocation) return
|
||||||
|
navigateToTaskSession(taskLocation)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="tool-call-message" data-key={item.key}>
|
<div class="tool-call-message" data-key={item.key}>
|
||||||
<div class="tool-call-header-label">
|
<div class="tool-call-header-label">
|
||||||
<span class="tool-call-icon">🔧</span>
|
<div class="tool-call-header-meta">
|
||||||
<span>Tool Call</span>
|
<span class="tool-call-icon">🔧</span>
|
||||||
<span class="tool-name">{toolPart?.tool || "unknown"}</span>
|
<span>Tool Call</span>
|
||||||
|
<span class="tool-name">{toolPart?.tool || "unknown"}</span>
|
||||||
|
</div>
|
||||||
|
<Show when={taskSessionId}>
|
||||||
|
<button
|
||||||
|
class="tool-call-header-button"
|
||||||
|
type="button"
|
||||||
|
disabled={!taskLocation}
|
||||||
|
onClick={handleGoToTaskSession}
|
||||||
|
title={!taskLocation ? "Session not available yet" : "Go to session"}
|
||||||
|
>
|
||||||
|
Go to Session
|
||||||
|
</button>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<ToolCall toolCall={toolPart} toolCallId={item.key} />
|
<ToolCall toolCall={toolPart} toolCallId={item.key} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import { createSignal, Show, For, createEffect, onCleanup } from "solid-js"
|
import { createSignal, Show, For, createEffect, onCleanup } from "solid-js"
|
||||||
import { isToolCallExpanded, toggleToolCallExpanded, setToolCallExpanded } from "../stores/tool-call-state"
|
import { isToolCallExpanded, toggleToolCallExpanded, setToolCallExpanded } from "../stores/tool-call-state"
|
||||||
import { Markdown } from "./markdown"
|
import { Markdown } from "./markdown"
|
||||||
|
import { ToolCallDiffViewer } from "./diff-viewer"
|
||||||
import { useTheme } from "../lib/theme"
|
import { useTheme } from "../lib/theme"
|
||||||
|
import { getLanguageFromPath } from "../lib/markdown"
|
||||||
|
import { isRenderableDiffText } from "../lib/diff-utils"
|
||||||
|
import { preferences, setDiffViewMode, type DiffViewMode } from "../stores/preferences"
|
||||||
import type { TextPart } from "../types/message"
|
import type { TextPart } from "../types/message"
|
||||||
|
|
||||||
|
|
||||||
const toolScrollState = new Map<string, { scrollTop: number; atBottom: boolean }>()
|
const toolScrollState = new Map<string, { scrollTop: number; atBottom: boolean }>()
|
||||||
|
|
||||||
function updateScrollState(id: string, element: HTMLElement) {
|
function updateScrollState(id: string, element: HTMLElement) {
|
||||||
@@ -94,40 +99,36 @@ function getRelativePath(path: string): string {
|
|||||||
return parts.slice(-1)[0] || path
|
return parts.slice(-1)[0] || path
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLanguageFromPath(path: string): string | undefined {
|
const diffCapableTools = new Set(["edit", "patch"])
|
||||||
if (!path) return undefined
|
|
||||||
const ext = path.split(".").pop()?.toLowerCase()
|
interface DiffPayload {
|
||||||
const langMap: Record<string, string> = {
|
diffText: string
|
||||||
ts: "typescript",
|
filePath?: string
|
||||||
tsx: "typescript",
|
}
|
||||||
js: "javascript",
|
|
||||||
jsx: "javascript",
|
function extractDiffPayload(toolName: string, state: any): DiffPayload | null {
|
||||||
py: "python",
|
|
||||||
sh: "bash",
|
if (!diffCapableTools.has(toolName)) return null
|
||||||
bash: "bash",
|
if (!state) return null
|
||||||
json: "json",
|
const metadata = state.metadata || {}
|
||||||
html: "html",
|
const candidates = [metadata.diff, state.output, metadata.output]
|
||||||
css: "css",
|
let diffText: string | null = null
|
||||||
md: "markdown",
|
|
||||||
yaml: "yaml",
|
for (const candidate of candidates) {
|
||||||
yml: "yaml",
|
if (typeof candidate === "string" && isRenderableDiffText(candidate)) {
|
||||||
sql: "sql",
|
diffText = candidate
|
||||||
rs: "rust",
|
break
|
||||||
go: "go",
|
}
|
||||||
cpp: "cpp",
|
|
||||||
cc: "cpp",
|
|
||||||
cxx: "cpp",
|
|
||||||
hpp: "cpp",
|
|
||||||
h: "cpp",
|
|
||||||
c: "c",
|
|
||||||
java: "java",
|
|
||||||
cs: "csharp",
|
|
||||||
php: "php",
|
|
||||||
rb: "ruby",
|
|
||||||
swift: "swift",
|
|
||||||
kt: "kotlin",
|
|
||||||
}
|
}
|
||||||
return ext ? langMap[ext] : undefined
|
|
||||||
|
if (!diffText) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = state.input || {}
|
||||||
|
const filePath = input.filePath || metadata.filePath || input.path
|
||||||
|
|
||||||
|
return { diffText, filePath }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ToolCall(props: ToolCallProps) {
|
export default function ToolCall(props: ToolCallProps) {
|
||||||
@@ -136,6 +137,7 @@ export default function ToolCall(props: ToolCallProps) {
|
|||||||
const expanded = () => isToolCallExpanded(toolCallId())
|
const expanded = () => isToolCallExpanded(toolCallId())
|
||||||
const [initializedId, setInitializedId] = createSignal<string | null>(null)
|
const [initializedId, setInitializedId] = createSignal<string | null>(null)
|
||||||
|
|
||||||
|
|
||||||
let scrollContainerRef: HTMLDivElement | undefined
|
let scrollContainerRef: HTMLDivElement | undefined
|
||||||
|
|
||||||
const handleScrollRendered = () => {
|
const handleScrollRendered = () => {
|
||||||
@@ -356,9 +358,58 @@ export default function ToolCall(props: ToolCallProps) {
|
|||||||
return renderTaskTool()
|
return renderTaskTool()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const diffPayload = extractDiffPayload(toolName, state)
|
||||||
|
if (diffPayload) {
|
||||||
|
return renderDiffTool(diffPayload)
|
||||||
|
}
|
||||||
|
|
||||||
return renderMarkdownTool(toolName, state)
|
return renderMarkdownTool(toolName, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderDiffTool(payload: DiffPayload) {
|
||||||
|
const diffMode = () => (preferences().diffViewMode || "split") as DiffViewMode
|
||||||
|
|
||||||
|
const handleModeChange = (mode: DiffViewMode) => {
|
||||||
|
setDiffViewMode(mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class="message-text tool-call-markdown tool-call-markdown-large tool-call-diff-shell"
|
||||||
|
ref={(element) => initializeScrollContainer(element)}
|
||||||
|
onScroll={(event) => updateScrollState(toolCallId(), event.currentTarget)}
|
||||||
|
>
|
||||||
|
<div class="tool-call-diff-toolbar" role="group" aria-label="Diff view mode">
|
||||||
|
<span class="tool-call-diff-toolbar-label">Diff view</span>
|
||||||
|
<div class="tool-call-diff-toggle">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class={`tool-call-diff-mode-button${diffMode() === "split" ? " active" : ""}`}
|
||||||
|
aria-pressed={diffMode() === "split"}
|
||||||
|
onClick={() => handleModeChange("split")}
|
||||||
|
>
|
||||||
|
Split
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class={`tool-call-diff-mode-button${diffMode() === "unified" ? " active" : ""}`}
|
||||||
|
aria-pressed={diffMode() === "unified"}
|
||||||
|
onClick={() => handleModeChange("unified")}
|
||||||
|
>
|
||||||
|
Unified
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ToolCallDiffViewer
|
||||||
|
diffText={payload.diffText}
|
||||||
|
filePath={payload.filePath}
|
||||||
|
theme={isDark() ? "dark" : "light"}
|
||||||
|
mode={diffMode()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function renderMarkdownTool(toolName: string, state: any) {
|
function renderMarkdownTool(toolName: string, state: any) {
|
||||||
const content = getMarkdownContent(toolName, state)
|
const content = getMarkdownContent(toolName, state)
|
||||||
if (!content) {
|
if (!content) {
|
||||||
|
|||||||
50
src/lib/diff-utils.ts
Normal file
50
src/lib/diff-utils.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
const HUNK_PATTERN = /(^|\n)@@/m
|
||||||
|
const FILE_MARKER_PATTERN = /(^|\n)(diff --git |--- |\+\+\+)/
|
||||||
|
const BEGIN_PATCH_PATTERN = /^\*\*\* (Begin|End) Patch/
|
||||||
|
const UPDATE_FILE_PATTERN = /^\*\*\* Update File: (.+)$/
|
||||||
|
|
||||||
|
function stripCodeFence(value: string): string {
|
||||||
|
const trimmed = value.trim()
|
||||||
|
if (!trimmed.startsWith("```")) return trimmed
|
||||||
|
const lines = trimmed.split("\n")
|
||||||
|
if (lines.length < 2) return ""
|
||||||
|
const lastLine = lines[lines.length - 1]
|
||||||
|
if (!lastLine.startsWith("```")) return trimmed
|
||||||
|
return lines.slice(1, -1).join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeDiffText(raw: string): string {
|
||||||
|
if (!raw) return ""
|
||||||
|
const withoutFence = stripCodeFence(raw.replace(/\r\n/g, "\n"))
|
||||||
|
const lines = withoutFence.split("\n").map((line) => line.replace(/\s+$/u, ""))
|
||||||
|
|
||||||
|
let pendingFilePath: string | null = null
|
||||||
|
const cleanedLines: string[] = []
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (!line) continue
|
||||||
|
if (BEGIN_PATCH_PATTERN.test(line)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const updateMatch = line.match(UPDATE_FILE_PATTERN)
|
||||||
|
if (updateMatch) {
|
||||||
|
pendingFilePath = updateMatch[1]?.trim() || null
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cleanedLines.push(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingFilePath && !FILE_MARKER_PATTERN.test(cleanedLines.join("\n"))) {
|
||||||
|
cleanedLines.unshift(`+++ b/${pendingFilePath}`)
|
||||||
|
cleanedLines.unshift(`--- a/${pendingFilePath}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanedLines.join("\n").trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRenderableDiffText(raw?: string | null): raw is string {
|
||||||
|
if (!raw) return false
|
||||||
|
const normalized = normalizeDiffText(raw)
|
||||||
|
if (!normalized) return false
|
||||||
|
return HUNK_PATTERN.test(normalized)
|
||||||
|
}
|
||||||
@@ -7,6 +7,43 @@ let currentTheme: "light" | "dark" = "light"
|
|||||||
let isInitialized = false
|
let isInitialized = false
|
||||||
let highlightSuppressed = false
|
let highlightSuppressed = false
|
||||||
|
|
||||||
|
const extensionToLanguage: Record<string, string> = {
|
||||||
|
ts: "typescript",
|
||||||
|
tsx: "typescript",
|
||||||
|
js: "javascript",
|
||||||
|
jsx: "javascript",
|
||||||
|
py: "python",
|
||||||
|
sh: "bash",
|
||||||
|
bash: "bash",
|
||||||
|
json: "json",
|
||||||
|
html: "html",
|
||||||
|
css: "css",
|
||||||
|
md: "markdown",
|
||||||
|
yaml: "yaml",
|
||||||
|
yml: "yaml",
|
||||||
|
sql: "sql",
|
||||||
|
rs: "rust",
|
||||||
|
go: "go",
|
||||||
|
cpp: "cpp",
|
||||||
|
cc: "cpp",
|
||||||
|
cxx: "cpp",
|
||||||
|
hpp: "cpp",
|
||||||
|
h: "cpp",
|
||||||
|
c: "c",
|
||||||
|
java: "java",
|
||||||
|
cs: "csharp",
|
||||||
|
php: "php",
|
||||||
|
rb: "ruby",
|
||||||
|
swift: "swift",
|
||||||
|
kt: "kotlin",
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLanguageFromPath(path?: string | null): string | undefined {
|
||||||
|
if (!path) return undefined
|
||||||
|
const ext = path.split(".").pop()?.toLowerCase()
|
||||||
|
return ext ? extensionToLanguage[ext] : undefined
|
||||||
|
}
|
||||||
|
|
||||||
// Track loaded languages and queue for on-demand loading
|
// Track loaded languages and queue for on-demand loading
|
||||||
const loadedLanguages = new Set<string>()
|
const loadedLanguages = new Set<string>()
|
||||||
const queuedLanguages = new Set<string>()
|
const queuedLanguages = new Set<string>()
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export class FileStorage {
|
|||||||
environmentVariables: {},
|
environmentVariables: {},
|
||||||
modelRecents: [],
|
modelRecents: [],
|
||||||
agentModelSelections: {},
|
agentModelSelections: {},
|
||||||
|
diffViewMode: "split",
|
||||||
},
|
},
|
||||||
recentFolders: [],
|
recentFolders: [],
|
||||||
opencodeBinaries: [],
|
opencodeBinaries: [],
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { render } from "solid-js/web"
|
|||||||
import App from "./App"
|
import App from "./App"
|
||||||
import { ThemeProvider } from "./lib/theme"
|
import { ThemeProvider } from "./lib/theme"
|
||||||
import "./index.css"
|
import "./index.css"
|
||||||
|
import "@git-diff-view/solid/styles/diff-view-pure.css"
|
||||||
|
|
||||||
const root = document.getElementById("root")
|
const root = document.getElementById("root")
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,15 @@ export interface AgentModelSelections {
|
|||||||
[instanceId: string]: Record<string, ModelPreference>
|
[instanceId: string]: Record<string, ModelPreference>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DiffViewMode = "split" | "unified"
|
||||||
|
|
||||||
export interface Preferences {
|
export interface Preferences {
|
||||||
showThinkingBlocks: boolean
|
showThinkingBlocks: boolean
|
||||||
lastUsedBinary?: string
|
lastUsedBinary?: string
|
||||||
environmentVariables?: Record<string, string>
|
environmentVariables?: Record<string, string>
|
||||||
modelRecents?: ModelPreference[]
|
modelRecents?: ModelPreference[]
|
||||||
agentModelSelections?: AgentModelSelections
|
agentModelSelections?: AgentModelSelections
|
||||||
|
diffViewMode?: DiffViewMode
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OpenCodeBinary {
|
export interface OpenCodeBinary {
|
||||||
@@ -36,6 +39,7 @@ const defaultPreferences: Preferences = {
|
|||||||
showThinkingBlocks: false,
|
showThinkingBlocks: false,
|
||||||
modelRecents: [],
|
modelRecents: [],
|
||||||
agentModelSelections: {},
|
agentModelSelections: {},
|
||||||
|
diffViewMode: "split",
|
||||||
}
|
}
|
||||||
|
|
||||||
const [preferences, setPreferences] = createSignal<Preferences>(defaultPreferences)
|
const [preferences, setPreferences] = createSignal<Preferences>(defaultPreferences)
|
||||||
@@ -72,6 +76,11 @@ function updatePreferences(updates: Partial<Preferences>): void {
|
|||||||
saveConfig().catch(console.error)
|
saveConfig().catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setDiffViewMode(mode: DiffViewMode): void {
|
||||||
|
if (preferences().diffViewMode === mode) return
|
||||||
|
updatePreferences({ diffViewMode: mode })
|
||||||
|
}
|
||||||
|
|
||||||
function toggleShowThinkingBlocks(): void {
|
function toggleShowThinkingBlocks(): void {
|
||||||
updatePreferences({ showThinkingBlocks: !preferences().showThinkingBlocks })
|
updatePreferences({ showThinkingBlocks: !preferences().showThinkingBlocks })
|
||||||
}
|
}
|
||||||
@@ -202,4 +211,5 @@ export {
|
|||||||
addRecentModelPreference,
|
addRecentModelPreference,
|
||||||
setAgentModelPreference,
|
setAgentModelPreference,
|
||||||
getAgentModelPreference,
|
getAgentModelPreference,
|
||||||
|
setDiffViewMode,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -583,11 +583,46 @@ button.button-primary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tool-call-header-label {
|
.tool-call-header-label {
|
||||||
@apply flex items-center gap-2 font-semibold text-sm;
|
@apply flex items-center justify-between gap-2 font-semibold text-sm;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
margin-bottom: 1px;
|
margin-bottom: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool-call-header-meta {
|
||||||
|
@apply flex items-center gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-header-button {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid var(--border-base);
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 0.15rem 0.75rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
line-height: 1;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 1.5rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-header-button:hover:not(:disabled) {
|
||||||
|
background-color: var(--surface-hover);
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-header-button:active:not(:disabled) {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-header-button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.tool-call-header-label .tool-call-icon {
|
.tool-call-header-label .tool-call-icon {
|
||||||
@apply text-base;
|
@apply text-base;
|
||||||
}
|
}
|
||||||
@@ -667,8 +702,14 @@ button.button-primary {
|
|||||||
@apply border rounded-md overflow-hidden;
|
@apply border rounded-md overflow-hidden;
|
||||||
border-color: var(--border-base);
|
border-color: var(--border-base);
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
--tool-call-line-unit: 1.4em;
|
||||||
|
--tool-call-lines-compact: 24;
|
||||||
|
--tool-call-lines-large: 48;
|
||||||
|
--tool-call-max-height-compact: calc(var(--tool-call-lines-compact) * var(--tool-call-line-unit));
|
||||||
|
--tool-call-max-height-large: calc(var(--tool-call-lines-large) * var(--tool-call-line-unit));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.tool-call-message .tool-call {
|
.tool-call-message .tool-call {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@@ -746,11 +787,12 @@ button.button-primary {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
max-height: calc(10 * 1.4em);
|
max-height: var(--tool-call-max-height-compact, calc(25 * 1.4em));
|
||||||
overflow-y: auto;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-call-details {
|
.tool-call-details {
|
||||||
|
|
||||||
@apply flex flex-col;
|
@apply flex flex-col;
|
||||||
background-color: var(--surface-code);
|
background-color: var(--surface-code);
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
@@ -763,8 +805,8 @@ button.button-primary {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
line-height: var(--line-height-tight);
|
line-height: var(--line-height-tight);
|
||||||
max-height: calc(15 * 1.4em);
|
max-height: var(--tool-call-max-height-compact, calc(25 * 1.4em));
|
||||||
overflow-y: auto;
|
overflow-y: scroll;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: var(--border-base) transparent;
|
scrollbar-color: var(--border-base) transparent;
|
||||||
scrollbar-gutter: stable both-edges;
|
scrollbar-gutter: stable both-edges;
|
||||||
@@ -772,10 +814,84 @@ button.button-primary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tool-call-markdown-large {
|
.tool-call-markdown-large {
|
||||||
max-height: calc(50 * 1.4em);
|
max-height: var(--tool-call-max-height-large, calc(48 * 1.4em));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-viewer {
|
||||||
|
max-height: var(--tool-call-max-height-large, calc(48 * 1.4em));
|
||||||
|
overflow: auto;
|
||||||
|
background-color: var(--surface-code);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-toolbar {
|
||||||
|
@apply flex items-center justify-between gap-3 px-3 py-2;
|
||||||
|
background-color: var(--surface-secondary);
|
||||||
|
border-bottom: 1px solid var(--border-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-toolbar-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-toggle {
|
||||||
|
@apply inline-flex items-center gap-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-mode-button {
|
||||||
|
@apply border text-xs font-semibold px-3 py-1 rounded transition-all duration-150;
|
||||||
|
border-color: var(--border-base);
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-mode-button:hover {
|
||||||
|
background-color: var(--surface-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-mode-button.active {
|
||||||
|
background-color: var(--accent-primary);
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
color: var(--text-inverted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-viewer .diff-tailwindcss-wrapper {
|
||||||
|
background-color: transparent;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-viewer .diff-view-wrapper {
|
||||||
|
font-family: var(--font-family-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-fallback {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background-color: var(--surface-code);
|
||||||
|
font-family: var(--font-family-mono);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
line-height: var(--line-height-tight);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-viewer .diff-line-old-num,
|
||||||
|
.tool-call-diff-viewer .diff-line-new-num,
|
||||||
|
.tool-call-diff-viewer .diff-line-num {
|
||||||
|
width: auto !important;
|
||||||
|
min-width: 4ch;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-call-markdown .markdown-code-block {
|
.tool-call-markdown .markdown-code-block {
|
||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: none;
|
border: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
@@ -823,11 +939,12 @@ button.button-primary {
|
|||||||
background-color: var(--surface-base);
|
background-color: var(--surface-base);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
max-height: calc(25 * 1.4em);
|
max-height: var(--tool-call-max-height-compact, calc(25 * 1.4em));
|
||||||
overflow-y: auto;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-call-section code {
|
.tool-call-section code {
|
||||||
|
|
||||||
font-family: var(--font-family-mono);
|
font-family: var(--font-family-mono);
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
line-height: var(--line-height-tight);
|
line-height: var(--line-height-tight);
|
||||||
@@ -861,6 +978,24 @@ button.button-primary {
|
|||||||
@apply text-base mr-1;
|
@apply text-base mr-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tool-call-action-button {
|
||||||
|
@apply border text-xs font-semibold px-3 py-1 rounded transition-colors h-8 flex items-center;
|
||||||
|
border-color: var(--border-base);
|
||||||
|
color: var(--text-muted);
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-action-button:hover:not(:disabled) {
|
||||||
|
background-color: var(--surface-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-action-button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.tool-call-bash,
|
.tool-call-bash,
|
||||||
.tool-call-diff {
|
.tool-call-diff {
|
||||||
@apply my-2;
|
@apply my-2;
|
||||||
|
|||||||
Reference in New Issue
Block a user