techprotrade's picture
download
raw
6.18 kB
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.