diff --git a/Discovery.md b/Discovery.md
new file mode 100644
index 0000000..4259c1c
--- /dev/null
+++ b/Discovery.md
@@ -0,0 +1,8 @@
+---
+app_name: "Chatpad AI"
+tagline: "Just another ChatGPT user-interface"
+git: "https://github.com/deta/chatpad"
+theme_color: "#08A678"
+---
+
+
diff --git a/Spacefile b/Spacefile
index 40091d6..0923cf5 100644
--- a/Spacefile
+++ b/Spacefile
@@ -1,5 +1,6 @@
# Spacefile Docs: https://go.deta.dev/docs/spacefile/v0
v: 0
+icon: public/icon.png
micros:
- name: app
src: .
diff --git a/public/icon.png b/public/icon.png
new file mode 100644
index 0000000..70348ba
Binary files /dev/null and b/public/icon.png differ
diff --git a/src/components/Chats.tsx b/src/components/Chats.tsx
index 2852896..a6af30a 100644
--- a/src/components/Chats.tsx
+++ b/src/components/Chats.tsx
@@ -58,7 +58,6 @@ export function Chats({ search }: { search: string }) {
}
color="teal"
- chat={chat}
label={chat.description}
/>
diff --git a/src/components/DatabaseModal.tsx b/src/components/DatabaseModal.tsx
deleted file mode 100644
index 513e641..0000000
--- a/src/components/DatabaseModal.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-import { Button, Card, Flex, Group, Modal, Stack, Text } from "@mantine/core";
-import { useDisclosure } from "@mantine/hooks";
-import { notifications } from "@mantine/notifications";
-import { IconDatabaseExport, IconDatabaseImport } from "@tabler/icons-react";
-import { useLiveQuery } from "dexie-react-hooks";
-import download from "downloadjs";
-import { cloneElement, ReactElement } from "react";
-import { db } from "../db";
-import { DeleteAllDataModal } from "./DeleteAllDataModal";
-import { DeleteChatsModal } from "./DeleteChatsModal";
-
-export function DatabaseModal({ children }: { children: ReactElement }) {
- const [opened, { open, close }] = useDisclosure(false);
- const chatsCount = useLiveQuery(async () => {
- return (await db.chats.toArray()).length;
- }, []);
- const messagesCount = useLiveQuery(async () => {
- return (await db.messages.toArray()).length;
- }, []);
- const promptsCount = useLiveQuery(async () => {
- return (await db.prompts.toArray()).length;
- });
-
- return (
- <>
- {cloneElement(children, { onClick: open })}
-
-
-
-
-
- {chatsCount}
-
-
- Chats
-
-
-
-
- {messagesCount}
-
-
- Messages
-
-
-
-
- {promptsCount}
-
-
- Prompts
-
-
-
-
- }
- onClick={async () => {
- const blob = await db.export();
- download(
- blob,
- `chatpad-export-${new Date().toLocaleString()}.json`,
- "application/json"
- );
- notifications.show({
- title: "Exporting Data",
- message: "Your data is being exported.",
- });
- }}
- >
- Export Data
-
- {
- const file = e.currentTarget.files?.[0];
- if (!file) return;
- db.import(file, {
- acceptNameDiff: true,
- overwriteValues: true,
- acceptMissingTables: true,
- clearTablesBeforeImport: true,
- })
- .then(() => {
- notifications.show({
- title: "Importing data",
- message: "Your data is being imported.",
- });
- })
- .catch((error) => {
- notifications.show({
- title: "Error",
- color: "red",
- message: "The file you selected is invalid",
- });
- });
- }}
- accept="application/json"
- hidden
- />
- }
- >
- Import Data
-
-
-
-
-
-
-
-
- >
- );
-}
diff --git a/src/components/DeleteAllDataModal.tsx b/src/components/DeleteAllDataModal.tsx
deleted file mode 100644
index 9b4cde6..0000000
--- a/src/components/DeleteAllDataModal.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Button, Modal, Stack, Text } from "@mantine/core";
-import { useDisclosure } from "@mantine/hooks";
-import { IconTrash } from "@tabler/icons-react";
-import { db } from "../db";
-
-export function DeleteAllDataModal({ onOpen }: { onOpen: () => void }) {
- const [opened, { open, close }] = useDisclosure(false, { onOpen });
-
- return (
- <>
- }
- >
- Delete All Data
-
-
-
- Are you sure you want to delete your data?
-
-
-
- >
- );
-}
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index a69ff9e..8b9531a 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -19,7 +19,6 @@ import {
import {
IconBrandGithub,
IconBrandTwitter,
- IconDatabase,
IconMessage,
IconMoonStars,
IconPlus,
@@ -34,7 +33,6 @@ import { Chat, detaDB, generateKey, Prompt, Settings } from "../db";
import { useChatId } from "../hooks/useChatId";
import { Chats } from "./Chats";
import { CreatePromptModal } from "./CreatePromptModal";
-import { DatabaseModal } from "./DatabaseModal";
import { LogoText } from "./Logo";
import { Prompts } from "./Prompts";
import { SettingsModal } from "./SettingsModal";
@@ -281,15 +279,6 @@ export function Layout() {
)}
- {config.allowDatabaseModal && (
-
-
-
-
-
-
-
- )}
{config.githubUrl && (
{
- return (await db.messages.orderBy("createdAt").toArray()).filter(
- (m) => m.chatId === chat.id
- )[0];
- }, [chat]);
-
+export function MainLink({ icon, color, label }: MainLinkProps) {
return (
({
@@ -41,8 +32,7 @@ export function MainLink({ icon, color, label, chat }: MainLinkProps) {
width: 0,
}}
>
- {label}
- {firstMessage?.content}
+ {label}
diff --git a/src/components/Prompts.tsx b/src/components/Prompts.tsx
index fd5993d..fc5a369 100644
--- a/src/components/Prompts.tsx
+++ b/src/components/Prompts.tsx
@@ -104,7 +104,7 @@ export function Prompts({
navigate({ to: `/chats/${chat.key}` });
onPlay();
- const result = await createChatCompletion(settings?.openAiApiKey, [
+ const result = await createChatCompletion(settings, [
{
role: "system",
content:
diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx
index 2168aaf..23507f6 100644
--- a/src/components/SettingsModal.tsx
+++ b/src/components/SettingsModal.tsx
@@ -14,7 +14,7 @@ import {
import { useDisclosure } from "@mantine/hooks";
import { notifications } from "@mantine/notifications";
import { cloneElement, ReactElement, useEffect, useState } from "react";
-import { db, detaDB } from "../db";
+import { Settings, detaDB } from "../db";
import { config } from "../utils/config";
import { checkOpenAIKey } from "../utils/openai";
import { useSettings } from "../hooks/contexts";
@@ -63,7 +63,7 @@ export function SettingsModal({ children }: { children: ReactElement }) {
try {
setSubmitting(true);
event.preventDefault();
- await checkOpenAIKey(value);
+ await checkOpenAIKey({ ...(settings as Settings), openAiApiKey: value });
if (settings?.openAiApiKey) {
await detaDB.settings.update({
@@ -92,14 +92,14 @@ export function SettingsModal({ children }: { children: ReactElement }) {
message: "Your OpenAI Key has been saved.",
});
} catch (error: any) {
- if (error.toJSON().message === "Network Error") {
+ if (error.message === "Network Error") {
notifications.show({
title: "Error",
color: "red",
message: "No internet connection.",
});
}
- const message = error.response?.data?.error?.message;
+ const message = error.message
if (message) {
notifications.show({
title: "Error",
@@ -150,9 +150,13 @@ export function SettingsModal({ children }: { children: ReactElement }) {
onChange={async (value) => {
setSubmitting(true);
try {
- await db.settings.update("general", {
- openAiApiType: value ?? 'openai',
- });
+ const updates = {
+ openAiApiType: value as any ?? 'openai',
+ }
+ await detaDB.settings.update(updates, "general");
+
+ setSettings(current => ({ ...(current!), ...updates }))
+
notifications.show({
title: "Saved",
message: "Your OpenAI Type has been saved.",
@@ -186,9 +190,12 @@ export function SettingsModal({ children }: { children: ReactElement }) {
onChange={async (value) => {
setSubmitting(true);
try {
- await db.settings.update("general", {
+ const updates = {
openAiModel: value ?? undefined,
- });
+ }
+ await detaDB.settings.update(updates, "general");
+ setSettings(current => ({ ...(current!), ...updates }))
+
notifications.show({
title: "Saved",
message: "Your OpenAI Model has been saved.",
@@ -226,9 +233,12 @@ export function SettingsModal({ children }: { children: ReactElement }) {
onChange={async (value) => {
setSubmitting(true);
try {
- await db.settings.update("general", {
- openAiApiAuth: value ?? 'none',
- });
+ const updates = {
+ openAiApiAuth: value as any ?? 'none',
+ }
+ await detaDB.settings.update(updates, "general");
+ setSettings(current => ({ ...(current!), ...updates }))
+
notifications.show({
title: "Saved",
message: "Your OpenAI Auth has been saved.",
@@ -261,10 +271,13 @@ export function SettingsModal({ children }: { children: ReactElement }) {
try {
setSubmitting(true);
event.preventDefault();
- await db.settings.where({ id: "general" }).modify((row) => {
- row.openAiApiBase = base;
- console.log(row);
- });
+
+ const updates = {
+ openAiApiBase: base,
+ }
+ await detaDB.settings.update(updates, "general");
+ setSettings(current => ({ ...(current!), ...updates }))
+
notifications.show({
title: "Saved",
message: "Your OpenAI Base has been saved.",
@@ -309,10 +322,13 @@ export function SettingsModal({ children }: { children: ReactElement }) {
try {
setSubmitting(true);
event.preventDefault();
- await db.settings.where({ id: "general" }).modify((row) => {
- row.openAiApiVersion = version;
- console.log(row);
- });
+
+ const updates = {
+ openAiApiVersion: version,
+ }
+ await detaDB.settings.update(updates, "general");
+ setSettings(current => ({ ...(current!), ...updates }))
+
notifications.show({
title: "Saved",
message: "Your OpenAI Version has been saved.",
diff --git a/src/db/index.ts b/src/db/index.ts
index e7edc46..9a7daf6 100644
--- a/src/db/index.ts
+++ b/src/db/index.ts
@@ -1,7 +1,5 @@
import { Deta } from "deta";
-import Dexie, { Table } from "dexie";
import "dexie-export-import";
-import { config } from "../utils/config";
import { nanoid } from "nanoid";
export interface Chat {
@@ -54,33 +52,33 @@ export const generateKey = (ascending: boolean = true): string => {
return `${ timestamp.toString(16) }${ nanoid(5) }`
}
-export class Database extends Dexie {
- chats!: Table;
- messages!: Table;
- prompts!: Table;
- settings!: Table;
+// export class Database extends Dexie {
+// chats!: Table;
+// messages!: Table;
+// prompts!: Table;
+// settings!: Table;
- constructor() {
- super("chatpad");
- this.version(2).stores({
- chats: "id, createdAt",
- messages: "id, chatId, createdAt",
- prompts: "id, createdAt",
- settings: "id",
- });
+// constructor() {
+// super("chatpad");
+// this.version(2).stores({
+// chats: "id, createdAt",
+// messages: "id, chatId, createdAt",
+// prompts: "id, createdAt",
+// settings: "id",
+// });
- this.on("populate", async () => {
- db.settings.add({
- key: "general",
- openAiModel: config.defaultModel,
- openAiApiType: config.defaultType,
- openAiApiAuth: config.defaultAuth,
- ...(config.defaultKey != '' && { openAiApiKey: config.defaultKey }),
- ...(config.defaultBase != '' && { openAiApiBase: config.defaultBase }),
- ...(config.defaultVersion != '' && { openAiApiVersion: config.defaultVersion }),
- });
- });
- }
-}
+// this.on("populate", async () => {
+// db.settings.add({
+// key: "general",
+// openAiModel: config.defaultModel,
+// openAiApiType: config.defaultType,
+// openAiApiAuth: config.defaultAuth,
+// ...(config.defaultKey != '' && { openAiApiKey: config.defaultKey }),
+// ...(config.defaultBase != '' && { openAiApiBase: config.defaultBase }),
+// ...(config.defaultVersion != '' && { openAiApiVersion: config.defaultVersion }),
+// });
+// });
+// }
+// }
-export const db = new Database();
+// export const db = new Database();
diff --git a/src/routes/ChatRoute.tsx b/src/routes/ChatRoute.tsx
index 6ab984d..d2685af 100644
--- a/src/routes/ChatRoute.tsx
+++ b/src/routes/ChatRoute.tsx
@@ -155,7 +155,7 @@ export function ChatRoute() {
// });
await createStreamChatCompletion(
- settings.openAiApiKey,
+ settings,
[
{
role: "system",
@@ -188,7 +188,7 @@ export function ChatRoute() {
// const messages = await db.messages
// .where({ chatId })
// .sortBy("createdAt");
- const createChatDescription = await createChatCompletion(settings.openAiApiKey, [
+ const createChatDescription = await createChatCompletion(settings, [
{
role: "system",
content: getSystemMessage(),
diff --git a/src/utils/openai.ts b/src/utils/openai.ts
index 39b42a1..fe42e57 100644
--- a/src/utils/openai.ts
+++ b/src/utils/openai.ts
@@ -1,7 +1,7 @@
import { encode } from "gpt-token-utils";
import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";
import { OpenAIExt } from "openai-ext";
-import { db, detaDB } from "../db";
+import { Settings, detaDB } from "../db";
import { config } from "./config";
import { useDebounce } from "./debounce";
@@ -22,13 +22,12 @@ function getClient(
}
export async function createStreamChatCompletion(
- apiKey: string,
+ settings: Settings,
messages: ChatCompletionRequestMessage[],
chatId: string,
messageId: string,
onContent: (content: string, isFinal: boolean) => void
) {
- const settings = await db.settings.get("general");
const model = settings?.openAiModel ?? config.defaultModel;
const debouncedSetStreamContent = useDebounce(setStreamContent, 200);
@@ -39,7 +38,7 @@ export async function createStreamChatCompletion(
messages,
},
{
- apiKey: apiKey,
+ apiKey: settings.openAiApiKey!,
handler: {
onContent(content, isFinal) {
debouncedSetStreamContent(messageId, content, isFinal);
@@ -83,15 +82,15 @@ async function setTotalTokens(chatId: string, content: string) {
}
export async function createChatCompletion(
- apiKey: string,
+ settings: Settings,
messages: ChatCompletionRequestMessage[]
) {
- const settings = await db.settings.get("general");
const model = settings?.openAiModel ?? config.defaultModel;
const type = settings?.openAiApiType ?? config.defaultType;
const auth = settings?.openAiApiAuth ?? config.defaultAuth;
const base = settings?.openAiApiBase ?? config.defaultBase;
const version = settings?.openAiApiVersion ?? config.defaultVersion;
+ const apiKey = settings.openAiApiKey!;
const client = getClient(apiKey, type, auth, base);
return client.createChatCompletion(
@@ -112,8 +111,8 @@ export async function createChatCompletion(
);
}
-export async function checkOpenAIKey(apiKey: string) {
- return createChatCompletion(apiKey, [
+export async function checkOpenAIKey(settings: Settings) {
+ return createChatCompletion(settings, [
{
role: "user",
content: "hello",