mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 03:15:06 +03:00
<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>
236 lines
7.4 KiB
TypeScript
236 lines
7.4 KiB
TypeScript
import {
|
|
Clipboard,
|
|
Edit,
|
|
Maximize,
|
|
MessageCirclePlus,
|
|
MoreVertical,
|
|
Settings,
|
|
X,
|
|
} from 'lucide-react'
|
|
import { KeyboardEvent, useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
Button,
|
|
copyToClipboard,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
Input,
|
|
} from 'ui'
|
|
|
|
import { ButtonTooltip } from '../ButtonTooltip'
|
|
import { ShortcutPills, ShortcutTooltip } from '../ShortcutTooltip'
|
|
import { AIAssistantChatSelector } from './AIAssistantChatSelector'
|
|
import { AIAssistantMetadataWarning } from './AIAssistantMetadataWarning'
|
|
import { useCreateChat } from '@/components/interfaces/Explorer/hooks'
|
|
import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
|
|
import { SHORTCUT_DEFINITIONS, SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
|
import { useShortcut } from '@/state/shortcuts/useShortcut'
|
|
|
|
interface AIAssistantHeaderProps {
|
|
isChatLoading: boolean
|
|
shortcutsEnabled?: boolean
|
|
onNewChat: () => void
|
|
onCloseAssistant: () => void
|
|
showMetadataWarning: boolean
|
|
updatedOptInSinceMCP: boolean
|
|
isHipaaProjectDisallowed: boolean
|
|
aiOptInLevel: 'disabled' | 'schema' | 'full' | string | undefined
|
|
}
|
|
|
|
export const AIAssistantHeader = ({
|
|
isChatLoading,
|
|
shortcutsEnabled = true,
|
|
onNewChat,
|
|
onCloseAssistant,
|
|
showMetadataWarning,
|
|
updatedOptInSinceMCP,
|
|
isHipaaProjectDisallowed,
|
|
aiOptInLevel,
|
|
}: AIAssistantHeaderProps) => {
|
|
const snap = useAiAssistantStateSnapshot()
|
|
const { openChat } = useCreateChat()
|
|
const [value, setValue] = useState(snap.activeChat?.name)
|
|
const [isEditingName, setIsEditingName] = useState(false)
|
|
const [isOptInModalOpen, setIsOptInModalOpen] = useState(false)
|
|
|
|
const handleOpenInExplorer = () => {
|
|
if (!snap.activeChatId) return
|
|
openChat(snap.activeChatId)
|
|
onCloseAssistant()
|
|
}
|
|
|
|
const handleCopyChatId = () => {
|
|
copyToClipboard(snap.activeChatId ?? '', () => {
|
|
toast.success(`Copied chat ID for ${snap.activeChat?.name}`)
|
|
})
|
|
}
|
|
|
|
const handleSaveName = () => {
|
|
if (snap.activeChatId && value?.trim()) {
|
|
snap.renameChat(snap.activeChatId, value.trim())
|
|
}
|
|
setIsEditingName(false)
|
|
}
|
|
|
|
const handleKeyDownInput = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
if (e.key === 'Escape') {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
setIsEditingName(false)
|
|
setValue(snap.activeChat?.name)
|
|
} else if (e.key === 'Enter') {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
handleSaveName()
|
|
}
|
|
}
|
|
|
|
const handleBlurInput = () => {
|
|
if (isEditingName) handleSaveName()
|
|
}
|
|
|
|
useShortcut(SHORTCUT_IDS.AI_ASSISTANT_COPY_CHAT_ID, handleCopyChatId, {
|
|
enabled: shortcutsEnabled && !isChatLoading,
|
|
})
|
|
|
|
useShortcut(SHORTCUT_IDS.AI_ASSISTANT_OPEN_PERMISSIONS, () => setIsOptInModalOpen(true), {
|
|
enabled: shortcutsEnabled && !isChatLoading,
|
|
})
|
|
|
|
useShortcut(SHORTCUT_IDS.AI_ASSISTANT_MAXIMIZE, handleOpenInExplorer, {
|
|
enabled: shortcutsEnabled && !isChatLoading,
|
|
})
|
|
|
|
return (
|
|
<div className="z-30 sticky top-0">
|
|
<div className="border-b border-b-muted flex items-center bg-card gap-x-4 px-3 min-h-(--header-height)">
|
|
<div className="text-sm flex-1 min-w-0 flex items-center gap-x-1">
|
|
{isEditingName ? (
|
|
<Input
|
|
autoFocus
|
|
value={value}
|
|
size="tiny"
|
|
onChange={(e) => setValue(e.target.value)}
|
|
onKeyDown={handleKeyDownInput}
|
|
onBlur={handleBlurInput}
|
|
/>
|
|
) : (
|
|
<Button
|
|
variant="text"
|
|
className="group min-w-0"
|
|
iconRight={<Edit className="transition opacity-0 group-hover:opacity-100" />}
|
|
onClick={() => {
|
|
setValue(snap.activeChat?.name)
|
|
setIsEditingName(true)
|
|
}}
|
|
>
|
|
{snap.activeChat?.name}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-x-4 shrink-0">
|
|
<div className="flex items-center">
|
|
<AIAssistantChatSelector shortcutsEnabled={shortcutsEnabled} />
|
|
|
|
<ShortcutTooltip
|
|
side="bottom"
|
|
label="New chat"
|
|
shortcutId={SHORTCUT_IDS.AI_ASSISTANT_NEW_CHAT}
|
|
>
|
|
<Button
|
|
variant="text"
|
|
aria-label="New chat"
|
|
size="tiny"
|
|
icon={<MessageCirclePlus />}
|
|
onClick={onNewChat}
|
|
className="h-7 w-7 p-0"
|
|
/>
|
|
</ShortcutTooltip>
|
|
|
|
<ShortcutTooltip
|
|
side="bottom"
|
|
label="Open in Explorer"
|
|
shortcutId={SHORTCUT_IDS.AI_ASSISTANT_MAXIMIZE}
|
|
>
|
|
<Button
|
|
variant="text"
|
|
aria-label="Open in Explorer"
|
|
size="tiny"
|
|
icon={<Maximize />}
|
|
onClick={handleOpenInExplorer}
|
|
className="h-7 w-7 p-0"
|
|
/>
|
|
</ShortcutTooltip>
|
|
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<ButtonTooltip
|
|
variant="text"
|
|
size="tiny"
|
|
icon={<MoreVertical />}
|
|
className="h-7 w-7 p-0"
|
|
disabled={isChatLoading}
|
|
tooltip={{ content: { side: 'bottom', text: 'More options' } }}
|
|
/>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-60">
|
|
<DropdownMenuItem className="justify-between" onClick={handleCopyChatId}>
|
|
<div className="flex items-center gap-x-2">
|
|
<Clipboard size={14} />
|
|
<span>Copy chat ID</span>
|
|
</div>
|
|
<ShortcutPills
|
|
sequence={SHORTCUT_DEFINITIONS[SHORTCUT_IDS.AI_ASSISTANT_COPY_CHAT_ID].sequence}
|
|
/>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem
|
|
className="justify-between"
|
|
onClick={() => setIsOptInModalOpen(true)}
|
|
>
|
|
<div className="flex items-center gap-x-2">
|
|
<Settings size={14} />
|
|
<span>Permission settings</span>
|
|
</div>
|
|
<ShortcutPills
|
|
sequence={
|
|
SHORTCUT_DEFINITIONS[SHORTCUT_IDS.AI_ASSISTANT_OPEN_PERMISSIONS].sequence
|
|
}
|
|
/>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
<ShortcutTooltip
|
|
side="bottom"
|
|
label="Close assistant"
|
|
shortcutId={SHORTCUT_IDS.AI_ASSISTANT_TOGGLE}
|
|
>
|
|
<Button
|
|
aria-label="Close Assistant"
|
|
variant="text"
|
|
className="w-7 h-7"
|
|
onClick={onCloseAssistant}
|
|
icon={<X strokeWidth={1.5} />}
|
|
/>
|
|
</ShortcutTooltip>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<AIAssistantMetadataWarning
|
|
visible={isOptInModalOpen}
|
|
onVisibleChange={setIsOptInModalOpen}
|
|
showMetadataWarning={showMetadataWarning}
|
|
updatedOptInSinceMCP={updatedOptInSinceMCP}
|
|
isHipaaProjectDisallowed={isHipaaProjectDisallowed}
|
|
aiOptInLevel={aiOptInLevel}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|