Buckets:
| import { useState, useRef, useEffect, useCallback } from "react"; | |
| import { AnimatePresence } from "framer-motion"; | |
| import { ChatHeader } from "./chat/ChatHeader"; | |
| import { ChatMessage } from "./chat/ChatMessage"; | |
| import { ChatInput } from "./chat/ChatInput"; | |
| import { TypingIndicator } from "./chat/TypingIndicator"; | |
| import { WelcomeScreen } from "./chat/WelcomeScreen"; | |
| import { ModelType } from "./chat/ModelSwitcher"; | |
| import { NexusSidebar, SkillCategory } from "./nexus/NexusSidebar"; | |
| import { CommandPalette } from "./nexus/CommandPalette"; | |
| import { StatusBar } from "./nexus/StatusBar"; | |
| import { SkillPanel } from "./nexus/SkillPanel"; | |
| import { SettingsPanel } from "./nexus/SettingsPanel"; | |
| import { FinancePanel } from "./nexus/panels/FinancePanel"; | |
| import { MoviesPanel } from "./nexus/panels/MoviesPanel"; | |
| import { GamingPanel } from "./nexus/panels/GamingPanel"; | |
| import { WeatherPanel } from "./nexus/panels/WeatherPanel"; | |
| import { MusicPanel } from "./nexus/panels/MusicPanel"; | |
| import { ResearchPanel } from "./nexus/panels/ResearchPanel"; | |
| import { streamChat } from "@/lib/chat"; | |
| import { useToast } from "@/hooks/use-toast"; | |
| interface Message { | |
| id: string; | |
| role: "user" | "assistant"; | |
| content: string; | |
| } | |
| export const NexusHub = () => { | |
| const [messages, setMessages] = useState<Message[]>([]); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [selectedModel, setSelectedModel] = useState<ModelType>("gemini-flash"); | |
| const [activeCategory, setActiveCategory] = useState<SkillCategory>("chat"); | |
| const messagesEndRef = useRef<HTMLDivElement>(null); | |
| const { toast } = useToast(); | |
| const scrollToBottom = () => { | |
| messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); | |
| }; | |
| useEffect(() => { | |
| scrollToBottom(); | |
| }, [messages, isLoading]); | |
| const handleModelChange = (model: ModelType) => { | |
| setSelectedModel(model); | |
| const names: Record<ModelType, string> = { | |
| "gemini-flash": "Gemini Flash", | |
| "perplexity-sonar": "Perplexity Sonar", | |
| "local": "LM Studio (LuunaOS)", | |
| }; | |
| toast({ title: "Model Switched", description: `NEXUS now using ${names[model]}` }); | |
| }; | |
| const handleSend = async (content: string) => { | |
| setActiveCategory("chat"); | |
| const userMessage: Message = { id: Date.now().toString(), role: "user", content }; | |
| setMessages((prev) => [...prev, userMessage]); | |
| setIsLoading(true); | |
| let assistantContent = ""; | |
| const upsertAssistant = (chunk: string) => { | |
| assistantContent += chunk; | |
| setMessages((prev) => { | |
| const last = prev[prev.length - 1]; | |
| if (last?.role === "assistant") { | |
| return prev.map((m, i) => i === prev.length - 1 ? { ...m, content: assistantContent } : m); | |
| } | |
| return [...prev, { id: (Date.now() + 1).toString(), role: "assistant", content: assistantContent }]; | |
| }); | |
| }; | |
| const chatMessages = [...messages, userMessage].map((m) => ({ role: m.role, content: m.content })); | |
| await streamChat({ | |
| messages: chatMessages, | |
| model: selectedModel, | |
| onDelta: (chunk) => upsertAssistant(chunk), | |
| onDone: () => setIsLoading(false), | |
| onError: (error) => { | |
| setIsLoading(false); | |
| toast({ title: "Error", description: error, variant: "destructive" }); | |
| }, | |
| }); | |
| }; | |
| const handleNewChat = () => { | |
| setMessages([]); | |
| setIsLoading(false); | |
| }; | |
| const handleCommandAction = useCallback((action: string) => { | |
| if (action === "new-chat") { handleNewChat(); setActiveCategory("chat"); } | |
| else if (action.startsWith("nav-")) { setActiveCategory(action.replace("nav-", "") as SkillCategory); } | |
| else if (action === "search-web") { handleSend("Search the web for the latest tech news"); } | |
| }, []); | |
| const hasMessages = messages.length > 0; | |
| const modelDisplayName: Record<ModelType, string> = { | |
| "gemini-flash": "Gemini Flash", | |
| "perplexity-sonar": "Perplexity Sonar", | |
| "local": "LM Studio", | |
| }; | |
| const renderContent = () => { | |
| switch (activeCategory) { | |
| case "chat": | |
| return ( | |
| <div className="flex flex-col h-full"> | |
| <ChatHeader onNewChat={handleNewChat} selectedModel={selectedModel} onModelChange={handleModelChange} /> | |
| <main className="flex-1 overflow-y-auto"> | |
| {!hasMessages ? ( | |
| <WelcomeScreen onSuggestionClick={handleSend} /> | |
| ) : ( | |
| <div className="max-w-4xl mx-auto"> | |
| {messages.map((message, index) => ( | |
| <ChatMessage key={message.id} role={message.role} content={message.content} isLatest={index === messages.length - 1 && message.role === "assistant"} /> | |
| ))} | |
| <AnimatePresence> | |
| {isLoading && messages[messages.length - 1]?.role !== "assistant" && <TypingIndicator />} | |
| </AnimatePresence> | |
| <div ref={messagesEndRef} className="h-4" /> | |
| </div> | |
| )} | |
| </main> | |
| <footer className="border-t border-border bg-[hsl(var(--surface-glass))] backdrop-blur-sm p-3"> | |
| <div className="max-w-4xl mx-auto"> | |
| <ChatInput onSend={handleSend} isLoading={isLoading} /> | |
| </div> | |
| </footer> | |
| </div> | |
| ); | |
| case "financial": return <FinancePanel />; | |
| case "movies": return <MoviesPanel />; | |
| case "gaming": return <GamingPanel />; | |
| case "health": return <WeatherPanel />; | |
| case "music": return <MusicPanel />; | |
| case "knowledge": return <ResearchPanel />; | |
| case "settings": return <SettingsPanel />; | |
| default: return <SkillPanel category={activeCategory as any} />; | |
| } | |
| }; | |
| return ( | |
| <div className="flex h-screen bg-background overflow-hidden"> | |
| <CommandPalette onAction={handleCommandAction} /> | |
| <NexusSidebar activeCategory={activeCategory} onCategoryChange={setActiveCategory} /> | |
| <div className="flex-1 flex flex-col min-w-0"> | |
| <div className="flex-1 flex flex-col overflow-hidden">{renderContent()}</div> | |
| <StatusBar activeSkills={7} modelName={modelDisplayName[selectedModel]} /> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
Xet Storage Details
- Size:
- 6.18 kB
- Xet hash:
- b74221488492aac4817314f4aa46068e7ee6ee7dec46688cc0950d8f40b40fab
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.