Move device ID logic from @multica/store (Zustand persist) into a simple useDeviceId hook in the web app. SSR returns empty string, client reads/writes localStorage directly — no hydration mismatch, no suppressHydrationWarning needed. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
import { useRef } from "react";
|
|
import { SidebarTrigger } from "@multica/ui/components/ui/sidebar";
|
|
import { ChatInput } from "@multica/ui/components/chat-input";
|
|
import { MemoizedMarkdown } from "@multica/ui/components/markdown";
|
|
import { useMessages } from "../hooks/use-messages";
|
|
import { useDeviceId } from "../hooks/use-device-id";
|
|
import { useScrollFade } from "../hooks/use-scroll-fade";
|
|
import { cn } from "@multica/ui/lib/utils";
|
|
|
|
export function Chat() {
|
|
const deviceId = useDeviceId();
|
|
const messages = useMessages();
|
|
const mainRef = useRef<HTMLElement>(null);
|
|
const fadeStyle = useScrollFade(mainRef);
|
|
|
|
return (
|
|
<div className="h-dvh flex flex-col overflow-hidden w-full">
|
|
<header className="flex items-center gap-2 p-2">
|
|
<SidebarTrigger />
|
|
<span className="text-xs text-muted-foreground font-mono">
|
|
{deviceId ? deviceId.slice(0, 8) : "\u00A0"}
|
|
</span>
|
|
</header>
|
|
|
|
<main ref={mainRef} className="flex-1 overflow-y-auto min-h-0" style={fadeStyle}>
|
|
<div className="px-4 py-6 space-y-6 max-w-4xl mx-auto">
|
|
{messages.map((msg) => (
|
|
<div
|
|
key={msg.id}
|
|
className={cn(
|
|
"flex",
|
|
msg.role === "user" ? "justify-end" : "justify-start"
|
|
)}
|
|
>
|
|
<div
|
|
className={cn(
|
|
"max-w-[85%] rounded-2xl px-4 py-3",
|
|
msg.role === "user"
|
|
? "bg-muted"
|
|
: ""
|
|
)}
|
|
>
|
|
<MemoizedMarkdown mode="minimal" id={msg.id}>
|
|
{msg.content}
|
|
</MemoizedMarkdown>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</main>
|
|
|
|
<footer className="w-full p-2 pt-1 max-w-4xl mx-auto">
|
|
<ChatInput />
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|