import { useState, useCallback, useReducer } from 'react'; import { ImageDrop } from './components/ImageDrop.jsx'; import { JobCard } from './components/JobCard.jsx'; import { useJobSocket } from './hooks/useJobSocket.js'; import { submitJob } from './lib/api.js'; import { MODELS, DEFAULT_MODEL_ID } from './models.js'; // Group models for the dropdown optgroup const GATEWAY_MODELS = MODELS.filter(m => m.provider !== 'ollama'); const OLLAMA_MODELS = MODELS.filter(m => m.provider === 'ollama'); // --------------------------------------------------------------------------- // Jobs state reducer // --------------------------------------------------------------------------- function jobsReducer(state, action) { switch (action.type) { case 'init': return action.jobs; case 'upsert': { const idx = state.findIndex(j => j.id === action.job.id); if (idx === -1) return [action.job, ...state]; const next = [...state]; next[idx] = action.job; return next; } default: return state; } } // --------------------------------------------------------------------------- // App // --------------------------------------------------------------------------- export default function App() { const [jobs, dispatch] = useReducer(jobsReducer, []); const [prompt, setPrompt] = useState(''); const [imageFile, setImageFile] = useState(null); const [modelId, setModelId] = useState(DEFAULT_MODEL_ID); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(''); const [pending, setPending] = useState(0); useJobSocket(useCallback(msg => { if (msg.type === 'init') dispatch({ type: 'init', jobs: msg.jobs }); if (msg.type === 'job_update') dispatch({ type: 'upsert', job: msg.job }); if (msg.type === 'queue_stats') setPending(msg.pending); }, [])); async function handleSubmit(e) { e.preventDefault(); if (!prompt.trim()) return setError('Please enter a prompt.'); if (!imageFile) return setError('Please select or drop an image.'); setError(''); setSubmitting(true); try { await submitJob(prompt, imageFile, modelId); setPrompt(''); setImageFile(null); } catch (err) { setError(err.message); } finally { setSubmitting(false); } } return (

Vision Jobs

{/* Model selector */}