diff --git a/app/[database]/ai-chat-message-prisma.tsx b/app/[database]/ai-chat-message-prisma.tsx index d150963f..21f3f426 100644 --- a/app/[database]/ai-chat-message-prisma.tsx +++ b/app/[database]/ai-chat-message-prisma.tsx @@ -1,4 +1,4 @@ -import { Play } from "lucide-react" +import { Play, Trash2 } from "lucide-react" import Prism from "prismjs" import { Button } from "@/components/ui/button" @@ -17,7 +17,7 @@ export const AIMessage = ({ msgIndex, }: { msgIndex: number - message: string + message?: string onRun: (props: { code: string lang: string @@ -35,7 +35,7 @@ export const AIMessage = ({ return {children} }, codeblock: function Code(props) { - const { lang, text } = props + const { lang='sql', text } = props const codeHtml = Prism.highlight(text, Prism.languages[lang], lang) // if it's a d3 codeblock, we need to render it differently if (lang === "js" && text.includes("d3.")) { @@ -68,7 +68,7 @@ export const AIMessage = ({ } return (
- + {message && }
) diff --git a/app/[database]/ai-chat-message.tsx b/app/[database]/ai-chat-message.tsx new file mode 100644 index 00000000..c904527d --- /dev/null +++ b/app/[database]/ai-chat-message.tsx @@ -0,0 +1,41 @@ +import { Bot, Trash2, User } from "lucide-react" +import { ChatCompletionResponseMessage } from "openai" + +import { AIMessage } from "./ai-chat-message-prisma" + +export const AIChatMessage = ({ + message, + msgIndex, + handleRunCode, +}: { + msgIndex: number + message: ChatCompletionResponseMessage + handleRunCode: any +}) => { + return ( +
+ {message.role === "assistant" && ( + <> + + + + )} + {message.role === "user" && ( + <> + +

{message.content}

+ + )} + {/* */} +
+ ) +} diff --git a/app/[database]/ai-chat.tsx b/app/[database]/ai-chat.tsx index 8f3c94af..79055eec 100644 --- a/app/[database]/ai-chat.tsx +++ b/app/[database]/ai-chat.tsx @@ -1,43 +1,41 @@ "use client" // for now it's under database page, maybe move to global later -import { useCallback, useRef, useState } from "react" +import { useKeyPress } from "ahooks" +import { Loader2, Paintbrush } from "lucide-react" import Link from "next/link" import { useParams } from "next/navigation" -import { useKeyPress, useSize } from "ahooks" -import { Bot, Loader2, Paintbrush, User } from "lucide-react" +import { useCallback, useRef, useState } from "react" -import { useAI } from "@/hooks/use-ai" -import { useAutoRunCode } from "@/hooks/use-auto-run-code" import { Button } from "@/components/ui/button" import { Textarea } from "@/components/ui/textarea" +import { toast } from "@/components/ui/use-toast" +import { useAI } from "@/hooks/use-ai" +import { useAutoRunCode } from "@/hooks/use-auto-run-code" +import { useSqliteStore } from "@/hooks/use-sqlite" +import { handleOpenAIFunctionCall } from "@/lib/ai/openai" import { useConfigStore } from "../settings/store" -import { AIMessage } from "./ai-chat-message-prisma" -import { useTableChange } from "./hook" +import { AIChatMessage } from "./ai-chat-message" import { useDatabaseAppStore } from "./store" -import { useSqliteStore } from "@/hooks/use-sqlite" export const AIChat = () => { - const { currentTableSchema, setCurrentQuery } = useDatabaseAppStore() + const { currentTableSchema } = useDatabaseAppStore() const { askAI } = useAI() - const { database, table } = useParams() + const { database } = useParams() const { aiConfig } = useConfigStore() const [input, setInput] = useState("") const [loading, setLoading] = useState(false) - const { autoRun: runCode, handleRunCode } = useAutoRunCode() + const { handleFunctionCall, handleRunCode } = useAutoRunCode() + const divRef = useRef() - const size = useSize(divRef) - const [messages, setMessages] = useState< - { - role: "user" | "assistant" - content: string - }[] - >([]) + const { aiMessages: messages, setAiMessages: setMessages } = + useDatabaseAppStore() const { allTables } = useSqliteStore() + const cleanMessages = useCallback(() => { setMessages([]) - }, []) + }, [setMessages]) const textInputRef = useRef() @@ -45,35 +43,81 @@ export const AIChat = () => { textInputRef.current?.focus() }) - useTableChange(cleanMessages) + const runFromIndex = async (index: number) => { + const _messages = messages.slice(0, index + 1) + const response = await askAI(_messages, { + tableSchema: currentTableSchema, + allTables, + databaseName: database, + }) + } + + // useTableChange(cleanMessages) + + const sendMessages = async (_messages: any) => { + setLoading(true) + try { + const response = await askAI(_messages, { + tableSchema: currentTableSchema, + allTables, + databaseName: database, + }) + + if (response?.finish_reason == "function_call") { + if (aiConfig.autoRunScope) { + const res = await handleOpenAIFunctionCall( + response.message!, + handleFunctionCall + ) + if (res) { + const { name, resp } = res + const newMessages = [ + ..._messages, + response.message, + { + role: "function", + name, + content: JSON.stringify(resp), + }, + ] + const newResponse = await askAI(newMessages, { + tableSchema: currentTableSchema, + allTables, + databaseName: database, + }) + const _newMessages = [ + ...newMessages, + { role: "assistant", content: newResponse?.message?.content }, + ] + console.log({ _newMessages }) + setMessages(_newMessages as any) + } + } + } else if (response?.message) { + const newMessages = [ + ..._messages, + { role: "assistant", content: response?.message?.content }, + ] + const thisMsgIndex = newMessages.length - 1 + setMessages(newMessages) + } + } catch (error: any) { + toast({ + title: "Error", + description: 'oops, something went wrong. please try again later.', + }) + } finally { + setLoading(false) + } + } const handleSend = async () => { if (loading) return if (!input.trim().length) return - setLoading(true) const _messages: any = [...messages, { role: "user", content: input }] setMessages(_messages) setInput("") - const response = await askAI(_messages, { - tableSchema: currentTableSchema, - allTables, - databaseName: database, - }) - const newMessages = [ - ..._messages, - { role: "assistant", content: response?.content! }, - ] - const thisMsgIndex = newMessages.length - 1 - setMessages(newMessages) - if (response?.content && aiConfig.autoRunScope) { - setTimeout(() => { - runCode(response.content, { - msgIndex: thisMsgIndex, - width: size?.width ?? 300, - }) - }, 1000) - } - setLoading(false) + await sendMessages(_messages) } const handleEnter = async (e: React.KeyboardEvent) => { @@ -97,28 +141,20 @@ export const AIChat = () => { first

)} - {messages.map((message, i) => ( -
- {message.role === "assistant" ? ( - <> - - - - ) : ( - <> - -

{message.content}

- - )} -
- ))} + {messages.map((message, i) => { + const m = message + if ((m.role === "user" || m.role == "assistant") && m.content) { + return ( + + ) + } + })} +
{loading && }
diff --git a/app/[database]/base-layout.tsx b/app/[database]/base-layout.tsx index 067d1c8c..cb636144 100644 --- a/app/[database]/base-layout.tsx +++ b/app/[database]/base-layout.tsx @@ -72,13 +72,13 @@ export function DatabaseLayoutBase({
-
+
{children}
- {AutoRunScopes.map((_item) => ( + {AutoRunScopesWithDesc.map(({ value: key, description }) => ( { return ( { return checked - ? field.onChange([...field.value, _item]) + ? field.onChange([...field.value, key]) : field.onChange( field.value?.filter( - (value) => value !== _item + (value) => value !== key ) ) }} /> - {_item} + + {description} + ) }} diff --git a/app/settings/layout.tsx b/app/settings/layout.tsx index 50588da6..78485912 100644 --- a/app/settings/layout.tsx +++ b/app/settings/layout.tsx @@ -9,7 +9,7 @@ import { Button } from "@/components/ui/button" import { Separator } from "@/components/ui/separator" import { SidebarNav } from "@/app/settings/components/sidebar-nav" -import { useLastOpenedDatabase } from "../[database]/hook" +import { useLastOpenedDatabase, useLastOpenedTable } from "../[database]/hook" // export const metadata: Metadata = { // title: "Forms", @@ -45,9 +45,9 @@ interface SettingsLayoutProps { export default function SettingsLayout({ children }: SettingsLayoutProps) { const router = useRouter() - const lastOpenedDatabase = useLastOpenedDatabase() + const lastOpenedTable = useLastOpenedTable() const goBack = () => - lastOpenedDatabase ? router.push(`${lastOpenedDatabase}`) : router.push("/") + lastOpenedTable ? router.push(`/${lastOpenedTable}`) : router.push("/") useKeyPress("esc", (e) => { e.preventDefault() goBack() diff --git a/components/sidebar/create-table.tsx b/components/sidebar/create-table.tsx index 7d9f3130..7847d7b4 100644 --- a/components/sidebar/create-table.tsx +++ b/components/sidebar/create-table.tsx @@ -15,22 +15,31 @@ import { } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" +import { Progress } from "@/components/ui/progress" import { csvFile2Sql } from "./helper" export function CreateTableDialog() { const [open, setOpen] = useState(false) const [tableName, setTableName] = useState("") + const [importing, setImporting] = useState(false) + const [progress, setProgress] = useState(0) const [file, setFile] = useState(null) const params = useParams() const router = useRouter() const { database } = params - const { createTable, createTableWithSql } = useSqlite(database) + const { createTable, createTableWithSqlAndInsertSqls } = useSqlite(database) const handleCreateTable = async () => { if (file) { const res = await csvFile2Sql(file, tableName.trim()) - await createTableWithSql(res.createTableSql, res.insertSql) + setImporting(true) + await createTableWithSqlAndInsertSqls( + res.createTableSql, + res.sqls, + setProgress + ) + setImporting(false) // await createTableWithSql(res.createTableSql, res.insertSql) router.push(`/${database}/${tableName}`) setOpen(false) @@ -49,6 +58,7 @@ export function CreateTableDialog() { + Create Table @@ -86,6 +96,7 @@ export function CreateTableDialog() { /> + {importing && }