Enable the ability to disable the chat history UI (#2501)
* Enable the ability to disable the chat history UI * forgot files
This commit is contained in:
parent
e71392d83f
commit
0524aadf58
@ -280,3 +280,11 @@ GID='1000'
|
||||
|
||||
#------ SearXNG ----------- https://github.com/searxng/searxng
|
||||
# AGENT_SEARXNG_API_URL=
|
||||
|
||||
###########################################
|
||||
######## Other Configurations ############
|
||||
###########################################
|
||||
|
||||
# Disable viewing chat history from the UI and frontend APIs.
|
||||
# See https://docs.anythingllm.com/configuration#disable-view-chat-history for more information.
|
||||
# DISABLE_VIEW_CHAT_HISTORY=1
|
||||
50
frontend/src/components/CanViewChatHistory/index.jsx
Normal file
50
frontend/src/components/CanViewChatHistory/index.jsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { FullScreenLoader } from "@/components/Preloader";
|
||||
import System from "@/models/system";
|
||||
import paths from "@/utils/paths";
|
||||
|
||||
/**
|
||||
* Protects the view from system set ups who cannot view chat history.
|
||||
* If the user cannot view chat history, they are redirected to the home page.
|
||||
* @param {React.ReactNode} children
|
||||
*/
|
||||
export function CanViewChatHistory({ children }) {
|
||||
const { loading, viewable } = useCanViewChatHistory();
|
||||
if (loading) return <FullScreenLoader />;
|
||||
if (!viewable) {
|
||||
window.location.href = paths.home();
|
||||
return <FullScreenLoader />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the `viewable` state to the children.
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
export function CanViewChatHistoryProvider({ children }) {
|
||||
const { loading, viewable } = useCanViewChatHistory();
|
||||
if (loading) return null;
|
||||
return <>{children({ viewable })}</>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that fetches the can view chat history state from local storage or the system settings.
|
||||
* @returns {Promise<{viewable: boolean, error: string | null}>}
|
||||
*/
|
||||
export function useCanViewChatHistory() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [viewable, setViewable] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchViewable() {
|
||||
const { viewable } = await System.fetchCanViewChatHistory();
|
||||
setViewable(viewable);
|
||||
setLoading(false);
|
||||
}
|
||||
fetchViewable();
|
||||
}, []);
|
||||
|
||||
return { loading, viewable };
|
||||
}
|
||||
@ -149,17 +149,32 @@ function useIsExpanded({
|
||||
return { isExpanded, setIsExpanded };
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the child options are visible to the user.
|
||||
* This hides the top level options if the child options are not visible
|
||||
* for either the users permissions or the child options hidden prop is set to true by other means.
|
||||
* If all child options return false for `isVisible` then the parent option will not be visible as well.
|
||||
* @param {object} user - The user object.
|
||||
* @param {array} childOptions - The child options.
|
||||
* @returns {boolean} - True if the child options are visible, false otherwise.
|
||||
*/
|
||||
function hasVisibleOptions(user = null, childOptions = []) {
|
||||
if (!Array.isArray(childOptions) || childOptions?.length === 0) return false;
|
||||
|
||||
function isVisible({ roles = [], user = null, flex = false }) {
|
||||
function isVisible({
|
||||
roles = [],
|
||||
user = null,
|
||||
flex = false,
|
||||
hidden = false,
|
||||
}) {
|
||||
if (hidden) return false;
|
||||
if (!flex && !roles.includes(user?.role)) return false;
|
||||
if (flex && !!user && !roles.includes(user?.role)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return childOptions.some((opt) =>
|
||||
isVisible({ roles: opt.roles, user, flex: opt.flex })
|
||||
isVisible({ roles: opt.roles, user, flex: opt.flex, hidden: opt.hidden })
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ import { useTranslation } from "react-i18next";
|
||||
import showToast from "@/utils/toast";
|
||||
import System from "@/models/system";
|
||||
import Option from "./MenuOption";
|
||||
import { CanViewChatHistoryProvider } from "../CanViewChatHistory";
|
||||
|
||||
export default function SettingsSidebar() {
|
||||
const { t } = useTranslation();
|
||||
@ -208,6 +209,8 @@ function SupportEmail() {
|
||||
}
|
||||
|
||||
const SidebarOptions = ({ user = null, t }) => (
|
||||
<CanViewChatHistoryProvider>
|
||||
{({ viewable: canViewChatHistory }) => (
|
||||
<>
|
||||
<Option
|
||||
btnText={t("settings.ai-providers")}
|
||||
@ -268,6 +271,7 @@ const SidebarOptions = ({ user = null, t }) => (
|
||||
roles: ["admin", "manager"],
|
||||
},
|
||||
{
|
||||
hidden: !canViewChatHistory,
|
||||
btnText: t("settings.workspace-chats"),
|
||||
href: paths.settings.chats(),
|
||||
flex: true,
|
||||
@ -302,6 +306,7 @@ const SidebarOptions = ({ user = null, t }) => (
|
||||
user={user}
|
||||
childOptions={[
|
||||
{
|
||||
hidden: !canViewChatHistory,
|
||||
btnText: t("settings.embed-chats"),
|
||||
href: paths.settings.embedChats(),
|
||||
flex: true,
|
||||
@ -353,6 +358,8 @@ const SidebarOptions = ({ user = null, t }) => (
|
||||
/>
|
||||
</HoldToReveal>
|
||||
</>
|
||||
)}
|
||||
</CanViewChatHistoryProvider>
|
||||
);
|
||||
|
||||
function HoldToReveal({ children, holdForMs = 3_000 }) {
|
||||
|
||||
@ -9,6 +9,7 @@ const System = {
|
||||
footerIcons: "anythingllm_footer_links",
|
||||
supportEmail: "anythingllm_support_email",
|
||||
customAppName: "anythingllm_custom_app_name",
|
||||
canViewChatHistory: "anythingllm_can_view_chat_history",
|
||||
},
|
||||
ping: async function () {
|
||||
return await fetch(`${API_BASE}/ping`)
|
||||
@ -675,6 +676,36 @@ const System = {
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches the can view chat history state from local storage or the system settings.
|
||||
* Notice: This is an instance setting that cannot be changed via the UI and it is cached
|
||||
* in local storage for 24 hours.
|
||||
* @returns {Promise<{viewable: boolean, error: string | null}>}
|
||||
*/
|
||||
fetchCanViewChatHistory: async function () {
|
||||
const cache = window.localStorage.getItem(
|
||||
this.cacheKeys.canViewChatHistory
|
||||
);
|
||||
const { viewable, lastFetched } = cache
|
||||
? safeJsonParse(cache, { viewable: false, lastFetched: 0 })
|
||||
: { viewable: false, lastFetched: 0 };
|
||||
|
||||
// Since this is an instance setting that cannot be changed via the UI,
|
||||
// we can cache it in local storage for a day and if the admin changes it,
|
||||
// they should instruct the users to clear local storage.
|
||||
if (typeof viewable === "boolean" && Date.now() - lastFetched < 8.64e7)
|
||||
return { viewable, error: null };
|
||||
|
||||
const res = await System.keys();
|
||||
const isViewable = res?.DisableViewChatHistory === false;
|
||||
|
||||
window.localStorage.setItem(
|
||||
this.cacheKeys.canViewChatHistory,
|
||||
JSON.stringify({ viewable: isViewable, lastFetched: Date.now() })
|
||||
);
|
||||
return { viewable: isViewable, error: null };
|
||||
},
|
||||
experimentalFeatures: {
|
||||
liveSync: LiveDocumentSync,
|
||||
agentPlugins: AgentPlugins,
|
||||
|
||||
@ -11,6 +11,7 @@ import { CaretDown, Download, Sparkle, Trash } from "@phosphor-icons/react";
|
||||
import { saveAs } from "file-saver";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import paths from "@/utils/paths";
|
||||
import { CanViewChatHistory } from "@/components/CanViewChatHistory";
|
||||
|
||||
const exportOptions = {
|
||||
csv: {
|
||||
@ -106,7 +107,8 @@ export default function WorkspaceChats() {
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchChats() {
|
||||
const { chats: _chats, hasPages = false } = await System.chats(offset);
|
||||
const { chats: _chats = [], hasPages = false } =
|
||||
await System.chats(offset);
|
||||
setChats(_chats);
|
||||
setCanNext(hasPages);
|
||||
setLoading(false);
|
||||
@ -115,6 +117,7 @@ export default function WorkspaceChats() {
|
||||
}, [offset]);
|
||||
|
||||
return (
|
||||
<CanViewChatHistory>
|
||||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
|
||||
<Sidebar />
|
||||
<div
|
||||
@ -194,6 +197,7 @@ export default function WorkspaceChats() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CanViewChatHistory>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import { CaretDown, Download } from "@phosphor-icons/react";
|
||||
import showToast from "@/utils/toast";
|
||||
import { saveAs } from "file-saver";
|
||||
import System from "@/models/system";
|
||||
import { CanViewChatHistory } from "@/components/CanViewChatHistory";
|
||||
|
||||
const exportOptions = {
|
||||
csv: {
|
||||
@ -88,6 +89,7 @@ export default function EmbedChats() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<CanViewChatHistory>
|
||||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
|
||||
<Sidebar />
|
||||
<div
|
||||
@ -141,6 +143,7 @@ export default function EmbedChats() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CanViewChatHistory>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -269,3 +269,11 @@ TTS_PROVIDER="native"
|
||||
|
||||
#------ SearXNG ----------- https://github.com/searxng/searxng
|
||||
# AGENT_SEARXNG_API_URL=
|
||||
|
||||
###########################################
|
||||
######## Other Configurations ############
|
||||
###########################################
|
||||
|
||||
# Disable viewing chat history from the UI and frontend APIs.
|
||||
# See https://docs.anythingllm.com/configuration#disable-view-chat-history for more information.
|
||||
# DISABLE_VIEW_CHAT_HISTORY=1
|
||||
@ -1,7 +1,6 @@
|
||||
const { EmbedChats } = require("../models/embedChats");
|
||||
const { EmbedConfig } = require("../models/embedConfig");
|
||||
const { EventLogs } = require("../models/eventLogs");
|
||||
const { Workspace } = require("../models/workspace");
|
||||
const { reqBody, userFromSession } = require("../utils/http");
|
||||
const { validEmbedConfigId } = require("../utils/middleware/embedMiddleware");
|
||||
const {
|
||||
@ -9,6 +8,9 @@ const {
|
||||
ROLES,
|
||||
} = require("../utils/middleware/multiUserProtected");
|
||||
const { validatedRequest } = require("../utils/middleware/validatedRequest");
|
||||
const {
|
||||
chatHistoryViewable,
|
||||
} = require("../utils/middleware/chatHistoryViewable");
|
||||
|
||||
function embedManagementEndpoints(app) {
|
||||
if (!app) return;
|
||||
@ -90,7 +92,7 @@ function embedManagementEndpoints(app) {
|
||||
|
||||
app.post(
|
||||
"/embed/chats",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||
[chatHistoryViewable, validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { offset = 0, limit = 20 } = reqBody(request);
|
||||
|
||||
@ -50,6 +50,9 @@ const {
|
||||
const { SlashCommandPresets } = require("../models/slashCommandsPresets");
|
||||
const { EncryptionManager } = require("../utils/EncryptionManager");
|
||||
const { BrowserExtensionApiKey } = require("../models/browserExtensionApiKey");
|
||||
const {
|
||||
chatHistoryViewable,
|
||||
} = require("../utils/middleware/chatHistoryViewable");
|
||||
|
||||
function systemEndpoints(app) {
|
||||
if (!app) return;
|
||||
@ -961,7 +964,11 @@ function systemEndpoints(app) {
|
||||
|
||||
app.post(
|
||||
"/system/workspace-chats",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
|
||||
[
|
||||
chatHistoryViewable,
|
||||
validatedRequest,
|
||||
flexUserRoleValid([ROLES.admin, ROLES.manager]),
|
||||
],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { offset = 0, limit = 20 } = reqBody(request);
|
||||
@ -1001,7 +1008,11 @@ function systemEndpoints(app) {
|
||||
|
||||
app.get(
|
||||
"/system/export-chats",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.manager, ROLES.admin])],
|
||||
[
|
||||
chatHistoryViewable,
|
||||
validatedRequest,
|
||||
flexUserRoleValid([ROLES.manager, ROLES.admin]),
|
||||
],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { type = "jsonl", chatType = "workspace" } = request.query;
|
||||
|
||||
@ -246,6 +246,13 @@ const SystemSettings = {
|
||||
AgentSerplyApiKey: !!process.env.AGENT_SERPLY_API_KEY || null,
|
||||
AgentSearXNGApiUrl: process.env.AGENT_SEARXNG_API_URL || null,
|
||||
AgentTavilyApiKey: !!process.env.AGENT_TAVILY_API_KEY || null,
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Compliance Settings
|
||||
// --------------------------------------------------------
|
||||
// Disable View Chat History for the whole instance.
|
||||
DisableViewChatHistory:
|
||||
"DISABLE_VIEW_CHAT_HISTORY" in process.env || false,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@ -886,6 +886,8 @@ function dumpENV() {
|
||||
"ENABLE_HTTPS",
|
||||
"HTTPS_CERT_PATH",
|
||||
"HTTPS_KEY_PATH",
|
||||
// Other Configuration Keys
|
||||
"DISABLE_VIEW_CHAT_HISTORY",
|
||||
];
|
||||
|
||||
// Simple sanitization of each value to prevent ENV injection via newline or quote escaping.
|
||||
|
||||
18
server/utils/middleware/chatHistoryViewable.js
Normal file
18
server/utils/middleware/chatHistoryViewable.js
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* A simple middleware that validates that the chat history is viewable.
|
||||
* via the `DISABLE_VIEW_CHAT_HISTORY` environment variable being set AT ALL.
|
||||
* @param {Request} request - The request object.
|
||||
* @param {Response} response - The response object.
|
||||
* @param {NextFunction} next - The next function.
|
||||
*/
|
||||
function chatHistoryViewable(_request, response, next) {
|
||||
if ("DISABLE_VIEW_CHAT_HISTORY" in process.env)
|
||||
return response
|
||||
.status(422)
|
||||
.send("This feature has been disabled by the administrator.");
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
chatHistoryViewable,
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user