import { useCallback, useRef, useState } from 'react'; import { useAgent } from './client/useAgent'; import { ChatPanel } from './components/Chat'; import { ModelDialog } from './components/ModelDialog'; import { TopBar } from './components/TopBar'; import { WorkspacePanel } from './components/Workspace'; const embedded = typeof window !== 'undefined' && window.self !== window.top; const noWebGpu = typeof navigator !== 'undefined' && !('gpu' in navigator); export default function App() { const { state, pending, shell, actions } = useAgent(); const [draft, setDraft] = useState(''); const [split, setSplit] = useState(55); const mainRef = useRef(null); const send = useCallback(() => { void actions.send(draft, () => setDraft('')); }, [actions, draft]); const loadClick = () => { actions.loadModel(false).catch((e: Error) => { if (e.name !== 'AbortError') actions.setError(e.message); }); }; const startDrag = (e: React.PointerEvent) => { const el = mainRef.current; if (!el) return; (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); const move = (ev: PointerEvent) => { const r = el.getBoundingClientRect(); setSplit(Math.min(80, Math.max(25, ((ev.clientX - r.left) / r.width) * 100))); }; const up = () => { window.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up); }; window.addEventListener('pointermove', move); window.addEventListener('pointerup', up); }; const message = state.error || state.notice; return (
{(embedded || noWebGpu) && (
{noWebGpu ? 'WebGPU is not available in this browser or frame. Use a recent Chrome or Edge.' : 'This app is running inside a frame. If model loading fails because WebGPU or browser storage is blocked, open it in its own tab.'} Open in a new tab ↗
)} {message && (
{message}
)}
setSplit(55)} onKeyDown={(e) => { if (e.key === 'ArrowLeft') setSplit((s) => Math.max(25, s - 3)); if (e.key === 'ArrowRight') setSplit((s) => Math.min(80, s + 3)); }} />
); }