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
21 changes: 17 additions & 4 deletions apps/studio/components/grid/components/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { toast } from 'sonner'

import { useDispatch, useTrackedState } from 'components/grid/store/Store'
import type { Filter, Sort, SupaTable } from 'components/grid/types'
import { Markdown } from 'components/interfaces/Markdown'
import { formatTableRowsToSQL } from 'components/interfaces/TableGridEditor/TableEntity.utils'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
Expand All @@ -31,12 +30,22 @@ import {
} from 'ui'
import FilterPopover from './filter/FilterPopover'
import { SortPopover } from './sort'
import Link from 'next/link'

// [Joshen] CSV exports require this guard as a fail-safe if the table is
// just too large for a browser to keep all the rows in memory before
// exporting. Either that or export as multiple CSV sheets with max n rows each
export const MAX_EXPORT_ROW_COUNT = 500000
export const MAX_EXPORT_ROW_COUNT_MESSAGE = `Sorry! We're unable to support exporting row counts larger than ${MAX_EXPORT_ROW_COUNT.toLocaleString()} at the moment. Alternatively, you may consider using [pg_dump](https://supabase.com/docs/reference/cli/supabase-db-dump) via our CLI instead.`
export const MAX_EXPORT_ROW_COUNT_MESSAGE = (
<>
Sorry! We're unable to support exporting row counts larger than $
{MAX_EXPORT_ROW_COUNT.toLocaleString()} at the moment. Alternatively, you may consider using
<Link href="https://supabase.com/docs/reference/cli/supabase-db-dump" target="_blank">
pg_dump
</Link>{' '}
via our CLI instead.
</>
)

