From bd1202bb71326436ffdbe1f6bb3c1f29f3b16d9d Mon Sep 17 00:00:00 2001 From: HN05 Date: Sat, 18 Oct 2025 20:29:05 +0200 Subject: [PATCH 01/11] sivert fix --- .../src/app/components/chat/OddyChat.tsx | 174 +++++++++++------- whitewash/src/app/components/oddy/Oddy.tsx | 99 ++++------ whitewash/src/app/page.tsx | 44 ++++- 3 files changed, 183 insertions(+), 134 deletions(-) diff --git a/whitewash/src/app/components/chat/OddyChat.tsx b/whitewash/src/app/components/chat/OddyChat.tsx index 1f920f8..3d27822 100644 --- a/whitewash/src/app/components/chat/OddyChat.tsx +++ b/whitewash/src/app/components/chat/OddyChat.tsx @@ -1,7 +1,12 @@ "use client" +import Image, { type StaticImageData } from "next/image" +import oddy from "@/assets/images/oddy.png" +import frog from "@/assets/images/frog.png" +import prideFrog from "@/assets/images/pride-froggy.png" +import styles from "../oddy/Oddy.module.css" +import { useCallback, useEffect, useRef, useState } from "react" import { useStateArray } from "@/hooks/useStateArray" -import { useEffect, useRef, useState } from "react" type ChatMsg = { id: string @@ -9,14 +14,55 @@ type ChatMsg = { message: string } -export const OddyChat = () => { +type OddyChatProps = { + isFrog?: boolean + isPride?: boolean +} + +export const OddyChat = ({ + isFrog = false, + isPride = false, +}: OddyChatProps) => { + const [avatar, setAvatar] = useState(oddy) + const [name, setName] = useState<"Froggy" | "Oddy">( + isFrog ? "Froggy" : "Oddy", + ) + + const messagesEndRef = useRef(null) const [messages, addMessage] = useStateArray([]) const [input, setInput] = useState("") - const [agent, setAgent] = useState<"oddy" | "froggy">("froggy") - const [isPride, setIsPride] = useState(false) const ws = useRef(null) + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) + } + useEffect(() => { + requestAnimationFrame(() => { + messagesEndRef.current?.scrollIntoView({ + behavior: "smooth", + block: "end", + }) + }) + }, []) + useEffect(() => { + scrollToBottom() + }, [scrollToBottom]) + + // pick correct avatar on mount or when props change + useEffect(() => { + if (isFrog) { + setAvatar(isPride ? prideFrog : frog) + setName("Froggy") + } else { + setAvatar(oddy) + setName("Oddy") + } + }, [isFrog, isPride]) + + // connect websocket + useEffect(() => { + const agent = isFrog ? "froggy" : "oddy" const url = new URL("ws://localhost:4001") url.searchParams.set("agent", agent) url.searchParams.set("isPride", String(isPride)) @@ -24,90 +70,80 @@ export const OddyChat = () => { ws.current?.close() ws.current = new WebSocket(url.toString()) - ws.current.onopen = () => { - console.log("✅ Connected to WebSocket") - } - ws.current.onmessage = (event) => { if (event.data === "Successfully connected") return - addMessage({ id: crypto.randomUUID(), role: "oddy", message: String(event.data) }) + addMessage({ + id: crypto.randomUUID(), + role: "oddy", + message: String(event.data), + }) + scrollToBottom() } - ws.current.onerror = (err) => console.error("❌ WebSocket error:", err) - ws.current.onclose = () => console.log("🔌 WebSocket closed") - - return () => { - ws.current?.close() - } - }, [agent, isPride, addMessage]) + return () => ws.current?.close() + }, [isFrog, isPride, addMessage]) const handleSubmit = (e: React.FormEvent) => { e.preventDefault() const trimmed = input.trim() if (!trimmed) return - - const msg: ChatMsg = { id: crypto.randomUUID(), role: "user", message: trimmed } + const msg: ChatMsg = { + id: crypto.randomUUID(), + role: "user", + message: trimmed, + } addMessage(msg) ws.current?.send(trimmed) setInput("") } return ( -
-
- - -