Buckets:
| import { useState, useRef, useEffect } 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 { streamChat } from "@/lib/chat"; | |
| import { useToast } from "@/hooks/use-toast"; | |
| interface Message { | |
| id: string; | |
| role: "user" | "assistant"; | |
| content: string; | |
| } | |
| export const GrokChat = () => { | |
| const [messages, setMessages] = useState<Message[]>([]); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [selectedModel, setSelectedModel] = useState<ModelType>("gemini-flash"); | |
| 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); | |
| toast({ | |
| title: "Model Changed", | |
| description: `Switched to ${model === "local" ? "Local (LM Studio)" : "Cloud (Grok)"}`, | |
| }); | |
| }; | |
| const handleSend = async (content: string) => { | |
| 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 hasMessages = messages.length > 0; | |
| return ( | |
| <div className="flex flex-col h-screen bg-background"> | |
| <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-background/80 backdrop-blur-sm p-4"> | |
| <div className="max-w-4xl mx-auto"> | |
| <ChatInput onSend={handleSend} isLoading={isLoading} /> | |
| </div> | |
| </footer> | |
| </div> | |
| ); | |
| }; | |
Xet Storage Details
- Size:
- 3.95 kB
- Xet hash:
- 52d4f38d34b9019768e5e00377719b4b78e06e0bfe6972afcb32abed5d916085
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.