import React, { useRef, useCallback, useMemo, useState } from 'react' import { FolderOpen, Share2, Loader2 } from 'lucide-react' import { Button } from '@/components/ui/button' import { cn } from '@/utils/cn' import { MessageList, type MessageListRef } from '../message-list' import { TaskPanel } from '../task-panel' import { NovaKitProvider } from '../context/NovaKitProvider' import { useNovaChatLogic } from '../hooks/useNovaChatLogic' import { ChatInputArea } from './ChatInputArea' import type { PlatformConfig, ApiEvent } from '../hooks/useNovaEvents' import { toast } from 'sonner' import { request } from '@/http/request' // 导出类型供外部使用 export type { ApiEvent, PlatformConfig } export interface NovaChatProps { /** 模式 */ mode?: 'chat' | 'share' /** 是否显示文件面板 */ panelMode?: 'sidebar' | 'dialog' /** 会话 ID */ conversationId?: string /** 代理 ID */ agentId?: string /** 侧边面板宽度 */ panelWidth?: number | string /** 是否正在加载 */ loading?: boolean /** 输入框占位符 */ placeholder?: string /** 是否禁用输入 */ disabled?: boolean /** 空状态渲染 */ emptyRender?: React.ReactNode /** 自定义类名 */ className?: string // useNovaEvents 相关配置 /** 平台配置(如果提供,则自动建立 WebSocket 连接和获取历史记录) */ platformConfig?: PlatformConfig /** WebSocket 重连次数限制,默认 3 */ reconnectLimit?: number /** WebSocket 重连间隔(毫秒),默认 3000 */ reconnectInterval?: number /** 获取认证 Token */ getToken?: () => string | undefined /** 获取租户 ID */ getTenantId?: () => string | undefined /** 新事件回调 */ onEvent?: (event: ApiEvent) => void /** 连接状态变化回调 */ onConnectionChange?: (connected: boolean) => void /** 错误回调 */ onError?: (error: Error) => void } /** * Nova 聊天组件 - 整合消息列表、输入框和文件面板 */ function InnerNovaChat({ mode = 'chat', panelMode = 'sidebar', conversationId, agentId, panelWidth = '50%', placeholder, disabled = false, emptyRender, className, platformConfig, reconnectLimit = 3, reconnectInterval = 3000, getToken, getTenantId, onEvent, onConnectionChange, onError, }: NovaChatProps) { const messageListRef = useRef(null) // 使用主业务逻辑 Hook const { messages, loading, taskStatus, artifacts, hasArtifacts, panelVisible, selectedAttachment, handleSend, handlePanelToggle, handlePanelClose, handleAttachmentClick, handleImageAttachmentClick, handleToolCallClick, setLoading, api, } = useNovaChatLogic({ conversationId, agentId, platformConfig, reconnectLimit, reconnectInterval, getToken, getTenantId, onEvent, onConnectionChange, onError, mode }) const [shareLoading, setShareLoading] = useState(false) // 包装 handleSend 以添加滚动逻辑 const handleSendWithScroll = useCallback( (payload: Parameters[0]) => { handleSend(payload) // 延迟滚动到底部,确保消息已添加到列表中 setTimeout(() => { messageListRef.current?.scrollToBottom('smooth') }, 100) }, [handleSend] ) const providerAgentId = agentId ?? platformConfig?.agentId ?? '' const shareUrl = useMemo(() => { if (!conversationId || !providerAgentId) return '' return `${window.location.origin}/share?conversationId=${encodeURIComponent(conversationId)}&agentId=${encodeURIComponent(providerAgentId)}` }, [conversationId, providerAgentId]) return (
{/* 主聊天区域 */}
{/* 头部通栏:白色磨砂,图标黑色 */}
{hasArtifacts && ( )} {mode === 'chat' && ( )}
{/* 消息列表 - 铺满全部高度,消息可滚动到输入框下方 */} handleSendWithScroll({ content })} /> {/* 输入区域 - absolute 固定在底部,磨砂透明,消息可穿过 */} {mode === 'chat' && (
)}
{/* 文件面板:支持 sidebar 与 dialog 两种模式 */} {(hasArtifacts || selectedAttachment) && panelVisible && ( )}
) } export const NovaChat = React.memo(InnerNovaChat) export default NovaChat