mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
fix(kilo-ui): add diff viewer action to write tool header (#10008)
Extend the "Open in Diff Viewer" icon button to appear on Write tool calls, not just Edit. Refactor shared CSS selectors to use generic data-slot names (tool-trigger-actions) so both triggers share styling.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Show an "Open in Diff Viewer" icon on Write tool calls (matching the Edit tool), so the changes can be inspected in a full diff tab.
|
||||
@@ -40,12 +40,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Hover-revealed "Open in Diff Viewer" icon button in the edit-tool header */
|
||||
[data-component="tool-trigger"] [data-slot="basic-tool-tool-info"]:has([data-component="edit-trigger"]) {
|
||||
/* Hover-revealed "Open in Diff Viewer" icon button in edit/write tool headers */
|
||||
[data-component="tool-trigger"] [data-slot="basic-tool-tool-info"]:has([data-component="edit-trigger"]),
|
||||
[data-component="tool-trigger"] [data-slot="basic-tool-tool-info"]:has([data-component="write-trigger"]) {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
[data-component="edit-trigger"] {
|
||||
[data-component="edit-trigger"],
|
||||
[data-component="write-trigger"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -58,7 +60,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-slot="edit-trigger-actions"] {
|
||||
[data-slot="tool-trigger-actions"] {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
@@ -68,8 +70,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="tool-trigger"]:hover [data-slot="edit-trigger-actions"],
|
||||
[data-slot="edit-trigger-actions"]:focus-within {
|
||||
[data-component="tool-trigger"]:hover [data-slot="tool-trigger-actions"],
|
||||
[data-slot="tool-trigger-actions"]:focus-within {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -2185,7 +2185,7 @@ ToolRegistry.register({
|
||||
</div>
|
||||
</div>
|
||||
<Show when={canOpenDiff()}>
|
||||
<span data-slot="edit-trigger-actions">
|
||||
<span data-slot="tool-trigger-actions">
|
||||
<Tooltip value={i18n.t("ui.messagePart.openInDiffViewer")} placement="top" gutter={4}>
|
||||
<IconButton
|
||||
icon="square-arrow-top-right"
|
||||
@@ -2249,21 +2249,35 @@ ToolRegistry.register({
|
||||
if (!diff?.patch) return
|
||||
return contents(diff)
|
||||
})
|
||||
const canOpenDiff = () => !!data.openDiff && !!props.input.filePath && (!!view() || !!props.input.content)
|
||||
const canOpenFile = () => !!data.openFile && !!props.input.filePath
|
||||
|
||||
const openDiff = () => {
|
||||
if (!data.openDiff || !props.input.filePath) return
|
||||
const v = view()
|
||||
data.openDiff({
|
||||
file: props.metadata?.filediff?.file || props.input.filePath,
|
||||
before: v?.before ?? "",
|
||||
after: v?.after ?? props.input.content ?? "",
|
||||
additions: props.metadata?.filediff?.additions ?? 0,
|
||||
deletions: props.metadata?.filediff?.deletions ?? 0,
|
||||
})
|
||||
}
|
||||
|
||||
const handleFileClick = (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
if (data.openDiff && view()) {
|
||||
data.openDiff({
|
||||
file: props.metadata?.filediff?.file || props.input.filePath,
|
||||
before: view()!.before,
|
||||
after: view()!.after,
|
||||
additions: props.metadata?.filediff?.additions ?? 0,
|
||||
deletions: props.metadata?.filediff?.deletions ?? 0,
|
||||
})
|
||||
if (canOpenDiff()) {
|
||||
openDiff()
|
||||
return
|
||||
}
|
||||
if (!data.openFile || !props.input.filePath) return
|
||||
data.openFile(props.input.filePath)
|
||||
if (canOpenFile()) {
|
||||
data.openFile!(props.input.filePath!)
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenDiffClick = (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
openDiff()
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -2286,16 +2300,26 @@ ToolRegistry.register({
|
||||
path={props.input.filePath?.includes("/") ? getDirectory(props.input.filePath!) : undefined}
|
||||
changes={props.metadata.filediff}
|
||||
animate={reveal()}
|
||||
onClick={
|
||||
(view() && data.openDiff) || (data.openFile && props.input.filePath)
|
||||
? handleFileClick
|
||||
: undefined
|
||||
}
|
||||
onClick={canOpenDiff() || canOpenFile() ? handleFileClick : undefined}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={canOpenDiff()}>
|
||||
<span data-slot="tool-trigger-actions">
|
||||
<Tooltip value={i18n.t("ui.messagePart.openInDiffViewer")} placement="top" gutter={4}>
|
||||
<IconButton
|
||||
icon="square-arrow-top-right"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={handleOpenDiffClick}
|
||||
aria-label={i18n.t("ui.messagePart.openInDiffViewer")}
|
||||
/>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -196,6 +196,35 @@ const editCompletedPart: ToolPart = {
|
||||
},
|
||||
}
|
||||
|
||||
// Completed write tool that creates a new file — exercises canOpenDiff() via
|
||||
// `props.input.content` so the "Open in Diff Viewer" icon button renders even
|
||||
// when metadata.filediff has no diff payload.
|
||||
const writeCompletedPart: ToolPart = {
|
||||
id: "part-tool-write-done",
|
||||
sessionID: SESSION_ID,
|
||||
messageID: ASST_MSG_ID,
|
||||
type: "tool",
|
||||
callID: "call-write-done",
|
||||
tool: "write",
|
||||
state: {
|
||||
status: "completed",
|
||||
input: {
|
||||
filePath: "src/greet.ts",
|
||||
content: "export function greet(name: string) {\n return `Hello, ${name}!`\n}\n",
|
||||
},
|
||||
output: "File written successfully",
|
||||
title: "Write file",
|
||||
metadata: {
|
||||
filediff: {
|
||||
file: "src/greet.ts",
|
||||
additions: 3,
|
||||
deletions: 0,
|
||||
},
|
||||
},
|
||||
time: { start: now - 4000, end: now - 3500 },
|
||||
},
|
||||
}
|
||||
|
||||
// --- Reasoning part ---
|
||||
|
||||
const reasoningPart: ReasoningPart = {
|
||||
@@ -243,6 +272,7 @@ const mockDataBash = createMockData([bashCompleted])
|
||||
const mockDataContextGroup = createMockData([completedToolPart, grepCompleted, globCompleted, textPart])
|
||||
// Completed edit tool with filediff — exercises the "Open in Diff Viewer" button path
|
||||
const mockDataEdit = createMockData([editCompletedPart])
|
||||
const mockDataWrite = createMockData([writeCompletedPart])
|
||||
|
||||
function AllProviders(props: { children: any; data?: MockData; onOpenDiff?: () => void }) {
|
||||
return (
|
||||
@@ -402,7 +432,18 @@ export const WithEditToolOpenDiffAction: Story = {
|
||||
name: "WithEditTool (open-diff action visible)",
|
||||
render: () => (
|
||||
<AllProviders data={mockDataEdit} onOpenDiff={() => {}}>
|
||||
<style>{`[data-slot="edit-trigger-actions"] { opacity: 1 !important; }`}</style>
|
||||
<style>{`[data-slot="tool-trigger-actions"] { opacity: 1 !important; }`}</style>
|
||||
<AssistantParts messages={[mockAssistantMessage]} />
|
||||
</AllProviders>
|
||||
),
|
||||
}
|
||||
|
||||
// --- Completed write tool with content → "Open in Diff Viewer" icon visible ---
|
||||
export const WithWriteToolOpenDiffAction: Story = {
|
||||
name: "WithWriteTool (open-diff action visible)",
|
||||
render: () => (
|
||||
<AllProviders data={mockDataWrite} onOpenDiff={() => {}}>
|
||||
<style>{`[data-slot="tool-trigger-actions"] { opacity: 1 !important; }`}</style>
|
||||
<AssistantParts messages={[mockAssistantMessage]} />
|
||||
</AllProviders>
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user