mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2025-04-17 18:18:11 +00:00
Custom default messages implementation for single and multi-user modes (#193)
* added ui for custom welcome messages and added label for custom logo in admin settings * linting * fixing img to use light/dark modes * converted ChatBubble into component * implemented backend for welcome messages and admin appearance page * completed custom welcome messages for admin * finished custom messages for single user mode * merged with master and linted * improved UI for appearance settings pages * linted and merged with master * small updates --------- Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
parent
882b362213
commit
31fbb0784b
10 changed files with 656 additions and 93 deletions
frontend/src
components
models
pages
server
29
frontend/src/components/ChatBubble/index.jsx
Normal file
29
frontend/src/components/ChatBubble/index.jsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import React from "react";
|
||||
|
||||
export default function ChatBubble({ message, type, popMsg }) {
|
||||
const isUser = type === "user";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex w-full mt-2 items-center ${
|
||||
popMsg ? "chat__message" : ""
|
||||
} ${isUser ? "justify-end" : "justify-start"}`}
|
||||
>
|
||||
<div
|
||||
className={`p-4 max-w-full md:max-w-[75%] ${
|
||||
isUser
|
||||
? "bg-slate-200 dark:bg-amber-800"
|
||||
: "bg-orange-100 dark:bg-stone-700"
|
||||
} rounded-b-2xl ${isUser ? "rounded-tl-2xl" : "rounded-tr-2xl"} ${
|
||||
isUser ? "rounded-tr-sm" : "rounded-tl-sm"
|
||||
}`}
|
||||
>
|
||||
{message && (
|
||||
<p className="text-slate-800 dark:text-slate-200 font-[500] md:font-semibold text-sm md:text-base">
|
||||
{message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -6,9 +6,12 @@ import NewWorkspaceModal, {
|
|||
import paths from "../../utils/paths";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { SidebarMobileHeader } from "../Sidebar";
|
||||
import ChatBubble from "../ChatBubble";
|
||||
import System from "../../models/system";
|
||||
|
||||
export default function DefaultChatContainer() {
|
||||
const [mockMsgs, setMockMessages] = useState([]);
|
||||
const [fetchedMessages, setFetchedMessages] = useState([]);
|
||||
const {
|
||||
showing: showingNewWsModal,
|
||||
showModal: showNewWsModal,
|
||||
|
@ -16,6 +19,14 @@ export default function DefaultChatContainer() {
|
|||
} = useNewWorkspaceModal();
|
||||
const popMsg = !window.localStorage.getItem("anythingllm_intro");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const fetchedMessages = await System.getWelcomeMessages();
|
||||
setFetchedMessages(fetchedMessages);
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const MESSAGES = [
|
||||
<React.Fragment>
|
||||
<div
|
||||
|
@ -251,9 +262,25 @@ export default function DefaultChatContainer() {
|
|||
className="transition-all duration-500 relative md:ml-[2px] md:mr-[8px] md:my-[16px] md:rounded-[26px] bg-white dark:bg-black-900 md:min-w-[82%] p-[18px] h-full overflow-y-scroll"
|
||||
>
|
||||
{isMobile && <SidebarMobileHeader />}
|
||||
{mockMsgs.map((content, i) => {
|
||||
return <React.Fragment key={i}>{content}</React.Fragment>;
|
||||
})}
|
||||
{fetchedMessages.length === 0
|
||||
? mockMsgs.map((content, i) => {
|
||||
return <React.Fragment key={i}>{content}</React.Fragment>;
|
||||
})
|
||||
: fetchedMessages.map((fetchedMessage, i) => {
|
||||
return (
|
||||
<React.Fragment key={i}>
|
||||
<ChatBubble
|
||||
message={
|
||||
fetchedMessage.user === ""
|
||||
? fetchedMessage.response
|
||||
: fetchedMessage.user
|
||||
}
|
||||
type={fetchedMessage.user === "" ? "response" : "user"}
|
||||
popMsg={popMsg}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
{showingNewWsModal && <NewWorkspaceModal hideModal={hideNewWsModal} />}
|
||||
</div>
|
||||
);
|
||||
|
|
67
frontend/src/components/EditingChatBubble/index.jsx
Normal file
67
frontend/src/components/EditingChatBubble/index.jsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import React, { useState } from "react";
|
||||
import { X } from "react-feather";
|
||||
|
||||
export default function EditingChatBubble({
|
||||
message,
|
||||
index,
|
||||
type,
|
||||
handleMessageChange,
|
||||
removeMessage,
|
||||
}) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [tempMessage, setTempMessage] = useState(message[type]);
|
||||
const isUser = type === "user";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex w-full mt-2 items-center ${
|
||||
isUser ? "justify-end" : "justify-start"
|
||||
}`}
|
||||
>
|
||||
{isUser && (
|
||||
<button
|
||||
className="flex items-center text-red-500 hover:text-red-700 transition mr-2"
|
||||
onClick={() => removeMessage(index)}
|
||||
>
|
||||
<X className="mr-2" size={20} />
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
className={`p-4 max-w-full md:max-w-[75%] ${
|
||||
isUser
|
||||
? "bg-slate-200 dark:bg-amber-800"
|
||||
: "bg-orange-100 dark:bg-stone-700"
|
||||
} rounded-b-2xl ${isUser ? "rounded-tl-2xl" : "rounded-tr-2xl"} ${
|
||||
isUser ? "rounded-tr-sm" : "rounded-tl-sm"
|
||||
}`}
|
||||
onDoubleClick={() => setIsEditing(true)}
|
||||
>
|
||||
{isEditing ? (
|
||||
<input
|
||||
value={tempMessage}
|
||||
onChange={(e) => setTempMessage(e.target.value)}
|
||||
onBlur={() => {
|
||||
handleMessageChange(index, type, tempMessage);
|
||||
setIsEditing(false);
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
tempMessage && (
|
||||
<p className="text-slate-800 dark:text-slate-200 font-[500] md:font-semibold text-sm md:text-base">
|
||||
{tempMessage}
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{!isUser && (
|
||||
<button
|
||||
className="flex items-center text-red-500 hover:text-red-700 transition ml-2"
|
||||
onClick={() => removeMessage(index)}
|
||||
>
|
||||
<X className="mr-2" size={20} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -216,6 +216,22 @@ const Admin = {
|
|||
return { success: false, error: e.message };
|
||||
});
|
||||
},
|
||||
setWelcomeMessages: async function (messages) {
|
||||
return fetch(`${API_BASE}/system/set-welcome-messages`, {
|
||||
method: "POST",
|
||||
headers: baseHeaders(),
|
||||
body: JSON.stringify({ messages }),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok)
|
||||
throw new Error(res.statusText || "Error setting welcome messages.");
|
||||
return res.json();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
return { success: false, error: e.message };
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default Admin;
|
||||
|
|
|
@ -188,6 +188,38 @@ const System = {
|
|||
return { success: false, error: e.message };
|
||||
});
|
||||
},
|
||||
getWelcomeMessages: async function () {
|
||||
return await fetch(`${API_BASE}/system/welcome-messages`, {
|
||||
method: "GET",
|
||||
cache: "no-cache",
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error("Could not fetch welcome messages.");
|
||||
return res.json();
|
||||
})
|
||||
.then((res) => res.welcomeMessages)
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
return null;
|
||||
});
|
||||
},
|
||||
setWelcomeMessages: async function (messages) {
|
||||
return fetch(`${API_BASE}/system/set-welcome-messages`, {
|
||||
method: "POST",
|
||||
headers: baseHeaders(),
|
||||
body: JSON.stringify({ messages }),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) {
|
||||
throw new Error(res.statusText || "Error setting welcome messages.");
|
||||
}
|
||||
return { success: true, ...res.json() };
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
return { success: false, error: e.message };
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default System;
|
||||
|
|
|
@ -7,12 +7,16 @@ import AnythingLLMDark from "../../../media/logo/anything-llm-dark.png";
|
|||
import usePrefersDarkMode from "../../../hooks/usePrefersDarkMode";
|
||||
import useLogo from "../../../hooks/useLogo";
|
||||
import System from "../../../models/system";
|
||||
import EditingChatBubble from "../../../components/EditingChatBubble";
|
||||
|
||||
export default function Appearance() {
|
||||
const { logo: _initLogo } = useLogo();
|
||||
const [logo, setLogo] = useState("");
|
||||
const prefersDarkMode = usePrefersDarkMode();
|
||||
const [errorMsg, setErrorMsg] = useState("");
|
||||
const [successMsg, setSuccessMsg] = useState("");
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [messages, setMessages] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function setInitLogo() {
|
||||
|
@ -27,7 +31,21 @@ export default function Appearance() {
|
|||
setErrorMsg("");
|
||||
}, 3_500);
|
||||
}
|
||||
}, [errorMsg]);
|
||||
|
||||
if (!!successMsg) {
|
||||
setTimeout(() => {
|
||||
setSuccessMsg("");
|
||||
}, 3_500);
|
||||
}
|
||||
}, [errorMsg, successMsg]);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchMessages() {
|
||||
const messages = await System.getWelcomeMessages();
|
||||
setMessages(messages);
|
||||
}
|
||||
fetchMessages();
|
||||
}, []);
|
||||
|
||||
const handleFileUpload = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
|
@ -62,6 +80,42 @@ export default function Appearance() {
|
|||
window.location.reload();
|
||||
};
|
||||
|
||||
const addMessage = (type) => {
|
||||
if (type === "user") {
|
||||
setMessages([
|
||||
...messages,
|
||||
{ user: "Double click to edit...", response: "" },
|
||||
]);
|
||||
} else {
|
||||
setMessages([
|
||||
...messages,
|
||||
{ user: "", response: "Double click to edit..." },
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const removeMessage = (index) => {
|
||||
setHasChanges(true);
|
||||
setMessages(messages.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleMessageChange = (index, type, value) => {
|
||||
setHasChanges(true);
|
||||
const newMessages = [...messages];
|
||||
newMessages[index][type] = value;
|
||||
setMessages(newMessages);
|
||||
};
|
||||
|
||||
const handleMessageSave = async () => {
|
||||
const { success, error } = await Admin.setWelcomeMessages(messages);
|
||||
if (!success) {
|
||||
setErrorMsg(error);
|
||||
return;
|
||||
}
|
||||
setSuccessMsg("Successfully updated welcome messages.");
|
||||
setHasChanges(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-orange-100 dark:bg-stone-700 flex">
|
||||
{!isMobile && <Sidebar />}
|
||||
|
@ -79,48 +133,118 @@ export default function Appearance() {
|
|||
Customize the appearance settings of your platform.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={logo}
|
||||
alt="Uploaded Logo"
|
||||
className="w-48 h-48 object-contain mr-6"
|
||||
onError={(e) =>
|
||||
(e.target.src = prefersDarkMode
|
||||
? AnythingLLMLight
|
||||
: AnythingLLMDark)
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="mb-4">
|
||||
<label className="cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
||||
Upload Image
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
onClick={handleRemoveLogo}
|
||||
className="ml-4 cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
|
||||
>
|
||||
Remove Custom Logo
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Upload your logo. Recommended size: 800x200.
|
||||
<div className="mb-6">
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<h2 className="leading-tight font-medium text-black dark:text-white">
|
||||
Custom Logo
|
||||
</h2>
|
||||
<p className="leading-tight text-sm text-gray-500 dark:text-slate-400">
|
||||
Change the logo that appears in the sidebar.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={logo}
|
||||
alt="Uploaded Logo"
|
||||
className="w-48 h-48 object-contain mr-6"
|
||||
onError={(e) =>
|
||||
(e.target.src = prefersDarkMode
|
||||
? AnythingLLMLight
|
||||
: AnythingLLMDark)
|
||||
}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<div className="mb-4">
|
||||
<label className="cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
||||
Upload Image
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
onClick={handleRemoveLogo}
|
||||
className="ml-4 cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
|
||||
>
|
||||
Remove Custom Logo
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Upload your logo. Recommended size: 800x200.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<h2 className="leading-tight font-medium text-black dark:text-white">
|
||||
Custom Messages
|
||||
</h2>
|
||||
<p className="leading-tight text-sm text-gray-500 dark:text-slate-400">
|
||||
Change the default messages that are displayed to the users.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col gap-y-6">
|
||||
{messages.map((message, index) => (
|
||||
<div key={index} className="flex flex-col gap-y-2">
|
||||
{message.user && (
|
||||
<EditingChatBubble
|
||||
message={message}
|
||||
index={index}
|
||||
type="user"
|
||||
handleMessageChange={handleMessageChange}
|
||||
removeMessage={removeMessage}
|
||||
/>
|
||||
)}
|
||||
{message.response && (
|
||||
<EditingChatBubble
|
||||
message={message}
|
||||
index={index}
|
||||
type="response"
|
||||
handleMessageChange={handleMessageChange}
|
||||
removeMessage={removeMessage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex gap-4 mt-4 justify-between">
|
||||
<button
|
||||
className="self-end text-orange-500 hover:text-orange-700 transition"
|
||||
onClick={() => addMessage("response")}
|
||||
>
|
||||
+ System Message
|
||||
</button>
|
||||
<button
|
||||
className="self-end text-orange-500 hover:text-orange-700 transition"
|
||||
onClick={() => addMessage("user")}
|
||||
>
|
||||
+ User Message
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{hasChanges && (
|
||||
<div className="flex justify-center py-6">
|
||||
<button
|
||||
className="ml-4 cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
|
||||
onClick={handleMessageSave}
|
||||
>
|
||||
Save Messages
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{errorMsg && (
|
||||
<div className="mt-4 text-sm text-red-600 dark:text-red-400 text-center">
|
||||
{errorMsg}
|
||||
</div>
|
||||
)}
|
||||
{successMsg && (
|
||||
<div className="mt-4 text-sm text-green-600 dark:text-green-400 text-center">
|
||||
{successMsg}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,10 @@ import AnythingLLMDark from "../../media/logo/anything-llm-dark.png";
|
|||
import System from "../../models/system";
|
||||
import usePrefersDarkMode from "../../hooks/usePrefersDarkMode";
|
||||
import useLogo from "../../hooks/useLogo";
|
||||
import EditingChatBubble from "../../components/EditingChatBubble";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { ArrowLeft } from "react-feather";
|
||||
import paths from "../../utils/paths";
|
||||
|
||||
export default function Appearance() {
|
||||
const { logo: _initLogo } = useLogo();
|
||||
|
@ -11,6 +15,16 @@ export default function Appearance() {
|
|||
const [logo, setLogo] = useState("");
|
||||
const [errorMsg, setErrorMsg] = useState("");
|
||||
const [successMsg, setSuccessMsg] = useState("");
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [messages, setMessages] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchMessages() {
|
||||
const messages = await System.getWelcomeMessages();
|
||||
setMessages(messages);
|
||||
}
|
||||
fetchMessages();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
async function setInitLogo() {
|
||||
|
@ -68,66 +82,181 @@ export default function Appearance() {
|
|||
setErrorMsg("");
|
||||
};
|
||||
|
||||
const addMessage = (type) => {
|
||||
if (type === "user") {
|
||||
setMessages([
|
||||
...messages,
|
||||
{ user: "Double click to edit...", response: "" },
|
||||
]);
|
||||
} else {
|
||||
setMessages([
|
||||
...messages,
|
||||
{ user: "", response: "Double click to edit..." },
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const removeMessage = (index) => {
|
||||
setHasChanges(true);
|
||||
setMessages(messages.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleMessageChange = (index, type, value) => {
|
||||
setHasChanges(true);
|
||||
const newMessages = [...messages];
|
||||
newMessages[index][type] = value;
|
||||
setMessages(newMessages);
|
||||
};
|
||||
|
||||
const handleMessageSave = async () => {
|
||||
const { success, error } = await System.setWelcomeMessages(messages);
|
||||
if (!success) {
|
||||
setErrorMsg(error);
|
||||
return;
|
||||
}
|
||||
setSuccessMsg("Successfully updated welcome messages.");
|
||||
setHasChanges(false);
|
||||
};
|
||||
|
||||
const handleBackNavigation = () => {
|
||||
window.location = paths.home();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-orange-100 dark:bg-black-900">
|
||||
<div className="p-6 w-full max-w-xl bg-white dark:bg-stone-600 rounded-xl shadow-md space-y-4">
|
||||
<h2 className="text-2xl font-bold text-center text-black dark:text-white">
|
||||
Customize Appearance
|
||||
</h2>
|
||||
<p className="text-center text-xs font-light text-black dark:text-white">
|
||||
Customize the logo you see on the sidebar
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col items-center border border-slate-200 dark:border-black-900 p-6 rounded-xl">
|
||||
<img
|
||||
src={logo}
|
||||
alt="Uploaded Logo"
|
||||
className="w-48 h-48 object-contain"
|
||||
onError={(e) =>
|
||||
(e.target.src = prefersDarkMode
|
||||
? AnythingLLMLight
|
||||
: AnythingLLMDark)
|
||||
}
|
||||
/>
|
||||
<div className="flex gap-2 p-2 flex-col items-center">
|
||||
<div className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Upload your logo
|
||||
<div className="w-screen h-screen overflow-hidden bg-orange-100 dark:bg-stone-700 flex justify-center py-6">
|
||||
<div
|
||||
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
|
||||
className="transition-all duration-500 relative md:ml-[2px] md:mr-[8px] md:my-[16px] md:rounded-[26px] bg-white dark:bg-black-900 md:min-w-[82%] p-[18px] h-full overflow-y-scroll"
|
||||
>
|
||||
<div className="px-1 md:px-8">
|
||||
<div className="mb-6">
|
||||
<div
|
||||
className="cursor-pointer inline-flex items-center gap-3 mb-5 py-2 pl-2 pr-4 text-white rounded-md hover:bg-gray-300 dark:hover:bg-gray-800 transition-all"
|
||||
onClick={handleBackNavigation}
|
||||
>
|
||||
<ArrowLeft />
|
||||
<span>Back</span>
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Recommended size at least 800x200
|
||||
<p className="text-3xl font-semibold text-slate-600 dark:text-slate-200">
|
||||
Appearance Settings
|
||||
</p>
|
||||
<p className="mt-2 text-sm font-base text-slate-600 dark:text-slate-200">
|
||||
Customize the appearance settings of your platform.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-6">
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<h2 className="leading-tight font-medium text-black dark:text-white">
|
||||
Custom Logo
|
||||
</h2>
|
||||
<p className="leading-tight text-sm text-gray-500 dark:text-slate-400">
|
||||
Change the logo that appears in the sidebar.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={logo}
|
||||
alt="Uploaded Logo"
|
||||
className="w-48 h-48 object-contain mr-6"
|
||||
onError={(e) =>
|
||||
(e.target.src = prefersDarkMode
|
||||
? AnythingLLMLight
|
||||
: AnythingLLMDark)
|
||||
}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<div className="mb-4">
|
||||
<label className="cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
||||
Upload Image
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
onClick={handleRemoveLogo}
|
||||
className="ml-4 cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
|
||||
>
|
||||
Remove Custom Logo
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Upload your logo. Recommended size: 800x200.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center mt-4 gap-2">
|
||||
<label className="cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
||||
Upload Image
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
onClick={handleRemoveLogo}
|
||||
className="cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
|
||||
>
|
||||
Remove Custom Logo
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{errorMsg && (
|
||||
<div className="text-sm text-red-600 dark:text-red-400 text-center">
|
||||
{errorMsg}
|
||||
<div className="mb-6">
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<h2 className="leading-tight font-medium text-black dark:text-white">
|
||||
Custom Messages
|
||||
</h2>
|
||||
<p className="leading-tight text-sm text-gray-500 dark:text-slate-400">
|
||||
Change the default messages that are displayed to the users.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col gap-y-6">
|
||||
{messages.map((message, index) => (
|
||||
<div key={index} className="flex flex-col gap-y-2">
|
||||
{message.user && (
|
||||
<EditingChatBubble
|
||||
message={message}
|
||||
index={index}
|
||||
type="user"
|
||||
handleMessageChange={handleMessageChange}
|
||||
removeMessage={removeMessage}
|
||||
/>
|
||||
)}
|
||||
{message.response && (
|
||||
<EditingChatBubble
|
||||
message={message}
|
||||
index={index}
|
||||
type="response"
|
||||
handleMessageChange={handleMessageChange}
|
||||
removeMessage={removeMessage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex gap-4 mt-4 justify-between">
|
||||
<button
|
||||
className="self-end text-orange-500 hover:text-orange-700 transition"
|
||||
onClick={() => addMessage("response")}
|
||||
>
|
||||
+ System Message
|
||||
</button>
|
||||
<button
|
||||
className="self-end text-orange-500 hover:text-orange-700 transition"
|
||||
onClick={() => addMessage("user")}
|
||||
>
|
||||
+ User Message
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{hasChanges && (
|
||||
<div className="flex justify-center py-6">
|
||||
<button
|
||||
className="ml-4 cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
|
||||
onClick={handleMessageSave}
|
||||
>
|
||||
Save Messages
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{successMsg && (
|
||||
<div className="text-sm text-green-600 dark:text-green-400 text-center">
|
||||
{successMsg}
|
||||
</div>
|
||||
)}
|
||||
{errorMsg && (
|
||||
<div className="mt-4 text-sm text-red-600 dark:text-red-400 text-center">
|
||||
{errorMsg}
|
||||
</div>
|
||||
)}
|
||||
{successMsg && (
|
||||
<div className="mt-4 text-sm text-green-600 dark:text-green-400 text-center">
|
||||
{successMsg}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -35,6 +35,7 @@ const {
|
|||
DARK_LOGO_FILENAME,
|
||||
} = require("../utils/files/logo");
|
||||
const { Telemetry } = require("../models/telemetry");
|
||||
const { WelcomeMessages } = require("../models/welcomeMessages");
|
||||
|
||||
function systemEndpoints(app) {
|
||||
if (!app) return;
|
||||
|
@ -477,6 +478,53 @@ function systemEndpoints(app) {
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.get("/system/welcome-messages", async function (request, response) {
|
||||
try {
|
||||
const welcomeMessages = await WelcomeMessages.getMessages();
|
||||
response.status(200).json({ success: true, welcomeMessages });
|
||||
} catch (error) {
|
||||
console.error("Error fetching welcome messages:", error);
|
||||
response
|
||||
.status(500)
|
||||
.json({ success: false, message: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
app.post(
|
||||
"/system/set-welcome-messages",
|
||||
[validatedRequest],
|
||||
async (request, response) => {
|
||||
try {
|
||||
if (
|
||||
response.locals.multiUserMode &&
|
||||
response.locals.user?.role !== "admin"
|
||||
) {
|
||||
return response.sendStatus(401).end();
|
||||
}
|
||||
|
||||
const { messages = [] } = reqBody(request);
|
||||
if (!Array.isArray(messages)) {
|
||||
return response.status(400).json({
|
||||
success: false,
|
||||
message: "Invalid message format. Expected an array of messages.",
|
||||
});
|
||||
}
|
||||
|
||||
await WelcomeMessages.saveAll(messages);
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
message: "Welcome messages saved successfully.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error processing the welcome messages:", error);
|
||||
response.status(500).json({
|
||||
success: true,
|
||||
message: "Error saving the welcome messages.",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { systemEndpoints };
|
||||
|
|
89
server/models/welcomeMessages.js
Normal file
89
server/models/welcomeMessages.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
const WelcomeMessages = {
|
||||
tablename: "welcome_messages",
|
||||
colsInit: `
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user TEXT NOT NULL,
|
||||
response TEXT NOT NULL,
|
||||
orderIndex INTEGER,
|
||||
createdAt TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
`,
|
||||
|
||||
migrateTable: async function () {
|
||||
const { checkForMigrations } = require("../utils/database");
|
||||
console.log(
|
||||
`\x1b[34m[MIGRATING]\x1b[0m Checking for Welcome Messages migrations`
|
||||
);
|
||||
const db = await this.db(false);
|
||||
await checkForMigrations(this, db);
|
||||
db.close();
|
||||
},
|
||||
|
||||
migrations: function () {
|
||||
return [];
|
||||
},
|
||||
|
||||
db: async function (tracing = true) {
|
||||
const sqlite3 = require("sqlite3").verbose();
|
||||
const { open } = require("sqlite");
|
||||
|
||||
const db = await open({
|
||||
filename: `${
|
||||
!!process.env.STORAGE_DIR ? `${process.env.STORAGE_DIR}/` : "storage/"
|
||||
}anythingllm.db`,
|
||||
driver: sqlite3.Database,
|
||||
});
|
||||
|
||||
await db.exec(
|
||||
`PRAGMA foreign_keys = ON;CREATE TABLE IF NOT EXISTS ${this.tablename} (${this.colsInit})`
|
||||
);
|
||||
|
||||
if (tracing) {
|
||||
db.on("trace", (sql) => console.log(sql));
|
||||
}
|
||||
|
||||
return db;
|
||||
},
|
||||
|
||||
get: async function (clause = "") {
|
||||
const db = await this.db();
|
||||
const result = await db
|
||||
.get(`SELECT * FROM ${this.tablename} WHERE ${clause}`)
|
||||
.then((res) => res || null);
|
||||
db.close();
|
||||
return result;
|
||||
},
|
||||
|
||||
where: async function (clause = null, limit = null) {
|
||||
const db = await this.db();
|
||||
const results = await db.all(
|
||||
`SELECT * FROM ${this.tablename} ${clause ? `WHERE ${clause}` : ""} ${
|
||||
!!limit ? `LIMIT ${limit}` : ""
|
||||
}`
|
||||
);
|
||||
db.close();
|
||||
return results;
|
||||
},
|
||||
|
||||
saveAll: async function (messages) {
|
||||
const db = await this.db();
|
||||
await db.run(`DELETE FROM ${this.tablename}`);
|
||||
for (const [index, message] of messages.entries()) {
|
||||
await db.run(
|
||||
`INSERT INTO ${this.tablename} (user, response, orderIndex) VALUES (?, ?, ?)`,
|
||||
[message.user, message.response, index]
|
||||
);
|
||||
}
|
||||
db.close();
|
||||
},
|
||||
|
||||
getMessages: async function () {
|
||||
const db = await this.db();
|
||||
const results = await db.all(
|
||||
`SELECT user, response FROM ${this.tablename} ORDER BY orderIndex ASC`
|
||||
);
|
||||
db.close();
|
||||
return results;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports.WelcomeMessages = WelcomeMessages;
|
|
@ -61,6 +61,7 @@ async function validateTablePragmas(force = false) {
|
|||
const { DocumentVectors } = require("../../models/vectors");
|
||||
const { WorkspaceChats } = require("../../models/workspaceChats");
|
||||
const { Invite } = require("../../models/invite");
|
||||
const { WelcomeMessages } = require("../../models/welcomeMessages");
|
||||
|
||||
await SystemSettings.migrateTable();
|
||||
await User.migrateTable();
|
||||
|
@ -70,6 +71,7 @@ async function validateTablePragmas(force = false) {
|
|||
await DocumentVectors.migrateTable();
|
||||
await WorkspaceChats.migrateTable();
|
||||
await Invite.migrateTable();
|
||||
await WelcomeMessages.migrateTable();
|
||||
} catch (e) {
|
||||
console.error(`validateTablePragmas: Migrations failed`, e);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue