mirror of
https://github.com/langchain-ai/openwork.git
synced 2026-07-19 22:53:42 -04:00
error cleanup, lints, context styling
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user