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
2 changes: 1 addition & 1 deletion apps/design-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"ui": "workspace:*",
"ui-patterns": "workspace:*",
"unist-util-visit": "^5.0.0",
"zod": "^3.22.4"
"zod": "^3.25.76"
},
"devDependencies": {
"@shikijs/compat": "^1.1.7",
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
"next-plugin-yaml": "^1.0.1",
"next-themes": "^0.3.0",
"nuqs": "^1.19.1",
"openai": "^4.20.1",
"openai": "^4.75.1",
"openapi-fetch": "0.12.4",
"react": "catalog:",
"react-copy-to-clipboard": "^5.1.0",
Expand All @@ -118,7 +118,7 @@
"uuid": "^9.0.1",
"valtio": "catalog:",
"yaml": "^2.4.5",
"zod": "^3.22.4"
"zod": "^3.25.76"
},
"devDependencies": {
"@graphiql/toolkit": "^0.9.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,26 +50,6 @@ with check (true);`.trim(),
{
id: 'policy-3',
preview: false,
templateName: 'Enable update access for users based on their email *',
description:
'This policy assumes that your table has a column "email", and allows users to update rows which the "email" column matches their email.',
statement: `
create policy "Enable update for users based on email"
on "${schema}"."${table}"
for update using (
(select auth.jwt()) ->> 'email' = email
) with check (
(select auth.jwt()) ->> 'email' = email
);`.trim(),
name: 'Enable update for users based on email',
definition: `(select auth.jwt()) ->> 'email' = email`,
check: `(select auth.jwt()) ->> 'email' = email`,
command: 'UPDATE',
roles: [],
},
{
id: 'policy-4',
preview: false,
templateName: 'Enable delete access for users based on their user ID *',
description:
'This policy assumes that your table has a column "user_id", and allows users to delete rows which the "user_id" column matches their ID',
Expand All @@ -86,7 +66,7 @@ for delete using (
roles: [],
},
{
id: 'policy-5',
id: 'policy-4',
preview: false,
templateName: 'Enable insert access for users based on their user ID *',
description:
Expand All @@ -104,7 +84,7 @@ for insert with check (
roles: [],
},
{
id: 'policy-6',
id: 'policy-5',
preview: true,
name: 'Policy with table joins',
templateName: 'Policy with table joins',
Expand All @@ -126,7 +106,7 @@ on teams for update using (
roles: [],
},
{
id: 'policy-7',
id: 'policy-6',
preview: true,
templateName: 'Policy with security definer functions',
description: `
Expand All @@ -152,7 +132,7 @@ for all using (
roles: [],
},
{
id: 'policy-8',
id: 'policy-7',
preview: true,
name: 'Policy to implement Time To Live (TTL)',
templateName: 'Policy to implement Time To Live (TTL)',
Expand All @@ -173,7 +153,7 @@ for select using (
roles: [],
},
{
id: 'policy-9',
id: 'policy-8',
preview: false,
templateName: 'Allow users to only view their own data',
description: 'Restrict users to reading only their own data.',
Expand Down
115 changes: 68 additions & 47 deletions apps/studio/components/interfaces/ProjectCreation/SchemaGenerator.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import { useEffect, useState } from 'react'

import { Markdown } from 'components/interfaces/Markdown'
Expand Down Expand Up @@ -35,36 +36,30 @@ export const SchemaGenerator = ({
const [promptIntendSent, setPromptIntendSent] = useState(false)
const { mutate: sendEvent } = useSendEventMutation()

const {
messages,
setMessages,
handleInputChange,
append,
isLoading: isMessagesLoading,
} = useChat({
api: `${BASE_PATH}/api/ai/onboarding/design`,
const { messages, setMessages, sendMessage, status, addToolResult } = useChat({
id: 'schema-generator',
maxSteps: 7,
onError: onErrorChat,
onFinish: () => {
setInput('')
},
async onToolCall({ toolCall }) {
if (toolCall.toolName === 'executeSql') {
try {
const sql = (toolCall.args as { sql: string }).sql
const sql = (toolCall.input as { sql: string }).sql
setHasSql(true)
onSqlGenerated(sql)
return {
success: true,
message: 'Database successfully updated. Respond with next steps.',
}
addToolResult({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
output: 'Database successfully updated. Respond with next steps.',
})
} catch (error) {
console.error('Failed to execute SQL:', error)
return {
success: false,
error: `SQL execution failed: ${error instanceof Error ? error.message : String(error)}`,
}
addToolResult({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
output: `SQL execution failed: ${error instanceof Error ? error.message : String(error)}`,
})
}
}

Expand All @@ -75,44 +70,79 @@ export const SchemaGenerator = ({
if (onReset) {
onReset()
}
return {
success: true,
message: 'Database successfully reset',
}
addToolResult({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
output: 'Database successfully reset',
})
} catch (error) {
console.error('Failed to reset the database', error)
return {
success: false,
error: `Resetting the database failed: ${error instanceof Error ? error.message : String(error)}`,
}
addToolResult({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
output: `Resetting the database failed: ${error instanceof Error ? error.message : String(error)}`,
})
}
}

if (toolCall.toolName === 'setServices') {
const newServices = (toolCall.args as { services: SupabaseService[] }).services
const newServices = (toolCall.input as { services: SupabaseService[] }).services
onServicesUpdated(newServices)
return 'Services updated successfully'
addToolResult({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
output: 'Services updated successfully',
})
}

if (toolCall.toolName === 'setTitle') {
const newTitle = (toolCall.args as { title: string }).title
const newTitle = (toolCall.input as { title: string }).title
onTitleUpdated(newTitle)
return 'Title updated successfully'
addToolResult({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
output: 'Title updated successfully',
})
}
},
transport: new DefaultChatTransport({
api: `${BASE_PATH}/api/ai/onboarding/design`,
}),
})

const isMessagesLoading = status === 'submitted' || status === 'streaming'

useEffect(() => {
if (isOneOff) {
setMessages([])
setInput('')
}
}, [isOneOff, setMessages, setInput])
}, [isOneOff, setMessages])

const sendUserMessage = (content: string) => {
const payload = {
role: 'user' as const,
createdAt: new Date(),
parts: [{ type: 'text' as const, text: content }],
id: `msg-${Date.now()}`,
}
sendMessage(payload)
}

const getLastAssistantMessage = () => {
const lastAssistantMessage = messages.filter((m) => m.role === 'assistant').slice(-1)[0]
if (!lastAssistantMessage?.parts) return ''

const textPart = lastAssistantMessage.parts.find((part: any) => part.type === 'text') as
| { text: string }
| undefined
return textPart?.text || ''
}

return (
<div>
{!isOneOff && (
<div className="flex justify-between w-full block items-center mb-4">
<div className="flex justify-between w-full items-center mb-4">
<Label_Shadcn_ className="text-foreground-light flex-1">
Generate a starting schema
</Label_Shadcn_>
Expand All @@ -122,10 +152,7 @@ export const SchemaGenerator = ({
size="tiny"
onClick={() => {
setInput('Reset the database, services and start over')
append({
role: 'user',
content: 'Reset the database, services and start over',
})
sendUserMessage('Reset the database, services and start over')
}}
>
Reset
Expand All @@ -136,16 +163,11 @@ export const SchemaGenerator = ({
<div className="rounded-md border bg-surface-100">
{messages.length > 0 &&
messages[messages.length - 1].role === 'assistant' &&
messages[messages.length - 1].content.length > 0 &&
getLastAssistantMessage() &&
((isOneOff && !hasSql) || !isOneOff) && (
<div className="px-4 py-3 border-b space-y-1">
<p>
<Markdown
className="text-foreground-light"
content={
messages.filter((m) => m.role === 'assistant').slice(-1)[0]?.content || ''
}
/>
<Markdown className="text-foreground-light" content={getLastAssistantMessage()} />
</p>
</div>
)}
Expand All @@ -158,7 +180,6 @@ export const SchemaGenerator = ({
value={input}
disabled={isMessagesLoading}
onChange={(e) => {
handleInputChange(e)
setInput(e.target.value)
}}
placeholder={messages.length > 0 ? 'Make an edit...' : 'Describe your application...'}
Expand Down Expand Up @@ -191,15 +212,15 @@ export const SchemaGenerator = ({
if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
e.preventDefault()
setHasSql(false)
append({ role: 'user', content: input })
sendUserMessage(input)
}
}}
/>
<Button
onClick={(e) => {
e.preventDefault()
setHasSql(false)
append({ role: 'user', content: input })
sendUserMessage(input)
}}
disabled={isMessagesLoading}
loading={isMessagesLoading}
Expand Down
30 changes: 21 additions & 9 deletions apps/studio/components/interfaces/QueryPerformance/QueryCosts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,29 @@ export const QueryCosts = ({
<div className="flex flex-col items-end gap-y-1">
<div className="flex items-center gap-x-4">
<p className="text-sm text-foreground-light">Currently:</p>
<p className="font-mono text-sm">{currentCost.toFixed(2)}</p>
<p className="font-mono text-sm">
{typeof currentCost === 'number' && !isNaN(currentCost) && isFinite(currentCost)
? currentCost.toFixed(2)
: 'N/A'}
</p>
</div>
{improvedCost && (
<div className="flex items-center gap-x-4">
<p className="text-sm text-foreground-light">With index:</p>
<div className="flex items-center gap-x-2">
<p className="font-mono text-sm">{improvedCost.toFixed(2)}</p>
{improvement && <p className="text-sm text-brand">↓ {improvement.toFixed(1)}%</p>}
{improvedCost &&
typeof improvedCost === 'number' &&
!isNaN(improvedCost) &&
isFinite(improvedCost) && (
<div className="flex items-center gap-x-4">
<p className="text-sm text-foreground-light">With index:</p>
<div className="flex items-center gap-x-2">
<p className="font-mono text-sm">{improvedCost.toFixed(2)}</p>
{improvement &&
typeof improvement === 'number' &&
!isNaN(improvement) &&
isFinite(improvement) && (
<p className="text-sm text-brand">↓ {improvement.toFixed(1)}%</p>
)}
</div>
</div>
</div>
)}
)}
</div>
</div>
<button className="text-sm text-brand hover:text-brand-600 transition">View more</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,17 @@ export const QueryDetail = ({
{report
.filter((x) => x.id !== 'query')
.map((x) => {
const rawValue = selectedRow?.[x.id]
const isTime = x.name.includes('time')

const formattedValue = isTime
? `${selectedRow?.[x.id].toFixed(2)}ms`
: String(selectedRow?.[x.id])
? typeof rawValue === 'number' && !isNaN(rawValue) && isFinite(rawValue)
? `${rawValue.toFixed(2)}ms`
: 'N/A'
: rawValue != null
? String(rawValue)
: 'N/A'

return (
<div key={x.id} className="flex gap-x-2">
<p className="text-foreground-lighter text-sm w-32">{x.name}</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const QueryPanelScoreSection = ({
) : (
<ArrowDown size={14} className="text-brand" />
)}
{before !== 0 && (
{typeof before === 'number' && before !== 0 && !isNaN(before) && isFinite(before) && (
<span
className={cn(
'font-mono tracking-tighter',
Expand Down
Loading
Loading