SchemaGenerator() — supabase Function Reference
Architecture documentation for the SchemaGenerator() function in SchemaGenerator.tsx from the supabase codebase.
Entity Profile
Relationship Graph
Source Code
apps/studio/components/interfaces/ProjectCreation/SchemaGenerator.tsx lines 25–230
export const SchemaGenerator = ({
step,
onSqlGenerated,
onServicesUpdated,
onTitleUpdated,
onReset,
isOneOff = false,
}: SchemaGeneratorProps) => {
const [input, setInput] = useState('')
const [hasSql, setHasSql] = useState(false)
const [promptIntendSent, setPromptIntendSent] = useState(false)
const track = useTrack()
const { messages, setMessages, sendMessage, status, addToolResult } = useChat({
id: 'schema-generator',
onError: onErrorChat,
onFinish: () => {
setInput('')
},
async onToolCall({ toolCall }) {
if (toolCall.toolName === 'executeSql') {
try {
const sql = (toolCall.input as { sql: string }).sql
setHasSql(true)
onSqlGenerated(sql)
addToolResult({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
output: 'Database successfully updated. Respond with next steps.',
})
} catch (error) {
console.error('Failed to execute SQL:', error)
addToolResult({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
output: `SQL execution failed: ${error instanceof Error ? error.message : String(error)}`,
})
}
}
if (toolCall.toolName === 'reset') {
try {
setHasSql(false)
onSqlGenerated('')
if (onReset) {
onReset()
}
addToolResult({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
output: 'Database successfully reset',
})
} catch (error) {
console.error('Failed to reset the database', 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.input as { services: SupabaseService[] }).services
onServicesUpdated(newServices)
addToolResult({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
output: 'Services updated successfully',
})
}
if (toolCall.toolName === 'setTitle') {
const newTitle = (toolCall.input as { title: string }).title
onTitleUpdated(newTitle)
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])
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 items-center mb-4">
<Label_Shadcn_ className="text-foreground-light flex-1">
Generate a starting schema
</Label_Shadcn_>
{messages?.length > 0 && (
<Button
type="outline"
size="tiny"
onClick={() => {
setInput('Reset the database, services and start over')
sendUserMessage('Reset the database, services and start over')
}}
>
Reset
</Button>
)}
</div>
)}
<div className="rounded-md border bg-surface-100">
{messages.length > 0 &&
messages[messages.length - 1].role === 'assistant' &&
getLastAssistantMessage() &&
((isOneOff && !hasSql) || !isOneOff) && (
<div className="px-4 py-3 border-b space-y-1">
<p>
<Markdown className="text-foreground-light" content={getLastAssistantMessage()} />
</p>
</div>
)}
<div className="w-full relative text-sm border-none block bg-muted mb-0 text-foreground-light placeholder:text-foreground-lighter">
<Textarea
id="input"
name="prompt"
autoComplete="off"
className="text-sm w-full bg-transparent border-none resize-none px-4 pt-3 pb-8"
value={input}
disabled={isMessagesLoading}
onChange={(e) => {
setInput(e.target.value)
}}
placeholder={messages.length > 0 ? 'Make an edit...' : 'Describe your application...'}
rows={Math.max(3, Math.min(input.split('\n').length, 10))}
onKeyDown={(e) => {
if (!(e.target instanceof HTMLTextAreaElement)) {
return
}
if (!promptIntendSent && e.target.value.length > 5) {
// distinguish between a new prompt or an edit
const isNewPrompt = messages.length == 0
// distinguish between initial step or second step
if (step === 'initial') {
track('project_creation_initial_step_prompt_intended', {
isNewPrompt,
})
} else {
track('project_creation_second_step_prompt_intended', {
isNewPrompt,
})
}
setPromptIntendSent(true)
}
if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
e.preventDefault()
setHasSql(false)
sendUserMessage(input)
}
}}
/>
<Button
onClick={(e) => {
e.preventDefault()
setHasSql(false)
sendUserMessage(input)
}}
disabled={isMessagesLoading}
loading={isMessagesLoading}
icon={<AiIconAnimation size={16} />}
className="absolute bottom-2 right-2"
>
Generate
</Button>
</div>
</div>
</div>
)
}
Domain
Subdomains
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free