Files
supabase/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.constants.tsx
Saxon Fletcher fd8d213135 feat: add Explorer chat discovery and controls (#49032)
<img width="1693" height="1038" alt="image"
src="https://github.com/user-attachments/assets/9ef52446-c517-4ea9-ada6-ac6dc57c8af4"
/>


## Summary

- add an Explorer-specific chat toolbar with rename, chat ID,
permission, and branching controls
- list searchable non-support chats in Explorer with reactive updates
and safe rehydrated-date sorting
- add chat creation entry points to Explorer home and navigation menus
- route chat recent items correctly and show chat icons across shared
tab surfaces
- make the assistant sidebar expand action open the active chat in
Explorer

This is PR 3 of 3 and is stacked on #49031. Review #48973 first, then
#49031, then this PR. Compared with its base, this PR contains only
discovery, toolbar, and cross-surface integration work.

## To Test
- visit /explorer
- Create a new chat either via home tab chat input OR the chats sidebar
- Validate chats show up in sidebar
- Validate chat conversation works
- Close the chat tab and open a chat via sidebar
- Change permission settings via the chat tab toolbar and verify it
persists


## Test plan

- `mise exec node@22 -- pnpm --dir apps/studio exec tsc --noEmit`
- focused Vitest suite: 4 files / 5 tests covering reactive chat lists,
navigation filtering and sorting, recent-item routing, and sidebar
handoff
- ESLint on changed TypeScript files
- Prettier check on changed source files


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added chat creation from the Explorer home, navigation, and new-tab
menu.
- Added an Explorer chat toolbar with editable names, ID copying,
permissions, shortcuts, and metadata warnings.
- Added searchable chat history with sorting, active-chat highlighting,
and clear empty states.
  - Chats can now open directly in Explorer from the AI Assistant panel.
  - Recent items now link correctly to chats and notebooks.

- **Bug Fixes**
- Improved chat list updates after creation, deletion, and restored
sessions.
  - Added safer handling for chats without update timestamps.

- **Tests**
- Expanded coverage for chat navigation, creation, metadata warnings,
shortcuts, and recent-item links.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 11:19:43 +10:00

106 lines
3.2 KiB
TypeScript

import { motion } from 'framer-motion'
import { ChevronLeft, MessageSquare, NotebookText, Plus } from 'lucide-react'
import { type ComponentType, type PropsWithChildren } from 'react'
import { Button, cn } from 'ui'
import { InnerSideBarFilters, InnerSideBarFilterSearchInput } from 'ui-patterns/InnerSideMenu'
import { useCreateChat, useCreateNotebook } from '@/components/interfaces/Explorer/hooks'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
export type ExplorerResourceType = 'notebook' | 'chat'
export const LEVEL_OFFSET = 8
export const LEVEL_TRANSITION = { duration: 0.09, ease: 'easeOut' } as const
export const EXPLORER_SECTIONS: Array<{
type: ExplorerResourceType
label: string
icon: ComponentType<{ size?: number; className?: string }>
searchPlaceholder: string
}> = [
{
type: 'notebook',
label: 'Notebooks',
icon: NotebookText,
searchPlaceholder: 'Search notebooks',
},
{ type: 'chat', label: 'Chats', icon: MessageSquare, searchPlaceholder: 'Search chats' },
]
export const rowClassName = (isActive: boolean) =>
cn(
'group relative flex h-7 w-full items-center gap-2 rounded-md pl-3 pr-2 text-sm',
isActive
? 'bg-selection text-foreground'
: 'text-foreground-light hover:bg-surface-200 hover:text-foreground'
)
export const ExplorerNavResourceWrapper = ({
type,
label,
className,
children,
search,
setSearch,
onBack,
}: PropsWithChildren<{
type: ExplorerResourceType
label?: string
className?: string
search?: string
setSearch: (value: string) => void
onBack: () => void
}>) => {
const { createNotebook } = useCreateNotebook()
const { createChat } = useCreateChat()
const searchPlaceholder = EXPLORER_SECTIONS.find((x) => x.type === type)?.searchPlaceholder
return (
<motion.div
role="group"
aria-label={label}
initial={{ opacity: 0, x: LEVEL_OFFSET }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: LEVEL_OFFSET }}
transition={LEVEL_TRANSITION}
className={cn('absolute inset-0 flex flex-col', className)}
>
<div className="flex items-center gap-2 p-3 pb-2">
<Button
size="tiny"
variant="outline"
aria-label="Back"
onClick={onBack}
className="size-7 shrink-0 px-0"
icon={<ChevronLeft />}
/>
<span id="explorer-sidebar-search-label" className="sr-only">
{searchPlaceholder}
</span>
<InnerSideBarFilters className="w-full gap-0 p-0">
<InnerSideBarFilterSearchInput
name="explorer-sidebar-search"
value={search}
placeholder={searchPlaceholder}
aria-labelledby="explorer-sidebar-search-label"
onChange={(event) => setSearch(event.target.value)}
/>
</InnerSideBarFilters>
<ButtonTooltip
size="tiny"
variant="outline"
aria-label={`New ${type}`}
className="size-7 shrink-0 px-0"
icon={<Plus />}
tooltip={{ content: { side: 'bottom', text: `New ${type}` } }}
onClick={() => {
if (type === 'notebook') createNotebook()
if (type === 'chat') createChat()
}}
/>
</div>
{children}
</motion.div>
)
}