Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] Implement new settings menu for experimental features #1735

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { ArrowSquareOut } from "@phosphor-icons/react";
import { useState } from "react";
import { Link } from "react-router-dom";

export default function LiveSyncToggle({ enabled = false }) {
export default function LiveSyncToggle({ enabled = false, onToggle }) {
const [status, setStatus] = useState(enabled);

async function toggleFeatureFlag() {
const updated = await System.experimentalFeatures.liveSync.toggleFeature(
!status
);
if (!updated) {
showToast(`Failed to update status of feature.`, "error", {
showToast("Failed to update status of feature.", "error", {
clear: true,
});
return false;
Expand All @@ -27,60 +27,63 @@ export default function LiveSyncToggle({ enabled = false }) {
"success",
{ clear: true }
);
onToggle();
}

return (
<div className="relative w-full max-h-full">
<div className="relative rounded-lg">
<div className="flex items-start justify-between px-6 py-4"></div>
<div className="space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
<div className="">
<label className="mb-2.5 block font-medium text-white">
Automatic document content sync
</label>
<label className="relative inline-flex cursor-pointer items-center">
<input
type="checkbox"
onClick={toggleFeatureFlag}
checked={status}
className="peer sr-only pointer-events-none"
/>
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-stone-400 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border after:border-gray-600 after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-lime-300 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800"></div>
</label>
</div>
</div>
<div className="p-4">
<div className="flex flex-col gap-y-6 max-w-[500px]">
<div className="flex items-center justify-between">
<h2 className="text-white text-md font-bold">
Automatic Document Content Sync
</h2>
<label className="relative inline-flex cursor-pointer items-center">
<input
type="checkbox"
onClick={toggleFeatureFlag}
checked={status}
className="peer sr-only pointer-events-none"
/>
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-stone-400 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border after:border-gray-600 after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-lime-300 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800"></div>
</label>
</div>
<div className="flex flex-col items-left space-y-2">
<p className="text-white/80 text-xs rounded-lg w-96">
<div className="flex flex-col space-y-4">
<p className="text-white/90 text-sm">
Enable the ability to specify a document to be "watched". Watched
document's content will be regularly fetched and updated in
AnythingLLM.
</p>
<p className="text-white/80 text-xs rounded-lg w-96">
Watched documents will automatically update the in all workspaces it
is referenced in at the same time of update.
<p className="text-white/90 text-sm">
Watched documents will automatically update in all workspaces they
are referenced in at the same time of update.
</p>
<p className="text-white/80 text-xs rounded-lg w-96 italic">
this feature only applies to web-based content. eg: Websites,
Confluence, YouTube, and Github files.
<p className="text-white/80 text-xs italic">
This feature only applies to web-based content, such as websites,
Confluence, YouTube, and GitHub files.
</p>
</div>
</div>
<div className="w-full flex flex-col gap-2 my-2">
<a
href="https://docs.useanything.com/beta-preview/active-features/live-document-sync"
target="_blank"
className="text-sm text-white rounded-full hover:underline flex items-center gap-x-2"
>
Feature documentation and warnings <ArrowSquareOut size={14} />
</a>
<Link
to={paths.experimental.liveDocumentSync.manage()}
className="text-sm text-white rounded-full hover:underline"
>
Manage watched documents &rarr;
</Link>
<div className="mt-8">
<ul className="space-y-2">
<li>
<a
href="https://docs.useanything.com/beta-preview/active-features/live-document-sync"
target="_blank"
className="text-sm text-blue-400 hover:underline flex items-center gap-x-1"
>
<ArrowSquareOut size={14} />
<span>Feature Documentation and Warnings</span>
</a>
</li>
<li>
<Link
to={paths.experimental.liveDocumentSync.manage()}
className="text-sm text-blue-400 hover:underline"
>
Manage Watched Documents &rarr;
</Link>
</li>
</ul>
</div>
</div>
);
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/pages/Admin/ExperimentalFeatures/features.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import LiveSyncToggle from "./Features/LiveSync/toggle";

export const configurableFeatures = {
experimental_live_file_sync: {
title: "Live Document Sync",
component: LiveSyncToggle,
key: "experimental_live_file_sync",
},
};
155 changes: 124 additions & 31 deletions frontend/src/pages/Admin/ExperimentalFeatures/index.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { useEffect, useState } from "react";
import Sidebar from "@/components/SettingsSidebar";
import { isMobile } from "react-device-detect";
import PreLoader from "@/components/Preloader";
import Admin from "@/models/admin";
import { FullScreenLoader } from "@/components/Preloader";
import { CaretRight, Flask } from "@phosphor-icons/react";
import { configurableFeatures } from "./features";
import ModalWrapper from "@/components/ModalWrapper";
import paths from "@/utils/paths";
import showToast from "@/utils/toast";
import LiveSyncToggle from "./Features/LiveSync/toggle";
import Admin from "@/models/admin";

export default function ExperimentalFeatures() {
const [featureFlags, setFeatureFlags] = useState({});
const [loading, setLoading] = useState(true);
const [selectedFeature, setSelectedFeature] = useState(
"experimental_live_file_sync"
);

useEffect(() => {
async function fetchSettings() {
Expand All @@ -22,46 +26,135 @@ export default function ExperimentalFeatures() {
fetchSettings();
}, []);

const refresh = async () => {
const { settings } = await Admin.systemPreferences();
setFeatureFlags(settings?.feature_flags ?? {});
};

if (loading) {
return (
<div
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] w-full h-full flex justify-center items-center"
>
<FullScreenLoader />
</div>
);
}

return (
<FeatureLayout>
<div className="flex-1 flex gap-x-6 p-4 mt-10">
{/* Feature settings nav */}
<div className="flex flex-col gap-y-[18px]">
<div className="text-white flex items-center gap-x-2">
<Flask size={24} />
<p className="text-lg font-medium">Experimental Features</p>
</div>
{/* Feature list */}
<FeatureList
features={configurableFeatures}
selectedFeature={selectedFeature}
handleClick={setSelectedFeature}
activeFeatures={Object.keys(featureFlags).filter(
(flag) => featureFlags[flag]
)}
/>
</div>

{/* Selected feature setting panel */}
<FeatureVerification>
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-4">
{selectedFeature ? (
<SelectedFeatureComponent
feature={configurableFeatures[selectedFeature]}
settings={featureFlags}
refresh={refresh}
/>
) : (
<div className="flex flex-col items-center justify-center h-full text-white/60">
<Flask size={40} />
<p className="font-medium">Select an experimental feature</p>
</div>
)}
</div>
</div>
</FeatureVerification>
</div>
</FeatureLayout>
);
}

function FeatureLayout({ children }) {
return (
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
<div
id="workspace-feature-settings-container"
className="w-screen h-screen overflow-hidden bg-sidebar flex md:mt-0 mt-6"
>
<Sidebar />
<div
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-main-gradient w-full h-full overflow-y-scroll"
className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] w-full h-full flex"
>
<div className="flex flex-col w-full px-1 md:pl-6 md:pr-[50px] md:py-6 py-16">
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
<div className="items-center flex gap-x-4">
<p className="text-lg leading-6 font-bold text-white">
Experimental Features
</p>
{children}
</div>
</div>
);
}

function FeatureList({
features = [],
selectedFeature = null,
handleClick = null,
activeFeatures = [],
}) {
if (Object.keys(features).length === 0) return null;

return (
<div
className={`bg-white/5 text-white rounded-xl ${
isMobile ? "w-full" : "min-w-[360px] w-fit"
}`}
>
{Object.entries(features).map(([feature, settings], index) => (
<div
key={feature}
className={`py-3 px-4 flex items-center justify-between ${
index === 0 ? "rounded-t-xl" : ""
} ${
index === Object.keys(features).length - 1
? "rounded-b-xl"
: "border-b border-white/10"
} cursor-pointer transition-all duration-300 hover:bg-white/5 ${
selectedFeature === feature ? "bg-white/10" : ""
}`}
onClick={() => handleClick?.(feature)}
>
<div className="text-sm font-light">{settings.title}</div>
<div className="flex items-center gap-x-2">
<div className="text-sm text-white/60 font-medium">
{activeFeatures.includes(settings.key) ? "On" : "Off"}
</div>
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
Enable and access experimental features of AnythingLLM. Features
here may change, move, or no longer exist at any time and support
for them is not guaranteed. While a feature is in preview it can
only be managed via this page.
</p>
<CaretRight size={14} weight="bold" className="text-white/80" />
</div>
{loading ? (
<div className="h-1/2 transition-all duration-500 relative md:ml-[2px] md:mr-[8px] md:my-[16px] md:rounded-[26px] p-[18px] h-full overflow-y-scroll">
<div className="w-full h-full flex justify-center items-center">
<PreLoader />
</div>
</div>
) : (
<FeatureVerification>
<LiveSyncToggle
enabled={featureFlags?.experimental_live_file_sync}
/>
</FeatureVerification>
)}
</div>
</div>
))}
</div>
);
}

function SelectedFeatureComponent({ feature, settings, refresh }) {
const Component = feature?.component;
return Component ? (
<Component
enabled={settings[feature.key]}
feature={feature.key}
onToggle={refresh}
/>
) : null;
}

function FeatureVerification({ children }) {
if (
!window.localStorage.getItem("anythingllm_tos_experimental_feature_set")
Expand Down