diff --git a/src/main/agent/runtime.ts b/src/main/agent/runtime.ts index 3bee29c..caf2959 100644 --- a/src/main/agent/runtime.ts +++ b/src/main/agent/runtime.ts @@ -59,7 +59,9 @@ export async function closeCheckpointer(threadId: string): Promise { } // Get the appropriate model instance based on configuration -function getModelInstance(modelId?: string): ChatAnthropic | ChatOpenAI | ChatGoogleGenerativeAI | string { +function getModelInstance( + modelId?: string +): ChatAnthropic | ChatOpenAI | ChatGoogleGenerativeAI | string { const model = modelId || getDefaultModel() console.log('[Runtime] Using model:', model) diff --git a/src/main/index.ts b/src/main/index.ts index 57ba223..ce7b355 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -5,42 +5,6 @@ import { registerThreadHandlers } from './ipc/threads' import { registerModelHandlers } from './ipc/models' import { initializeDatabase } from './db' -// Suppress expected errors from LangChain stream handlers when streams are aborted -// These occur when the LLM is still generating but the stream has been closed -const originalConsoleError = console.error -console.error = (...args: unknown[]) => { - const message = args.map((a) => String(a)).join(' ') - if ( - message.includes('Controller is already closed') || - message.includes('ERR_INVALID_STATE') || - (message.includes('StreamMessagesHandler') && message.includes('aborted')) - ) { - // Expected during stream cancellation - suppress - return - } - originalConsoleError.apply(console, args) -} - -process.on('uncaughtException', (error) => { - if ( - error.message?.includes('Controller is already closed') || - error.message?.includes('aborted') - ) { - // Expected during stream cancellation - suppress - return - } - originalConsoleError('Uncaught exception:', error) -}) - -process.on('unhandledRejection', (reason) => { - const message = reason instanceof Error ? reason.message : String(reason) - if (message?.includes('Controller is already closed') || message?.includes('aborted')) { - // Expected during stream cancellation - suppress - return - } - originalConsoleError('Unhandled rejection:', reason) -}) - let mainWindow: BrowserWindow | null = null // Simple dev check - replaces @electron-toolkit/utils is.dev diff --git a/src/renderer/src/components/chat/ContextUsageIndicator.tsx b/src/renderer/src/components/chat/ContextUsageIndicator.tsx index b2251bd..3f58e99 100644 --- a/src/renderer/src/components/chat/ContextUsageIndicator.tsx +++ b/src/renderer/src/components/chat/ContextUsageIndicator.tsx @@ -92,9 +92,9 @@ export function ContextUsageIndicator({ const usagePercent = Math.min((usedTokens / contextLimit) * 100, 100) // Determine color based on usage - let colorClass = 'text-muted-foreground' - let bgColorClass = 'bg-muted-foreground/20' - let barColorClass = 'bg-muted-foreground' + let colorClass = 'text-blue-500' + let bgColorClass = 'bg-blue-500/20' + let barColorClass = 'bg-blue-500' let statusText = 'Normal' if (usagePercent >= 90) { @@ -200,40 +200,48 @@ export function ContextUsageIndicator({ - {/* Cache info (if available) */} - {hasCacheData && ( -
-
- Cache -
- -
- {tokenUsage.cacheReadTokens !== undefined && tokenUsage.cacheReadTokens > 0 && ( -
-
- - Cache hits -
- - {formatTokenCountFull(tokenUsage.cacheReadTokens)} - -
- )} - - {tokenUsage.cacheCreationTokens !== undefined && tokenUsage.cacheCreationTokens > 0 && ( -
-
- - Cache created -
- - {formatTokenCountFull(tokenUsage.cacheCreationTokens)} - -
- )} -
+ {/* Cache info (always show, with "none" state) */} +
+
+ Cache
- )} + +
+ {hasCacheData ? ( + <> + {tokenUsage.cacheReadTokens !== undefined && tokenUsage.cacheReadTokens > 0 && ( +
+
+ + Cache hits +
+ + {formatTokenCountFull(tokenUsage.cacheReadTokens)} + +
+ )} + + {tokenUsage.cacheCreationTokens !== undefined && tokenUsage.cacheCreationTokens > 0 && ( +
+
+ + Cache created +
+ + {formatTokenCountFull(tokenUsage.cacheCreationTokens)} + +
+ )} + + ) : ( +
+ No cached tokens +
+ )} +
+
+ + {/* Last updated */}
diff --git a/src/renderer/src/components/settings/SettingsDialog.tsx b/src/renderer/src/components/settings/SettingsDialog.tsx deleted file mode 100644 index b29965e..0000000 --- a/src/renderer/src/components/settings/SettingsDialog.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import { useState, useEffect } from 'react' -import { Eye, EyeOff, Check, AlertCircle, Loader2 } from 'lucide-react' -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle -} from '@/components/ui/dialog' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Separator } from '@/components/ui/separator' - -interface SettingsDialogProps { - open: boolean - onOpenChange: (open: boolean) => void -} - -interface ProviderConfig { - id: string - name: string - envVar: string - placeholder: string -} - -const PROVIDERS: ProviderConfig[] = [ - { - id: 'anthropic', - name: 'Anthropic', - envVar: 'ANTHROPIC_API_KEY', - placeholder: 'sk-ant-...' - }, - { - id: 'openai', - name: 'OpenAI', - envVar: 'OPENAI_API_KEY', - placeholder: 'sk-...' - }, - { - id: 'google', - name: 'Google AI', - envVar: 'GOOGLE_API_KEY', - placeholder: 'AIza...' - } -] - -export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps): React.JSX.Element { - const [apiKeys, setApiKeys] = useState>({}) - const [savedKeys, setSavedKeys] = useState>({}) - const [showKeys, setShowKeys] = useState>({}) - const [saving, setSaving] = useState>({}) - const [loading, setLoading] = useState(true) - - // Load existing settings on mount - useEffect(() => { - if (open) { - loadApiKeys() - } - }, [open]) - - async function loadApiKeys(): Promise { - setLoading(true) - const keys: Record = {} - const saved: Record = {} - - for (const provider of PROVIDERS) { - try { - const key = await window.api.models.getApiKey(provider.id) - if (key) { - // Show masked version - keys[provider.id] = '••••••••••••••••' - saved[provider.id] = true - } else { - keys[provider.id] = '' - saved[provider.id] = false - } - } catch (e) { - keys[provider.id] = '' - saved[provider.id] = false - } - } - - setApiKeys(keys) - setSavedKeys(saved) - setLoading(false) - } - - async function saveApiKey(providerId: string): Promise { - const key = apiKeys[providerId] - if (!key || key === '••••••••••••••••') return - - setSaving((prev) => ({ ...prev, [providerId]: true })) - - try { - await window.api.models.setApiKey(providerId, key) - setSavedKeys((prev) => ({ ...prev, [providerId]: true })) - setApiKeys((prev) => ({ ...prev, [providerId]: '••••••••••••••••' })) - setShowKeys((prev) => ({ ...prev, [providerId]: false })) - } catch (e) { - console.error('Failed to save API key:', e) - } finally { - setSaving((prev) => ({ ...prev, [providerId]: false })) - } - } - - function handleKeyChange(providerId: string, value: string): void { - // If user starts typing on a masked field, clear it - if (apiKeys[providerId] === '••••••••••••••••' && value.length > 16) { - value = value.slice(16) - } - setApiKeys((prev) => ({ ...prev, [providerId]: value })) - setSavedKeys((prev) => ({ ...prev, [providerId]: false })) - } - - function toggleShowKey(providerId: string): void { - setShowKeys((prev) => ({ ...prev, [providerId]: !prev[providerId] })) - } - - return ( - - - - Settings - - Configure API keys for model providers. Keys are stored securely on your device. - - - - - -
-
API KEYS
- - {loading ? ( -
- -
- ) : ( -
- {PROVIDERS.map((provider) => ( -
-
- - {savedKeys[provider.id] ? ( - - - Configured - - ) : apiKeys[provider.id] ? ( - - - Unsaved - - ) : ( - Not set - )} -
-
-
- handleKeyChange(provider.id, e.target.value)} - placeholder={provider.placeholder} - className="pr-10" - /> - -
- -
-

- Environment variable: {provider.envVar} -

-
- ))} -
- )} -
- - - -
- -
-
-
- ) -}