532 uiux add slash command modal ()

* 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>
This commit is contained in:
Sean Hatfield 2024-01-09 13:07:09 -08:00 committed by GitHub
parent 58971e8b30
commit 5c3bb4b8cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 148 additions and 38 deletions
frontend/src
App.jsx
components/WorkspaceChat/ChatContainer
ChatHistory
PromptInput
index.jsx
index.css

View file

@ -19,28 +19,28 @@ const AdminInvites = lazy(() => import("@/pages/Admin/Invitations"));
const AdminWorkspaces = lazy(() => import("@/pages/Admin/Workspaces")); const AdminWorkspaces = lazy(() => import("@/pages/Admin/Workspaces"));
const AdminSystem = lazy(() => import("@/pages/Admin/System")); const AdminSystem = lazy(() => import("@/pages/Admin/System"));
const GeneralChats = lazy(() => import("@/pages/GeneralSettings/Chats")); const GeneralChats = lazy(() => import("@/pages/GeneralSettings/Chats"));
const GeneralAppearance = lazy(() => const GeneralAppearance = lazy(
import("@/pages/GeneralSettings/Appearance") () => import("@/pages/GeneralSettings/Appearance")
); );
const GeneralApiKeys = lazy(() => import("@/pages/GeneralSettings/ApiKeys")); const GeneralApiKeys = lazy(() => import("@/pages/GeneralSettings/ApiKeys"));
const GeneralLLMPreference = lazy(() => const GeneralLLMPreference = lazy(
import("@/pages/GeneralSettings/LLMPreference") () => import("@/pages/GeneralSettings/LLMPreference")
); );
const GeneralEmbeddingPreference = lazy(() => const GeneralEmbeddingPreference = lazy(
import("@/pages/GeneralSettings/EmbeddingPreference") () => import("@/pages/GeneralSettings/EmbeddingPreference")
); );
const GeneralVectorDatabase = lazy(() => const GeneralVectorDatabase = lazy(
import("@/pages/GeneralSettings/VectorDatabase") () => import("@/pages/GeneralSettings/VectorDatabase")
); );
const GeneralExportImport = lazy(() => const GeneralExportImport = lazy(
import("@/pages/GeneralSettings/ExportImport") () => import("@/pages/GeneralSettings/ExportImport")
); );
const GeneralSecurity = lazy(() => import("@/pages/GeneralSettings/Security")); const GeneralSecurity = lazy(() => import("@/pages/GeneralSettings/Security"));
const DataConnectors = lazy(() => const DataConnectors = lazy(
import("@/pages/GeneralSettings/DataConnectors") () => import("@/pages/GeneralSettings/DataConnectors")
); );
const DataConnectorSetup = lazy(() => const DataConnectorSetup = lazy(
import("@/pages/GeneralSettings/DataConnectors/Connectors") () => import("@/pages/GeneralSettings/DataConnectors/Connectors")
); );
const OnboardingFlow = lazy(() => import("@/pages/OnboardingFlow")); const OnboardingFlow = lazy(() => import("@/pages/OnboardingFlow"));

View file

@ -24,16 +24,14 @@ export default function ChatHistory({ history = [], workspace }) {
}; };
const debouncedScroll = debounce(handleScroll, 100); const debouncedScroll = debounce(handleScroll, 100);
useEffect(() => { useEffect(() => {
if (!chatHistoryRef.current) return null; function watchScrollEvent() {
const chatHistoryElement = chatHistoryRef.current; if (!chatHistoryRef.current) return null;
chatHistoryElement.addEventListener("scroll", debouncedScroll); const chatHistoryElement = chatHistoryRef.current;
if (!chatHistoryElement) return null;
return () => { chatHistoryElement.addEventListener("scroll", debouncedScroll);
chatHistoryElement.removeEventListener("scroll", debouncedScroll); }
debouncedScroll.cancel(); watchScrollEvent();
};
}, []); }, []);
const scrollToBottom = () => { const scrollToBottom = () => {
@ -49,11 +47,11 @@ export default function ChatHistory({ history = [], workspace }) {
return ( return (
<div className="flex flex-col h-full md:mt-0 pb-48 w-full justify-end items-center"> <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"> <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. Welcome to your new workspace.
</p> </p>
<div className="w-full text-center"> <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{" "} To get started either{" "}
<span <span
className="underline font-medium cursor-pointer" className="underline font-medium cursor-pointer"

View file

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

After

(image error) Size: 350 B

View file

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

View file

@ -11,6 +11,10 @@ import ManageWorkspace, {
useManageWorkspaceModal, useManageWorkspaceModal,
} from "../../../Modals/MangeWorkspace"; } from "../../../Modals/MangeWorkspace";
import useUser from "@/hooks/useUser"; import useUser from "@/hooks/useUser";
import SlashCommandsButton, {
SlashCommands,
useSlashCommands,
} from "./SlashCommands";
export default function PromptInput({ export default function PromptInput({
workspace, workspace,
@ -19,7 +23,9 @@ export default function PromptInput({
onChange, onChange,
inputDisabled, inputDisabled,
buttonDisabled, buttonDisabled,
sendCommand,
}) { }) {
const { showSlashCommand, setShowSlashCommand } = useSlashCommands();
const { showing, showModal, hideModal } = useManageWorkspaceModal(); const { showing, showModal, hideModal } = useManageWorkspaceModal();
const formRef = useRef(null); const formRef = useRef(null);
const [_, setFocused] = useState(false); const [_, setFocused] = useState(false);
@ -49,7 +55,12 @@ export default function PromptInput({
}; };
return ( 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 <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="flex flex-col gap-y-1 rounded-t-lg md:w-3/4 w-full mx-auto max-w-xl" 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" weight="fill"
/> />
)} )}
<ChatModeSelector workspace={workspace} /> <ChatModeSelector workspace={workspace} />
{/* <TextT <SlashCommandsButton
className="w-7 h-7 text-white/30 cursor-not-allowed" showing={showSlashCommand}
weight="fill" setShowSlashCommand={setShowSlashCommand}
/> */} />
</div> </div>
{/* <Microphone
className="w-7 h-7 text-white/30 cursor-not-allowed"
weight="fill"
/> */}
</div> </div>
</div> </div>
</div> </div>

View file

@ -10,7 +10,6 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const [loadingResponse, setLoadingResponse] = useState(false); const [loadingResponse, setLoadingResponse] = useState(false);
const [chatHistory, setChatHistory] = useState(knownHistory); const [chatHistory, setChatHistory] = useState(knownHistory);
const handleMessageChange = (event) => { const handleMessageChange = (event) => {
setMessage(event.target.value); setMessage(event.target.value);
}; };
@ -36,6 +35,30 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
setLoadingResponse(true); 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(() => { useEffect(() => {
async function fetchReply() { async function fetchReply() {
const promptMessage = const promptMessage =
@ -97,6 +120,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
onChange={handleMessageChange} onChange={handleMessageChange}
inputDisabled={loadingResponse} inputDisabled={loadingResponse}
buttonDisabled={loadingResponse} buttonDisabled={loadingResponse}
sendCommand={sendCommand}
/> />
</div> </div>
</div> </div>

View file

@ -6,8 +6,18 @@ html,
body { body {
padding: 0; padding: 0;
margin: 0; margin: 0;
font-family: "plus-jakarta-sans", -apple-system, BlinkMacSystemFont, Segoe UI, font-family:
Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, "plus-jakarta-sans",
-apple-system,
BlinkMacSystemFont,
Segoe UI,
Roboto,
Oxygen,
Ubuntu,
Cantarell,
Fira Sans,
Droid Sans,
Helvetica Neue,
sans-serif; sans-serif;
background-color: white; background-color: white;
} }