mirror of
https://fastgit.cc/https://github.com/anomalyco/opencode
synced 2026-04-20 21:00:29 +08:00
fix(app): patch tool diff rendering
This commit is contained in:
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 { AnimatedCountList } from "./tool-count-summary"
|
||||
import { ToolStatusTitle } from "./tool-status-title"
|
||||
import { patchFiles } from "./apply-patch-file"
|
||||
import { animate } from "motion"
|
||||
import { useLocation } from "@solidjs/router"
|
||||
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({
|
||||
name: "apply_patch",
|
||||
render(props) {
|
||||
const i18n = useI18n()
|
||||
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 single = createMemo(() => {
|
||||
const list = files()
|
||||
@@ -2137,12 +2126,7 @@ ToolRegistry.register({
|
||||
<Accordion.Content>
|
||||
<Show when={visible()}>
|
||||
<div data-component="apply-patch-file-diff">
|
||||
<Dynamic
|
||||
component={fileComponent}
|
||||
mode="diff"
|
||||
before={{ name: file.filePath, contents: file.before }}
|
||||
after={{ name: file.movePath ?? file.filePath, contents: file.after }}
|
||||
/>
|
||||
<Dynamic component={fileComponent} mode="diff" fileDiff={file.view.fileDiff} />
|
||||
</div>
|
||||
</Show>
|
||||
</Accordion.Content>
|
||||
@@ -2212,12 +2196,7 @@ ToolRegistry.register({
|
||||
}
|
||||
>
|
||||
<div data-component="apply-patch-file-diff">
|
||||
<Dynamic
|
||||
component={fileComponent}
|
||||
mode="diff"
|
||||
before={{ name: single()!.filePath, contents: single()!.before }}
|
||||
after={{ name: single()!.movePath ?? single()!.filePath, contents: single()!.after }}
|
||||
/>
|
||||
<Dynamic component={fileComponent} mode="diff" fileDiff={single()!.view.fileDiff} />
|
||||
</div>
|
||||
</ToolFileAccordion>
|
||||
</BasicTool>
|
||||
|
||||
Reference in New Issue
Block a user