/** * JARVIS — Image Panel * Drag-and-drop image upload, visual analysis with BLIP + CLIP, then chat. */ import React, { useState, useCallback } from 'react' import { useDropzone } from 'react-dropzone' import { motion, AnimatePresence } from 'framer-motion' import { Upload, Image, X, Loader2, Eye, Tag, ChevronDown, ChevronUp, Sparkles, } from 'lucide-react' import clsx from 'clsx' import toast from 'react-hot-toast' import useAppStore from '../../stores/appStore' import { imageAPI } from '../../services/api' export default function ImagePanel() { const { activeSessionId, addImageResult, setPendingImage, setInputMode } = useAppStore() const [file, setFile] = useState(null) const [preview, setPreview] = useState(null) const [question, setQuestion] = useState('') const [result, setResult] = useState(null) const [loading, setLoading] = useState(false) const [expanded, setExpanded] = useState(true) // ── Dropzone ─────────────────────────────────── const onDrop = useCallback((acceptedFiles) => { const f = acceptedFiles[0] if (!f) return setFile(f) setResult(null) const url = URL.createObjectURL(f) setPreview(url) // Auto-set pending image for chat context setPendingImage({ file: f, preview: url, caption: null }) }, [setPendingImage]) const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: { 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp'] }, maxFiles: 1, maxSize: 50 * 1024 * 1024, }) const clearImage = () => { setFile(null) setPreview(null) setResult(null) setQuestion('') setPendingImage(null) } // ── Analyze ──────────────────────────────────── const analyze = async () => { if (!file) return setLoading(true) try { const data = await imageAPI.analyzeImage(file, question || null, activeSessionId) setResult(data) // Update pending image caption for chat context setPendingImage({ file, preview, caption: data.caption }) // Add to message feed addImageResult( data.session_id, question || 'Analyze this image', data, ) toast.success('Image analyzed!') } catch (err) { toast.error(err.message || 'Analysis failed') } finally { setLoading(false) } } // ── Use in chat ──────────────────────────────── const useInChat = () => { setInputMode('text') toast('Image context attached to chat', { icon: '📎' }) } return (
{isDragActive ? 'Drop image here…' : 'Drag & drop or click to upload'}
PNG, JPG, WEBP · max 50MB
Caption
{result.caption}
CLIP Categories