error cleanup, lints, context styling

This commit is contained in:
Hunter Lovell
2026-01-18 22:21:47 -08:00
parent 3fdbc50ce5
commit e2f051495a
4 changed files with 47 additions and 288 deletions
+3 -1
View File
@@ -59,7 +59,9 @@ export async function closeCheckpointer(threadId: string): Promise<void> {
}
// 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)
-36
View File
@@ -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
@@ -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({
</div>
</div>
{/* Cache info (if available) */}
{hasCacheData && (
<div className="space-y-1.5 pt-2 border-t border-border">
<div className="text-[10px] font-medium text-muted-foreground uppercase tracking-wider">
Cache
</div>
<div className="space-y-1">
{tokenUsage.cacheReadTokens !== undefined && tokenUsage.cacheReadTokens > 0 && (
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-1.5 text-green-500">
<Database className="size-3" />
<span>Cache hits</span>
</div>
<span className="font-mono text-green-500">
{formatTokenCountFull(tokenUsage.cacheReadTokens)}
</span>
</div>
)}
{tokenUsage.cacheCreationTokens !== undefined && tokenUsage.cacheCreationTokens > 0 && (
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-1.5 text-blue-500">
<Database className="size-3" />
<span>Cache created</span>
</div>
<span className="font-mono text-blue-500">
{formatTokenCountFull(tokenUsage.cacheCreationTokens)}
</span>
</div>
)}
</div>
{/* Cache info (always show, with "none" state) */}
<div className="space-y-1.5 pt-2 border-t border-border">
<div className="text-[10px] font-medium text-muted-foreground uppercase tracking-wider">
Cache
</div>
)}
<div className="space-y-1">
{hasCacheData ? (
<>
{tokenUsage.cacheReadTokens !== undefined && tokenUsage.cacheReadTokens > 0 && (
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-1.5 text-green-500">
<Database className="size-3" />
<span>Cache hits</span>
</div>
<span className="font-mono text-green-500">
{formatTokenCountFull(tokenUsage.cacheReadTokens)}
</span>
</div>
)}
{tokenUsage.cacheCreationTokens !== undefined && tokenUsage.cacheCreationTokens > 0 && (
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-1.5 text-blue-500">
<Database className="size-3" />
<span>Cache created</span>
</div>
<span className="font-mono text-blue-500">
{formatTokenCountFull(tokenUsage.cacheCreationTokens)}
</span>
</div>
)}
</>
) : (
<div className="text-xs text-muted-foreground">
No cached tokens
</div>
)}
</div>
</div>
{/* Last updated */}
<div className="pt-2 border-t border-border">
@@ -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<Record<string, string>>({})
const [savedKeys, setSavedKeys] = useState<Record<string, boolean>>({})
const [showKeys, setShowKeys] = useState<Record<string, boolean>>({})
const [saving, setSaving] = useState<Record<string, boolean>>({})
const [loading, setLoading] = useState(true)
// Load existing settings on mount
useEffect(() => {
if (open) {
loadApiKeys()
}
}, [open])
async function loadApiKeys(): Promise<void> {
setLoading(true)
const keys: Record<string, string> = {}
const saved: Record<string, boolean> = {}
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<void> {
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 (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>Settings</DialogTitle>
<DialogDescription>
Configure API keys for model providers. Keys are stored securely on your device.
</DialogDescription>
</DialogHeader>
<Separator />
<div className="space-y-6 py-2">
<div className="text-section-header">API KEYS</div>
{loading ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="size-6 animate-spin text-muted-foreground" />
</div>
) : (
<div className="space-y-4">
{PROVIDERS.map((provider) => (
<div key={provider.id} className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-sm font-medium">{provider.name}</label>
{savedKeys[provider.id] ? (
<span className="flex items-center gap-1 text-xs text-status-nominal">
<Check className="size-3" />
Configured
</span>
) : apiKeys[provider.id] ? (
<span className="flex items-center gap-1 text-xs text-status-warning">
<AlertCircle className="size-3" />
Unsaved
</span>
) : (
<span className="text-xs text-muted-foreground">Not set</span>
)}
</div>
<div className="flex gap-2">
<div className="relative flex-1">
<Input
type={showKeys[provider.id] ? 'text' : 'password'}
value={apiKeys[provider.id] || ''}
onChange={(e) => handleKeyChange(provider.id, e.target.value)}
placeholder={provider.placeholder}
className="pr-10"
/>
<button
type="button"
onClick={() => toggleShowKey(provider.id)}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
>
{showKeys[provider.id] ? (
<EyeOff className="size-4" />
) : (
<Eye className="size-4" />
)}
</button>
</div>
<Button
variant={savedKeys[provider.id] ? 'outline' : 'default'}
size="sm"
onClick={() => saveApiKey(provider.id)}
disabled={
saving[provider.id] ||
!apiKeys[provider.id] ||
apiKeys[provider.id] === '••••••••••••••••'
}
>
{saving[provider.id] ? (
<Loader2 className="size-4 animate-spin" />
) : (
'Save'
)}
</Button>
</div>
<p className="text-xs text-muted-foreground">
Environment variable: <code className="text-foreground">{provider.envVar}</code>
</p>
</div>
))}
</div>
)}
</div>
<Separator />
<div className="flex justify-end">
<Button variant="outline" onClick={() => onOpenChange(false)}>
Done
</Button>
</div>
</DialogContent>
</Dialog>
)
}