fix(app): patch tool diff rendering
This commit is contained in:
parent
039c60170d
commit
d98be39344
3 changed files with 125 additions and 25 deletions
43
packages/ui/src/components/apply-patch-file.test.ts
Normal file
43
packages/ui/src/components/apply-patch-file.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import { patchFiles } from "./apply-patch-file"
|
||||||
|
import { text } from "./session-diff"
|
||||||
|
|
||||||
|
describe("apply patch file", () => {
|
||||||
|
test("parses patch metadata from the server", () => {
|
||||||
|
const file = patchFiles([
|
||||||
|
{
|
||||||
|
filePath: "/tmp/a.ts",
|
||||||
|
relativePath: "a.ts",
|
||||||
|
type: "update",
|
||||||
|
patch:
|
||||||
|
"Index: a.ts\n===================================================================\n--- a.ts\t\n+++ a.ts\t\n@@ -1,2 +1,2 @@\n one\n-two\n+three\n",
|
||||||
|
additions: 1,
|
||||||
|
deletions: 1,
|
||||||
|
},
|
||||||
|
])[0]
|
||||||
|
|
||||||
|
expect(file).toBeDefined()
|
||||||
|
expect(file?.view.fileDiff.name).toBe("a.ts")
|
||||||
|
expect(text(file!.view, "deletions")).toBe("one\ntwo\n")
|
||||||
|
expect(text(file!.view, "additions")).toBe("one\nthree\n")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("keeps legacy before and after payloads working", () => {
|
||||||
|
const file = patchFiles([
|
||||||
|
{
|
||||||
|
filePath: "/tmp/a.ts",
|
||||||
|
relativePath: "a.ts",
|
||||||
|
type: "update",
|
||||||
|
before: "one\n",
|
||||||
|
after: "two\n",
|
||||||
|
additions: 1,
|
||||||
|
deletions: 1,
|
||||||
|
},
|
||||||
|
])[0]
|
||||||
|
|
||||||
|
expect(file).toBeDefined()
|
||||||
|
expect(file?.view.patch).toContain("@@ -1,1 +1,1 @@")
|
||||||
|
expect(text(file!.view, "deletions")).toBe("one\n")
|
||||||
|
expect(text(file!.view, "additions")).toBe("two\n")
|
||||||
|
})
|
||||||
|
})
|
||||||
78
packages/ui/src/components/apply-patch-file.ts
Normal file
78
packages/ui/src/components/apply-patch-file.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { normalize, type ViewDiff } from "./session-diff"
|
||||||
|
|
||||||
|
type Kind = "add" | "update" | "delete" | "move"
|
||||||
|
|
||||||
|
type Raw = {
|
||||||
|
filePath?: string
|
||||||
|
relativePath?: string
|
||||||
|
type?: Kind
|
||||||
|
patch?: string
|
||||||
|
diff?: string
|
||||||
|
before?: string
|
||||||
|
after?: string
|
||||||
|
additions?: number
|
||||||
|
deletions?: number
|
||||||
|
movePath?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApplyPatchFile = {
|
||||||
|
filePath: string
|
||||||
|
relativePath: string
|
||||||
|
type: Kind
|
||||||
|
additions: number
|
||||||
|
deletions: number
|
||||||
|
movePath?: string
|
||||||
|
view: ViewDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
function kind(value: unknown) {
|
||||||
|
if (value === "add" || value === "update" || value === "delete" || value === "move") return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function status(type: Kind): "added" | "deleted" | "modified" {
|
||||||
|
if (type === "add") return "added"
|
||||||
|
if (type === "delete") return "deleted"
|
||||||
|
return "modified"
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchFile(raw: unknown): ApplyPatchFile | undefined {
|
||||||
|
if (!raw || typeof raw !== "object") return
|
||||||
|
|
||||||
|
const value = raw as Raw
|
||||||
|
const type = kind(value.type)
|
||||||
|
const filePath = typeof value.filePath === "string" ? value.filePath : undefined
|
||||||
|
const relativePath = typeof value.relativePath === "string" ? value.relativePath : filePath
|
||||||
|
const patch = typeof value.patch === "string" ? value.patch : typeof value.diff === "string" ? value.diff : undefined
|
||||||
|
const before = typeof value.before === "string" ? value.before : undefined
|
||||||
|
const after = typeof value.after === "string" ? value.after : undefined
|
||||||
|
|
||||||
|
if (!type || !filePath || !relativePath) return
|
||||||
|
if (!patch && before === undefined && after === undefined) return
|
||||||
|
|
||||||
|
const additions = typeof value.additions === "number" ? value.additions : 0
|
||||||
|
const deletions = typeof value.deletions === "number" ? value.deletions : 0
|
||||||
|
const movePath = typeof value.movePath === "string" ? value.movePath : undefined
|
||||||
|
|
||||||
|
return {
|
||||||
|
filePath,
|
||||||
|
relativePath,
|
||||||
|
type,
|
||||||
|
additions,
|
||||||
|
deletions,
|
||||||
|
movePath,
|
||||||
|
view: normalize({
|
||||||
|
file: relativePath,
|
||||||
|
patch,
|
||||||
|
before,
|
||||||
|
after,
|
||||||
|
additions,
|
||||||
|
deletions,
|
||||||
|
status: status(type),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchFiles(raw: unknown) {
|
||||||
|
if (!Array.isArray(raw)) return []
|
||||||
|
return raw.map(patchFile).filter((file): file is ApplyPatchFile => !!file)
|
||||||
|
}
|
||||||
|
|
@ -54,6 +54,7 @@ import { Spinner } from "./spinner"
|
||||||
import { TextShimmer } from "./text-shimmer"
|
import { TextShimmer } from "./text-shimmer"
|
||||||
import { AnimatedCountList } from "./tool-count-summary"
|
import { AnimatedCountList } from "./tool-count-summary"
|
||||||
import { ToolStatusTitle } from "./tool-status-title"
|
import { ToolStatusTitle } from "./tool-status-title"
|
||||||
|
import { patchFiles } from "./apply-patch-file"
|
||||||
import { animate } from "motion"
|
import { animate } from "motion"
|
||||||
import { useLocation } from "@solidjs/router"
|
import { useLocation } from "@solidjs/router"
|
||||||
import { attached, inline, kind } from "./message-file"
|
import { attached, inline, kind } from "./message-file"
|
||||||
|
|
@ -2014,24 +2015,12 @@ ToolRegistry.register({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
interface ApplyPatchFile {
|
|
||||||
filePath: string
|
|
||||||
relativePath: string
|
|
||||||
type: "add" | "update" | "delete" | "move"
|
|
||||||
diff: string
|
|
||||||
before: string
|
|
||||||
after: string
|
|
||||||
additions: number
|
|
||||||
deletions: number
|
|
||||||
movePath?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolRegistry.register({
|
ToolRegistry.register({
|
||||||
name: "apply_patch",
|
name: "apply_patch",
|
||||||
render(props) {
|
render(props) {
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const fileComponent = useFileComponent()
|
const fileComponent = useFileComponent()
|
||||||
const files = createMemo(() => (props.metadata.files ?? []) as ApplyPatchFile[])
|
const files = createMemo(() => patchFiles(props.metadata.files))
|
||||||
const pending = createMemo(() => props.status === "pending" || props.status === "running")
|
const pending = createMemo(() => props.status === "pending" || props.status === "running")
|
||||||
const single = createMemo(() => {
|
const single = createMemo(() => {
|
||||||
const list = files()
|
const list = files()
|
||||||
|
|
@ -2137,12 +2126,7 @@ ToolRegistry.register({
|
||||||
<Accordion.Content>
|
<Accordion.Content>
|
||||||
<Show when={visible()}>
|
<Show when={visible()}>
|
||||||
<div data-component="apply-patch-file-diff">
|
<div data-component="apply-patch-file-diff">
|
||||||
<Dynamic
|
<Dynamic component={fileComponent} mode="diff" fileDiff={file.view.fileDiff} />
|
||||||
component={fileComponent}
|
|
||||||
mode="diff"
|
|
||||||
before={{ name: file.filePath, contents: file.before }}
|
|
||||||
after={{ name: file.movePath ?? file.filePath, contents: file.after }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</Accordion.Content>
|
</Accordion.Content>
|
||||||
|
|
@ -2212,12 +2196,7 @@ ToolRegistry.register({
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div data-component="apply-patch-file-diff">
|
<div data-component="apply-patch-file-diff">
|
||||||
<Dynamic
|
<Dynamic component={fileComponent} mode="diff" fileDiff={single()!.view.fileDiff} />
|
||||||
component={fileComponent}
|
|
||||||
mode="diff"
|
|
||||||
before={{ name: single()!.filePath, contents: single()!.before }}
|
|
||||||
after={{ name: single()!.movePath ?? single()!.filePath, contents: single()!.after }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</ToolFileAccordion>
|
</ToolFileAccordion>
|
||||||
</BasicTool>
|
</BasicTool>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue