Spaces:
Running
Running
| import { useState, useEffect } from "react"; | |
| import { motion } from "motion/react"; | |
| import { Bot } from "lucide-react"; | |
| function useLoadingMessages() { | |
| const [messages, setMessages] = useState<string[]>([]); | |
| useEffect(() => { | |
| fetch("/loading-messages.yaml") | |
| .then((r) => r.text()) | |
| .then((text) => { | |
| const parsed = text | |
| .split("\n") | |
| .filter((l) => l.trimStart().startsWith("- ")) | |
| .map((l) => l.replace(/^\s*- /, "").trim()) | |
| .filter(Boolean); | |
| if (parsed.length > 0) setMessages(parsed); | |
| }) | |
| .catch(() => {}); | |
| }, []); | |
| return messages; | |
| } | |
| export default function TypingIndicator() { | |
| const loadingMessages = useLoadingMessages(); | |
| const [phraseIndex, setPhraseIndex] = useState(0); | |
| useEffect(() => { | |
| if (loadingMessages.length === 0) return; | |
| setPhraseIndex(Math.floor(Math.random() * loadingMessages.length)); | |
| const id = setInterval(() => { | |
| setPhraseIndex((prev) => { | |
| let next: number; | |
| do { | |
| next = Math.floor(Math.random() * loadingMessages.length); | |
| } while (loadingMessages.length > 1 && next === prev); | |
| return next; | |
| }); | |
| }, 300); | |
| return () => clearInterval(id); | |
| }, [loadingMessages]); | |
| return ( | |
| <motion.div | |
| className="flex gap-3 px-4 py-2" | |
| initial={{ opacity: 0, y: 8 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| transition={{ duration: 0.25, ease: "easeOut" }} | |
| > | |
| <div className="w-7 h-7 rounded-full bg-gradient-to-br from-brand-green-light to-brand-green flex-shrink-0 flex items-center justify-center shadow-sm"> | |
| <Bot className="h-4 w-4 text-white" /> | |
| </div> | |
| <div className="bg-white border border-neutral-100 rounded-2xl rounded-tl-sm shadow-sm px-4 py-3 min-w-[6rem]"> | |
| {loadingMessages.length > 0 ? ( | |
| <span className="text-sm text-neutral-400 font-medium"> | |
| {loadingMessages[phraseIndex]}… | |
| </span> | |
| ) : ( | |
| <div className="flex items-center gap-1.5 animate-pulse"> | |
| <span className="w-2 h-2 rounded-full bg-neutral-300" /> | |
| <span className="w-2 h-2 rounded-full bg-neutral-300" style={{ animationDelay: "150ms" }} /> | |
| <span className="w-2 h-2 rounded-full bg-neutral-300" style={{ animationDelay: "300ms" }} /> | |
| </div> | |
| )} | |
| </div> | |
| </motion.div> | |
| ); | |
| } | |