feat(db-playground): pglite poc

This commit is contained in:
Greg Richardson committed 2024-06-07 16:17:38 -06:00
1 parent d12719d283
commit 49b246089a
3 files changed
+177 -78

No files matched your search

-9
View File
@@ -139,15 +139,6 @@ main {
}
}
.monaco-editor,
.monaco-diff-editor {
--vscode-editor-background: hsl(var(--background-alternative)) !important;
--vscode-editorGutter-background: hsl(var(--background-alternative)) !important;
padding-top: 12px;
}
/* .monaco-editor .overflow-guard */
.shimmering-loader {
animation: shimmer 2s infinite linear;
background: linear-gradient(
+163 -67
View File
@@ -1,20 +1,68 @@
'use client'
import { PGlite } from '@electric-sql/pglite'
import { Editor } from '@monaco-editor/react'
import { FunctionBinding, executeJS } from 'ai-sandbox'
import { useChat } from 'ai/react'
import { codeBlock } from 'common-tags'
import { ArrowUp } from 'lucide-react'
import { useMemo } from 'react'
import { useMemo, useState } from 'react'
import ReactMarkdown from 'react-markdown'
import rehypeKatex from 'rehype-katex'
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import { format } from 'sql-formatter'
import { CodeBlock, markdownComponents } from 'ui'
import { Button } from 'ui/src/components/shadcn/ui/button'
import { Input } from 'ui/src/components/shadcn/ui/input'
// React's double-rendering in dev mode causes pglite errors
// Temp: storing single instance in module scope
let db: PGlite = new PGlite('idb://local')
export default function Page() {
const [sql, setSql] = useState('')
const functionBindings: Record<string, FunctionBinding> = useMemo(() => {
return {}
return {
runSql: {
description: codeBlock`
Runs Postgres SQL.
type Row<T = {
[key: string]: any;
}> = T;
type Results<T = {
[key: string]: any;
}> = {
rows: Row<T>[];
affectedRows?: number;
fields: {
name: string;
dataTypeID: number;
}[];
};
`,
typeDef: '(sql: string): Promise<Result>',
fn: async (sql: string) => {
setSql((s) => {
const newSql = (s + '\n' + sql).trim()
return format(newSql, {
language: 'postgresql',
keywordCase: 'lower',
identifierCase: 'lower',
dataTypeCase: 'lower',
functionCase: 'lower',
})
})
console.log('about to exec')
const results = await db.exec(sql)
console.log('after exec')
return results
},
},
}
}, [])
const expose = useMemo(() => {
@@ -56,78 +104,126 @@ export default function Page() {
})
return (
<div className="w-full h-full max-h-full flex flex-col items-stretch">
<div className="flex-1 flex flex-col items-center overflow-y-auto">
<div className="flex flex-col gap-4 w-full max-w-4xl p-10">
{messages.map((message) => {
switch (message.role) {
case 'user':
return (
<div
key={message.id}
className="self-end px-5 py-2.5 text-base rounded-full bg-neutral-100"
>
{message.content}
</div>
)
case 'assistant':
return (
<div key={message.id} className="self-stretch flex flex-col items-stretch gap-4">
<ReactMarkdown
remarkPlugins={[remarkGfm, [remarkMath, { singleDollarTextMath: false }]]}
rehypePlugins={[[rehypeKatex, { output: 'html' }]]}
components={markdownComponents}
className="prose [&_.katex-display>.katex]:text-left"
<div className="w-full h-full flex p-6">
<div className="h-full w-[50rem] py-4 rounded-md bg-[#1e1e1e]">
<Editor
language="pgsql"
value={sql}
theme="vs-dark"
options={{
tabSize: 2,
minimap: {
enabled: false,
},
fontSize: 13,
}}
onMount={async (editor, monaco) => {
// Register pgsql formatter
monaco.languages.registerDocumentFormattingEditProvider('pgsql', {
async provideDocumentFormattingEdits(model) {
const currentCode = editor.getValue()
const formattedCode = format(currentCode, {
language: 'postgresql',
keywordCase: 'lower',
})
return [
{
range: model.getFullModelRange(),
text: formattedCode,
},
]
},
})
// Format on cmd+s
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, async () => {
await editor.getAction('editor.action.formatDocument').run()
})
// Run format on the initial value
await editor.getAction('editor.action.formatDocument').run()
}}
/>
</div>
<div className="flex-1 max-h-full flex flex-col items-stretch">
<div className="flex-1 flex flex-col items-center overflow-y-auto">
<div className="flex flex-col gap-4 w-full max-w-4xl p-10">
{messages.map((message) => {
switch (message.role) {
case 'user':
return (
<div
key={message.id}
className="self-end px-5 py-2.5 text-base rounded-full bg-neutral-100"
>
{message.content}
</ReactMarkdown>
{message.toolInvocations &&
message.toolInvocations.map((invocation) => (
<div key={invocation.toolCallId}>
<div className="flex items-center px-3 py-2 bg-neutral-600 text-white text-xs rounded-t-md">
<h3>JavaScript (Interpreter)</h3>
</div>
<div className="">
<div className="py-4 bg-neutral-100">
<CodeBlock className="language-js border-none p-0">
{invocation.args.source}
</CodeBlock>
</div>
)
case 'assistant':
return (
<div
key={message.id}
className="self-stretch flex flex-col items-stretch gap-4"
>
<ReactMarkdown
remarkPlugins={[remarkGfm, [remarkMath, { singleDollarTextMath: false }]]}
rehypePlugins={[[rehypeKatex, { output: 'html' }]]}
components={markdownComponents}
className="prose [&_.katex-display>.katex]:text-left"
>
{message.content}
</ReactMarkdown>
{message.toolInvocations &&
message.toolInvocations.map((invocation) => (
<div key={invocation.toolCallId}>
<div className="flex items-center px-3 py-2 bg-neutral-600 text-white text-xs rounded-t-md">
<h3>JavaScript (Interpreter)</h3>
</div>
{'result' in invocation && (
<div className="bg-white p-4 border border-neutral-100 rounded-b-md">
<CodeBlock className="language-js border-none p-0" hideLineNumbers>
{JSON.stringify(invocation.result, undefined, 2)}
<div className="">
<div className="py-4 bg-neutral-100">
<CodeBlock className="language-js border-none p-0">
{invocation.args.source}
</CodeBlock>
</div>
)}
{'result' in invocation && (
<div className="bg-white p-4 border border-neutral-100 rounded-b-md">
<CodeBlock
className="language-js border-none p-0"
hideLineNumbers
>
{JSON.stringify(invocation.result, undefined, 2)}
</CodeBlock>
</div>
)}
</div>
</div>
</div>
))}
</div>
)
}
})}
))}
</div>
)
}
})}
</div>
</div>
</div>
<div className="flex flex-col items-center gap-2 pb-2">
<form
className="flex items-center py-2 px-3 rounded-full bg-neutral-100 w-full max-w-4xl"
onSubmit={handleSubmit}
>
<Input
className="flex-grow border-none text-base placeholder:text-neutral-400"
name="prompt"
value={input}
onChange={handleInputChange}
placeholder="Message Supabase AI"
id="input"
/>
<Button className="rounded-full w-8 h-8 p-1.5 text-white bg-neutral-800" type="submit">
<ArrowUp />
</Button>
</form>
<div className="text-xs text-neutral-400">
AI can make mistakes. Check import information.
<div className="flex flex-col items-center gap-2 pb-2">
<form
className="flex items-center py-2 px-3 rounded-full bg-neutral-100 w-full max-w-4xl"
onSubmit={handleSubmit}
>
<Input
className="flex-grow border-none text-base placeholder:text-neutral-400"
name="prompt"
value={input}
onChange={handleInputChange}
placeholder="Message Supabase AI"
id="input"
/>
<Button className="rounded-full w-8 h-8 p-1.5 text-white bg-neutral-800" type="submit">
<ArrowUp />
</Button>
</form>
<div className="text-xs text-neutral-500">
AI can make mistakes. Check import information.
</div>
</div>
</div>
</div>
+14 -2
View File
@@ -1,4 +1,16 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
webpack: (config) => {
config.resolve = {
...config.resolve,
fallback: {
fs: false,
module: false,
},
}
export default nextConfig;
return config
},
}
export default nextConfig