import React, { type Dispatch, type SetStateAction } from 'react'; import { History, Plus } from 'lucide-react'; import type { AIPermissionMode, AISession, ChatMessage, DiscoveredAgent, ExternalAgentConfig, AgentModelPreset, ProviderConfig, UploadedFile } from '../infrastructure/ai/types'; import type { Host, VaultNote } from '../types'; import type { UserSkillOption } from './ai/userSkillsState'; import type { AIQuickMessage } from '../infrastructure/ai/quickMessages'; import { Button } from './ui/button'; import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; import AgentSelector from './ai/AgentSelector'; import ChatInput from './ai/ChatInput'; import ChatMessageList from './ai/ChatMessageList'; import ConversationExport from './ai/ConversationExport'; import { SessionHistoryDrawer, formatRelativeTime } from './AIChatSessionHistoryDrawer'; import { cn } from '../lib/utils'; import { TERMINAL_SIDE_PANEL_INNER_HEADER_CLASS } from './terminalLayer/terminalSidePanelChrome'; import { getAIPanelDiagnosticHiddenParts, getAIPanelProfilerProps, isAIPanelDiagnosticPartHidden, } from './ai/aiPanelDiagnostics'; type Translate = (key: string) => string; type ExportFormat = 'md' | 'json' | 'txt'; type TerminalSessionSummary = { sessionId: string; hostname: string; label: string; connected: boolean; }; interface AIChatPanelContentProps { t: Translate; currentAgentId: string; externalAgents: ExternalAgentConfig[]; discoveredAgents: DiscoveredAgent[]; isDiscovering: boolean; handleAgentChange: (agentId: string) => void; handleEnableDiscoveredAgent: (agent: DiscoveredAgent) => void; rediscover: () => void; handleOpenSettings: () => void; activeSession: AISession | null; handleExport: (format: ExportFormat) => void; showHistory: boolean; setShowHistory: Dispatch>; handleNewChat: () => void; historySessions: AISession[]; activeSessionId: string | null; handleSelectSession: (sessionId: string) => void; handleDeleteSession: (event: React.MouseEvent, sessionId: string) => void; messages: ChatMessage[]; isStreaming: boolean; activeCompaction?: import('../application/state/useAgentCompactionUi').ActiveCompactionUi | null; contextUsage?: import('../application/state/useAgentCompactionUi').AgentContextUsage | null; inputValue: string; setInputValue: (value: string) => void; handleSend: () => void; handleCompact: () => void; canCompact?: boolean; handleSteer: () => void; handleStop: () => void; canSteer: boolean; isSteering: boolean; steerWarning?: string; lockTurnConfiguration: boolean; canSendCurrentAgent: boolean; providerDisplayName?: string; modelDisplayName?: string; modelCatalogWarning?: string; agentModelPresets: AgentModelPreset[]; selectedAgentModel: string; handleAgentModelSelect: (modelId: string) => void; cattyConfiguredProviders: ProviderConfig[]; effectiveActiveProvider?: ProviderConfig; effectiveActiveModelId?: string; handleAgentProviderModelSelect: (providerId: string, modelId: string, contextWindow?: number) => void; selectedCattyThinking?: string; handleCattyThinkingSelect?: (level: string) => void; files: UploadedFile[]; addFiles: (inputFiles: File[]) => Promise; removeFile: (fileId: string) => void; terminalSessions: TerminalSessionSummary[]; selectedUserSkills: UserSkillOption[]; userSkillOptions: UserSkillOption[]; quickMessages: AIQuickMessage[]; addSelectedUserSkill: (slug: string) => void; removeSelectedUserSkill: (slug: string) => void; globalPermissionMode: AIPermissionMode; setGlobalPermissionMode?: (mode: AIPermissionMode) => void; notes?: VaultNote[]; hosts?: Host[]; /** Mention Note: attach a Vault → Notes entry as inline context. */ onMentionNote?: (note: VaultNote) => void; onOpenVaultNote?: (noteId: string) => void; onOpenVaultHost?: (hostId: string) => void; onOpenVaultSection?: (section: 'notes' | 'hosts') => void; /** Hidden retained panels keep the composer warm without the message tree. */ parked?: boolean; /** Disable header transitions while send preflight is in flight. */ sending?: boolean; } export const AIChatPanelContent: React.FC = ({ t, currentAgentId, externalAgents, discoveredAgents, isDiscovering, handleAgentChange, handleEnableDiscoveredAgent, rediscover, handleOpenSettings, activeSession, handleExport, showHistory, setShowHistory, handleNewChat, historySessions, activeSessionId, handleSelectSession, handleDeleteSession, messages, isStreaming, activeCompaction = null, contextUsage = null, inputValue, setInputValue, handleSend, handleCompact, canCompact = false, handleSteer, handleStop, canSteer, isSteering, steerWarning, lockTurnConfiguration, canSendCurrentAgent, providerDisplayName, modelDisplayName, modelCatalogWarning, agentModelPresets, selectedAgentModel, handleAgentModelSelect, cattyConfiguredProviders, effectiveActiveProvider, effectiveActiveModelId, handleAgentProviderModelSelect, selectedCattyThinking, handleCattyThinkingSelect, files, addFiles, removeFile, terminalSessions, selectedUserSkills, userSkillOptions, quickMessages, addSelectedUserSkill, removeSelectedUserSkill, globalPermissionMode, setGlobalPermissionMode, notes = [], hosts = [], onMentionNote, onOpenVaultNote, onOpenVaultHost, onOpenVaultSection, parked = false, sending = false, }) => { const mentionHosts = React.useMemo( () => terminalSessions.map((session) => ({ sessionId: session.sessionId, hostname: session.hostname, label: session.label, connected: session.connected, })), [terminalSessions], ); const providerSwitcher = React.useMemo( () => ( currentAgentId === 'catty' && cattyConfiguredProviders.length > 0 ? { providers: cattyConfiguredProviders, selectedProviderId: effectiveActiveProvider?.id, selectedModelId: effectiveActiveModelId || undefined, onSelect: handleAgentProviderModelSelect, } : undefined ), [ cattyConfiguredProviders, currentAgentId, effectiveActiveModelId, effectiveActiveProvider?.id, handleAgentProviderModelSelect, ], ); const hiddenParts = getAIPanelDiagnosticHiddenParts(); const hideHeader = isAIPanelDiagnosticPartHidden('header', hiddenParts); const hideHistory = isAIPanelDiagnosticPartHidden('history', hiddenParts); const hideMessages = isAIPanelDiagnosticPartHidden('messages', hiddenParts); const hideRecent = isAIPanelDiagnosticPartHidden('recent', hiddenParts); const hideInput = isAIPanelDiagnosticPartHidden('input', hiddenParts); return (
{/* ── Header ── */} {!hideHeader && (
{t('ai.chat.sessionHistory')} {t('ai.chat.newChat')}
)} {/* ── Main content ── */} {!parked && showHistory && !hideHistory ? ( undefined : handleSelectSession} onDelete={handleDeleteSession} onClose={() => setShowHistory(false)} /> ) : ( <> {/* Chat messages */} {!parked && !hideMessages && ( )} {/* Recent sessions (Zed-style, shown when no messages) */} {!parked && messages.length === 0 && historySessions.length > 0 && !hideRecent && (
{t('ai.chat.recent')}
{historySessions.slice(0, 3).map((session) => ( ))}
)} {/* Input area */} {!hideInput && (
{steerWarning ? (
{steerWarning}
) : modelCatalogWarning ? (
{modelCatalogWarning}
) : null} a.id === currentAgentId)?.name} modelPresets={agentModelPresets} selectedModelId={selectedAgentModel} onModelSelect={handleAgentModelSelect} providerSwitcher={providerSwitcher} pickerScope={currentAgentId} thinkingLevel={currentAgentId === 'catty' ? selectedCattyThinking : undefined} onThinkingLevelChange={currentAgentId === 'catty' ? handleCattyThinkingSelect : undefined} files={files} onAddFiles={addFiles} onRemoveFile={removeFile} hosts={mentionHosts} notes={notes} onMentionNote={onMentionNote} selectedUserSkills={selectedUserSkills} userSkills={userSkillOptions} quickMessages={quickMessages} onAddUserSkill={addSelectedUserSkill} onRemoveUserSkill={removeSelectedUserSkill} permissionMode={globalPermissionMode} onPermissionModeChange={setGlobalPermissionMode} contextUsage={contextUsage} />
)} )}
); };