summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/components/prompt-input
diff options
context:
space:
mode:
Diffstat (limited to 'packages/app/src/components/prompt-input')
-rw-r--r--packages/app/src/components/prompt-input/attachments.ts22
-rw-r--r--packages/app/src/components/prompt-input/editor-dom.test.ts22
-rw-r--r--packages/app/src/components/prompt-input/editor-dom.ts15
3 files changed, 58 insertions, 1 deletions
diff --git a/packages/app/src/components/prompt-input/attachments.ts b/packages/app/src/components/prompt-input/attachments.ts
index 9ea2e62a6..a9e4e4965 100644
--- a/packages/app/src/components/prompt-input/attachments.ts
+++ b/packages/app/src/components/prompt-input/attachments.ts
@@ -7,6 +7,19 @@ import { getCursorPosition } from "./editor-dom"
export const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"]
export const ACCEPTED_FILE_TYPES = [...ACCEPTED_IMAGE_TYPES, "application/pdf"]
+const LARGE_PASTE_CHARS = 8000
+const LARGE_PASTE_BREAKS = 120
+
+function largePaste(text: string) {
+ if (text.length >= LARGE_PASTE_CHARS) return true
+ let breaks = 0
+ for (const char of text) {
+ if (char !== "\n") continue
+ breaks += 1
+ if (breaks >= LARGE_PASTE_BREAKS) return true
+ }
+ return false
+}
type PromptAttachmentsInput = {
editor: () => HTMLDivElement | undefined
@@ -14,7 +27,7 @@ type PromptAttachmentsInput = {
isDialogActive: () => boolean
setDraggingType: (type: "image" | "@mention" | null) => void
focusEditor: () => void
- addPart: (part: ContentPart) => void
+ addPart: (part: ContentPart) => boolean
readClipboardImage?: () => Promise<File | null>
}
@@ -89,6 +102,13 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
}
if (!plainText) return
+
+ if (largePaste(plainText)) {
+ if (input.addPart({ type: "text", content: plainText, start: 0, end: 0 })) return
+ input.focusEditor()
+ if (input.addPart({ type: "text", content: plainText, start: 0, end: 0 })) return
+ }
+
const inserted = typeof document.execCommand === "function" && document.execCommand("insertText", false, plainText)
if (inserted) return
diff --git a/packages/app/src/components/prompt-input/editor-dom.test.ts b/packages/app/src/components/prompt-input/editor-dom.test.ts
index 15e759f44..3088522a5 100644
--- a/packages/app/src/components/prompt-input/editor-dom.test.ts
+++ b/packages/app/src/components/prompt-input/editor-dom.test.ts
@@ -24,6 +24,28 @@ describe("prompt-input editor dom", () => {
expect((container.childNodes[1] as HTMLElement).tagName).toBe("BR")
})
+ test("createTextFragment avoids break-node explosion for large multiline content", () => {
+ const content = Array.from({ length: 220 }, () => "line").join("\n")
+ const fragment = createTextFragment(content)
+ const container = document.createElement("div")
+ container.appendChild(fragment)
+
+ expect(container.childNodes.length).toBe(1)
+ expect(container.childNodes[0]?.nodeType).toBe(Node.TEXT_NODE)
+ expect(container.textContent).toBe(content)
+ })
+
+ test("createTextFragment keeps terminal break in large multiline fallback", () => {
+ const content = `${Array.from({ length: 220 }, () => "line").join("\n")}\n`
+ const fragment = createTextFragment(content)
+ const container = document.createElement("div")
+ container.appendChild(fragment)
+
+ expect(container.childNodes.length).toBe(2)
+ expect(container.childNodes[0]?.textContent).toBe(content.slice(0, -1))
+ expect((container.childNodes[1] as HTMLElement).tagName).toBe("BR")
+ })
+
test("length helpers treat breaks as one char and ignore zero-width chars", () => {
const container = document.createElement("div")
container.appendChild(document.createTextNode("ab\u200B"))
diff --git a/packages/app/src/components/prompt-input/editor-dom.ts b/packages/app/src/components/prompt-input/editor-dom.ts
index 4850a26ec..8575140d7 100644
--- a/packages/app/src/components/prompt-input/editor-dom.ts
+++ b/packages/app/src/components/prompt-input/editor-dom.ts
@@ -1,5 +1,20 @@
+const MAX_BREAKS = 200
+
export function createTextFragment(content: string): DocumentFragment {
const fragment = document.createDocumentFragment()
+ let breaks = 0
+ for (const char of content) {
+ if (char !== "\n") continue
+ breaks += 1
+ if (breaks > MAX_BREAKS) {
+ const tail = content.endsWith("\n")
+ const text = tail ? content.slice(0, -1) : content
+ if (text) fragment.appendChild(document.createTextNode(text))
+ if (tail) fragment.appendChild(document.createElement("br"))
+ return fragment
+ }
+ }
+
const segments = content.split("\n")
segments.forEach((segment, index) => {
if (segment) {