Pārlūkot izejas kodu

add apply patch to desktop app

Aiden Cline 3 mēneši atpakaļ
vecāks
revīzija
cfd6a7ae96

+ 72 - 0
packages/ui/src/components/message-part.css

@@ -689,3 +689,75 @@
     }
   }
 }
+
+[data-component="apply-patch-files"] {
+  display: flex;
+  flex-direction: column;
+}
+
+[data-component="apply-patch-file"] {
+  display: flex;
+  flex-direction: column;
+  border-top: 1px solid var(--border-weaker-base);
+
+  &:first-child {
+    border-top: 1px solid var(--border-weaker-base);
+  }
+
+  [data-slot="apply-patch-file-header"] {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 8px 12px;
+    background-color: var(--surface-inset-base);
+  }
+
+  [data-slot="apply-patch-file-action"] {
+    font-family: var(--font-family-sans);
+    font-size: var(--font-size-small);
+    font-weight: var(--font-weight-medium);
+    line-height: var(--line-height-large);
+    color: var(--text-base);
+    flex-shrink: 0;
+
+    &[data-type="delete"] {
+      color: var(--text-critical-base);
+    }
+
+    &[data-type="add"] {
+      color: var(--text-success-base);
+    }
+
+    &[data-type="move"] {
+      color: var(--text-warning-base);
+    }
+  }
+
+  [data-slot="apply-patch-file-path"] {
+    font-family: var(--font-family-mono);
+    font-size: var(--font-size-small);
+    color: var(--text-weak);
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    flex-grow: 1;
+  }
+
+  [data-slot="apply-patch-deletion-count"] {
+    font-family: var(--font-family-mono);
+    font-size: var(--font-size-small);
+    color: var(--text-critical-base);
+    flex-shrink: 0;
+  }
+}
+
+[data-component="apply-patch-file-diff"] {
+  max-height: 420px;
+  overflow-y: auto;
+  scrollbar-width: none;
+  -ms-overflow-style: none;
+
+  &::-webkit-scrollbar {
+    display: none;
+  }
+}

+ 94 - 0
packages/ui/src/components/message-part.tsx

@@ -233,6 +233,12 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
         title: "Write",
         subtitle: input.filePath ? getFilename(input.filePath) : undefined,
       }
+    case "apply_patch":
+      return {
+        icon: "code-lines",
+        title: "Patch",
+        subtitle: input.files?.length ? `${input.files.length} file${input.files.length > 1 ? "s" : ""}` : undefined,
+      }
     case "todowrite":
       return {
         icon: "checklist",
@@ -1027,6 +1033,94 @@ 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 diffComponent = useDiffComponent()
+    const files = createMemo(() => (props.metadata.files ?? []) as ApplyPatchFile[])
+
+    const subtitle = createMemo(() => {
+      const count = files().length
+      if (count === 0) return ""
+      return `${count} file${count > 1 ? "s" : ""}`
+    })
+
+    return (
+      <BasicTool
+        {...props}
+        icon="code-lines"
+        trigger={{
+          title: "Patch",
+          subtitle: subtitle(),
+        }}
+      >
+        <Show when={files().length > 0}>
+          <div data-component="apply-patch-files">
+            <For each={files()}>
+              {(file) => (
+                <div data-component="apply-patch-file">
+                  <div data-slot="apply-patch-file-header">
+                    <Switch>
+                      <Match when={file.type === "delete"}>
+                        <span data-slot="apply-patch-file-action" data-type="delete">
+                          Deleted
+                        </span>
+                      </Match>
+                      <Match when={file.type === "add"}>
+                        <span data-slot="apply-patch-file-action" data-type="add">
+                          Created
+                        </span>
+                      </Match>
+                      <Match when={file.type === "move"}>
+                        <span data-slot="apply-patch-file-action" data-type="move">
+                          Moved
+                        </span>
+                      </Match>
+                      <Match when={file.type === "update"}>
+                        <span data-slot="apply-patch-file-action" data-type="update">
+                          Edit
+                        </span>
+                      </Match>
+                    </Switch>
+                    <span data-slot="apply-patch-file-path">{file.relativePath}</span>
+                    <Show when={file.type !== "delete"}>
+                      <DiffChanges changes={{ additions: file.additions, deletions: file.deletions }} />
+                    </Show>
+                    <Show when={file.type === "delete"}>
+                      <span data-slot="apply-patch-deletion-count">-{file.deletions}</span>
+                    </Show>
+                  </div>
+                  <Show when={file.type !== "delete"}>
+                    <div data-component="apply-patch-file-diff">
+                      <Dynamic
+                        component={diffComponent}
+                        before={{ name: file.filePath, contents: file.before }}
+                        after={{ name: file.filePath, contents: file.after }}
+                      />
+                    </div>
+                  </Show>
+                </div>
+              )}
+            </For>
+          </div>
+        </Show>
+      </BasicTool>
+    )
+  },
+})
+
 ToolRegistry.register({
   name: "todowrite",
   render(props) {