From ff5d7d83726f43e77257773efb9996eb653df896 Mon Sep 17 00:00:00 2001 From: Sean Hatfield <seanhatfield5@gmail.com> Date: Tue, 23 Apr 2024 16:22:37 -0700 Subject: [PATCH] [FEAT] Chat UI font size (#1172) * WIP text font size change feature * store text size settings in localstorage and improve styles of popup menu --- .../ChatHistory/HistoricalMessage/index.jsx | 2 +- .../ChatHistory/PromptReply/index.jsx | 6 +- .../ChatContainer/ChatHistory/index.jsx | 32 ++++- .../PromptInput/TextSizeMenu/index.jsx | 124 ++++++++++++++++++ .../ChatContainer/PromptInput/index.jsx | 2 + 5 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/TextSizeMenu/index.jsx diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx index a128e0195..d9efd98cc 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx @@ -44,7 +44,7 @@ const HistoricalMessage = ({ </div> ) : ( <span - className={`flex flex-col gap-y-1 mt-2`} + className={`flex flex-col gap-y-1`} dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(renderMarkdown(message)), }} diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/PromptReply/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/PromptReply/index.jsx index 5c48e9224..98d41a11c 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/PromptReply/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/PromptReply/index.jsx @@ -21,7 +21,7 @@ const PromptReply = ({ <div className={`flex justify-center items-end w-full ${assistantBackgroundColor}`} > - <div className="py-8 px-4 w-full flex gap-x-5 md:max-w-[800px] flex-col"> + <div className="py-6 px-4 w-full flex gap-x-5 md:max-w-[800px] flex-col"> <div className="flex gap-x-5"> <WorkspaceProfileImage workspace={workspace} /> <div className="mt-3 ml-5 dot-falling"></div> @@ -36,7 +36,7 @@ const PromptReply = ({ <div className={`flex justify-center items-end w-full ${assistantBackgroundColor}`} > - <div className="py-8 px-4 w-full flex gap-x-5 md:max-w-[800px] flex-col"> + <div className="py-6 px-4 w-full flex gap-x-5 md:max-w-[800px] flex-col"> <div className="flex gap-x-5"> <WorkspaceProfileImage workspace={workspace} /> <span @@ -57,7 +57,7 @@ const PromptReply = ({ key={uuid} className={`flex justify-center items-end w-full ${assistantBackgroundColor}`} > - <div className="py-8 px-4 w-full flex gap-x-5 md:max-w-[800px] flex-col"> + <div className="py-6 px-4 w-full flex gap-x-5 md:max-w-[800px] flex-col"> <div className="flex gap-x-5"> <WorkspaceProfileImage workspace={workspace} /> <span diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx index 8fa4815dc..902409e49 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx @@ -12,6 +12,36 @@ export default function ChatHistory({ history = [], workspace, sendCommand }) { const { showing, showModal, hideModal } = useManageWorkspaceModal(); const [isAtBottom, setIsAtBottom] = useState(true); const chatHistoryRef = useRef(null); + const [textSize, setTextSize] = useState("normal"); + + const getTextSizeClass = (size) => { + switch (size) { + case "small": + return "text-[12px]"; + case "large": + return "text-[18px]"; + default: + return "text-[14px]"; + } + }; + + useEffect(() => { + const storedTextSize = window.localStorage.getItem("anythingllm_text_size"); + if (storedTextSize) { + setTextSize(getTextSizeClass(storedTextSize)); + } + + const handleTextSizeChange = (event) => { + const size = event.detail; + setTextSize(getTextSizeClass(size)); + }; + + window.addEventListener("textSizeChange", handleTextSizeChange); + + return () => { + window.removeEventListener("textSizeChange", handleTextSizeChange); + }; + }, []); useEffect(() => { if (isAtBottom) scrollToBottom(); @@ -91,7 +121,7 @@ export default function ChatHistory({ history = [], workspace, sendCommand }) { return ( <div - className="markdown text-white/80 font-light text-sm h-full md:h-[83%] pb-[100px] pt-6 md:pt-0 md:pb-20 md:mx-0 overflow-y-scroll flex flex-col justify-start no-scroll" + className={`markdown text-white/80 font-light ${textSize} h-full md:h-[83%] pb-[100px] pt-6 md:pt-0 md:pb-20 md:mx-0 overflow-y-scroll flex flex-col justify-start no-scroll`} id="chat-history" ref={chatHistoryRef} > diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/TextSizeMenu/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/TextSizeMenu/index.jsx new file mode 100644 index 000000000..645e943c0 --- /dev/null +++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/TextSizeMenu/index.jsx @@ -0,0 +1,124 @@ +import { useState, useRef, useEffect } from "react"; +import { TextT } from "@phosphor-icons/react"; +import { Tooltip } from "react-tooltip"; + +export default function TextSizeButton() { + const [showTextSizeMenu, setShowTextSizeMenu] = useState(false); + const buttonRef = useRef(null); + + return ( + <> + <div + ref={buttonRef} + id="text-size-btn" + data-tooltip-id="tooltip-text-size-btn" + data-tooltip-content="Change text size" + aria-label="Change text size" + onClick={() => setShowTextSizeMenu(!showTextSizeMenu)} + className={`relative flex justify-center items-center opacity-60 hover:opacity-100 cursor-pointer ${ + showTextSizeMenu ? "!opacity-100" : "" + }`} + > + <TextT + weight="fill" + className="w-6 h-6 pointer-events-none text-white" + /> + <Tooltip + id="tooltip-text-size-btn" + place="top" + delayShow={300} + className="tooltip !text-xs z-99" + /> + </div> + <TextSizeMenu + showing={showTextSizeMenu} + setShowing={setShowTextSizeMenu} + buttonRef={buttonRef} + /> + </> + ); +} + +function TextSizeMenu({ showing, setShowing, buttonRef }) { + const formRef = useRef(null); + const [selectedSize, setSelectedSize] = useState( + window.localStorage.getItem("anythingllm_text_size") || "normal" + ); + + useEffect(() => { + function listenForOutsideClick() { + if (!showing || !formRef.current) return false; + document.addEventListener("click", closeIfOutside); + } + listenForOutsideClick(); + }, [showing, formRef.current]); + + const closeIfOutside = ({ target }) => { + if (target.id === "text-size-btn") return; + const isOutside = !formRef?.current?.contains(target); + if (!isOutside) return; + setShowing(false); + }; + + const handleTextSizeChange = (size) => { + setSelectedSize(size); + window.localStorage.setItem("anythingllm_text_size", size); + window.dispatchEvent(new CustomEvent("textSizeChange", { detail: size })); + }; + + if (!buttonRef.current) return null; + + return ( + <div hidden={!showing}> + <div + ref={formRef} + className="absolute bottom-16 -ml-8 w-[140px] p-2 bg-zinc-800 rounded-lg shadow-md flex flex-col justify-center items-start gap-2 z-50" + > + <button + onClick={(e) => { + e.preventDefault(); + setShowing(false); + handleTextSizeChange("small"); + }} + className={`w-full hover:cursor-pointer px-2 py-1 rounded-md flex flex-col justify-start group ${ + selectedSize === "small" ? "bg-zinc-700" : "hover:bg-zinc-700" + }`} + > + <div className="w-full flex-col text-left flex pointer-events-none"> + <div className="text-white text-xs">Small</div> + </div> + </button> + + <button + onClick={(e) => { + e.preventDefault(); + setShowing(false); + handleTextSizeChange("normal"); + }} + className={`w-full hover:cursor-pointer px-2 py-1 rounded-md flex flex-col justify-start group ${ + selectedSize === "normal" ? "bg-zinc-700" : "hover:bg-zinc-700" + }`} + > + <div className="w-full flex-col text-left flex pointer-events-none"> + <div className="text-white text-sm">Normal</div> + </div> + </button> + + <button + onClick={(e) => { + e.preventDefault(); + setShowing(false); + handleTextSizeChange("large"); + }} + className={`w-full hover:cursor-pointer px-2 py-1 rounded-md flex flex-col justify-start group ${ + selectedSize === "large" ? "bg-zinc-700" : "hover:bg-zinc-700" + }`} + > + <div className="w-full flex-col text-left flex pointer-events-none"> + <div className="text-white text-[16px]">Large</div> + </div> + </button> + </div> + </div> + ); +} diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx index cf460f3b0..859f84174 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx @@ -11,6 +11,7 @@ import AvailableAgentsButton, { AvailableAgents, useAvailableAgents, } from "./AgentMenu"; +import TextSizeButton from "./TextSizeMenu"; export default function PromptInput({ message, submit, @@ -137,6 +138,7 @@ export default function PromptInput({ showing={showAgents} setShowAgents={setShowAgents} /> + <TextSizeButton /> </div> </div> </div> -- GitLab