Skip to content

Commit

Permalink
Add user PFP support and context to logo (#408)
Browse files Browse the repository at this point in the history
* fix sizing of onboarding modals & lint

* fix extra scrolling on mobile onboarding flow

* added message to use desktop for onboarding

* linting

* add arrow to scroll to bottom (debounced) and fix chat scrolling to always scroll to very bottom on message history change

* fix for empty chat

* change mobile alert copy

* WIP adding PFP upload support

* WIP pfp for users

* edit account menu complete with change username/password and upload profile picture

* add pfp context to update all instances of usePfp hook on update

* linting

* add context for logo change to immediately update logo

* fix div with bullet points to use list-disc instead

* fix: small changes

* update multer file storage locations

* fix: use STORAGE_DIR for filepathing

---------

Co-authored-by: timothycarambat <[email protected]>
  • Loading branch information
shatfield4 and timothycarambat authored Dec 7, 2023
1 parent f48e6b1 commit fcb591d
Show file tree
Hide file tree
Showing 16 changed files with 656 additions and 101 deletions.
130 changes: 68 additions & 62 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import PrivateRoute, {
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"));
Expand Down Expand Up @@ -40,69 +42,73 @@ export default function App() {
return (
<Suspense fallback={<div />}>
<ContextWrapper>
<Routes>
<Route path="/" element={<PrivateRoute Component={Main} />} />
<Route path="/login" element={<Login />} />
<Route
path="/workspace/:slug"
element={<PrivateRoute Component={WorkspaceChat} />}
/>
<Route path="/accept-invite/:code" element={<InvitePage />} />
<LogoProvider>
<PfpProvider>
<Routes>
<Route path="/" element={<PrivateRoute Component={Main} />} />
<Route path="/login" element={<Login />} />
<Route
path="/workspace/:slug"
element={<PrivateRoute Component={WorkspaceChat} />}
/>
<Route path="/accept-invite/:code" element={<InvitePage />} />

{/* Admin */}
<Route
path="/settings/llm-preference"
element={<AdminRoute Component={GeneralLLMPreference} />}
/>
<Route
path="/settings/embedding-preference"
element={<AdminRoute Component={GeneralEmbeddingPreference} />}
/>
<Route
path="/settings/vector-database"
element={<AdminRoute Component={GeneralVectorDatabase} />}
/>
{/* Manager */}
<Route
path="/settings/export-import"
element={<ManagerRoute Component={GeneralExportImport} />}
/>
<Route
path="/settings/security"
element={<ManagerRoute Component={GeneralSecurity} />}
/>
<Route
path="/settings/appearance"
element={<ManagerRoute Component={GeneralAppearance} />}
/>
<Route
path="/settings/api-keys"
element={<ManagerRoute Component={GeneralApiKeys} />}
/>
<Route
path="/settings/workspace-chats"
element={<ManagerRoute Component={GeneralChats} />}
/>
<Route
path="/settings/system-preferences"
element={<ManagerRoute Component={AdminSystem} />}
/>
<Route
path="/settings/invites"
element={<ManagerRoute Component={AdminInvites} />}
/>
<Route
path="/settings/users"
element={<ManagerRoute Component={AdminUsers} />}
/>
<Route
path="/settings/workspaces"
element={<ManagerRoute Component={AdminWorkspaces} />}
/>
{/* Onboarding Flow */}
<Route path="/onboarding" element={<OnboardingFlow />} />
</Routes>
<ToastContainer />
{/* Admin */}
<Route
path="/settings/llm-preference"
element={<AdminRoute Component={GeneralLLMPreference} />}
/>
<Route
path="/settings/embedding-preference"
element={<AdminRoute Component={GeneralEmbeddingPreference} />}
/>
<Route
path="/settings/vector-database"
element={<AdminRoute Component={GeneralVectorDatabase} />}
/>
{/* Manager */}
<Route
path="/settings/export-import"
element={<ManagerRoute Component={GeneralExportImport} />}
/>
<Route
path="/settings/security"
element={<ManagerRoute Component={GeneralSecurity} />}
/>
<Route
path="/settings/appearance"
element={<ManagerRoute Component={GeneralAppearance} />}
/>
<Route
path="/settings/api-keys"
element={<ManagerRoute Component={GeneralApiKeys} />}
/>
<Route
path="/settings/workspace-chats"
element={<ManagerRoute Component={GeneralChats} />}
/>
<Route
path="/settings/system-preferences"
element={<ManagerRoute Component={AdminSystem} />}
/>
<Route
path="/settings/invites"
element={<ManagerRoute Component={AdminInvites} />}
/>
<Route
path="/settings/users"
element={<ManagerRoute Component={AdminUsers} />}
/>
<Route
path="/settings/workspaces"
element={<ManagerRoute Component={AdminWorkspaces} />}
/>
{/* Onboarding Flow */}
<Route path="/onboarding" element={<OnboardingFlow />} />
</Routes>
<ToastContainer />
</PfpProvider>
</LogoProvider>
</ContextWrapper>
</Suspense>
);
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/LogoContext.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<LogoContext.Provider value={{ logo, setLogo }}>
{children}
</LogoContext.Provider>
);
}
30 changes: 30 additions & 0 deletions frontend/src/PfpContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React, { createContext, useState, 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() {
if (!user?.id) return;
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 (
<PfpContext.Provider value={{ pfp, setPfp }}>
{children}
</PfpContext.Provider>
);
}
29 changes: 16 additions & 13 deletions frontend/src/components/UserIcon/index.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
className={`flex ${role === "user" ? "user-reply" : ""}`}
ref={divRef}
/>
<div className="relative w-[35px] h-[35px] rounded-full flex-shrink-0 overflow-hidden">
<div ref={divRef} />
{role === "user" && pfp && (
<img
src={pfp}
alt="User profile picture"
className="absolute top-0 left-0 w-full h-full object-cover rounded-full bg-white"
/>
)}
</div>
);
}

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);
}
Loading

0 comments on commit fcb591d

Please sign in to comment.