mirror of
https://github.com/deiucanta/chatpad.git
synced 2026-03-11 09:04:31 +00:00
feat: customizable and reusable prompts
This commit is contained in:
parent
19db10b1b0
commit
0d265e51ff
8 changed files with 412 additions and 171 deletions
|
|
@ -2,6 +2,8 @@ import {
|
|||
ActionIcon,
|
||||
Button,
|
||||
Modal,
|
||||
Select,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Textarea,
|
||||
TextInput,
|
||||
|
|
@ -13,31 +15,45 @@ import { IconPlaylistAdd, IconPlus } from "@tabler/icons-react";
|
|||
import { useEffect, useState } from "react";
|
||||
import { detaDB, generateKey, Prompt } from "../db";
|
||||
import { usePrompts } from "../hooks/contexts";
|
||||
import { config } from "../utils/config";
|
||||
|
||||
export function CreatePromptModal({ content }: { content?: string }) {
|
||||
export function CreatePromptModal({ content, title: titleProp, open: openProp }: { content?: string, title?: string, open?: boolean }) {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const { setPrompts } = usePrompts()
|
||||
|
||||
const [writingCharacter, setWritingCharacter] = useState<string | null>(null);
|
||||
const [writingTone, setWritingTone] = useState<string | null>(null);
|
||||
const [writingStyle, setWritingStyle] = useState<string | null>(null);
|
||||
const [writingFormat, setWritingFormat] = useState<string | null>(null);
|
||||
|
||||
const [value, setValue] = useState("");
|
||||
const [title, setTitle] = useState("");
|
||||
useEffect(() => {
|
||||
setValue(content ?? "");
|
||||
}, [content]);
|
||||
setTitle(titleProp ?? "");
|
||||
if (openProp) {
|
||||
open()
|
||||
}
|
||||
}, [content, titleProp, openProp]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{content ? (
|
||||
<Tooltip label="Save Prompt" position="top">
|
||||
<ActionIcon onClick={open}>
|
||||
<IconPlaylistAdd opacity={0.5} size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button fullWidth onClick={open} leftIcon={<IconPlus size={20} />}>
|
||||
New Prompt
|
||||
</Button>
|
||||
{!openProp && (
|
||||
<>
|
||||
{content ? (
|
||||
<Tooltip label="Save Prompt" position="top">
|
||||
<ActionIcon onClick={open}>
|
||||
<IconPlaylistAdd opacity={0.5} size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button fullWidth onClick={open} leftIcon={<IconPlus size={20} />}>
|
||||
New Prompt
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Modal opened={opened} onClose={close} title="Create Prompt" size="lg">
|
||||
<form
|
||||
|
|
@ -49,6 +65,10 @@ export function CreatePromptModal({ content }: { content?: string }) {
|
|||
const item = await detaDB.prompts.put({
|
||||
title,
|
||||
content: value,
|
||||
writingCharacter,
|
||||
writingTone,
|
||||
writingStyle,
|
||||
writingFormat,
|
||||
createdAt: new Date().toISOString(),
|
||||
}, generateKey())
|
||||
|
||||
|
|
@ -62,6 +82,10 @@ export function CreatePromptModal({ content }: { content?: string }) {
|
|||
|
||||
setTitle("")
|
||||
setValue("")
|
||||
setWritingCharacter(null)
|
||||
setWritingTone(null)
|
||||
setWritingStyle(null)
|
||||
setWritingFormat(null)
|
||||
|
||||
close();
|
||||
} catch (error: any) {
|
||||
|
|
@ -88,13 +112,62 @@ export function CreatePromptModal({ content }: { content?: string }) {
|
|||
<Stack>
|
||||
<TextInput
|
||||
label="Title"
|
||||
placeholder="Prompt title"
|
||||
value={title}
|
||||
onChange={(event) => setTitle(event.currentTarget.value)}
|
||||
formNoValidate
|
||||
data-autofocus
|
||||
/>
|
||||
<SimpleGrid
|
||||
spacing="xs"
|
||||
breakpoints={[
|
||||
{ minWidth: "sm", cols: 4 },
|
||||
{ maxWidth: "sm", cols: 2 },
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
value={writingCharacter}
|
||||
onChange={setWritingCharacter}
|
||||
data={config.writingCharacters}
|
||||
placeholder="Character"
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<Select
|
||||
value={writingTone}
|
||||
onChange={setWritingTone}
|
||||
data={config.writingTones}
|
||||
placeholder="Tone"
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<Select
|
||||
value={writingStyle}
|
||||
onChange={setWritingStyle}
|
||||
data={config.writingStyles}
|
||||
placeholder="Style"
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<Select
|
||||
value={writingFormat}
|
||||
onChange={setWritingFormat}
|
||||
data={config.writingFormats}
|
||||
placeholder="Format"
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
<Textarea
|
||||
placeholder="Content"
|
||||
placeholder="Further instructions..."
|
||||
autosize
|
||||
minRows={5}
|
||||
maxRows={10}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import {
|
|||
ActionIcon,
|
||||
Button,
|
||||
Modal,
|
||||
Select,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Textarea,
|
||||
TextInput,
|
||||
|
|
@ -13,6 +15,7 @@ import { IconPencil } from "@tabler/icons-react";
|
|||
import { useEffect, useState } from "react";
|
||||
import { detaDB, Prompt } from "../db";
|
||||
import { usePrompts } from "../hooks/contexts";
|
||||
import { config } from "../utils/config";
|
||||
|
||||
export function EditPromptModal({ prompt }: { prompt: Prompt }) {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
|
@ -20,11 +23,20 @@ export function EditPromptModal({ prompt }: { prompt: Prompt }) {
|
|||
|
||||
const { setPrompts } = usePrompts()
|
||||
|
||||
const [writingCharacter, setWritingCharacter] = useState<string | null>(null);
|
||||
const [writingTone, setWritingTone] = useState<string | null>(null);
|
||||
const [writingStyle, setWritingStyle] = useState<string | null>(null);
|
||||
const [writingFormat, setWritingFormat] = useState<string | null>(null);
|
||||
|
||||
const [value, setValue] = useState("");
|
||||
const [title, setTitle] = useState("");
|
||||
useEffect(() => {
|
||||
setValue(prompt?.content ?? "");
|
||||
setTitle(prompt?.title ?? "");
|
||||
setWritingCharacter(prompt?.writingCharacter ?? null);
|
||||
setWritingTone(prompt?.writingTone ?? null);
|
||||
setWritingStyle(prompt?.writingStyle ?? null);
|
||||
setWritingFormat(prompt?.writingFormat ?? null);
|
||||
}, [prompt]);
|
||||
|
||||
return (
|
||||
|
|
@ -36,10 +48,21 @@ export function EditPromptModal({ prompt }: { prompt: Prompt }) {
|
|||
setSubmitting(true);
|
||||
event.preventDefault();
|
||||
|
||||
await detaDB.prompts.update({ title: title, content: value }, prompt.key)
|
||||
const updates: Partial<Prompt> = {
|
||||
title,
|
||||
content: value,
|
||||
writingCharacter,
|
||||
writingFormat,
|
||||
writingStyle,
|
||||
writingTone
|
||||
}
|
||||
|
||||
console.log({ updates })
|
||||
|
||||
await detaDB.prompts.update(updates, prompt.key)
|
||||
setPrompts(current => (current || []).map(item => {
|
||||
if (item.key === prompt.key) {
|
||||
return { ...item, title, content: value };
|
||||
return { ...item, ...updates };
|
||||
}
|
||||
|
||||
return item;
|
||||
|
|
@ -80,8 +103,57 @@ export function EditPromptModal({ prompt }: { prompt: Prompt }) {
|
|||
formNoValidate
|
||||
data-autofocus
|
||||
/>
|
||||
<SimpleGrid
|
||||
spacing="xs"
|
||||
breakpoints={[
|
||||
{ minWidth: "sm", cols: 4 },
|
||||
{ maxWidth: "sm", cols: 2 },
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
value={writingCharacter}
|
||||
onChange={setWritingCharacter}
|
||||
data={config.writingCharacters}
|
||||
placeholder="Character"
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<Select
|
||||
value={writingTone}
|
||||
onChange={setWritingTone}
|
||||
data={config.writingTones}
|
||||
placeholder="Tone"
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<Select
|
||||
value={writingStyle}
|
||||
onChange={setWritingStyle}
|
||||
data={config.writingStyles}
|
||||
placeholder="Style"
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<Select
|
||||
value={writingFormat}
|
||||
onChange={setWritingFormat}
|
||||
data={config.writingFormats}
|
||||
placeholder="Format"
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
<Textarea
|
||||
label="Content"
|
||||
placeholder="Further instructions..."
|
||||
autosize
|
||||
minRows={5}
|
||||
maxRows={10}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import {
|
|||
Box,
|
||||
Burger,
|
||||
Button,
|
||||
Center,
|
||||
Header,
|
||||
MediaQuery,
|
||||
Navbar,
|
||||
|
|
@ -17,14 +16,9 @@ import {
|
|||
useMantineTheme,
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
IconBrandGithub,
|
||||
IconBrandTwitter,
|
||||
IconMessage,
|
||||
IconMoonStars,
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
IconSunHigh,
|
||||
IconX,
|
||||
} from "@tabler/icons-react";
|
||||
import { Link, Outlet, useNavigate, useRouter } from "@tanstack/react-location";
|
||||
|
|
@ -49,7 +43,7 @@ export function Layout() {
|
|||
const theme = useMantineTheme();
|
||||
const [opened, setOpened] = useState(false);
|
||||
const [tab, setTab] = useState<"Chats" | "Prompts">("Chats");
|
||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const navigate = useNavigate();
|
||||
const router = useRouter();
|
||||
|
||||
|
|
@ -140,7 +134,8 @@ export function Layout() {
|
|||
height: 60,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: 10,
|
||||
borderBottom: border,
|
||||
}}
|
||||
>
|
||||
|
|
@ -157,16 +152,29 @@ export function Layout() {
|
|||
}}
|
||||
/>
|
||||
</Link>
|
||||
<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", right: 16 }}
|
||||
/>
|
||||
</MediaQuery>
|
||||
<Box
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<SettingsModal>
|
||||
<Tooltip label="Settings">
|
||||
<ActionIcon size="xl">
|
||||
<IconSettings size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</SettingsModal>
|
||||
<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"
|
||||
/>
|
||||
</MediaQuery>
|
||||
</Box>
|
||||
</Box>
|
||||
</Navbar.Section>
|
||||
<Navbar.Section
|
||||
|
|
@ -185,36 +193,6 @@ export function Layout() {
|
|||
onChange={(value) => setTab(value as typeof tab)}
|
||||
data={["Chats", "Prompts"]}
|
||||
/>
|
||||
<Box sx={{ padding: 4 }}>
|
||||
{tab === "Chats" && (
|
||||
<Button
|
||||
fullWidth
|
||||
leftIcon={<IconPlus size={20} />}
|
||||
onClick={async () => {
|
||||
const item = await detaDB.chats.put({
|
||||
description: "New Chat",
|
||||
totalTokens: 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
}, generateKey())
|
||||
|
||||
setChats(chats => ([...(chats || []), item as unknown as Chat]))
|
||||
|
||||
const id = item!.key as string
|
||||
// const id = nanoid();
|
||||
// db.chats.add({
|
||||
// id,
|
||||
// description: "New Chat",
|
||||
// totalTokens: 0,
|
||||
// createdAt: new Date(),
|
||||
// });
|
||||
navigate({ to: `/chats/${id}`, replace: true });
|
||||
}}
|
||||
>
|
||||
New Chat
|
||||
</Button>
|
||||
)}
|
||||
{tab === "Prompts" && <CreatePromptModal />}
|
||||
</Box>
|
||||
</Navbar.Section>
|
||||
<Navbar.Section
|
||||
sx={(theme) => ({
|
||||
|
|
@ -251,7 +229,39 @@ export function Layout() {
|
|||
<Prompts search={search} onPlay={() => setTab("Chats")} />
|
||||
)}
|
||||
</Navbar.Section>
|
||||
<Navbar.Section sx={{ borderTop: border }} p="xs">
|
||||
<Navbar.Section>
|
||||
<Box sx={{ padding: 10 }}>
|
||||
{tab === "Chats" && (
|
||||
<Button
|
||||
fullWidth
|
||||
leftIcon={<IconPlus size={20} />}
|
||||
onClick={async () => {
|
||||
const item = await detaDB.chats.put({
|
||||
description: "New Chat",
|
||||
totalTokens: 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
}, generateKey())
|
||||
|
||||
setChats(chats => ([...(chats || []), item as unknown as Chat]))
|
||||
|
||||
const id = item!.key as string
|
||||
// const id = nanoid();
|
||||
// db.chats.add({
|
||||
// id,
|
||||
// description: "New Chat",
|
||||
// totalTokens: 0,
|
||||
// createdAt: new Date(),
|
||||
// });
|
||||
navigate({ to: `/chats/${id}`, replace: true });
|
||||
}}
|
||||
>
|
||||
New Chat
|
||||
</Button>
|
||||
)}
|
||||
{tab === "Prompts" && <CreatePromptModal />}
|
||||
</Box>
|
||||
</Navbar.Section>
|
||||
{/* <Navbar.Section sx={{ borderTop: border }} p="xs">
|
||||
<Center>
|
||||
{config.allowDarkModeToggle && (
|
||||
<Tooltip
|
||||
|
|
@ -327,7 +337,7 @@ export function Layout() {
|
|||
</Tooltip>
|
||||
)}
|
||||
</Center>
|
||||
</Navbar.Section>
|
||||
</Navbar.Section> */}
|
||||
</Navbar>
|
||||
}
|
||||
header={
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { IconPlayerPlay } from "@tabler/icons-react";
|
|||
import { useNavigate } from "@tanstack/react-location";
|
||||
import { useMemo } from "react";
|
||||
import { Chat, detaDB, generateKey } from "../db";
|
||||
import { createChatCompletion } from "../utils/openai";
|
||||
import { DeletePromptModal } from "./DeletePromptModal";
|
||||
import { EditPromptModal } from "./EditPromptModal";
|
||||
import { useChats, usePrompts, useSettings } from "../hooks/contexts";
|
||||
|
|
@ -75,7 +74,7 @@ export function Prompts({
|
|||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{prompt.content}
|
||||
{prompt.content || prompt.writingCharacter || prompt.writingTone || prompt.writingFormat || prompt.writingStyle}
|
||||
</Text>
|
||||
</Box>
|
||||
<Group spacing="none">
|
||||
|
|
@ -86,7 +85,13 @@ export function Prompts({
|
|||
if (!settings?.openAiApiKey) return;
|
||||
|
||||
const item = await detaDB.chats.put({
|
||||
description: "New Chat",
|
||||
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())
|
||||
|
|
@ -94,47 +99,47 @@ export function Prompts({
|
|||
const chat = item as unknown as Chat
|
||||
setChats(current => ([...(current || []), chat]))
|
||||
|
||||
await detaDB.messages.put({
|
||||
chatId: chat.key,
|
||||
content: prompt.content,
|
||||
role: "user",
|
||||
createdAt: new Date().toISOString(),
|
||||
}, generateKey())
|
||||
// const systemMessage = getSystemMessage(prompt);
|
||||
|
||||
// await detaDB.messages.put({
|
||||
// chatId: chat.key,
|
||||
// content: systemMessage,
|
||||
// role: "system",
|
||||
// createdAt: new Date().toISOString(),
|
||||
// }, generateKey())
|
||||
|
||||
navigate({ to: `/chats/${chat.key}` });
|
||||
onPlay();
|
||||
|
||||
const result = await createChatCompletion(settings, [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"You are ChatGPT, a large language model trained by OpenAI.",
|
||||
},
|
||||
{ role: "user", content: prompt.content },
|
||||
]);
|
||||
// const result = await createChatCompletion(settings, [
|
||||
// {
|
||||
// role: "system",
|
||||
// content: systemMessage,
|
||||
// },
|
||||
// ]);
|
||||
|
||||
const resultDescription =
|
||||
result.data.choices[0].message?.content;
|
||||
// const resultDescription =
|
||||
// result.data.choices[0].message?.content;
|
||||
|
||||
await detaDB.messages.put({
|
||||
chatId: chat.key,
|
||||
content: resultDescription ?? "unknown reponse",
|
||||
role: "assistant",
|
||||
createdAt: new Date().toISOString(),
|
||||
}, generateKey())
|
||||
// await detaDB.messages.put({
|
||||
// chatId: chat.key,
|
||||
// content: resultDescription ?? "unknown reponse",
|
||||
// role: "assistant",
|
||||
// createdAt: new Date().toISOString(),
|
||||
// }, generateKey())
|
||||
|
||||
if (result.data.usage) {
|
||||
// todo: add to chat totalTokens
|
||||
await detaDB.chats.update({ totalTokens: result.data.usage!.total_tokens }, chat.key)
|
||||
// if (result.data.usage) {
|
||||
// // todo: add to chat totalTokens
|
||||
// await detaDB.chats.update({ totalTokens: result.data.usage!.total_tokens }, chat.key)
|
||||
|
||||
// await db.chats.where({ id: chat.key }).modify((chat) => {
|
||||
// if (chat.totalTokens) {
|
||||
// chat.totalTokens += result.data.usage!.total_tokens;
|
||||
// } else {
|
||||
// chat.totalTokens = result.data.usage!.total_tokens;
|
||||
// }
|
||||
// });
|
||||
}
|
||||
// // await db.chats.where({ id: chat.key }).modify((chat) => {
|
||||
// // if (chat.totalTokens) {
|
||||
// // chat.totalTokens += result.data.usage!.total_tokens;
|
||||
// // } else {
|
||||
// // chat.totalTokens = result.data.usage!.total_tokens;
|
||||
// // }
|
||||
// // });
|
||||
// }
|
||||
}}
|
||||
>
|
||||
<IconPlayerPlay size={20} />
|
||||
|
|
|
|||
|
|
@ -201,6 +201,10 @@
|
|||
"Technical"
|
||||
],
|
||||
"writingFormats": [
|
||||
{
|
||||
"value": "Answer with a One-Liner",
|
||||
"label": "One-Liner"
|
||||
},
|
||||
{
|
||||
"value": "Answer as concise as possible",
|
||||
"label": "Concise"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,13 @@ export interface Chat {
|
|||
key: string;
|
||||
description: string;
|
||||
totalTokens: number;
|
||||
createdAt: Date;
|
||||
prompt?: string;
|
||||
writingInstructions?: string | null;
|
||||
writingCharacter?: string | null;
|
||||
writingTone?: string | null;
|
||||
writingStyle?: string | null;
|
||||
writingFormat?: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
|
|
@ -14,14 +20,18 @@ export interface Message {
|
|||
chatId: string;
|
||||
role: "system" | "assistant" | "user";
|
||||
content: string;
|
||||
createdAt: Date;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Prompt {
|
||||
key: string;
|
||||
title: string;
|
||||
content: string;
|
||||
createdAt: Date;
|
||||
writingCharacter?: string | null;
|
||||
writingTone?: string | null;
|
||||
writingStyle?: string | null;
|
||||
writingFormat?: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
Flex,
|
||||
MediaQuery,
|
||||
Select,
|
||||
SimpleGrid,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Textarea,
|
||||
|
|
@ -15,20 +14,23 @@ import { notifications } from "@mantine/notifications";
|
|||
import { KeyboardEvent, useState, type ChangeEvent, useEffect } from "react";
|
||||
import { AiOutlineSend } from "react-icons/ai";
|
||||
import { MessageItem } from "../components/MessageItem";
|
||||
import { Chat, Message, detaDB, generateKey } from "../db";
|
||||
import { Chat, Message, Prompt, detaDB, generateKey } from "../db";
|
||||
import { useChatId } from "../hooks/useChatId";
|
||||
import { config } from "../utils/config";
|
||||
import {
|
||||
createChatCompletion,
|
||||
createStreamChatCompletion,
|
||||
getSystemMessage,
|
||||
} from "../utils/openai";
|
||||
import { useChat, useChats, useSettings } from "../hooks/contexts";
|
||||
import { useChat, useChats, usePrompts, useSettings } from "../hooks/contexts";
|
||||
import { CreatePromptModal } from "../components/CreatePromptModal";
|
||||
|
||||
export function ChatRoute() {
|
||||
const chatId = useChatId();
|
||||
|
||||
const { settings } = useSettings()
|
||||
|
||||
const { prompts } = usePrompts()
|
||||
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -54,6 +56,8 @@ export function ChatRoute() {
|
|||
const [content, setContent] = useState("");
|
||||
const [contentDraft, setContentDraft] = useState("");
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [promptKey, setPromptKey] = useState<string | null>(null);
|
||||
const [newPromptTitle, setNewPromptTitle] = useState<string | null>(null);
|
||||
|
||||
const { setChats } = useChats()
|
||||
const { chat, setChat } = useChat()
|
||||
|
|
@ -61,8 +65,13 @@ export function ChatRoute() {
|
|||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
const item = await detaDB.chats.get(chatId!);
|
||||
const fetchedChat = item as unknown as Chat
|
||||
|
||||
setChat(item as unknown as Chat);
|
||||
setChat(fetchedChat);
|
||||
|
||||
if (fetchedChat.prompt) {
|
||||
setPromptKey(fetchedChat.prompt)
|
||||
}
|
||||
};
|
||||
|
||||
if (!chat) {
|
||||
|
|
@ -75,24 +84,6 @@ export function ChatRoute() {
|
|||
// return db.chats.get(chatId);
|
||||
// }, [chatId]);
|
||||
|
||||
const [writingCharacter, setWritingCharacter] = useState<string | null>(null);
|
||||
const [writingTone, setWritingTone] = useState<string | null>(null);
|
||||
const [writingStyle, setWritingStyle] = useState<string | null>(null);
|
||||
const [writingFormat, setWritingFormat] = useState<string | null>(null);
|
||||
|
||||
const getSystemMessage = () => {
|
||||
const message: string[] = [];
|
||||
if (writingCharacter) message.push(`You are ${writingCharacter}.`);
|
||||
if (writingTone) message.push(`Respond in ${writingTone} tone.`);
|
||||
if (writingStyle) message.push(`Respond in ${writingStyle} style.`);
|
||||
if (writingFormat) message.push(writingFormat);
|
||||
if (message.length === 0)
|
||||
message.push(
|
||||
"You are ChatGPT, a large language model trained by OpenAI."
|
||||
);
|
||||
return message.join(" ");
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
if (submitting) return;
|
||||
|
||||
|
|
@ -117,6 +108,42 @@ export function ChatRoute() {
|
|||
try {
|
||||
setSubmitting(true);
|
||||
|
||||
let systemMessageValue = ""
|
||||
if (promptKey) {
|
||||
const item = await detaDB.prompts.get(promptKey);
|
||||
if (item) {
|
||||
const prompt = item as unknown as Prompt
|
||||
|
||||
systemMessageValue = getSystemMessage({
|
||||
content: prompt.content,
|
||||
character: prompt?.writingCharacter ?? undefined,
|
||||
tone: prompt?.writingTone ?? undefined,
|
||||
style: prompt?.writingStyle ?? undefined,
|
||||
format: prompt?.writingFormat ?? undefined,
|
||||
})
|
||||
|
||||
const updates = {
|
||||
writingInstructions: prompt.content,
|
||||
writingCharacter: prompt.writingCharacter,
|
||||
writingTone: prompt.writingTone,
|
||||
writingStyle: prompt.writingStyle,
|
||||
writingFormat: prompt.writingFormat,
|
||||
}
|
||||
setChat(chat => ({ ...chat!, ...updates }))
|
||||
await detaDB.chats.update(updates, chatId)
|
||||
}
|
||||
}
|
||||
|
||||
if (!systemMessageValue) {
|
||||
systemMessageValue = getSystemMessage({
|
||||
content: chat?.writingInstructions ?? undefined,
|
||||
character: chat?.writingCharacter ?? undefined,
|
||||
tone: chat?.writingTone ?? undefined,
|
||||
style: chat?.writingStyle ?? undefined,
|
||||
format: chat?.writingFormat ?? undefined,
|
||||
})
|
||||
}
|
||||
|
||||
const userMessage = await detaDB.messages.put({
|
||||
chatId,
|
||||
content,
|
||||
|
|
@ -134,6 +161,7 @@ export function ChatRoute() {
|
|||
// createdAt: new Date(),
|
||||
// });
|
||||
setContent("");
|
||||
setPromptKey(null);
|
||||
|
||||
const systemMessage = await detaDB.messages.put({
|
||||
chatId,
|
||||
|
|
@ -159,7 +187,7 @@ export function ChatRoute() {
|
|||
[
|
||||
{
|
||||
role: "system",
|
||||
content: getSystemMessage(),
|
||||
content: systemMessageValue,
|
||||
},
|
||||
...(messages ?? []).map((message) => ({
|
||||
role: message.role,
|
||||
|
|
@ -191,7 +219,7 @@ export function ChatRoute() {
|
|||
const createChatDescription = await createChatCompletion(settings, [
|
||||
{
|
||||
role: "system",
|
||||
content: getSystemMessage(),
|
||||
content: systemMessageValue,
|
||||
},
|
||||
...(messages ?? []).map((message) => ({
|
||||
role: message.role,
|
||||
|
|
@ -329,55 +357,80 @@ export function ChatRoute() {
|
|||
>
|
||||
<Container>
|
||||
{messages?.length === 0 && (
|
||||
<SimpleGrid
|
||||
<Box
|
||||
mb="sm"
|
||||
spacing="xs"
|
||||
breakpoints={[
|
||||
{ minWidth: "sm", cols: 4 },
|
||||
{ maxWidth: "sm", cols: 2 },
|
||||
]}
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
value={writingCharacter}
|
||||
onChange={setWritingCharacter}
|
||||
data={config.writingCharacters}
|
||||
placeholder="Character"
|
||||
value={promptKey}
|
||||
onChange={setPromptKey}
|
||||
data={prompts.map(prompt => ({ value: prompt.key, label: prompt.title }))}
|
||||
placeholder="Select Prompt"
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
sx={{ flex: 1 }}
|
||||
creatable
|
||||
getCreateLabel={(query) => `+ Create "${query}" Prompt`}
|
||||
onCreate={(query) => {
|
||||
setNewPromptTitle(query)
|
||||
return query;
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
value={writingTone}
|
||||
onChange={setWritingTone}
|
||||
data={config.writingTones}
|
||||
placeholder="Tone"
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<Select
|
||||
value={writingStyle}
|
||||
onChange={setWritingStyle}
|
||||
data={config.writingStyles}
|
||||
placeholder="Style"
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<Select
|
||||
value={writingFormat}
|
||||
onChange={setWritingFormat}
|
||||
data={config.writingFormats}
|
||||
placeholder="Format"
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
|
||||
{newPromptTitle && <CreatePromptModal title={newPromptTitle} open={true} />}
|
||||
</Box>
|
||||
// <SimpleGrid
|
||||
// mb="sm"
|
||||
// spacing="xs"
|
||||
// breakpoints={[
|
||||
// { minWidth: "sm", cols: 4 },
|
||||
// { maxWidth: "sm", cols: 2 },
|
||||
// ]}
|
||||
// >
|
||||
// <Select
|
||||
// value={writingCharacter}
|
||||
// onChange={setWritingCharacter}
|
||||
// data={config.writingCharacters}
|
||||
// placeholder="Character"
|
||||
// variant="filled"
|
||||
// searchable
|
||||
// clearable
|
||||
// sx={{ flex: 1 }}
|
||||
// />
|
||||
// <Select
|
||||
// value={writingTone}
|
||||
// onChange={setWritingTone}
|
||||
// data={config.writingTones}
|
||||
// placeholder="Tone"
|
||||
// variant="filled"
|
||||
// searchable
|
||||
// clearable
|
||||
// sx={{ flex: 1 }}
|
||||
// />
|
||||
// <Select
|
||||
// value={writingStyle}
|
||||
// onChange={setWritingStyle}
|
||||
// data={config.writingStyles}
|
||||
// placeholder="Style"
|
||||
// variant="filled"
|
||||
// searchable
|
||||
// clearable
|
||||
// sx={{ flex: 1 }}
|
||||
// />
|
||||
// <Select
|
||||
// value={writingFormat}
|
||||
// onChange={setWritingFormat}
|
||||
// data={config.writingFormats}
|
||||
// placeholder="Format"
|
||||
// variant="filled"
|
||||
// searchable
|
||||
// clearable
|
||||
// sx={{ flex: 1 }}
|
||||
// />
|
||||
// </SimpleGrid>
|
||||
)}
|
||||
<Flex gap="sm">
|
||||
<Textarea
|
||||
|
|
|
|||
|
|
@ -119,3 +119,17 @@ export async function checkOpenAIKey(settings: Settings) {
|
|||
},
|
||||
]);
|
||||
}
|
||||
|
||||
export function getSystemMessage(prompt: { content?: string, character?: string, tone?: string, style?: string, format?: string }) {
|
||||
const message: string[] = [];
|
||||
if (prompt.character) message.push(`You are ${prompt.character}`);
|
||||
if (prompt.tone) message.push(`Respond in ${prompt.tone.toLowerCase()} tone.`);
|
||||
if (prompt.style) message.push(`Respond in ${prompt.style.toLowerCase()} style.`);
|
||||
if (prompt.format) message.push(`${prompt.format.toLowerCase()}.`);
|
||||
if (message.length === 0)
|
||||
message.push(
|
||||
"You are ChatGPT, a large language model trained by OpenAI."
|
||||
);
|
||||
if (prompt.content) message.push(prompt.content);
|
||||
return message.join(" ");
|
||||
};
|
||||
Loading…
Reference in a new issue