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