feat: improve public chat UI and shared components

This commit is contained in:
CREDO23 2026-01-26 18:39:59 +02:00
parent 37adc54d6a
commit ee65e1377f
14 changed files with 403 additions and 275 deletions

View file

@ -1,34 +0,0 @@
import { formatDistanceToNow } from "date-fns";
import Image from "next/image";
import Link from "next/link";
interface PublicChatHeaderProps {
title: string;
createdAt: string;
}
export function PublicChatHeader({ title, createdAt }: PublicChatHeaderProps) {
const timeAgo = formatDistanceToNow(new Date(createdAt), { addSuffix: true });
return (
<header className="sticky top-0 z-10 -mx-4 mb-4 border-b bg-background/95 px-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="mx-auto flex max-w-(--thread-max-width) items-center justify-between py-3">
<div className="flex items-center gap-3">
<Link href="/" className="shrink-0">
<Image
src="/surfsenselogo.png"
alt="SurfSense"
width={32}
height={32}
className="rounded"
/>
</Link>
<div className="min-w-0">
<h1 className="truncate font-medium">{title}</h1>
<p className="text-xs text-muted-foreground">{timeAgo}</p>
</div>
</div>
</div>
</header>
);
}

View file

@ -2,6 +2,7 @@
import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { Loader2 } from "lucide-react";
import { Navbar } from "@/components/homepage/navbar";
import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast";
import { LinkPreviewToolUI } from "@/components/tool-ui/link-preview";
@ -9,7 +10,6 @@ import { ScrapeWebpageToolUI } from "@/components/tool-ui/scrape-webpage";
import { usePublicChat } from "@/hooks/use-public-chat";
import { usePublicChatRuntime } from "@/hooks/use-public-chat-runtime";
import { PublicChatFooter } from "./public-chat-footer";
import { PublicChatHeader } from "./public-chat-header";
import { PublicThread } from "./public-thread";
interface PublicChatViewProps {
@ -22,37 +22,43 @@ export function PublicChatView({ shareToken }: PublicChatViewProps) {
if (isLoading) {
return (
<div className="flex h-screen items-center justify-center">
<Loader2 className="size-8 animate-spin text-muted-foreground" />
</div>
<main className="min-h-screen bg-linear-to-b from-gray-50 to-gray-100 text-gray-900 dark:from-black dark:to-gray-900 dark:text-white overflow-x-hidden">
<Navbar />
<div className="flex h-screen items-center justify-center">
<Loader2 className="size-8 animate-spin text-muted-foreground" />
</div>
</main>
);
}
if (error || !data) {
return (
<div className="flex h-screen flex-col items-center justify-center gap-4 px-4 text-center">
<h1 className="text-2xl font-semibold">Chat not found</h1>
<p className="text-muted-foreground">
This chat may have been removed or is no longer public.
</p>
</div>
<main className="min-h-screen bg-linear-to-b from-gray-50 to-gray-100 text-gray-900 dark:from-black dark:to-gray-900 dark:text-white overflow-x-hidden">
<Navbar />
<div className="flex h-screen flex-col items-center justify-center gap-4 px-4 text-center">
<h1 className="text-2xl font-semibold">Chat not found</h1>
<p className="text-muted-foreground">
This chat may have been removed or is no longer public.
</p>
</div>
</main>
);
}
return (
<AssistantRuntimeProvider runtime={runtime}>
{/* Tool UIs for rendering tool results */}
<GeneratePodcastToolUI />
<LinkPreviewToolUI />
<DisplayImageToolUI />
<ScrapeWebpageToolUI />
<main className="min-h-screen bg-linear-to-b from-gray-50 to-gray-100 text-gray-900 dark:from-black dark:to-gray-900 dark:text-white overflow-x-hidden">
<Navbar />
<AssistantRuntimeProvider runtime={runtime}>
{/* Tool UIs for rendering tool results */}
<GeneratePodcastToolUI />
<LinkPreviewToolUI />
<DisplayImageToolUI />
<ScrapeWebpageToolUI />
<div className="flex h-screen flex-col bg-background">
<PublicThread
header={<PublicChatHeader title={data.thread.title} createdAt={data.thread.created_at} />}
footer={<PublicChatFooter shareToken={shareToken} />}
/>
</div>
</AssistantRuntimeProvider>
<div className="flex h-screen flex-col pt-16">
<PublicThread footer={<PublicChatFooter shareToken={shareToken} />} />
</div>
</AssistantRuntimeProvider>
</main>
);
}

View file

@ -12,10 +12,8 @@ import { type FC, type ReactNode, useState } from "react";
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { cn } from "@/lib/utils";
interface PublicThreadProps {
header?: ReactNode;
footer?: ReactNode;
}
@ -23,7 +21,7 @@ interface PublicThreadProps {
* Read-only thread component for public chat viewing.
* No composer, no edit capabilities - just message display.
*/
export const PublicThread: FC<PublicThreadProps> = ({ header, footer }) => {
export const PublicThread: FC<PublicThreadProps> = ({ footer }) => {
return (
<ThreadPrimitive.Root
className="aui-root aui-thread-root @container flex h-full min-h-0 flex-col bg-background"
@ -32,8 +30,6 @@ export const PublicThread: FC<PublicThreadProps> = ({ header, footer }) => {
}}
>
<ThreadPrimitive.Viewport className="aui-thread-viewport relative flex flex-1 min-h-0 flex-col overflow-y-auto px-4 pt-4">
{header}
<ThreadPrimitive.Messages
components={{
UserMessage: PublicUserMessage,
@ -159,10 +155,8 @@ const PublicAssistantActionBar: FC = () => {
return (
<ActionBarPrimitive.Root
autohide="not-last"
className={cn(
"aui-assistant-action-bar-root -ml-1 flex gap-1 text-muted-foreground",
"opacity-0 group-hover:opacity-100 transition-opacity"
)}
autohideFloat="single-branch"
className="aui-assistant-action-bar-root -ml-1 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm"
>
<ActionBarPrimitive.Copy asChild>
<TooltipIconButton tooltip="Copy">