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) => { 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 { 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; diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 4876f46..72acbba 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 ?? []; @@ -1665,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; @@ -1701,6 +1707,7 @@ export function App() { if (streamToken !== streamSequenceRef.current) { return; } + resetWatchdog(); queueDelta(messageId, delta); }, onCitations: ({ messageId, citations }) => { @@ -1720,6 +1727,7 @@ export function App() { setTodos(updatedTodos); }, onToolStatus: ({ toolName, status }) => { + resetWatchdog(); if (status === "calling") { setToolStatus({ toolName, status }); } else { @@ -2699,7 +2707,7 @@ export function App() { ) : null} {message.role === "assistant" ? (
- + {message.content}