"use client"; import { Spinner } from "@/components/ui/spinner"; import "../Flashcard/cards.css"; import { useState, useEffect, useTransition, useRef, useCallback } from "react"; import { WordData } from "@/zoom/logic/types"; import { fetchWordsByToneAndSyllables, mutateToneSelection, } from "@/actions/tones"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Skeleton } from "@/components/ui/skeleton"; // For loading state import { MutationOrder, PhoneticData, ToneQuery } from "@/lib/types/phonetics"; import { ProsodySyllable } from "@/lib/types/cards"; import { ArrowLeft, ArrowRight, Volume2, FlipHorizontal2 } from "lucide-react"; function getColorByTone(tone: string): string { if (tone === "mid") return "blue"; if (tone === "low") return "green"; if (tone === "falling") return "gold"; if (tone === "high") return "purple"; if (tone === "rising") return "black"; else return "black"; } // Helper to display tones prominently const ProminentToneDisplay = ({ word }: { word: any }) => { const [isLoading, setLoading] = useState(false); const tones: string[] = word.tone_sequence.split(","); const syls: string[] = word.syl_seq.split(","); const [isPending, startTransition] = useTransition(); function mutateWord(idx: number) { console.log("changing", idx); const mutationOrder: MutationOrder = syls.map((s, i) => { if (idx === i) return { change: tones[idx]! }; else return { keep: syls[i]! }; }); console.log("hey hey", word); startTransition(async () => { const words = await mutateToneSelection(mutationOrder); console.log({ words }); // setCurrentWord(word); }); } // playing audio // const sourceRef = useRef(null); const audioRef = useRef(null); async function playAudio() { setLoading(true); // const audioContext = new (window.AudioContext || // (window as any).webkitAudioContext)(); // const response = await fetch(audioUrl); // const arrayBuffer = await response.arrayBuffer(); // const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); // if (audioContext && audioBuffer) { // setLoading(false); // const source = audioContext.createBufferSource(); // source.buffer = audioBuffer; // source.connect(audioContext.destination); // source.start(); // sourceRef.current = source; // } const res = await fetch(`/api/tts?word=${word.spelling}&lang=thai`); const audioBlob = await res.blob(); const audioURL = URL.createObjectURL(audioBlob); setLoading(false); if (audioRef.current) { audioRef.current.src = audioURL; audioRef.current.play(); } } useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === " ") { e.preventDefault(); playAudio(); } }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }, [playAudio]); return (

{syls.map((syl: string, idx: number) => ( mutateWord(idx)} style={{ color: getColorByTone(tones[idx]!) }} className="cursor-pointer hover:text-gray-700" > {syl} ))}

{word.ipa}

{(isPending || isLoading) && }
); }; export default function ToneSelectorClient({ initialData, initialTones, }: { initialData: PhoneticData[]; initialTones: ToneQuery; }) { console.log({ initialData }); const [data, setData] = useState(initialData); const [currentIdx, setCurrentIdx] = useState(0); const [isLoading, startTransition] = useTransition(); const [selectedTones, setTones] = useState(initialTones); const goPrev = useCallback(() => { setCurrentIdx((i) => (i === 0 ? 0 : i - 1)); }, []); const goNext = useCallback(() => { setCurrentIdx((i) => (i === data.length - 1 ? data.length - 1 : i + 1)); }, [data.length]); useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === "ArrowLeft") { e.preventDefault(); goPrev(); } else if (e.key === "ArrowRight") { e.preventDefault(); goNext(); } }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }, [goPrev, goNext]); const handleFetch = () => { startTransition(async () => { const words = await fetchWordsByToneAndSyllables(selectedTones); setData(words); }); }; return (
); } type IProps = { isLoading: boolean; currentWord: PhoneticData; goPrev: () => void; goNext: () => void; }; function Inner({ isLoading, currentWord, goPrev, goNext }: IProps) { const [isFlipped, setIsFlipped] = useState(false); const toggleFlip = () => setIsFlipped((f) => !f); if (isLoading) { return ( ); } if (!currentWord) { return ( No Word Found

Could not find a Thai word matching your criteria. Try different selections.

); } console.log({ currentWord }); return (
{/* Front */}

Current Word

{/* Back */}

Details

{/* Placeholder for user-provided data */}
              {JSON.stringify(currentWord, null, 2)}
            
Back of card – replace with your data
); } type ToneFormProps = { isLoading: boolean; handleFetch: (tones: ToneQuery) => void; selectedTones: ToneQuery; setTones: React.Dispatch>; }; function ToneForm({ selectedTones, setTones, isLoading, handleFetch, }: ToneFormProps) { const thaiTones = [ { value: "mid", label: "1 (Mid)" }, { value: "low", label: "2 (Low)" }, { value: "falling", label: "3 (Falling)" }, { value: "high", label: "4 (High)" }, { value: "rising", label: "5 (Rising)" }, ]; const [syllableCount, setSyllableCount] = useState(2); const decrSyl = useCallback(() => { setSyllableCount((s) => (s <= 1 ? 1 : s - 1)); }, []); const incrSyl = useCallback(() => { setSyllableCount((s) => (s >= 5 ? 5 : s + 1)); }, []); useEffect(() => { // Adjust selectedTones array length when syllableCount changes setTones((prevTones) => { const newTones = Array(syllableCount).fill(null); for (let i = 0; i < Math.min(prevTones.length, syllableCount); i++) { newTones[i] = prevTones[i]; } return newTones; }); }, [syllableCount]); const handleSyllableCountChange = (value: string) => { const count = parseInt(value, 10); if (!isNaN(count) && count > 0 && count <= 5) { // Max 5 syllables for simplicity setSyllableCount(count); } }; useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === "ArrowUp") { e.preventDefault(); incrSyl(); } else if (e.key === "ArrowDown") { e.preventDefault(); decrSyl(); } }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }, [incrSyl, decrSyl]); const handleToneChange = (syllableIndex: number, value: string) => { const tone = value === "any" ? null : value; setTones((prevTones) => { const newTones = [...prevTones]; newTones[syllableIndex] = tone; return newTones; }); }; return ( Thai Tone Explorer Select syllable count and tones to find Thai words.
{Array.from({ length: syllableCount }).map((_, index) => (
))}
); }