mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 03:45:06 +03:00
## Problem Our `<Button>` component breaks the default `button` contract by redefining the `type` prop to set its variant (`primary`, `default`, etc) instead of the button type (`submit`, `button`, etc). This is confusing and forces to write more code when using it with shadcn components that expect/inject the standard button props. ## Solution - rename the `type` prop to `variant` - rename the `htmlType` prop to `type` - propagate the changes where necessary - format code ## How to test As this is just prop renaming, if it builds it's ok --------- Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
178 lines
6.8 KiB
TypeScript
178 lines
6.8 KiB
TypeScript
import { useParams } from 'common'
|
|
import { motion } from 'framer-motion'
|
|
import { BarChart, FileText, Shield } from 'lucide-react'
|
|
import { Button, Skeleton } from 'ui'
|
|
|
|
import { codeSnippetPrompts, defaultPrompts } from './AIAssistant.prompts'
|
|
import type { SqlSnippet } from './AIAssistant.types'
|
|
import { LINTER_LEVELS } from '@/components/interfaces/Linter/Linter.constants'
|
|
import { createLintSummaryPrompt } from '@/components/interfaces/Linter/Linter.utils'
|
|
import { useProjectLintsQuery, type Lint } from '@/data/lint/lint-query'
|
|
|
|
interface AIOnboardingProps {
|
|
sqlSnippets?: SqlSnippet[]
|
|
suggestions?: {
|
|
title?: string
|
|
prompts?: { label: string; description: string }[]
|
|
}
|
|
onValueChange: (value: string) => void
|
|
onFocusInput?: () => void
|
|
}
|
|
|
|
export const AIOnboarding = ({
|
|
sqlSnippets,
|
|
suggestions,
|
|
onValueChange,
|
|
onFocusInput,
|
|
}: AIOnboardingProps) => {
|
|
const prompts = suggestions?.prompts
|
|
? suggestions.prompts.map((suggestion) => ({
|
|
title: suggestion.label,
|
|
prompt: suggestion.description,
|
|
icon: <FileText strokeWidth={1.25} size={14} className="w-4! h-4!" />,
|
|
}))
|
|
: sqlSnippets && sqlSnippets.length > 0
|
|
? codeSnippetPrompts
|
|
: defaultPrompts
|
|
|
|
const { ref: projectRef } = useParams()
|
|
const {
|
|
data: lints,
|
|
isPending: isLoadingLints,
|
|
isFetching: isFetchingLints,
|
|
} = useProjectLintsQuery({ projectRef })
|
|
const isLintsLoading = isLoadingLints || isFetchingLints
|
|
|
|
const errorLints: Lint[] = (lints?.filter((lint) => lint.level === LINTER_LEVELS.ERROR) ??
|
|
[]) as Lint[]
|
|
const securityErrorLints = errorLints.filter((lint) => lint.categories?.[0] === 'SECURITY')
|
|
const performanceErrorLints = errorLints.filter((lint) => lint.categories?.[0] !== 'SECURITY')
|
|
|
|
return (
|
|
<div className="flex-1 overflow-y-auto">
|
|
<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 ">
|
|
<h2 className="heading-section text-foreground mx-4">How can I assist you?</h2>
|
|
{suggestions?.prompts?.length ? (
|
|
<div>
|
|
<h3 className="heading-meta text-foreground-light mb-3 mx-4">Suggestions</h3>
|
|
{prompts.map((item, index) => (
|
|
<motion.div
|
|
key={item.title}
|
|
initial={{ y: 5, opacity: 0 }}
|
|
animate={{ y: 0, opacity: 1 }}
|
|
transition={{ duration: 0.3, delay: index * 0.05 }}
|
|
>
|
|
<Button
|
|
size="small"
|
|
variant="text"
|
|
className="w-full justify-start border-b hover:border-b-0 hover:rounded-md rounded-none"
|
|
icon={
|
|
<FileText strokeWidth={1.5} size={14} className="text-foreground-light" />
|
|
}
|
|
onClick={() => {
|
|
onValueChange(item.prompt)
|
|
onFocusInput?.()
|
|
}}
|
|
>
|
|
{item.title}
|
|
</Button>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<>
|
|
{isLintsLoading ? (
|
|
<div className="px-4 flex flex-col gap-2">
|
|
{Array.from({ length: 6 }).map((_, index) => (
|
|
<Skeleton key={`loader-${index}`} className="h-4 w-full" />
|
|
))}
|
|
</div>
|
|
) : (
|
|
<>
|
|
{performanceErrorLints.length > 0 && (
|
|
<div className="mb-4">
|
|
<h3 className="heading-meta text-foreground-light mb-3 mx-4">
|
|
Improve Performance
|
|
</h3>
|
|
{performanceErrorLints.map((lint, index) => {
|
|
return (
|
|
<Button
|
|
key={`${lint.name}-${index}`}
|
|
size="small"
|
|
variant="text"
|
|
className="w-full justify-start"
|
|
icon={
|
|
<BarChart
|
|
strokeWidth={1.5}
|
|
size={14}
|
|
className="text-foreground-light"
|
|
/>
|
|
}
|
|
onClick={() => {
|
|
onValueChange(createLintSummaryPrompt(lint))
|
|
onFocusInput?.()
|
|
}}
|
|
>
|
|
{lint.detail ? lint.detail.replace('\\`', '') : lint.title}
|
|
</Button>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
|
|
{securityErrorLints.length > 0 && (
|
|
<div className="mb-4">
|
|
<h3 className="heading-meta text-foreground-light mb-3 mx-4">
|
|
Improve Security
|
|
</h3>
|
|
{securityErrorLints.map((lint, index) => {
|
|
return (
|
|
<Button
|
|
key={`${lint.name}-${index}`}
|
|
size="small"
|
|
variant="text"
|
|
className="w-full justify-start"
|
|
icon={<Shield strokeWidth={1.5} size={14} className="text-warning" />}
|
|
onClick={() => {
|
|
onValueChange(createLintSummaryPrompt(lint))
|
|
onFocusInput?.()
|
|
}}
|
|
>
|
|
{lint.detail ? lint.detail.replace(/\\`/g, '') : lint.title}
|
|
</Button>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<h3 className="heading-meta text-foreground-light mb-3 mx-4">Ideas</h3>
|
|
{prompts.map((item, index) => (
|
|
<Button
|
|
key={`${item.title}-${index}`}
|
|
size="small"
|
|
variant="text"
|
|
className="w-full justify-start"
|
|
icon={
|
|
<FileText strokeWidth={1.5} size={14} className="text-foreground-light" />
|
|
}
|
|
onClick={() => {
|
|
onValueChange(item.prompt)
|
|
onFocusInput?.()
|
|
}}
|
|
>
|
|
{item.title}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|