From 6ca2f09079856176e5929c45539a178dc4892a13 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Mon, 27 Nov 2023 15:15:56 -0800 Subject: [PATCH 01/17] fix sizing of onboarding modals & lint --- .../src/components/Modals/NewWorkspace.jsx | 10 ++----- frontend/src/components/UserMenu/index.jsx | 24 +++++++-------- frontend/src/index.css | 2 +- .../Steps/AppearanceSetup/index.jsx | 4 +-- .../Steps/CreateFirstWorkspace/index.jsx | 2 +- .../Steps/DataHandling/index.jsx | 29 ++++++++++--------- .../Steps/EmbeddingSelection/index.jsx | 8 ++--- .../Steps/LLMSelection/index.jsx | 8 ++--- .../Steps/MultiUserSetup/index.jsx | 6 ++-- .../Steps/PasswordProtection/index.jsx | 6 ++-- .../Steps/UserModeSelection/index.jsx | 2 +- .../Steps/VectorDatabaseConnection/index.jsx | 8 ++--- .../OnboardingFlow/OnboardingModal/index.jsx | 2 +- 13 files changed, 55 insertions(+), 56 deletions(-) diff --git a/frontend/src/components/Modals/NewWorkspace.jsx b/frontend/src/components/Modals/NewWorkspace.jsx index 5b8ecb8fc33..fb485040451 100644 --- a/frontend/src/components/Modals/NewWorkspace.jsx +++ b/frontend/src/components/Modals/NewWorkspace.jsx @@ -14,7 +14,7 @@ export default function NewWorkspaceModal({ hideModal = noop }) { const form = new FormData(formEl.current); for (var [key, value] of form.entries()) data[key] = value; const { workspace, message } = await Workspace.new(data); - if (!!workspace){ + if (!!workspace) { window.location.href = paths.workspace.chat(workspace.slug); } setError(message); @@ -29,9 +29,7 @@ export default function NewWorkspaceModal({ hideModal = noop }) {
-

- New Workspace -

+

New Workspace

{error && ( -

- Error: {error} -

+

Error: {error}

)}
diff --git a/frontend/src/components/UserMenu/index.jsx b/frontend/src/components/UserMenu/index.jsx index deb303fbd54..6a549f82029 100644 --- a/frontend/src/components/UserMenu/index.jsx +++ b/frontend/src/components/UserMenu/index.jsx @@ -1,9 +1,9 @@ -import React, { useState, useEffect, useRef } from 'react'; -import { isMobile } from 'react-device-detect'; -import paths from '../../utils/paths'; -import { AUTH_TIMESTAMP, AUTH_TOKEN, AUTH_USER } from '../../utils/constants'; -import { Person, SignOut } from '@phosphor-icons/react'; -import { userFromStorage } from '../../utils/request'; +import React, { useState, useEffect, useRef } from "react"; +import { isMobile } from "react-device-detect"; +import paths from "../../utils/paths"; +import { AUTH_TIMESTAMP, AUTH_TOKEN, AUTH_USER } from "../../utils/constants"; +import { Person, SignOut } from "@phosphor-icons/react"; +import { userFromStorage } from "../../utils/request"; export default function UserMenu({ children }) { if (isMobile) return <>{children}; @@ -20,14 +20,14 @@ function useLoginMode() { const user = !!window.localStorage.getItem(AUTH_USER); const token = !!window.localStorage.getItem(AUTH_TOKEN); - if (user && token) return 'multi'; - if (!user && token) return 'single'; + if (user && token) return "multi"; + if (!user && token) return "single"; return null; } function userDisplay() { const user = userFromStorage(); - return user?.username?.slice(0, 2) || 'AA'; + return user?.username?.slice(0, 2) || "AA"; } function UserButton() { @@ -47,9 +47,9 @@ function UserButton() { useEffect(() => { if (showMenu) { - document.addEventListener('mousedown', handleClose); + document.addEventListener("mousedown", handleClose); } - return () => document.removeEventListener('mousedown', handleClose); + return () => document.removeEventListener("mousedown", handleClose); }, [showMenu]); if (mode === null) return null; @@ -62,7 +62,7 @@ function UserButton() { type="button" className="uppercase transition-all duration-300 w-[35px] h-[35px] text-base font-semibold rounded-full flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient justify-center text-white p-2 hover:border-slate-100 hover:border-opacity-50 border-transparent border" > - {mode === 'multi' ? userDisplay() : } + {mode === "multi" ? userDisplay() : } {showMenu && ( diff --git a/frontend/src/index.css b/frontend/src/index.css index 22affd2026e..937631beba2 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -378,4 +378,4 @@ dialog::backdrop { 100% { opacity: 0; } -} \ No newline at end of file +} diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/AppearanceSetup/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/AppearanceSetup/index.jsx index 3ccfa8c58c1..ed5c5579904 100644 --- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/AppearanceSetup/index.jsx +++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/AppearanceSetup/index.jsx @@ -58,7 +58,7 @@ function AppearanceSetup({ prevStep, nextStep }) { return (
-
+

Custom Logo

@@ -108,7 +108,7 @@ function AppearanceSetup({ prevStep, nextStep }) {

-
+
-
+
- + {modalVisible && + + }
); } From 1ebd3ae5849e83f29401d853b84df2d278ea2970 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Mon, 27 Nov 2023 15:53:54 -0800 Subject: [PATCH 03/17] added message to use desktop for onboarding --- frontend/src/pages/OnboardingFlow/index.jsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/frontend/src/pages/OnboardingFlow/index.jsx b/frontend/src/pages/OnboardingFlow/index.jsx index c914c28d6ee..6e625cb4480 100644 --- a/frontend/src/pages/OnboardingFlow/index.jsx +++ b/frontend/src/pages/OnboardingFlow/index.jsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from "react"; import OnboardingModal, { OnboardingModalId } from "./OnboardingModal"; import useLogo from "../../hooks/useLogo"; +import { isMobile } from "react-device-detect"; export default function OnboardingFlow() { const { logo } = useLogo(); @@ -16,6 +17,24 @@ export default function OnboardingFlow() { setModalVisible(true); } + if(isMobile) { + return( +
+
+
+ Welcome to +
+ logo +
+

+ Please use a desktop browser to continue. +

+
+
+
+ ) + } + return (
From 67fc8d5a160510826a154a0875d6fbcb3d9cc9f5 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Mon, 27 Nov 2023 15:55:35 -0800 Subject: [PATCH 04/17] linting --- frontend/src/pages/OnboardingFlow/index.jsx | 32 ++++++++++----------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/frontend/src/pages/OnboardingFlow/index.jsx b/frontend/src/pages/OnboardingFlow/index.jsx index 6e625cb4480..6f4d2b2e9c0 100644 --- a/frontend/src/pages/OnboardingFlow/index.jsx +++ b/frontend/src/pages/OnboardingFlow/index.jsx @@ -17,23 +17,23 @@ export default function OnboardingFlow() { setModalVisible(true); } - if(isMobile) { - return( + if (isMobile) { + return (
-
-
- Welcome to -
- logo -
-

- Please use a desktop browser to continue. -

+
+
+ Welcome to +
+ logo +
+

+ Please use a desktop browser to continue. +

+
-
- ) - } + ); + } return (
@@ -51,9 +51,7 @@ export default function OnboardingFlow() {
- {modalVisible && - - } + {modalVisible && }
); } From 24dde673d12c8f29301e0a38458a002ebb080380 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Tue, 28 Nov 2023 16:18:18 -0800 Subject: [PATCH 05/17] add arrow to scroll to bottom (debounced) and fix chat scrolling to always scroll to very bottom on message history change --- .../ChatContainer/ChatHistory/index.jsx | 54 ++++++++++++++++--- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx index 20ee990ef8b..83dea41ae62 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx @@ -1,21 +1,49 @@ import HistoricalMessage from "./HistoricalMessage"; import PromptReply from "./PromptReply"; -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { useManageWorkspaceModal } from "../../../Modals/MangeWorkspace"; import ManageWorkspace from "../../../Modals/MangeWorkspace"; +import { ArrowDown } from "@phosphor-icons/react"; +import debounce from "lodash.debounce"; export default function ChatHistory({ history = [], workspace }) { const replyRef = useRef(null); const { showing, showModal, hideModal } = useManageWorkspaceModal(); + const [isAtBottom, setIsAtBottom] = useState(true); + const chatHistoryRef = useRef(null); useEffect(() => { - if (replyRef.current) { - setTimeout(() => { - replyRef.current.scrollIntoView({ behavior: "smooth", block: "end" }); - }, 700); - } + scrollToBottom(); }, [history]); + const handleScroll = () => { + const isBottom = + chatHistoryRef.current.scrollHeight - chatHistoryRef.current.scrollTop === + chatHistoryRef.current.clientHeight; + setIsAtBottom(isBottom); + }; + + const debouncedScroll = debounce(handleScroll, 100); + + useEffect(() => { + const chatHistoryElement = chatHistoryRef.current; + chatHistoryElement.addEventListener("scroll", debouncedScroll); + + return () => { + chatHistoryElement.removeEventListener("scroll", debouncedScroll); + debouncedScroll.cancel(); + }; + }, []); + + const scrollToBottom = () => { + if (chatHistoryRef.current) { + chatHistoryRef.current.scrollTo({ + top: chatHistoryRef.current.scrollHeight, + behavior: "smooth", + }); + } + }; + if (history.length === 0) { return (
@@ -50,6 +78,7 @@ export default function ChatHistory({ history = [], workspace }) {
{history.map((props, index) => { const isLastMessage = index === history.length - 1; @@ -88,6 +117,19 @@ export default function ChatHistory({ history = [], workspace }) { {showing && ( )} + {!isAtBottom && ( +
+
+
+ +
+
+
+ )}
); } From 67f97e15336e30e8a9d8c66c8dbfbc888d2e4dd1 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Tue, 28 Nov 2023 16:34:43 -0800 Subject: [PATCH 06/17] fix for empty chat --- .../components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx index 83dea41ae62..036bb03895a 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx @@ -26,6 +26,7 @@ export default function ChatHistory({ history = [], workspace }) { const debouncedScroll = debounce(handleScroll, 100); useEffect(() => { + if(!chatHistoryRef.current) return null; const chatHistoryElement = chatHistoryRef.current; chatHistoryElement.addEventListener("scroll", debouncedScroll); From 058745464b9eaed3d49cd2af683d9a5406dfd03f Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Mon, 4 Dec 2023 08:47:56 -0800 Subject: [PATCH 07/17] change mobile alert copy --- frontend/src/pages/OnboardingFlow/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/OnboardingFlow/index.jsx b/frontend/src/pages/OnboardingFlow/index.jsx index 6f4d2b2e9c0..0839635c25e 100644 --- a/frontend/src/pages/OnboardingFlow/index.jsx +++ b/frontend/src/pages/OnboardingFlow/index.jsx @@ -26,8 +26,8 @@ export default function OnboardingFlow() {
logo
-

- Please use a desktop browser to continue. +

+ Please use a desktop browser to continue onboarding.

From d8a0d1ddcd861c680813713e86c2e363ac94a7e5 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Tue, 28 Nov 2023 17:46:59 -0800 Subject: [PATCH 08/17] WIP adding PFP upload support --- frontend/src/components/UserMenu/index.jsx | 9 +++ .../ChatContainer/ChatHistory/index.jsx | 2 +- server/endpoints/system.js | 31 +++++++- .../20231129012019_add/migration.sql | 2 + server/prisma/schema.prisma | 1 + server/storage/anythingllm.db-journal | Bin 0 -> 12824 bytes server/utils/files/multer.js | 17 +++++ server/utils/files/pfp.js | 70 ++++++++++++++++++ 8 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 server/prisma/migrations/20231129012019_add/migration.sql create mode 100644 server/storage/anythingllm.db-journal create mode 100644 server/utils/files/pfp.js diff --git a/frontend/src/components/UserMenu/index.jsx b/frontend/src/components/UserMenu/index.jsx index 6a549f82029..334c6dd43b8 100644 --- a/frontend/src/components/UserMenu/index.jsx +++ b/frontend/src/components/UserMenu/index.jsx @@ -4,6 +4,7 @@ import paths from "../../utils/paths"; import { AUTH_TIMESTAMP, AUTH_TOKEN, AUTH_USER } from "../../utils/constants"; import { Person, SignOut } from "@phosphor-icons/react"; import { userFromStorage } from "../../utils/request"; +import useUser from "../../hooks/useUser"; export default function UserMenu({ children }) { if (isMobile) return <>{children}; @@ -20,6 +21,8 @@ function useLoginMode() { const user = !!window.localStorage.getItem(AUTH_USER); const token = !!window.localStorage.getItem(AUTH_TOKEN); + console.log(useUser()); + if (user && token) return "multi"; if (!user && token) return "single"; return null; @@ -77,6 +80,12 @@ function UserButton() { > Support + + Account + + + console.log("submitted")}> +
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+ + + + + ); +} diff --git a/frontend/src/hooks/usePfp.js b/frontend/src/hooks/usePfp.js new file mode 100644 index 00000000000..e4ecd5e877e --- /dev/null +++ b/frontend/src/hooks/usePfp.js @@ -0,0 +1,22 @@ +import { useEffect, useState } from "react"; +import System from "../models/system"; +import AnythingLLM from "../media/logo/anything-llm.png"; + +export default function useLogo() { + const [logo, setLogo] = useState(""); + + useEffect(() => { + async function fetchInstanceLogo() { + try { + const logoURL = await System.fetchLogo(); + logoURL ? setLogo(logoURL) : setLogo(AnythingLLM); + } catch (err) { + setLogo(AnythingLLM); + console.error("Failed to fetch logo:", err); + } + } + fetchInstanceLogo(); + }, []); + + return { logo }; +} diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js index f69636a4289..fc220c19dd4 100644 --- a/frontend/src/models/system.js +++ b/frontend/src/models/system.js @@ -170,6 +170,21 @@ const System = { return { success: false, error: e.message }; }); }, + uploadPfp: async function (formData) { + return await fetch(`${API_BASE}/system/upload-pfp`, { + method: "POST", + body: formData, + headers: baseHeaders(), + }) + .then((res) => { + if (!res.ok) throw new Error("Error uploading pfp."); + return { success: true, error: null }; + }) + .catch((e) => { + console.log(e); + return { success: false, error: e.message }; + }); + }, uploadLogo: async function (formData) { return await fetch(`${API_BASE}/system/upload-logo`, { method: "POST", diff --git a/server/endpoints/system.js b/server/endpoints/system.js index 72cf87e4d23..1139d7635bb 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -16,7 +16,11 @@ const { userFromSession, multiUserMode, } = require("../utils/http"); -const { setupDataImports, setupLogoUploads, setupPfpUploads } = require("../utils/files/multer"); +const { + setupDataImports, + setupLogoUploads, + setupPfpUploads, +} = require("../utils/files/multer"); const { v4 } = require("uuid"); const { SystemSettings } = require("../models/systemSettings"); const { User } = require("../models/user"); @@ -419,19 +423,19 @@ function systemEndpoints(app) { // Upload a pfp app.post( - "/system/pfp/:id", + "/system/upload-pfp", [validatedRequest, flexUserRoleValid], - handlePfpUploads.single("pfp"), + handlePfpUploads.single("file"), async function (request, response) { try { - const { id } = request.params; + const user = await userFromSession(request, response); const { buffer, size, mime } = fetchPfp( - path.resolve(__dirname, `../../storage/assets/pfp/${id}`) + path.resolve(__dirname, `../../storage/assets/pfp/${user.id}`) ); response.writeHead(200, { "Content-Type": mime || "image/png", "Content-Disposition": `attachment; filename=${path.basename( - id + user.id )}.png`, "Content-Length": size, }); diff --git a/server/storage/anythingllm.db-journal b/server/storage/anythingllm.db-journal deleted file mode 100644 index a3c257f7bcd8e62a4cc6a9874916c559b37ab6e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12824 zcmeHM&2Jk;6!+%KiQ6^Y!Av z@@!zpybu_oB3?$Ep%pYtF&KhKL6bOC5E11SQxXwTiE4-%5sAoiR57W_t61SoLsCUa zQ7sK#EQ=_zVOXkYX@+Qth?6-Y7`!Pe@L(`czJ2t;yM1#w#BoRzk#4tazbAP8V0mU} zk03}QSg#cnL6E^7A{R8G(Sm|4nexOG5GM$xYH(Cg6vT-HQG_i~HDyyVppKVSqG`ks zB~ukRYI2I9$(WN&OO+(biBvtLA~+M{ZO@S36)h=jmV zhD8d7A&~;sEXC9~6&VJRWRsKO(j*NbOO%KLFk(Yh6hkouO*TwHRCrF507@DBrC@CE zf@sJxMHVlr%#+XFxOzUYhtqh3G^8Ri#0Lb+^Y;Bk2yABOoekTktkv!`vCj&rmr@Lq zdXr^A^gYN`xD>d8w&&rRgDWab^lLmoQDR?%G03P%F5HT`x#~Nm-LqlYjc0i z{yy{S%HPNLPb8-ofx`=D(%DOwn8%Ed4TpZz?(BOmHmNs!I$Nrhi}f;FFJ9Xyv$>%l z$F62rC^?(t*h;ltzEQ5R)vY>PZES3?+qKGOv9`;;UEXDjjrvxl3VJrn)%se$Qwuj~ zj;)t(Li?~!DDj-`-ek#LYWnR?9f#tuz1gAIr=;lT*mW?pUfC=URI=;k>&3=Koh>zL zH5gnEq&xNE=62Y*?b24!uG6NQV_}{fW7Tij9*lQy!t5foJQsFwu;({iGOZ!oop#fO zi3GT0@(<2u(%C|RIo$1ghdO4vN%bc6JiG@!ioKtSc`7EJ&R<=Rb|!nJLp`_M@{aLl zyF+NF(jxTZ0gO}m4N!S)sfWtLd=C}UHoHyQ^7Vdnc=7klI4W`Rbf|!~3Pk^;Fz%ak zoOG(7J$bMJdd^}xo6RzhlpcD4TfN@DrE#2kb<=?ps2DEu(CaStg3aX^&^m1=9!G;E ze3?)ScOCz7s0qx5#Y^a<-6vX~(enK7;9g6ovsbP#4_A6{d-pt_Hg%8szTMjMVy}zi zE{I8|bB2Qr>KqfP*y)C-Pqj3|&QnF_hYRWKg$vB1D?PaE)?FYrUU*B7!xPp{2aDMz z@qmlM%g27#iy<})#vt^-g;6k?4z`<=TpNBYUIdh3*p6r&eA!+A*bmWsIx7gwCrdrB z2VZtQ+9QW7U|6*>Ib-Wkyb0lpd#xIDX6h2Y*O1JkPkL#kCu^`NS;9dIhPRNN^Q*UOpw_S{0So%IQc zcgKO{3A@>yE^Ps69Dki3i8h7-_QX3bXj9#8R0y6o-TTD8O5c62?0i(Me+iPOIY43HgZv*2xS~D{a z*3kxIum$8jR(eEcl)A^bxp|wK`(C%18}B!X|5$L2_ij_7V*q+sB8ncOJD82$U)f>9 zNeZ3z9z^Sr5;P5!0ef&@Km(imqoc-#^nBcb^TKG0Kxx!!u&)gEdb8W<1O(HE9UeM2 z-EcVUx{eKs;FIopy4miwdNYl-mqv95mSTDRs6sB2FU>4u-$P8w4w!McHgMN(_uloR zj?>Z6y4n7@@?82?_vS7PzwfkakJ(0MDPB1-Oztz-X2_$piF0P9g73@&Lh4G)W#HNE3`5Xrq%?bZGt8 fAEA;w0Hl77oT#B9$pcLI4e`|H0Y3lgryu_Tsg{^! diff --git a/server/utils/files/multer.js b/server/utils/files/multer.js index 8a4f73bd82f..24afaef49e7 100644 --- a/server/utils/files/multer.js +++ b/server/utils/files/multer.js @@ -65,7 +65,7 @@ function setupPfpUploads() { }, }); - return { handleLogoUploads: multer({ storage }) }; + return { handlePfpUploads: multer({ storage }) }; } module.exports = { From 3e4693079ce3c8ba3704648a32531c64b9cb376f Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Wed, 29 Nov 2023 17:56:08 -0800 Subject: [PATCH 10/17] edit account menu complete with change username/password and upload profile picture --- frontend/src/components/UserIcon/index.jsx | 29 ++- frontend/src/components/UserMenu/index.jsx | 271 ++++++++++++--------- frontend/src/hooks/usePfp.js | 21 +- frontend/src/models/system.js | 45 ++++ server/endpoints/system.js | 139 +++++++++-- server/utils/files/multer.js | 9 +- server/utils/files/pfp.js | 62 +---- 7 files changed, 374 insertions(+), 202 deletions(-) diff --git a/frontend/src/components/UserIcon/index.jsx b/frontend/src/components/UserIcon/index.jsx index 40694606d33..6cc9b57d042 100644 --- a/frontend/src/components/UserIcon/index.jsx +++ b/frontend/src/components/UserIcon/index.jsx @@ -1,32 +1,35 @@ import React, { useRef, useEffect } from "react"; import JAZZ from "@metamask/jazzicon"; +import usePfp from "../../hooks/usePfp"; export default function Jazzicon({ size = 10, user, role }) { + const { pfp } = usePfp(); const divRef = useRef(null); const seed = user?.uid ? toPseudoRandomInteger(user.uid) : Math.floor(100000 + Math.random() * 900000); - const result = JAZZ(size, seed); useEffect(() => { - if (!divRef || !divRef.current) return null; + if (!divRef.current || (role === "user" && pfp)) return; + const result = JAZZ(size, seed); divRef.current.appendChild(result); - }, []); // eslint-disable-line react-hooks/exhaustive-deps + }, [pfp, role, seed, size]); return ( -
+
+
+ {role === "user" && pfp && ( + User profile picture + )} +
); } function toPseudoRandomInteger(uidString = "") { - var numberArray = [uidString.length]; - for (var i = 0; i < uidString.length; i++) { - numberArray[i] = uidString.charCodeAt(i); - } - - return numberArray.reduce((a, b) => a + b, 0); + return uidString.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0); } diff --git a/frontend/src/components/UserMenu/index.jsx b/frontend/src/components/UserMenu/index.jsx index 7cfad0e50e2..2e37fe07f5b 100644 --- a/frontend/src/components/UserMenu/index.jsx +++ b/frontend/src/components/UserMenu/index.jsx @@ -7,6 +7,7 @@ import { userFromStorage } from "../../utils/request"; import useUser from "../../hooks/useUser"; import System from "../../models/system"; import showToast from "../../utils/toast"; +import usePfp from "../../hooks/usePfp"; export default function UserMenu({ children }) { if (isMobile) return <>{children}; @@ -23,15 +24,27 @@ function useLoginMode() { const user = !!window.localStorage.getItem(AUTH_USER); const token = !!window.localStorage.getItem(AUTH_TOKEN); - console.log(useUser()); - if (user && token) return "multi"; if (!user && token) return "single"; return null; } function userDisplay() { + const { pfp } = usePfp(); const user = userFromStorage(); + + if (pfp) { + return ( +
+ User profile picture +
+ ); + } + return user?.username?.slice(0, 2) || "AA"; } @@ -87,12 +100,14 @@ function UserButton() { > Support - + {mode === "multi" && ( + + )} +
+
+
+
+ + {pfp && ( + + )} +
+
+
+
+ + +
+
+ + +
+
+
+
- console.log("submitted")}> -
-
-
-
- - -
-
-
-
-
-
-
- - -
-
- - -
-
-
-
- - -
-
-
+ ); diff --git a/frontend/src/hooks/usePfp.js b/frontend/src/hooks/usePfp.js index e4ecd5e877e..99cae6999e9 100644 --- a/frontend/src/hooks/usePfp.js +++ b/frontend/src/hooks/usePfp.js @@ -1,22 +1,23 @@ import { useEffect, useState } from "react"; import System from "../models/system"; -import AnythingLLM from "../media/logo/anything-llm.png"; +import useUser from "./useUser"; -export default function useLogo() { - const [logo, setLogo] = useState(""); +export default function usePfp() { + const [pfp, setPfp] = useState(null); + const { user } = useUser(); useEffect(() => { - async function fetchInstanceLogo() { + async function fetchPfp() { try { - const logoURL = await System.fetchLogo(); - logoURL ? setLogo(logoURL) : setLogo(AnythingLLM); + const pfpUrl = await System.fetchPfp(user.id); + setPfp(pfpUrl); } catch (err) { - setLogo(AnythingLLM); - console.error("Failed to fetch logo:", err); + setPfp(null); + console.error("Failed to fetch pfp:", err); } } - fetchInstanceLogo(); + fetchPfp(); }, []); - return { logo }; + return { pfp }; } diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js index fc220c19dd4..364b4409937 100644 --- a/frontend/src/models/system.js +++ b/frontend/src/models/system.js @@ -215,6 +215,39 @@ const System = { return null; }); }, + fetchPfp: async function (id) { + return await fetch(`${API_BASE}/system/pfp/${id}`, { + method: "GET", + cache: "no-cache", + }) + .then((res) => { + if (res.ok) { + if (res.status === 204) return null; + return res.blob(); + } + throw new Error("Failed to fetch pfp."); + }) + .then((blob) => (blob ? URL.createObjectURL(blob) : null)) + .catch((e) => { + console.log(e); + return null; + }); + }, + removePfp: async function (id) { + return await fetch(`${API_BASE}/system/remove-pfp`, { + method: "DELETE", + headers: baseHeaders(), + }) + .then((res) => { + if (res.ok) return { success: true, error: null }; + throw new Error("Failed to remove pfp."); + }) + .catch((e) => { + console.log(e); + return { success: false, error: e.message }; + }); + }, + isDefaultLogo: async function () { return await fetch(`${API_BASE}/system/is-default-logo`, { method: "GET", @@ -389,6 +422,18 @@ const System = { return null; }); }, + updateUser: async (data) => { + return await fetch(`${API_BASE}/system/user`, { + method: "POST", + headers: baseHeaders(), + body: JSON.stringify(data), + }) + .then((res) => res.json()) + .catch((e) => { + console.error(e); + return { success: false, error: e.message }; + }); + } }; export default System; diff --git a/server/endpoints/system.js b/server/endpoints/system.js index 1139d7635bb..e1fa820d849 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -46,7 +46,7 @@ const { getCustomModels } = require("../utils/helpers/customModels"); const { WorkspaceChats } = require("../models/workspaceChats"); const { Workspace } = require("../models/workspace"); const { flexUserRoleValid } = require("../utils/middleware/multiUserProtected"); -const { fetchPfp } = require("../utils/files/pfp"); +const { fetchPfp, determinePfpFilepath } = require("../utils/files/pfp"); function systemEndpoints(app) { if (!app) return; @@ -421,7 +421,31 @@ function systemEndpoints(app) { } }); - // Upload a pfp + app.get("/system/pfp/:id", async function (request, response) { + try { + const { id } = request.params; + const pfpPath = await determinePfpFilepath(id); + + if (!pfpPath) { + response.status(204).end(); + return; + } + + const { buffer, size, mime } = fetchPfp(pfpPath); + + response.writeHead(200, { + "Content-Type": mime || "image/png", + "Content-Disposition": `attachment; filename=${path.basename(pfpPath)}`, + "Content-Length": size, + }); + response.end(Buffer.from(buffer, "base64")); + return; + } catch (error) { + console.error("Error processing the logo request:", error); + response.status(500).json({ message: "Internal server error" }); + } + }); + app.post( "/system/upload-pfp", [validatedRequest, flexUserRoleValid], @@ -429,20 +453,69 @@ function systemEndpoints(app) { async function (request, response) { try { const user = await userFromSession(request, response); - const { buffer, size, mime } = fetchPfp( - path.resolve(__dirname, `../../storage/assets/pfp/${user.id}`) - ); - response.writeHead(200, { - "Content-Type": mime || "image/png", - "Content-Disposition": `attachment; filename=${path.basename( - user.id - )}.png`, - "Content-Length": size, + const uploadedFileName = request.randomFileName; + + if (!uploadedFileName) { + return response.status(400).json({ message: "File upload failed." }); + } + + const userRecord = await User.get({ id: user.id }); + const oldPfpFilename = userRecord.pfpFilename; + console.log("oldPfpFilename", oldPfpFilename); + if (oldPfpFilename) { + const oldPfpPath = path.join( + __dirname, + `../storage/assets/pfp/${oldPfpFilename}` + ); + + if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath); + } + + const { success, error } = await User.update(user.id, { + pfpFilename: uploadedFileName, + }); + + return response.status(success ? 200 : 500).json({ + message: success + ? "Profile picture uploaded successfully." + : error || "Failed to update with new profile picture.", + }); + } catch (error) { + console.error("Error processing the profile picture upload:", error); + response.status(500).json({ message: "Internal server error" }); + } + } + ); + + app.delete( + "/system/remove-pfp", + [validatedRequest, flexUserRoleValid], + async function (request, response) { + try { + const user = await userFromSession(request, response); + const userRecord = await User.get({ id: user.id }); + const oldPfpFilename = userRecord.pfpFilename; + console.log("oldPfpFilename", oldPfpFilename); + if (oldPfpFilename) { + const oldPfpPath = path.join( + __dirname, + `../storage/assets/pfp/${oldPfpFilename}` + ); + + if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath); + } + + const { success, error } = await User.update(user.id, { + pfpFilename: null, + }); + + return response.status(success ? 200 : 500).json({ + message: success + ? "Profile picture removed successfully." + : error || "Failed to remove profile picture.", }); - response.end(Buffer.from(buffer, "base64")); - return; } catch (error) { - console.error("Error processing the logo request:", error); + console.error("Error processing the profile picture removal:", error); response.status(500).json({ message: "Internal server error" }); } } @@ -771,6 +844,44 @@ function systemEndpoints(app) { } } ); + + + app.post( + "/system/user", + [validatedRequest], + async (request, response) => { + try { + const sessionUser = await userFromSession(request, response); + const { username, password } = reqBody(request); + const id = Number(sessionUser.id); + + if (!id) { + response.status(400).json({ success: false, error: "Invalid user ID" }); + return; + } + + const updates = {}; + if (username) { + updates.username = username; + } + if (password) { + updates.password = password; + } + + if (Object.keys(updates).length === 0) { + response.status(400).json({ success: false, error: "No updates provided" }); + return; + } + + const { success, error } = await User.update(id, updates); + response.status(200).json({ success, error }); + } catch (e) { + console.error(e); + response.sendStatus(500).end(); + } + } + ); + } module.exports = { systemEndpoints }; diff --git a/server/utils/files/multer.js b/server/utils/files/multer.js index 24afaef49e7..ca2cf345fb0 100644 --- a/server/utils/files/multer.js +++ b/server/utils/files/multer.js @@ -1,6 +1,8 @@ const multer = require("multer"); const path = require("path"); const fs = require("fs"); +const { v4 } = require("uuid"); +const { userFromSession } = require("../http"); function setupMulter() { // Handle File uploads for auto-uploading. @@ -53,15 +55,16 @@ function setupLogoUploads() { } function setupPfpUploads() { - // Handle pfp uploads. const storage = multer.diskStorage({ destination: function (_, _, cb) { const uploadOutput = path.resolve(__dirname, `../../storage/assets/pfp`); fs.mkdirSync(uploadOutput, { recursive: true }); return cb(null, uploadOutput); }, - filename: function (_, file, cb) { - cb(null, file.originalname); + filename: function (req, file, cb) { + const randomFileName = `${v4()}${path.extname(file.originalname)}`; + req.randomFileName = randomFileName; + cb(null, randomFileName); }, }); diff --git a/server/utils/files/pfp.js b/server/utils/files/pfp.js index d412ac7bdd6..6796ce43677 100644 --- a/server/utils/files/pfp.js +++ b/server/utils/files/pfp.js @@ -1,30 +1,7 @@ const path = require("path"); const fs = require("fs"); const { getType } = require("mime"); -const { v4 } = require("uuid"); -const { SystemSettings } = require("../../models/systemSettings"); -const LOGO_FILENAME = "anything-llm.png"; - -function validFilename(newFilename = "") { - return ![LOGO_FILENAME].includes(newFilename); -} - -function getDefaultFilename() { - return LOGO_FILENAME; -} - -async function determineLogoFilepath(defaultFilename = LOGO_FILENAME) { - const currentLogoFilename = await SystemSettings.currentLogoFilename(); - const basePath = path.join(__dirname, "../../storage/assets"); - const defaultFilepath = path.join(basePath, defaultFilename); - - if (currentLogoFilename && validFilename(currentLogoFilename)) { - customLogoPath = path.join(basePath, currentLogoFilename); - return fs.existsSync(customLogoPath) ? customLogoPath : defaultFilepath; - } - - return defaultFilepath; -} +const { User } = require("../../models/user"); function fetchPfp(pfpPath) { const mime = getType(pfpPath); @@ -36,35 +13,22 @@ function fetchPfp(pfpPath) { }; } -async function renameLogoFile(originalFilename = null) { - const extname = path.extname(originalFilename) || ".png"; - const newFilename = `${v4()}${extname}`; - const originalFilepath = path.join( - __dirname, - `../../storage/assets/${originalFilename}` - ); - const outputFilepath = path.join( - __dirname, - `../../storage/assets/${newFilename}` - ); +async function determinePfpFilepath(id) { + const numberId = Number(id); + const user = await User.get({ id: numberId }); + const pfpFilename = user.pfpFilename; + if (!pfpFilename) return null; + const basePath = path.join(__dirname, "../../storage/assets/pfp"); + const pfpFilepath = path.join(basePath, pfpFilename); - fs.renameSync(originalFilepath, outputFilepath); - return newFilename; -} + if (pfpFilename && fs.existsSync(pfpFilepath)) { + return pfpFilepath; + } -async function removeCustomLogo(logoFilename = LOGO_FILENAME) { - if (!logoFilename || !validFilename(logoFilename)) return false; - const logoPath = path.join(__dirname, `../../storage/assets/${logoFilename}`); - if (fs.existsSync(logoPath)) fs.unlinkSync(logoPath); - return true; + return null; } module.exports = { fetchPfp, - renameLogoFile, - removeCustomLogo, - validFilename, - getDefaultFilename, - determineLogoFilepath, - LOGO_FILENAME, + determinePfpFilepath, }; From 224442b897e8a9a1560710cb0578d6cf339cb982 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Wed, 29 Nov 2023 18:08:36 -0800 Subject: [PATCH 11/17] add pfp context to update all instances of usePfp hook on update --- frontend/src/App.jsx | 3 +++ frontend/src/PfpContext.jsx | 30 ++++++++++++++++++++++ frontend/src/components/UserMenu/index.jsx | 6 ++++- frontend/src/hooks/usePfp.js | 25 ++++-------------- 4 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 frontend/src/PfpContext.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 2b8a645b3ad..9959867256e 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -8,6 +8,7 @@ import PrivateRoute, { import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import Login from "./pages/Login"; +import { PfpProvider } from "./PfpContext"; const Main = lazy(() => import("./pages/Main")); const InvitePage = lazy(() => import("./pages/Invite")); @@ -42,6 +43,7 @@ export default function App() { return ( }> + } /> } /> @@ -105,6 +107,7 @@ export default function App() { } /> + ); diff --git a/frontend/src/PfpContext.jsx b/frontend/src/PfpContext.jsx new file mode 100644 index 00000000000..76c70f796f6 --- /dev/null +++ b/frontend/src/PfpContext.jsx @@ -0,0 +1,30 @@ +import React, { createContext, useState, useContext, useEffect } from 'react'; +import useUser from './hooks/useUser'; +import System from './models/system'; + + +export const PfpContext = createContext(); + +export function PfpProvider({ children }) { + const [pfp, setPfp] = useState(null); + const { user } = useUser(); + + useEffect(() => { + async function fetchPfp() { + try { + const pfpUrl = await System.fetchPfp(user.id); + setPfp(pfpUrl); + } catch (err) { + setPfp(null); + console.error("Failed to fetch pfp:", err); + } + } + fetchPfp(); + }, [user.id]); + + return ( + + {children} + + ); +} diff --git a/frontend/src/components/UserMenu/index.jsx b/frontend/src/components/UserMenu/index.jsx index 2e37fe07f5b..9f530cf1cca 100644 --- a/frontend/src/components/UserMenu/index.jsx +++ b/frontend/src/components/UserMenu/index.jsx @@ -130,7 +130,7 @@ function UserButton() { function AccountModal() { const { user } = useUser(); - const { pfp } = usePfp(); + const { pfp, setPfp } = usePfp(); const hideModal = () => { document.getElementById("account-modal")?.close(); }; @@ -147,6 +147,9 @@ function AccountModal() { return; } + const pfpUrl = await System.fetchPfp(user.id); + setPfp(pfpUrl); + showToast("Profile picture uploaded successfully.", "success"); }; @@ -157,6 +160,7 @@ function AccountModal() { return; } + setPfp(null); showToast("Profile picture removed successfully.", "success"); }; diff --git a/frontend/src/hooks/usePfp.js b/frontend/src/hooks/usePfp.js index 99cae6999e9..a29864c3670 100644 --- a/frontend/src/hooks/usePfp.js +++ b/frontend/src/hooks/usePfp.js @@ -1,23 +1,8 @@ -import { useEffect, useState } from "react"; -import System from "../models/system"; -import useUser from "./useUser"; +import { useContext } from "react"; +import { PfpContext } from "../PfpContext"; -export default function usePfp() { - const [pfp, setPfp] = useState(null); - const { user } = useUser(); - - useEffect(() => { - async function fetchPfp() { - try { - const pfpUrl = await System.fetchPfp(user.id); - setPfp(pfpUrl); - } catch (err) { - setPfp(null); - console.error("Failed to fetch pfp:", err); - } - } - fetchPfp(); - }, []); - return { pfp }; +export default function usePfp() { + const { pfp, setPfp } = useContext(PfpContext); + return { pfp, setPfp }; } From b3f92ecd46fce9c5b729920507c33d54fa4f5a99 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Wed, 29 Nov 2023 18:09:24 -0800 Subject: [PATCH 12/17] linting --- frontend/src/App.jsx | 124 ++++++++++----------- frontend/src/PfpContext.jsx | 7 +- frontend/src/components/UserMenu/index.jsx | 3 +- frontend/src/hooks/usePfp.js | 1 - frontend/src/models/system.js | 2 +- server/endpoints/system.js | 60 +++++----- 6 files changed, 95 insertions(+), 102 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 9959867256e..26295579d76 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -44,69 +44,69 @@ export default function App() { }> - - } /> - } /> - } - /> - } /> + + } /> + } /> + } + /> + } /> - {/* Admin */} - } - /> - } - /> - } - /> - {/* Manager */} - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - {/* Onboarding Flow */} - } /> - - + {/* Admin */} + } + /> + } + /> + } + /> + {/* Manager */} + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + {/* Onboarding Flow */} + } /> + + diff --git a/frontend/src/PfpContext.jsx b/frontend/src/PfpContext.jsx index 76c70f796f6..c1cf59267d4 100644 --- a/frontend/src/PfpContext.jsx +++ b/frontend/src/PfpContext.jsx @@ -1,7 +1,6 @@ -import React, { createContext, useState, useContext, useEffect } from 'react'; -import useUser from './hooks/useUser'; -import System from './models/system'; - +import React, { createContext, useState, useContext, useEffect } from "react"; +import useUser from "./hooks/useUser"; +import System from "./models/system"; export const PfpContext = createContext(); diff --git a/frontend/src/components/UserMenu/index.jsx b/frontend/src/components/UserMenu/index.jsx index 9f530cf1cca..60bef15dad9 100644 --- a/frontend/src/components/UserMenu/index.jsx +++ b/frontend/src/components/UserMenu/index.jsx @@ -188,8 +188,7 @@ function AccountModal() { } else { showToast(`Failed to update user: ${error}`, "error"); } -}; - + }; return ( diff --git a/frontend/src/hooks/usePfp.js b/frontend/src/hooks/usePfp.js index a29864c3670..36c54497fdf 100644 --- a/frontend/src/hooks/usePfp.js +++ b/frontend/src/hooks/usePfp.js @@ -1,7 +1,6 @@ import { useContext } from "react"; import { PfpContext } from "../PfpContext"; - export default function usePfp() { const { pfp, setPfp } = useContext(PfpContext); return { pfp, setPfp }; diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js index 364b4409937..4c66dd2beb0 100644 --- a/frontend/src/models/system.js +++ b/frontend/src/models/system.js @@ -433,7 +433,7 @@ const System = { console.error(e); return { success: false, error: e.message }; }); - } + }, }; export default System; diff --git a/server/endpoints/system.js b/server/endpoints/system.js index e1fa820d849..fb7e98c559c 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -845,43 +845,39 @@ function systemEndpoints(app) { } ); + app.post("/system/user", [validatedRequest], async (request, response) => { + try { + const sessionUser = await userFromSession(request, response); + const { username, password } = reqBody(request); + const id = Number(sessionUser.id); - app.post( - "/system/user", - [validatedRequest], - async (request, response) => { - try { - const sessionUser = await userFromSession(request, response); - const { username, password } = reqBody(request); - const id = Number(sessionUser.id); - - if (!id) { - response.status(400).json({ success: false, error: "Invalid user ID" }); - return; - } - - const updates = {}; - if (username) { - updates.username = username; - } - if (password) { - updates.password = password; - } + if (!id) { + response.status(400).json({ success: false, error: "Invalid user ID" }); + return; + } - if (Object.keys(updates).length === 0) { - response.status(400).json({ success: false, error: "No updates provided" }); - return; - } + const updates = {}; + if (username) { + updates.username = username; + } + if (password) { + updates.password = password; + } - const { success, error } = await User.update(id, updates); - response.status(200).json({ success, error }); - } catch (e) { - console.error(e); - response.sendStatus(500).end(); + if (Object.keys(updates).length === 0) { + response + .status(400) + .json({ success: false, error: "No updates provided" }); + return; } - } - ); + const { success, error } = await User.update(id, updates); + response.status(200).json({ success, error }); + } catch (e) { + console.error(e); + response.sendStatus(500).end(); + } + }); } module.exports = { systemEndpoints }; From 9fcdf6e3a7729c0f904004ce08c5daa03cfc7d6d Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Wed, 29 Nov 2023 18:21:27 -0800 Subject: [PATCH 13/17] add context for logo change to immediately update logo --- frontend/src/App.jsx | 131 +++++++++--------- frontend/src/LogoContext.jsx | 28 ++++ frontend/src/hooks/useLogo.js | 23 +-- .../GeneralSettings/Appearance/index.jsx | 8 +- .../Steps/AppearanceSetup/index.jsx | 8 +- 5 files changed, 113 insertions(+), 85 deletions(-) create mode 100644 frontend/src/LogoContext.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 26295579d76..71552ba95ee 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -9,6 +9,7 @@ import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import Login from "./pages/Login"; import { PfpProvider } from "./PfpContext"; +import { LogoProvider } from "./LogoContext"; const Main = lazy(() => import("./pages/Main")); const InvitePage = lazy(() => import("./pages/Invite")); @@ -43,71 +44,73 @@ export default function App() { return ( }> - - - } /> - } /> - } - /> - } /> + + + + } /> + } /> + } + /> + } /> - {/* Admin */} - } - /> - } - /> - } - /> - {/* Manager */} - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - {/* Onboarding Flow */} - } /> - - - + {/* Admin */} + } + /> + } + /> + } + /> + {/* Manager */} + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + {/* Onboarding Flow */} + } /> + + + + ); diff --git a/frontend/src/LogoContext.jsx b/frontend/src/LogoContext.jsx new file mode 100644 index 00000000000..6818967b8c0 --- /dev/null +++ b/frontend/src/LogoContext.jsx @@ -0,0 +1,28 @@ +import { createContext, useEffect, useState } from "react"; +import AnythingLLM from "./media/logo/anything-llm.png"; +import System from "./models/system"; + +export const LogoContext = createContext(); + +export function LogoProvider({ children }) { + const [logo, setLogo] = useState(""); + + useEffect(() => { + async function fetchInstanceLogo() { + try { + const logoURL = await System.fetchLogo(); + logoURL ? setLogo(logoURL) : setLogo(AnythingLLM); + } catch (err) { + setLogo(AnythingLLM); + console.error("Failed to fetch logo:", err); + } + } + fetchInstanceLogo(); + }, []); + + return ( + + {children} + + ); +} diff --git a/frontend/src/hooks/useLogo.js b/frontend/src/hooks/useLogo.js index e4ecd5e877e..4834b7a8e1f 100644 --- a/frontend/src/hooks/useLogo.js +++ b/frontend/src/hooks/useLogo.js @@ -1,22 +1,7 @@ -import { useEffect, useState } from "react"; -import System from "../models/system"; -import AnythingLLM from "../media/logo/anything-llm.png"; +import { useContext } from "react"; +import { LogoContext } from "../LogoContext"; export default function useLogo() { - const [logo, setLogo] = useState(""); - - useEffect(() => { - async function fetchInstanceLogo() { - try { - const logoURL = await System.fetchLogo(); - logoURL ? setLogo(logoURL) : setLogo(AnythingLLM); - } catch (err) { - setLogo(AnythingLLM); - console.error("Failed to fetch logo:", err); - } - } - fetchInstanceLogo(); - }, []); - - return { logo }; + const { logo, setLogo } = useContext(LogoContext); + return { logo, setLogo }; } diff --git a/frontend/src/pages/GeneralSettings/Appearance/index.jsx b/frontend/src/pages/GeneralSettings/Appearance/index.jsx index 107e57caeaa..0ee38f70adc 100644 --- a/frontend/src/pages/GeneralSettings/Appearance/index.jsx +++ b/frontend/src/pages/GeneralSettings/Appearance/index.jsx @@ -12,7 +12,7 @@ import showToast from "../../../utils/toast"; import { Plus } from "@phosphor-icons/react"; export default function Appearance() { - const { logo: _initLogo } = useLogo(); + const { logo: _initLogo, setLogo: _setLogo } = useLogo(); const [logo, setLogo] = useState(""); const [hasChanges, setHasChanges] = useState(false); const [messages, setMessages] = useState([]); @@ -51,6 +51,9 @@ export default function Appearance() { return; } + const logoURL = await System.fetchLogo(); + _setLogo(logoURL); + showToast("Image uploaded successfully.", "success"); setIsDefaultLogo(false); }; @@ -69,6 +72,9 @@ export default function Appearance() { return; } + const logoURL = await System.fetchLogo(); + _setLogo(logoURL); + showToast("Image successfully removed.", "success"); }; diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/AppearanceSetup/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/AppearanceSetup/index.jsx index ed5c5579904..ec5e8325d6b 100644 --- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/AppearanceSetup/index.jsx +++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/AppearanceSetup/index.jsx @@ -6,7 +6,7 @@ import { Plus } from "@phosphor-icons/react"; import showToast from "../../../../../utils/toast"; function AppearanceSetup({ prevStep, nextStep }) { - const { logo: _initLogo } = useLogo(); + const { logo: _initLogo, setLogo: _setLogo } = useLogo(); const [logo, setLogo] = useState(""); const [isDefaultLogo, setIsDefaultLogo] = useState(true); @@ -35,6 +35,9 @@ function AppearanceSetup({ prevStep, nextStep }) { return; } + const logoURL = await System.fetchLogo(); + _setLogo(logoURL); + showToast("Image uploaded successfully.", "success"); setIsDefaultLogo(false); }; @@ -53,6 +56,9 @@ function AppearanceSetup({ prevStep, nextStep }) { return; } + const logoURL = await System.fetchLogo(); + _setLogo(logoURL); + showToast("Image successfully removed.", "success"); }; From a50848ac1ae7c53775cb5a23b13871d3e8695b18 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Mon, 4 Dec 2023 12:50:19 -0800 Subject: [PATCH 14/17] fix div with bullet points to use list-disc instead --- .../Steps/DataHandling/index.jsx | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx index 5d031cd7df4..d988da7975c 100644 --- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx +++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx @@ -165,13 +165,11 @@ function DataHandling({ nextStep, prevStep, currentStep }) { {LLM_SELECTION_PRIVACY[llmChoice].name}

-
+
    {LLM_SELECTION_PRIVACY[llmChoice].description.map((desc) => ( -

    - {desc} -

    +
  • {desc}
  • ))} -
+
Embedding Engine
@@ -185,15 +183,13 @@ function DataHandling({ nextStep, prevStep, currentStep }) { {EMBEDDING_ENGINE_PRIVACY[embeddingEngine].name}

-
+
    {EMBEDDING_ENGINE_PRIVACY[embeddingEngine].description.map( (desc) => ( -

    - {desc} -

    +
  • {desc}
  • ) )} -
+
@@ -208,13 +204,11 @@ function DataHandling({ nextStep, prevStep, currentStep }) { {VECTOR_DB_PRIVACY[vectorDb].name}

-
+
    {VECTOR_DB_PRIVACY[vectorDb].description.map((desc) => ( -

    - {desc} -

    +
  • {desc}
  • ))} -
+
From 69695c26c1951006bf15e4b63af66a7cc0aadb08 Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Thu, 7 Dec 2023 13:50:27 -0800 Subject: [PATCH 15/17] fix: small changes --- frontend/src/PfpContext.jsx | 5 ++- frontend/src/components/UserMenu/index.jsx | 44 +++++++++++----------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/frontend/src/PfpContext.jsx b/frontend/src/PfpContext.jsx index c1cf59267d4..3d60d559d2b 100644 --- a/frontend/src/PfpContext.jsx +++ b/frontend/src/PfpContext.jsx @@ -1,4 +1,4 @@ -import React, { createContext, useState, useContext, useEffect } from "react"; +import React, { createContext, useState, useEffect } from "react"; import useUser from "./hooks/useUser"; import System from "./models/system"; @@ -10,6 +10,7 @@ export function PfpProvider({ children }) { useEffect(() => { async function fetchPfp() { + if (!user?.id) return; try { const pfpUrl = await System.fetchPfp(user.id); setPfp(pfpUrl); @@ -19,7 +20,7 @@ export function PfpProvider({ children }) { } } fetchPfp(); - }, [user.id]); + }, [user?.id]); return ( diff --git a/frontend/src/components/UserMenu/index.jsx b/frontend/src/components/UserMenu/index.jsx index 1ee23a984b0..61e111da4e1 100644 --- a/frontend/src/components/UserMenu/index.jsx +++ b/frontend/src/components/UserMenu/index.jsx @@ -49,7 +49,9 @@ function userDisplay() { } function UserButton() { + const { user } = useUser(); const [showMenu, setShowMenu] = useState(false); + const [showAccountSettings, setShowAccountSettings] = useState(false); const mode = useLoginMode(); const menuRef = useRef(); const buttonRef = useRef(); @@ -64,7 +66,7 @@ function UserButton() { }; const handleOpenAccountModal = () => { - document.getElementById("account-modal")?.showModal(); + setShowAccountSettings(true); setShowMenu(false); }; @@ -94,13 +96,7 @@ function UserButton() { className="w-fit rounded-lg absolute top-12 right-0 bg-sidebar p-4 flex items-center-justify-center" >
- - Support - - {mode === "multi" && ( + {mode === "multi" && !!user && (
)} - + {user && showAccountSettings && ( + setShowAccountSettings(false)} + /> + )} ); } -function AccountModal() { - const { user } = useUser(); +function AccountModal({ user, hideModal }) { const { pfp, setPfp } = usePfp(); - const hideModal = () => { - document.getElementById("account-modal")?.close(); - }; - const handleFileUpload = async (event) => { const file = event.target.files[0]; if (!file) return false; @@ -174,9 +176,7 @@ function AccountModal() { data[key] = value; } - console.log(data); const { success, error } = await System.updateUser(data); - if (success) { let storedUser = JSON.parse(localStorage.getItem(AUTH_USER)); @@ -191,7 +191,10 @@ function AccountModal() { }; return ( - +

Edit Account

@@ -271,10 +274,9 @@ function AccountModal() {
@@ -295,6 +297,6 @@ function AccountModal() {
-
+ ); } From dd93db4ae79358528669eb0ce77d66493ebee695 Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Thu, 7 Dec 2023 13:57:45 -0800 Subject: [PATCH 16/17] update multer file storage locations --- server/utils/files/multer.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/server/utils/files/multer.js b/server/utils/files/multer.js index ca2cf345fb0..9c2967e0175 100644 --- a/server/utils/files/multer.js +++ b/server/utils/files/multer.js @@ -2,7 +2,6 @@ const multer = require("multer"); const path = require("path"); const fs = require("fs"); const { v4 } = require("uuid"); -const { userFromSession } = require("../http"); function setupMulter() { // Handle File uploads for auto-uploading. @@ -42,7 +41,10 @@ function setupLogoUploads() { // Handle Logo uploads. const storage = multer.diskStorage({ destination: function (_, _, cb) { - const uploadOutput = path.resolve(__dirname, `../../storage/assets`); + const uploadOutput = + process.env.NODE_ENV === "development" + ? path.resolve(__dirname, `../../storage/assets`) + : path.resolve(process.env.STORAGE_DIR, "assets"); fs.mkdirSync(uploadOutput, { recursive: true }); return cb(null, uploadOutput); }, @@ -57,7 +59,10 @@ function setupLogoUploads() { function setupPfpUploads() { const storage = multer.diskStorage({ destination: function (_, _, cb) { - const uploadOutput = path.resolve(__dirname, `../../storage/assets/pfp`); + const uploadOutput = + process.env.NODE_ENV === "development" + ? path.resolve(__dirname, `../../storage/assets/pfp`) + : path.resolve(process.env.STORAGE_DIR, "assets/pfp"); fs.mkdirSync(uploadOutput, { recursive: true }); return cb(null, uploadOutput); }, From 4d8ca636dfb782d4da1e222a056410cfb5e440d3 Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Thu, 7 Dec 2023 14:09:13 -0800 Subject: [PATCH 17/17] fix: use STORAGE_DIR for filepathing --- frontend/src/models/system.js | 7 ++----- server/endpoints/system.js | 15 ++++++++++++--- server/utils/files/logo.js | 1 + server/utils/files/pfp.js | 22 ++++++++++++++++------ 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js index 9ad82fe3007..79c203d9424 100644 --- a/frontend/src/models/system.js +++ b/frontend/src/models/system.js @@ -206,7 +206,7 @@ const System = { cache: "no-cache", }) .then((res) => { - if (res.ok) return res.blob(); + if (res.ok && res.status !== 204) return res.blob(); throw new Error("Failed to fetch logo!"); }) .then((blob) => URL.createObjectURL(blob)) @@ -221,10 +221,7 @@ const System = { cache: "no-cache", }) .then((res) => { - if (res.ok) { - if (res.status === 204) return null; - return res.blob(); - } + if (res.ok && res.status !== 204) return res.blob(); throw new Error("Failed to fetch pfp."); }) .then((blob) => (blob ? URL.createObjectURL(blob) : null)) diff --git a/server/endpoints/system.js b/server/endpoints/system.js index 9cb9baf2868..024bdd9957e 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -405,7 +405,12 @@ function systemEndpoints(app) { try { const defaultFilename = getDefaultFilename(); const logoPath = await determineLogoFilepath(defaultFilename); - const { buffer, size, mime } = fetchLogo(logoPath); + const { found, buffer, size, mime } = fetchLogo(logoPath); + if (!found) { + response.sendStatus(204).end(); + return; + } + response.writeHead(200, { "Content-Type": mime || "image/png", "Content-Disposition": `attachment; filename=${path.basename( @@ -427,11 +432,15 @@ function systemEndpoints(app) { const pfpPath = await determinePfpFilepath(id); if (!pfpPath) { - response.status(204).end(); + response.sendStatus(204).end(); return; } - const { buffer, size, mime } = fetchPfp(pfpPath); + const { found, buffer, size, mime } = fetchPfp(pfpPath); + if (!found) { + response.sendStatus(204).end(); + return; + } response.writeHead(200, { "Content-Type": mime || "image/png", diff --git a/server/utils/files/logo.js b/server/utils/files/logo.js index 14e8032f90b..eb4738b09d4 100644 --- a/server/utils/files/logo.js +++ b/server/utils/files/logo.js @@ -41,6 +41,7 @@ function fetchLogo(logoPath) { const mime = getType(logoPath); const buffer = fs.readFileSync(logoPath); return { + found: true, buffer, size: buffer.length, mime, diff --git a/server/utils/files/pfp.js b/server/utils/files/pfp.js index 6796ce43677..30c42a51935 100644 --- a/server/utils/files/pfp.js +++ b/server/utils/files/pfp.js @@ -4,9 +4,19 @@ const { getType } = require("mime"); const { User } = require("../../models/user"); function fetchPfp(pfpPath) { + if (!fs.existsSync(pfpPath)) { + return { + found: false, + buffer: null, + size: 0, + mime: "none/none", + }; + } + const mime = getType(pfpPath); const buffer = fs.readFileSync(pfpPath); return { + found: true, buffer, size: buffer.length, mime, @@ -18,14 +28,14 @@ async function determinePfpFilepath(id) { const user = await User.get({ id: numberId }); const pfpFilename = user.pfpFilename; if (!pfpFilename) return null; - const basePath = path.join(__dirname, "../../storage/assets/pfp"); - const pfpFilepath = path.join(basePath, pfpFilename); - if (pfpFilename && fs.existsSync(pfpFilepath)) { - return pfpFilepath; - } + const basePath = process.env.STORAGE_DIR + ? path.join(process.env.STORAGE_DIR, "assets/pfp") + : path.join(__dirname, "../../storage/assets/pfp"); + const pfpFilepath = path.join(basePath, pfpFilename); - return null; + if (!fs.existsSync(pfpFilepath)) return null; + return pfpFilepath; } module.exports = {