diff --git a/TODO.md b/TODO.md index 18e90af..e436226 100644 --- a/TODO.md +++ b/TODO.md @@ -3,3 +3,4 @@ 3. Remove chat configs 4. Command: /fork "question" -> fork chats to new pane. does not focus pane. /new -> opens new pane, with focus 5. Rename temperature db typo +6. Add max tokens to config diff --git a/src/components/Home.tsx b/src/components/Home.tsx index 8a81187..928fb79 100644 --- a/src/components/Home.tsx +++ b/src/components/Home.tsx @@ -3,9 +3,10 @@ import { ConversationList } from "./conversations/Conversations"; import { useLocalStorage } from "./hooks/useLocalStorage"; import { ChatConfigList } from "./chat-config/ChatConfigList"; import { OpenAIKeyInput } from "./OpenAIKey"; +import { OPEN_AI_KEY } from "../utils/openai"; export const Home = () => { - const [storedKey, setStoredKey] = useLocalStorage("openai-key", ""); + const [storedKey, setStoredKey] = useLocalStorage(OPEN_AI_KEY, ""); return (
diff --git a/src/components/chat-config/ChatConfigContext.tsx b/src/components/chat-config/ChatConfigContext.tsx deleted file mode 100644 index b2a4319..0000000 --- a/src/components/chat-config/ChatConfigContext.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { - createContext, - useContext, - FC, - useState, - useCallback, -} from "react"; -import { ChatConfig } from "../../db"; - -interface ChatConfigContextState { - chatConfig: Omit; - isCreatingConfig: boolean; - setTitle: (title: string) => void; - setSystemPrompt: (systemPrompt: string) => void; - setTemperature: (temperature: number) => void; - setShortcut: (shortcut: string | null) => void; - setModel: (model: "gpt-4" | "gpt-3.5-turbo") => void; - submit: () => void; -} - -const ChatConfigContext = createContext(null); - -interface Props { - children: React.ReactNode; - initialChatConfig?: ChatConfig; - onSubmit: (chatConfig: Omit) => void; -} -export const ChatConfigProvider: FC = ({ - children, - initialChatConfig, - onSubmit, -}) => { - const [chatConfig, setChatConfig] = useState>( - initialChatConfig ?? { - title: "", - models: ["gpt-4"], - systemPrompt: "", - temprature: 1.0, - shortcut: null, - } - ); - - const setTitle = useCallback((title: string) => { - setChatConfig((p) => ({ ...p, title })); - }, []); - const setModel = useCallback((model: "gpt-4" | "gpt-3.5-turbo") => { - setChatConfig((p) => ({ ...p, models: [model] })); - }, []); - const setSystemPrompt = useCallback((systemPrompt: string) => { - setChatConfig((p) => ({ ...p, systemPrompt })); - }, []); - const setTemperature = useCallback((temperature: number) => { - // TODO: typo in db - setChatConfig((p) => ({ ...p, temprature: temperature })); - }, []); - const setShortcut = useCallback((shortcut: string | null) => { - setChatConfig((p) => ({ ...p, shortcut })); - }, []); - const submit = useCallback(() => { - onSubmit(chatConfig); - }, [chatConfig, onSubmit]); - return ( - - {children} - - ); -}; - -// eslint-disable-next-line react-refresh/only-export-components -export const useChatConfigContext = () => { - const context = useContext(ChatConfigContext); - if (context == null) { - throw new Error("no chat config context"); - } - return context; -}; diff --git a/src/components/chat-config/ChatConfigList.tsx b/src/components/chat-config/ChatConfigList.tsx index af182c3..e64d2e7 100644 --- a/src/components/chat-config/ChatConfigList.tsx +++ b/src/components/chat-config/ChatConfigList.tsx @@ -9,10 +9,11 @@ import { useLiveQuery } from "dexie-react-hooks"; import { dbSelectChatConfigs } from "../../db/db-selectors"; import { ChatConfigModal } from "./ChatConfigModal"; import { useOpenChatConfigShortcut as useStartChatShortcut } from "./useShortcut"; -import { ChatConfig, db } from "../../db"; +import { db } from "../../db"; +import { ChatConfig } from "../../db/models"; export const ChatConfigList: FC = () => { - const chatConfigs = useLiveQuery(() => dbSelectChatConfigs(), []); + const chatConfigs = useLiveQuery(dbSelectChatConfigs, []); useStartChatShortcut(); return (
@@ -66,6 +67,7 @@ const ChatConfigItem: FC = ({ chatConfig }) => { ? `⌥ + ${chatConfig.shortcut.toUpperCase()}` : null; })(); + return ( <> {isOpen && chatConfig !== "default" && ( @@ -73,7 +75,12 @@ const ChatConfigItem: FC = ({ chatConfig }) => { db.putChatConfig({ ...chatConfig, ...p })} + onSubmit={(modalChatConfig) => { + db.putChatConfig({ + ...chatConfig, + ...modalChatConfig, + }); + }} /> )} @@ -84,7 +91,11 @@ const ChatConfigItem: FC = ({ chatConfig }) => { onClick={onClick} > - file_open + {chatConfig !== "default" && + // TODO: support multiple providers + chatConfig.providers[0]?.type === "godmode" + ? "smart_toy" + : "file_open"}
{chatConfig === "default" ? "Default chat" : chatConfig.title} @@ -111,7 +122,12 @@ const AddChatConfigButton: FC = () => { <> {isOpen && ( - + { + db.addChatConfig(modalChatConfig); + }} + /> )}
; + +interface ChatConfigModalProps { initialChatConfig?: ChatConfig; onClose: () => void; - onSubmit: (chatConfig: Omit) => void; + onSubmit: (chatConfig: ModalChatConfig) => void; } -export const ChatConfigModal: FC = ({ + +export const ChatConfigModal: FC = ({ initialChatConfig, - onSubmit, onClose, + onSubmit, }) => { - return ( - - - - ); -}; - -interface ChatConfigDumbProps { - onClose: () => void; -} - -const ChatConfigDumb: FC = ({ onClose }) => { - const { - chatConfig, - isCreatingConfig, - setTitle, - setModel, - setShortcut, - setSystemPrompt, - setTemperature, - submit, - } = useChatConfigContext(); - - const [textTemperature, setTextTemperature] = useState( - chatConfig.temprature.toString() ?? "1.0" - ); - - const temperature = (() => { - const numeric = Number(textTemperature); - if (Number.isNaN(numeric) || numeric < 0 || numeric > 1) { - return null; - } - return numeric; - })(); - const isFaultyTemperature = temperature == null; - - useEffect(() => { - if (isFaultyTemperature) { - return; + const [chatConfig, setChatConfig] = useState( + initialChatConfig ?? { + title: "", + shortcut: null, + providers: [ + { + type: "open-ai", + model: "gpt-4", + temperature: 0, + systemPrompt: "You are a helpful bot", + }, + ], } - // TODO: bit hacky - setTemperature(temperature); - }, [isFaultyTemperature, setTemperature, temperature]); + ); const onClickDropshadow: MouseEventHandler = (e) => { if (e.target !== e.currentTarget) { @@ -67,32 +44,213 @@ const ChatConfigDumb: FC = ({ onClose }) => { onClose(); }; - const onSubmit = () => { - submit(); + const handleSubmit = () => { + onSubmit(chatConfig); onClose(); }; - const { title, shortcut, models, systemPrompt } = chatConfig; - const disable = title === "" || isFaultyTemperature; + const addProvider = (type: ChatConfigProvider["type"]) => { + const initProvider: ChatConfigProvider = (() => { + switch (type) { + case "open-ai": { + return { + type: "open-ai", + model: "gpt-4", + temperature: 0, + systemPrompt: "You are a helpful bot", + }; + } + case "godmode": { + return { + type: "godmode", + description: "Autonomous agent making the world a better place", + }; + } + default: { + throw new Error("never"); + } + } + })(); + setChatConfig((p) => ({ ...p, providers: [...p.providers, initProvider] })); + }; + + // const removeProvider = () => {}; + + // TODO(gab): disabled on invalid temperature + const disabled = chatConfig.title === ""; return (
-
+
Chat Config
-
- - close - +
+ {/* */} +
+ + close + +
+ +
+
+ + +
+ +
+
+ ); +}; + +interface AddProviderButtonProps { + onSubmit: (type: ChatConfigProvider["type"]) => void; +} +const AddProviderButton: FC = ({ onSubmit }) => { + return ( + + + + + + + + {"Select chat config"} + + { + onSubmit("open-ai"); + }} + className="w-full cursor-pointer truncate p-2 text-[11px] outline-none hover:bg-gray-100" + > + Open AI Chat + + { + onSubmit("godmode"); + }} + className="w-full cursor-pointer truncate p-2 text-[11px] outline-none hover:bg-gray-100" + > + God Mode + + + + + ); +}; + +interface ProvidersFormProps { + chatConfig: ModalChatConfig; + setChatConfig: (chatConfig: ModalChatConfig) => void; +} +const ProvidersForm: FC = ({ + chatConfig, + setChatConfig, +}) => { + return ( + <> + {chatConfig.providers.map((provider) => { + const setProvider = ( + newProvider: Config + ) => { + // TODO: immer + const newProviders = chatConfig.providers.slice(); + newProviders[j] = newProvider; + setChatConfig({ + ...chatConfig, + providers: newProviders, + }); + }; + + switch (provider.type) { + case "open-ai": { + return ( +
+ } + /> +
+ ); + } + case "godmode": { + return ( +
+ } + /> +
+ ); + } + default: { + throw new Error("never"); + } + } + })} + + ); +}; + +interface ChatConfigFormProps { + chatConfig: ModalChatConfig; + setChatConfig: (provider: ModalChatConfig) => void; +} +const ChatConfigForm: FC = ({ + chatConfig, + setChatConfig, +}) => { + const { title, shortcut } = chatConfig; + + const setTitle = (title: string) => { + setChatConfig({ ...chatConfig, title }); + }; + const setShortcut = (shortcut: string | null) => { + setChatConfig({ ...chatConfig, shortcut }); + }; + return ( +
+
Title = ({ onClose }) => {
-
- Model -
- - -
-
-
- Temperature (0-1) - +
+ ); +}; + +interface OAIProviderFormProps { + provider: OAIProvider; + setProvider: (provider: OAIProvider) => void; +} +const OAIProviderForm: FC = ({ + provider, + setProvider, +}) => { + const [textTemperature, setTextTemperature] = useState( + provider.temperature.toString() ?? "1.0" + ); + const setModel = (model: "gpt-4" | "gpt-3.5-turbo") => { + setProvider({ ...provider, model }); + }; + const setSystemPrompt = (systemPrompt: string) => { + setProvider({ ...provider, systemPrompt }); + }; + + const temperature = (() => { + const numeric = Number(textTemperature); + if (Number.isNaN(numeric) || numeric < 0 || numeric > 1) { + return null; + } + return numeric; + })(); + const isFaultyTemperature = temperature == null; + + useEffect(() => { + if (isFaultyTemperature) { + return; + } + // TODO: bit hacky + setProvider({ ...provider, temperature }); + // TODO: setPRocider dangerous dep + }, [ + isFaultyTemperature, + textTemperature, + temperature, + setProvider, + provider, + ]); + return ( + <> +
+ Model +
+
-
- System prompt -