Skip to content
Merged
329 changes: 94 additions & 235 deletions frontend/src/components/DefaultChat/index.jsx
Original file line number Diff line number Diff line change
@@ -1,257 +1,116 @@
import React, { useEffect, useState } from "react";
import {
GithubLogo,
GitMerge,
EnvelopeSimple,
Plus,
} from "@phosphor-icons/react";
import NewWorkspaceModal, {
useNewWorkspaceModal,
} from "../Modals/NewWorkspace";
import paths from "@/utils/paths";
import { isMobile } from "react-device-detect";
import { SidebarMobileHeader } from "../Sidebar";
import ChatBubble from "../ChatBubble";
import System from "@/models/system";
import UserIcon from "../UserIcon";
import { userFromStorage } from "@/utils/request";
import useUser from "@/hooks/useUser";
import { useTranslation, Trans } from "react-i18next";
import Appearance from "@/models/appearance";
import { useChatMessageAlignment } from "@/hooks/useChatMessageAlignment";
import useLogo from "@/hooks/useLogo";
import Workspace from "@/models/workspace";
import { NavLink } from "react-router-dom";
import { LAST_VISITED_WORKSPACE } from "@/utils/constants";
import { useTranslation } from "react-i18next";
import { safeJsonParse } from "@/utils/request";

