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 && (
+
+ )}
+
)}