From 8b38e040391583f5eb6c8d908840e6faee5b3d97 Mon Sep 17 00:00:00 2001 From: h Date: Wed, 21 Jan 2026 10:25:16 +0100 Subject: [PATCH] feat(frontend): POST for images --- frontend/src/routes/[mnemonic]/+server.ts | 60 ++++++++++++++++++++--- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/frontend/src/routes/[mnemonic]/+server.ts b/frontend/src/routes/[mnemonic]/+server.ts index e937f7e..877305a 100644 --- a/frontend/src/routes/[mnemonic]/+server.ts +++ b/frontend/src/routes/[mnemonic]/+server.ts @@ -2,6 +2,22 @@ import { api } from '$lib/convex/_generated/api'; import { error } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; +function detectImageType(bytes: Uint8Array): string | null { + if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) { + return 'image/jpeg'; + } + if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) { + return 'image/png'; + } + if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) { + return 'image/gif'; + } + if (bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46) { + return 'image/webp'; + } + return null; +} + export const POST: RequestHandler = async ({ params, request, locals }) => { const mnemonic = params.mnemonic; @@ -10,22 +26,54 @@ export const POST: RequestHandler = async ({ params, request, locals }) => { throw error(404, 'Chat not found'); } - const contentType = request.headers.get('content-type') || 'image/jpeg'; - const buffer = await request.arrayBuffer(); - const base64 = Buffer.from(buffer).toString('base64'); - + const rawContentType = request.headers.get('content-type') || ''; const caption = request.headers.get('x-caption') || ''; + console.log('[POST /{mnemonic}] headers:', Object.fromEntries(request.headers.entries())); + console.log('[POST /{mnemonic}] content-type:', rawContentType); + + let base64: string; + let mediaType: string; + + if (rawContentType.includes('multipart/form-data')) { + const formData = await request.formData(); + console.log('[POST /{mnemonic}] formData keys:', [...formData.keys()]); + + const file = formData.get('file') || formData.get('image') || formData.values().next().value; + if (!(file instanceof File)) { + throw error(400, 'No file found in form data'); + } + + const buffer = await file.arrayBuffer(); + const bytes = new Uint8Array(buffer); + base64 = Buffer.from(buffer).toString('base64'); + mediaType = detectImageType(bytes) || file.type || 'image/jpeg'; + + console.log('[POST /{mnemonic}] file:', file.name, file.type, 'size:', buffer.byteLength); + } else { + const buffer = await request.arrayBuffer(); + const bytes = new Uint8Array(buffer); + base64 = Buffer.from(buffer).toString('base64'); + mediaType = detectImageType(bytes) || rawContentType || 'image/jpeg'; + + console.log('[POST /{mnemonic}] raw bytes size:', buffer.byteLength); + console.log('[POST /{mnemonic}] detected type:', mediaType); + } + + if (!base64 || base64.length === 0) { + throw error(400, 'Empty image data'); + } + await locals.convex.mutation(api.messages.create, { chatId: chatData._id, role: 'user', content: caption, source: 'web', imageBase64: base64, - imageMediaType: contentType + imageMediaType: mediaType }); - return new Response(JSON.stringify({ ok: true }), { + return new Response(JSON.stringify({ ok: true, mediaType, size: base64.length }), { headers: { 'Content-Type': 'application/json' } }); };