Sfoglia il codice sorgente

fix(tui): dedupe consecutive prompt history entries (#27816)

Kit Langton 3 mesi fa
parent
commit
f3b0d3d7ac

+ 9 - 0
packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx

@@ -27,6 +27,11 @@ export type PromptInfo = {
 
 const MAX_HISTORY_ENTRIES = 50
 
+export function isDuplicateEntry(previous: PromptInfo | undefined, next: PromptInfo): boolean {
+  if (!previous) return false
+  return JSON.stringify(previous) === JSON.stringify(next)
+}
+
 export const { use: usePromptHistory, provider: PromptHistoryProvider } = createSimpleContext({
   name: "PromptHistory",
   init: () => {
@@ -83,6 +88,10 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create
       },
       append(item: PromptInfo) {
         const entry = structuredClone(unwrap(item))
+        if (isDuplicateEntry(store.history.at(-1), entry)) {
+          setStore("index", 0)
+          return
+        }
         let trimmed = false
         setStore(
           produce((draft) => {

+ 44 - 0
packages/opencode/test/cli/cmd/tui/prompt-history.test.ts

@@ -0,0 +1,44 @@
+import { describe, expect, test } from "bun:test"
+import { isDuplicateEntry, type PromptInfo } from "../../../../src/cli/cmd/tui/component/prompt/history"
+
+const entry = (input: string, parts: PromptInfo["parts"] = []): PromptInfo => ({ input, parts })
+
+describe("prompt history dedupe", () => {
+  test("returns false when there is no previous entry", () => {
+    expect(isDuplicateEntry(undefined, entry("hello"))).toBe(false)
+  })
+
+  test("dedupes identical consecutive entries", () => {
+    const a = entry("hello world this is over twenty chars")
+    const b = entry("hello world this is over twenty chars")
+    expect(isDuplicateEntry(a, b)).toBe(true)
+  })
+
+  test("does not dedupe when input text differs", () => {
+    expect(isDuplicateEntry(entry("foo"), entry("bar"))).toBe(false)
+  })
+
+  test("does not dedupe when parts differ", () => {
+    const a = entry("describe this", [
+      {
+        type: "file",
+        mime: "image/png",
+        filename: "a.png",
+        url: "data:image/png;base64,AAA",
+      },
+    ])
+    const b = entry("describe this", [
+      {
+        type: "file",
+        mime: "image/png",
+        filename: "b.png",
+        url: "data:image/png;base64,BBB",
+      },
+    ])
+    expect(isDuplicateEntry(a, b)).toBe(false)
+  })
+
+  test("does not dedupe when mode differs", () => {
+    expect(isDuplicateEntry({ ...entry("ls"), mode: "normal" }, { ...entry("ls"), mode: "shell" })).toBe(false)
+  })
+})