Skip to content

Commit 1a88f4b

Browse files
committed
feat: history view in the header and dialog modal
1 parent e86104b commit 1a88f4b

File tree

2 files changed

+150
-8
lines changed

2 files changed

+150
-8
lines changed

apps/web/components/header.tsx

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
LogOut,
1313
WaypointsIcon,
1414
Gauge,
15+
HistoryIcon,
16+
Trash2,
1517
} from "lucide-react"
1618
import {
1719
DropdownMenuContent,
@@ -26,20 +28,57 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/components/tooltip"
2628
import { useAuth } from "@lib/auth-context"
2729
import { ConnectAIModal } from "./connect-ai-modal"
2830
import { useTheme } from "next-themes"
29-
import { cn } from "@lib/utils"
3031
import { usePathname, useRouter } from "next/navigation"
3132
import { MCPIcon } from "./menu"
3233
import { authClient } from "@lib/auth"
3334
import { analytics } from "@/lib/analytics"
34-
import { useGraphModal, usePersistentChat } from "@/stores"
35+
import { useGraphModal, usePersistentChat, useProject } from "@/stores"
36+
import {
37+
Dialog,
38+
DialogContent,
39+
DialogDescription,
40+
DialogHeader,
41+
DialogTitle,
42+
DialogTrigger,
43+
} from "@ui/components/dialog"
44+
import { ScrollArea } from "@ui/components/scroll-area"
45+
import { formatDistanceToNow } from "date-fns"
46+
import { cn } from "@lib/utils"
47+
import { useMemo, useState } from "react"
3548

3649
export function Header({ onAddMemory }: { onAddMemory?: () => void }) {
3750
const { user } = useAuth()
3851
const { theme, setTheme } = useTheme()
3952
const router = useRouter()
4053
const { setIsOpen: setGraphModalOpen } = useGraphModal()
41-
const { getCurrentChat } = usePersistentChat()
54+
const {
55+
getCurrentChat,
56+
conversations,
57+
currentChatId,
58+
setCurrentChatId,
59+
deleteConversation,
60+
} = usePersistentChat()
61+
const { selectedProject } = useProject()
4262
const pathname = usePathname()
63+
const [isDialogOpen, setIsDialogOpen] = useState(false)
64+
65+
const sorted = useMemo(() => {
66+
return [...conversations].sort((a, b) =>
67+
a.lastUpdated < b.lastUpdated ? 1 : -1,
68+
)
69+
}, [conversations])
70+
71+
function handleNewChat() {
72+
analytics.newChatStarted()
73+
const newId = crypto.randomUUID()
74+
setCurrentChatId(newId)
75+
router.push(`/chat/${newId}`)
76+
setIsDialogOpen(false)
77+
}
78+
79+
function formatRelativeTime(isoString: string): string {
80+
return formatDistanceToNow(new Date(isoString), { addSuffix: true })
81+
}
4382

4483
const handleSignOut = () => {
4584
analytics.userSignedOut()
@@ -87,6 +126,106 @@ export function Header({ onAddMemory }: { onAddMemory?: () => void }) {
87126
c
88127
</span>
89128
</Button>
129+
<Dialog
130+
open={isDialogOpen}
131+
onOpenChange={(open) => {
132+
setIsDialogOpen(open)
133+
if (open) {
134+
analytics.chatHistoryViewed()
135+
}
136+
}}
137+
>
138+
<Tooltip>
139+
<TooltipTrigger asChild>
140+
<DialogTrigger asChild>
141+
<Button variant="ghost" size="sm">
142+
<HistoryIcon className="h-4 w-4" />
143+
</Button>
144+
</DialogTrigger>
145+
</TooltipTrigger>
146+
<TooltipContent>
147+
<p>Chat History</p>
148+
</TooltipContent>
149+
</Tooltip>
150+
<DialogContent className="sm:max-w-lg">
151+
<DialogHeader className="pb-4 border-b rounded-t-lg">
152+
<DialogTitle className="">Conversations</DialogTitle>
153+
<DialogDescription>
154+
Project{" "}
155+
<span className="font-mono font-medium">
156+
{selectedProject}
157+
</span>
158+
</DialogDescription>
159+
</DialogHeader>
160+
161+
<ScrollArea className="max-h-96">
162+
<div className="flex flex-col gap-1">
163+
{sorted.map((c) => {
164+
const isActive = c.id === currentChatId
165+
return (
166+
<button
167+
key={c.id}
168+
type="button"
169+
onClick={() => {
170+
setCurrentChatId(c.id)
171+
router.push(`/chat/${c.id}`)
172+
setIsDialogOpen(false)
173+
}}
174+
className={cn(
175+
"flex items-center justify-between rounded-md px-3 py-2 outline-none w-full text-left",
176+
"transition-colors",
177+
isActive ? "bg-primary/10" : "hover:bg-muted",
178+
)}
179+
aria-current={isActive ? "true" : undefined}
180+
>
181+
<div className="min-w-0">
182+
<div className="flex items-center gap-2">
183+
<span
184+
className={cn(
185+
"text-sm font-medium truncate",
186+
isActive ? "text-foreground" : undefined,
187+
)}
188+
>
189+
{c.title || "Untitled Chat"}
190+
</span>
191+
</div>
192+
<div className="text-xs text-muted-foreground">
193+
Last updated {formatRelativeTime(c.lastUpdated)}
194+
</div>
195+
</div>
196+
<Button
197+
type="button"
198+
variant="ghost"
199+
size="icon"
200+
onClick={(e) => {
201+
e.stopPropagation()
202+
analytics.chatDeleted()
203+
deleteConversation(c.id)
204+
}}
205+
aria-label="Delete conversation"
206+
>
207+
<Trash2 className="size-4 text-muted-foreground" />
208+
</Button>
209+
</button>
210+
)
211+
})}
212+
{sorted.length === 0 && (
213+
<div className="text-xs text-muted-foreground px-3 py-2">
214+
No conversations yet
215+
</div>
216+
)}
217+
</div>
218+
</ScrollArea>
219+
<Button
220+
variant="outline"
221+
size="lg"
222+
className="w-full border-dashed"
223+
onClick={handleNewChat}
224+
>
225+
<Plus className="size-4 mr-1" /> New Conversation
226+
</Button>
227+
</DialogContent>
228+
</Dialog>
90229
<Tooltip>
91230
<TooltipTrigger asChild>
92231
<Button

apps/web/components/views/chat/chat-messages.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
ChevronDown,
1111
ChevronRight,
1212
Copy,
13-
Plus,
1413
RotateCcw,
1514
X,
1615
} from "lucide-react"
@@ -221,15 +220,20 @@ export function ChatMessages() {
221220
const [input, setInput] = useState("")
222221
const [selectedModel, setSelectedModel] = useState<
223222
"gpt-5" | "claude-sonnet-4.5" | "gemini-2.5-pro"
224-
>((sessionStorage.getItem(storageKey) as "gpt-5" | "claude-sonnet-4.5" | "gemini-2.5-pro" || "gemini-2.5-pro") || "gemini-2.5-pro")
223+
>(
224+
(sessionStorage.getItem(storageKey) as
225+
| "gpt-5"
226+
| "claude-sonnet-4.5"
227+
| "gemini-2.5-pro") ||
228+
"gemini-2.5-pro" ||
229+
"gemini-2.5-pro",
230+
)
225231
const activeChatIdRef = useRef<string | null>(null)
226232
const shouldGenerateTitleRef = useRef<boolean>(false)
227233
const hasRunInitialMessageRef = useRef<boolean>(false)
228234

229235
const { setDocumentIds } = useGraphHighlights()
230236

231-
console.log("selectedModel", selectedModel)
232-
233237
const { messages, sendMessage, status, stop, setMessages, id, regenerate } =
234238
useChat({
235239
id: currentChatId ?? undefined,
@@ -277,7 +281,6 @@ export function ChatMessages() {
277281
savedModel &&
278282
["gpt-5", "claude-sonnet-4.5", "gemini-2.5-pro"].includes(savedModel)
279283
) {
280-
console.log("savedModel", savedModel)
281284
setSelectedModel(savedModel)
282285
}
283286
}

0 commit comments

Comments
 (0)