feat: change icon

This commit is contained in:
Cristian Crețu 2023-09-22 14:03:38 +03:00
parent 39863efbde
commit 29ea107f0f
No known key found for this signature in database
GPG key ID: 57D21B19BF5249D2
10 changed files with 693 additions and 603 deletions

View file

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

View file

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

View file

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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 148 KiB

View file

@ -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> */}
</>
);
</>
);
}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>
</>
);
}