diff --git a/apps/desktop/src/components/local-chat.tsx b/apps/desktop/src/components/local-chat.tsx index 5384c063..ade9db6c 100644 --- a/apps/desktop/src/components/local-chat.tsx +++ b/apps/desktop/src/components/local-chat.tsx @@ -1,6 +1,10 @@ +import { useState, useCallback } from 'react' import { Loading } from '@multica/ui/components/ui/loading' import { ChatView } from '@multica/ui/components/chat-view' import { useLocalChat } from '../hooks/use-local-chat' +import { useProvider } from '../hooks/use-provider' +import { ApiKeyDialog } from './api-key-dialog' +import { OAuthDialog } from './oauth-dialog' export function LocalChat() { const { @@ -17,8 +21,41 @@ export function LocalChat() { sendMessage, loadMore, resolveApproval, + clearError, } = useLocalChat() + const { providers, current, setProvider: switchProvider, refresh: refreshProviders } = useProvider() + + // Provider config dialog state + const [apiKeyDialogOpen, setApiKeyDialogOpen] = useState(false) + const [oauthDialogOpen, setOauthDialogOpen] = useState(false) + + const handleConfigureProvider = useCallback(() => { + const providerId = current?.provider + if (!providerId) return + + const meta = providers.find((p) => p.id === providerId) + if (!meta) return + + if (meta.authMethod === 'oauth') { + setOauthDialogOpen(true) + } else { + setApiKeyDialogOpen(true) + } + }, [current, providers]) + + const handleProviderConfigSuccess = useCallback(async () => { + const providerId = current?.provider + if (!providerId) return + + await refreshProviders() + await switchProvider(providerId) + clearError() + }, [current, refreshProviders, switchProvider, clearError]) + + // Derive provider info for dialogs + const currentMeta = current ? providers.find((p) => p.id === current.provider) : null + if (initError) { return (
@@ -36,19 +73,48 @@ export function LocalChat() { ) } + // Show "Configure" button when error is about provider/API key + const errorAction = error?.code === 'AGENT_ERROR' && currentMeta + ? { label: 'Configure', onClick: handleConfigureProvider } + : undefined + return ( - + <> + + + {currentMeta && currentMeta.authMethod === 'api-key' && ( + + )} + + {currentMeta && currentMeta.authMethod === 'oauth' && ( + + )} + ) } diff --git a/apps/desktop/src/hooks/use-local-chat.ts b/apps/desktop/src/hooks/use-local-chat.ts index c6353e12..749ee071 100644 --- a/apps/desktop/src/hooks/use-local-chat.ts +++ b/apps/desktop/src/hooks/use-local-chat.ts @@ -142,6 +142,10 @@ export function useLocalChat() { [], ) + const clearError = useCallback(() => { + chatRef.current.setError(null) + }, []) + return { agentId, initError, @@ -156,5 +160,6 @@ export function useLocalChat() { sendMessage, loadMore, resolveApproval, + clearError, } } diff --git a/packages/ui/src/components/chat-view.tsx b/packages/ui/src/components/chat-view.tsx index 4582b7be..b47edf0b 100644 --- a/packages/ui/src/components/chat-view.tsx +++ b/packages/ui/src/components/chat-view.tsx @@ -38,6 +38,8 @@ export interface ChatViewProps { loadMore?: () => void; resolveApproval: (approvalId: string, decision: "allow-once" | "allow-always" | "deny") => void; onDisconnect?: () => void; + /** Optional action button in the error banner (e.g. "Configure Provider") */ + errorAction?: { label: string; onClick: () => void }; } export function ChatView({ @@ -53,6 +55,7 @@ export function ChatView({ loadMore, resolveApproval, onDisconnect, + errorAction, }: ChatViewProps) { const mainRef = useRef(null); const sentinelRef = useRef(null); @@ -219,16 +222,28 @@ export function ChatView({
{error.message} - {onDisconnect && ( - - )} +
+ {errorAction && ( + + )} + {onDisconnect && ( + + )} +
)}