mirror of
https://github.com/supabase/supabase.git
synced 2026-10-18 07:39:07 +03:00
Merge branch 'feat/mamba-d1' of github.com:supabase/supabase into feat/mamba-d1
This commit is contained in:
commit
26500b019a
20 files changed
+200
-71
No files matched your search
@@ -1,8 +1,10 @@
|
||||
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { AiIconAnimation, Button } from 'ui'
|
||||
|
||||
const AssistantButton = () => {
|
||||
const snap = useAiAssistantStateSnapshot()
|
||||
const { setEditorPanel } = useAppStateSnapshot()
|
||||
|
||||
return (
|
||||
<Button
|
||||
@@ -12,6 +14,7 @@ const AssistantButton = () => {
|
||||
className="h-full w-full rounded-none"
|
||||
onClick={() => {
|
||||
snap.toggleAssistant()
|
||||
setEditorPanel({ open: false })
|
||||
}}
|
||||
>
|
||||
<AiIconAnimation allowHoverEffect size={20} />
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { SqlEditor } from 'icons'
|
||||
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { Button } from 'ui'
|
||||
import { SqlEditor } from 'icons'
|
||||
|
||||
const InlineEditorButton = () => {
|
||||
const { closeAssistant } = useAiAssistantStateSnapshot()
|
||||
const { setEditorPanel, editorPanel } = useAppStateSnapshot()
|
||||
|
||||
return (
|
||||
@@ -12,6 +14,7 @@ const InlineEditorButton = () => {
|
||||
id="editor-trigger"
|
||||
className="h-full w-full rounded-none text-foreground-light"
|
||||
onClick={() => {
|
||||
closeAssistant()
|
||||
setEditorPanel({ open: !editorPanel.open })
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -237,10 +237,11 @@ const ProjectLayout = forwardRef<HTMLDivElement, PropsWithChildren<ProjectLayout
|
||||
'w-screen h-[100dvh] md:h-auto md:w-auto'
|
||||
)}
|
||||
>
|
||||
{aiSnap.open && (
|
||||
{aiSnap.open ? (
|
||||
<AIAssistant className="w-full h-[100dvh] md:h-full max-h-[100dvh]" />
|
||||
)}
|
||||
{editorPanel.open && <EditorPanel />}
|
||||
) : editorPanel.open ? (
|
||||
<EditorPanel />
|
||||
) : null}
|
||||
</ResizablePanel>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -48,14 +48,32 @@ import { Message } from './Message'
|
||||
import { useAutoScroll } from './hooks'
|
||||
|
||||
const MemoizedMessage = memo(
|
||||
({ message, isLoading }: { message: MessageType; isLoading: boolean }) => {
|
||||
({
|
||||
message,
|
||||
isLoading,
|
||||
onResults,
|
||||
}: {
|
||||
message: MessageType
|
||||
isLoading: boolean
|
||||
onResults: ({
|
||||
messageId,
|
||||
resultId,
|
||||
results,
|
||||
}: {
|
||||
messageId: string
|
||||
resultId?: string
|
||||
results: any[]
|
||||
}) => void
|
||||
}) => {
|
||||
return (
|
||||
<Message
|
||||
key={message.id}
|
||||
id={message.id}
|
||||
role={message.role}
|
||||
content={message.content}
|
||||
readOnly={message.role === 'user'}
|
||||
isLoading={isLoading}
|
||||
onResults={onResults}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -134,6 +152,9 @@ export const AIAssistant = ({ className }: AIAssistantProps) => {
|
||||
}
|
||||
}, [])
|
||||
|
||||
// TODO(refactor): This useChat hook should be moved down into each chat session.
|
||||
// That way we won't have to disable switching chats while the chat is loading,
|
||||
// and don't run the risk of messages getting mixed up between chats.
|
||||
const {
|
||||
messages: chatMessages,
|
||||
isLoading: isChatLoading,
|
||||
@@ -160,6 +181,21 @@ export const AIAssistant = ({ className }: AIAssistantProps) => {
|
||||
const canUpdateOrganization = useCheckPermissions(PermissionAction.UPDATE, 'organizations')
|
||||
const { mutate: updateOrganization, isLoading: isUpdating } = useOrganizationUpdateMutation()
|
||||
|
||||
const updateMessage = useCallback(
|
||||
({
|
||||
messageId,
|
||||
resultId,
|
||||
results,
|
||||
}: {
|
||||
messageId: string
|
||||
resultId?: string
|
||||
results: any[]
|
||||
}) => {
|
||||
snap.updateMessage({ id: messageId, resultId, results })
|
||||
},
|
||||
[snap]
|
||||
)
|
||||
|
||||
const renderedMessages = useMemo(
|
||||
() =>
|
||||
chatMessages.map((message) => {
|
||||
@@ -168,6 +204,7 @@ export const AIAssistant = ({ className }: AIAssistantProps) => {
|
||||
key={message.id}
|
||||
message={message}
|
||||
isLoading={isChatLoading && message.id === chatMessages[chatMessages.length - 1].id}
|
||||
onResults={updateMessage}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
@@ -294,7 +331,7 @@ export const AIAssistant = ({ className }: AIAssistantProps) => {
|
||||
<TooltipContent side="bottom">Current chat: {currentChat}</TooltipContent>
|
||||
</Tooltip>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<AIAssistantChatSelector />
|
||||
<AIAssistantChatSelector disabled={isChatLoading} />
|
||||
<ButtonTooltip
|
||||
type="default"
|
||||
size="tiny"
|
||||
|
||||
@@ -2,6 +2,7 @@ export type SupportedAssistantEntities = 'rls-policies' | 'functions'
|
||||
export type SupportedAssistantQuickPromptTypes = 'suggest' | 'examples' | 'ask'
|
||||
|
||||
export interface AssistantSnippetProps {
|
||||
id?: string
|
||||
title?: string
|
||||
isChart?: string
|
||||
runQuery?: string
|
||||
|
||||
@@ -22,9 +22,13 @@ import { ButtonTooltip } from '../ButtonTooltip'
|
||||
|
||||
interface AIAssistantChatSelectorProps {
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export const AIAssistantChatSelector = ({ className }: AIAssistantChatSelectorProps) => {
|
||||
export const AIAssistantChatSelector = ({
|
||||
className,
|
||||
disabled = false,
|
||||
}: AIAssistantChatSelectorProps) => {
|
||||
const snap = useAiAssistantStateSnapshot()
|
||||
|
||||
const [chatSelectorOpen, setChatSelectorOpen] = useState(false)
|
||||
@@ -102,13 +106,15 @@ export const AIAssistantChatSelector = ({ className }: AIAssistantChatSelectorPr
|
||||
<CommandEmpty_Shadcn_>No chats found.</CommandEmpty_Shadcn_>
|
||||
<CommandGroup_Shadcn_>
|
||||
<ScrollArea className={chats.length > 4 ? 'h-40' : ''}>
|
||||
{/* @ts-ignore */}
|
||||
{chats.map(([id, chat]) => (
|
||||
<CommandItem_Shadcn_
|
||||
key={id}
|
||||
value={id}
|
||||
onSelect={() => handleSelectChat(id)}
|
||||
className="flex items-center justify-between gap-2 py-1 w-full overflow-hidden group"
|
||||
keywords={[chat.name]}
|
||||
keywords={!!chat.name ? [chat.name] : undefined}
|
||||
disabled={disabled}
|
||||
>
|
||||
<div className="flex items-center w-full flex-1 min-w-0">
|
||||
{editingChatId === id ? (
|
||||
@@ -200,6 +206,7 @@ export const AIAssistantChatSelector = ({ className }: AIAssistantChatSelectorPr
|
||||
snap.newChat()
|
||||
setChatSelectorOpen(false)
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Plus size={14} strokeWidth={1.5} />
|
||||
<span>New chat</span>
|
||||
|
||||
@@ -19,19 +19,29 @@ const baseMarkdownComponents: Partial<Components> = {
|
||||
h3: Heading3,
|
||||
code: InlineCode,
|
||||
a: Link,
|
||||
pre: MarkdownPre,
|
||||
}
|
||||
|
||||
interface MessageProps {
|
||||
id: string
|
||||
role: 'function' | 'system' | 'user' | 'assistant' | 'data' | 'tool'
|
||||
content?: string
|
||||
isLoading: boolean
|
||||
readOnly?: boolean
|
||||
action?: React.ReactNode
|
||||
variant?: 'default' | 'warning'
|
||||
onResults: ({
|
||||
messageId,
|
||||
resultId,
|
||||
results,
|
||||
}: {
|
||||
messageId: string
|
||||
resultId?: string
|
||||
results: any[]
|
||||
}) => void
|
||||
}
|
||||
|
||||
export const Message = function Message({
|
||||
id,
|
||||
role,
|
||||
content,
|
||||
isLoading,
|
||||
@@ -39,10 +49,19 @@ export const Message = function Message({
|
||||
children,
|
||||
action = null,
|
||||
variant = 'default',
|
||||
onResults,
|
||||
}: PropsWithChildren<MessageProps>) {
|
||||
const isUser = role === 'user'
|
||||
const allMarkdownComponents = useMemo(
|
||||
() => ({ ...markdownComponents, ...baseMarkdownComponents }),
|
||||
const allMarkdownComponents: Partial<Components> = useMemo(
|
||||
() => ({
|
||||
...markdownComponents,
|
||||
...baseMarkdownComponents,
|
||||
pre: ({ children }) => (
|
||||
<MarkdownPre id={id} onResults={onResults}>
|
||||
{children}
|
||||
</MarkdownPre>
|
||||
),
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { DragEvent, memo, ReactNode, useContext, useEffect, useMemo, useRef } from 'react'
|
||||
import {
|
||||
DragEvent,
|
||||
memo,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig'
|
||||
@@ -7,15 +16,16 @@ import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { useFlag } from 'hooks/ui/useFlag'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
|
||||
import { Dashboards } from 'types'
|
||||
import { Badge, cn, CodeBlock, CodeBlockLang } from 'ui'
|
||||
import { DebouncedComponent } from '../DebouncedComponent'
|
||||
import { EdgeFunctionBlock } from '../EdgeFunctionBlock/EdgeFunctionBlock'
|
||||
import { QueryBlock } from '../QueryBlock/QueryBlock'
|
||||
import { AssistantSnippetProps } from './AIAssistant.types'
|
||||
import { identifyQueryType } from './AIAssistant.utils'
|
||||
import CollapsibleCodeBlock from './CollapsibleCodeBlock'
|
||||
import { MessageContext } from './Message'
|
||||
import { EdgeFunctionBlock } from '../EdgeFunctionBlock/EdgeFunctionBlock'
|
||||
|
||||
export const OrderedList = memo(({ children }: { children: ReactNode }) => (
|
||||
<ol className="flex flex-col gap-y-4">{children}</ol>
|
||||
@@ -61,7 +71,9 @@ const MemoizedQueryBlock = memo(
|
||||
isLoading,
|
||||
isDraggable,
|
||||
runQuery,
|
||||
results,
|
||||
onRunQuery,
|
||||
onResults,
|
||||
onDragStart,
|
||||
onUpdateChartConfig,
|
||||
}: {
|
||||
@@ -73,7 +85,9 @@ const MemoizedQueryBlock = memo(
|
||||
isLoading: boolean
|
||||
isDraggable: boolean
|
||||
runQuery: boolean
|
||||
results?: any[]
|
||||
onRunQuery: (queryType: 'select' | 'mutation') => void
|
||||
onResults: (results: any[]) => void
|
||||
onDragStart: (e: DragEvent<Element>) => void
|
||||
onUpdateChartConfig?: ({
|
||||
chart,
|
||||
@@ -84,7 +98,7 @@ const MemoizedQueryBlock = memo(
|
||||
}) => void
|
||||
}) => (
|
||||
<DebouncedComponent
|
||||
delay={500}
|
||||
delay={isLoading ? 500 : 0}
|
||||
value={sql}
|
||||
fallback={
|
||||
<div className="bg-surface-100 border-overlay rounded border shadow-sm px-3 py-2 text-xs">
|
||||
@@ -118,7 +132,9 @@ const MemoizedQueryBlock = memo(
|
||||
isLoading={isLoading}
|
||||
draggable={isDraggable}
|
||||
runQuery={runQuery}
|
||||
results={results}
|
||||
onRunQuery={onRunQuery}
|
||||
onResults={onResults}
|
||||
onDragStart={onDragStart}
|
||||
onUpdateChartConfig={onUpdateChartConfig}
|
||||
/>
|
||||
@@ -127,11 +143,28 @@ const MemoizedQueryBlock = memo(
|
||||
)
|
||||
MemoizedQueryBlock.displayName = 'MemoizedQueryBlock'
|
||||
|
||||
export const MarkdownPre = ({ children }: { children: any }) => {
|
||||
export const MarkdownPre = ({
|
||||
children,
|
||||
id,
|
||||
onResults,
|
||||
}: {
|
||||
children: any
|
||||
id: string
|
||||
onResults: ({
|
||||
messageId,
|
||||
resultId,
|
||||
results,
|
||||
}: {
|
||||
messageId: string
|
||||
resultId?: string
|
||||
results: any[]
|
||||
}) => void
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
const { profile } = useProfile()
|
||||
const { isLoading, readOnly } = useContext(MessageContext)
|
||||
const { mutate: sendEvent } = useSendEventMutation()
|
||||
const snap = useAiAssistantStateSnapshot()
|
||||
const supportSQLBlocks = useFlag('reportsV2')
|
||||
|
||||
const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
|
||||
@@ -158,9 +191,11 @@ export const MarkdownPre = ({ children }: { children: any }) => {
|
||||
)
|
||||
|
||||
const { xAxis, yAxis } = snippetProps
|
||||
const snippetId = snippetProps.id
|
||||
const title = snippetProps.title || (language === 'edge' ? 'Edge Function' : 'SQL Query')
|
||||
const isChart = snippetProps.isChart === 'true'
|
||||
const runQuery = snippetProps.runQuery === 'true'
|
||||
const results = snap.getCachedSQLResults({ messageId: id, snippetId })
|
||||
|
||||
// Strip props from the content for both SQL and edge functions
|
||||
const cleanContent = rawContent.replace(/(?:--|\/\/)\s*props:\s*\{[^}]+\}/, '').trim()
|
||||
@@ -178,6 +213,13 @@ export const MarkdownPre = ({ children }: { children: any }) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [snippetProps])
|
||||
|
||||
const onResultsReturned = useCallback(
|
||||
(results: any[]) => {
|
||||
onResults({ messageId: id, resultId: snippetProps.id, results })
|
||||
},
|
||||
[onResults, snippetProps.id]
|
||||
)
|
||||
|
||||
const onRunQuery = async (queryType: 'select' | 'mutation') => {
|
||||
sendEvent({
|
||||
action: 'assistant_suggestion_run_query_clicked',
|
||||
@@ -211,8 +253,10 @@ export const MarkdownPre = ({ children }: { children: any }) => {
|
||||
isChart={isChart}
|
||||
isLoading={isLoading}
|
||||
isDraggable={isDraggableToReports}
|
||||
runQuery={runQuery}
|
||||
runQuery={!results && runQuery}
|
||||
results={results}
|
||||
onRunQuery={onRunQuery}
|
||||
onResults={onResultsReturned}
|
||||
onUpdateChartConfig={({ chartConfig: config }) => {
|
||||
chartConfig.current = { ...chartConfig.current, ...config }
|
||||
}}
|
||||
|
||||
@@ -2,11 +2,15 @@ import { Edit } from 'lucide-react'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { useIsInlineEditorEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
||||
import { DiffType } from 'components/interfaces/SQLEditor/SQLEditor.types'
|
||||
import useNewQuery from 'components/interfaces/SQLEditor/hooks'
|
||||
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
|
||||
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
|
||||
import Link from 'next/link'
|
||||
import { ComponentProps } from 'react'
|
||||
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
|
||||
import {
|
||||
cn,
|
||||
@@ -17,9 +21,6 @@ import {
|
||||
TooltipContent,
|
||||
} from 'ui'
|
||||
import { ButtonTooltip } from '../ButtonTooltip'
|
||||
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { useIsInlineEditorEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
||||
|
||||
interface EditQueryButtonProps {
|
||||
id?: string
|
||||
@@ -39,8 +40,11 @@ export const EditQueryButton = ({
|
||||
const router = useRouter()
|
||||
const { ref } = useParams()
|
||||
const { newQuery } = useNewQuery()
|
||||
|
||||
const sqlEditorSnap = useSqlEditorV2StateSnapshot()
|
||||
const { setEditorPanel } = useAppStateSnapshot()
|
||||
const snap = useAiAssistantStateSnapshot()
|
||||
|
||||
const isInSQLEditor = router.pathname.includes('/sql')
|
||||
const isInNewSnippet = router.pathname.endsWith('/sql')
|
||||
const isInlineEditorEnabled = useIsInlineEditorEnabled()
|
||||
@@ -51,16 +55,6 @@ export const EditQueryButton = ({
|
||||
const org = useSelectedOrganization()
|
||||
const { mutate: sendEvent } = useSendEventMutation()
|
||||
|
||||
const handleEditInSQLEditor = () => {
|
||||
if (sql) {
|
||||
if (isInSQLEditor) {
|
||||
sqlEditorSnap.setDiffContent(sql, DiffType.Addition)
|
||||
} else {
|
||||
newQuery(sql, title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (id !== undefined) {
|
||||
return (
|
||||
<ButtonTooltip
|
||||
@@ -88,8 +82,9 @@ export const EditQueryButton = ({
|
||||
open: true,
|
||||
initialValue: sql,
|
||||
})
|
||||
snap.closeAssistant()
|
||||
} else {
|
||||
handleEditInSQLEditor()
|
||||
if (sql) newQuery(sql, title)
|
||||
}
|
||||
sendEvent({
|
||||
action: 'assistant_edit_in_sql_editor_clicked',
|
||||
@@ -124,7 +119,7 @@ export const EditQueryButton = ({
|
||||
>
|
||||
Replace code
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => sqlEditorSnap.setDiffContent(sql, DiffType.NewSnippet)}>
|
||||
<DropdownMenuItem onClick={() => newQuery(sql, title)}>
|
||||
Create new snippet
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -11,24 +11,15 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte
|
||||
import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation'
|
||||
import { Parameter, parseParameters } from 'lib/sql-parameters'
|
||||
import { Dashboards } from 'types'
|
||||
import {
|
||||
Button,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
cn,
|
||||
CodeBlock,
|
||||
SQL_ICON,
|
||||
} from 'ui'
|
||||
import { Admonition } from 'ui-patterns'
|
||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, cn, CodeBlock, SQL_ICON } from 'ui'
|
||||
import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
|
||||
import { containsUnknownFunction, isReadOnlySelect } from '../AIAssistantPanel/AIAssistant.utils'
|
||||
import { ButtonTooltip } from '../ButtonTooltip'
|
||||
import SqlWarningAdmonition from '../SqlWarningAdmonition'
|
||||
import { BlockViewConfiguration } from './BlockViewConfiguration'
|
||||
import { EditQueryButton } from './EditQueryButton'
|
||||
import { ParametersPopover } from './ParametersPopover'
|
||||
import { getCumulativeResults } from './QueryBlock.utils'
|
||||
import SqlWarningAdmonition from '../SqlWarningAdmonition'
|
||||
|
||||
export const DEFAULT_CHART_CONFIG: ChartConfig = {
|
||||
type: 'bar',
|
||||
@@ -69,12 +60,16 @@ interface QueryBlockProps {
|
||||
draggable?: boolean
|
||||
/** Tooltip when hovering over the header of the block (Used in Assistant Panel) */
|
||||
tooltip?: ReactNode
|
||||
/** Optional: Any initial results to render as part of the query*/
|
||||
results?: any[]
|
||||
/** Not implemented yet: Will be the next part of ReportsV2 */
|
||||
onSetParameter?: (params: Parameter[]) => void
|
||||
/** Optional callback the SQL query is run */
|
||||
onRunQuery?: (queryType: 'select' | 'mutation') => void
|
||||
/** Optional callback on drag start */
|
||||
onDragStart?: (e: DragEvent<Element>) => void
|
||||
/** Optional: callback when the results are returned from running the SQL query*/
|
||||
onResults?: (results: any[]) => void
|
||||
|
||||
// [Joshen] Params below are currently only used by ReportsV2 (Might revisit to see how to improve these)
|
||||
/** Optional height set to render the SQL query (Used in Reports) */
|
||||
@@ -116,10 +111,12 @@ export const QueryBlock = ({
|
||||
disableRunIfMutation = false,
|
||||
noResultPlaceholder = null,
|
||||
tooltip,
|
||||
results,
|
||||
onRunQuery,
|
||||
onSetParameter,
|
||||
onUpdateChartConfig,
|
||||
onDragStart,
|
||||
onResults,
|
||||
}: QueryBlockProps) => {
|
||||
const { ref } = useParams()
|
||||
const { project } = useProjectContext()
|
||||
@@ -128,7 +125,7 @@ export const QueryBlock = ({
|
||||
const { xKey, yKey, view = 'table' } = chartSettings
|
||||
|
||||
const [showSql, setShowSql] = useState(_showSql)
|
||||
const [queryResult, setQueryResult] = useState<any[]>()
|
||||
const [queryResult, setQueryResult] = useState<any[] | undefined>(results)
|
||||
const [parameterValues, setParameterValues] = useState<Record<string, string>>({})
|
||||
const [showWarning, setShowWarning] = useState<'hasWriteOperation' | 'hasUnknownFunctions'>()
|
||||
|
||||
@@ -141,7 +138,10 @@ export const QueryBlock = ({
|
||||
const isReadOnlySelectSQL = isReadOnlySelect(sql ?? '')
|
||||
|
||||
const { mutate: execute, isLoading: isExecuting } = useExecuteSqlMutation({
|
||||
onSuccess: (data) => setQueryResult(data.result),
|
||||
onSuccess: (data) => {
|
||||
onResults?.(data.result)
|
||||
setQueryResult(data.result)
|
||||
},
|
||||
})
|
||||
|
||||
const handleExecute = () => {
|
||||
|
||||
@@ -69,9 +69,10 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse) {
|
||||
- Always use semicolons
|
||||
- Output as markdown
|
||||
- Always include code snippets if available
|
||||
- If a code snippet is SQL, the first line of the snippet should always be -- props: {"title": "Query title", "runQuery": "false", "isChart": "true", "xAxis": "columnOrAlias", "yAxis": "columnOrAlias"}
|
||||
- If a code snippet is SQL, the first line of the snippet should always be -- props: {"id": "id", "title": "Query title", "runQuery": "false", "isChart": "true", "xAxis": "columnOrAlias", "yAxis": "columnOrAlias"}
|
||||
- Only include one line of comment props per markdown snippet, even if the snippet has multiple queries
|
||||
- Only set chart to true if the query makes sense as a chart. xAxis and yAxis need to be columns or aliases returned by the query.
|
||||
- Set the id to a random uuidv4 value
|
||||
- Only set runQuery to true if the query has no risk of writing data and is not a debugging request. Set it to false if there are any values that need to be replaced with real data.
|
||||
- Explain what the snippet does in a sentence or two before showing it
|
||||
- Use vector(384) data type for any embedding/vector related query
|
||||
|
||||
@@ -9,10 +9,12 @@ type SuggestionsType = {
|
||||
prompts?: string[]
|
||||
}
|
||||
|
||||
type AssistantMessageType = MessageType & { results?: { [id: string]: any[] } }
|
||||
|
||||
type ChatSession = {
|
||||
id: string
|
||||
name: string
|
||||
messages: readonly MessageType[]
|
||||
messages: readonly AssistantMessageType[]
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
@@ -195,6 +197,29 @@ export const createAiAssistantState = (projectRef: string | undefined) => {
|
||||
}
|
||||
},
|
||||
|
||||
updateMessage: ({
|
||||
id,
|
||||
resultId,
|
||||
results,
|
||||
}: {
|
||||
id: string
|
||||
resultId?: string
|
||||
results: any[]
|
||||
}) => {
|
||||
let chat = state.activeChat
|
||||
if (!chat || !resultId) return
|
||||
|
||||
const existingMessages = chat.messages
|
||||
const updatedMessages = existingMessages.map((msg) => {
|
||||
if (msg.id === id) {
|
||||
return { ...msg, results: { ...(msg.results ?? {}), [resultId]: results } }
|
||||
} else {
|
||||
return msg
|
||||
}
|
||||
})
|
||||
chat.messages = updatedMessages
|
||||
},
|
||||
|
||||
setSqlSnippets: (snippets: string[]) => {
|
||||
state.sqlSnippets = snippets
|
||||
},
|
||||
@@ -205,6 +230,15 @@ export const createAiAssistantState = (projectRef: string | undefined) => {
|
||||
// Remove suggestions if sqlSnippets were removed
|
||||
state.suggestions = undefined
|
||||
},
|
||||
|
||||
getCachedSQLResults: ({ messageId, snippetId }: { messageId: string; snippetId?: string }) => {
|
||||
let chat = state.activeChat
|
||||
if (!chat || !snippetId) return
|
||||
|
||||
const message = chat.messages.find((msg) => msg.id === messageId)
|
||||
const results = (message?.results ?? {})[snippetId]
|
||||
return results
|
||||
},
|
||||
})
|
||||
|
||||
// If there's no active chat when the state is created
|
||||
|
||||
@@ -43,11 +43,7 @@ The File Upload component makes it easy to add file uploads to your app, with bu
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Dropzone,
|
||||
DropzoneContent,
|
||||
DropzoneEmptyState,
|
||||
} from '@/components/dropzone'
|
||||
import { Dropzone, DropzoneContent, DropzoneEmptyState } from '@/components/dropzone'
|
||||
import { useSupabaseUpload } from '@/hooks/use-supabase-upload'
|
||||
|
||||
const FileUploadDemo = () => {
|
||||
@@ -69,7 +65,7 @@ const FileUploadDemo = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export FileUploadDemo
|
||||
export { FileUploadDemo }
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
@@ -41,11 +41,7 @@ The File Upload component makes it easy to add file uploads to your app, with bu
|
||||
- For control over file upload, you can pass in a `props` object to the component.
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Dropzone,
|
||||
DropzoneContent,
|
||||
DropzoneEmptyState,
|
||||
} from '@/components/dropzone'
|
||||
import { Dropzone, DropzoneContent, DropzoneEmptyState } from '@/components/dropzone'
|
||||
import { useSupabaseUpload } from '@/hooks/use-supabase-upload'
|
||||
|
||||
const FileUploadDemo = () => {
|
||||
@@ -67,7 +63,7 @@ const FileUploadDemo = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export FileUploadDemo
|
||||
export { FileUploadDemo }
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
@@ -41,11 +41,7 @@ The File Upload component makes it easy to add file uploads to your app, with bu
|
||||
- For control over file upload, you can pass in a `props` object to the component.
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Dropzone,
|
||||
DropzoneContent,
|
||||
DropzoneEmptyState,
|
||||
} from '@/components/dropzone'
|
||||
import { Dropzone, DropzoneContent, DropzoneEmptyState } from '@/components/dropzone'
|
||||
import { useSupabaseUpload } from '@/hooks/use-supabase-upload'
|
||||
|
||||
const FileUploadDemo = () => {
|
||||
@@ -67,7 +63,7 @@ const FileUploadDemo = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export FileUploadDemo
|
||||
export { FileUploadDemo }
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
@@ -41,11 +41,7 @@ The File Upload component makes it easy to add file uploads to your app, with bu
|
||||
- For control over file upload, you can pass in a `props` object to the component.
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Dropzone,
|
||||
DropzoneContent,
|
||||
DropzoneEmptyState,
|
||||
} from '@/components/dropzone'
|
||||
import { Dropzone, DropzoneContent, DropzoneEmptyState } from '@/components/dropzone'
|
||||
import { useSupabaseUpload } from '@/hooks/use-supabase-upload'
|
||||
|
||||
const FileUploadDemo = () => {
|
||||
@@ -67,7 +63,7 @@ const FileUploadDemo = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export FileUploadDemo
|
||||
export { FileUploadDemo }
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
@@ -15,7 +15,7 @@ thumb: lw14-clerk-tpa-pricing/clerk-tpa-pricing-thumb.png
|
||||
launchweek: 14
|
||||
---
|
||||
|
||||
Supabase is a modular platform. You can choose which products you use: one or all. Using Supabase Auth one way to add authentication to your application, but it’s not the only choice. This is why today we're expanding our [third-party auth integrations](https://supabase.com/docs/guides/auth/third-party/overview) to officially include [Clerk](https://clerk.com).
|
||||
Supabase is a modular platform. You can choose which products you use: one or all. Using Supabase Auth is one way to add authentication to your application, but it’s not the only choice. This is why today we're expanding our [third-party auth integrations](https://supabase.com/docs/guides/auth/third-party/overview) to officially include [Clerk](https://clerk.com).
|
||||
|
||||

|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 404 KiB After Width: | Height: | Size: 117 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 6.5 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 5.3 KiB |
Reference in new issue
Block a user