mirror of
https://github.com/deiucanta/chatpad.git
synced 2026-03-11 09:04:31 +00:00
feat: change icon
This commit is contained in:
parent
39863efbde
commit
29ea107f0f
10 changed files with 693 additions and 603 deletions
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
app_name: "Dialogue AI"
|
||||
app_name: "Dialogue"
|
||||
tagline: "An open-source ChatGPT UI alternative"
|
||||
git: "https://github.com/deta/dialogue"
|
||||
theme_color: "#F76705"
|
||||
|
|
@ -19,4 +19,4 @@ ported_from: "https://github.com/deiucanta/chatpad"
|
|||
|
||||
## Credits
|
||||
|
||||
Dialogue AI is a fork of [Chatpad AI](https://github.com/deiucanta/chatpad) with modfications to make it run on Deta Space and various changes and new features like "Incognito Mode", shared chats and a reworked prompts system. It is built on top of the amazing work done by [@deiucanta](https://github.com/deiucanta) and contributors.
|
||||
Dialogue is a fork of [Chatpad AI](https://github.com/deiucanta/chatpad) with modfications to make it run on Deta Space and various changes and new features like "Incognito Mode", shared chats and a reworked prompts system. It is built on top of the amazing work done by [@deiucanta](https://github.com/deiucanta) and contributors.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<img src="https://github.com/deta/dialogue/blob/main/public/assets/favicon.png?raw=true" title="icon" alt="icon" width="128">
|
||||
|
||||
# Dialogue AI
|
||||
# Dialogue
|
||||
|
||||
An open-source ChatGPT UI alternative. Fork of [Chatpad AI](https://github.com/deiucanta/chatpad)
|
||||
|
||||
|
|
@ -33,4 +33,4 @@ An open-source ChatGPT UI alternative. Fork of [Chatpad AI](https://github.com/d
|
|||
|
||||
## Credits
|
||||
|
||||
Dialogue AI is a fork of [Chatpad AI](https://github.com/deiucanta/chatpad) with modfications to make it run on Deta Space and various changes and new features like "Incognito Mode", shared chats and a reworked prompts system. It is built on top of the amazing work done by [@deiucanta](https://github.com/deiucanta) and contributors.
|
||||
Dialogue is a fork of [Chatpad AI](https://github.com/deiucanta/chatpad) with modfications to make it run on Deta Space and various changes and new features like "Incognito Mode", shared chats and a reworked prompts system. It is built on top of the amazing work done by [@deiucanta](https://github.com/deiucanta) and contributors.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Dialogue AI</title>
|
||||
<title>Dialogue</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="A free, open-source, and slick UI for ChatGPT"
|
||||
|
|
|
|||
BIN
public/.DS_Store
vendored
Normal file
BIN
public/.DS_Store
vendored
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 148 KiB |
BIN
public/icon.png
BIN
public/icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 148 KiB |
|
|
@ -1,146 +1,160 @@
|
|||
import { notifications } from "@mantine/notifications";
|
||||
import { SpotlightProvider, spotlight } from '@mantine/spotlight';
|
||||
import { SpotlightProvider, spotlight } from "@mantine/spotlight";
|
||||
import { cloneElement, ReactElement, useEffect, useState } from "react";
|
||||
import { useChat, useSpaceAppActions } from "../hooks/contexts";
|
||||
import { IconBolt, IconSearch } from "@tabler/icons-react";
|
||||
import { AppAction } from "../db";
|
||||
|
||||
export function SelectIntegrationModal({
|
||||
contentText,
|
||||
contentHtml,
|
||||
children,
|
||||
contentText,
|
||||
contentHtml,
|
||||
children,
|
||||
}: {
|
||||
contentText: string;
|
||||
contentHtml: string;
|
||||
children: ReactElement;
|
||||
contentText: string;
|
||||
contentHtml: string;
|
||||
children: ReactElement;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { chat } = useChat()
|
||||
const { setSpaceAppActions } = useSpaceAppActions()
|
||||
const { chat } = useChat();
|
||||
const { setSpaceAppActions } = useSpaceAppActions();
|
||||
|
||||
const [spotlightActions, setSpotlightActions] = useState<any[]>([]);
|
||||
const [spotlightActions, setSpotlightActions] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
const res = await fetch(`/api/space/actions`)
|
||||
const items = await res.json()
|
||||
const actions = items as unknown as AppAction[]
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
const res = await fetch(`/api/space/actions`);
|
||||
const items = await res.json();
|
||||
const actions = items as unknown as AppAction[];
|
||||
|
||||
setSpaceAppActions(actions)
|
||||
setSpaceAppActions(actions);
|
||||
|
||||
const spotlightActionsItems = actions.map(action => ({
|
||||
title: action.title,
|
||||
description: action.instance_alias,
|
||||
icon: action.icon_url ? <img width={30} style={{ borderRadius: 5 }} src={action.icon_url} alt={action.title} /> : <IconBolt size={28} />,
|
||||
onTrigger: async () => {
|
||||
try {
|
||||
if (action) {
|
||||
const titleKeys = ['title', 'name']
|
||||
const contentKeys = ['content', 'description', 'text']
|
||||
|
||||
let payload: { [key: string]: string } = {}
|
||||
for (const input of action.input!) {
|
||||
if (titleKeys.includes(input.name)) {
|
||||
payload[input.name] = chat?.description || 'Dialogue AI Chat'
|
||||
} else if (contentKeys.includes(input.name)) {
|
||||
payload[input.name] = action.app_name === 'Minima' ? contentHtml : contentText
|
||||
} else if (input.name === 'html') {
|
||||
payload[input.name] = contentHtml
|
||||
}
|
||||
}
|
||||
const spotlightActionsItems = actions.map((action) => ({
|
||||
title: action.title,
|
||||
description: action.instance_alias,
|
||||
icon: action.icon_url ? (
|
||||
<img
|
||||
width={30}
|
||||
style={{ borderRadius: 5 }}
|
||||
src={action.icon_url}
|
||||
alt={action.title}
|
||||
/>
|
||||
) : (
|
||||
<IconBolt size={28} />
|
||||
),
|
||||
onTrigger: async () => {
|
||||
try {
|
||||
if (action) {
|
||||
const titleKeys = ["title", "name"];
|
||||
const contentKeys = ["content", "description", "text"];
|
||||
|
||||
const res = await fetch(`/api/space/actions/invoke`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
instanceId: action.instance_id,
|
||||
actionName: action.name,
|
||||
payload: payload
|
||||
})
|
||||
})
|
||||
let payload: { [key: string]: string } = {};
|
||||
for (const input of action.input!) {
|
||||
if (titleKeys.includes(input.name)) {
|
||||
payload[input.name] = chat?.description || "Dialogue Chat";
|
||||
} else if (contentKeys.includes(input.name)) {
|
||||
payload[input.name] =
|
||||
action.app_name === "Minima" ? contentHtml : contentText;
|
||||
} else if (input.name === "html") {
|
||||
payload[input.name] = contentHtml;
|
||||
}
|
||||
}
|
||||
|
||||
const invocation = await res.json()
|
||||
console.log(invocation)
|
||||
if (invocation.type === '@deta/raw') {
|
||||
navigator.clipboard.writeText(invocation.data)
|
||||
} else if (invocation.type === '@deta/detail') {
|
||||
navigator.clipboard.writeText(invocation.data.ref || invocation.data.url || invocation.data.title || invocation.data.text)
|
||||
} else {
|
||||
navigator.clipboard.writeText(JSON.stringify(invocation.data))
|
||||
}
|
||||
|
||||
notifications.show({
|
||||
title: "Success",
|
||||
color: "green",
|
||||
message: `Action "${action.title}" invoked successfully.`,
|
||||
})
|
||||
} else {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
color: "red",
|
||||
message: "Integration not found.",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
});
|
||||
}
|
||||
const message = error.response?.data?.error?.message;
|
||||
if (message) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
color: "red",
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
const res = await fetch(`/api/space/actions/invoke`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}))
|
||||
body: JSON.stringify({
|
||||
instanceId: action.instance_id,
|
||||
actionName: action.name,
|
||||
payload: payload,
|
||||
}),
|
||||
});
|
||||
|
||||
setSpotlightActions(spotlightActionsItems)
|
||||
spotlight.open()
|
||||
}
|
||||
if (open) {
|
||||
dataFetch()
|
||||
}
|
||||
}, [open])
|
||||
const invocation = await res.json();
|
||||
console.log(invocation);
|
||||
if (invocation.type === "@deta/raw") {
|
||||
navigator.clipboard.writeText(invocation.data);
|
||||
} else if (invocation.type === "@deta/detail") {
|
||||
navigator.clipboard.writeText(
|
||||
invocation.data.ref ||
|
||||
invocation.data.url ||
|
||||
invocation.data.title ||
|
||||
invocation.data.text
|
||||
);
|
||||
} else {
|
||||
navigator.clipboard.writeText(JSON.stringify(invocation.data));
|
||||
}
|
||||
|
||||
const handleOpen = () => {
|
||||
setOpen(true)
|
||||
notifications.show({
|
||||
title: "Success",
|
||||
color: "green",
|
||||
message: `Action "${action.title}" invoked successfully.`,
|
||||
});
|
||||
} else {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
color: "red",
|
||||
message: "Integration not found.",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
});
|
||||
}
|
||||
const message = error.response?.data?.error?.message;
|
||||
if (message) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
color: "red",
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
setSpotlightActions(spotlightActionsItems);
|
||||
spotlight.open();
|
||||
};
|
||||
if (open) {
|
||||
dataFetch();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{cloneElement(children, { onClick: handleOpen })}
|
||||
{open && (
|
||||
<SpotlightProvider
|
||||
actions={spotlightActions}
|
||||
searchIcon={<img src="/assets/integrations/deta.png" height={28} />}
|
||||
searchPlaceholder="Search Space App Actions..."
|
||||
shortcut={null}
|
||||
nothingFoundMessage="Nothing found..."
|
||||
onSpotlightOpen={() => setOpen(true)}
|
||||
onSpotlightClose={() => setOpen(false)}
|
||||
limit={Infinity}
|
||||
styles={{
|
||||
'content': {
|
||||
border: '4px solid #dbdbdb88'
|
||||
},
|
||||
'searchInput': {
|
||||
'border-bottom': '3px solid #dbdbdb54 !important',
|
||||
}
|
||||
}}
|
||||
>
|
||||
</SpotlightProvider>
|
||||
)}
|
||||
{/* <Modal opened={opened} onClose={close} title="Space App Actions" withinPortal size="lg">
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{cloneElement(children, { onClick: handleOpen })}
|
||||
{open && (
|
||||
<SpotlightProvider
|
||||
actions={spotlightActions}
|
||||
searchIcon={<img src="/assets/integrations/deta.png" height={28} />}
|
||||
searchPlaceholder="Search Space App Actions..."
|
||||
shortcut={null}
|
||||
nothingFoundMessage="Nothing found..."
|
||||
onSpotlightOpen={() => setOpen(true)}
|
||||
onSpotlightClose={() => setOpen(false)}
|
||||
limit={Infinity}
|
||||
styles={{
|
||||
content: {
|
||||
border: "4px solid #dbdbdb88",
|
||||
},
|
||||
searchInput: {
|
||||
"border-bottom": "3px solid #dbdbdb54 !important",
|
||||
},
|
||||
}}
|
||||
></SpotlightProvider>
|
||||
)}
|
||||
{/* <Modal opened={opened} onClose={close} title="Space App Actions" withinPortal size="lg">
|
||||
<form
|
||||
onSubmit={async (event) => {
|
||||
try {
|
||||
|
|
@ -162,7 +176,7 @@ export function SelectIntegrationModal({
|
|||
let payload: { [key: string]: string } = {}
|
||||
for (const input of action.input!) {
|
||||
if (titleKeys.includes(input.name)) {
|
||||
payload[input.name] = chat?.description || 'Dialogue AI Chat'
|
||||
payload[input.name] = chat?.description || 'Dialogue Chat'
|
||||
} else if (contentKeys.includes(input.name)) {
|
||||
payload[input.name] = contentHtml
|
||||
}
|
||||
|
|
@ -234,6 +248,6 @@ export function SelectIntegrationModal({
|
|||
</Stack>
|
||||
</form>
|
||||
</Modal> */}
|
||||
</>
|
||||
);
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
import {
|
||||
ActionIcon,
|
||||
AppShell,
|
||||
Box,
|
||||
Burger,
|
||||
Flex,
|
||||
MediaQuery,
|
||||
Navbar,
|
||||
rem,
|
||||
ScrollArea,
|
||||
SegmentedControl,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
Text,
|
||||
useMantineColorScheme,
|
||||
useMantineTheme,
|
||||
Skeleton,
|
||||
Stack,
|
||||
ActionIcon,
|
||||
AppShell,
|
||||
Box,
|
||||
Burger,
|
||||
Flex,
|
||||
MediaQuery,
|
||||
Navbar,
|
||||
rem,
|
||||
ScrollArea,
|
||||
SegmentedControl,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
Text,
|
||||
useMantineColorScheme,
|
||||
useMantineTheme,
|
||||
Skeleton,
|
||||
Stack,
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
IconSpy,
|
||||
IconSpyOff,
|
||||
IconX,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
IconSpy,
|
||||
IconSpyOff,
|
||||
IconX,
|
||||
} from "@tabler/icons-react";
|
||||
import { Link, useNavigate, useRouter } from "@tanstack/react-location";
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
|
|
@ -33,326 +33,358 @@ import { CreatePromptModal } from "../components/CreatePromptModal";
|
|||
import { LogoIcon } from "../components/Logo";
|
||||
import { SettingsModal } from "../components/SettingsModal";
|
||||
import { config } from "../utils/config";
|
||||
import { ChatContext, ChatsContext, IncognitoModeContext, PromptsContext, SettingsContext, SpaceAppActionsContext } from "../hooks/contexts";
|
||||
import {
|
||||
ChatContext,
|
||||
ChatsContext,
|
||||
IncognitoModeContext,
|
||||
PromptsContext,
|
||||
SettingsContext,
|
||||
SpaceAppActionsContext,
|
||||
} from "../hooks/contexts";
|
||||
import { ChatHeader } from "../components/ChatHeader";
|
||||
import { useLocalStorage } from "@mantine/hooks";
|
||||
import { CreateChatButton } from "../components/CreateChatButton";
|
||||
import { DeleteChatsModal } from "../components/DeleteChatsModal";
|
||||
import React from "react";
|
||||
|
||||
const Prompts = React.lazy(() => import('../components/Prompts'));
|
||||
const Prompts = React.lazy(() => import("../components/Prompts"));
|
||||
|
||||
export function BaseLayout({ children }: { children: React.ReactNode }) {
|
||||
const theme = useMantineTheme();
|
||||
const navigate = useNavigate();
|
||||
const router = useRouter();
|
||||
const theme = useMantineTheme();
|
||||
const navigate = useNavigate();
|
||||
const router = useRouter();
|
||||
|
||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||
const chatId = useChatId();
|
||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||
const chatId = useChatId();
|
||||
|
||||
const [opened, setOpened] = useState(false);
|
||||
const [tab, setTab] = useState<"Chats" | "Prompts">("Chats");
|
||||
const [search, setSearch] = useState("");
|
||||
const [opened, setOpened] = useState(false);
|
||||
const [tab, setTab] = useState<"Chats" | "Prompts">("Chats");
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const [incognitoMode, setIncognitoMode] = useLocalStorage({
|
||||
key: 'incognito-mode', defaultValue: false, getInitialValueInEffect: false
|
||||
});
|
||||
const [incognitoMode, setIncognitoMode] = useLocalStorage({
|
||||
key: "incognito-mode",
|
||||
defaultValue: false,
|
||||
getInitialValueInEffect: false,
|
||||
});
|
||||
|
||||
const [settings, setSettings] = useState<Settings | null>(null);
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
let item = await detaDB.settings.get('general');
|
||||
const [settings, setSettings] = useState<Settings | null>(null);
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
let item = await detaDB.settings.get("general");
|
||||
|
||||
if (!item) {
|
||||
item = await detaDB.settings.put({
|
||||
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 }),
|
||||
}, 'general');
|
||||
}
|
||||
if (!item) {
|
||||
item = await detaDB.settings.put(
|
||||
{
|
||||
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,
|
||||
}),
|
||||
},
|
||||
"general"
|
||||
);
|
||||
}
|
||||
|
||||
setSettings(item as unknown as Settings);
|
||||
};
|
||||
setSettings(item as unknown as Settings);
|
||||
};
|
||||
|
||||
dataFetch();
|
||||
}, []);
|
||||
dataFetch();
|
||||
}, []);
|
||||
|
||||
const [spaceAppActions, setSpaceAppActions] = useState<any[] | null>(null);
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
const res = await fetch(`/api/space/actions/config`)
|
||||
const config = await res.json()
|
||||
const [spaceAppActions, setSpaceAppActions] = useState<any[] | null>(null);
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
const res = await fetch(`/api/space/actions/config`);
|
||||
const config = await res.json();
|
||||
|
||||
if (config.isSetup) {
|
||||
setSpaceAppActions([])
|
||||
}
|
||||
};
|
||||
if (config.isSetup) {
|
||||
setSpaceAppActions([]);
|
||||
}
|
||||
};
|
||||
|
||||
dataFetch();
|
||||
}, []);
|
||||
dataFetch();
|
||||
}, []);
|
||||
|
||||
const [chat, setChat] = useState<Chat | null>(null);
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
const item = await detaDB.chats.get(chatId!);
|
||||
const fetchedChat = item as unknown as Chat
|
||||
const [chat, setChat] = useState<Chat | null>(null);
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
const item = await detaDB.chats.get(chatId!);
|
||||
const fetchedChat = item as unknown as Chat;
|
||||
|
||||
setChat(fetchedChat);
|
||||
setChat(fetchedChat);
|
||||
|
||||
if (fetchedChat.private && !incognitoMode) {
|
||||
setIncognitoMode(true)
|
||||
if (colorScheme === 'light') {
|
||||
toggleColorScheme()
|
||||
}
|
||||
} else if (!(fetchedChat.private ?? false) && incognitoMode) {
|
||||
setIncognitoMode(false)
|
||||
if (colorScheme === 'dark') {
|
||||
toggleColorScheme()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (chatId) {
|
||||
dataFetch();
|
||||
} else {
|
||||
setChat(null);
|
||||
if (fetchedChat.private && !incognitoMode) {
|
||||
setIncognitoMode(true);
|
||||
if (colorScheme === "light") {
|
||||
toggleColorScheme();
|
||||
}
|
||||
}, [chatId]);
|
||||
|
||||
const [fetchingChats, setFetchingChats] = useState<boolean>(false);
|
||||
const [chats, setChats] = useState<Chat[]>([]);
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
setFetchingChats(true);
|
||||
const { items } = await detaDB.chats.fetch(incognitoMode ? { private: true } : { 'private?ne': true });
|
||||
|
||||
setChats(items as unknown as Chat[]);
|
||||
setFetchingChats(false);
|
||||
};
|
||||
|
||||
dataFetch();
|
||||
}, [incognitoMode]);
|
||||
|
||||
const [prompts, setPrompts] = useState<Prompt[]>([]);
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
const { items } = await detaDB.prompts.fetch();
|
||||
|
||||
setPrompts(items as unknown as Prompt[]);
|
||||
};
|
||||
|
||||
dataFetch();
|
||||
}, []);
|
||||
|
||||
const border = `${rem(1)} solid ${
|
||||
theme.colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[2]
|
||||
}`;
|
||||
|
||||
useEffect(() => {
|
||||
setOpened(false);
|
||||
}, [router.state.location]);
|
||||
|
||||
const handleIncognito = () => {
|
||||
const newValue = !incognitoMode
|
||||
|
||||
// if we are in a chat that doesn't match the mode, navigate to home view
|
||||
if (chat && (chat?.private ?? false) !== newValue) {
|
||||
setChat(null)
|
||||
navigate({ to: `/`, replace: true });
|
||||
} else if (!(fetchedChat.private ?? false) && incognitoMode) {
|
||||
setIncognitoMode(false);
|
||||
if (colorScheme === "dark") {
|
||||
toggleColorScheme();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setIncognitoMode(newValue)
|
||||
if (chatId) {
|
||||
dataFetch();
|
||||
} else {
|
||||
setChat(null);
|
||||
}
|
||||
}, [chatId]);
|
||||
|
||||
const isDark = colorScheme === 'dark'
|
||||
if (newValue && !isDark) {
|
||||
toggleColorScheme()
|
||||
} else if (!newValue && isDark) {
|
||||
toggleColorScheme()
|
||||
}
|
||||
const [fetchingChats, setFetchingChats] = useState<boolean>(false);
|
||||
const [chats, setChats] = useState<Chat[]>([]);
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
setFetchingChats(true);
|
||||
const { items } = await detaDB.chats.fetch(
|
||||
incognitoMode ? { private: true } : { "private?ne": true }
|
||||
);
|
||||
|
||||
setChats(items as unknown as Chat[]);
|
||||
setFetchingChats(false);
|
||||
};
|
||||
|
||||
dataFetch();
|
||||
}, [incognitoMode]);
|
||||
|
||||
const [prompts, setPrompts] = useState<Prompt[]>([]);
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
const { items } = await detaDB.prompts.fetch();
|
||||
|
||||
setPrompts(items as unknown as Prompt[]);
|
||||
};
|
||||
|
||||
dataFetch();
|
||||
}, []);
|
||||
|
||||
const border = `${rem(1)} solid ${
|
||||
theme.colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[2]
|
||||
}`;
|
||||
|
||||
useEffect(() => {
|
||||
setOpened(false);
|
||||
}, [router.state.location]);
|
||||
|
||||
const handleIncognito = () => {
|
||||
const newValue = !incognitoMode;
|
||||
|
||||
// if we are in a chat that doesn't match the mode, navigate to home view
|
||||
if (chat && (chat?.private ?? false) !== newValue) {
|
||||
setChat(null);
|
||||
navigate({ to: `/`, replace: true });
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsContext.Provider value={{ settings: settings, setSettings: setSettings }}>
|
||||
<SpaceAppActionsContext.Provider value={{ spaceAppActions: spaceAppActions, setSpaceAppActions: setSpaceAppActions }}>
|
||||
<ChatContext.Provider value={{ chat: chat, setChat: setChat }}>
|
||||
<ChatsContext.Provider value={{ chats: chats, setChats: setChats }}>
|
||||
<PromptsContext.Provider value={{ prompts: prompts, setPrompts: setPrompts }}>
|
||||
<IncognitoModeContext.Provider value={{ incognitoMode: incognitoMode, setIncognitoMode: setIncognitoMode }}>
|
||||
<AppShell
|
||||
className={`${colorScheme}-theme`}
|
||||
navbarOffsetBreakpoint="sm"
|
||||
navbar={
|
||||
<Navbar width={{ md: 300 }} hiddenBreakpoint="md" hidden={!opened}>
|
||||
<Navbar.Section className="app-region-drag">
|
||||
<Box
|
||||
style={{
|
||||
height: 60,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: 10,
|
||||
borderBottom: border,
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to="/"
|
||||
className="app-region-no-drag"
|
||||
style={{ padding: 4, textDecoration: 'none', color: 'inherit', display: 'flex', alignItems: 'center', gap: 8 }}
|
||||
>
|
||||
<LogoIcon style={{ height: 22, display: "block", }} color1={theme.colors[theme.primaryColor][3]} color2={theme.colors[theme.primaryColor][7]} />
|
||||
<Text size='1.25rem' weight={600}>
|
||||
Dialogue AI
|
||||
</Text>
|
||||
</Link>
|
||||
<Box style={{ display: 'flex', alignItems: 'center' }}>
|
||||
{incognitoMode ? (
|
||||
<Tooltip label="Disable Incognito Mode">
|
||||
<ActionIcon
|
||||
size="xl"
|
||||
onClick={handleIncognito}
|
||||
>
|
||||
<IconSpyOff size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip label="Incognito Mode">
|
||||
<ActionIcon
|
||||
size="xl"
|
||||
onClick={handleIncognito}
|
||||
>
|
||||
<IconSpy size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
<SettingsModal>
|
||||
<Tooltip label="Settings">
|
||||
<ActionIcon
|
||||
size="xl"
|
||||
sx={(theme) => ({
|
||||
[theme.fn.smallerThan('md')]: {
|
||||
marginRight: '2.5rem',
|
||||
},
|
||||
})}
|
||||
>
|
||||
<IconSettings size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</SettingsModal>
|
||||
</Box>
|
||||
</Box>
|
||||
</Navbar.Section>
|
||||
<Navbar.Section
|
||||
sx={(theme) => ({
|
||||
padding: rem(4),
|
||||
background:
|
||||
theme.colorScheme === "dark"
|
||||
? theme.colors.dark[8]
|
||||
: theme.colors.gray[1],
|
||||
borderBottom: border,
|
||||
})}
|
||||
>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={tab}
|
||||
onChange={(value) => setTab(value as typeof tab)}
|
||||
data={["Chats", "Prompts"]}
|
||||
/>
|
||||
</Navbar.Section>
|
||||
<Navbar.Section
|
||||
sx={(theme) => ({
|
||||
padding: rem(4),
|
||||
background:
|
||||
theme.colorScheme === "dark"
|
||||
? theme.colors.dark[7]
|
||||
: theme.white,
|
||||
borderBottom: border,
|
||||
})}
|
||||
>
|
||||
<TextInput
|
||||
variant="unstyled"
|
||||
radius={0}
|
||||
placeholder="Search"
|
||||
value={search}
|
||||
onChange={(event) =>
|
||||
setSearch(event.currentTarget.value.toLowerCase())
|
||||
}
|
||||
sx={{ paddingInline: 4 }}
|
||||
icon={<IconSearch opacity={0.8} size={20} />}
|
||||
rightSection={
|
||||
!!search && (
|
||||
<ActionIcon onClick={() => setSearch("")}>
|
||||
<IconX opacity={0.5} size={20} />{" "}
|
||||
</ActionIcon>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Navbar.Section>
|
||||
<Navbar.Section grow component={ScrollArea} id="chats">
|
||||
{tab === "Chats" && (fetchingChats ? (
|
||||
<Stack spacing="xs" mt={8} px={10}>
|
||||
<Skeleton height="2rem" />
|
||||
<Skeleton height="2rem" />
|
||||
<Skeleton height="2rem" />
|
||||
</Stack>
|
||||
) : (<Chats search={search} />))}
|
||||
{tab === "Prompts" && (
|
||||
<Suspense fallback={
|
||||
<Stack spacing="xs" mt={8} px={10}>
|
||||
<Skeleton height="2rem" />
|
||||
<Skeleton height="2rem" />
|
||||
<Skeleton height="2rem" />
|
||||
</Stack>
|
||||
}>
|
||||
<Prompts search={search} onPlay={() => setTab("Chats")} />
|
||||
</Suspense>
|
||||
)}
|
||||
</Navbar.Section>
|
||||
<Navbar.Section>
|
||||
<Flex direction="column" p={10} gap="xs">
|
||||
{tab === "Chats" && (
|
||||
<>
|
||||
{ incognitoMode && (
|
||||
<DeleteChatsModal />
|
||||
)}
|
||||
<CreateChatButton fullWidth>
|
||||
{incognitoMode ? "New Private Chat" : "New Chat"}
|
||||
</CreateChatButton>
|
||||
</>
|
||||
)}
|
||||
{tab === "Prompts" && <CreatePromptModal />}
|
||||
</Flex>
|
||||
</Navbar.Section>
|
||||
</Navbar>
|
||||
}
|
||||
header={
|
||||
chat ? (
|
||||
<ChatHeader />
|
||||
) : undefined
|
||||
}
|
||||
layout="alt"
|
||||
padding={0}
|
||||
setIncognitoMode(newValue);
|
||||
|
||||
const isDark = colorScheme === "dark";
|
||||
if (newValue && !isDark) {
|
||||
toggleColorScheme();
|
||||
} else if (!newValue && isDark) {
|
||||
toggleColorScheme();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsContext.Provider
|
||||
value={{ settings: settings, setSettings: setSettings }}
|
||||
>
|
||||
<SpaceAppActionsContext.Provider
|
||||
value={{
|
||||
spaceAppActions: spaceAppActions,
|
||||
setSpaceAppActions: setSpaceAppActions,
|
||||
}}
|
||||
>
|
||||
<ChatContext.Provider value={{ chat: chat, setChat: setChat }}>
|
||||
<ChatsContext.Provider value={{ chats: chats, setChats: setChats }}>
|
||||
<PromptsContext.Provider
|
||||
value={{ prompts: prompts, setPrompts: setPrompts }}
|
||||
>
|
||||
<IncognitoModeContext.Provider
|
||||
value={{
|
||||
incognitoMode: incognitoMode,
|
||||
setIncognitoMode: setIncognitoMode,
|
||||
}}
|
||||
>
|
||||
<AppShell
|
||||
className={`${colorScheme}-theme`}
|
||||
navbarOffsetBreakpoint="sm"
|
||||
navbar={
|
||||
<Navbar width={{ md: 300 }} hiddenBreakpoint="md" hidden={!opened}>
|
||||
<Navbar.Section className="app-region-drag">
|
||||
<Box
|
||||
style={{
|
||||
height: 60,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: 10,
|
||||
borderBottom: border,
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to="/"
|
||||
className="app-region-no-drag"
|
||||
style={{
|
||||
padding: 4,
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<LogoIcon
|
||||
style={{ height: 22, display: "block" }}
|
||||
color1={theme.colors[theme.primaryColor][3]}
|
||||
color2={theme.colors[theme.primaryColor][7]}
|
||||
/>
|
||||
<Text size="1.25rem" weight={600}>
|
||||
Dialogue
|
||||
</Text>
|
||||
</Link>
|
||||
<Box style={{ display: "flex", alignItems: "center" }}>
|
||||
{incognitoMode ? (
|
||||
<Tooltip label="Disable Incognito Mode">
|
||||
<ActionIcon size="xl" onClick={handleIncognito}>
|
||||
<IconSpyOff size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip label="Incognito Mode">
|
||||
<ActionIcon size="xl" onClick={handleIncognito}>
|
||||
<IconSpy size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
<SettingsModal>
|
||||
<Tooltip label="Settings">
|
||||
<ActionIcon
|
||||
size="xl"
|
||||
sx={(theme) => ({
|
||||
[theme.fn.smallerThan("md")]: {
|
||||
marginRight: "2.5rem",
|
||||
},
|
||||
})}
|
||||
>
|
||||
<MediaQuery largerThan="md" styles={{ display: "none" }}>
|
||||
<Burger
|
||||
opened={opened}
|
||||
onClick={() => setOpened((o) => !o)}
|
||||
size="sm"
|
||||
color={theme.colors.gray[6]}
|
||||
className="app-region-no-drag"
|
||||
sx={{ position: "fixed", top: 16, right: 16, zIndex: 100 }}
|
||||
/>
|
||||
</MediaQuery>
|
||||
{children}
|
||||
</AppShell>
|
||||
</IncognitoModeContext.Provider>
|
||||
</PromptsContext.Provider>
|
||||
</ChatsContext.Provider>
|
||||
</ChatContext.Provider>
|
||||
</SpaceAppActionsContext.Provider>
|
||||
</SettingsContext.Provider>
|
||||
);
|
||||
<IconSettings size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</SettingsModal>
|
||||
</Box>
|
||||
</Box>
|
||||
</Navbar.Section>
|
||||
<Navbar.Section
|
||||
sx={(theme) => ({
|
||||
padding: rem(4),
|
||||
background:
|
||||
theme.colorScheme === "dark"
|
||||
? theme.colors.dark[8]
|
||||
: theme.colors.gray[1],
|
||||
borderBottom: border,
|
||||
})}
|
||||
>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={tab}
|
||||
onChange={(value) => setTab(value as typeof tab)}
|
||||
data={["Chats", "Prompts"]}
|
||||
/>
|
||||
</Navbar.Section>
|
||||
<Navbar.Section
|
||||
sx={(theme) => ({
|
||||
padding: rem(4),
|
||||
background:
|
||||
theme.colorScheme === "dark" ? theme.colors.dark[7] : theme.white,
|
||||
borderBottom: border,
|
||||
})}
|
||||
>
|
||||
<TextInput
|
||||
variant="unstyled"
|
||||
radius={0}
|
||||
placeholder="Search"
|
||||
value={search}
|
||||
onChange={(event) =>
|
||||
setSearch(event.currentTarget.value.toLowerCase())
|
||||
}
|
||||
sx={{ paddingInline: 4 }}
|
||||
icon={<IconSearch opacity={0.8} size={20} />}
|
||||
rightSection={
|
||||
!!search && (
|
||||
<ActionIcon onClick={() => setSearch("")}>
|
||||
<IconX opacity={0.5} size={20} />{" "}
|
||||
</ActionIcon>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Navbar.Section>
|
||||
<Navbar.Section grow component={ScrollArea} id="chats">
|
||||
{tab === "Chats" &&
|
||||
(fetchingChats ? (
|
||||
<Stack spacing="xs" mt={8} px={10}>
|
||||
<Skeleton height="2rem" />
|
||||
<Skeleton height="2rem" />
|
||||
<Skeleton height="2rem" />
|
||||
</Stack>
|
||||
) : (
|
||||
<Chats search={search} />
|
||||
))}
|
||||
{tab === "Prompts" && (
|
||||
<Suspense
|
||||
fallback={
|
||||
<Stack spacing="xs" mt={8} px={10}>
|
||||
<Skeleton height="2rem" />
|
||||
<Skeleton height="2rem" />
|
||||
<Skeleton height="2rem" />
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Prompts search={search} onPlay={() => setTab("Chats")} />
|
||||
</Suspense>
|
||||
)}
|
||||
</Navbar.Section>
|
||||
<Navbar.Section>
|
||||
<Flex direction="column" p={10} gap="xs">
|
||||
{tab === "Chats" && (
|
||||
<>
|
||||
{incognitoMode && <DeleteChatsModal />}
|
||||
<CreateChatButton fullWidth>
|
||||
{incognitoMode ? "New Private Chat" : "New Chat"}
|
||||
</CreateChatButton>
|
||||
</>
|
||||
)}
|
||||
{tab === "Prompts" && <CreatePromptModal />}
|
||||
</Flex>
|
||||
</Navbar.Section>
|
||||
</Navbar>
|
||||
}
|
||||
header={chat ? <ChatHeader /> : undefined}
|
||||
layout="alt"
|
||||
padding={0}
|
||||
>
|
||||
<MediaQuery largerThan="md" styles={{ display: "none" }}>
|
||||
<Burger
|
||||
opened={opened}
|
||||
onClick={() => setOpened((o) => !o)}
|
||||
size="sm"
|
||||
color={theme.colors.gray[6]}
|
||||
className="app-region-no-drag"
|
||||
sx={{ position: "fixed", top: 16, right: 16, zIndex: 100 }}
|
||||
/>
|
||||
</MediaQuery>
|
||||
{children}
|
||||
</AppShell>
|
||||
</IncognitoModeContext.Provider>
|
||||
</PromptsContext.Provider>
|
||||
</ChatsContext.Provider>
|
||||
</ChatContext.Provider>
|
||||
</SpaceAppActionsContext.Provider>
|
||||
</SettingsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import {
|
||||
AppShell,
|
||||
useMantineColorScheme,
|
||||
} from "@mantine/core";
|
||||
import { AppShell, useMantineColorScheme } from "@mantine/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Chat } from "../db";
|
||||
import { usePublicChatId } from "../hooks/useChatId";
|
||||
|
|
@ -10,52 +7,54 @@ import { ChatHeader } from "../components/ChatHeader";
|
|||
import { useLocalStorage } from "@mantine/hooks";
|
||||
|
||||
export function PublicLayout({ children }: { children: React.ReactNode }) {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const chatId = usePublicChatId();
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const chatId = usePublicChatId();
|
||||
|
||||
const [incognitoMode, setIncognitoMode] = useLocalStorage({
|
||||
key: 'incognito-mode', defaultValue: false, getInitialValueInEffect: false
|
||||
});
|
||||
const [incognitoMode, setIncognitoMode] = useLocalStorage({
|
||||
key: "incognito-mode",
|
||||
defaultValue: false,
|
||||
getInitialValueInEffect: false,
|
||||
});
|
||||
|
||||
const [chat, setChat] = useState<Chat | null>(null);
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/public/chats/${chatId}`)
|
||||
const item = await res.json()
|
||||
const [chat, setChat] = useState<Chat | null>(null);
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/public/chats/${chatId}`);
|
||||
const item = await res.json();
|
||||
|
||||
const fetchedChat = item as unknown as Chat
|
||||
setChat(fetchedChat);
|
||||
const fetchedChat = item as unknown as Chat;
|
||||
setChat(fetchedChat);
|
||||
|
||||
document.title = fetchedChat.description ? `${fetchedChat.description} | Dialogue AI` : 'Dialogue AI'
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
};
|
||||
document.title = fetchedChat.description
|
||||
? `${fetchedChat.description} | Dialogue`
|
||||
: "Dialogue";
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
if (chatId) {
|
||||
dataFetch();
|
||||
} else {
|
||||
setChat(null);
|
||||
}
|
||||
}, [chatId]);
|
||||
if (chatId) {
|
||||
dataFetch();
|
||||
} else {
|
||||
setChat(null);
|
||||
}
|
||||
}, [chatId]);
|
||||
|
||||
return (
|
||||
<ChatContext.Provider value={{ chat: chat, setChat: setChat }}>
|
||||
<IncognitoModeContext.Provider value={{ incognitoMode: incognitoMode, setIncognitoMode: setIncognitoMode }}>
|
||||
<AppShell
|
||||
className={`${colorScheme}-theme`}
|
||||
header={
|
||||
chat ? (
|
||||
<ChatHeader readOnly />
|
||||
) : undefined
|
||||
}
|
||||
layout="alt"
|
||||
padding={0}
|
||||
>
|
||||
{children}
|
||||
</AppShell>
|
||||
</IncognitoModeContext.Provider>
|
||||
</ChatContext.Provider>
|
||||
);
|
||||
return (
|
||||
<ChatContext.Provider value={{ chat: chat, setChat: setChat }}>
|
||||
<IncognitoModeContext.Provider
|
||||
value={{ incognitoMode: incognitoMode, setIncognitoMode: setIncognitoMode }}
|
||||
>
|
||||
<AppShell
|
||||
className={`${colorScheme}-theme`}
|
||||
header={chat ? <ChatHeader readOnly /> : undefined}
|
||||
layout="alt"
|
||||
padding={0}
|
||||
>
|
||||
{children}
|
||||
</AppShell>
|
||||
</IncognitoModeContext.Provider>
|
||||
</ChatContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,22 @@
|
|||
import { Container, SimpleGrid, Flex, Button, Text, useMantineTheme, Center, Box, Tooltip, ActionIcon, Card } from "@mantine/core";
|
||||
import { useChats, useIncognitoMode, usePrompts, useSettings } from "../hooks/contexts";
|
||||
import {
|
||||
Container,
|
||||
SimpleGrid,
|
||||
Flex,
|
||||
Button,
|
||||
Text,
|
||||
useMantineTheme,
|
||||
Center,
|
||||
Box,
|
||||
Tooltip,
|
||||
ActionIcon,
|
||||
Card,
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
useChats,
|
||||
useIncognitoMode,
|
||||
usePrompts,
|
||||
useSettings,
|
||||
} from "../hooks/contexts";
|
||||
import { Welcome } from "../components/Welcome";
|
||||
import { LogoIcon } from "../components/Logo";
|
||||
import { CreateChatButton } from "../components/CreateChatButton";
|
||||
|
|
@ -9,120 +26,148 @@ import { notifications } from "@mantine/notifications";
|
|||
import { Chat, detaDB, generateKey } from "../db";
|
||||
import { useNavigate } from "@tanstack/react-location";
|
||||
|
||||
|
||||
export function IndexRoute() {
|
||||
const { settings } = useSettings()
|
||||
const theme = useMantineTheme()
|
||||
const { incognitoMode } = useIncognitoMode()
|
||||
const navigate = useNavigate();
|
||||
const { settings } = useSettings();
|
||||
const theme = useMantineTheme();
|
||||
const { incognitoMode } = useIncognitoMode();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { setChats } = useChats()
|
||||
const { prompts } = usePrompts()
|
||||
const { setChats } = useChats();
|
||||
const { prompts } = usePrompts();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Center py="xl" sx={{ height: "100%" }}>
|
||||
{prompts && prompts.length > 0 ? (
|
||||
<Container size="md" fluid>
|
||||
<Flex align="center" gap={10}>
|
||||
<LogoIcon style={{ maxWidth: 45 }} color1={theme.colors[theme.primaryColor][3]} color2={theme.colors[theme.primaryColor][7]} />
|
||||
<Text size='2.25rem' weight={700}>
|
||||
Dialogue AI
|
||||
</Text>
|
||||
</Flex>
|
||||
<Text mt={4} size="xl">
|
||||
An open-source ChatGPT UI alternative
|
||||
</Text>
|
||||
<SimpleGrid
|
||||
mt={50}
|
||||
cols={3}
|
||||
spacing={10}
|
||||
breakpoints={[{ maxWidth: "lg", cols: 1 }]}
|
||||
>
|
||||
{prompts.slice(-9).reverse().map((prompt) => (
|
||||
<Card key={prompt.key} display="flex" padding="sm" radius="md" withBorder style={{ overflow: 'unset', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Box maw="180px">
|
||||
<Text
|
||||
weight={500}
|
||||
sx={{
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{prompt.title}
|
||||
</Text>
|
||||
<Text
|
||||
color="dimmed"
|
||||
sx={{
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{prompt.content || prompt.writingCharacter || prompt.writingTone || prompt.writingFormat || prompt.writingStyle || "No Instructions"}
|
||||
</Text>
|
||||
</Box>
|
||||
return (
|
||||
<>
|
||||
<Center py="xl" sx={{ height: "100%" }}>
|
||||
{prompts && prompts.length > 0 ? (
|
||||
<Container size="md" fluid>
|
||||
<Flex align="center" gap={10}>
|
||||
<LogoIcon
|
||||
style={{ maxWidth: 45 }}
|
||||
color1={theme.colors[theme.primaryColor][3]}
|
||||
color2={theme.colors[theme.primaryColor][7]}
|
||||
/>
|
||||
<Text size="2.25rem" weight={700}>
|
||||
Dialogue
|
||||
</Text>
|
||||
</Flex>
|
||||
<Text mt={4} size="xl">
|
||||
An open-source ChatGPT UI alternative
|
||||
</Text>
|
||||
<SimpleGrid
|
||||
mt={50}
|
||||
cols={3}
|
||||
spacing={10}
|
||||
breakpoints={[{ maxWidth: "lg", cols: 1 }]}
|
||||
>
|
||||
{prompts
|
||||
.slice(-9)
|
||||
.reverse()
|
||||
.map((prompt) => (
|
||||
<Card
|
||||
key={prompt.key}
|
||||
display="flex"
|
||||
padding="sm"
|
||||
radius="md"
|
||||
withBorder
|
||||
style={{
|
||||
overflow: "unset",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Box maw="180px">
|
||||
<Text
|
||||
weight={500}
|
||||
sx={{
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{prompt.title}
|
||||
</Text>
|
||||
<Text
|
||||
color="dimmed"
|
||||
sx={{
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{prompt.content ||
|
||||
prompt.writingCharacter ||
|
||||
prompt.writingTone ||
|
||||
prompt.writingFormat ||
|
||||
prompt.writingStyle ||
|
||||
"No Instructions"}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Tooltip label="Start Chat From Prompt">
|
||||
<ActionIcon
|
||||
size="lg"
|
||||
onClick={async () => {
|
||||
if (!settings?.openAiApiKey) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
color: "red",
|
||||
message: "OpenAI API Key is not defined. Please set your API Key",
|
||||
});
|
||||
return;
|
||||
};
|
||||
<Tooltip label="Start Chat From Prompt">
|
||||
<ActionIcon
|
||||
size="lg"
|
||||
onClick={async () => {
|
||||
if (!settings?.openAiApiKey) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
color: "red",
|
||||
message:
|
||||
"OpenAI API Key is not defined. Please set your API Key",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const item = await detaDB.chats.put({
|
||||
description: prompt.title ? `New ${prompt.title} Chat` : "New Chat",
|
||||
prompt: prompt.key,
|
||||
writingInstructions: prompt.content,
|
||||
writingCharacter: prompt.writingCharacter,
|
||||
writingTone: prompt.writingTone,
|
||||
writingStyle: prompt.writingStyle,
|
||||
writingFormat: prompt.writingFormat,
|
||||
totalTokens: 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
}, generateKey())
|
||||
const item = await detaDB.chats.put(
|
||||
{
|
||||
description: prompt.title
|
||||
? `New ${prompt.title} Chat`
|
||||
: "New Chat",
|
||||
prompt: prompt.key,
|
||||
writingInstructions: prompt.content,
|
||||
writingCharacter: prompt.writingCharacter,
|
||||
writingTone: prompt.writingTone,
|
||||
writingStyle: prompt.writingStyle,
|
||||
writingFormat: prompt.writingFormat,
|
||||
totalTokens: 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
generateKey()
|
||||
);
|
||||
|
||||
const chat = item as unknown as Chat
|
||||
setChats(current => ([...(current || []), chat]))
|
||||
const chat = item as unknown as Chat;
|
||||
setChats((current) => [...(current || []), chat]);
|
||||
|
||||
navigate({ to: `/chats/${chat.key}` });
|
||||
}}
|
||||
>
|
||||
<IconPlayerPlay size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Card>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
<Flex mt={50} align='center' gap='md' wrap="wrap">
|
||||
{settings?.openAiApiKey && (
|
||||
<CreateChatButton size="md">
|
||||
{incognitoMode ? "Create a New Private Chat" : "Create a New Chat"}
|
||||
</CreateChatButton>
|
||||
)}
|
||||
<SettingsModal>
|
||||
<Button
|
||||
size="md"
|
||||
variant={settings?.openAiApiKey ? "light" : "filled"}
|
||||
leftIcon={<IconKey size={20} />}
|
||||
>
|
||||
{settings?.openAiApiKey ? "Change OpenAI Key" : "Enter OpenAI Key"}
|
||||
</Button>
|
||||
</SettingsModal>
|
||||
</Flex>
|
||||
</Container>
|
||||
) : (
|
||||
<Welcome />
|
||||
)}
|
||||
</Center>
|
||||
</>
|
||||
);
|
||||
navigate({ to: `/chats/${chat.key}` });
|
||||
}}
|
||||
>
|
||||
<IconPlayerPlay size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Card>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
<Flex mt={50} align="center" gap="md" wrap="wrap">
|
||||
{settings?.openAiApiKey && (
|
||||
<CreateChatButton size="md">
|
||||
{incognitoMode ? "Create a New Private Chat" : "Create a New Chat"}
|
||||
</CreateChatButton>
|
||||
)}
|
||||
<SettingsModal>
|
||||
<Button
|
||||
size="md"
|
||||
variant={settings?.openAiApiKey ? "light" : "filled"}
|
||||
leftIcon={<IconKey size={20} />}
|
||||
>
|
||||
{settings?.openAiApiKey ? "Change OpenAI Key" : "Enter OpenAI Key"}
|
||||
</Button>
|
||||
</SettingsModal>
|
||||
</Flex>
|
||||
</Container>
|
||||
) : (
|
||||
<Welcome />
|
||||
)}
|
||||
</Center>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue