mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Adjust AIAssistantHeader (#47912)
## Context As per PR title, just adjusting the Assistant's header a little to improve the UX ### Before <img width="442" height="71" alt="image" src="https://github.com/user-attachments/assets/c714e264-7724-451a-aeaf-7ced456d0639" /> ### After <img width="438" height="77" alt="image" src="https://github.com/user-attachments/assets/11467bf2-7306-4ec9-80e0-2aadd959eff1" /> ## Changes involved - Shift permission settings into "More" dropdown - Chat selection is now "history" - Added keyboard shortcuts for history and copy chat ID <img width="185" height="95" alt="image" src="https://github.com/user-attachments/assets/fc1c9bdc-9180-48ba-940f-2f39fef53a1d" /> <img width="287" height="159" alt="image" src="https://github.com/user-attachments/assets/8f597306-9ea0-4282-884b-722bab16e4d0" /> - Chat name is now clickable to directly edit it - Saves on Enter or on blur - Resets on Esc <img width="433" height="70" alt="image" src="https://github.com/user-attachments/assets/f0c6fb4a-c368-4722-97a2-22ae94cc5511" /> <img width="439" height="64" alt="image" src="https://github.com/user-attachments/assets/1eb21aaf-3979-433a-acc2-378b47b79e94" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added keyboard shortcuts to toggle the AI Assistant chat history and copy the active chat ID. * Added shortcut hint pills to AI Assistant tooltips and the “More options” menu. * Enabled inline editing of the active chat name with save/cancel and blur support. * **Improvements** * Refreshed AI Assistant header actions and icons (including “New chat” and menu controls) for clearer navigation. * Updated onboarding header styling with an assistant icon/animation. * Standardized shortcut rendering in tooltip pill formatting. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
fd5ec9fade
commit
c97bc6282d
5 files changed
+150
-66
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { Check, ChevronDown, Edit, Plus, Trash, X } from 'lucide-react'
|
||||
import { Check, Edit, History, Plus, Trash, X } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
@@ -17,7 +17,10 @@ import {
|
||||
ScrollArea,
|
||||
} from 'ui'
|
||||
|
||||
import { ShortcutTooltip } from '../ShortcutTooltip'
|
||||
import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
|
||||
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
||||
import { useShortcut } from '@/state/shortcuts/useShortcut'
|
||||
|
||||
interface AIAssistantChatSelectorProps {
|
||||
disabled?: boolean
|
||||
@@ -25,7 +28,6 @@ interface AIAssistantChatSelectorProps {
|
||||
|
||||
export const AIAssistantChatSelector = ({ disabled = false }: AIAssistantChatSelectorProps) => {
|
||||
const snap = useAiAssistantStateSnapshot()
|
||||
const currentChat = snap.activeChat?.name
|
||||
|
||||
const [chatSelectorOpen, setChatSelectorOpen] = useState(false)
|
||||
const [editingChatId, setEditingChatId] = useState<string | null>(null)
|
||||
@@ -33,6 +35,8 @@ export const AIAssistantChatSelector = ({ disabled = false }: AIAssistantChatSel
|
||||
|
||||
const chats = Object.entries(snap.chats)
|
||||
|
||||
useShortcut(SHORTCUT_IDS.AI_ASSISTANT_TOGGLE_HISTORY, () => setChatSelectorOpen((prev) => !prev))
|
||||
|
||||
const handleSelectChat = (id: string) => {
|
||||
snap.selectChat(id)
|
||||
setChatSelectorOpen(false)
|
||||
@@ -78,17 +82,22 @@ export const AIAssistantChatSelector = ({ disabled = false }: AIAssistantChatSel
|
||||
|
||||
return (
|
||||
<Popover open={chatSelectorOpen} onOpenChange={setChatSelectorOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="text"
|
||||
size="tiny"
|
||||
iconRight={<ChevronDown size={14} />}
|
||||
className="max-w-64 truncate"
|
||||
>
|
||||
{currentChat}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[250px] p-0" align="center">
|
||||
<ShortcutTooltip
|
||||
side="bottom"
|
||||
label="History"
|
||||
shortcutId={SHORTCUT_IDS.AI_ASSISTANT_TOGGLE_HISTORY}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
aria-label="History"
|
||||
variant="text"
|
||||
size="tiny"
|
||||
className="h-7 w-7 p-0"
|
||||
icon={<History />}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
</ShortcutTooltip>
|
||||
<PopoverContent className="w-[250px] p-0" align="end">
|
||||
<Command>
|
||||
<CommandInput className="text-xs" placeholder="Search chats..." />
|
||||
<CommandList>
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { Clipboard, Ellipsis, Plus, Settings, X } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Clipboard, Edit, MessageCirclePlus, MoreVertical, Settings, X } from 'lucide-react'
|
||||
import { KeyboardEvent, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
AiIconAnimation,
|
||||
Button,
|
||||
copyToClipboard,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
Input,
|
||||
} from 'ui'
|
||||
import { Admonition } from 'ui-patterns/admonition'
|
||||
|
||||
import { ButtonTooltip } from '../ButtonTooltip'
|
||||
import { ShortcutTooltip } from '../ShortcutTooltip'
|
||||
import { ShortcutPills, ShortcutTooltip } from '../ShortcutTooltip'
|
||||
import { AIAssistantChatSelector } from './AIAssistantChatSelector'
|
||||
import { AIOptInModal } from './AIOptInModal'
|
||||
import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
|
||||
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
||||
import { SHORTCUT_DEFINITIONS, SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
||||
import { useShortcut } from '@/state/shortcuts/useShortcut'
|
||||
|
||||
interface AIAssistantHeaderProps {
|
||||
@@ -39,36 +41,80 @@ export const AIAssistantHeader = ({
|
||||
aiOptInLevel,
|
||||
}: AIAssistantHeaderProps) => {
|
||||
const snap = useAiAssistantStateSnapshot()
|
||||
const [value, setValue] = useState(snap.activeChat?.name)
|
||||
const [isEditingName, setIsEditingName] = useState(false)
|
||||
const [isOptInModalOpen, setIsOptInModalOpen] = useState(false)
|
||||
|
||||
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: !isChatLoading,
|
||||
})
|
||||
|
||||
useShortcut(SHORTCUT_IDS.AI_ASSISTANT_OPEN_PERMISSIONS, () => setIsOptInModalOpen(true), {
|
||||
enabled: !isChatLoading,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="z-30 sticky top-0">
|
||||
<div className="border-b border-b-muted flex items-center bg-card gap-x-4 pl-4 pr-3 min-h-(--header-height)">
|
||||
<div className="text-sm flex-1 flex items-center">
|
||||
<AiIconAnimation size={18} allowHoverEffect={false} />
|
||||
<span className="text-border-stronger dark:text-border-strong ml-2">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="16"
|
||||
height="16"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
shapeRendering="geometricPrecision"
|
||||
<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)
|
||||
}}
|
||||
>
|
||||
<path d="M16 3.549L7.12 20.600" />
|
||||
</svg>
|
||||
</span>
|
||||
<AIAssistantChatSelector />
|
||||
{snap.activeChat?.name}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4">
|
||||
|
||||
<div className="flex items-center gap-x-4 shrink-0">
|
||||
<div className="flex items-center">
|
||||
<AIAssistantChatSelector />
|
||||
|
||||
<ShortcutTooltip
|
||||
side="bottom"
|
||||
label="New chat"
|
||||
@@ -78,41 +124,47 @@ export const AIAssistantHeader = ({
|
||||
variant="text"
|
||||
aria-label="New chat"
|
||||
size="tiny"
|
||||
icon={<Plus strokeWidth={1.5} />}
|
||||
icon={<MessageCirclePlus />}
|
||||
onClick={onNewChat}
|
||||
className="h-7 w-7 p-0"
|
||||
/>
|
||||
</ShortcutTooltip>
|
||||
|
||||
<ShortcutTooltip side="bottom" shortcutId={SHORTCUT_IDS.AI_ASSISTANT_OPEN_PERMISSIONS}>
|
||||
<Button
|
||||
variant="text"
|
||||
aria-label="Permission settings"
|
||||
size="tiny"
|
||||
icon={<Settings strokeWidth={1.5} />}
|
||||
onClick={() => setIsOptInModalOpen(true)}
|
||||
className="h-7 w-7 p-0"
|
||||
disabled={isChatLoading}
|
||||
/>
|
||||
</ShortcutTooltip>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<ButtonTooltip
|
||||
variant="text"
|
||||
size="tiny"
|
||||
icon={<Ellipsis strokeWidth={1.5} />}
|
||||
icon={<MoreVertical />}
|
||||
className="h-7 w-7 p-0"
|
||||
disabled={isChatLoading}
|
||||
tooltip={{ content: { side: 'bottom', text: 'More options' } }}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
<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="gap-x-2"
|
||||
onClick={() => copyToClipboard(snap.activeChatId ?? '')}
|
||||
className="justify-between"
|
||||
onClick={() => setIsOptInModalOpen(true)}
|
||||
>
|
||||
<Clipboard size={14} strokeWidth={1.5} />
|
||||
<span>Copy chat ID</span>
|
||||
<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>
|
||||
@@ -133,6 +185,7 @@ export const AIAssistantHeader = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showMetadataWarning && (
|
||||
<Admonition
|
||||
type="default"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useParams } from 'common'
|
||||
import { motion } from 'framer-motion'
|
||||
import { BarChart, FileText, Shield } from 'lucide-react'
|
||||
import { Button, Skeleton } from 'ui'
|
||||
import { AiIconAnimation, Button, Skeleton } from 'ui'
|
||||
|
||||
import { codeSnippetPrompts, defaultPrompts } from './AIAssistant.prompts'
|
||||
import type { SqlSnippet } from './AIAssistant.types'
|
||||
@@ -48,12 +48,13 @@ export const AIOnboarding = ({
|
||||
<div className="w-full flex-1 max-h-full min-h-full px-4 flex flex-col gap-0">
|
||||
<div className="mt-auto w-full space-y-6 py-8 ">
|
||||
<motion.h2
|
||||
className="heading-section text-foreground mx-4"
|
||||
className="heading-section text-foreground mx-4 flex items-center gap-x-3"
|
||||
initial={{ y: 5, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
How can I assist you?
|
||||
<AiIconAnimation size={18} allowHoverEffect={false} />
|
||||
</motion.h2>
|
||||
{suggestions?.prompts?.length ? (
|
||||
<div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { HotkeySequence } from '@tanstack/react-hotkeys'
|
||||
import { TooltipContentProps } from '@ui/components/shadcn/ui/tooltip'
|
||||
import { Fragment, useState, type ReactNode } from 'react'
|
||||
import { KeyboardShortcut, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
@@ -65,15 +66,21 @@ export const ShortcutTooltip = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<span>{label}</span>
|
||||
<span className="flex items-center gap-1">
|
||||
{def.sequence.map((step, i) => (
|
||||
<Fragment key={i}>
|
||||
{i > 0 && <span className="text-foreground-lighter text-[11px]">then</span>}
|
||||
<KeyboardShortcut keys={hotkeyToKeys(step)} />
|
||||
</Fragment>
|
||||
))}
|
||||
</span>
|
||||
<ShortcutPills sequence={def.sequence} />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export const ShortcutPills = ({ sequence }: { sequence: HotkeySequence }) => {
|
||||
return (
|
||||
<span className="flex items-center gap-1">
|
||||
{sequence.map((step, i) => (
|
||||
<Fragment key={i}>
|
||||
{i > 0 && <span className="text-foreground-lighter text-[11px]">then</span>}
|
||||
<KeyboardShortcut keys={hotkeyToKeys(step)} />
|
||||
</Fragment>
|
||||
))}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -71,6 +71,8 @@ export const SHORTCUT_IDS = {
|
||||
COMMAND_MENU_OPEN: 'command-menu.open',
|
||||
AI_ASSISTANT_TOGGLE: 'ai-assistant.toggle',
|
||||
AI_ASSISTANT_NEW_CHAT: 'ai-assistant.new-chat',
|
||||
AI_ASSISTANT_COPY_CHAT_ID: 'ai-assistant.copy-chat-id',
|
||||
AI_ASSISTANT_TOGGLE_HISTORY: 'ai-assistant.toggle-history',
|
||||
AI_ASSISTANT_OPEN_PERMISSIONS: 'ai-assistant.open-permissions',
|
||||
AI_ASSISTANT_CANCEL_EDIT: 'ai-assistant.cancel-edit',
|
||||
INLINE_EDITOR_TOGGLE: 'inline-editor.toggle',
|
||||
@@ -261,6 +263,18 @@ export const SHORTCUT_DEFINITIONS: Record<ShortcutId, ShortcutDefinition> = {
|
||||
sequence: ['A', 'N'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.AI_ASSISTANT_COPY_CHAT_ID]: {
|
||||
id: SHORTCUT_IDS.AI_ASSISTANT_COPY_CHAT_ID,
|
||||
label: 'Copy chat ID',
|
||||
sequence: ['A', 'C'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.AI_ASSISTANT_TOGGLE_HISTORY]: {
|
||||
id: SHORTCUT_IDS.AI_ASSISTANT_TOGGLE_HISTORY,
|
||||
label: 'Toggle chat history',
|
||||
sequence: ['A', 'Y'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.AI_ASSISTANT_OPEN_PERMISSIONS]: {
|
||||
id: SHORTCUT_IDS.AI_ASSISTANT_OPEN_PERMISSIONS,
|
||||
label: 'Permission settings',
|
||||
|
||||
Reference in new issue
Block a user