这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions frontend/src/LogoContext.jsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
import { createContext, useEffect, useState } from "react";
import AnythingLLM from "./media/logo/anything-llm.png";
import DefaultLoginLogo from "./media/illustrations/login-logo.svg";
import System from "./models/system";

export const LogoContext = createContext();

export function LogoProvider({ children }) {
const [logo, setLogo] = useState("");
const [loginLogo, setLoginLogo] = useState("");
const [isCustomLogo, setIsCustomLogo] = useState(false);

useEffect(() => {
async function fetchInstanceLogo() {
try {
const logoURL = await System.fetchLogo();
logoURL ? setLogo(logoURL) : setLogo(AnythingLLM);
const { isCustomLogo, logoURL } = await System.fetchLogo();
if (logoURL) {
setLogo(logoURL);
setLoginLogo(isCustomLogo ? logoURL : DefaultLoginLogo);
setIsCustomLogo(isCustomLogo);
} else {
setLogo(AnythingLLM);
setLoginLogo(DefaultLoginLogo);
setIsCustomLogo(false);
}
} catch (err) {
setLogo(AnythingLLM);
setLoginLogo(DefaultLoginLogo);
setIsCustomLogo(false);
console.error("Failed to fetch logo:", err);
}
}

fetchInstanceLogo();
}, []);

return (
<LogoContext.Provider value={{ logo, setLogo }}>
<LogoContext.Provider value={{ logo, setLogo, loginLogo, isCustomLogo }}>
{children}
</LogoContext.Provider>
);
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/components/Modals/Password/MultiUserAuth.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export default function MultiUserAuth() {
const [token, setToken] = useState(null);
const [showRecoveryForm, setShowRecoveryForm] = useState(false);
const [showResetPasswordForm, setShowResetPasswordForm] = useState(false);
const [customAppName, setCustomAppName] = useState(null);

const {
isOpen: isRecoveryCodeModalOpen,
Expand Down Expand Up @@ -250,6 +251,15 @@ export default function MultiUserAuth() {
}
}, [downloadComplete, user, token]);

useEffect(() => {
const fetchCustomAppName = async () => {
const { appName } = await System.fetchCustomAppName();
setCustomAppName(appName || "");
setLoading(false);
};
fetchCustomAppName();
}, []);

if (showRecoveryForm) {
return (
<RecoveryForm
Expand All @@ -272,11 +282,11 @@ export default function MultiUserAuth() {
Welcome to
</h3>
<p className="text-4xl md:text-2xl font-bold bg-gradient-to-r from-[#75D6FF] via-[#FFFFFF] to-[#FFFFFF] bg-clip-text text-transparent">
AnythingLLM
{customAppName || "AnythingLLM"}
</p>
</div>
<p className="text-sm text-white/90 text-center">
Sign in to your AnythingLLM account.
Sign in to your {customAppName || "AnythingLLM"} account.
</p>
</div>
</div>
Expand Down
16 changes: 12 additions & 4 deletions frontend/src/components/Modals/Password/SingleUserAuth.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useEffect, useState } from "react";
import System from "../../../models/system";
import { AUTH_TOKEN } from "../../../utils/constants";
import useLogo from "../../../hooks/useLogo";
import paths from "../../../utils/paths";
import ModalWrapper from "@/components/ModalWrapper";
import { useModal } from "@/hooks/useModal";
Expand All @@ -10,10 +9,10 @@ import RecoveryCodeModal from "@/components/Modals/DisplayRecoveryCodeModal";
export default function SingleUserAuth() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const { logo: _initLogo } = useLogo();
const [recoveryCodes, setRecoveryCodes] = useState([]);
const [downloadComplete, setDownloadComplete] = useState(false);
const [token, setToken] = useState(null);
const [customAppName, setCustomAppName] = useState(null);

const {
isOpen: isRecoveryCodeModalOpen,
Expand Down Expand Up @@ -57,6 +56,15 @@ export default function SingleUserAuth() {
}
}, [downloadComplete, token]);

useEffect(() => {
const fetchCustomAppName = async () => {
const { appName } = await System.fetchCustomAppName();
setCustomAppName(appName || "");
setLoading(false);
};
fetchCustomAppName();
}, []);

return (
<>
<form onSubmit={handleLogin}>
Expand All @@ -68,11 +76,11 @@ export default function SingleUserAuth() {
Welcome to
</h3>
<p className="text-4xl md:text-2xl font-bold bg-gradient-to-r from-[#75D6FF] via-[#FFFFFF] to-[#FFFFFF] bg-clip-text text-transparent">
AnythingLLM
{customAppName || "AnythingLLM"}
</p>
</div>
<p className="text-sm text-white/90 text-center">
Sign in to your AnythingLLM instance.
Sign in to your {customAppName || "AnythingLLM"} instance.
</p>
</div>
</div>
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/components/Modals/Password/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import {
} from "../../../utils/constants";
import useLogo from "../../../hooks/useLogo";
import illustration from "@/media/illustrations/login-illustration.svg";
import loginLogo from "@/media/illustrations/login-logo.svg";

export default function PasswordModal({ mode = "single" }) {
const { logo: _initLogo } = useLogo();
const { loginLogo } = useLogo();
return (
<div className="fixed top-0 left-0 right-0 z-50 w-full overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] h-full bg-[#25272C] flex flex-col md:flex-row items-center justify-center">
<div
Expand All @@ -37,10 +36,11 @@ export default function PasswordModal({ mode = "single" }) {
<div className="flex flex-col items-center justify-center h-full w-full md:w-1/2 z-50 relative">
<img
src={loginLogo}
className={`mb-8 w-[84px] h-[84px] absolute ${
mode === "single" ? "md:top-50" : "md:top-36"
} top-44 z-30`}
alt="logo"
alt="Logo"
className={`hidden md:flex rounded-2xl w-fit m-4 z-30 ${
mode === "single" ? "md:top-[170px]" : "md:top-36"
} absolute max-h-[65px] md:bg-login-gradient md:shadow-[0_4px_14px_rgba(0,0,0,0.25)]`}
style={{ objectFit: "contain" }}
/>
{mode === "single" ? <SingleUserAuth /> : <MultiUserAuth />}
</div>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/hooks/useLogo.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { useContext } from "react";
import { LogoContext } from "../LogoContext";

export default function useLogo() {
const { logo, setLogo } = useContext(LogoContext);
return { logo, setLogo };
const { logo, setLogo, loginLogo, isCustomLogo } = useContext(LogoContext);
return { logo, setLogo, loginLogo, isCustomLogo };
}
48 changes: 44 additions & 4 deletions frontend/src/models/system.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const System = {
cacheKeys: {
footerIcons: "anythingllm_footer_links",
supportEmail: "anythingllm_support_email",
customAppName: "anythingllm_custom_app_name",
},
ping: async function () {
return await fetch(`${API_BASE}/ping`)
Expand Down Expand Up @@ -305,19 +306,58 @@ const System = {
);
return { email: supportEmail, error: null };
},

fetchCustomAppName: async function () {
const cache = window.localStorage.getItem(this.cacheKeys.customAppName);
const { appName, lastFetched } = cache
? safeJsonParse(cache, { appName: "", lastFetched: 0 })
: { appName: "", lastFetched: 0 };

if (!!appName && Date.now() - lastFetched < 3_600_000)
return { appName: appName, error: null };

const { customAppName, error } = await fetch(
`${API_BASE}/system/custom-app-name`,
{
method: "GET",
cache: "no-cache",
headers: baseHeaders(),
}
)
.then((res) => res.json())
.catch((e) => {
console.log(e);
return { customAppName: "", error: e.message };
});

if (!customAppName || !!error) {
window.localStorage.removeItem(this.cacheKeys.customAppName);
return { appName: "", error: null };
}

window.localStorage.setItem(
this.cacheKeys.customAppName,
JSON.stringify({ appName: customAppName, lastFetched: Date.now() })
);
return { appName: customAppName, error: null };
},
fetchLogo: async function () {
return await fetch(`${API_BASE}/system/logo`, {
method: "GET",
cache: "no-cache",
})
.then((res) => {
if (res.ok && res.status !== 204) return res.blob();
.then(async (res) => {
if (res.ok && res.status !== 204) {
const isCustomLogo = res.headers.get("X-Is-Custom-Logo") === "true";
const blob = await res.blob();
const logoURL = URL.createObjectURL(blob);
return { isCustomLogo, logoURL };
}
throw new Error("Failed to fetch logo!");
})
.then((blob) => URL.createObjectURL(blob))
.catch((e) => {
console.log(e);
return null;
return { isCustomLogo: false, logoURL: null };
});
},
fetchPfp: async function (id) {
Expand Down
100 changes: 100 additions & 0 deletions frontend/src/pages/GeneralSettings/Appearance/CustomAppName/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import Admin from "@/models/admin";
import System from "@/models/system";
import showToast from "@/utils/toast";
import { useEffect, useState } from "react";

export default function CustomAppName() {
const [loading, setLoading] = useState(true);
const [hasChanges, setHasChanges] = useState(false);
const [customAppName, setCustomAppName] = useState("");
const [originalAppName, setOriginalAppName] = useState("");
const [canCustomize, setCanCustomize] = useState(false);

useEffect(() => {
const fetchInitialParams = async () => {
const settings = await System.keys();
if (!settings?.MultiUserMode && !settings?.RequiresAuth) {
setCanCustomize(false);
return false;
}

const { appName } = await System.fetchCustomAppName();
setCustomAppName(appName || "");
setOriginalAppName(appName || "");
setCanCustomize(true);
setLoading(false);
};
fetchInitialParams();
}, []);

const updateCustomAppName = async (e, newValue = null) => {
e.preventDefault();
let custom_app_name = newValue;
if (newValue === null) {
const form = new FormData(e.target);
custom_app_name = form.get("customAppName");
}
const { success, error } = await Admin.updateSystemPreferences({
custom_app_name,
});
if (!success) {
showToast(`Failed to update custom app name: ${error}`, "error");
return;
} else {
showToast("Successfully updated custom app name.", "success");
window.localStorage.removeItem(System.cacheKeys.customAppName);
setCustomAppName(custom_app_name);
setOriginalAppName(custom_app_name);
setHasChanges(false);
}
};

const handleChange = (e) => {
setCustomAppName(e.target.value);
setHasChanges(true);
};

if (!canCustomize || loading) return null;

return (
<form className="mb-6" onSubmit={updateCustomAppName}>
<div className="flex flex-col gap-y-1">
<h2 className="text-base leading-6 font-bold text-white">
Custom App Name
</h2>
<p className="text-xs leading-[18px] font-base text-white/60">
Set a custom app name that is displayed on the login page.
</p>
</div>
<div className="flex items-center gap-x-4">
<input
name="customAppName"
type="text"
className="bg-zinc-900 mt-3 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 max-w-[275px] placeholder:text-white/20"
placeholder="AnythingLLM"
required={true}
autoComplete="off"
onChange={handleChange}
value={customAppName}
/>
{originalAppName !== "" && (
<button
type="button"
onClick={(e) => updateCustomAppName(e, "")}
className="mt-4 text-white text-base font-medium hover:text-opacity-60"
>
Clear
</button>
)}
</div>
{hasChanges && (
<button
type="submit"
className="transition-all mt-6 w-fit duration-300 border border-slate-200 px-5 py-2.5 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
>
Save
</button>
)}
</form>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import useLogo from "@/hooks/useLogo";
import System from "@/models/system";
import showToast from "@/utils/toast";
import { useEffect, useRef, useState } from "react";
import AnythingLLM from "@/media/logo/anything-llm.png";
import { Plus } from "@phosphor-icons/react";

export default function CustomLogo() {
Expand Down Expand Up @@ -36,7 +35,7 @@ export default function CustomLogo() {
return;
}

const logoURL = await System.fetchLogo();
const { logoURL } = await System.fetchLogo();
_setLogo(logoURL);

showToast("Image uploaded successfully.", "success");
Expand All @@ -51,13 +50,13 @@ export default function CustomLogo() {
if (!success) {
console.error("Failed to remove logo:", error);
showToast(`Failed to remove logo: ${error}`, "error");
const logoURL = await System.fetchLogo();
const { logoURL } = await System.fetchLogo();
setLogo(logoURL);
setIsDefaultLogo(false);
return;
}

const logoURL = await System.fetchLogo();
const { logoURL } = await System.fetchLogo();
_setLogo(logoURL);

showToast("Image successfully removed.", "success");
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/pages/GeneralSettings/Appearance/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import FooterCustomization from "./FooterCustomization";
import SupportEmail from "./SupportEmail";
import CustomLogo from "./CustomLogo";
import CustomMessages from "./CustomMessages";
import CustomAppName from "./CustomAppName";

export default function Appearance() {
return (
Expand All @@ -25,6 +26,7 @@ export default function Appearance() {
</p>
</div>
<CustomLogo />
<CustomAppName />
<CustomMessages />
<FooterCustomization />
<SupportEmail />
Expand Down
Loading