export default function DefaultChatContainer() {
const { getMessageAlignment } = useChatMessageAlignment();
const { showScrollbar } = Appearance.getSettings();
const [mockMsgs, setMockMessages] = useState([]);
const { user } = useUser();
const [fetchedMessages, setFetchedMessages] = useState([]);
const {
showing: showingNewWsModal,
showModal: showNewWsModal,
hideModal: hideNewWsModal,
} = useNewWorkspaceModal();
const popMsg = !window.localStorage.getItem("anythingllm_intro");
const { t } = useTranslation();
const { user } = useUser();
const { logo } = useLogo();
const [lastVisitedWorkspace, setLastVisitedWorkspace] = useState(null);
const [{ workspaces, loading }, setWorkspaces] = useState({
workspaces: [],
loading: true,
});

useEffect(() => {
const fetchData = async () => {
const fetchedMessages = await System.getWelcomeMessages();
setFetchedMessages(fetchedMessages);
};
fetchData();
}, []);

const MESSAGES = [
<React.Fragment key="msg1">
<MessageContainer>
<MessageContent alignmentCls={getMessageAlignment("assistant")}>
<UserIcon user={{ uid: "system" }} role={"assistant"} />
<MessageText>{t("welcomeMessage.part1")}</MessageText>
</MessageContent>
</MessageContainer>
</React.Fragment>,

<React.Fragment key="msg2">
<MessageContainer>
<MessageContent alignmentCls={getMessageAlignment("assistant")}>
<UserIcon user={{ uid: "system" }} role={"assistant"} />
<MessageText>{t("welcomeMessage.part2")}</MessageText>
</MessageContent>
</MessageContainer>
</React.Fragment>,

<React.Fragment key="msg3">
<MessageContainer>
<MessageContent alignmentCls={getMessageAlignment("assistant")}>
<UserIcon user={{ uid: "system" }} role={"assistant"} />
<div>
<MessageText>{t("welcomeMessage.part3")}</MessageText>
<a
href={paths.github()}
target="_blank"
rel="noreferrer"
className="mt-5 w-fit transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white light:border-black/50 light:text-theme-text-primary text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
>
<GitMerge className="h-4 w-4" />
<p>{t("welcomeMessage.githubIssue")}</p>
</a>
</div>
</MessageContent>
</MessageContainer>
</React.Fragment>,

<React.Fragment key="msg4">
<MessageContainer>
<MessageContent alignmentCls={getMessageAlignment("user")}>
<UserIcon user={{ uid: userFromStorage()?.username }} role={"user"} />
<MessageText>{t("welcomeMessage.user1")}</MessageText>
</MessageContent>
</MessageContainer>
</React.Fragment>,

<React.Fragment key="msg5">
<MessageContainer>
<MessageContent alignmentCls={getMessageAlignment("assistant")}>
<UserIcon user={{ uid: "system" }} role={"assistant"} />
<div>
<MessageText>{t("welcomeMessage.part4")}</MessageText>

{(!user || user?.role !== "default") && (
<button
onClick={showNewWsModal}
className="mt-5 w-fit transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white light:border-black/50 light:text-theme-text-primary text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
>
<Plus className="h-4 w-4" />
<p>{t("welcomeMessage.createWorkspace")}</p>
</button>
)}
</div>
</MessageContent>
</MessageContainer>
</React.Fragment>,

<React.Fragment key="msg6">
<MessageContainer>
<MessageContent alignmentCls={getMessageAlignment("user")}>
<UserIcon user={{ uid: userFromStorage()?.username }} role={"user"} />
<MessageText>{t("welcomeMessage.user2")}</MessageText>
</MessageContent>
</MessageContainer>
</React.Fragment>,

<React.Fragment key="msg7">
<MessageContainer>
<MessageContent alignmentCls={getMessageAlignment("assistant")}>
<UserIcon user={{ uid: "system" }} role={"assistant"} />
<MessageText>
<Trans
i18nKey="welcomeMessage.part5"
components={{
i: <i />,
br: <br />,
}}
/>
</MessageText>
</MessageContent>
</MessageContainer>
</React.Fragment>,

<React.Fragment key="msg8">
<MessageContainer>
<MessageContent alignmentCls={getMessageAlignment("user")}>
<UserIcon user={{ uid: userFromStorage()?.username }} role={"user"} />
<MessageText>{t("welcomeMessage.user3")}</MessageText>
</MessageContent>
</MessageContainer>
</React.Fragment>,

<React.Fragment key="msg9">
<MessageContainer>
<MessageContent alignmentCls={getMessageAlignment("assistant")}>
<UserIcon user={{ uid: "system" }} role={"assistant"} />
<div>
<MessageText>{t("welcomeMessage.part6")}</MessageText>

<div className="flex flex-col md:flex-row items-start md:items-center gap-1 md:gap-4">
<a
href={paths.github()}
target="_blank"
rel="noreferrer"
className="mt-5 w-fit transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white light:border-black/50 light:text-theme-text-primary text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
>
<GithubLogo className="h-4 w-4" />
<p>{t("welcomeMessage.starOnGitHub")}</p>
</a>
<a
href={paths.mailToMintplex()}
className="mt-5 w-fit transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white light:border-black/50 light:text-theme-text-primary text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
>
<EnvelopeSimple className="h-4 w-4" />
<p>{t("welcomeMessage.contact")}</p>
</a>
</div>
</div>
</MessageContent>
</MessageContainer>
</React.Fragment>,
];

useEffect(() => {
function processMsgs() {
if (!!window.localStorage.getItem("anythingllm_intro")) {
setMockMessages([...MESSAGES]);
return false;
} else {
setMockMessages([MESSAGES[0]]);
async function fetchWorkspaces() {
const availableWorkspaces = await Workspace.all();
const serializedLastVisitedWorkspace = localStorage.getItem(
LAST_VISITED_WORKSPACE
);
if (!serializedLastVisitedWorkspace)
return setWorkspaces({
workspaces: availableWorkspaces,
loading: false,
});

try {
const lastVisitedWorkspace = safeJsonParse(
serializedLastVisitedWorkspace,
null
);
if (lastVisitedWorkspace == null) throw new Error("Non-parseable!");
const isValid = availableWorkspaces.some(
(ws) => ws.slug === lastVisitedWorkspace?.slug
);
if (!isValid) throw new Error("Invalid value!");
setLastVisitedWorkspace(lastVisitedWorkspace);
} catch {
localStorage.removeItem(LAST_VISITED_WORKSPACE);
} finally {
setWorkspaces({ workspaces: availableWorkspaces, loading: false });
}

var timer = 500;
var messages = [];

MESSAGES.map((child) => {
setTimeout(() => {
setMockMessages([...messages, child]);
messages.push(child);
}, timer);
timer += 2_500;
});
window.localStorage.setItem("anythingllm_intro", 1);
}

processMsgs();
fetchWorkspaces();
}, []);

if (loading) {
return (
<Layout>
<div className="w-full h-full flex flex-col items-center justify-center overflow-y-auto no-scroll">
{/* Logo skeleton */}
<div className="w-[140px] h-[140px] mb-5 rounded-lg bg-theme-bg-primary animate-pulse" />
{/* Title skeleton */}
<div className="w-48 h-6 mb-4 rounded bg-theme-bg-primary animate-pulse" />
{/* Paragraph skeleton */}
<div className="w-80 h-4 mb-2 rounded bg-theme-bg-primary animate-pulse" />
<div className="w-64 h-4 rounded bg-theme-bg-primary animate-pulse" />
{/* Button skeleton */}
<div className="mt-[29px] w-40 h-[34px] rounded-lg bg-theme-bg-primary animate-pulse" />
</div>
</Layout>
);
}

const hasWorkspaces = workspaces.length > 0;
return (
<div
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
className={`transition-all duration-500 relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-secondary light:border-[1px] light:border-theme-sidebar-border w-full h-full overflow-y-scroll ${
showScrollbar ? "show-scrollbar" : "no-scroll"
}`}
>
{isMobile && <SidebarMobileHeader />}
{fetchedMessages.length === 0
? mockMsgs.map((content, i) => {
return <React.Fragment key={i}>{content}</React.Fragment>;
})
: fetchedMessages.map((fetchedMessage, i) => {
return (
<React.Fragment key={i}>
<ChatBubble
message={
fetchedMessage.user === ""
? fetchedMessage.response
: fetchedMessage.user
}
type={fetchedMessage.user === "" ? "response" : "user"}
popMsg={popMsg}
/>
</React.Fragment>
);
})}
{showingNewWsModal && <NewWorkspaceModal hideModal={hideNewWsModal} />}
</div>
);
}

function MessageContainer({ children }) {
return (
<div className="flex justify-center items-end w-full">
<div className="py-6 px-4 w-full flex gap-x-5 md:max-w-[80%] flex-col">
{children}
<Layout>
<div className="w-full h-full flex flex-col items-center justify-center overflow-y-auto no-scroll">
<img
src={logo}
alt="Custom Logo"
className=" w-[200px] h-fit mb-5 rounded-lg"
/>
<h1 className="text-white text-2xl font-semibold">
{t("home.welcome")}, {user.username}!
</h1>
<p className="text-theme-home-text-secondary text-base text-center whitespace-pre-line">
{hasWorkspaces ? t("home.chooseWorkspace") : t("home.notAssigned")}
</p>
{hasWorkspaces && (
<NavLink
to={paths.workspace.chat(
lastVisitedWorkspace?.slug || workspaces[0].slug
)}
className="text-sm font-medium mt-[10px] w-fit px-4 h-[34px] flex items-center justify-center rounded-lg cursor-pointer bg-theme-home-button-secondary hover:bg-theme-home-button-secondary-hover text-theme-home-button-secondary-text hover:text-theme-home-button-secondary-hover-text transition-all duration-200"
>
{t("home.goToWorkspace", {
workspace: lastVisitedWorkspace?.name || workspaces[0].name,
})}{" "}
&rarr;
</NavLink>
)}
</div>
</div>
</Layout>
);
}

function MessageContent({ children, alignmentCls = "" }) {
return <div className={`flex gap-x-5 ${alignmentCls}`}>{children}</div>;
}

function MessageText({ children }) {
const Layout = ({ children }) => {
const { showScrollbar } = Appearance.getSettings();
return (
<span className="text-white/80 light:text-theme-text-primary font-light text-[14px] flex flex-col gap-y-1 mt-2">
<div
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
className={`relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-secondary light:border-[1px] light:border-theme-sidebar-border w-full h-full overflow-y-scroll ${showScrollbar ? "show-scrollbar" : "no-scroll"}`}
>
{children}
</span>
</div>
);
}
};
8 changes: 7 additions & 1 deletion frontend/src/components/UserMenu/UserButton/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import { userFromStorage } from "@/utils/request";
import { Person } from "@phosphor-icons/react";
import { useEffect, useRef, useState } from "react";
import AccountModal from "../AccountModal";
import { AUTH_TIMESTAMP, AUTH_TOKEN, AUTH_USER } from "@/utils/constants";
import {
AUTH_TIMESTAMP,
AUTH_TOKEN,
AUTH_USER,
LAST_VISITED_WORKSPACE,
} from "@/utils/constants";
import { useTranslation } from "react-i18next";

export default function UserButton() {
Expand Down Expand Up @@ -91,6 +96,7 @@ export default function UserButton() {
window.localStorage.removeItem(AUTH_USER);
window.localStorage.removeItem(AUTH_TOKEN);
window.localStorage.removeItem(AUTH_TIMESTAMP);
window.localStorage.removeItem(LAST_VISITED_WORKSPACE);
window.location.replace(paths.home());
}}
type="button"
Expand Down
Loading