Wire up query history: localStorage session persistence + sidebar UI

This commit is contained in:
2026-08-14 17:44:36 +07:00
parent 9be5819710
commit 057d4ed9dc
23 changed files with 1231 additions and 30 deletions
+61 -1
View File
@@ -1,6 +1,6 @@
"use client";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import {
Plus,
MessageSquare,
@@ -11,6 +11,7 @@ import {
ChevronLeft,
ChevronRight,
ShieldCheck,
History,
Zap,
} from "lucide-react";
import { cn } from "@duoc-thu/ui";
@@ -59,6 +60,12 @@ const QUICK_PROMPTS = [
},
];
interface HistoryItem {
trace_id: string;
query: string;
decision: string;
}
export function Sidebar({
currentSessionId,
sessions,
@@ -70,6 +77,32 @@ export function Sidebar({
}: SidebarProps) {
const [searchTerm, setSearchTerm] = useState("");
const [isCollapsed, setIsCollapsed] = useState(false);
const [historyItems, setHistoryItems] = useState<HistoryItem[]>([]);
// Feature-List #25: past queries FOR THIS SESSION, most recent first —
// click one to re-run it (via `onQuickQuery`, same path the hardcoded
// quick-prompts below already use). Deliberately re-fetched whenever the
// active session changes, not lifted to page.tsx: `Composer.tsx`'s own
// autocomplete fetch already establishes the pattern of a component
// owning its own small read, rather than everything prop-drilled down.
useEffect(() => {
if (!currentSessionId) {
setHistoryItems([]);
return;
}
let cancelled = false;
fetch(`/api/history?conversation_id=${encodeURIComponent(currentSessionId)}`)
.then((res) => (res.ok ? res.json() : { items: [] }))
.then((data) => {
if (!cancelled) setHistoryItems(Array.isArray(data?.items) ? data.items : []);
})
.catch(() => {
if (!cancelled) setHistoryItems([]);
});
return () => {
cancelled = true;
};
}, [currentSessionId]);
const filteredSessions = sessions.filter((s) =>
s.title.toLowerCase().includes(searchTerm.toLowerCase())
@@ -204,6 +237,33 @@ export function Sidebar({
})
)}
{/* Query History — real past questions for this session (Feature-
List #25), click to re-run the same question. Empty when the
session has no persisted queries yet (a brand-new session, or
history backend unavailable) — the quick-prompt templates below
still work as a starting point either way. */}
{historyItems.length > 0 && (
<div className="pt-4 px-2 border-t border-border-subtle mt-4">
<div className="pb-2 text-[0.68rem] font-bold tracking-wider text-txt-muted uppercase flex items-center gap-1">
<History className="w-3 h-3 text-accent-primary" />
<span>Lịch sử câu hỏi</span>
</div>
<div className="space-y-1.5">
{historyItems.map((item) => (
<button
key={item.trace_id}
onClick={() => onQuickQuery(item.query)}
title={item.query}
className="w-full text-left p-2 rounded-xl bg-surface-elevated hover:bg-surface-hover border border-border-subtle text-txt-secondary hover:text-txt-primary text-[0.72rem] leading-snug transition-all flex items-center justify-between group"
>
<span className="truncate pr-1">{item.query}</span>
<History className="w-3 h-3 text-accent-primary shrink-0 opacity-70 group-hover:opacity-100" />
</button>
))}
</div>
</div>
)}
{/* Quick Prompts Section */}
<div className="pt-4 px-2 border-t border-border-subtle mt-4">
<div className="pb-2 text-[0.68rem] font-bold tracking-wider text-txt-muted uppercase flex items-center gap-1">