"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { Controller, useForm } from "react-hook-form"; import { toast } from "sonner"; import * as z from "zod"; import { Button } from "@/components/ui/button"; import { Field, FieldGroup, FieldLabel, } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { useChat } from "@ai-sdk/react"; import { ArrowUp, Eraser, Loader2, Plus, PlusIcon, Square } from "lucide-react"; import { MessageWall } from "@/components/messages/message-wall"; import { ChatHeader } from "@/app/parts/chat-header"; import { ChatHeaderBlock } from "@/app/parts/chat-header"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { UIMessage } from "ai"; import { useEffect, useState, useRef } from "react"; import { AI_NAME, CLEAR_CHAT_TEXT, OWNER_NAME, WELCOME_MESSAGE } from "@/config"; import Image from "next/image"; import Link from "next/link"; const formSchema = z.object({ message: z .string() .min(1, "Message cannot be empty.") .max(2000, "Message must be at most 2000 characters."), }); const STORAGE_KEY = 'chat-messages'; type StorageData = { messages: UIMessage[]; durations: Record; }; const loadMessagesFromStorage = (): { messages: UIMessage[]; durations: Record } => { if (typeof window === 'undefined') return { messages: [], durations: {} }; try { const stored = localStorage.getItem(STORAGE_KEY); if (!stored) return { messages: [], durations: {} }; const parsed = JSON.parse(stored); return { messages: parsed.messages || [], durations: parsed.durations || {}, }; } catch (error) { console.error('Failed to load messages from localStorage:', error); return { messages: [], durations: {} }; } }; const saveMessagesToStorage = (messages: UIMessage[], durations: Record) => { if (typeof window === 'undefined') return; try { const data: StorageData = { messages, durations }; localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch (error) { console.error('Failed to save messages to localStorage:', error); } }; export default function Chat() { const [isClient, setIsClient] = useState(false); const [durations, setDurations] = useState>({}); const welcomeMessageShownRef = useRef(false); const stored = typeof window !== 'undefined' ? loadMessagesFromStorage() : { messages: [], durations: {} }; const [initialMessages] = useState(stored.messages); const { messages, sendMessage, status, stop, setMessages } = useChat({ messages: initialMessages, }); useEffect(() => { setIsClient(true); setDurations(stored.durations); setMessages(stored.messages); }, []); useEffect(() => { if (isClient) { saveMessagesToStorage(messages, durations); } }, [durations, messages, isClient]); const handleDurationChange = (key: string, duration: number) => { setDurations((prevDurations) => { const newDurations = { ...prevDurations }; newDurations[key] = duration; return newDurations; }); }; useEffect(() => { if (isClient && initialMessages.length === 0 && !welcomeMessageShownRef.current) { const welcomeMessage: UIMessage = { id: `welcome-${Date.now()}`, role: "assistant", parts: [ { type: "text", text: WELCOME_MESSAGE, }, ], }; setMessages([welcomeMessage]); saveMessagesToStorage([welcomeMessage], {}); welcomeMessageShownRef.current = true; } }, [isClient, initialMessages.length, setMessages]); const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { message: "", }, }); function onSubmit(data: z.infer) { sendMessage({ text: data.message }); form.reset(); } function clearChat() { const newMessages: UIMessage[] = []; const newDurations = {}; setMessages(newMessages); setDurations(newDurations); saveMessagesToStorage(newMessages, newDurations); toast.success("Chat cleared"); } return (
Logo

Chat with {AI_NAME}

{isClient ? ( <> {status === "submitted" && (
)} ) : (
)}
( Message
{ if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); form.handleSubmit(onSubmit)(); } }} /> {(status == "ready" || status == "error") && ( )} {(status == "streaming" || status == "submitted") && ( )}
)} />
© {new Date().getFullYear()} {OWNER_NAME} Terms of Use Powered by Ringel.AI
); }