feat: history view in the header and dialog modal (#456)

This commit is contained in:
MaheshtheDev 2025-10-06 16:29:39 +00:00
parent e86104b645
commit 4a23ab1326
2 changed files with 150 additions and 8 deletions

View file

@ -12,6 +12,8 @@ import {
LogOut,
WaypointsIcon,
Gauge,
HistoryIcon,
Trash2,
} from "lucide-react"
import {
DropdownMenuContent,
@ -26,20 +28,57 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/components/tooltip"
import { useAuth } from "@lib/auth-context"
import { ConnectAIModal } from "./connect-ai-modal"
import { useTheme } from "next-themes"
import { cn } from "@lib/utils"
import { usePathname, useRouter } from "next/navigation"
import { MCPIcon } from "./menu"
import { authClient } from "@lib/auth"
import { analytics } from "@/lib/analytics"
import { useGraphModal, usePersistentChat } from "@/stores"
import { useGraphModal, usePersistentChat, useProject } from "@/stores"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@ui/components/dialog"
import { ScrollArea } from "@ui/components/scroll-area"
import { formatDistanceToNow } from "date-fns"
import { cn } from "@lib/utils"
import { useMemo, useState } from "react"
export function Header({ onAddMemory }: { onAddMemory?: () => void }) {
const { user } = useAuth()
const { theme, setTheme } = useTheme()
const router = useRouter()
const { setIsOpen: setGraphModalOpen } = useGraphModal()
const { getCurrentChat } = usePersistentChat()
const {
getCurrentChat,
conversations,
currentChatId,
setCurrentChatId,
deleteConversation,
} = usePersistentChat()
const { selectedProject } = useProject()
const pathname = usePathname()
const [isDialogOpen, setIsDialogOpen] = useState(false)
const sorted = useMemo(() => {
return [...conversations].sort((a, b) =>
a.lastUpdated < b.lastUpdated ? 1 : -1,
)
}, [conversations])
function handleNewChat() {
analytics.newChatStarted()
const newId = crypto.randomUUID()
setCurrentChatId(newId)
router.push(`/chat/${newId}`)
setIsDialogOpen(false)
}
function formatRelativeTime(isoString: string): string {
return formatDistanceToNow(new Date(isoString), { addSuffix: true })
}
const handleSignOut = () => {
analytics.userSignedOut()
@ -87,6 +126,106 @@ export function Header({ onAddMemory }: { onAddMemory?: () => void }) {
c
</span>
</Button>
<Dialog
open={isDialogOpen}
onOpenChange={(open) => {
setIsDialogOpen(open)
if (open) {
analytics.chatHistoryViewed()
}
}}
>
<Tooltip>
<TooltipTrigger asChild>
<DialogTrigger asChild>
<Button variant="ghost" size="sm">
<HistoryIcon className="h-4 w-4" />
</Button>
</DialogTrigger>
</TooltipTrigger>
<TooltipContent>
<p>Chat History</p>
</TooltipContent>
</Tooltip>
<DialogContent className="sm:max-w-lg">
<DialogHeader className="pb-4 border-b rounded-t-lg">
<DialogTitle className="">Conversations</DialogTitle>
<DialogDescription>
Project{" "}
<span className="font-mono font-medium">
{selectedProject}
</span>
</DialogDescription>
</DialogHeader>
<ScrollArea className="max-h-96">
<div className="flex flex-col gap-1">
{sorted.map((c) => {
const isActive = c.id === currentChatId
return (
<button
key={c.id}
type="button"
onClick={() => {
setCurrentChatId(c.id)
router.push(`/chat/${c.id}`)
setIsDialogOpen(false)
}}
className={cn(
"flex items-center justify-between rounded-md px-3 py-2 outline-none w-full text-left",
"transition-colors",
isActive ? "bg-primary/10" : "hover:bg-muted",
)}
aria-current={isActive ? "true" : undefined}
>
<div className="min-w-0">
<div className="flex items-center gap-2">
<span
className={cn(
"text-sm font-medium truncate",
isActive ? "text-foreground" : undefined,
)}
>
{c.title || "Untitled Chat"}
</span>
</div>
<div className="text-xs text-muted-foreground">
Last updated {formatRelativeTime(c.lastUpdated)}
</div>
</div>
<Button
type="button"
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation()
analytics.chatDeleted()
deleteConversation(c.id)
}}
aria-label="Delete conversation"
>
<Trash2 className="size-4 text-muted-foreground" />
</Button>
</button>
)
})}
{sorted.length === 0 && (
<div className="text-xs text-muted-foreground px-3 py-2">
No conversations yet
</div>
)}
</div>
</ScrollArea>
<Button
variant="outline"
size="lg"
className="w-full border-dashed"
onClick={handleNewChat}
>
<Plus className="size-4 mr-1" /> New Conversation
</Button>
</DialogContent>
</Dialog>
<Tooltip>
<TooltipTrigger asChild>
<Button

View file

@ -10,7 +10,6 @@ import {
ChevronDown,
ChevronRight,
Copy,
Plus,
RotateCcw,
X,
} from "lucide-react"
@ -221,15 +220,20 @@ export function ChatMessages() {
const [input, setInput] = useState("")
const [selectedModel, setSelectedModel] = useState<
"gpt-5" | "claude-sonnet-4.5" | "gemini-2.5-pro"
>((sessionStorage.getItem(storageKey) as "gpt-5" | "claude-sonnet-4.5" | "gemini-2.5-pro" || "gemini-2.5-pro") || "gemini-2.5-pro")
>(
(sessionStorage.getItem(storageKey) as
| "gpt-5"
| "claude-sonnet-4.5"
| "gemini-2.5-pro") ||
"gemini-2.5-pro" ||
"gemini-2.5-pro",
)
const activeChatIdRef = useRef<string | null>(null)
const shouldGenerateTitleRef = useRef<boolean>(false)
const hasRunInitialMessageRef = useRef<boolean>(false)
const { setDocumentIds } = useGraphHighlights()
console.log("selectedModel", selectedModel)
const { messages, sendMessage, status, stop, setMessages, id, regenerate } =
useChat({
id: currentChatId ?? undefined,
@ -277,7 +281,6 @@ export function ChatMessages() {
savedModel &&
["gpt-5", "claude-sonnet-4.5", "gemini-2.5-pro"].includes(savedModel)
) {
console.log("savedModel", savedModel)
setSelectedModel(savedModel)
}
}