Global search overlay (Cmd+K), copy button on messages, fix Turbopack ESM

This commit is contained in:
Hermes
2026-04-09 21:41:46 -05:00
parent 61e512ffe5
commit a125e38d07
4 changed files with 132 additions and 1 deletions
+109
View File
@@ -0,0 +1,109 @@
"use client";
import { useState, useEffect, useRef } from "react";
import { Search, X, Clock, MessageSquare } from "lucide-react";
import { useRouter } from "next/navigation";
import { useSessionSearch } from "@/lib/hooks";
export function SearchOverlay() {
const [open, setOpen] = useState(false);
const [query, setQuery] = useState("");
const inputRef = useRef<HTMLInputElement>(null);
const router = useRouter();
const { data, isFetching } = useSessionSearch(open ? query : "");
// Cmd+K / Ctrl+K to open
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault();
setOpen(true);
}
if (e.key === "Escape") {
setOpen(false);
setQuery("");
}
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, []);
useEffect(() => {
if (open) inputRef.current?.focus();
}, [open]);
const go = (sessionId: string) => {
setOpen(false);
setQuery("");
router.push("/history/" + sessionId);
};
if (!open) return null;
return (
<div
className="fixed inset-0 z-[200] flex flex-col"
style={{ background: "rgba(0,0,0,0.7)", backdropFilter: "blur(4px)" }}
onClick={() => { setOpen(false); setQuery(""); }}
>
<div
className="mx-4 mt-16 max-w-lg w-full self-center"
onClick={(e) => e.stopPropagation()}
>
<div className="relative">
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2" style={{ color: "var(--text-3)" }} />
<input
ref={inputRef}
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search all sessions..."
className="w-full pl-9 pr-9 py-3 rounded-t-2xl text-[15px] outline-none"
style={{ background: "var(--bg-raised)", border: "1px solid var(--border)", color: "var(--text-1)", borderBottom: "none" }}
/>
{query && (
<button onClick={() => setQuery("")} className="absolute right-3 top-1/2 -translate-y-1/2" style={{ color: "var(--text-3)" }}>
<X size={16} />
</button>
)}
</div>
<div
className="rounded-b-2xl max-h-[60vh] overflow-y-auto"
style={{ background: "var(--bg-raised)", border: "1px solid var(--border)", borderTop: "1px solid var(--border)" }}
>
{query.length < 2 ? (
<div className="p-4 text-center text-[13px]" style={{ color: "var(--text-3)" }}>
Type to search across all sessions
</div>
) : isFetching ? (
<div className="p-4 text-center text-[13px]" style={{ color: "var(--text-3)" }}>
Searching...
</div>
) : data?.results?.length ? (
data.results.map((r) => (
<button
key={r.message_id}
onClick={() => go(r.session_id)}
className="w-full text-left px-4 py-3 border-b border-white/[0.04] active:bg-white/[0.04] transition-colors"
>
<div className="flex items-center gap-2 mb-0.5">
<MessageSquare size={12} style={{ color: "var(--accent)" }} />
<span className="text-[12px] font-medium" style={{ color: "var(--text-2)" }}>
{r.session_title || r.session_id.slice(0, 12)}
</span>
<span className="text-[10px] ml-auto" style={{ color: "var(--text-3)" }}>{r.role}</span>
</div>
<p className="text-[13px] line-clamp-2" style={{ color: "var(--text-1)" }}>{r.content}</p>
</button>
))
) : (
<div className="p-4 text-center text-[13px]" style={{ color: "var(--text-3)" }}>
No results for &ldquo;{query}&rdquo;
</div>
)}
</div>
</div>
</div>
);
}