export type HeaderProps = {
table: SupaTable
Expand Down Expand Up @@ -290,7 +299,9 @@ const RowHeader = ({ table, sorts, filters }: RowHeaderProps) => {
setIsExporting(true)

if (allRowsSelected && totalRows > MAX_EXPORT_ROW_COUNT) {
toast.error(<Markdown content={MAX_EXPORT_ROW_COUNT_MESSAGE} className="text-foreground" />)
toast.error(
<div className="prose text-sm text-foreground">{MAX_EXPORT_ROW_COUNT_MESSAGE}</div>
)
return setIsExporting(false)
}

Expand Down Expand Up @@ -331,7 +342,9 @@ const RowHeader = ({ table, sorts, filters }: RowHeaderProps) => {
setIsExporting(true)

if (allRowsSelected && totalRows > MAX_EXPORT_ROW_COUNT) {
toast.error(<Markdown content={MAX_EXPORT_ROW_COUNT_MESSAGE} className="text-foreground" />)
toast.error(
<div className="prose text-sm text-foreground">{MAX_EXPORT_ROW_COUNT_MESSAGE}</div>
)
return setIsExporting(false)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ const PolicyRow = ({
open: true,
sqlSnippets: [sql],
initialInput: `Update the policy with name "${policy.name}" in the ${policy.schema} schema on the ${policy.table} table. It should...`,
suggestions: {
title: `I can help you make a change to the policy "${policy.name}" in the ${policy.schema} schema on the ${policy.table} table, here are a few example prompts to get you started:`,
prompts: [
'Tell me how I can improve this policy...',
'Duplicate this policy for another table...',
'Add extra conditions to this policy...',
],
},
})
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
MAX_EXPORT_ROW_COUNT_MESSAGE,
} from 'components/grid/components/header/Header'
import { parseSupaTable } from 'components/grid/SupabaseGrid.utils'
import { Markdown } from 'components/interfaces/Markdown'
import {
formatTableRowsToSQL,
getEntityLintDetails,
Expand Down Expand Up @@ -117,7 +116,7 @@ const EntityListItem: ItemRenderer<Entity, EntityListItemProps> = ({
})
if (isTableLike(table) && table.live_rows_estimate > MAX_EXPORT_ROW_COUNT) {
return toast.error(
<Markdown content={MAX_EXPORT_ROW_COUNT_MESSAGE} className="text-foreground" />,
<div className="text-foreground prose text-sm">{MAX_EXPORT_ROW_COUNT_MESSAGE}</div>,
{ id: toastId }
)
}
Expand Down Expand Up @@ -171,7 +170,7 @@ const EntityListItem: ItemRenderer<Entity, EntityListItemProps> = ({

if (isTableLike(table) && table.live_rows_estimate > MAX_EXPORT_ROW_COUNT) {
return toast.error(
<Markdown content={MAX_EXPORT_ROW_COUNT_MESSAGE} className="text-foreground" />,
<div className="text-foreground prose text-sm">{MAX_EXPORT_ROW_COUNT_MESSAGE}</div>,
{ id: toastId }
)
}
Expand Down
37 changes: 34 additions & 3 deletions apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import AIOnboarding from './AIOnboarding'
import CollapsibleCodeBlock from './CollapsibleCodeBlock'
import { Message } from './Message'
import { useParams } from 'common/hooks'
import { useSearchParamsShallow } from 'common/hooks'
import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
import { useRouter } from 'next/router'

const MemoizedMessage = memo(
({ message, isLoading }: { message: MessageType; isLoading: boolean }) => {
Expand Down Expand Up @@ -71,14 +75,18 @@ export const AIAssistant = ({
className,
onResetConversation,
}: AIAssistantProps) => {
const router = useRouter()
const project = useSelectedProject()
const isOptedInToAI = useOrgOptedIntoAi()
const selectedOrganization = useSelectedOrganization()
const { id: entityId } = useParams()
const searchParams = useSearchParamsShallow()
const includeSchemaMetadata = isOptedInToAI || !IS_PLATFORM

const disablePrompts = useFlag('disableAssistantPrompts')
const { snippets } = useSqlEditorV2StateSnapshot()
const { aiAssistantPanel, setAiAssistantPanel } = useAppStateSnapshot()
const { initialInput, sqlSnippets, suggestions } = aiAssistantPanel
const { open, initialInput, sqlSnippets, suggestions } = aiAssistantPanel

const inputRef = useRef<HTMLTextAreaElement>(null)
const bottomRef = useRef<HTMLDivElement>(null)
Expand All @@ -93,6 +101,10 @@ export const AIAssistant = ({
const { data: check } = useCheckOpenAIKeyQuery()
const isApiKeySet = IS_PLATFORM || !!check?.hasKey

const isInSQLEditor = router.pathname.includes('/sql/[id]')
const snippet = snippets[entityId ?? '']
const snippetContent = snippet?.snippet?.content?.sql

const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: selectedOrganization?.slug })
const hasHipaaAddon = subscriptionHasHipaaAddon(subscription)

Expand All @@ -102,6 +114,9 @@ export const AIAssistant = ({
schema: 'public',
})

const currentTable = tables?.find((t) => t.id.toString() === entityId)
const currentSchema = searchParams?.get('schema') ?? 'public'

const { mutate: sendEvent } = useSendEventMutation()
const sendTelemetryEvent = (value: string) => {
sendEvent({
Expand All @@ -126,6 +141,8 @@ export const AIAssistant = ({
includeSchemaMetadata,
projectRef: project?.ref,
connectionString: project?.connectionString,
schema: currentSchema,
table: currentTable?.name,
},
})

Expand Down Expand Up @@ -264,6 +281,20 @@ export const AIAssistant = ({
}
}, [messages, isChatLoading, setAiAssistantPanel])

// Remove suggestions if sqlSnippets were removed
useEffect(() => {
if (!sqlSnippets || sqlSnippets.length === 0) {
setAiAssistantPanel({ suggestions: undefined })
}
}, [sqlSnippets, suggestions, setAiAssistantPanel])

useEffect(() => {
if (open && isInSQLEditor && !!snippetContent) {
setAiAssistantPanel({ sqlSnippets: [snippetContent] })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, isInSQLEditor, snippetContent])

if (isLoadingTables) {
return (
<div className="h-full w-full flex justify-center items-center">
Expand Down Expand Up @@ -329,7 +360,7 @@ export const AIAssistant = ({
})}
</div>
{renderedMessages}
{last(messages)?.role === 'user' && (
{(last(messages)?.role === 'user' || last(messages)?.content?.length === 0) && (
<motion.div className="text-foreground-lighter text-sm flex gap-1.5 items-center">
<span>Thinking</span>
<div className="flex gap-1">
Expand Down Expand Up @@ -363,7 +394,7 @@ export const AIAssistant = ({
</h3>
{suggestions.title && <p>{suggestions.title}</p>}
<div className="-mx-3 mt-4 mb-12">
{suggestions?.prompts.map((prompt) => (
{suggestions?.prompts?.map((prompt) => (
<Button
size="small"
icon={<FileText strokeWidth={1.5} size={16} />}
Expand Down
6 changes: 4 additions & 2 deletions apps/studio/components/ui/AIAssistantPanel/SqlSnippet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,9 @@ export const SqlCard = ({
className="w-7 h-7"
icon={<Code size={14} />}
onClick={() => setShowCode(!showCode)}
tooltip={{ content: { side: 'bottom', text: 'Show query' } }}
tooltip={{
content: { side: 'bottom', text: showCode ? 'Hide query' : 'Show query' },
}}
/>

{!isInSQLEditor || isInNewSnippet ? (
Expand Down Expand Up @@ -314,7 +316,7 @@ export const SqlCard = ({
value={sql}
language="sql"
className={cn(
'max-w-full max-h-96 block !bg-transparent !py-3 !px-3.5 prose dark:prose-dark border-0 border-t text-foreground !rounded-none w-full',
'max-h-96 max-w-none block !bg-transparent !py-3 !px-3.5 prose dark:prose-dark border-0 border-t text-foreground !rounded-none w-full',
'[&>code]:m-0 [&>code>span]:flex [&>code>span]:flex-wrap [&>code]:block [&>code>span]:text-foreground'
)}
/>
Expand Down
11 changes: 2 additions & 9 deletions apps/studio/components/ui/Forms/FormHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import ReactMarkdown from 'react-markdown'

import { Markdown } from 'components/interfaces/Markdown'
import { ReactNode } from 'react'
import { cn } from 'ui'
import { DocsButton } from '../DocsButton'
Expand All @@ -21,12 +18,8 @@ const FormHeader = ({
return (
<div className={cn(`mb-6 flex items-center justify-between gap-x-4 ${className}`)}>
<div className="space-y-1">
<h3 className="text-foreground text-xl">
<ReactMarkdown unwrapDisallowed disallowedElements={['p']}>
{title}
</ReactMarkdown>
</h3>
{description && <Markdown content={description} className="max-w-full" />}
<h3 className="text-foreground text-xl prose">{title}</h3>
{description && <div className="prose text-sm max-w-full">{description}</div>}
</div>
<div className="flex items-center gap-x-2">
{docsUrl !== undefined && <DocsButton href={docsUrl} />}
Expand Down
9 changes: 3 additions & 6 deletions apps/studio/components/ui/Panel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Megaphone } from 'lucide-react'
import { forwardRef, PropsWithChildren, ReactNode } from 'react'
import ReactMarkdown from 'react-markdown'
import { Badge, Button, Loading, cn } from 'ui'

interface PanelProps {
Expand Down Expand Up @@ -109,11 +108,9 @@ const PanelNotice = forwardRef<
{/* <span className="font-medium text-foreground text-sm">{title}</span> */}
</div>
{description && (
<span className="text-foreground-light text-sm flex flex-col gap-0">
<ReactMarkdown className="prose text-xs max-w-none [&_p]:mt-2 [&_p]:mb-0">
{description}
</ReactMarkdown>
</span>
<div className="text-foreground-light text-sm flex flex-col gap-0">
<div className="prose text-xs max-w-none [&_p]:mt-2 [&_p]:mb-0">{description}</div>
</div>
)}
</div>

Expand Down
5 changes: 4 additions & 1 deletion apps/studio/pages/api/ai/sql/generate-v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}

async function handlePost(req: NextApiRequest, res: NextApiResponse) {
const { messages, projectRef, connectionString, includeSchemaMetadata } = req.body
const { messages, projectRef, connectionString, includeSchemaMetadata, schema, table } = req.body

if (!projectRef) {
return res.status(400).json({
Expand Down Expand Up @@ -109,6 +109,9 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse) {
- First look at the list of provided schemas and if needed, get more information about a schema. You will almost always need to retrieve information about the public schema before answering a question. If the question is about users, also retrieve the auth schema.

Here are the existing database schema names you can retrieve: ${schemas}

${schema !== undefined ? `The user is currently looking at the ${schema} schema.` : ''}
${table !== undefined ? `The user is currently looking at the ${table} table.` : ''}
`,
messages,
tools: getTools({ projectRef, connectionString, authorization, includeSchemaMetadata }),
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/state/app-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export type CommonDatabaseEntity = {

export type SuggestionsType = {
title: string
prompts: string[]
prompts?: string[]
}

type AiAssistantPanelType = {
Expand Down
Loading