diff --git a/public/index.html b/public/index.html
index 68609d3..11959f1 100644
--- a/public/index.html
+++ b/public/index.html
@@ -355,29 +355,47 @@
} catch(e) {} finally { inFlight = false; }
}
- // Auto-send on paste (text or image)
- inputEl.addEventListener('paste', (e) => {
- const items = (e.clipboardData || window.clipboardData)?.items || [];
- let imgItem = null;
- for (const it of items) {
- if (it.kind === 'file' && it.type.startsWith('image/')) { imgItem = it; break; }
- }
- if (imgItem) {
- e.preventDefault();
- const file = imgItem.getAsFile();
+ // Auto-send on paste — all images + all text in one clipboard event go as ONE request
+ function readFileB64(file) {
+ return new Promise(resolve => {
const fr = new FileReader();
- fr.onload = () => {
- const dataUrl = fr.result;
- const base64 = dataUrl.split(',')[1];
- send({ image: { data: base64, media_type: file.type || 'image/png' } });
- };
+ fr.onload = () => resolve({
+ data: fr.result.split(',')[1],
+ media_type: file.type || 'image/png',
+ });
fr.readAsDataURL(file);
- } else {
+ });
+ }
+ function getAsStringP(item) {
+ return new Promise(r => item.getAsString(r));
+ }
+
+ inputEl.addEventListener('paste', async (e) => {
+ const items = Array.from((e.clipboardData || window.clipboardData)?.items || []);
+ const imageFiles = [];
+ let pastedTextP = Promise.resolve('');
+ for (const it of items) {
+ if (it.kind === 'file' && it.type.startsWith('image/')) {
+ imageFiles.push(it.getAsFile());
+ } else if (it.kind === 'string' && it.type === 'text/plain') {
+ pastedTextP = getAsStringP(it);
+ }
+ }
+
+ if (imageFiles.length === 0) {
setTimeout(() => {
const t = inputEl.value.trim();
if (t) send({ text: t });
}, 50);
+ return;
}
+
+ e.preventDefault();
+ const images = await Promise.all(imageFiles.map(readFileB64));
+ const pastedText = (await pastedTextP).trim();
+ const existing = inputEl.value.trim();
+ const fullText = [existing, pastedText].filter(Boolean).join('\n');
+ send({ text: fullText, images });
});
diff --git a/server.js b/server.js
index 323dd26..6f166ee 100644
--- a/server.js
+++ b/server.js
@@ -73,17 +73,18 @@ async function callClaude(modelId, content, accessToken) {
});
}
-app.post('/api/chat', express.json({ limit: '25mb' }), async (req, res) => {
- const { text, model = 'sonnet', image } = req.body;
+app.post('/api/chat', express.json({ limit: '50mb' }), async (req, res) => {
+ const { text, model = 'sonnet', image, images } = req.body;
const hasText = text?.trim();
- if (!hasText && !image) return res.status(400).json({ error: 'text or image required' });
+ const imgList = Array.isArray(images) ? images : (image ? [image] : []);
+ if (!hasText && imgList.length === 0) return res.status(400).json({ error: 'text or image required' });
let content;
- if (image?.data && image?.media_type) {
- content = [
- { type: 'image', source: { type: 'base64', media_type: image.media_type, data: image.data } },
- { type: 'text', text: hasText || 'Analyze the image and answer any questions shown in it.' },
- ];
+ if (imgList.length > 0) {
+ content = imgList
+ .filter(img => img?.data && img?.media_type)
+ .map(img => ({ type: 'image', source: { type: 'base64', media_type: img.media_type, data: img.data } }));
+ content.push({ type: 'text', text: hasText || 'Analyze all images and answer any questions shown in them.' });
} else {
content = text;
}