From c175b82a6fcb7471fa1b341c9edd80c094a8a0a5 Mon Sep 17 00:00:00 2001 From: Robin Schulz Date: Sun, 14 Jun 2026 19:26:49 +0200 Subject: [PATCH 1/2] Handle the AgentForge offline state better by disabling the user prompt controls and showing a message about it. Reenable the controls and reload the chat history (left sidebar) on reconnect --- src/components/ChatInput.jsx | 98 +++++++++++++++++++++++++----------- src/components/ChatPage.jsx | 1 + src/components/ChatView.jsx | 2 + src/components/Sidebar.jsx | 7 +++ 4 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src/components/ChatInput.jsx b/src/components/ChatInput.jsx index 26910c7..e947c25 100644 --- a/src/components/ChatInput.jsx +++ b/src/components/ChatInput.jsx @@ -4,6 +4,8 @@ import ProviderSelector from "./ProviderSelector"; const PROMPT_DRAFT_KEY = "agentforge:prompt-draft"; +const OFFLINE_GRACE_MS = 3000; + const dragHasFiles = (e) => Array.from(e.dataTransfer?.types || []).includes("Files"); const BLOCKED_UPLOAD_EXTS = new Set([ @@ -29,6 +31,7 @@ export default function ChatInput({ onSend, disabled, running, + connected = true, onCancel, onOpenProfiles, onOpenMemory, @@ -68,9 +71,22 @@ export default function ChatInput({ }) { const [text, setText] = useState(() => localStorage.getItem(PROMPT_DRAFT_KEY) || ""); const [helpOpen, setHelpOpen] = useState(false); + const [showOffline, setShowOffline] = useState(false); const inputRef = useRef(null); const fileInputRef = useRef(null); + useEffect(() => { + if (connected) { + setShowOffline(false); + return; + } + const t = setTimeout(() => setShowOffline(true), OFFLINE_GRACE_MS); + return () => clearTimeout(t); + }, [connected]); + + const offline = showOffline; + const busyOrOffline = disabled || offline; + useEffect(() => { if (text) localStorage.setItem(PROMPT_DRAFT_KEY, text); else localStorage.removeItem(PROMPT_DRAFT_KEY); @@ -129,7 +145,7 @@ export default function ChatInput({ const remainingTokens = maxTokens - usedTokens; const wouldExceedContext = estimatedNewTokens > remainingTokens; const contextPercent = Math.min(((usedTokens + estimatedNewTokens) / maxTokens) * 100, 100); - const attachDisabled = disabled || atFileLimit || wouldExceedContext; + const attachDisabled = busyOrOffline || atFileLimit || wouldExceedContext; const autoResize = useCallback(() => { const el = inputRef.current; @@ -141,7 +157,7 @@ export default function ChatInput({ const handleSubmit = (e) => { e.preventDefault(); const trimmed = text.trim(); - if ((!trimmed && !hasFiles) || disabled || stillUploading) return; + if ((!trimmed && !hasFiles) || busyOrOffline || stillUploading) return; if (wouldExceedContext) { setAttachError( `Content too large for context window — estimated ${estimatedNewTokens.toLocaleString()} tokens but only ~${remainingTokens.toLocaleString()} remaining. Remove attachments or shorten your prompt.`, @@ -583,7 +599,7 @@ export default function ChatInput({ modes={modes} selected={selectedMode} onChange={onModeChange} - disabled={disabled} + disabled={busyOrOffline} /> )} @@ -593,7 +609,7 @@ export default function ChatInput({ selected={selectedProvider} onChange={onProviderChange} locked={providerLocked} - disabled={disabled} + disabled={busyOrOffline} /> )} @@ -610,7 +626,7 @@ export default function ChatInput({