From c97bc6282d36101920910116a2e9ffad07a031c4 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 15 Jul 2026 13:35:32 +0800 Subject: [PATCH 1/3] Adjust AIAssistantHeader (#47912) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context As per PR title, just adjusting the Assistant's header a little to improve the UX ### Before image ### After image ## Changes involved - Shift permission settings into "More" dropdown - Chat selection is now "history" - Added keyboard shortcuts for history and copy chat ID image image - Chat name is now clickable to directly edit it - Saves on Enter or on blur - Resets on Esc image image ## 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. --- .../AIAssistantChatSelector.tsx | 35 +++-- .../ui/AIAssistantPanel/AIAssistantHeader.tsx | 139 ++++++++++++------ .../ui/AIAssistantPanel/AIOnboarding.tsx | 5 +- apps/studio/components/ui/ShortcutTooltip.tsx | 23 ++- apps/studio/state/shortcuts/registry.ts | 14 ++ 5 files changed, 150 insertions(+), 66 deletions(-) diff --git a/apps/studio/components/ui/AIAssistantPanel/AIAssistantChatSelector.tsx b/apps/studio/components/ui/AIAssistantPanel/AIAssistantChatSelector.tsx index 9e648c51b69e3..e5631c81b38d0 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIAssistantChatSelector.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIAssistantChatSelector.tsx @@ -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(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 ( - - - - + + + + )} -
+ +
+ + } + icon={} onClick={onNewChat} className="h-7 w-7 p-0" /> - -
+ {showMetadataWarning && (
How can I assist you? + {suggestions?.prompts?.length ? (
diff --git a/apps/studio/components/ui/ShortcutTooltip.tsx b/apps/studio/components/ui/ShortcutTooltip.tsx index 5b8c3b70889b4..23bc80df5b7aa 100644 --- a/apps/studio/components/ui/ShortcutTooltip.tsx +++ b/apps/studio/components/ui/ShortcutTooltip.tsx @@ -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" > {label} - - {def.sequence.map((step, i) => ( - - {i > 0 && then} - - - ))} - + ) } + +export const ShortcutPills = ({ sequence }: { sequence: HotkeySequence }) => { + return ( + + {sequence.map((step, i) => ( + + {i > 0 && then} + + + ))} + + ) +} diff --git a/apps/studio/state/shortcuts/registry.ts b/apps/studio/state/shortcuts/registry.ts index 9945b09edf438..6f9ddc54950b4 100644 --- a/apps/studio/state/shortcuts/registry.ts +++ b/apps/studio/state/shortcuts/registry.ts @@ -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 = { 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', From c9eb33abc7a53c4647a589213bf64ae0aab71618 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Wed, 15 Jul 2026 08:20:59 +0200 Subject: [PATCH 2/3] feat(pipelines): Add new cost estimation dialog (#47915) --- .../AnalyticsBucket/AnalyticsBucket.utils.ts | 8 +- .../AnalyticsBucket/Fields.tsx | 8 +- .../BigQuery/BigQuery.utils.ts | 2 +- .../DestinationForm/BigQuery/Fields.tsx | 2 +- .../DestinationForm/DestinationForm.schema.ts | 19 +- .../DestinationForm.utils.test.ts | 38 +-- .../DuckLake/DuckLake.utils.ts | 14 +- .../DestinationForm/DuckLake/Fields.tsx | 8 +- .../DestinationForm/PipelineCostDialog.tsx | 248 ++++++++++++++++++ .../DestinationForm/index.tsx | 116 ++++---- .../ReadReplicaPricingDialog.tsx | 79 +++--- .../Database/Replication/DestinationRow.tsx | 12 +- .../Database/Replication/Destinations.tsx | 18 +- .../Organization/Usage/Usage.constants.tsx | 14 +- .../Usage/UsageSection/DiskUsage.tsx | 112 ++++---- .../data/replication/cost-estimate-query.ts | 57 ++++ apps/studio/data/replication/keys.ts | 14 + packages/api-types/types/platform.d.ts | 150 +++++++++++ 18 files changed, 725 insertions(+), 194 deletions(-) create mode 100644 apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PipelineCostDialog.tsx create mode 100644 apps/studio/data/replication/cost-estimate-query.ts diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AnalyticsBucket/AnalyticsBucket.utils.ts b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AnalyticsBucket/AnalyticsBucket.utils.ts index f516a8ba8ece8..3aeb5cd90606e 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AnalyticsBucket/AnalyticsBucket.utils.ts +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AnalyticsBucket/AnalyticsBucket.utils.ts @@ -22,8 +22,8 @@ type AnalyticsBucketValidationOptions = { // Fields that are always required regardless of the namespace / access key selections. const ANALYTICS_BUCKET_REQUIRED_FIELDS: AnalyticsBucketValidationIssue[] = [ { path: 'warehouseName', message: 'Bucket is required' }, - { path: 's3Region', message: 'S3 Region is required' }, - { path: 's3AccessKeyId', message: 'S3 Access Key ID is required' }, + { path: 's3Region', message: 'S3 region is required' }, + { path: 's3AccessKeyId', message: 'S3 access key ID is required' }, ] export const getAnalyticsBucketValidationIssues = ( @@ -53,7 +53,7 @@ export const getAnalyticsBucketValidationIssues = ( data.s3SecretAccessKey?.trim().length && !data.s3AccessKeyId?.trim().length ) { - issues.push({ path: 's3AccessKeyId', message: 'S3 Access Key ID is required' }) + issues.push({ path: 's3AccessKeyId', message: 'S3 access key ID is required' }) } const currentS3AccessKeyId = data.s3AccessKeyId?.trim() @@ -69,7 +69,7 @@ export const getAnalyticsBucketValidationIssues = ( (!options.secretsOptional || hasChangedStoredS3AccessKey) && !data.s3SecretAccessKey?.trim().length ) { - issues.push({ path: 's3SecretAccessKey', message: 'S3 Secret Access Key is required' }) + issues.push({ path: 's3SecretAccessKey', message: 'S3 secret access key is required' }) } return issues diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AnalyticsBucket/Fields.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AnalyticsBucket/Fields.tsx index ad1022650094d..eb674d27ea529 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AnalyticsBucket/Fields.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AnalyticsBucket/Fields.tsx @@ -255,7 +255,7 @@ export const AnalyticsBucketFields = ({ name="newNamespaceName" render={({ field }) => ( @@ -273,7 +273,7 @@ export const AnalyticsBucketFields = ({ render={({ field }) => (