fixes & discovery

This commit is contained in:
BetaHuhn 2023-08-30 11:20:56 +02:00
parent d79dd616ce
commit 19db10b1b0
13 changed files with 84 additions and 271 deletions

8
Discovery.md Normal file
View file

@ -0,0 +1,8 @@
---
app_name: "Chatpad AI"
tagline: "Just another ChatGPT user-interface"
git: "https://github.com/deta/chatpad"
theme_color: "#08A678"
---

View file

@ -1,5 +1,6 @@
# Spacefile Docs: https://go.deta.dev/docs/spacefile/v0
v: 0
icon: public/icon.png
micros:
- name: app
src: .

BIN
public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

View file

@ -58,7 +58,6 @@ export function Chats({ search }: { search: string }) {
<MainLink
icon={<IconMessages size="1rem" />}
color="teal"
chat={chat}
label={chat.description}
/>
</Link>

View file

@ -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 })}
<Modal
opened={opened}
onClose={close}
title="Database"
size="lg"
withinPortal
keepMounted
>
<Stack>
<Flex>
<Card withBorder sx={{ flex: 1 }}>
<Text size="xl" align="center">
{chatsCount}
</Text>
<Text
size="xs"
color="dimmed"
align="center"
tt="uppercase"
fw={700}
>
Chats
</Text>
</Card>
<Card withBorder sx={{ flex: 1, marginLeft: -1 }}>
<Text size="xl" align="center">
{messagesCount}
</Text>
<Text
size="xs"
color="dimmed"
align="center"
tt="uppercase"
fw={700}
>
Messages
</Text>
</Card>
<Card withBorder sx={{ flex: 1, marginLeft: -1 }}>
<Text size="xl" align="center">
{promptsCount}
</Text>
<Text
size="xs"
color="dimmed"
align="center"
tt="uppercase"
fw={700}
>
Prompts
</Text>
</Card>
</Flex>
<Group>
<Button
variant="default"
leftIcon={<IconDatabaseExport size={20} />}
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
</Button>
<input
id="file-upload-btn"
type="file"
onChange={async (e) => {
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
/>
<Button
component="label"
htmlFor="file-upload-btn"
variant="default"
leftIcon={<IconDatabaseImport size={20} />}
>
Import Data
</Button>
</Group>
<Group>
<DeleteChatsModal onOpen={close} />
<DeleteAllDataModal onOpen={close} />
</Group>
</Stack>
</Modal>
</>
);
}

View file

@ -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 (
<>
<Button
onClick={open}
variant="outline"
color="red"
leftIcon={<IconTrash size={20} />}
>
Delete All Data
</Button>
<Modal
opened={opened}
onClose={close}
title="Delete All Data"
size="md"
withinPortal
>
<Stack>
<Text size="sm">Are you sure you want to delete your data?</Text>
<Button
onClick={async () => {
await db.delete();
localStorage.clear();
window.location.assign("/");
}}
color="red"
>
Delete
</Button>
</Stack>
</Modal>
</>
);
}

View file

@ -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() {
</Tooltip>
</SettingsModal>
)}
{config.allowDatabaseModal && (
<DatabaseModal>
<Tooltip label="Database">
<ActionIcon sx={{ flex: 1 }} size="xl">
<IconDatabase size={20} />
</ActionIcon>
</Tooltip>
</DatabaseModal>
)}
{config.githubUrl && (
<Tooltip label="Source Code">
<ActionIcon

View file

@ -1,21 +1,12 @@
import { Group, Text, ThemeIcon, UnstyledButton } from "@mantine/core";
import { useLiveQuery } from "dexie-react-hooks";
import { Chat, db } from "../db";
interface MainLinkProps {
icon: React.ReactNode;
color: string;
label: string;
chat: Chat;
}
export function MainLink({ icon, color, label, chat }: MainLinkProps) {
const firstMessage = useLiveQuery(async () => {
return (await db.messages.orderBy("createdAt").toArray()).filter(
(m) => m.chatId === chat.id
)[0];
}, [chat]);
export function MainLink({ icon, color, label }: MainLinkProps) {
return (
<UnstyledButton
sx={(theme) => ({
@ -41,8 +32,7 @@ export function MainLink({ icon, color, label, chat }: MainLinkProps) {
width: 0,
}}
>
{label} <br />
{firstMessage?.content}
{label}
</Text>
</Group>
</UnstyledButton>

View file

@ -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:

View file

@ -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.",

View file

@ -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<Chat>;
messages!: Table<Message>;
prompts!: Table<Prompt>;
settings!: Table<Settings>;
// export class Database extends Dexie {
// chats!: Table<Chat>;
// messages!: Table<Message>;
// prompts!: Table<Prompt>;
// settings!: Table<Settings>;
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();

View file

@ -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(),

View file

@ -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",