Stats history with year filter, folding and XLSX/CSV export; snippets, read-only links, commit suggestions, weekly review, offline copies, server speech-to-text, planning model, job reports; fal.ai health fix
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c08c6c7436
commit
388ca96419
31 files changed
+1518
-82
No files matched your search
@@ -0,0 +1,41 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user