diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index 02f6342605c8..80d4bfa8b8fe 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -31,7 +31,7 @@ import { promptOffsetWidth } from "../../prompt/display" import { createStore, produce, unwrap } from "solid-js/store" import { usePromptHistory, type PromptInfo } from "../../prompt/history" import { computePromptTraits } from "../../prompt/traits" -import { expandPastedTextPlaceholders, expandTrackedPastedText } from "../../prompt/part" +import { countPasteLines, expandPastedTextPlaceholders, expandTrackedPastedText, shouldSummarizePaste } from "../../prompt/part" import { usePromptStash } from "../../prompt/stash" import { DialogStash } from "../dialog-stash" import { type AutocompleteRef, Autocomplete } from "./autocomplete" @@ -1198,12 +1198,11 @@ export function Prompt(props: PromptProps) { } } - const lineCount = (pastedContent.match(/\n/g)?.length ?? 0) + 1 if ( - (lineCount >= 3 || pastedContent.length > 150) && + shouldSummarizePaste(pastedContent) && kv.get("paste_summary_enabled", !sync.data.config.experimental?.disable_paste_summary) ) { - pasteText(pastedContent, `[Pasted ~${lineCount} lines]`) + pasteText(pastedContent, `[Pasted ~${countPasteLines(pastedContent)} lines]`) return } diff --git a/packages/tui/src/prompt/part.ts b/packages/tui/src/prompt/part.ts index 0027b9aaed77..ff9ff46b16d8 100644 --- a/packages/tui/src/prompt/part.ts +++ b/packages/tui/src/prompt/part.ts @@ -1,5 +1,19 @@ import { displaySlice } from "./display" +// Summarize only genuinely large pastes into a "[Pasted ~N lines]" placeholder. +// Short pastes, including dictated speech-to-text input (typically a sentence +// or two on a single line), must stay inline and editable. +const PASTE_SUMMARY_LINE_LIMIT = 10 +const PASTE_SUMMARY_CHAR_LIMIT = 1000 + +export function countPasteLines(text: string) { + return (text.match(/\n/g)?.length ?? 0) + 1 +} + +export function shouldSummarizePaste(text: string) { + return countPasteLines(text) > PASTE_SUMMARY_LINE_LIMIT || text.length > PASTE_SUMMARY_CHAR_LIMIT +} + export function stripPromptPartIDs(part: Part) { const { id: _id, messageID: _messageID, sessionID: _sessionID, ...rest } = part return rest diff --git a/packages/tui/test/prompt/part.test.ts b/packages/tui/test/prompt/part.test.ts index 2d5605ac0c49..b4651fcf3b6d 100644 --- a/packages/tui/test/prompt/part.test.ts +++ b/packages/tui/test/prompt/part.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { expandTrackedPastedText, stripPromptPartIDs } from "../../src/prompt/part" +import { expandTrackedPastedText, shouldSummarizePaste, stripPromptPartIDs } from "../../src/prompt/part" describe("prompt part", () => { test("strips persisted IDs from reused parts", () => { @@ -36,6 +36,22 @@ describe("prompt part", () => { ).toBe("你好你好\npublic:\n\tvoid ExecuteTask();\nprivate:\n阿斯顿法国红酒看来") }) + test("keeps short pastes inline", () => { + expect(shouldSummarizePaste("a short sentence")).toBe(false) + expect(shouldSummarizePaste("one\ntwo\nthree\nfour lines")).toBe(false) + // dictated speech-to-text sized input: a paragraph on a single line + expect(shouldSummarizePaste("word ".repeat(150).trim())).toBe(false) + // boundary: exactly at the limits stays inline + expect(shouldSummarizePaste(Array.from({ length: 10 }, () => "line").join("\n"))).toBe(false) + expect(shouldSummarizePaste("x".repeat(1000))).toBe(false) + }) + + test("summarizes genuinely large pastes", () => { + expect(shouldSummarizePaste("x".repeat(1001))).toBe(true) + expect(shouldSummarizePaste(Array.from({ length: 11 }, () => "line").join("\n"))).toBe(true) + expect(shouldSummarizePaste("log line\n".repeat(200))).toBe(true) + }) + test("only expands the tracked placeholder occurrence", () => { const marker = "[Pasted ~3 lines]" const prefix = `keep ${marker} then `