|
1 | 1 | 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"; |
11 | 2 | import paths from "@/utils/paths"; |
12 | 3 | 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"; |
18 | 4 | import useUser from "@/hooks/useUser"; |
19 | | -import { useTranslation, Trans } from "react-i18next"; |
20 | 5 | 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"; |
22 | 12 |
|
23 | 13 | 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"); |
35 | 14 | 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 | + }); |
36 | 22 |
|
37 | 23 | 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 }); |
187 | 50 | } |
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); |
200 | 51 | } |
201 | | - |
202 | | - processMsgs(); |
| 52 | + fetchWorkspaces(); |
203 | 53 | }, []); |
204 | 54 |
|
| 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; |
205 | 74 | 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 | + → |
| 99 | + </NavLink> |
| 100 | + )} |
242 | 101 | </div> |
243 | | - </div> |
| 102 | + </Layout> |
244 | 103 | ); |
245 | 104 | } |
246 | 105 |
|
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(); |
252 | 108 | 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 | + > |
254 | 113 | {children} |
255 | | - </span> |
| 114 | + </div> |
256 | 115 | ); |
257 | | -} |
| 116 | +}; |
0 commit comments