Skip to content

Commit 8c97240

Browse files
Refactor DefaultChatContainer To Display A Simple Welcome Message (#4542)
* Refactor DefaultChat component to display a simple welcome message instead of mock chat interface onboarding text * Add last visited workspace functionality - Implemented localStorage management for the last visited workspace in DefaultChat and WorkspaceChat components. - Updated UserButton to clear last visited workspace on logout. - Refactored workspace navigation to prioritize last visited workspace if available. * Refactor workspace fetching & last visited workspace logic into one useEffect * Enhance loading state in DefaultChat component with skeleton UI elements for improved user experience * minor UI spacing changes * refactor order for guard clauses for early exit conditions * move last known to end of getWorkspace loader * languages for PR #4542 (#4550) * languages for PR #4542 * Drop welcome message keys from translations --------- Co-authored-by: timothycarambat <[email protected]>
1 parent f5f8fb1 commit 8c97240

File tree

26 files changed

+267
-698
lines changed

26 files changed

+267
-698
lines changed
Lines changed: 94 additions & 235 deletions
Original file line numberDiff line numberDiff line change
@@ -1,257 +1,116 @@
11
import React, { useEffect, useState } from "react";
2-
import {
3-
GithubLogo,
4-
GitMerge,
5-
EnvelopeSimple,
6-
Plus,
7-
} from "@phosphor-icons/react";
8-
import NewWorkspaceModal, {
9-
useNewWorkspaceModal,
10-
} from "../Modals/NewWorkspace";
112
import paths from "@/utils/paths";
123
import { isMobile } from "react-device-detect";
13-
import { SidebarMobileHeader } from "../Sidebar";
14-
import ChatBubble from "../ChatBubble";
15-
import System from "@/models/system";
16-
import UserIcon from "../UserIcon";
17-
import { userFromStorage } from "@/utils/request";
184
import useUser from "@/hooks/useUser";
19-
import { useTranslation, Trans } from "react-i18next";
205
import Appearance from "@/models/appearance";
21-
import { useChatMessageAlignment } from "@/hooks/useChatMessageAlignment";
6+
import useLogo from "@/hooks/useLogo";
7+
import Workspace from "@/models/workspace";
8+
import { NavLink } from "react-router-dom";
9+
import { LAST_VISITED_WORKSPACE } from "@/utils/constants";
10+
import { useTranslation } from "react-i18next";
11+
import { safeJsonParse } from "@/utils/request";
2212

2313
export default function DefaultChatContainer() {
24-
const { getMessageAlignment } = useChatMessageAlignment();
25-
const { showScrollbar } = Appearance.getSettings();
26-
const [mockMsgs, setMockMessages] = useState([]);
27-
const { user } = useUser();
28-
const [fetchedMessages, setFetchedMessages] = useState([]);
29-
const {
30-
showing: showingNewWsModal,
31-
showModal: showNewWsModal,
32-
hideModal: hideNewWsModal,
33-
} = useNewWorkspaceModal();
34-
const popMsg = !window.localStorage.getItem("anythingllm_intro");
3514
const { t } = useTranslation();
15+
const { user } = useUser();
16+
const { logo } = useLogo();
17+
const [lastVisitedWorkspace, setLastVisitedWorkspace] = useState(null);
18+
const [{ workspaces, loading }, setWorkspaces] = useState({
19+
workspaces: [],
20+
loading: true,
21+
});
3622

3723
useEffect(() => {
38-
const fetchData = async () => {
39-
const fetchedMessages = await System.getWelcomeMessages();
40-
setFetchedMessages(fetchedMessages);
41-
};
42-
fetchData();
43-
}, []);
44-
45-
const MESSAGES = [
46-
<React.Fragment key="msg1">
47-
<MessageContainer>
48-
<MessageContent alignmentCls={getMessageAlignment("assistant")}>
49-
<UserIcon user={{ uid: "system" }} role={"assistant"} />
50-
<MessageText>{t("welcomeMessage.part1")}</MessageText>
51-
</MessageContent>
52-
</MessageContainer>
53-
</React.Fragment>,
54-
55-
<React.Fragment key="msg2">
56-
<MessageContainer>
57-
<MessageContent alignmentCls={getMessageAlignment("assistant")}>
58-
<UserIcon user={{ uid: "system" }} role={"assistant"} />
59-
<MessageText>{t("welcomeMessage.part2")}</MessageText>
60-
</MessageContent>
61-
</MessageContainer>
62-
</React.Fragment>,
63-
64-
<React.Fragment key="msg3">
65-
<MessageContainer>
66-
<MessageContent alignmentCls={getMessageAlignment("assistant")}>
67-
<UserIcon user={{ uid: "system" }} role={"assistant"} />
68-
<div>
69-
<MessageText>{t("welcomeMessage.part3")}</MessageText>
70-
<a
71-
href={paths.github()}
72-
target="_blank"
73-
rel="noreferrer"
74-
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"
75-
>
76-
<GitMerge className="h-4 w-4" />
77-
<p>{t("welcomeMessage.githubIssue")}</p>
78-
</a>
79-
</div>
80-
</MessageContent>
81-
</MessageContainer>
82-
</React.Fragment>,
83-
84-
<React.Fragment key="msg4">
85-
<MessageContainer>
86-
<MessageContent alignmentCls={getMessageAlignment("user")}>
87-
<UserIcon user={{ uid: userFromStorage()?.username }} role={"user"} />
88-
<MessageText>{t("welcomeMessage.user1")}</MessageText>
89-
</MessageContent>
90-
</MessageContainer>
91-
</React.Fragment>,
92-
93-
<React.Fragment key="msg5">
94-
<MessageContainer>
95-
<MessageContent alignmentCls={getMessageAlignment("assistant")}>
96-
<UserIcon user={{ uid: "system" }} role={"assistant"} />
97-
<div>
98-
<MessageText>{t("welcomeMessage.part4")}</MessageText>
99-
100-
{(!user || user?.role !== "default") && (
101-
<button
102-
onClick={showNewWsModal}
103-
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"
104-
>
105-
<Plus className="h-4 w-4" />
106-
<p>{t("welcomeMessage.createWorkspace")}</p>
107-
</button>
108-
)}
109-
</div>
110-
</MessageContent>
111-
</MessageContainer>
112-
</React.Fragment>,
113-
114-
<React.Fragment key="msg6">
115-
<MessageContainer>
116-
<MessageContent alignmentCls={getMessageAlignment("user")}>
117-
<UserIcon user={{ uid: userFromStorage()?.username }} role={"user"} />
118-
<MessageText>{t("welcomeMessage.user2")}</MessageText>
119-
</MessageContent>
120-
</MessageContainer>
121-
</React.Fragment>,
122-
123-
<React.Fragment key="msg7">
124-
<MessageContainer>
125-
<MessageContent alignmentCls={getMessageAlignment("assistant")}>
126-
<UserIcon user={{ uid: "system" }} role={"assistant"} />
127-
<MessageText>
128-
<Trans
129-
i18nKey="welcomeMessage.part5"
130-
components={{
131-
i: <i />,
132-
br: <br />,
133-
}}
134-
/>
135-
</MessageText>
136-
</MessageContent>
137-
</MessageContainer>
138-
</React.Fragment>,
139-
140-
<React.Fragment key="msg8">
141-
<MessageContainer>
142-
<MessageContent alignmentCls={getMessageAlignment("user")}>
143-
<UserIcon user={{ uid: userFromStorage()?.username }} role={"user"} />
144-
<MessageText>{t("welcomeMessage.user3")}</MessageText>
145-
</MessageContent>
146-
</MessageContainer>
147-
</React.Fragment>,
148-
149-
<React.Fragment key="msg9">
150-
<MessageContainer>
151-
<MessageContent alignmentCls={getMessageAlignment("assistant")}>
152-
<UserIcon user={{ uid: "system" }} role={"assistant"} />
153-
<div>
154-
<MessageText>{t("welcomeMessage.part6")}</MessageText>
155-
156-
<div className="flex flex-col md:flex-row items-start md:items-center gap-1 md:gap-4">
157-
<a
158-
href={paths.github()}
159-
target="_blank"
160-
rel="noreferrer"
161-
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"
162-
>
163-
<GithubLogo className="h-4 w-4" />
164-
<p>{t("welcomeMessage.starOnGitHub")}</p>
165-
</a>
166-
<a
167-
href={paths.mailToMintplex()}
168-
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"
169-
>
170-
<EnvelopeSimple className="h-4 w-4" />
171-
<p>{t("welcomeMessage.contact")}</p>
172-
</a>
173-
</div>
174-
</div>
175-
</MessageContent>
176-
</MessageContainer>
177-
</React.Fragment>,
178-
];
179-
180-
useEffect(() => {
181-
function processMsgs() {
182-
if (!!window.localStorage.getItem("anythingllm_intro")) {
183-
setMockMessages([...MESSAGES]);
184-
return false;
185-
} else {
186-
setMockMessages([MESSAGES[0]]);
24+
async function fetchWorkspaces() {
25+
const availableWorkspaces = await Workspace.all();
26+
const serializedLastVisitedWorkspace = localStorage.getItem(
27+
LAST_VISITED_WORKSPACE
28+
);
29+
if (!serializedLastVisitedWorkspace)
30+
return setWorkspaces({
31+
workspaces: availableWorkspaces,
32+
loading: false,
33+
});
34+
35+
try {
36+
const lastVisitedWorkspace = safeJsonParse(
37+
serializedLastVisitedWorkspace,
38+
null
39+
);
40+
if (lastVisitedWorkspace == null) throw new Error("Non-parseable!");
41+
const isValid = availableWorkspaces.some(
42+
(ws) => ws.slug === lastVisitedWorkspace?.slug
43+
);
44+
if (!isValid) throw new Error("Invalid value!");
45+
setLastVisitedWorkspace(lastVisitedWorkspace);
46+
} catch {
47+
localStorage.removeItem(LAST_VISITED_WORKSPACE);
48+
} finally {
49+
setWorkspaces({ workspaces: availableWorkspaces, loading: false });
18750
}
188-
189-
var timer = 500;
190-
var messages = [];
191-
192-
MESSAGES.map((child) => {
193-
setTimeout(() => {
194-
setMockMessages([...messages, child]);
195-
messages.push(child);
196-
}, timer);
197-
timer += 2_500;
198-
});
199-
window.localStorage.setItem("anythingllm_intro", 1);
20051
}
201-
202-
processMsgs();
52+
fetchWorkspaces();
20353
}, []);
20454

55+
if (loading) {
56+
return (
57+
<Layout>
58+
<div className="w-full h-full flex flex-col items-center justify-center overflow-y-auto no-scroll">
59+
{/* Logo skeleton */}
60+
<div className="w-[140px] h-[140px] mb-5 rounded-lg bg-theme-bg-primary animate-pulse" />
61+
{/* Title skeleton */}
62+
<div className="w-48 h-6 mb-4 rounded bg-theme-bg-primary animate-pulse" />
63+
{/* Paragraph skeleton */}
64+
<div className="w-80 h-4 mb-2 rounded bg-theme-bg-primary animate-pulse" />
65+
<div className="w-64 h-4 rounded bg-theme-bg-primary animate-pulse" />
66+
{/* Button skeleton */}
67+
<div className="mt-[29px] w-40 h-[34px] rounded-lg bg-theme-bg-primary animate-pulse" />
68+
</div>
69+
</Layout>
70+
);
71+
}
72+
73+
const hasWorkspaces = workspaces.length > 0;
20574
return (
206-
<div
207-
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
208-
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 ${
209-
showScrollbar ? "show-scrollbar" : "no-scroll"
210-
}`}
211-
>
212-
{isMobile && <SidebarMobileHeader />}
213-
{fetchedMessages.length === 0
214-
? mockMsgs.map((content, i) => {
215-
return <React.Fragment key={i}>{content}</React.Fragment>;
216-
})
217-
: fetchedMessages.map((fetchedMessage, i) => {
218-
return (
219-
<React.Fragment key={i}>
220-
<ChatBubble
221-
message={
222-
fetchedMessage.user === ""
223-
? fetchedMessage.response
224-
: fetchedMessage.user
225-
}
226-
type={fetchedMessage.user === "" ? "response" : "user"}
227-
popMsg={popMsg}
228-
/>
229-
</React.Fragment>
230-
);
231-
})}
232-
{showingNewWsModal && <NewWorkspaceModal hideModal={hideNewWsModal} />}
233-
</div>
234-
);
235-
}
236-
237-
function MessageContainer({ children }) {
238-
return (
239-
<div className="flex justify-center items-end w-full">
240-
<div className="py-6 px-4 w-full flex gap-x-5 md:max-w-[80%] flex-col">
241-
{children}
75+
<Layout>
76+
<div className="w-full h-full flex flex-col items-center justify-center overflow-y-auto no-scroll">
77+
<img
78+
src={logo}
79+
alt="Custom Logo"
80+
className=" w-[200px] h-fit mb-5 rounded-lg"
81+
/>
82+
<h1 className="text-white text-2xl font-semibold">
83+
{t("home.welcome")}, {user.username}!
84+
</h1>
85+
<p className="text-theme-home-text-secondary text-base text-center whitespace-pre-line">
86+
{hasWorkspaces ? t("home.chooseWorkspace") : t("home.notAssigned")}
87+
</p>
88+
{hasWorkspaces && (
89+
<NavLink
90+
to={paths.workspace.chat(
91+
lastVisitedWorkspace?.slug || workspaces[0].slug
92+
)}
93+
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"
94+
>
95+
{t("home.goToWorkspace", {
96+
workspace: lastVisitedWorkspace?.name || workspaces[0].name,
97+
})}{" "}
98+
&rarr;
99+
</NavLink>
100+
)}
242101
</div>
243-
</div>
102+
</Layout>
244103
);
245104
}
246105

247-
function MessageContent({ children, alignmentCls = "" }) {
248-
return <div className={`flex gap-x-5 ${alignmentCls}`}>{children}</div>;
249-
}
250-
251-
function MessageText({ children }) {
106+
const Layout = ({ children }) => {
107+
const { showScrollbar } = Appearance.getSettings();
252108
return (
253-
<span className="text-white/80 light:text-theme-text-primary font-light text-[14px] flex flex-col gap-y-1 mt-2">
109+
<div
110+
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
111+
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"}`}
112+
>
254113
{children}
255-
</span>
114+
</div>
256115
);
257-
}
116+
};

frontend/src/components/UserMenu/UserButton/index.jsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { userFromStorage } from "@/utils/request";
77
import { Person } from "@phosphor-icons/react";
88
import { useEffect, useRef, useState } from "react";
99
import AccountModal from "../AccountModal";
10-
import { AUTH_TIMESTAMP, AUTH_TOKEN, AUTH_USER } from "@/utils/constants";
10+
import {
11+
AUTH_TIMESTAMP,
12+
AUTH_TOKEN,
13+
AUTH_USER,
14+
LAST_VISITED_WORKSPACE,
15+
} from "@/utils/constants";
1116
import { useTranslation } from "react-i18next";
1217

1318
export default function UserButton() {
@@ -91,6 +96,7 @@ export default function UserButton() {
9196
window.localStorage.removeItem(AUTH_USER);
9297
window.localStorage.removeItem(AUTH_TOKEN);
9398
window.localStorage.removeItem(AUTH_TIMESTAMP);
99+
window.localStorage.removeItem(LAST_VISITED_WORKSPACE);
94100
window.location.replace(paths.home());
95101
}}
96102
type="button"

0 commit comments

Comments
 (0)