improve UX and useability for Settings and prefernces for mobile and desktop (#200)

* improve UX and useability for Settings and prefernces for mobile and desktop

* linting
This commit is contained in:
Timothy Carambat 2023-08-18 14:36:58 -07:00 committed by GitHub
parent 1b4e29a3b9
commit 7ae0d28ef0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 239 additions and 192 deletions

View File

@ -12,7 +12,6 @@ const AdminWorkspaces = lazy(() => import("./pages/Admin/Workspaces"));
const AdminChats = lazy(() => import("./pages/Admin/Chats"));
const AdminSystem = lazy(() => import("./pages/Admin/System"));
const AdminAppearance = lazy(() => import("./pages/Admin/Appearance"));
const Appearance = lazy(() => import("./pages/System/Appearance"));
export default function App() {
return (
@ -20,7 +19,6 @@ export default function App() {
<ContextWrapper>
<Routes>
<Route path="/" element={<Main />} />
<Route path="/system/appearance" element={<Appearance />} />
<Route
path="/workspace/:slug"
element={<PrivateRoute Component={WorkspaceChat} />}

View File

@ -182,12 +182,12 @@ export function SidebarMobileHeader() {
/>
<div
ref={sidebarRef}
className="h-[100vh] fixed top-0 left-0 rounded-r-[26px] bg-white dark:bg-black-900 w-[70%] p-[18px] "
className="h-[100vh] fixed top-0 left-0 rounded-r-[26px] bg-white dark:bg-black-900 w-[80%] p-[18px] "
>
<div className="w-full h-full flex flex-col overflow-x-hidden items-between">
{/* Header Information */}
<div className="flex w-full items-center justify-between">
<div className="flex shrink-0 w-fit items-center justify-start">
<div className="flex w-full items-center justify-between gap-x-4">
<div className="flex shrink-1 w-fit items-center justify-start">
<img
src={logo}
alt="Logo"
@ -195,7 +195,7 @@ export function SidebarMobileHeader() {
style={{ objectFit: "contain" }}
/>
</div>
<div className="flex gap-x-2 items-center text-slate-500">
<div className="flex gap-x-2 items-center text-slate-500 shrink-0">
<a
href={paths.home()}
className="transition-all duration-300 p-2 rounded-full bg-slate-200 text-slate-400 dark:bg-stone-800 hover:bg-slate-800 hover:text-slate-200 dark:hover:text-slate-200"

View File

@ -1,13 +1,10 @@
import React, { useState, useEffect } from "react";
import AnythingLLMLight from "../../media/logo/anything-llm-light.png";
import AnythingLLMDark from "../../media/logo/anything-llm-dark.png";
import System from "../../models/system";
import usePrefersDarkMode from "../../hooks/usePrefersDarkMode";
import useLogo from "../../hooks/useLogo";
import EditingChatBubble from "../../components/EditingChatBubble";
import { isMobile } from "react-device-detect";
import { ArrowLeft } from "react-feather";
import paths from "../../utils/paths";
import { useEffect, useState } from "react";
import useLogo from "../../../../hooks/useLogo";
import usePrefersDarkMode from "../../../../hooks/usePrefersDarkMode";
import System from "../../../../models/system";
import EditingChatBubble from "../../../EditingChatBubble";
import AnythingLLMLight from "../../../../media/logo/anything-llm-light.png";
import AnythingLLMDark from "../../../../media/logo/anything-llm-dark.png";
export default function Appearance() {
const { logo: _initLogo } = useLogo();
@ -118,32 +115,16 @@ export default function Appearance() {
setHasChanges(false);
};
const handleBackNavigation = () => {
window.location = paths.home();
};
return (
<div className="w-screen h-screen overflow-hidden bg-orange-100 dark:bg-stone-700 flex justify-center py-6">
<div
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
className="transition-all duration-500 relative md:ml-[2px] md:mr-[8px] md:my-[16px] md:rounded-[26px] bg-white dark:bg-black-900 md:min-w-[82%] p-[18px] h-full overflow-y-scroll"
>
<div className="px-1 md:px-8">
<div className="mb-6">
<div
className="cursor-pointer inline-flex items-center gap-3 mb-5 py-2 pl-2 pr-4 text-white rounded-md hover:bg-gray-300 dark:hover:bg-gray-800 transition-all"
onClick={handleBackNavigation}
>
<ArrowLeft />
<span>Back</span>
</div>
<p className="text-3xl font-semibold text-slate-600 dark:text-slate-200">
Appearance Settings
</p>
<p className="mt-2 text-sm font-base text-slate-600 dark:text-slate-200">
Customize the appearance settings of your platform.
</p>
</div>
<div className="relative w-full w-full max-h-full">
<div className="relative bg-white rounded-lg shadow dark:bg-stone-700">
<div className="flex items-start justify-between px-6 py-4">
<p className="text-gray-800 dark:text-stone-200 text-base ">
Customize the appearance settings of AnythingLLM instance.
</p>
</div>
<div className="px-1 md:px-8 pb-10">
<div className="mb-6">
<div className="flex flex-col gap-y-2">
<h2 className="leading-tight font-medium text-black dark:text-white">
@ -153,7 +134,7 @@ export default function Appearance() {
Change the logo that appears in the sidebar.
</p>
</div>
<div className="flex items-center">
<div className="flex flex-col md:flex-row items-center">
<img
src={logo}
alt="Uploaded Logo"
@ -197,7 +178,7 @@ export default function Appearance() {
Change the default messages that are displayed to the users.
</p>
</div>
<div className="mt-6 flex flex-col gap-y-6">
<div className="mt-6 flex flex-col gap-y-6 bg-white dark:bg-black-900 p-4 rounded-lg">
{messages.map((message, index) => (
<div key={index} className="flex flex-col gap-y-2">
{message.user && (

View File

@ -61,7 +61,7 @@ export default function VectorDBSelection({
<p className="block text-sm font-medium text-gray-800 dark:text-slate-200">
Vector database providers
</p>
<div className="w-full flex overflow-x-scroll gap-x-4">
<div className="w-full flex md:flex-wrap overflow-x-scroll gap-4">
<input hidden={true} name="VectorDB" value={vectorDB} />
<VectorDBOption
name="Chroma"

View File

@ -1,13 +1,5 @@
import React, { useEffect, useState } from "react";
import {
Archive,
Lock,
X,
Users,
Database,
MessageSquare,
Eye,
} from "react-feather";
import { X } from "react-feather";
import ExportOrImportData from "./ExportImport";
import PasswordProtection from "./PasswordProtection";
import System from "../../../models/system";
@ -15,23 +7,23 @@ import MultiUserMode from "./MultiUserMode";
import useUser from "../../../hooks/useUser";
import VectorDBSelection from "./VectorDbs";
import LLMSelection from "./LLMSelection";
import paths from "../../../utils/paths";
import Appearance from "./Appearance";
const TABS = {
export const TABS = {
llm: LLMSelection,
exportimport: ExportOrImportData,
password: PasswordProtection,
multiuser: MultiUserMode,
vectordb: VectorDBSelection,
appearance: Appearance,
};
const noop = () => false;
export default function SystemSettingsModal({ hideModal = noop }) {
export default function SystemSettingsModal({ tab = null, hideModal = noop }) {
const { user } = useUser();
const [loading, setLoading] = useState(true);
const [selectedTab, setSelectedTab] = useState("llm");
const [settings, setSettings] = useState(null);
const Component = TABS[selectedTab || "llm"];
const Component = TABS[tab || "llm"];
useEffect(() => {
async function fetchKeys() {
@ -64,12 +56,6 @@ export default function SystemSettingsModal({ hideModal = noop }) {
<X className="text-gray-300 text-lg" />
</button>
</div>
<SettingTabs
selectedTab={selectedTab}
changeTab={setSelectedTab}
settings={settings}
user={user}
/>
</div>
{loading ? (
<div className="w-full flex h-[400px] p-6">
@ -84,92 +70,6 @@ export default function SystemSettingsModal({ hideModal = noop }) {
);
}
function SettingTabs({ selectedTab, changeTab, settings, user }) {
if (!settings) {
return (
<div className="w-full flex h-[60px] pb-2">
<div className="w-full flex h-full bg-gray-200 dark:bg-stone-600 animate-pulse rounded-lg" />
</div>
);
}
return (
<ul className="flex overflow-x-scroll no-scroll -mb-px text-sm gap-x-2 font-medium text-center text-gray-500 dark:text-gray-400">
<SettingTab
active={selectedTab === "llm"}
displayName="LLM Choice"
tabName="llm"
icon={<MessageSquare className="h-4 w-4 flex-shrink-0" />}
onClick={changeTab}
/>
<SettingTab
active={selectedTab === "vectordb"}
displayName="Vector Database"
tabName="vectordb"
icon={<Database className="h-4 w-4 flex-shrink-0" />}
onClick={changeTab}
/>
<SettingTab
active={selectedTab === "exportimport"}
displayName="Export or Import"
tabName="exportimport"
icon={<Archive className="h-4 w-4 flex-shrink-0" />}
onClick={changeTab}
/>
{!settings?.MultiUserMode && (
<>
<SettingTab
active={selectedTab === "multiuser"}
displayName="Multi User Mode"
tabName="multiuser"
icon={<Users className="h-4 w-4 flex-shrink-0" />}
onClick={changeTab}
/>
<SettingTab
active={selectedTab === "password"}
displayName="Password Protection"
tabName="password"
icon={<Lock className="h-4 w-4 flex-shrink-0" />}
onClick={changeTab}
/>
<SettingTab
displayName="Appearance"
tabName="appearance"
icon={<Eye className="h-4 w-4 flex-shrink-0" />}
onClick={() => window.open(paths.appearance())}
/>
</>
)}
</ul>
);
}
function SettingTab({
active = false,
displayName,
tabName,
icon = "",
onClick,
}) {
const classes = active
? "text-blue-600 border-blue-600 active dark:text-blue-400 dark:border-blue-400 bg-blue-500 bg-opacity-5"
: "border-transparent hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300";
return (
<li className="mr-2">
<button
disabled={active}
onClick={() => onClick(tabName)}
className={
"flex items-center gap-x-1 p-4 border-b-2 rounded-t-lg group whitespace-nowrap " +
classes
}
>
{icon} {displayName}
</button>
</li>
);
}
export function useSystemSettingsModal() {
const [showing, setShowing] = useState(false);
const showModal = () => {

View File

@ -0,0 +1,181 @@
import React, { useEffect, useState } from "react";
import {
X,
Archive,
Lock,
Users,
Database,
MessageSquare,
Eye,
} from "react-feather";
import SystemSettingsModal, {
useSystemSettingsModal,
} from "../../Modals/Settings";
import useLogo from "../../../hooks/useLogo";
import System from "../../../models/system";
const OVERLAY_ID = "anything-llm-system-overlay";
const OVERLAY_CLASSES = {
enabled: ["z-10", "opacity-1"],
disabled: ["-z-10", "opacity-0"],
};
export default function SettingsOverlay() {
const { logo } = useLogo();
const [tab, setTab] = useState(null);
const [settings, setSettings] = useState(null);
const [loading, setLoading] = useState(true);
const { showing, hideModal, showModal } = useSystemSettingsModal();
const selectTab = (tab = null) => {
setTab(tab);
showModal(true);
};
const handleModalClose = () => {
hideModal();
setTab(null);
};
useEffect(() => {
async function fetchKeys() {
const _settings = await System.keys();
setSettings(_settings);
setLoading(false);
}
fetchKeys();
}, []);
return (
<div
id={OVERLAY_ID}
className="absolute left-0 rounded-[26px] top-0 w-full h-full opacity-0 -z-10 p-[18px] transition-all duration-300 bg-white dark:bg-black-900 flex flex-col overflow-x-hidden items-between"
>
<div className="flex w-full items-center justify-between">
<div className="flex shrink-0 max-w-[50%] items-center justify-start">
<img
src={logo}
alt="Logo"
className="rounded max-h-[40px]"
style={{ objectFit: "contain" }}
/>
</div>
<div className="flex gap-x-2 items-center text-slate-500">
<button
onClick={() => {
setTab(null);
hideOverlay();
}}
className="transition-all duration-300 p-2 rounded-full bg-slate-200 text-slate-400 dark:bg-stone-800 hover:bg-slate-800 hover:text-slate-200 dark:hover:text-slate-200"
>
<X className="h-4 w-4 " />
</button>
</div>
</div>
<div className="h-[100%] flex flex-col w-full justify-between pt-4 overflow-y-hidden">
<div className="h-auto sidebar-items dark:sidebar-items">
<p className="text-sm leading-loose my-2 text-slate-800 dark:text-slate-200 ">
Select a setting to configure
</p>
{loading ? (
<div className="flex flex-col gap-y-4 h-[65vh] pb-8 overflow-y-scroll no-scroll">
<div className="rounded-lg w-[90%] h-[36px] bg-stone-600 animate-pulse" />
<div className="rounded-lg w-[90%] h-[36px] bg-stone-600 animate-pulse" />
<div className="rounded-lg w-[90%] h-[36px] bg-stone-600 animate-pulse" />
<div className="rounded-lg w-[90%] h-[36px] bg-stone-600 animate-pulse" />
<div className="rounded-lg w-[90%] h-[36px] bg-stone-600 animate-pulse" />
<div className="rounded-lg w-[90%] h-[36px] bg-stone-600 animate-pulse" />
</div>
) : (
<div className="flex flex-col gap-y-4 h-[65vh] pb-8 overflow-y-scroll no-scroll">
{!settings?.MultiUserMode && (
<Option
btnText="Appearance"
icon={<Eye className="h-4 w-4 flex-shrink-0" />}
isActive={tab === "appearance"}
onClick={() => selectTab("appearance")}
/>
)}
<Option
btnText="LLM Preference"
icon={<MessageSquare className="h-4 w-4 flex-shrink-0" />}
isActive={tab === "llm"}
onClick={() => selectTab("llm")}
/>
<Option
btnText="Vector Database"
icon={<Database className="h-4 w-4 flex-shrink-0" />}
isActive={tab === "vectordb"}
onClick={() => selectTab("vectordb")}
/>
<Option
btnText="Export or Import"
icon={<Archive className="h-4 w-4 flex-shrink-0" />}
isActive={tab === "exportimport"}
onClick={() => selectTab("exportimport")}
/>
{!settings?.MultiUserMode && (
<>
<Option
btnText="Password Protection"
icon={<Lock className="h-4 w-4 flex-shrink-0" />}
isActive={tab === "password"}
onClick={() => selectTab("password")}
/>
<Option
btnText="Multi User Mode"
icon={<Users className="h-4 w-4 flex-shrink-0" />}
isActive={tab === "multiuser"}
onClick={() => selectTab("multiuser")}
/>
</>
)}
</div>
)}
</div>
</div>
{showing && !!tab && (
<SystemSettingsModal tab={tab} hideModal={handleModalClose} />
)}
</div>
);
}
const Option = ({ btnText, icon, isActive, onClick }) => {
return (
<div className="flex gap-x-2 items-center justify-between">
<button
onClick={onClick}
className={`flex flex-grow w-[75%] h-[36px] gap-x-2 py-[5px] px-4 border border-slate-400 rounded-lg text-slate-800 dark:text-slate-200 justify-start items-center ${
isActive
? "bg-gray-100 dark:bg-stone-600"
: "hover:bg-slate-100 dark:hover:bg-stone-900 "
}`}
>
{icon}
<p className="text-slate-800 dark:text-slate-200 text-xs leading-loose font-semibold whitespace-nowrap overflow-hidden ">
{btnText}
</p>
</button>
</div>
);
};
function showOverlay() {
document
.getElementById(OVERLAY_ID)
.classList.remove(...OVERLAY_CLASSES.disabled);
document.getElementById(OVERLAY_ID).classList.add(...OVERLAY_CLASSES.enabled);
}
function hideOverlay() {
document
.getElementById(OVERLAY_ID)
.classList.remove(...OVERLAY_CLASSES.enabled);
document
.getElementById(OVERLAY_ID)
.classList.add(...OVERLAY_CLASSES.disabled);
}
export function useSystemSettingsOverlay() {
return { showOverlay, hideOverlay };
}

View File

@ -2,8 +2,6 @@ import React, { useEffect, useRef, useState } from "react";
import {
AtSign,
BookOpen,
Briefcase,
Cpu,
GitHub,
LogOut,
Menu,
@ -11,12 +9,10 @@ import {
Plus,
Shield,
Tool,
X,
} from "react-feather";
import IndexCount from "./IndexCount";
import LLMStatus from "./LLMStatus";
import SystemSettingsModal, {
useSystemSettingsModal,
} from "../Modals/Settings";
import NewWorkspaceModal, {
useNewWorkspaceModal,
} from "../Modals/NewWorkspace";
@ -27,15 +23,12 @@ import useUser from "../../hooks/useUser";
import { userFromStorage } from "../../utils/request";
import { AUTH_TOKEN, AUTH_USER } from "../../utils/constants";
import useLogo from "../../hooks/useLogo";
import SettingsOverlay, { useSystemSettingsOverlay } from "./SettingsOverlay";
export default function Sidebar() {
const { logo } = useLogo();
const sidebarRef = useRef(null);
const {
showing: showingSystemSettingsModal,
showModal: showSystemSettingsModal,
hideModal: hideSystemSettingsModal,
} = useSystemSettingsModal();
const { showOverlay } = useSystemSettingsOverlay();
const {
showing: showingNewWsModal,
showModal: showNewWsModal,
@ -47,8 +40,9 @@ export default function Sidebar() {
<div
ref={sidebarRef}
style={{ height: "calc(100% - 32px)" }}
className="transition-all duration-500 relative m-[16px] rounded-[26px] bg-white dark:bg-black-900 min-w-[15.5%] p-[18px] "
className="relative transition-all duration-500 relative m-[16px] rounded-[26px] bg-white dark:bg-black-900 min-w-[15.5%] p-[18px] "
>
<SettingsOverlay />
<div className="w-full h-full flex flex-col overflow-x-hidden items-between">
{/* Header Information */}
<div className="flex w-full items-center justify-between">
@ -62,12 +56,7 @@ export default function Sidebar() {
</div>
<div className="flex gap-x-2 items-center text-slate-500">
<AdminHome />
<button
onClick={showSystemSettingsModal}
className="transition-all duration-300 p-2 rounded-full bg-slate-200 text-slate-400 dark:bg-stone-800 hover:bg-slate-800 hover:text-slate-200 dark:hover:text-slate-200"
>
<Tool className="h-4 w-4 " />
</button>
<SettingsButton onClick={showOverlay} />
</div>
</div>
@ -142,9 +131,6 @@ export default function Sidebar() {
</div>
</div>
</div>
{showingSystemSettingsModal && (
<SystemSettingsModal hideModal={hideSystemSettingsModal} />
)}
{showingNewWsModal && <NewWorkspaceModal hideModal={hideNewWsModal} />}
</>
);
@ -155,11 +141,7 @@ export function SidebarMobileHeader() {
const sidebarRef = useRef(null);
const [showSidebar, setShowSidebar] = useState(false);
const [showBgOverlay, setShowBgOverlay] = useState(false);
const {
showing: showingSystemSettingsModal,
showModal: showSystemSettingsModal,
hideModal: hideSystemSettingsModal,
} = useSystemSettingsModal();
const { showOverlay } = useSystemSettingsOverlay();
const {
showing: showingNewWsModal,
showModal: showNewWsModal,
@ -167,6 +149,8 @@ export function SidebarMobileHeader() {
} = useNewWorkspaceModal();
useEffect(() => {
// Darkens the rest of the screen
// when sidebar is open.
function handleBg() {
if (showSidebar) {
setTimeout(() => {
@ -213,12 +197,13 @@ export function SidebarMobileHeader() {
/>
<div
ref={sidebarRef}
className="h-[100vh] fixed top-0 left-0 rounded-r-[26px] bg-white dark:bg-black-900 w-[70%] p-[18px] "
className="relative h-[100vh] fixed top-0 left-0 rounded-r-[26px] bg-white dark:bg-black-900 w-[80%] p-[18px] "
>
<SettingsOverlay />
<div className="w-full h-full flex flex-col overflow-x-hidden items-between">
{/* Header Information */}
<div className="flex w-full items-center justify-between">
<div className="flex shrink-0 w-fit items-center justify-start">
<div className="flex w-full items-center justify-between gap-x-4">
<div className="flex shrink-1 w-fit items-center justify-start">
<img
src={logo}
alt="Logo"
@ -226,14 +211,9 @@ export function SidebarMobileHeader() {
style={{ objectFit: "contain" }}
/>
</div>
<div className="flex gap-x-2 items-center text-slate-500">
<div className="flex gap-x-2 items-center text-slate-500 shink-0">
<AdminHome />
<button
onClick={showSystemSettingsModal}
className="transition-all duration-300 p-2 rounded-full bg-slate-200 text-slate-400 dark:bg-stone-800 hover:bg-slate-800 hover:text-slate-200 dark:hover:text-slate-200"
>
<Tool className="h-4 w-4 " />
</button>
<SettingsButton onClick={showOverlay} />
</div>
</div>
@ -311,9 +291,6 @@ export function SidebarMobileHeader() {
</div>
</div>
</div>
{showingSystemSettingsModal && (
<SystemSettingsModal hideModal={hideSystemSettingsModal} />
)}
{showingNewWsModal && <NewWorkspaceModal hideModal={hideNewWsModal} />}
</div>
</>
@ -355,6 +332,19 @@ function LogoutButton() {
);
}
function SettingsButton({ onClick }) {
const { user } = useUser();
if (!!user) return null;
return (
<button
onClick={onClick}
className="transition-all duration-300 p-2 rounded-full bg-slate-200 text-slate-400 dark:bg-stone-800 hover:bg-slate-800 hover:text-slate-200 dark:hover:text-slate-200"
>
<Tool className="h-4 w-4 " />
</button>
);
}
function ManagedHosting() {
if (window.location.origin.includes(".useanything.com")) return null;
return (

View File

@ -142,7 +142,7 @@ export default function Appearance() {
Change the logo that appears in the sidebar.
</p>
</div>
<div className="flex items-center">
<div className="flex md:flex-row flex-col items-center">
<img
src={logo}
alt="Uploaded Logo"

View File

@ -22,9 +22,6 @@ export default {
feedback: () => {
return "https://mintplexlabs.typeform.com/to/i0KE3aEW";
},
appearance: () => {
return "/system/appearance";
},
workspace: {
chat: (slug) => {
return `/workspace/${slug}`;