|
|
@@ -4,12 +4,27 @@ import { usePrompt, type ContentPart, type ImageAttachmentPart } from "@/context
|
|
|
import { useLanguage } from "@/context/language"
|
|
|
import { uuid } from "@/utils/uuid"
|
|
|
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"]
|
|
|
+import { attachmentMime } from "./files"
|
|
|
const LARGE_PASTE_CHARS = 8000
|
|
|
const LARGE_PASTE_BREAKS = 120
|
|
|
|
|
|
+function dataUrl(file: File, mime: string) {
|
|
|
+ return new Promise<string>((resolve) => {
|
|
|
+ const reader = new FileReader()
|
|
|
+ reader.addEventListener("error", () => resolve(""))
|
|
|
+ reader.addEventListener("load", () => {
|
|
|
+ const value = typeof reader.result === "string" ? reader.result : ""
|
|
|
+ const idx = value.indexOf(",")
|
|
|
+ if (idx === -1) {
|
|
|
+ resolve(value)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ resolve(`data:${mime};base64,${value.slice(idx + 1)}`)
|
|
|
+ })
|
|
|
+ reader.readAsDataURL(file)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
function largePaste(text: string) {
|
|
|
if (text.length >= LARGE_PASTE_CHARS) return true
|
|
|
let breaks = 0
|
|
|
@@ -35,28 +50,41 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
|
|
|
const prompt = usePrompt()
|
|
|
const language = useLanguage()
|
|
|
|
|
|
- const addImageAttachment = async (file: File) => {
|
|
|
- if (!ACCEPTED_FILE_TYPES.includes(file.type)) return
|
|
|
+ const warn = () => {
|
|
|
+ showToast({
|
|
|
+ title: language.t("prompt.toast.pasteUnsupported.title"),
|
|
|
+ description: language.t("prompt.toast.pasteUnsupported.description"),
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
- const reader = new FileReader()
|
|
|
- reader.onload = () => {
|
|
|
- const editor = input.editor()
|
|
|
- if (!editor) return
|
|
|
- const dataUrl = reader.result as string
|
|
|
- const attachment: ImageAttachmentPart = {
|
|
|
- type: "image",
|
|
|
- id: uuid(),
|
|
|
- filename: file.name,
|
|
|
- mime: file.type,
|
|
|
- dataUrl,
|
|
|
- }
|
|
|
- const cursorPosition = prompt.cursor() ?? getCursorPosition(editor)
|
|
|
- prompt.set([...prompt.current(), attachment], cursorPosition)
|
|
|
+ const add = async (file: File, toast = true) => {
|
|
|
+ const mime = await attachmentMime(file)
|
|
|
+ if (!mime) {
|
|
|
+ if (toast) warn()
|
|
|
+ return false
|
|
|
}
|
|
|
- reader.readAsDataURL(file)
|
|
|
+
|
|
|
+ const editor = input.editor()
|
|
|
+ if (!editor) return false
|
|
|
+
|
|
|
+ const url = await dataUrl(file, mime)
|
|
|
+ if (!url) return false
|
|
|
+
|
|
|
+ const attachment: ImageAttachmentPart = {
|
|
|
+ type: "image",
|
|
|
+ id: uuid(),
|
|
|
+ filename: file.name,
|
|
|
+ mime,
|
|
|
+ dataUrl: url,
|
|
|
+ }
|
|
|
+ const cursor = prompt.cursor() ?? getCursorPosition(editor)
|
|
|
+ prompt.set([...prompt.current(), attachment], cursor)
|
|
|
+ return true
|
|
|
}
|
|
|
|
|
|
- const removeImageAttachment = (id: string) => {
|
|
|
+ const addAttachment = (file: File) => add(file)
|
|
|
+
|
|
|
+ const removeAttachment = (id: string) => {
|
|
|
const current = prompt.current()
|
|
|
const next = current.filter((part) => part.type !== "image" || part.id !== id)
|
|
|
prompt.set(next, prompt.cursor())
|
|
|
@@ -72,21 +100,16 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
|
|
|
|
|
|
const items = Array.from(clipboardData.items)
|
|
|
const fileItems = items.filter((item) => item.kind === "file")
|
|
|
- const imageItems = fileItems.filter((item) => ACCEPTED_FILE_TYPES.includes(item.type))
|
|
|
|
|
|
- if (imageItems.length > 0) {
|
|
|
- for (const item of imageItems) {
|
|
|
+ if (fileItems.length > 0) {
|
|
|
+ let found = false
|
|
|
+ for (const item of fileItems) {
|
|
|
const file = item.getAsFile()
|
|
|
- if (file) await addImageAttachment(file)
|
|
|
+ if (!file) continue
|
|
|
+ const ok = await add(file, false)
|
|
|
+ if (ok) found = true
|
|
|
}
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- if (fileItems.length > 0) {
|
|
|
- showToast({
|
|
|
- title: language.t("prompt.toast.pasteUnsupported.title"),
|
|
|
- description: language.t("prompt.toast.pasteUnsupported.description"),
|
|
|
- })
|
|
|
+ if (!found) warn()
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -96,7 +119,7 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
|
|
|
if (input.readClipboardImage && !plainText) {
|
|
|
const file = await input.readClipboardImage()
|
|
|
if (file) {
|
|
|
- await addImageAttachment(file)
|
|
|
+ await addAttachment(file)
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
@@ -153,11 +176,12 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
|
|
|
const dropped = event.dataTransfer?.files
|
|
|
if (!dropped) return
|
|
|
|
|
|
+ let found = false
|
|
|
for (const file of Array.from(dropped)) {
|
|
|
- if (ACCEPTED_FILE_TYPES.includes(file.type)) {
|
|
|
- await addImageAttachment(file)
|
|
|
- }
|
|
|
+ const ok = await add(file, false)
|
|
|
+ if (ok) found = true
|
|
|
}
|
|
|
+ if (!found && dropped.length > 0) warn()
|
|
|
}
|
|
|
|
|
|
onMount(() => {
|
|
|
@@ -173,8 +197,8 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
|
|
|
})
|
|
|
|
|
|
return {
|
|
|
- addImageAttachment,
|
|
|
- removeImageAttachment,
|
|
|
+ addAttachment,
|
|
|
+ removeAttachment,
|
|
|
handlePaste,
|
|
|
}
|
|
|
}
|