wip - ui
This commit is contained in:
parent
ab78a46396
commit
f1ec28176f
2 changed files with 115 additions and 18 deletions
|
|
@ -1385,8 +1385,8 @@ function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMess
|
||||||
<Match when={props.part.tool === "task"}>
|
<Match when={props.part.tool === "task"}>
|
||||||
<Task {...toolprops} />
|
<Task {...toolprops} />
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={props.part.tool === "patch"}>
|
<Match when={props.part.tool === "apply_patch"}>
|
||||||
<Patch {...toolprops} />
|
<ApplyPatch {...toolprops} />
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={props.part.tool === "todowrite"}>
|
<Match when={props.part.tool === "todowrite"}>
|
||||||
<TodoWrite {...toolprops} />
|
<TodoWrite {...toolprops} />
|
||||||
|
|
@ -1835,20 +1835,74 @@ function Edit(props: ToolProps<typeof EditTool>) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Patch(props: ToolProps<typeof ApplyPatchTool>) {
|
function ApplyPatch(props: ToolProps<typeof ApplyPatchTool>) {
|
||||||
const { theme } = useTheme()
|
const ctx = use()
|
||||||
|
const { theme, syntax } = useTheme()
|
||||||
|
|
||||||
|
const files = createMemo(() => props.metadata.files ?? [])
|
||||||
|
|
||||||
|
const view = createMemo(() => {
|
||||||
|
const diffStyle = ctx.sync.data.config.tui?.diff_style
|
||||||
|
if (diffStyle === "stacked") return "unified"
|
||||||
|
return ctx.width > 120 ? "split" : "unified"
|
||||||
|
})
|
||||||
|
|
||||||
|
function Diff(p: { diff: string; filePath: string }) {
|
||||||
|
return (
|
||||||
|
<box paddingLeft={1}>
|
||||||
|
<diff
|
||||||
|
diff={p.diff}
|
||||||
|
view={view()}
|
||||||
|
filetype={filetype(p.filePath)}
|
||||||
|
syntaxStyle={syntax()}
|
||||||
|
showLineNumbers={true}
|
||||||
|
width="100%"
|
||||||
|
wrapMode={ctx.diffWrapMode()}
|
||||||
|
fg={theme.text}
|
||||||
|
addedBg={theme.diffAddedBg}
|
||||||
|
removedBg={theme.diffRemovedBg}
|
||||||
|
contextBg={theme.diffContextBg}
|
||||||
|
addedSignColor={theme.diffHighlightAdded}
|
||||||
|
removedSignColor={theme.diffHighlightRemoved}
|
||||||
|
lineNumberFg={theme.diffLineNumber}
|
||||||
|
lineNumberBg={theme.diffContextBg}
|
||||||
|
addedLineNumberBg={theme.diffAddedLineNumberBg}
|
||||||
|
removedLineNumberBg={theme.diffRemovedLineNumberBg}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function title(file: { type: string; relativePath: string; filePath: string; deletions: number }) {
|
||||||
|
if (file.type === "delete") return "# Deleted " + file.relativePath
|
||||||
|
if (file.type === "add") return "# Created " + file.relativePath
|
||||||
|
if (file.type === "move") return "# Moved " + normalizePath(file.filePath) + " → " + file.relativePath
|
||||||
|
return "← Edit " + file.relativePath
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={props.output !== undefined}>
|
<Match when={files().length > 0}>
|
||||||
<BlockTool title="# Patch" part={props.part}>
|
<For each={files()}>
|
||||||
<box>
|
{(file) => (
|
||||||
<text fg={theme.text}>{props.output?.trim()}</text>
|
<BlockTool title={title(file)} part={props.part}>
|
||||||
</box>
|
<Show
|
||||||
</BlockTool>
|
when={file.type !== "delete"}
|
||||||
|
fallback={
|
||||||
|
<text fg={theme.diffRemoved}>
|
||||||
|
-{file.deletions} line{file.deletions !== 1 ? "s" : ""}
|
||||||
|
</text>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Diff diff={file.diff} filePath={file.filePath} />
|
||||||
|
</Show>
|
||||||
|
</BlockTool>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<InlineTool icon="%" pending="Preparing patch..." complete={false} part={props.part}>
|
<InlineTool icon="%" pending="Preparing apply_patch..." complete={false} part={props.part}>
|
||||||
Patch
|
apply_patch
|
||||||
</InlineTool>
|
</InlineTool>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ import { Bus } from "../bus"
|
||||||
import { FileWatcher } from "../file/watcher"
|
import { FileWatcher } from "../file/watcher"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { Patch } from "../patch"
|
import { Patch } from "../patch"
|
||||||
import { createTwoFilesPatch } from "diff"
|
import { createTwoFilesPatch, diffLines } from "diff"
|
||||||
import { assertExternalDirectory } from "./external-directory"
|
import { assertExternalDirectory } from "./external-directory"
|
||||||
|
import { trimDiff } from "./edit"
|
||||||
|
|
||||||
const PatchParams = z.object({
|
const PatchParams = z.object({
|
||||||
patchText: z.string().describe("The full patch text that describes all changes to be made"),
|
patchText: z.string().describe("The full patch text that describes all changes to be made"),
|
||||||
|
|
@ -46,6 +47,9 @@ export const ApplyPatchTool = Tool.define("apply_patch", {
|
||||||
newContent: string
|
newContent: string
|
||||||
type: "add" | "update" | "delete" | "move"
|
type: "add" | "update" | "delete" | "move"
|
||||||
movePath?: string
|
movePath?: string
|
||||||
|
diff: string
|
||||||
|
additions: number
|
||||||
|
deletions: number
|
||||||
}> = []
|
}> = []
|
||||||
|
|
||||||
let totalDiff = ""
|
let totalDiff = ""
|
||||||
|
|
@ -59,20 +63,30 @@ export const ApplyPatchTool = Tool.define("apply_patch", {
|
||||||
const oldContent = ""
|
const oldContent = ""
|
||||||
const newContent =
|
const newContent =
|
||||||
hunk.contents.length === 0 || hunk.contents.endsWith("\n") ? hunk.contents : `${hunk.contents}\n`
|
hunk.contents.length === 0 || hunk.contents.endsWith("\n") ? hunk.contents : `${hunk.contents}\n`
|
||||||
const diff = createTwoFilesPatch(filePath, filePath, oldContent, newContent)
|
const diff = trimDiff(createTwoFilesPatch(filePath, filePath, oldContent, newContent))
|
||||||
|
|
||||||
|
let additions = 0
|
||||||
|
let deletions = 0
|
||||||
|
for (const change of diffLines(oldContent, newContent)) {
|
||||||
|
if (change.added) additions += change.count || 0
|
||||||
|
if (change.removed) deletions += change.count || 0
|
||||||
|
}
|
||||||
|
|
||||||
fileChanges.push({
|
fileChanges.push({
|
||||||
filePath,
|
filePath,
|
||||||
oldContent,
|
oldContent,
|
||||||
newContent,
|
newContent,
|
||||||
type: "add",
|
type: "add",
|
||||||
|
diff,
|
||||||
|
additions,
|
||||||
|
deletions,
|
||||||
})
|
})
|
||||||
|
|
||||||
totalDiff += diff + "\n"
|
totalDiff += diff + "\n"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case "update":
|
case "update": {
|
||||||
// Check if file exists for update
|
// Check if file exists for update
|
||||||
const stats = await fs.stat(filePath).catch(() => null)
|
const stats = await fs.stat(filePath).catch(() => null)
|
||||||
if (!stats || stats.isDirectory()) {
|
if (!stats || stats.isDirectory()) {
|
||||||
|
|
@ -92,7 +106,14 @@ export const ApplyPatchTool = Tool.define("apply_patch", {
|
||||||
throw new Error(`apply_patch verification failed: ${error}`)
|
throw new Error(`apply_patch verification failed: ${error}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const diff = createTwoFilesPatch(filePath, filePath, oldContent, newContent)
|
const diff = trimDiff(createTwoFilesPatch(filePath, filePath, oldContent, newContent))
|
||||||
|
|
||||||
|
let additions = 0
|
||||||
|
let deletions = 0
|
||||||
|
for (const change of diffLines(oldContent, newContent)) {
|
||||||
|
if (change.added) additions += change.count || 0
|
||||||
|
if (change.removed) deletions += change.count || 0
|
||||||
|
}
|
||||||
|
|
||||||
const movePath = hunk.move_path ? path.resolve(Instance.directory, hunk.move_path) : undefined
|
const movePath = hunk.move_path ? path.resolve(Instance.directory, hunk.move_path) : undefined
|
||||||
await assertExternalDirectory(ctx, movePath)
|
await assertExternalDirectory(ctx, movePath)
|
||||||
|
|
@ -103,28 +124,38 @@ export const ApplyPatchTool = Tool.define("apply_patch", {
|
||||||
newContent,
|
newContent,
|
||||||
type: hunk.move_path ? "move" : "update",
|
type: hunk.move_path ? "move" : "update",
|
||||||
movePath,
|
movePath,
|
||||||
|
diff,
|
||||||
|
additions,
|
||||||
|
deletions,
|
||||||
})
|
})
|
||||||
|
|
||||||
totalDiff += diff + "\n"
|
totalDiff += diff + "\n"
|
||||||
break
|
break
|
||||||
|
}
|
||||||
|
|
||||||
case "delete":
|
case "delete": {
|
||||||
// Check if file exists for deletion
|
// Check if file exists for deletion
|
||||||
await FileTime.assert(ctx.sessionID, filePath)
|
await FileTime.assert(ctx.sessionID, filePath)
|
||||||
const contentToDelete = await fs.readFile(filePath, "utf-8").catch((error) => {
|
const contentToDelete = await fs.readFile(filePath, "utf-8").catch((error) => {
|
||||||
throw new Error(`apply_patch verification failed: ${error}`)
|
throw new Error(`apply_patch verification failed: ${error}`)
|
||||||
})
|
})
|
||||||
const deleteDiff = createTwoFilesPatch(filePath, filePath, contentToDelete, "")
|
const deleteDiff = trimDiff(createTwoFilesPatch(filePath, filePath, contentToDelete, ""))
|
||||||
|
|
||||||
|
const deletions = contentToDelete.split("\n").length
|
||||||
|
|
||||||
fileChanges.push({
|
fileChanges.push({
|
||||||
filePath,
|
filePath,
|
||||||
oldContent: contentToDelete,
|
oldContent: contentToDelete,
|
||||||
newContent: "",
|
newContent: "",
|
||||||
type: "delete",
|
type: "delete",
|
||||||
|
diff: deleteDiff,
|
||||||
|
additions: 0,
|
||||||
|
deletions,
|
||||||
})
|
})
|
||||||
|
|
||||||
totalDiff += deleteDiff + "\n"
|
totalDiff += deleteDiff + "\n"
|
||||||
break
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,10 +227,22 @@ export const ApplyPatchTool = Tool.define("apply_patch", {
|
||||||
})
|
})
|
||||||
const summary = `Success. Updated the following files:\n${summaryLines.join("\n")}`
|
const summary = `Success. Updated the following files:\n${summaryLines.join("\n")}`
|
||||||
|
|
||||||
|
// Build per-file metadata for UI rendering
|
||||||
|
const files = fileChanges.map((change) => ({
|
||||||
|
filePath: change.filePath,
|
||||||
|
relativePath: path.relative(Instance.worktree, change.movePath ?? change.filePath),
|
||||||
|
type: change.type,
|
||||||
|
diff: change.diff,
|
||||||
|
additions: change.additions,
|
||||||
|
deletions: change.deletions,
|
||||||
|
movePath: change.movePath,
|
||||||
|
}))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: summary,
|
title: summary,
|
||||||
metadata: {
|
metadata: {
|
||||||
diff: totalDiff,
|
diff: totalDiff,
|
||||||
|
files,
|
||||||
},
|
},
|
||||||
output: summary,
|
output: summary,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue