mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2025-04-23 13:08:11 +00:00
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>
This commit is contained in:
parent
58971e8b30
commit
5c3bb4b8cc
7 changed files with 148 additions and 38 deletions
|
@ -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"));
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 |
|
@ -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 };
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue