Skip to content
Open
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
186 changes: 101 additions & 85 deletions apps/roam/src/components/settings/AdminPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useState, useEffect, useMemo } from "react";
import React, { useState, useEffect } from "react";
import {
Button,
Checkbox,
HTMLTable,
Alert,
Intent,
Expand All @@ -12,7 +11,6 @@ import {
TabId,
Tabs,
} from "@blueprintjs/core";
import Description from "roamjs-components/components/Description";
import { Select } from "@blueprintjs/select";
import {
getSupabaseContext,
Expand All @@ -29,15 +27,13 @@ import {
import type { DGSupabaseClient } from "@repo/database/lib/client";
import internalError from "~/utils/internalError";
import SuggestiveModeSettings from "./SuggestiveModeSettings";
import { getFormattedConfigTree } from "~/utils/discourseConfigRef";
import refreshConfigTree from "~/utils/refreshConfigTree";
import createBlock from "roamjs-components/writes/createBlock";
import deleteBlock from "roamjs-components/writes/deleteBlock";
import {
setFeatureFlag,
getFeatureFlag,
isSyncEnabled,
} from "~/components/settings/utils/accessors";
import { FeatureFlagPanel } from "./components/BlockPropSettingPanels";
import type { FeatureFlags } from "./utils/zodSchema";

const NodeRow = ({ node }: { node: PConceptFull }) => {
return (
Expand Down Expand Up @@ -258,106 +254,130 @@ const NodeListTab = (): React.ReactElement => {
};

const FeatureFlagsTab = (): React.ReactElement => {
const settings = useMemo(() => {
refreshConfigTree();
return getFormattedConfigTree();
}, []);

const [suggestiveModeEnabled, setSuggestiveModeEnabled] = useState(
settings.suggestiveModeEnabled.value || false,
const [isConsentAlertOpen, setIsConsentAlertOpen] = useState(false);
const [isInstructionAlertOpen, setIsInstructionAlertOpen] = useState(false);
const [isFirstSyncEnable, setIsFirstSyncEnable] = useState(false);
const [pendingFeatureKey, setPendingFeatureKey] = useState<
keyof FeatureFlags | null
>(null);
const [duplicateNodeAlertValue, setDuplicateNodeAlertValue] = useState(
getFeatureFlag("Duplicate node alert enabled"),
);
const [suggestiveModeUid, setSuggestiveModeUid] = useState(
settings.suggestiveModeEnabled.uid,
const [suggestiveOverlayValue, setSuggestiveOverlayValue] = useState(
getFeatureFlag("Suggestive mode overlay enabled"),
);
const [isAlertOpen, setIsAlertOpen] = useState(false);
const [isInstructionOpen, setIsInstructionOpen] = useState(false);

const syncAlreadyEnabled = duplicateNodeAlertValue || suggestiveOverlayValue;

const ensureSyncEnabled = (
featureKey: keyof FeatureFlags,
): Promise<boolean> => {
if (syncAlreadyEnabled) {
return Promise.resolve(true);
}
setPendingFeatureKey(featureKey);
setIsConsentAlertOpen(true);
return Promise.resolve(false);
};

const handleFeatureToggled = (
checked: boolean,
setter: (v: boolean) => void,
) => {
setter(checked);
if (checked) {
setIsInstructionAlertOpen(true);
}
};
Comment on lines +283 to +291
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider prompting for reload when disabling features too.

The reload prompt is only shown when checked is true (enabling). However, based on the initialization logic in initializeObserversAndListeners.ts, disabling a feature flag also requires a reload to take effect—the observer registered at startup will remain active until the page reloads.

Users might disable the feature expecting an immediate effect, but the observer will persist.

💡 Suggested fix
 const handleFeatureToggled = (
   checked: boolean,
   setter: (v: boolean) => void,
 ) => {
   setter(checked);
-  if (checked) {
-    setIsInstructionAlertOpen(true);
-  }
+  setIsInstructionAlertOpen(true);
 };

Then update the instruction alert message to be more generic (applicable for both enable/disable):

-<p>Please reload the graph for this change to take effect.</p>
+<p>Please reload the graph for this setting change to take effect.</p>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/roam/src/components/settings/AdminPanel.tsx` around lines 283 - 291,
handleFeatureToggled currently only opens the reload/instruction alert when
enabling (checked === true); change it to open the alert for both enable and
disable by calling setIsInstructionAlertOpen(true) after calling setter(checked)
(i.e., remove the checked-only condition), and update the instruction alert
text/state (used where setIsInstructionAlertOpen is read) to a generic message
that applies to both enabling and disabling so users are prompted to reload when
the observer change requires it.


return (
<div className="flex flex-col gap-4 p-4">
<Checkbox
checked={suggestiveModeEnabled}
onChange={(e) => {
const checked = (e.target as HTMLInputElement).checked;
if (checked) {
setIsAlertOpen(true);
} else {
if (suggestiveModeUid) {
void deleteBlock(suggestiveModeUid);
setSuggestiveModeUid(undefined);
}
setSuggestiveModeEnabled(false);
setFeatureFlag("Suggestive mode enabled", false);
}
}}
labelElement={
<>
(BETA) Suggestive mode enabled
<Description
description={
"Whether or not to enable the suggestive mode, if this is first time enabling it, you will need to generate and upload all node embeddings to supabase. Go to Suggestive Mode -> Sync Config -> Click on 'Generate & Upload All Node Embeddings'"
}
/>
</>
<FeatureFlagPanel
title="Duplicate node alert"
description="Show possible duplicate nodes when viewing a discourse node page."
featureKey="Duplicate node alert enabled"
value={duplicateNodeAlertValue}
onBeforeEnable={() => ensureSyncEnabled("Duplicate node alert enabled")}
onAfterChange={(checked) =>
handleFeatureToggled(checked, setDuplicateNodeAlertValue)
}
/>

<FeatureFlagPanel
title="Suggestive mode overlay"
description="Overlay suggestive mode button over discourse node references."
featureKey="Suggestive mode overlay enabled"
value={suggestiveOverlayValue}
onBeforeEnable={() =>
ensureSyncEnabled("Suggestive mode overlay enabled")
}
onAfterChange={(checked) =>
handleFeatureToggled(checked, setSuggestiveOverlayValue)
}
/>

<Alert
isOpen={isAlertOpen}
isOpen={isConsentAlertOpen}
onConfirm={() => {
void createBlock({
parentUid: settings.settingsUid,
node: { text: "(BETA) Suggestive Mode Enabled" },
}).then((uid) => {
setSuggestiveModeUid(uid);
setSuggestiveModeEnabled(true);
setFeatureFlag("Suggestive mode enabled", true);
setIsAlertOpen(false);
setIsInstructionOpen(true);
});
if (pendingFeatureKey) {
setFeatureFlag(pendingFeatureKey, true);
if (pendingFeatureKey === "Duplicate node alert enabled") {
setDuplicateNodeAlertValue(true);
} else if (
pendingFeatureKey === "Suggestive mode overlay enabled"
) {
setSuggestiveOverlayValue(true);
}
}
setIsConsentAlertOpen(false);
setIsFirstSyncEnable(true);
setIsInstructionAlertOpen(true);
}}
onCancel={() => {
setPendingFeatureKey(null);
setIsConsentAlertOpen(false);
}}
onCancel={() => setIsAlertOpen(false)}
canEscapeKeyCancel={true}
canOutsideClickCancel={true}
intent={Intent.PRIMARY}
confirmButtonText="Enable"
cancelButtonText="Cancel"
>
<p>
Enabling Suggestive Mode will send your data (nodes) to our servers
and OpenAI servers to generate embeddings and suggestions.
Enabling this feature will send your data (nodes) to our servers and
OpenAI servers to generate embeddings and suggestions.
</p>
<p>Are you sure you want to proceed?</p>
</Alert>

<Alert
isOpen={isInstructionOpen}
isOpen={isInstructionAlertOpen}
onConfirm={() => window.location.reload()}
onCancel={() => setIsInstructionOpen(false)}
onCancel={() => {
setIsInstructionAlertOpen(false);
setIsFirstSyncEnable(false);
}}
confirmButtonText="Reload Graph"
cancelButtonText="Later"
intent={Intent.PRIMARY}
>
<p>
If this is the first time enabling it, you will need to generate and
upload all node embeddings to supabase.
</p>
<p>
Please reload the graph to see the new &apos;Suggestive Mode&apos;
tab.
</p>
<p>
Then go to Suggestive Mode{" "}
{"-> Sync Config -> Click on 'Generate & Upload All Node Embeddings'"}
</p>
<p>Please reload the graph for this change to take effect.</p>
{isFirstSyncEnable && (
<>
<p>
If this is the first time enabling sync, you will need to generate
and upload all node embeddings.
</p>
<p>
After reloading, go to Sync mode{" "}
{
"-> Sync config -> Click on 'Generate & Upload All Node Embeddings'"
}
</p>
</>
)}
</Alert>

<FeatureFlagPanel
title="Duplicate node alert"
description="Show possible duplicate nodes when viewing a discourse node page. Requires Suggestive mode to be enabled."
featureKey="Duplicate node alert enabled"
initialValue={getFeatureFlag("Duplicate node alert enabled")}
disabled={!suggestiveModeEnabled}
/>

<Button
className="w-96"
icon="send-message"
Expand All @@ -379,10 +399,6 @@ const FeatureFlagsTab = (): React.ReactElement => {

const AdminPanel = (): React.ReactElement => {
const [selectedTabId, setSelectedTabId] = useState<TabId>("admin");
const settings = useMemo(() => {
refreshConfigTree();
return getFormattedConfigTree();
}, []);

return (
<Tabs
Expand All @@ -408,10 +424,10 @@ const AdminPanel = (): React.ReactElement => {
</div>
}
/>
{settings.suggestiveModeEnabled.value && (
{isSyncEnabled() && (
<Tab
id="suggestive-mode-settings"
title="Suggestive mode"
id="sync-mode-settings"
title="Sync mode"
className="overflow-y-auto"
panel={<SuggestiveModeSettings />}
/>
Expand Down
20 changes: 1 addition & 19 deletions apps/roam/src/components/settings/HomePersonalSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo, useState } from "react";
import React, { useState } from "react";
import { OnloadArgs } from "roamjs-components/types";
import { render as renderToast } from "roamjs-components/components/Toast";
import { Label, Dialog, Button, Intent, Classes } from "@blueprintjs/core";
Expand All @@ -7,7 +7,6 @@ import { addStyle } from "roamjs-components/dom";
import { NodeMenuTriggerComponent } from "~/components/DiscourseNodeMenu";
import {
getOverlayHandler,
getSuggestiveOverlayHandler,
onPageRefObserverChange,
previewPageRefHandler,
} from "~/utils/pageRefObserverHandlers";
Expand All @@ -28,7 +27,6 @@ import { getSetting, setSetting } from "~/utils/extensionSettings";
import { enablePostHog, disablePostHog } from "~/utils/posthog";
import KeyboardShortcutInput from "./KeyboardShortcutInput";
import streamlineStyling from "~/styles/streamlineStyling";
import { getFormattedConfigTree } from "~/utils/discourseConfigRef";
import { PersonalFlagPanel } from "./components/BlockPropSettingPanels";
import migrateRelations from "~/utils/migrateRelations";
import { countReifiedRelations } from "~/utils/createReifiedBlock";
Expand All @@ -47,7 +45,6 @@ const enum RelationMigrationDialog {
const HomePersonalSettings = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => {
const extensionAPI = onloadArgs.extensionAPI;
const overlayHandler = getOverlayHandler(onloadArgs);
const settings = useMemo(() => getFormattedConfigTree(), []);
const [activeRelationMigration, setActiveRelationMigration] =
useState<RelationMigrationDialog>(RelationMigrationDialog.none);
const [numExistingRelations, setNumExistingRelations] = useState<number>(0);
Expand Down Expand Up @@ -152,21 +149,6 @@ const HomePersonalSettings = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => {
});
}}
/>
{settings.suggestiveModeEnabled?.value && (
<PersonalFlagPanel
title="Suggestive mode overlay"
description="Whether or not to overlay suggestive mode button over discourse node references."
settingKeys={["Suggestive mode overlay"]}
initialValue={getSetting<boolean>("suggestive-mode-overlay", false)}
onChange={(checked) => {
void setSetting("suggestive-mode-overlay", checked);
onPageRefObserverChange(getSuggestiveOverlayHandler(onloadArgs))(
checked,
);
}}
/>
)}

<PersonalFlagPanel
title="Enable stored relations"
description="Use stored relations instead of legacy pattern-based relations"
Expand Down
11 changes: 3 additions & 8 deletions apps/roam/src/components/settings/NodeConfig.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useCallback, useEffect, useMemo } from "react";
import React, { useState, useCallback, useEffect } from "react";
import { DiscourseNode } from "~/utils/getDiscourseNodes";
import DualWriteBlocksPanel from "./components/EphemeralBlocksPanel";
import { getSubTree } from "roamjs-components/util";
Expand All @@ -11,8 +11,7 @@ import DiscourseNodeIndex from "./DiscourseNodeIndex";
import { OnloadArgs } from "roamjs-components/types";
import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid";
import DiscourseNodeSuggestiveRules from "./DiscourseNodeSuggestiveRules";
import { getFormattedConfigTree } from "~/utils/discourseConfigRef";
import refreshConfigTree from "~/utils/refreshConfigTree";
import { isSyncEnabled } from "~/components/settings/utils/accessors";
import {
DiscourseNodeTextPanel,
DiscourseNodeFlagPanel,
Expand Down Expand Up @@ -45,10 +44,6 @@ const NodeConfig = ({
node: DiscourseNode;
onloadArgs: OnloadArgs;
}) => {
const settings = useMemo(() => {
refreshConfigTree();
return getFormattedConfigTree();
}, []);
const getUid = (key: string) =>
getSubTree({
parentUid: node.type,
Expand Down Expand Up @@ -296,7 +291,7 @@ const NodeConfig = ({
</div>
}
/>
{settings.suggestiveModeEnabled.value && (
{isSyncEnabled() && (
<Tab
id="suggestive-mode"
title="Suggestive mode"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ export const FeatureFlagPanel = ({
description,
featureKey,
initialValue,
value,
disabled,
onBeforeEnable,
onAfterChange,
Expand All @@ -512,6 +513,7 @@ export const FeatureFlagPanel = ({
description: string;
featureKey: keyof FeatureFlags;
initialValue?: boolean;
value?: boolean;
disabled?: boolean;
onBeforeEnable?: () => Promise<boolean>;
onAfterChange?: (checked: boolean) => void;
Expand All @@ -534,6 +536,7 @@ export const FeatureFlagPanel = ({
settingKeys={[featureKey as string]}
setter={featureFlagSetter}
initialValue={initialValue}
value={value}
disabled={disabled}
onBeforeChange={handleBeforeChange}
onChange={onAfterChange}
Expand Down
4 changes: 4 additions & 0 deletions apps/roam/src/components/settings/utils/accessors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ export const getFeatureFlag = (key: keyof FeatureFlags): boolean => {
return flags[key];
};

export const isSyncEnabled = (): boolean =>
getFeatureFlag("Duplicate node alert enabled") ||
getFeatureFlag("Suggestive mode overlay enabled");

export const setFeatureFlag = (
key: keyof FeatureFlags,
value: boolean,
Expand Down
Loading
Loading