Merge remote-tracking branch 'origin/main' into feat/telegram-channel
# Conflicts: # apps/desktop/src/hooks/use-local-chat.ts # packages/sdk/src/actions/stream.ts # packages/ui/src/components/chat-view.tsx # src/agent/async-agent.ts # src/agent/events.ts
This commit is contained in:
commit
0895d42d3b
21 changed files with 462 additions and 60 deletions
|
|
@ -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 (
|
||||
<div className="flex-1 flex items-center justify-center text-sm text-destructive">
|
||||
|
|
@ -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 (
|
||||
<ChatView
|
||||
messages={messages}
|
||||
streamingIds={streamingIds}
|
||||
isLoading={isLoading}
|
||||
isLoadingHistory={isLoadingHistory}
|
||||
isLoadingMore={isLoadingMore}
|
||||
hasMore={hasMore}
|
||||
error={error}
|
||||
pendingApprovals={pendingApprovals}
|
||||
sendMessage={sendMessage}
|
||||
loadMore={loadMore}
|
||||
resolveApproval={resolveApproval}
|
||||
/>
|
||||
<>
|
||||
<ChatView
|
||||
messages={messages}
|
||||
streamingIds={streamingIds}
|
||||
isLoading={isLoading}
|
||||
isLoadingHistory={isLoadingHistory}
|
||||
isLoadingMore={isLoadingMore}
|
||||
hasMore={hasMore}
|
||||
error={error}
|
||||
pendingApprovals={pendingApprovals}
|
||||
sendMessage={sendMessage}
|
||||
loadMore={loadMore}
|
||||
resolveApproval={resolveApproval}
|
||||
errorAction={errorAction}
|
||||
/>
|
||||
|
||||
{currentMeta && currentMeta.authMethod === 'api-key' && (
|
||||
<ApiKeyDialog
|
||||
open={apiKeyDialogOpen}
|
||||
onOpenChange={setApiKeyDialogOpen}
|
||||
providerId={currentMeta.id}
|
||||
providerName={currentMeta.name}
|
||||
onSuccess={handleProviderConfigSuccess}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentMeta && currentMeta.authMethod === 'oauth' && (
|
||||
<OAuthDialog
|
||||
open={oauthDialogOpen}
|
||||
onOpenChange={setOauthDialogOpen}
|
||||
providerId={currentMeta.id}
|
||||
providerName={currentMeta.name}
|
||||
loginCommand={currentMeta.loginCommand}
|
||||
onSuccess={handleProviderConfigSuccess}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import type {
|
|||
ExecApprovalRequestPayload,
|
||||
ApprovalDecision,
|
||||
AgentMessageItem,
|
||||
AgentErrorEvent,
|
||||
} from '@multica/sdk'
|
||||
import { DEFAULT_MESSAGES_LIMIT } from '@multica/sdk'
|
||||
|
||||
|
|
@ -56,10 +57,10 @@ export function useLocalChat() {
|
|||
const payload = data as unknown as StreamPayload
|
||||
if (!payload.event) return
|
||||
|
||||
// Handle agent errors as transient UI feedback (not persisted to history)
|
||||
// Handle agent error events
|
||||
if (payload.event.type === 'agent_error') {
|
||||
const errorMsg = (payload.event as { error?: string }).error ?? 'Unknown error'
|
||||
chatRef.current.setError({ code: 'AGENT_ERROR', message: errorMsg })
|
||||
const errorEvent = payload.event as AgentErrorEvent
|
||||
chatRef.current.setError({ code: 'AGENT_ERROR', message: errorEvent.message })
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
|
|
@ -141,6 +142,10 @@ export function useLocalChat() {
|
|||
[],
|
||||
)
|
||||
|
||||
const clearError = useCallback(() => {
|
||||
chatRef.current.setError(null)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
agentId,
|
||||
initError,
|
||||
|
|
@ -155,5 +160,6 @@ export function useLocalChat() {
|
|||
sendMessage,
|
||||
loadMore,
|
||||
resolveApproval,
|
||||
clearError,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue