Skip to content

Commit c39747f

Browse files
authored
feat: added copy prompt button for AI assistant for your own agent (supabase#42624)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Any instance where we want to ask the AI assistant, we create a copy prompt button for your agent ## Demo https://github.com/user-attachments/assets/c6afe319-ad36-49b7-a244-a8bf04c809a1 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced a new dropdown-style AI assistant trigger across explain, debug, and lint features with improved interaction flow. * Added copy-to-clipboard functionality for AI prompts with visual feedback confirmation. * Enhanced AI assistant integration across query performance, SQL editor, and lint detail interfaces for consistent experience. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 37748b5 commit c39747f

10 files changed

Lines changed: 316 additions & 114 deletions

File tree

apps/studio/components/interfaces/ExplainVisualizer/ExplainVisualizer.Header.tsx

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import { ArrowUp, Eye, Code, HelpCircle } from 'lucide-react'
2-
3-
import { useFlag } from 'common'
4-
import { AiIconAnimation, Button } from 'ui'
51
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
2+
import { AiAssistantDropdown } from 'components/ui/AiAssistantDropdown'
3+
import { Code, Eye, HelpCircle } from 'lucide-react'
64
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
7-
import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
85
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
6+
import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
7+
import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
8+
99
import { buildExplainPrompt } from './ExplainVisualizer.ai'
1010
import type { QueryPlanRow } from './ExplainVisualizer.types'
11-
import { Tooltip, TooltipContent, TooltipTrigger } from 'ui'
1211

1312
export interface ExplainSummary {
1413
totalTime: number
@@ -31,28 +30,41 @@ export function ExplainHeader({ mode, onToggleMode, summary, id, rows }: Explain
3130
const { openSidebar } = useSidebarManagerSnapshot()
3231
const aiSnap = useAiAssistantStateSnapshot()
3332

34-
const handleExplainWithAI = () => {
35-
if (!id) return
33+
const getPromptData = () => {
34+
if (!id) return null
3635
const snippet = snapV2.snippets[id]?.snippet
37-
if (!snippet?.content?.sql) return
36+
if (!snippet?.content?.sql) return null
3837

39-
const { query, prompt } = buildExplainPrompt({
38+
return buildExplainPrompt({
4039
sql: snippet.content.sql,
4140
explainPlanRows: (rows as QueryPlanRow[]) ?? [],
4241
})
42+
}
43+
44+
const handleExplainWithAI = () => {
45+
const promptData = getPromptData()
46+
if (!promptData) return
4347

4448
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
4549
aiSnap.newChat({
4650
sqlSnippets: [
4751
{
4852
label: 'Query',
49-
content: query,
53+
content: promptData.query,
5054
},
5155
],
52-
initialMessage: prompt,
56+
initialMessage: promptData.prompt,
5357
})
5458
}
5559

60+
const buildPromptForCopy = () => {
61+
const promptData = getPromptData()
62+
if (!promptData) return ''
63+
64+
// Combine SQL and prompt into a single copyable text
65+
return `${promptData.prompt}\n\nSQL Query:\n\`\`\`sql\n${promptData.query}\n\`\`\``
66+
}
67+
5668
const hasSummaryStats =
5769
isVisual && summary && (summary.totalTime > 0 || (summary.hasSeqScan && !summary.hasIndexScan))
5870

@@ -111,14 +123,14 @@ export function ExplainHeader({ mode, onToggleMode, summary, id, rows }: Explain
111123
</div>
112124
<div className="flex items-center gap-2">
113125
{id && rows && (
114-
<Button
115-
type="default"
126+
<AiAssistantDropdown
127+
label="Explain with AI"
128+
buildPrompt={buildPromptForCopy}
129+
onOpenAssistant={handleExplainWithAI}
130+
telemetrySource="explain_visualizer"
116131
size="tiny"
117-
icon={<AiIconAnimation size={14} />}
118-
onClick={handleExplainWithAI}
119-
>
120-
Explain with AI
121-
</Button>
132+
type="default"
133+
/>
122134
)}
123135
<Button
124136
type="default"

apps/studio/components/interfaces/Home/AdvisorWidget.tsx

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import { Activity, ExternalLink, Shield } from 'lucide-react'
2-
import Link from 'next/link'
3-
import { useCallback, useMemo, useState } from 'react'
4-
51
import { useParams } from 'common'
62
import { LINTER_LEVELS } from 'components/interfaces/Linter/Linter.constants'
7-
import { EntityTypeIcon, createLintSummaryPrompt } from 'components/interfaces/Linter/Linter.utils'
3+
import { createLintSummaryPrompt, EntityTypeIcon } from 'components/interfaces/Linter/Linter.utils'
84
import { useQueryPerformanceQuery } from 'components/interfaces/Reports/Reports.queries'
95
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
6+
import { AiAssistantDropdown } from 'components/ui/AiAssistantDropdown'
107
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
118
import { Lint, useProjectLintsQuery } from 'data/lint/lint-query'
9+
import { useTrack } from 'lib/telemetry/track'
10+
import { Activity, ExternalLink, Shield } from 'lucide-react'
11+
import Link from 'next/link'
12+
import { useCallback, useMemo, useState } from 'react'
1213
import { useAdvisorStateSnapshot } from 'state/advisor-state'
1314
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
1415
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
1516
import {
16-
AiIconAnimation,
1717
Card,
1818
CardContent,
1919
CardHeader,
@@ -50,6 +50,7 @@ export const AdvisorWidget = () => {
5050
const snap = useAiAssistantStateSnapshot()
5151
const { openSidebar } = useSidebarManagerSnapshot()
5252
const { setSelectedItem } = useAdvisorStateSnapshot()
53+
const track = useTrack()
5354

5455
const securityLints = useMemo(
5556
() => (lints ?? []).filter((lint: Lint) => lint.categories.includes('SECURITY')),
@@ -153,23 +154,36 @@ export const AdvisorWidget = () => {
153154
{lintText.replace(/\\`/g, '`')}
154155
</p>
155156
</button>
156-
<ButtonTooltip
157-
type="text"
158-
className="px-1 opacity-0 group-hover:opacity-100 w-7"
159-
icon={<AiIconAnimation size={16} />}
157+
<div
160158
onClick={(e) => {
161159
e.stopPropagation()
162160
e.preventDefault()
163-
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
164-
snap.newChat({
165-
name: 'Summarize lint',
166-
initialInput: createLintSummaryPrompt(lint),
167-
})
168-
}}
169-
tooltip={{
170-
content: { side: 'bottom', text: 'Help me fix this issue' },
171161
}}
172-
/>
162+
className="opacity-0 group-hover:opacity-100"
163+
>
164+
<AiAssistantDropdown
165+
label="Ask Assistant"
166+
iconOnly
167+
tooltip="Help me fix this issue"
168+
buildPrompt={() => createLintSummaryPrompt(lint)}
169+
onOpenAssistant={() => {
170+
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
171+
snap.newChat({
172+
name: 'Summarize lint',
173+
initialInput: createLintSummaryPrompt(lint),
174+
})
175+
track('advisor_assistant_button_clicked', {
176+
origin: 'homepage',
177+
advisorCategory: lint.categories[0],
178+
advisorType: lint.name,
179+
advisorLevel: lint.level,
180+
})
181+
}}
182+
telemetrySource="advisor_widget"
183+
type="text"
184+
className="px-1 w-7"
185+
/>
186+
</div>
173187
</div>
174188
</li>
175189
)

apps/studio/components/interfaces/HomeNew/AdvisorSection.tsx

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import { BarChart, Shield } from 'lucide-react'
2-
import { useCallback, useMemo } from 'react'
3-
41
import { useParams } from 'common'
52
import { LINTER_LEVELS } from 'components/interfaces/Linter/Linter.constants'
63
import { createLintSummaryPrompt } from 'components/interfaces/Linter/Linter.utils'
74
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
8-
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
5+
import { AiAssistantDropdown } from 'components/ui/AiAssistantDropdown'
96
import { Lint, useProjectLintsQuery } from 'data/lint/lint-query'
107
import { useTrack } from 'lib/telemetry/track'
8+
import { BarChart, Shield } from 'lucide-react'
9+
import { useCallback, useMemo } from 'react'
1110
import { useAdvisorStateSnapshot } from 'state/advisor-state'
1211
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
1312
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
1413
import { AiIconAnimation, Button, Card, CardContent, CardHeader, CardTitle } from 'ui'
1514
import { Row } from 'ui-patterns'
1615
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
16+
1717
import { Markdown } from '../Markdown'
1818

1919
export const AdvisorSection = ({ showEmptyState = false }: { showEmptyState?: boolean }) => {
@@ -114,29 +114,35 @@ export const AdvisorSection = ({ showEmptyState = false }: { showEmptyState?: bo
114114
)}
115115
<CardTitle className="text-foreground-light">{lint.categories[0]}</CardTitle>
116116
</div>
117-
<ButtonTooltip
118-
type="text"
119-
className="w-7 h-7"
120-
icon={<AiIconAnimation size={16} />}
117+
<div
121118
onClick={(e) => {
122119
e.stopPropagation()
123120
e.preventDefault()
124-
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
125-
snap.newChat({
126-
name: 'Summarize lint',
127-
initialInput: createLintSummaryPrompt(lint),
128-
})
129-
track('advisor_assistant_button_clicked', {
130-
origin: 'homepage',
131-
advisorCategory: lint.categories[0],
132-
advisorType: lint.name,
133-
advisorLevel: lint.level,
134-
})
135121
}}
136-
tooltip={{
137-
content: { side: 'bottom', text: 'Help me fix this issue' },
138-
}}
139-
/>
122+
>
123+
<AiAssistantDropdown
124+
label="Ask Assistant"
125+
iconOnly
126+
tooltip="Help me fix this issue"
127+
buildPrompt={() => createLintSummaryPrompt(lint)}
128+
onOpenAssistant={() => {
129+
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
130+
snap.newChat({
131+
name: 'Summarize lint',
132+
initialInput: createLintSummaryPrompt(lint),
133+
})
134+
track('advisor_assistant_button_clicked', {
135+
origin: 'homepage',
136+
advisorCategory: lint.categories[0],
137+
advisorType: lint.name,
138+
advisorLevel: lint.level,
139+
})
140+
}}
141+
telemetrySource="advisor_section"
142+
type="text"
143+
className="w-7 h-7"
144+
/>
145+
</div>
140146
</CardHeader>
141147
<CardContent className="p-6 pt-16 flex flex-col justify-end flex-1 overflow-auto">
142148
<h3 className="mb-1">{lint.title}</h3>

apps/studio/components/interfaces/Linter/LintDetail.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import Link from 'next/link'
2-
31
import { createLintSummaryPrompt, lintInfoMap } from 'components/interfaces/Linter/Linter.utils'
42
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
3+
import { AiAssistantDropdown } from 'components/ui/AiAssistantDropdown'
54
import { Lint } from 'data/lint/lint-query'
65
import { DOCS_URL } from 'lib/constants'
76
import { useTrack } from 'lib/telemetry/track'
87
import { ExternalLink } from 'lucide-react'
8+
import Link from 'next/link'
99
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
1010
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
11-
import { AiIconAnimation, Button } from 'ui'
11+
import { Button } from 'ui'
12+
1213
import { Markdown } from '../Markdown'
1314
import { EntityTypeIcon, LintCTA, LintEntity } from './Linter.utils'
1415

@@ -39,6 +40,10 @@ const LintDetail = ({ lint, projectRef, onAskAssistant }: LintDetailProps) => {
3940
})
4041
}
4142

43+
const buildPromptForCopy = () => {
44+
return createLintSummaryPrompt(lint)
45+
}
46+
4247
return (
4348
<div>
4449
<h3 className="text-sm mb-2">Entity</h3>
@@ -58,12 +63,12 @@ const LintDetail = ({ lint, projectRef, onAskAssistant }: LintDetailProps) => {
5863

5964
<h3 className="text-sm mb-2">Resolve</h3>
6065
<div className="flex items-center gap-2">
61-
<Button
62-
icon={<AiIconAnimation className="scale-75 w-3 h-3" />}
63-
onClick={handleAskAssistant}
64-
>
65-
Ask Assistant
66-
</Button>
66+
<AiAssistantDropdown
67+
label="Ask Assistant"
68+
buildPrompt={buildPromptForCopy}
69+
onOpenAssistant={handleAskAssistant}
70+
telemetrySource="lint_detail"
71+
/>
6772
<LintCTA title={lint.name} projectRef={projectRef} metadata={lint.metadata} />
6873
<Button asChild type="text">
6974
<Link

apps/studio/components/interfaces/QueryPerformance/QueryDetail.tsx

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,23 @@
1-
import { Lightbulb, ChevronsUpDown } from 'lucide-react'
2-
import { useEffect, useState } from 'react'
3-
import dynamic from 'next/dynamic'
4-
51
import { useFlag } from 'common'
6-
import { formatSql } from 'lib/formatSql'
7-
import {
8-
AlertDescription_Shadcn_,
9-
AlertTitle_Shadcn_,
10-
Alert_Shadcn_,
11-
Button,
12-
cn,
13-
AiIconAnimation,
14-
} from 'ui'
152
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
3+
import { AiAssistantDropdown } from 'components/ui/AiAssistantDropdown'
4+
import { formatSql } from 'lib/formatSql'
5+
import { useTrack } from 'lib/telemetry/track'
6+
import { ChevronsUpDown, Lightbulb } from 'lucide-react'
7+
import dynamic from 'next/dynamic'
8+
import { useEffect, useState } from 'react'
169
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
1710
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
11+
import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Button, cn } from 'ui'
12+
1813
import { QueryPanelContainer, QueryPanelSection } from './QueryPanel'
14+
import { buildQueryExplanationPrompt } from './QueryPerformance.ai'
1915
import {
2016
QUERY_PERFORMANCE_COLUMNS,
2117
QUERY_PERFORMANCE_REPORT_TYPES,
2218
} from './QueryPerformance.constants'
2319
import { QueryPerformanceRow } from './QueryPerformance.types'
24-
import { buildQueryExplanationPrompt } from './QueryPerformance.ai'
2520
import { formatDuration } from './QueryPerformance.utils'
26-
import { useTrack } from 'lib/telemetry/track'
2721

2822
interface QueryDetailProps {
2923
selectedRow?: QueryPerformanceRow
@@ -82,20 +76,27 @@ export const QueryDetail = ({ selectedRow, onClickViewSuggestion, onClose }: Que
8276
onClose?.()
8377
}
8478

79+
const buildPromptForCopy = () => {
80+
if (!selectedRow?.query) return ''
81+
82+
const { query, prompt } = buildQueryExplanationPrompt(selectedRow)
83+
return `${prompt}\n\nSQL Query:\n\`\`\`sql\n${query}\n\`\`\``
84+
}
85+
8586
return (
8687
<QueryPanelContainer>
8788
<QueryPanelSection className="pt-2 border-b relative">
8889
<div className="flex items-center justify-between mb-4">
8990
<h4>Query pattern</h4>
9091
{showExplainWithAiInQueryPerformance && (
91-
<Button
92-
type="default"
92+
<AiAssistantDropdown
93+
label="Explain with AI"
94+
buildPrompt={buildPromptForCopy}
95+
onOpenAssistant={handleExplainQuery}
96+
telemetrySource="query_performance"
9397
size="tiny"
94-
icon={<AiIconAnimation size={14} />}
95-
onClick={handleExplainQuery}
96-
>
97-
Explain with AI
98-
</Button>
98+
type="default"
99+
/>
99100
)}
100101
</div>
101102
<div

0 commit comments

Comments
 (0)