diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 8007b5ad1304cca10313278167b557e025489ccf..fa74d434e4a157e12670833efa6b946e58569faf 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 1de2504b7232c24f5a47338ea607f8ccacf65112..4a7cd4827424cf16d5c138d27841e2702a542db9 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 0000000000000000000000000000000000000000..368bdf526e6464e24570e20810cdbf0ac7249ae7 --- /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 0000000000000000000000000000000000000000..0e4f26aa921c48abf6fe8031c0fd4c022abdcb11 --- /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 14daff8095e2a871d2c38ea94573ccec8d935cd1..e141cc0a43e8331b32ade061585fb7b6d5ae93fb 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 34ae8de9af939f530fcd4fc47ec187f9522e32ed..6dd1cdf503e096ac6bd73722f86cff4ec3a036a6 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 a7aef9a7edbfb450ee2e4aa2010c62d5cf9db0ca..1d1b2da85373bd82af1e0a262fee8ac9e0269d2f 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; }