added search bar

This commit is contained in:
Arjun 2026-02-17 23:17:09 +05:30
parent 9da91bc6f3
commit ac30af4d10

View file

@ -16,10 +16,12 @@ import {
Pencil,
Plug,
LoaderIcon,
Search,
Settings,
Square,
SquarePen,
Trash2,
X,
} from "lucide-react"
import {
@ -354,6 +356,36 @@ function SyncStatusBar() {
)
}
function filterTree(nodes: TreeNode[], query: string): TreeNode[] {
const lowerQuery = query.toLowerCase()
return nodes.reduce<TreeNode[]>((acc, node) => {
if (node.kind === "dir") {
const filteredChildren = filterTree(node.children || [], query)
if (filteredChildren.length > 0) {
acc.push({ ...node, children: filteredChildren })
}
} else if (node.name.toLowerCase().includes(lowerQuery)) {
acc.push(node)
}
return acc
}, [])
}
function collectDirPaths(nodes: TreeNode[]): Set<string> {
const paths = new Set<string>()
for (const node of nodes) {
if (node.kind === "dir") {
paths.add(node.path)
if (node.children) {
for (const p of collectDirPaths(node.children)) {
paths.add(p)
}
}
}
}
return paths
}
export function SidebarContentPanel({
tree,
selectedPath,
@ -370,6 +402,35 @@ export function SidebarContentPanel({
...props
}: SidebarContentPanelProps) {
const { activeSection, setActiveSection } = useSidebarSection()
const [searchOpen, setSearchOpen] = useState(false)
const [searchQuery, setSearchQuery] = useState("")
const searchInputRef = useRef<HTMLInputElement>(null)
// Auto-focus when search opens
useEffect(() => {
if (searchOpen) {
// Small delay so the input is rendered before focusing
requestAnimationFrame(() => searchInputRef.current?.focus())
}
}, [searchOpen])
// Clear query when search closes or section changes
useEffect(() => {
if (!searchOpen) setSearchQuery("")
}, [searchOpen])
useEffect(() => {
setSearchQuery("")
}, [activeSection])
// Compute filtered data
const filteredRuns = searchQuery
? runs.filter((r) => (r.title || "").toLowerCase().includes(searchQuery.toLowerCase()))
: runs
const filteredBackgroundTasks = searchQuery
? backgroundTasks.filter((t) => t.name.toLowerCase().includes(searchQuery.toLowerCase()))
: backgroundTasks
const filteredTree = searchQuery ? filterTree(tree, searchQuery) : tree
const searchExpandedPaths = searchQuery ? collectDirPaths(filteredTree) : expandedPaths
return (
<Sidebar className="border-r-0" {...props}>
@ -399,22 +460,32 @@ export function SidebarContentPanel({
<SidebarContent>
{activeSection === "knowledge" && (
<KnowledgeSection
tree={tree}
tree={filteredTree}
selectedPath={selectedPath}
expandedPaths={expandedPaths}
expandedPaths={searchExpandedPaths}
onSelectFile={onSelectFile}
actions={knowledgeActions}
onVoiceNoteCreated={onVoiceNoteCreated}
searchOpen={searchOpen}
setSearchOpen={setSearchOpen}
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
searchInputRef={searchInputRef}
/>
)}
{activeSection === "tasks" && (
<TasksSection
runs={runs}
runs={filteredRuns}
currentRunId={currentRunId}
processingRunIds={processingRunIds}
actions={tasksActions}
backgroundTasks={backgroundTasks}
backgroundTasks={filteredBackgroundTasks}
selectedBackgroundTask={selectedBackgroundTask}
searchOpen={searchOpen}
setSearchOpen={setSearchOpen}
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
searchInputRef={searchInputRef}
/>
)}
</SidebarContent>
@ -691,6 +762,11 @@ function KnowledgeSection({
onSelectFile,
actions,
onVoiceNoteCreated,
searchOpen,
setSearchOpen,
searchQuery,
setSearchQuery,
searchInputRef,
}: {
tree: TreeNode[]
selectedPath: string | null
@ -698,6 +774,11 @@ function KnowledgeSection({
onSelectFile: (path: string, kind: "file" | "dir") => void
actions: KnowledgeActions
onVoiceNoteCreated?: (path: string) => void
searchOpen: boolean
setSearchOpen: (open: boolean) => void
searchQuery: string
setSearchQuery: (query: string) => void
searchInputRef: React.RefObject<HTMLInputElement | null>
}) {
const isExpanded = expandedPaths.size > 0
@ -711,38 +792,79 @@ function KnowledgeSection({
<ContextMenu>
<ContextMenuTrigger asChild>
<SidebarGroup className="flex-1 flex flex-col overflow-hidden">
<div className="flex items-center justify-center gap-1 py-1 sticky top-0 z-10 bg-sidebar border-b border-sidebar-border">
{quickActions.map((action) => (
<Tooltip key={action.label}>
<div className="sticky top-0 z-10 bg-sidebar border-b border-sidebar-border">
<div className="flex items-center justify-center gap-1 py-1">
{quickActions.map((action) => (
<Tooltip key={action.label}>
<TooltipTrigger asChild>
<button
onClick={action.action}
className="text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent rounded p-1.5 transition-colors"
>
<action.icon className="size-4" />
</button>
</TooltipTrigger>
<TooltipContent side="bottom">{action.label}</TooltipContent>
</Tooltip>
))}
<VoiceNoteButton onNoteCreated={onVoiceNoteCreated} />
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={action.action}
onClick={isExpanded ? actions.collapseAll : actions.expandAll}
className="text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent rounded p-1.5 transition-colors"
>
<action.icon className="size-4" />
{isExpanded ? (
<ChevronsDownUp className="size-4" />
) : (
<ChevronsUpDown className="size-4" />
)}
</button>
</TooltipTrigger>
<TooltipContent side="bottom">{action.label}</TooltipContent>
<TooltipContent side="bottom">
{isExpanded ? "Collapse All" : "Expand All"}
</TooltipContent>
</Tooltip>
))}
<VoiceNoteButton onNoteCreated={onVoiceNoteCreated} />
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={isExpanded ? actions.collapseAll : actions.expandAll}
className="text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent rounded p-1.5 transition-colors"
>
{isExpanded ? (
<ChevronsDownUp className="size-4" />
) : (
<ChevronsUpDown className="size-4" />
)}
</button>
</TooltipTrigger>
<TooltipContent side="bottom">
{isExpanded ? "Collapse All" : "Expand All"}
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => setSearchOpen(!searchOpen)}
className={cn(
"rounded p-1.5 transition-colors",
searchOpen
? "text-sidebar-foreground bg-sidebar-accent"
: "text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent"
)}
>
<Search className="size-4" />
</button>
</TooltipTrigger>
<TooltipContent side="bottom">Search</TooltipContent>
</Tooltip>
</div>
{searchOpen && (
<div className="flex items-center gap-1 rounded-md border border-sidebar-border bg-sidebar-accent/30 mx-2 mb-1 px-2 py-1">
<Search className="size-3.5 shrink-0 text-muted-foreground" />
<input
ref={searchInputRef}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Escape") setSearchOpen(false)
}}
placeholder="Search files..."
className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground/60"
/>
{searchQuery && (
<button
onClick={() => setSearchQuery("")}
className="rounded p-0.5 text-muted-foreground hover:text-sidebar-foreground transition-colors"
>
<X className="size-3.5" />
</button>
)}
</div>
)}
</div>
<SidebarGroupContent className="flex-1 overflow-y-auto">
<SidebarMenu>
@ -996,6 +1118,11 @@ function TasksSection({
actions,
backgroundTasks = [],
selectedBackgroundTask,
searchOpen,
setSearchOpen,
searchQuery,
setSearchQuery,
searchInputRef,
}: {
runs: RunListItem[]
currentRunId?: string | null
@ -1003,19 +1130,65 @@ function TasksSection({
actions?: TasksActions
backgroundTasks?: BackgroundTaskItem[]
selectedBackgroundTask?: string | null
searchOpen: boolean
setSearchOpen: (open: boolean) => void
searchQuery: string
setSearchQuery: (query: string) => void
searchInputRef: React.RefObject<HTMLInputElement | null>
}) {
return (
<SidebarGroup className="flex-1 flex flex-col overflow-hidden">
{/* Sticky New Chat button - matches Knowledge section height */}
{/* Sticky New Chat button + search */}
<div className="sticky top-0 z-10 bg-sidebar border-b border-sidebar-border py-0.5">
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton onClick={actions?.onNewChat} className="gap-2">
<SquarePen className="size-4 shrink-0" />
<span className="text-sm">New chat</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
<div className="flex items-center gap-1 px-1">
<SidebarMenu className="flex-1">
<SidebarMenuItem>
<SidebarMenuButton onClick={actions?.onNewChat} className="gap-2">
<SquarePen className="size-4 shrink-0" />
<span className="text-sm">New chat</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => setSearchOpen(!searchOpen)}
className={cn(
"rounded p-1.5 transition-colors shrink-0",
searchOpen
? "text-sidebar-foreground bg-sidebar-accent"
: "text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent"
)}
>
<Search className="size-4" />
</button>
</TooltipTrigger>
<TooltipContent side="bottom">Search</TooltipContent>
</Tooltip>
</div>
{searchOpen && (
<div className="flex items-center gap-1 rounded-md border border-sidebar-border bg-sidebar-accent/30 mx-2 mb-1 mt-0.5 px-2 py-1">
<Search className="size-3.5 shrink-0 text-muted-foreground" />
<input
ref={searchInputRef}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Escape") setSearchOpen(false)
}}
placeholder="Search chats..."
className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground/60"
/>
{searchQuery && (
<button
onClick={() => setSearchQuery("")}
className="rounded p-0.5 text-muted-foreground hover:text-sidebar-foreground transition-colors"
>
<X className="size-3.5" />
</button>
)}
</div>
)}
</div>
<SidebarGroupContent className="flex-1 overflow-y-auto">
{/* Background Tasks Section */}