Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions apps/studio/components/grid/components/grid/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,12 @@ export const Grid = memo(

return (
<div
data-testid="table-editor-grid-container"
className={cn('flex flex-col relative transition-colors', containerClass)}
style={{ width: width || '100%', height: height || '50vh' }}
onDragOver={onDragOver}
onDragLeave={onDragOver}
onDrop={onFileDrop}
>
{/* Render no rows fallback outside of the DataGrid */}
{(rows ?? []).length === 0 && (
Expand All @@ -241,9 +245,6 @@ export const Grid = memo(
isTableEmpty && isDraggedOver && 'border-2 border-dashed',
isValidFileDraggedOver ? 'border-brand-600' : 'border-destructive-600'
)}
onDragOver={onDragOver}
onDragLeave={onDragOver}
onDrop={onFileDrop}
>
{isLoading && !isDisabled && (
<GenericSkeletonLoader className="w-full top-9 absolute p-2" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { ArrowUp, Eye, Code, HelpCircle } from 'lucide-react'

import { useFlag } from 'common'
import { AiIconAnimation, Button } from 'ui'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { AiAssistantDropdown } from 'components/ui/AiAssistantDropdown'
import { Code, Eye, HelpCircle } from 'lucide-react'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'

import { buildExplainPrompt } from './ExplainVisualizer.ai'
import type { QueryPlanRow } from './ExplainVisualizer.types'
import { Tooltip, TooltipContent, TooltipTrigger } from 'ui'

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

const handleExplainWithAI = () => {
if (!id) return
const getPromptData = () => {
if (!id) return null
const snippet = snapV2.snippets[id]?.snippet
if (!snippet?.content?.sql) return
if (!snippet?.content?.sql) return null

const { query, prompt } = buildExplainPrompt({
return buildExplainPrompt({
sql: snippet.content.sql,
explainPlanRows: (rows as QueryPlanRow[]) ?? [],
})
}

const handleExplainWithAI = () => {
const promptData = getPromptData()
if (!promptData) return

openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
aiSnap.newChat({
sqlSnippets: [
{
label: 'Query',
content: query,
content: promptData.query,
},
],
initialMessage: prompt,
initialMessage: promptData.prompt,
})
}

const buildPromptForCopy = () => {
const promptData = getPromptData()
if (!promptData) return ''

// Combine SQL and prompt into a single copyable text
return `${promptData.prompt}\n\nSQL Query:\n\`\`\`sql\n${promptData.query}\n\`\`\``
}

const hasSummaryStats =
isVisual && summary && (summary.totalTime > 0 || (summary.hasSeqScan && !summary.hasIndexScan))

Expand Down Expand Up @@ -111,14 +123,14 @@ export function ExplainHeader({ mode, onToggleMode, summary, id, rows }: Explain
</div>
<div className="flex items-center gap-2">
{id && rows && (
<Button
type="default"
<AiAssistantDropdown
label="Explain with AI"
buildPrompt={buildPromptForCopy}
onOpenAssistant={handleExplainWithAI}
telemetrySource="explain_visualizer"
size="tiny"
icon={<AiIconAnimation size={14} />}
onClick={handleExplainWithAI}
>
Explain with AI
</Button>
type="default"
/>
)}
<Button
type="default"
Expand Down
52 changes: 33 additions & 19 deletions apps/studio/components/interfaces/Home/AdvisorWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { Activity, ExternalLink, Shield } from 'lucide-react'
import Link from 'next/link'
import { useCallback, useMemo, useState } from 'react'

import { useParams } from 'common'
import { LINTER_LEVELS } from 'components/interfaces/Linter/Linter.constants'
import { EntityTypeIcon, createLintSummaryPrompt } from 'components/interfaces/Linter/Linter.utils'
import { createLintSummaryPrompt, EntityTypeIcon } from 'components/interfaces/Linter/Linter.utils'
import { useQueryPerformanceQuery } from 'components/interfaces/Reports/Reports.queries'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { AiAssistantDropdown } from 'components/ui/AiAssistantDropdown'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { Lint, useProjectLintsQuery } from 'data/lint/lint-query'
import { useTrack } from 'lib/telemetry/track'
import { Activity, ExternalLink, Shield } from 'lucide-react'
import Link from 'next/link'
import { useCallback, useMemo, useState } from 'react'
import { useAdvisorStateSnapshot } from 'state/advisor-state'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
import {
AiIconAnimation,
Card,
CardContent,
CardHeader,
Expand Down Expand Up @@ -50,6 +50,7 @@ export const AdvisorWidget = () => {
const snap = useAiAssistantStateSnapshot()
const { openSidebar } = useSidebarManagerSnapshot()
const { setSelectedItem } = useAdvisorStateSnapshot()
const track = useTrack()

const securityLints = useMemo(
() => (lints ?? []).filter((lint: Lint) => lint.categories.includes('SECURITY')),
Expand Down Expand Up @@ -153,23 +154,36 @@ export const AdvisorWidget = () => {
{lintText.replace(/\\`/g, '`')}
</p>
</button>
<ButtonTooltip
type="text"
className="px-1 opacity-0 group-hover:opacity-100 w-7"
icon={<AiIconAnimation size={16} />}
<div
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
snap.newChat({
name: 'Summarize lint',
initialInput: createLintSummaryPrompt(lint),
})
}}
tooltip={{
content: { side: 'bottom', text: 'Help me fix this issue' },
}}
/>
className="opacity-0 group-hover:opacity-100"
>
<AiAssistantDropdown
label="Ask Assistant"
iconOnly
tooltip="Help me fix this issue"
buildPrompt={() => createLintSummaryPrompt(lint)}
onOpenAssistant={() => {
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
snap.newChat({
name: 'Summarize lint',
initialInput: createLintSummaryPrompt(lint),
})
track('advisor_assistant_button_clicked', {
origin: 'homepage',
advisorCategory: lint.categories[0],
advisorType: lint.name,
advisorLevel: lint.level,
})
}}
telemetrySource="advisor_widget"
type="text"
className="px-1 w-7"
/>
</div>
</div>
</li>
)
Expand Down
52 changes: 29 additions & 23 deletions apps/studio/components/interfaces/HomeNew/AdvisorSection.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { BarChart, Shield } from 'lucide-react'
import { useCallback, useMemo } from 'react'

import { useParams } from 'common'
import { LINTER_LEVELS } from 'components/interfaces/Linter/Linter.constants'
import { createLintSummaryPrompt } from 'components/interfaces/Linter/Linter.utils'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { AiAssistantDropdown } from 'components/ui/AiAssistantDropdown'
import { Lint, useProjectLintsQuery } from 'data/lint/lint-query'
import { useTrack } from 'lib/telemetry/track'
import { BarChart, Shield } from 'lucide-react'
import { useCallback, useMemo } from 'react'
import { useAdvisorStateSnapshot } from 'state/advisor-state'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
import { AiIconAnimation, Button, Card, CardContent, CardHeader, CardTitle } from 'ui'
import { Row } from 'ui-patterns'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'

import { Markdown } from '../Markdown'

export const AdvisorSection = ({ showEmptyState = false }: { showEmptyState?: boolean }) => {
Expand Down Expand Up @@ -114,29 +114,35 @@ export const AdvisorSection = ({ showEmptyState = false }: { showEmptyState?: bo
)}
<CardTitle className="text-foreground-light">{lint.categories[0]}</CardTitle>
</div>
<ButtonTooltip
type="text"
className="w-7 h-7"
icon={<AiIconAnimation size={16} />}
<div
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
snap.newChat({
name: 'Summarize lint',
initialInput: createLintSummaryPrompt(lint),
})
track('advisor_assistant_button_clicked', {
origin: 'homepage',
advisorCategory: lint.categories[0],
advisorType: lint.name,
advisorLevel: lint.level,
})
}}
tooltip={{
content: { side: 'bottom', text: 'Help me fix this issue' },
}}
/>
>
<AiAssistantDropdown
label="Ask Assistant"
iconOnly
tooltip="Help me fix this issue"
buildPrompt={() => createLintSummaryPrompt(lint)}
onOpenAssistant={() => {
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
snap.newChat({
name: 'Summarize lint',
initialInput: createLintSummaryPrompt(lint),
})
track('advisor_assistant_button_clicked', {
origin: 'homepage',
advisorCategory: lint.categories[0],
advisorType: lint.name,
advisorLevel: lint.level,
})
}}
telemetrySource="advisor_section"
type="text"
className="w-7 h-7"
/>
</div>
</CardHeader>
<CardContent className="p-6 pt-16 flex flex-col justify-end flex-1 overflow-auto">
<h3 className="mb-1">{lint.title}</h3>
Expand Down
23 changes: 14 additions & 9 deletions apps/studio/components/interfaces/Linter/LintDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import Link from 'next/link'

import { createLintSummaryPrompt, lintInfoMap } from 'components/interfaces/Linter/Linter.utils'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { AiAssistantDropdown } from 'components/ui/AiAssistantDropdown'
import { Lint } from 'data/lint/lint-query'
import { DOCS_URL } from 'lib/constants'
import { useTrack } from 'lib/telemetry/track'
import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
import { AiIconAnimation, Button } from 'ui'
import { Button } from 'ui'

import { Markdown } from '../Markdown'
import { EntityTypeIcon, LintCTA, LintEntity } from './Linter.utils'

Expand Down Expand Up @@ -39,6 +40,10 @@ const LintDetail = ({ lint, projectRef, onAskAssistant }: LintDetailProps) => {
})
}

const buildPromptForCopy = () => {
return createLintSummaryPrompt(lint)
}

return (
<div>
<h3 className="text-sm mb-2">Entity</h3>
Expand All @@ -58,12 +63,12 @@ const LintDetail = ({ lint, projectRef, onAskAssistant }: LintDetailProps) => {

<h3 className="text-sm mb-2">Resolve</h3>
<div className="flex items-center gap-2">
<Button
icon={<AiIconAnimation className="scale-75 w-3 h-3" />}
onClick={handleAskAssistant}
>
Ask Assistant
</Button>
<AiAssistantDropdown
label="Ask Assistant"
buildPrompt={buildPromptForCopy}
onOpenAssistant={handleAskAssistant}
telemetrySource="lint_detail"
/>
<LintCTA title={lint.name} projectRef={projectRef} metadata={lint.metadata} />
<Button asChild type="text">
<Link
Expand Down
45 changes: 23 additions & 22 deletions apps/studio/components/interfaces/QueryPerformance/QueryDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
import { Lightbulb, ChevronsUpDown } from 'lucide-react'
import { useEffect, useState } from 'react'
import dynamic from 'next/dynamic'

import { useFlag } from 'common'
import { formatSql } from 'lib/formatSql'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
cn,
AiIconAnimation,
} from 'ui'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { AiAssistantDropdown } from 'components/ui/AiAssistantDropdown'
import { formatSql } from 'lib/formatSql'
import { useTrack } from 'lib/telemetry/track'
import { ChevronsUpDown, Lightbulb } from 'lucide-react'
import dynamic from 'next/dynamic'
import { useEffect, useState } from 'react'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Button, cn } from 'ui'

import { QueryPanelContainer, QueryPanelSection } from './QueryPanel'
import { buildQueryExplanationPrompt } from './QueryPerformance.ai'
import {
QUERY_PERFORMANCE_COLUMNS,
QUERY_PERFORMANCE_REPORT_TYPES,
} from './QueryPerformance.constants'
import { QueryPerformanceRow } from './QueryPerformance.types'
import { buildQueryExplanationPrompt } from './QueryPerformance.ai'
import { formatDuration } from './QueryPerformance.utils'
import { useTrack } from 'lib/telemetry/track'

interface QueryDetailProps {
selectedRow?: QueryPerformanceRow
Expand Down Expand Up @@ -82,20 +76,27 @@ export const QueryDetail = ({ selectedRow, onClickViewSuggestion, onClose }: Que
onClose?.()
}

const buildPromptForCopy = () => {
if (!selectedRow?.query) return ''

const { query, prompt } = buildQueryExplanationPrompt(selectedRow)
return `${prompt}\n\nSQL Query:\n\`\`\`sql\n${query}\n\`\`\``
}

return (
<QueryPanelContainer>
<QueryPanelSection className="pt-2 border-b relative">
<div className="flex items-center justify-between mb-4">
<h4>Query pattern</h4>
{showExplainWithAiInQueryPerformance && (
<Button
type="default"
<AiAssistantDropdown
label="Explain with AI"
buildPrompt={buildPromptForCopy}
onOpenAssistant={handleExplainQuery}
telemetrySource="query_performance"
size="tiny"
icon={<AiIconAnimation size={14} />}
onClick={handleExplainQuery}
>
Explain with AI
</Button>
type="default"
/>
)}
</div>
<div
Expand Down
Loading
Loading