Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 9 additions & 21 deletions app/chat/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ import { headers } from "next/headers";
import { getUserChats } from "@/lib/data/chat";
import { redirect } from "next/navigation";

// Sidebar width configuration matching the original ChatInterface
const SIDEBAR_WIDTH = "300px";

export default async function ChatLayout({
children,
}: {
Expand All @@ -26,25 +23,16 @@ export default async function ChatLayout({
const initialChats = await getUserChats(session.user.id);

return (
<SidebarProvider
defaultOpen
style={
{
"--sidebar-width": SIDEBAR_WIDTH,
} as React.CSSProperties
}
>
<SidebarProvider defaultOpen>
<div className="flex h-dvh min-h-0 w-full overflow-hidden bg-background">
<div className="relative h-full w-(--sidebar-width) shrink-0">
<ChatSidebar
chats={initialChats}
currentUser={{
name: session.user.name,
email: session.user.email,
image: session.user.image,
}}
/>
</div>
<ChatSidebar
chats={initialChats}
currentUser={{
name: session.user.name,
email: session.user.email,
image: session.user.image,
}}
/>
<SidebarInset className="min-h-0 min-w-0 flex-1 overflow-hidden">
{children}
</SidebarInset>
Expand Down
60 changes: 35 additions & 25 deletions app/chat/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,44 @@ import { Skeleton } from "@/components/ui/skeleton";
export default function Loading() {
return (
<div className="flex h-dvh min-h-0 w-full overflow-hidden bg-background">
<div className="w-75 shrink-0 border-r border-sidebar-border hidden md:flex flex-col">
<div className="p-4 border-b border-sidebar-border">
<Skeleton className="h-10 w-full rounded-md" />
</div>
<div className="p-4 flex flex-col gap-2">
<Skeleton className="h-12 w-full rounded-md bg-sidebar-accent/50" />
<Skeleton className="h-12 w-full rounded-md bg-sidebar-accent/50" />
<Skeleton className="h-12 w-full rounded-md bg-sidebar-accent/50" />
<Skeleton className="h-12 w-full rounded-md bg-sidebar-accent/50" />
</div>
</div>
<div className="flex-1 flex flex-col">
<div className="h-14 border-b border-border bg-card px-4 py-3 flex items-center">
<Skeleton className="h-6 w-48 rounded-md" />
</div>
<div className="flex-1 p-6 flex flex-col gap-6">
<div className="flex gap-4">
<Skeleton className="size-8 shrink-0 rounded-sm" />
<Skeleton className="h-16 w-[80%] rounded-lg" />
<div className="flex min-h-0 flex-1 flex-col bg-background">
<div className="border-b border-border bg-card px-4 py-3 md:px-6">
<div className="flex items-center justify-between gap-3">
<div className="min-w-0 space-y-1">
<Skeleton className="h-6 w-36 sm:w-56 rounded-md" />
<Skeleton className="h-4 w-24 sm:w-40 rounded-md" />
</div>
<Skeleton className="h-10 w-24 sm:w-40 rounded-md" />
</div>
Comment thread
alvinsjoy marked this conversation as resolved.
<div className="flex gap-4 flex-row-reverse">
<Skeleton className="size-8 shrink-0 rounded-sm" />
<Skeleton className="h-12 w-[60%] rounded-lg" />
</div>

<div className="min-h-0 flex-1 overflow-y-auto px-4 pb-44 md:px-6 md:pb-44">
<div className="mx-auto flex w-full max-w-3xl flex-col gap-6 py-4">
<div className="flex flex-row-reverse gap-4">
<Skeleton className="size-8 shrink-0 rounded-sm" />
<Skeleton className="h-12 w-[60%] rounded-lg" />
</div>
<div className="flex gap-4">
<Skeleton className="size-8 shrink-0 rounded-sm" />
<Skeleton className="h-16 w-[80%] rounded-lg" />
</div>
<div className="flex flex-row-reverse gap-4">
<Skeleton className="size-8 shrink-0 rounded-sm" />
<Skeleton className="h-12 w-[60%] rounded-lg" />
</div>
<div className="flex gap-4">
<Skeleton className="size-8 shrink-0 rounded-sm" />
<Skeleton className="h-16 w-[80%] rounded-lg" />
</div>
</div>
</div>
<div className="h-14 border-t border-border bg-card px-4 py-2 flex items-center gap-2">
<Skeleton className="h-10 flex-1 rounded-md" />
<Skeleton className="size-10 rounded-md shrink-0" />

<div className="sticky bottom-0 z-20 bg-background px-2 pb-3 pt-2 backdrop-blur md:px-4 md:pb-3">
<Skeleton className="mx-auto w-full max-w-3xl min-h-25 gap-0 rounded-4xl border-0 bg-card py-3 shadow-xl"></Skeleton>

<div className="px-4 pt-3 md:px-6">
<Skeleton className="mx-auto h-3 w-64 rounded-md" />
</div>
</div>
</div>
</div>
Expand Down
Binary file removed app/favicon.ico
Binary file not shown.
21 changes: 5 additions & 16 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type React from "react";
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { ThemeProvider } from "@/components/theme-provider";
import { TooltipProvider } from "@/components/ui/tooltip";
import "./globals.css";
import { Toaster } from "@/components/ui/sonner";

Expand All @@ -13,21 +14,9 @@ export const metadata: Metadata = {
description: "Intelligent chatbot with document understanding capabilities",
generator: "Next.js",
icons: {
icon: [
{
url: "/icon-light-32x32.png",
media: "(prefers-color-scheme: light)",
},
{
url: "/icon-dark-32x32.png",
media: "(prefers-color-scheme: dark)",
},
{
url: "/icon.svg",
type: "image/svg+xml",
},
],
apple: "/apple-icon.png",
icon: "/icon.png",
shortcut: "/icon.png",
apple: "/icon.png",
},
};

Expand All @@ -45,7 +34,7 @@ export default function RootLayout({
enableSystem
disableTransitionOnChange
>
{children}
<TooltipProvider>{children}</TooltipProvider>
</ThemeProvider>
<Toaster />
</body>
Expand Down
87 changes: 24 additions & 63 deletions components/chat-area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { SidebarTrigger } from "@/components/ui/sidebar";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { cn } from "@/lib/utils";
import {
formatChatLongDate,
formatChatTime,
getStableDayKey,
parseTimestampToMs,
} from "@/lib/date-format";
// Import shared types
import type { Chat as ChatModel, Message } from "@/lib/types";
import {
Expand All @@ -19,29 +26,6 @@ import {
} from "@/lib/actions/chat";
import { toast } from "sonner";

const STABLE_LOCALE = "en-IN";
const STABLE_TIME_ZONE = "Asia/Kolkata";

const stableTimeFormatter = new Intl.DateTimeFormat(STABLE_LOCALE, {
hour: "2-digit",
minute: "2-digit",
timeZone: STABLE_TIME_ZONE,
});

const stableDateFormatter = new Intl.DateTimeFormat(STABLE_LOCALE, {
year: "numeric",
month: "long",
day: "numeric",
timeZone: STABLE_TIME_ZONE,
});

const stableDayKeyFormatter = new Intl.DateTimeFormat(STABLE_LOCALE, {
year: "numeric",
month: "2-digit",
day: "2-digit",
timeZone: STABLE_TIME_ZONE,
});

interface ChatAreaProps {
currentChat: ChatModel | undefined;
messages: Message[];
Expand Down Expand Up @@ -84,32 +68,6 @@ export function ChatArea({
nameParts.length > 1 ? nameParts[nameParts.length - 1][0] : "";
const userInitials = `${firstInitial}${lastInitial}`.toUpperCase();

const parseTimestamp = (timestamp: string): number | null => {
const ms = Date.parse(timestamp);
return Number.isNaN(ms) ? null : ms;
};

const getStableDayKey = (timestamp: number): string => {
const parts = stableDayKeyFormatter.formatToParts(new Date(timestamp));
const year = parts.find((part) => part.type === "year")?.value;
const month = parts.find((part) => part.type === "month")?.value;
const day = parts.find((part) => part.type === "day")?.value;

if (!year || !month || !day) {
return "";
}

return `${year}-${month}-${day}`;
};

const formatTime = (timestamp: number): string => {
return stableTimeFormatter.format(new Date(timestamp));
};

const formatLongDate = (timestamp: number): string => {
return stableDateFormatter.format(new Date(timestamp));
};

useEffect(() => {
const container = messagesContainerRef.current;
if (!container) return;
Expand Down Expand Up @@ -260,15 +218,18 @@ export function ChatArea({
<div className="flex min-h-0 flex-1 flex-col bg-background">
<div className="border-b border-border bg-card px-4 py-3 md:px-6">
<div className="flex items-center justify-between gap-3">
<div>
<h1 className="text-xl font-semibold text-card-foreground">
{currentChat.title}
</h1>
<p className="mt-0.5 text-sm text-muted-foreground">
{currentChat.documentCount === 0
? "No documents uploaded"
: `${currentChat.documentCount} ${currentChat.documentCount === 1 ? "document" : "documents"} uploaded`}
</p>
<div className="flex items-center gap-2">
<SidebarTrigger className="md:hidden" />
<div>
<h1 className="text-xl font-semibold text-card-foreground">
{currentChat.title}
</h1>
<p className="mt-0.5 text-sm text-muted-foreground">
{currentChat.documentCount === 0
? "No documents uploaded"
: `${currentChat.documentCount} ${currentChat.documentCount === 1 ? "document" : "documents"} uploaded`}
</p>
</div>
</div>

<Button
Expand Down Expand Up @@ -340,12 +301,12 @@ export function ChatArea({
{localMessages.map((message, index, allMessages) => {
const prevMessage = allMessages[index - 1];
const nextMessage = allMessages[index + 1];
const ts = parseTimestamp(message.timestamp);
const ts = parseTimestampToMs(message.timestamp);
const prevTs = prevMessage
? parseTimestamp(prevMessage.timestamp)
? parseTimestampToMs(prevMessage.timestamp)
: null;
const nextTs = nextMessage
? parseTimestamp(nextMessage.timestamp)
? parseTimestampToMs(nextMessage.timestamp)
: null;

const sameRoleAsNext = message.role === nextMessage?.role;
Expand All @@ -360,7 +321,7 @@ export function ChatArea({
<div className="my-2 flex items-center gap-1">
<Separator className="flex-1" />
<span className="min-w-max text-xs font-semibold text-muted-foreground">
{formatLongDate(ts)}
{formatChatLongDate(ts)}
</span>
<Separator className="flex-1" />
</div>
Expand Down Expand Up @@ -416,7 +377,7 @@ export function ChatArea({
{message.role === "assistant" ? "AI" : "You"}
</span>
{ts !== null ? (
<span>{formatTime(ts)}</span>
<span>{formatChatTime(ts)}</span>
) : (
<span>{message.timestamp}</span>
)}
Expand Down
Loading