feat(frontend): pasting images to chat in web
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
onsubmit: (message: string) => void;
|
||||
onpasteimage?: (base64: string, mediaType: string) => void;
|
||||
disabled?: boolean;
|
||||
allowEmpty?: boolean;
|
||||
}
|
||||
|
||||
let { onsubmit, disabled = false, allowEmpty = false }: Props = $props();
|
||||
let { onsubmit, onpasteimage, disabled = false, allowEmpty = false }: Props = $props();
|
||||
let value = $state('');
|
||||
|
||||
function handleSubmit(e: Event) {
|
||||
@@ -16,6 +17,46 @@
|
||||
value = '';
|
||||
}
|
||||
}
|
||||
|
||||
async function fileToCompressedBase64(
|
||||
file: File
|
||||
): Promise<{ base64: string; mediaType: string }> {
|
||||
const bitmap = await createImageBitmap(file);
|
||||
const maxSize = 1920;
|
||||
const scale = Math.min(maxSize / bitmap.width, maxSize / bitmap.height, 1);
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = Math.round(bitmap.width * scale);
|
||||
canvas.height = Math.round(bitmap.height * scale);
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) throw new Error('canvas 2d context unavailable');
|
||||
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
|
||||
bitmap.close();
|
||||
const base64 = canvas.toDataURL('image/jpeg', 0.65).split(',')[1];
|
||||
return { base64, mediaType: 'image/jpeg' };
|
||||
}
|
||||
|
||||
async function handlePaste(e: ClipboardEvent) {
|
||||
if (!onpasteimage || disabled) return;
|
||||
const items = e.clipboardData?.items;
|
||||
if (!items) return;
|
||||
const imageFiles: File[] = [];
|
||||
for (const item of items) {
|
||||
if (item.kind === 'file' && item.type.startsWith('image/')) {
|
||||
const f = item.getAsFile();
|
||||
if (f) imageFiles.push(f);
|
||||
}
|
||||
}
|
||||
if (imageFiles.length === 0) return;
|
||||
e.preventDefault();
|
||||
for (const file of imageFiles) {
|
||||
try {
|
||||
const { base64, mediaType } = await fileToCompressedBase64(file);
|
||||
onpasteimage(base64, mediaType);
|
||||
} catch {
|
||||
// ignore unreadable image
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form onsubmit={handleSubmit} class="flex gap-1">
|
||||
@@ -23,6 +64,7 @@
|
||||
type="text"
|
||||
bind:value
|
||||
{disabled}
|
||||
onpaste={handlePaste}
|
||||
placeholder="..."
|
||||
class="min-w-0 flex-1 rounded bg-neutral-800 px-2 py-1 text-[10px] text-white placeholder-neutral-500 outline-none"
|
||||
/>
|
||||
|
||||
@@ -685,7 +685,11 @@
|
||||
ondismiss={handleDismissSheet}
|
||||
/>
|
||||
{/if}
|
||||
<ChatInput onsubmit={sendMessage} allowEmpty={draftPhotos.length > 0} />
|
||||
<ChatInput
|
||||
onsubmit={sendMessage}
|
||||
onpasteimage={handleCameraCapture}
|
||||
allowEmpty={draftPhotos.length > 0}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user