From 5c3bb4b8cce9a9ff7addb571f81aad6483dadbdd Mon Sep 17 00:00:00 2001 From: Sean Hatfield <seanhatfield5@gmail.com> Date: Tue, 9 Jan 2024 13:07:09 -0800 Subject: [PATCH] 532 uiux add slash command modal (#555) * WIP slash commands * add slash command image * WIP slash commands * slash command menu feature complete * move icons to slash command local * update how slash command component works * relint with new linter * Finalize slash command input Change empty workspace text layout Patch dev unmount issues on Chatworkspace/index.jsx --------- Co-authored-by: timothycarambat <rambat1010@gmail.com> --- frontend/src/App.jsx | 28 ++++---- .../ChatContainer/ChatHistory/index.jsx | 20 +++--- .../icons/slash-commands-icon.svg | 4 ++ .../PromptInput/SlashCommands/index.jsx | 68 +++++++++++++++++++ .../ChatContainer/PromptInput/index.jsx | 26 ++++--- .../WorkspaceChat/ChatContainer/index.jsx | 26 ++++++- frontend/src/index.css | 14 +++- 7 files changed, 148 insertions(+), 38 deletions(-) create mode 100644 frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/SlashCommands/icons/slash-commands-icon.svg create mode 100644 frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/SlashCommands/index.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 8007b5ad1..fa74d434e 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -19,28 +19,28 @@ const AdminInvites = lazy(() => import("@/pages/Admin/Invitations")); const AdminWorkspaces = lazy(() => import("@/pages/Admin/Workspaces")); const AdminSystem = lazy(() => import("@/pages/Admin/System")); const GeneralChats = lazy(() => import("@/pages/GeneralSettings/Chats")); -const GeneralAppearance = lazy(() => - import("@/pages/GeneralSettings/Appearance") +const GeneralAppearance = lazy( + () => import("@/pages/GeneralSettings/Appearance") ); const GeneralApiKeys = lazy(() => import("@/pages/GeneralSettings/ApiKeys")); -const GeneralLLMPreference = lazy(() => - import("@/pages/GeneralSettings/LLMPreference") +const GeneralLLMPreference = lazy( + () => import("@/pages/GeneralSettings/LLMPreference") ); -const GeneralEmbeddingPreference = lazy(() => - import("@/pages/GeneralSettings/EmbeddingPreference") +const GeneralEmbeddingPreference = lazy( + () => import("@/pages/GeneralSettings/EmbeddingPreference") ); -const GeneralVectorDatabase = lazy(() => - import("@/pages/GeneralSettings/VectorDatabase") +const GeneralVectorDatabase = lazy( + () => import("@/pages/GeneralSettings/VectorDatabase") ); -const GeneralExportImport = lazy(() => - import("@/pages/GeneralSettings/ExportImport") +const GeneralExportImport = lazy( + () => import("@/pages/GeneralSettings/ExportImport") ); const GeneralSecurity = lazy(() => import("@/pages/GeneralSettings/Security")); -const DataConnectors = lazy(() => - import("@/pages/GeneralSettings/DataConnectors") +const DataConnectors = lazy( + () => import("@/pages/GeneralSettings/DataConnectors") ); -const DataConnectorSetup = lazy(() => - import("@/pages/GeneralSettings/DataConnectors/Connectors") +const DataConnectorSetup = lazy( + () => import("@/pages/GeneralSettings/DataConnectors/Connectors") ); const OnboardingFlow = lazy(() => import("@/pages/OnboardingFlow")); diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx index 1de2504b7..4a7cd4827 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx @@ -24,16 +24,14 @@ export default function ChatHistory({ history = [], workspace }) { }; const debouncedScroll = debounce(handleScroll, 100); - useEffect(() => { - if (!chatHistoryRef.current) return null; - const chatHistoryElement = chatHistoryRef.current; - chatHistoryElement.addEventListener("scroll", debouncedScroll); - - return () => { - chatHistoryElement.removeEventListener("scroll", debouncedScroll); - debouncedScroll.cancel(); - }; + function watchScrollEvent() { + if (!chatHistoryRef.current) return null; + const chatHistoryElement = chatHistoryRef.current; + if (!chatHistoryElement) return null; + chatHistoryElement.addEventListener("scroll", debouncedScroll); + } + watchScrollEvent(); }, []); const scrollToBottom = () => { @@ -49,11 +47,11 @@ export default function ChatHistory({ history = [], workspace }) { return ( <div className="flex flex-col h-full md:mt-0 pb-48 w-full justify-end items-center"> <div className="flex flex-col items-start"> - <p className="text-white/60 text-lg font-base -ml-6 py-4"> + <p className="text-white/60 text-lg font-base py-4"> Welcome to your new workspace. </p> <div className="w-full text-center"> - <p className="text-white/60 text-lg font-base inline-flex items-center gap-x-2"> + <p className="text-white/60 text-lg font-base inline-grid md:inline-flex items-center gap-x-2"> To get started either{" "} <span className="underline font-medium cursor-pointer" diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/SlashCommands/icons/slash-commands-icon.svg b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/SlashCommands/icons/slash-commands-icon.svg new file mode 100644 index 000000000..368bdf526 --- /dev/null +++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/SlashCommands/icons/slash-commands-icon.svg @@ -0,0 +1,4 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect x="1.02539" y="1.43799" width="17.252" height="17.252" rx="2" stroke="white" stroke-opacity="1.0" stroke-width="1.5"/> +<path d="M6.70312 14.5408L12.5996 5.8056" stroke="white" stroke-opacity="1.0" stroke-width="1.5" stroke-linecap="round"/> +</svg> diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/SlashCommands/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/SlashCommands/index.jsx new file mode 100644 index 000000000..0e4f26aa9 --- /dev/null +++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/SlashCommands/index.jsx @@ -0,0 +1,68 @@ +import { useEffect, useRef, useState } from "react"; +import SlashCommandIcon from "./icons/slash-commands-icon.svg"; + +export default function SlashCommandsButton({ showing, setShowSlashCommand }) { + return ( + <div + id="slash-cmd-btn" + onClick={() => setShowSlashCommand(!showing)} + className={`flex justify-center items-center opacity-60 hover:opacity-100 cursor-pointer ${ + showing ? "!opacity-100" : "" + }`} + > + <img + src={SlashCommandIcon} + className="w-6 h-6 pointer-events-none" + alt="Slash commands button" + /> + </div> + ); +} + +export function SlashCommands({ showing, setShowing, sendCommand }) { + const cmdRef = useRef(null); + useEffect(() => { + function listenForOutsideClick() { + if (!showing || !cmdRef.current) return false; + document.addEventListener("click", closeIfOutside); + } + listenForOutsideClick(); + }, [showing, cmdRef.current]); + + if (!showing) return null; + const closeIfOutside = ({ target }) => { + if (target.id === "slash-cmd-btn") return; + const isOutside = !cmdRef?.current?.contains(target); + if (!isOutside) return; + setShowing(false); + }; + + return ( + <div className="w-full flex justify-center absolute bottom-[130px] md:bottom-[150px] left-0 z-10 px-4"> + <div + ref={cmdRef} + className="w-[600px] p-2 bg-zinc-800 rounded-2xl shadow flex-col justify-center items-start gap-2.5 inline-flex" + > + <button + onClick={() => { + setShowing(false); + sendCommand("/reset", true); + }} + className="w-full hover:cursor-pointer hover:bg-zinc-700 px-2 py-2 rounded-xl flex flex-col justify-start" + > + <div className="w-full flex-col text-left flex pointer-events-none"> + <div className="text-white text-sm font-bold">/reset</div> + <div className="text-white text-opacity-60 text-sm"> + Clear your chat history and begin a new chat + </div> + </div> + </button> + </div> + </div> + ); +} + +export function useSlashCommands() { + const [showSlashCommand, setShowSlashCommand] = useState(false); + return { showSlashCommand, setShowSlashCommand }; +} diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx index 14daff809..e141cc0a4 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx @@ -11,6 +11,10 @@ import ManageWorkspace, { useManageWorkspaceModal, } from "../../../Modals/MangeWorkspace"; import useUser from "@/hooks/useUser"; +import SlashCommandsButton, { + SlashCommands, + useSlashCommands, +} from "./SlashCommands"; export default function PromptInput({ workspace, @@ -19,7 +23,9 @@ export default function PromptInput({ onChange, inputDisabled, buttonDisabled, + sendCommand, }) { + const { showSlashCommand, setShowSlashCommand } = useSlashCommands(); const { showing, showModal, hideModal } = useManageWorkspaceModal(); const formRef = useRef(null); const [_, setFocused] = useState(false); @@ -49,7 +55,12 @@ export default function PromptInput({ }; return ( - <div className="w-full fixed md:absolute bottom-0 left-0 z-10 md:z-0 flex justify-center items-center overflow-hidden"> + <div className="w-full fixed md:absolute bottom-0 left-0 z-10 md:z-0 flex justify-center items-center"> + <SlashCommands + showing={showSlashCommand} + setShowing={setShowSlashCommand} + sendCommand={sendCommand} + /> <form onSubmit={handleSubmit} className="flex flex-col gap-y-1 rounded-t-lg md:w-3/4 w-full mx-auto max-w-xl" @@ -95,17 +106,12 @@ export default function PromptInput({ weight="fill" /> )} - <ChatModeSelector workspace={workspace} /> - {/* <TextT - className="w-7 h-7 text-white/30 cursor-not-allowed" - weight="fill" - /> */} + <SlashCommandsButton + showing={showSlashCommand} + setShowSlashCommand={setShowSlashCommand} + /> </div> - {/* <Microphone - className="w-7 h-7 text-white/30 cursor-not-allowed" - weight="fill" - /> */} </div> </div> </div> diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/index.jsx index 34ae8de9a..6dd1cdf50 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/index.jsx @@ -10,7 +10,6 @@ export default function ChatContainer({ workspace, knownHistory = [] }) { const [message, setMessage] = useState(""); const [loadingResponse, setLoadingResponse] = useState(false); const [chatHistory, setChatHistory] = useState(knownHistory); - const handleMessageChange = (event) => { setMessage(event.target.value); }; @@ -36,6 +35,30 @@ export default function ChatContainer({ workspace, knownHistory = [] }) { setLoadingResponse(true); }; + const sendCommand = async (command, submit = false) => { + if (!command || command === "") return false; + if (!submit) { + setMessage(command); + return; + } + + const prevChatHistory = [ + ...chatHistory, + { content: command, role: "user" }, + { + content: "", + role: "assistant", + pending: true, + userMessage: command, + animate: true, + }, + ]; + + setChatHistory(prevChatHistory); + setMessage(""); + setLoadingResponse(true); + }; + useEffect(() => { async function fetchReply() { const promptMessage = @@ -97,6 +120,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) { onChange={handleMessageChange} inputDisabled={loadingResponse} buttonDisabled={loadingResponse} + sendCommand={sendCommand} /> </div> </div> diff --git a/frontend/src/index.css b/frontend/src/index.css index a7aef9a7e..1d1b2da85 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -6,8 +6,18 @@ html, body { padding: 0; margin: 0; - font-family: "plus-jakarta-sans", -apple-system, BlinkMacSystemFont, Segoe UI, - Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, + font-family: + "plus-jakarta-sans", + -apple-system, + BlinkMacSystemFont, + Segoe UI, + Roboto, + Oxygen, + Ubuntu, + Cantarell, + Fira Sans, + Droid Sans, + Helvetica Neue, sans-serif; background-color: white; } -- GitLab