Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
42 lines
1.4 KiB
TypeScript
42 lines
1.4 KiB
TypeScript
import { useEffect, useState } from "react";
|
||
import { get } from "../api";
|
||
import { fmtMsgTime, useT } from "../i18n";
|
||
import { Markdown } from "./Markdown";
|
||
|
||
interface Shared {
|
||
title: string;
|
||
messages: { id: string; role: "user" | "assistant"; text: string; created_at: number }[];
|
||
}
|
||
|
||
/** A chat opened through a read-only link (no account needed). */
|
||
export function PublicChat({ token }: { token: string }) {
|
||
const t = useT();
|
||
const [chat, setChat] = useState<Shared>();
|
||
const [error, setError] = useState<string>();
|
||
useEffect(() => {
|
||
get<Shared>(`/public/chat/${encodeURIComponent(token)}`)
|
||
.then((c) => (setChat(c), (document.title = `${c.title || "Chat"} – CodeCraft`)))
|
||
.catch((e) => setError(e.message));
|
||
}, [token]);
|
||
if (error) return <div className="empty">{error}</div>;
|
||
if (!chat) return <div className="empty">{t("common.loading")}</div>;
|
||
return (
|
||
<div className="public-chat">
|
||
<header>
|
||
<h1>{chat.title || "Chat"}</h1>
|
||
<div className="hint">{t("link.readOnly")}</div>
|
||
</header>
|
||
<div className="chat-scroll">
|
||
{chat.messages.map((m) => (
|
||
<div key={m.id} className={m.role === "user" ? "bubble user-msg" : "bubble agent"}>
|
||
<Markdown text={m.text} />
|
||
<div className="msg-foot">
|
||
<span className="hint">{fmtMsgTime(m.created_at)}</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|