@@ -4,12 +4,27 @@ import { usePrompt, type ContentPart, type ImageAttachmentPart } from "@/context
44import { useLanguage } from "@/context/language"
55import { uuid } from "@/utils/uuid"
66import { getCursorPosition } from "./editor-dom"
7-
8- export const ACCEPTED_IMAGE_TYPES = [ "image/png" , "image/jpeg" , "image/gif" , "image/webp" ]
9- export const ACCEPTED_FILE_TYPES = [ ...ACCEPTED_IMAGE_TYPES , "application/pdf" ]
7+ import { attachmentMime } from "./files"
108const LARGE_PASTE_CHARS = 8000
119const LARGE_PASTE_BREAKS = 120
1210
11+ function dataUrl ( file : File , mime : string ) {
12+ return new Promise < string > ( ( resolve ) => {
13+ const reader = new FileReader ( )
14+ reader . addEventListener ( "error" , ( ) => resolve ( "" ) )
15+ reader . addEventListener ( "load" , ( ) => {
16+ const value = typeof reader . result === "string" ? reader . result : ""
17+ const idx = value . indexOf ( "," )
18+ if ( idx === - 1 ) {
19+ resolve ( value )
20+ return
21+ }
22+ resolve ( `data:${ mime } ;base64,${ value . slice ( idx + 1 ) } ` )
23+ } )
24+ reader . readAsDataURL ( file )
25+ } )
26+ }
27+
1328function largePaste ( text : string ) {
1429 if ( text . length >= LARGE_PASTE_CHARS ) return true
1530 let breaks = 0
@@ -35,28 +50,41 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
3550 const prompt = usePrompt ( )
3651 const language = useLanguage ( )
3752
38- const addImageAttachment = async ( file : File ) => {
39- if ( ! ACCEPTED_FILE_TYPES . includes ( file . type ) ) return
53+ const warn = ( ) => {
54+ showToast ( {
55+ title : language . t ( "prompt.toast.pasteUnsupported.title" ) ,
56+ description : language . t ( "prompt.toast.pasteUnsupported.description" ) ,
57+ } )
58+ }
4059
41- const reader = new FileReader ( )
42- reader . onload = ( ) => {
43- const editor = input . editor ( )
44- if ( ! editor ) return
45- const dataUrl = reader . result as string
46- const attachment : ImageAttachmentPart = {
47- type : "image" ,
48- id : uuid ( ) ,
49- filename : file . name ,
50- mime : file . type ,
51- dataUrl,
52- }
53- const cursorPosition = prompt . cursor ( ) ?? getCursorPosition ( editor )
54- prompt . set ( [ ...prompt . current ( ) , attachment ] , cursorPosition )
60+ const add = async ( file : File , toast = true ) => {
61+ const mime = await attachmentMime ( file )
62+ if ( ! mime ) {
63+ if ( toast ) warn ( )
64+ return false
5565 }
56- reader . readAsDataURL ( file )
66+
67+ const editor = input . editor ( )
68+ if ( ! editor ) return false
69+
70+ const url = await dataUrl ( file , mime )
71+ if ( ! url ) return false
72+
73+ const attachment : ImageAttachmentPart = {
74+ type : "image" ,
75+ id : uuid ( ) ,
76+ filename : file . name ,
77+ mime,
78+ dataUrl : url ,
79+ }
80+ const cursor = prompt . cursor ( ) ?? getCursorPosition ( editor )
81+ prompt . set ( [ ...prompt . current ( ) , attachment ] , cursor )
82+ return true
5783 }
5884
59- const removeImageAttachment = ( id : string ) => {
85+ const addAttachment = ( file : File ) => add ( file )
86+
87+ const removeAttachment = ( id : string ) => {
6088 const current = prompt . current ( )
6189 const next = current . filter ( ( part ) => part . type !== "image" || part . id !== id )
6290 prompt . set ( next , prompt . cursor ( ) )
@@ -72,21 +100,16 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
72100
73101 const items = Array . from ( clipboardData . items )
74102 const fileItems = items . filter ( ( item ) => item . kind === "file" )
75- const imageItems = fileItems . filter ( ( item ) => ACCEPTED_FILE_TYPES . includes ( item . type ) )
76103
77- if ( imageItems . length > 0 ) {
78- for ( const item of imageItems ) {
104+ if ( fileItems . length > 0 ) {
105+ let found = false
106+ for ( const item of fileItems ) {
79107 const file = item . getAsFile ( )
80- if ( file ) await addImageAttachment ( file )
108+ if ( ! file ) continue
109+ const ok = await add ( file , false )
110+ if ( ok ) found = true
81111 }
82- return
83- }
84-
85- if ( fileItems . length > 0 ) {
86- showToast ( {
87- title : language . t ( "prompt.toast.pasteUnsupported.title" ) ,
88- description : language . t ( "prompt.toast.pasteUnsupported.description" ) ,
89- } )
112+ if ( ! found ) warn ( )
90113 return
91114 }
92115
@@ -96,7 +119,7 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
96119 if ( input . readClipboardImage && ! plainText ) {
97120 const file = await input . readClipboardImage ( )
98121 if ( file ) {
99- await addImageAttachment ( file )
122+ await addAttachment ( file )
100123 return
101124 }
102125 }
@@ -153,11 +176,12 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
153176 const dropped = event . dataTransfer ?. files
154177 if ( ! dropped ) return
155178
179+ let found = false
156180 for ( const file of Array . from ( dropped ) ) {
157- if ( ACCEPTED_FILE_TYPES . includes ( file . type ) ) {
158- await addImageAttachment ( file )
159- }
181+ const ok = await add ( file , false )
182+ if ( ok ) found = true
160183 }
184+ if ( ! found && dropped . length > 0 ) warn ( )
161185 }
162186
163187 onMount ( ( ) => {
@@ -173,8 +197,8 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
173197 } )
174198
175199 return {
176- addImageAttachment ,
177- removeImageAttachment ,
200+ addAttachment ,
201+ removeAttachment ,
178202 handlePaste,
179203 }
180204}
0 commit comments