From be43af981cd139c64916d7f725997a92eeb02ab8 Mon Sep 17 00:00:00 2001 From: Karan Singh Date: Sat, 21 Mar 2026 00:42:27 +0530 Subject: [PATCH 1/5] =?UTF-8?q?perf(tools):=20reduce=20fetch=5Furl=20timeo?= =?UTF-8?q?ut=2015s=E2=86=928s,=20web=5Fsearch=2010s=E2=86=926s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/tools/fetchUrl.ts | 2 +- src/main/tools/webSearch.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/tools/fetchUrl.ts b/src/main/tools/fetchUrl.ts index 26fe790..b3f5a6e 100644 --- a/src/main/tools/fetchUrl.ts +++ b/src/main/tools/fetchUrl.ts @@ -1,7 +1,7 @@ import { ToolExecutor } from "./types"; const MAX_CONTENT_CHARS = 30_000; -const FETCH_TIMEOUT_MS = 15_000; +const FETCH_TIMEOUT_MS = 8_000; const USER_AGENT = "Robin/1.0 (Personal AI Assistant)"; function stripHtml(html: string): string { diff --git a/src/main/tools/webSearch.ts b/src/main/tools/webSearch.ts index 3d3ab83..2d3cbb6 100644 --- a/src/main/tools/webSearch.ts +++ b/src/main/tools/webSearch.ts @@ -1,6 +1,6 @@ import { ToolExecutor } from "./types"; -const SEARCH_TIMEOUT_MS = 10_000; +const SEARCH_TIMEOUT_MS = 6_000; interface BraveWebResult { title?: string; From 4b2eba27fc97aa3d1fc65a11229d2bbcac11e198 Mon Sep 17 00:00:00 2001 From: Karan Singh Date: Sat, 21 Mar 2026 00:42:39 +0530 Subject: [PATCH 2/5] perf(storage): stop writing settings.json on every read --- src/main/storage.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/storage.ts b/src/main/storage.ts index 98b0694..caa95d9 100644 --- a/src/main/storage.ts +++ b/src/main/storage.ts @@ -220,9 +220,7 @@ export class AppStorage { async getSettings(): Promise { const raw = await this.readJson(this.settingsPath, DEFAULT_SETTINGS); - const normalized = normalizeSettings(raw); - await this.writeJson(this.settingsPath, normalized); - return normalized; + return normalizeSettings(raw); } async saveSettings(updater: (current: SettingsData) => SettingsData): Promise { From 34a54287658874a3c1fc3adc1a8d604d1226995a Mon Sep 17 00:00:00 2001 From: Karan Singh Date: Sat, 21 Mar 2026 00:43:03 +0530 Subject: [PATCH 3/5] perf(backend): parallelize pre-flight ops, cache context providers - Run system prompt build + Brave key fetch in parallel (Promise.all) - Remove duplicate getSettings() call; reuse settings from earlier read - Cache TodoContextProvider and NotesContextProvider instances instead of re-creating on every access via getter --- src/main/providerService.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/providerService.ts b/src/main/providerService.ts index ed1bc76..46b0a74 100644 --- a/src/main/providerService.ts +++ b/src/main/providerService.ts @@ -147,15 +147,15 @@ export class ProviderService { checkedAt: number; }>(); + private readonly contextProviders: Array; + constructor( private readonly storage: AppStorage, private readonly secureConfig: SecureConfig - ) {} - - private get contextProviders() { - return [ - new TodoContextProvider(this.storage), - new NotesContextProvider(this.storage) + ) { + this.contextProviders = [ + new TodoContextProvider(storage), + new NotesContextProvider(storage) ]; } @@ -495,10 +495,11 @@ export class ProviderService { }); let finalCitations: Citation[] = []; - const systemPrompt = await buildSystemPrompt(this.contextProviders, request.prompt); - const braveApiKey = await this.secureConfig.getToolApiKey("brave"); - const toolToggles = (await this.storage.getSettings()).toolToggles; - const toolExecutors = buildToolExecutors(braveApiKey, toolToggles); + const [systemPrompt, braveApiKey] = await Promise.all([ + buildSystemPrompt(this.contextProviders, request.prompt), + this.secureConfig.getToolApiKey("brave") + ]); + const toolExecutors = buildToolExecutors(braveApiKey, settings.toolToggles); const toolDefs = getToolDefinitions(toolExecutors); const onDelta = (delta: string) => { From 87841f397e13726fa8657e47558c3209c39a5228 Mon Sep 17 00:00:00 2001 From: Karan Singh Date: Sat, 21 Mar 2026 00:43:33 +0530 Subject: [PATCH 4/5] perf(ui): memoize remarkPlugins, debounce scroll-into-view - Move [remarkGfm] array to module-level constant to prevent re-allocation on every render (was defeating React memo) - Debounce scrollIntoView with 80ms timer to prevent DOM thrashing during fast delta streaming (was firing every animation frame) --- src/renderer/App.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 4876f46..d3074eb 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -4,6 +4,8 @@ import "@fontsource/gochi-hand"; import "@fontsource/dm-sans/500.css"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; + +const REMARK_PLUGINS = [remarkGfm]; import { AssistantMode, CLOUD_PROVIDER_IDS, @@ -738,8 +740,13 @@ export function App() { }); } + const scrollTimerRef = useRef | null>(null); useEffect(() => { - chatEndRef.current?.scrollIntoView({ behavior: "smooth" }); + if (scrollTimerRef.current) clearTimeout(scrollTimerRef.current); + scrollTimerRef.current = setTimeout(() => { + chatEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, 80); + return () => { if (scrollTimerRef.current) clearTimeout(scrollTimerRef.current); }; }, [messages.length, messages[messages.length - 1]?.content]); const localModels = ollamaStatus?.models ?? []; @@ -2699,7 +2706,7 @@ export function App() { ) : null} {message.role === "assistant" ? (
- + {message.content}
From f57f96085d8774683a169624c6cb38562d996f08 Mon Sep 17 00:00:00 2001 From: Karan Singh Date: Sat, 21 Mar 2026 00:44:31 +0530 Subject: [PATCH 5/5] fix(chat): reset watchdog timer during tool execution The 30s watchdog never reset while tools were executing, causing "Model response timed out" errors during web search flows that take 30-75s across multiple tool rounds. Now resets on every onDelta and onToolStatus event. Increased timeout from 30s to 60s to accommodate multi-round tool loops. --- src/renderer/App.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index d3074eb..72acbba 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1672,15 +1672,14 @@ export function App() { setError(null); const streamToken = streamSequenceRef.current + 1; streamSequenceRef.current = streamToken; - if (streamWatchdogRef.current) { - clearTimeout(streamWatchdogRef.current); - } - streamWatchdogRef.current = setTimeout(() => { - if (streamToken !== streamSequenceRef.current) { - return; - } - void stopPendingResponse("Model response timed out. Press Stop and retry."); - }, 30000); + const resetWatchdog = () => { + if (streamWatchdogRef.current) clearTimeout(streamWatchdogRef.current); + streamWatchdogRef.current = setTimeout(() => { + if (streamToken !== streamSequenceRef.current) return; + void stopPendingResponse("Model response timed out. Press Stop and retry."); + }, 60000); + }; + resetWatchdog(); setIsStreaming(true); const text = prompt.trim(); const outgoingAttachments = pendingAttachments; @@ -1708,6 +1707,7 @@ export function App() { if (streamToken !== streamSequenceRef.current) { return; } + resetWatchdog(); queueDelta(messageId, delta); }, onCitations: ({ messageId, citations }) => { @@ -1727,6 +1727,7 @@ export function App() { setTodos(updatedTodos); }, onToolStatus: ({ toolName, status }) => { + resetWatchdog(); if (status === "calling") { setToolStatus({ toolName, status }); } else {