749 lines
27 KiB
JavaScript
749 lines
27 KiB
JavaScript
/**
|
|
* Core settings persistence and config application.
|
|
* Handles load/save from SettingsStore and application to APP_CONFIG.
|
|
*/
|
|
|
|
import { APP_CONFIG, APP_DEFAULTS } from "../config.js";
|
|
import {
|
|
getSecuritySettings,
|
|
bootstrapSecurityToken,
|
|
getVectorSearchSettings,
|
|
getExecutionGroupingSettings,
|
|
} from "../../api/client.js";
|
|
import { safeDispatchCustomEvent } from "../../utils/events.js";
|
|
import { SettingsStore } from "./SettingsStore.js";
|
|
import { SETTINGS_KEY, SETTINGS_SCHEMA_VERSION } from "../settingsStore.js";
|
|
import {
|
|
_safeBool,
|
|
_safeNum,
|
|
deepMerge,
|
|
resolveFeedGridMinSize,
|
|
resolveGridMinSize,
|
|
} from "./settingsUtils.js";
|
|
import { t } from "../i18n.js";
|
|
|
|
// ─── Default settings ─────────────────────────────────────────────────────
|
|
|
|
export const DEFAULT_SETTINGS = {
|
|
debug: {
|
|
safeCall: APP_DEFAULTS.DEBUG_SAFE_CALL,
|
|
safeListeners: APP_DEFAULTS.DEBUG_SAFE_LISTENERS,
|
|
viewer: APP_DEFAULTS.DEBUG_VIEWER,
|
|
},
|
|
grid: {
|
|
pageSize: APP_DEFAULTS.DEFAULT_PAGE_SIZE,
|
|
minSize: APP_DEFAULTS.GRID_MIN_SIZE,
|
|
minSizePreset: "medium",
|
|
gap: APP_DEFAULTS.GRID_GAP,
|
|
showExtBadge: APP_DEFAULTS.GRID_SHOW_BADGES_EXTENSION,
|
|
showRatingBadge: APP_DEFAULTS.GRID_SHOW_BADGES_RATING,
|
|
showTagsBadge: APP_DEFAULTS.GRID_SHOW_BADGES_TAGS,
|
|
showDetails: APP_DEFAULTS.GRID_SHOW_DETAILS,
|
|
showFilename: APP_DEFAULTS.GRID_SHOW_DETAILS_FILENAME,
|
|
showDate: APP_DEFAULTS.GRID_SHOW_DETAILS_DATE,
|
|
showDimensions: APP_DEFAULTS.GRID_SHOW_DETAILS_DIMENSIONS,
|
|
showGenTime: APP_DEFAULTS.GRID_SHOW_DETAILS_GENTIME,
|
|
showHoverInfo: APP_DEFAULTS.GRID_SHOW_HOVER_INFO,
|
|
showWorkflowDot: APP_DEFAULTS.GRID_SHOW_WORKFLOW_DOT,
|
|
videoAutoplayMode: APP_DEFAULTS.GRID_VIDEO_AUTOPLAY_MODE,
|
|
starColor: APP_DEFAULTS.BADGE_STAR_COLOR,
|
|
badgeImageColor: APP_DEFAULTS.BADGE_IMAGE_COLOR,
|
|
badgeVideoColor: APP_DEFAULTS.BADGE_VIDEO_COLOR,
|
|
badgeAudioColor: APP_DEFAULTS.BADGE_AUDIO_COLOR,
|
|
badgeModel3dColor: APP_DEFAULTS.BADGE_MODEL3D_COLOR,
|
|
badgeDuplicateAlertColor: APP_DEFAULTS.BADGE_DUPLICATE_ALERT_COLOR,
|
|
},
|
|
infiniteScroll: {
|
|
enabled: APP_DEFAULTS.INFINITE_SCROLL_ENABLED,
|
|
rootMargin: APP_DEFAULTS.INFINITE_SCROLL_ROOT_MARGIN,
|
|
threshold: APP_DEFAULTS.INFINITE_SCROLL_THRESHOLD,
|
|
bottomGapPx: APP_DEFAULTS.BOTTOM_GAP_PX,
|
|
},
|
|
siblings: {
|
|
hidePngSiblings: true,
|
|
},
|
|
autoScan: {
|
|
onStartup: APP_DEFAULTS.AUTO_SCAN_ON_STARTUP,
|
|
},
|
|
scan: {
|
|
fastMode: true,
|
|
},
|
|
watcher: {
|
|
enabled: true,
|
|
debounceMs: APP_DEFAULTS.WATCHER_DEBOUNCE_MS,
|
|
dedupeTtlMs: APP_DEFAULTS.WATCHER_DEDUPE_TTL_MS,
|
|
maxPending: 500,
|
|
minSize: 100,
|
|
maxSize: 4294967296,
|
|
},
|
|
safety: {
|
|
confirmDeletion: true,
|
|
},
|
|
status: {
|
|
pollInterval: APP_DEFAULTS.STATUS_POLL_INTERVAL,
|
|
},
|
|
viewer: {
|
|
allowPanAtZoom1: APP_DEFAULTS.VIEWER_ALLOW_PAN_AT_ZOOM_1,
|
|
disableWebGL: APP_DEFAULTS.VIEWER_DISABLE_WEBGL_VIDEO,
|
|
pauseDuringExecution: APP_DEFAULTS.VIEWER_PAUSE_DURING_EXECUTION,
|
|
floatingPauseDuringExecution: APP_DEFAULTS.FLOATING_VIEWER_PAUSE_DURING_EXECUTION,
|
|
mfvLiveDefault: APP_DEFAULTS.MFV_LIVE_DEFAULT,
|
|
mfvPreviewDefault: APP_DEFAULTS.MFV_PREVIEW_DEFAULT,
|
|
videoGradeThrottleFps: APP_DEFAULTS.VIEWER_VIDEO_GRADE_THROTTLE_FPS,
|
|
scopesFps: APP_DEFAULTS.VIEWER_SCOPES_FPS,
|
|
metaTtlMs: APP_DEFAULTS.VIEWER_META_TTL_MS,
|
|
metaMaxEntries: APP_DEFAULTS.VIEWER_META_MAX_ENTRIES,
|
|
mfvSidebarPosition: "right",
|
|
mfvPreviewMethod: APP_DEFAULTS.MFV_PREVIEW_METHOD,
|
|
ltxavRgbFallback: false,
|
|
},
|
|
rtHydrate: {
|
|
concurrency: APP_DEFAULTS.RT_HYDRATE_CONCURRENCY,
|
|
queueMax: APP_DEFAULTS.RT_HYDRATE_QUEUE_MAX,
|
|
seenMax: APP_DEFAULTS.RT_HYDRATE_SEEN_MAX,
|
|
pruneBudget: APP_DEFAULTS.RT_HYDRATE_PRUNE_BUDGET,
|
|
seenTtlMs: APP_DEFAULTS.RT_HYDRATE_SEEN_TTL_MS,
|
|
},
|
|
observability: {
|
|
enabled: false,
|
|
verboseErrors: false,
|
|
verboseRouteRegistrationLogs: false,
|
|
verboseStartupLogs: false,
|
|
},
|
|
feed: {
|
|
minSize: APP_DEFAULTS.FEED_GRID_MIN_SIZE,
|
|
showInfo: APP_DEFAULTS.FEED_SHOW_INFO,
|
|
showFilename: APP_DEFAULTS.FEED_SHOW_FILENAME,
|
|
showDimensions: APP_DEFAULTS.FEED_SHOW_DIMENSIONS,
|
|
showDate: APP_DEFAULTS.FEED_SHOW_DATE,
|
|
showGenTime: APP_DEFAULTS.FEED_SHOW_GENTIME,
|
|
showWorkflowDot: APP_DEFAULTS.FEED_SHOW_WORKFLOW_DOT,
|
|
showExtBadge: APP_DEFAULTS.FEED_SHOW_BADGES_EXTENSION,
|
|
showRatingBadge: APP_DEFAULTS.FEED_SHOW_BADGES_RATING,
|
|
showTagsBadge: APP_DEFAULTS.FEED_SHOW_BADGES_TAGS,
|
|
},
|
|
sidebar: {
|
|
position: "right",
|
|
showPreviewThumb: true,
|
|
widthPx: 360,
|
|
},
|
|
probeBackend: {
|
|
mode: "auto",
|
|
},
|
|
i18n: {
|
|
followComfyLanguage: true,
|
|
},
|
|
metadataFallback: {
|
|
image: true,
|
|
media: true,
|
|
},
|
|
paths: {
|
|
outputDirectory: "",
|
|
indexDirectory: "",
|
|
},
|
|
db: {
|
|
timeoutMs: 5000,
|
|
maxConnections: 10,
|
|
queryTimeoutMs: 1000,
|
|
},
|
|
ratingTagsSync: {
|
|
enabled: true,
|
|
},
|
|
cache: {
|
|
tagsTTLms: 30_000,
|
|
},
|
|
search: {
|
|
maxResults: APP_DEFAULTS.SEARCH_DEFAULT_LIMIT,
|
|
},
|
|
ai: {
|
|
vectorSearchEnabled: true,
|
|
verboseAiLogs: false,
|
|
},
|
|
executionGrouping: {
|
|
enabled: APP_DEFAULTS.EXECUTION_GROUPING_ENABLED,
|
|
},
|
|
workflowMinimap: {
|
|
enabled: false,
|
|
nodeColors: true,
|
|
showLinks: true,
|
|
showGroups: true,
|
|
renderBypassState: true,
|
|
renderErrorState: true,
|
|
showViewport: true,
|
|
showNodeLabels: false,
|
|
size: "comfortable",
|
|
},
|
|
ui: {
|
|
cardHoverColor: APP_DEFAULTS.UI_CARD_HOVER_COLOR,
|
|
cardSelectionColor: APP_DEFAULTS.UI_CARD_SELECTION_COLOR,
|
|
ratingColor: APP_DEFAULTS.UI_RATING_COLOR,
|
|
tagColor: APP_DEFAULTS.UI_TAG_COLOR,
|
|
},
|
|
security: {
|
|
safeMode: false,
|
|
allowWrite: true,
|
|
allowRemoteWrite: false,
|
|
allowDelete: true,
|
|
allowRename: true,
|
|
allowOpenInFolder: true,
|
|
allowResetIndex: true,
|
|
apiToken: "",
|
|
tokenConfigured: false,
|
|
tokenHint: "",
|
|
},
|
|
};
|
|
|
|
// ─── Load / Save ──────────────────────────────────────────────────────────
|
|
|
|
export const loadMajoorSettings = () => {
|
|
try {
|
|
const raw = SettingsStore.get(SETTINGS_KEY);
|
|
if (!raw) return { ...DEFAULT_SETTINGS };
|
|
const parsed = JSON.parse(raw);
|
|
const isWrapped =
|
|
parsed &&
|
|
typeof parsed === "object" &&
|
|
Number.isInteger(parsed.version) &&
|
|
parsed.data &&
|
|
typeof parsed.data === "object";
|
|
const isLegacyObject = parsed && typeof parsed === "object" && !Array.isArray(parsed);
|
|
if (!isWrapped && !isLegacyObject) return { ...DEFAULT_SETTINGS };
|
|
if (isWrapped && Number(parsed.version) > Number(SETTINGS_SCHEMA_VERSION)) {
|
|
console.warn(
|
|
"[Majoor] settings schema version is newer than this build, using defaults",
|
|
);
|
|
return { ...DEFAULT_SETTINGS };
|
|
}
|
|
const payload = isWrapped ? parsed.data : parsed;
|
|
const allowed = new Set([
|
|
"debug",
|
|
"grid",
|
|
"infiniteScroll",
|
|
"siblings",
|
|
"autoScan",
|
|
"scan",
|
|
"watcher",
|
|
"status",
|
|
"viewer",
|
|
"rtHydrate",
|
|
"observability",
|
|
"feed",
|
|
"sidebar",
|
|
"probeBackend",
|
|
"i18n",
|
|
"paths",
|
|
"db",
|
|
"ratingTagsSync",
|
|
"cache",
|
|
"search",
|
|
"ai",
|
|
"executionGrouping",
|
|
"workflowMinimap",
|
|
"ui",
|
|
"security",
|
|
"safety",
|
|
]);
|
|
const sanitized = {};
|
|
if (payload && typeof payload === "object") {
|
|
for (const [key, value] of Object.entries(payload)) {
|
|
if (allowed.has(key)) sanitized[key] = value;
|
|
}
|
|
}
|
|
const merged = deepMerge(DEFAULT_SETTINGS, sanitized);
|
|
// Auto-migrate legacy format to wrapped versioned format.
|
|
if (!isWrapped) {
|
|
try {
|
|
saveMajoorSettings(merged);
|
|
} catch (e) {
|
|
console.debug?.(e);
|
|
}
|
|
}
|
|
return merged;
|
|
} catch (error) {
|
|
console.warn("[Majoor] settings load failed, using defaults", error);
|
|
return { ...DEFAULT_SETTINGS };
|
|
}
|
|
};
|
|
|
|
export const saveMajoorSettings = (settings) => {
|
|
try {
|
|
const sanitized = JSON.parse(JSON.stringify(settings || {}));
|
|
if (sanitized?.security && typeof sanitized.security === "object") {
|
|
sanitized.security.apiToken = "";
|
|
}
|
|
const wrapped = {
|
|
version: SETTINGS_SCHEMA_VERSION,
|
|
data: sanitized,
|
|
};
|
|
SettingsStore.set(SETTINGS_KEY, JSON.stringify(wrapped));
|
|
} catch (error) {
|
|
console.warn("[Majoor] settings save failed", error);
|
|
try {
|
|
const now = Date.now();
|
|
const last = Number(window?._mjrSettingsSaveFailAt || 0) || 0;
|
|
if (now - last > 30_000) {
|
|
window._mjrSettingsSaveFailAt = now;
|
|
import("../dialogs.js")
|
|
.then(({ comfyAlert }) =>
|
|
comfyAlert(
|
|
t(
|
|
"dialog.settingsSaveFailed",
|
|
"Majoor: Failed to save settings (browser storage full or blocked).",
|
|
),
|
|
),
|
|
)
|
|
.catch(() => {});
|
|
}
|
|
} catch (e) {
|
|
console.debug?.(e);
|
|
}
|
|
try {
|
|
safeDispatchCustomEvent(
|
|
"mjr-settings-save-failed",
|
|
{ error: String(error?.message || error || "") },
|
|
{ warnPrefix: "[Majoor]" },
|
|
);
|
|
} catch (e) {
|
|
console.debug?.(e);
|
|
}
|
|
}
|
|
};
|
|
|
|
// ─── Apply settings to runtime APP_CONFIG ─────────────────────────────────
|
|
|
|
export const applySettingsToConfig = (settings) => {
|
|
const maxPage = Number(APP_DEFAULTS.MAX_PAGE_SIZE) || 2000;
|
|
const pageSize = Math.max(
|
|
50,
|
|
Math.min(maxPage, Number(settings.grid?.pageSize) || APP_DEFAULTS.DEFAULT_PAGE_SIZE),
|
|
);
|
|
APP_CONFIG.DEFAULT_PAGE_SIZE = pageSize;
|
|
APP_CONFIG.AUTO_SCAN_ON_STARTUP = !!settings.autoScan?.onStartup;
|
|
APP_CONFIG.EXECUTION_GROUPING_ENABLED = !!(
|
|
settings.executionGrouping?.enabled ?? APP_DEFAULTS.EXECUTION_GROUPING_ENABLED
|
|
);
|
|
APP_CONFIG.STATUS_POLL_INTERVAL = Math.max(
|
|
1000,
|
|
Number(settings.status?.pollInterval) || APP_DEFAULTS.STATUS_POLL_INTERVAL,
|
|
);
|
|
|
|
APP_CONFIG.DEBUG_SAFE_CALL = !!settings.debug?.safeCall;
|
|
APP_CONFIG.DEBUG_SAFE_LISTENERS = !!settings.debug?.safeListeners;
|
|
APP_CONFIG.DEBUG_VIEWER = !!settings.debug?.viewer;
|
|
|
|
APP_CONFIG.GRID_MIN_SIZE = resolveGridMinSize(settings.grid);
|
|
APP_CONFIG.FEED_GRID_MIN_SIZE = resolveFeedGridMinSize(settings.feed);
|
|
APP_CONFIG.GRID_GAP = Math.max(
|
|
0,
|
|
Math.min(40, Math.round(_safeNum(settings.grid?.gap, APP_DEFAULTS.GRID_GAP))),
|
|
);
|
|
|
|
APP_CONFIG.GRID_SHOW_BADGES_EXTENSION = !!(
|
|
settings.grid?.showExtBadge ?? APP_DEFAULTS.GRID_SHOW_BADGES_EXTENSION
|
|
);
|
|
APP_CONFIG.GRID_SHOW_BADGES_RATING = !!(
|
|
settings.grid?.showRatingBadge ?? APP_DEFAULTS.GRID_SHOW_BADGES_RATING
|
|
);
|
|
APP_CONFIG.GRID_SHOW_BADGES_TAGS = !!(
|
|
settings.grid?.showTagsBadge ?? APP_DEFAULTS.GRID_SHOW_BADGES_TAGS
|
|
);
|
|
APP_CONFIG.GRID_SHOW_DETAILS = !!(settings.grid?.showDetails ?? APP_DEFAULTS.GRID_SHOW_DETAILS);
|
|
APP_CONFIG.GRID_SHOW_DETAILS_FILENAME = !!(
|
|
settings.grid?.showFilename ?? APP_DEFAULTS.GRID_SHOW_DETAILS_FILENAME
|
|
);
|
|
APP_CONFIG.GRID_SHOW_DETAILS_DATE = !!(
|
|
settings.grid?.showDate ?? APP_DEFAULTS.GRID_SHOW_DETAILS_DATE
|
|
);
|
|
APP_CONFIG.GRID_SHOW_DETAILS_DIMENSIONS = !!(
|
|
settings.grid?.showDimensions ?? APP_DEFAULTS.GRID_SHOW_DETAILS_DIMENSIONS
|
|
);
|
|
APP_CONFIG.GRID_SHOW_DETAILS_GENTIME = !!(
|
|
settings.grid?.showGenTime ?? APP_DEFAULTS.GRID_SHOW_DETAILS_GENTIME
|
|
);
|
|
APP_CONFIG.GRID_SHOW_HOVER_INFO = !!(
|
|
settings.grid?.showHoverInfo ?? APP_DEFAULTS.GRID_SHOW_HOVER_INFO
|
|
);
|
|
APP_CONFIG.GRID_SHOW_WORKFLOW_DOT = !!(
|
|
settings.grid?.showWorkflowDot ?? APP_DEFAULTS.GRID_SHOW_WORKFLOW_DOT
|
|
);
|
|
|
|
// Bottom feed card display
|
|
APP_CONFIG.FEED_SHOW_INFO = !!(settings.feed?.showInfo ?? APP_DEFAULTS.FEED_SHOW_INFO);
|
|
APP_CONFIG.FEED_SHOW_FILENAME = !!(
|
|
settings.feed?.showFilename ?? APP_DEFAULTS.FEED_SHOW_FILENAME
|
|
);
|
|
APP_CONFIG.FEED_SHOW_DIMENSIONS = !!(
|
|
settings.feed?.showDimensions ?? APP_DEFAULTS.FEED_SHOW_DIMENSIONS
|
|
);
|
|
APP_CONFIG.FEED_SHOW_DATE = !!(settings.feed?.showDate ?? APP_DEFAULTS.FEED_SHOW_DATE);
|
|
APP_CONFIG.FEED_SHOW_GENTIME = !!(settings.feed?.showGenTime ?? APP_DEFAULTS.FEED_SHOW_GENTIME);
|
|
APP_CONFIG.FEED_SHOW_WORKFLOW_DOT = !!(
|
|
settings.feed?.showWorkflowDot ?? APP_DEFAULTS.FEED_SHOW_WORKFLOW_DOT
|
|
);
|
|
APP_CONFIG.FEED_SHOW_BADGES_EXTENSION = !!(
|
|
settings.feed?.showExtBadge ?? APP_DEFAULTS.FEED_SHOW_BADGES_EXTENSION
|
|
);
|
|
APP_CONFIG.FEED_SHOW_BADGES_RATING = !!(
|
|
settings.feed?.showRatingBadge ?? APP_DEFAULTS.FEED_SHOW_BADGES_RATING
|
|
);
|
|
APP_CONFIG.FEED_SHOW_BADGES_TAGS = !!(
|
|
settings.feed?.showTagsBadge ?? APP_DEFAULTS.FEED_SHOW_BADGES_TAGS
|
|
);
|
|
|
|
// Video autoplay mode: migrate old boolean → new tri-state
|
|
{
|
|
let mode = settings.grid?.videoAutoplayMode ?? APP_DEFAULTS.GRID_VIDEO_AUTOPLAY_MODE;
|
|
// Backward compat: old boolean "videoHoverAutoplay" → "hover"
|
|
if (mode === undefined || mode === null) {
|
|
mode = settings.grid?.videoHoverAutoplay === false ? "off" : "hover";
|
|
}
|
|
if (mode === true) mode = "hover";
|
|
if (mode === false) mode = "off";
|
|
if (mode !== "hover" && mode !== "always" && mode !== "off") mode = "hover";
|
|
APP_CONFIG.GRID_VIDEO_AUTOPLAY_MODE = mode;
|
|
}
|
|
|
|
const _safeColor = (v, fallback) => {
|
|
let c = String(v || "").trim();
|
|
if (/^[0-9a-fA-F]{6}$/.test(c)) c = `#${c}`;
|
|
return /^#[0-9a-fA-F]{3,8}$/.test(c) ? c : fallback;
|
|
};
|
|
APP_CONFIG.BADGE_STAR_COLOR = _safeColor(
|
|
settings.grid?.starColor,
|
|
APP_DEFAULTS.BADGE_STAR_COLOR,
|
|
);
|
|
APP_CONFIG.BADGE_IMAGE_COLOR = _safeColor(
|
|
settings.grid?.badgeImageColor,
|
|
APP_DEFAULTS.BADGE_IMAGE_COLOR,
|
|
);
|
|
APP_CONFIG.BADGE_VIDEO_COLOR = _safeColor(
|
|
settings.grid?.badgeVideoColor,
|
|
APP_DEFAULTS.BADGE_VIDEO_COLOR,
|
|
);
|
|
APP_CONFIG.BADGE_AUDIO_COLOR = _safeColor(
|
|
settings.grid?.badgeAudioColor,
|
|
APP_DEFAULTS.BADGE_AUDIO_COLOR,
|
|
);
|
|
APP_CONFIG.BADGE_MODEL3D_COLOR = _safeColor(
|
|
settings.grid?.badgeModel3dColor,
|
|
APP_DEFAULTS.BADGE_MODEL3D_COLOR,
|
|
);
|
|
APP_CONFIG.BADGE_DUPLICATE_ALERT_COLOR = _safeColor(
|
|
settings.grid?.badgeDuplicateAlertColor,
|
|
APP_DEFAULTS.BADGE_DUPLICATE_ALERT_COLOR,
|
|
);
|
|
APP_CONFIG.UI_CARD_HOVER_COLOR = _safeColor(
|
|
settings.ui?.cardHoverColor,
|
|
APP_DEFAULTS.UI_CARD_HOVER_COLOR,
|
|
);
|
|
APP_CONFIG.UI_CARD_SELECTION_COLOR = _safeColor(
|
|
settings.ui?.cardSelectionColor,
|
|
APP_DEFAULTS.UI_CARD_SELECTION_COLOR,
|
|
);
|
|
APP_CONFIG.UI_RATING_COLOR = _safeColor(settings.ui?.ratingColor, APP_DEFAULTS.UI_RATING_COLOR);
|
|
APP_CONFIG.UI_TAG_COLOR = _safeColor(settings.ui?.tagColor, APP_DEFAULTS.UI_TAG_COLOR);
|
|
|
|
try {
|
|
const roots = Array.from(document.querySelectorAll(".mjr-assets-manager"));
|
|
for (const root of roots) {
|
|
root.style.setProperty("--mjr-star-active", APP_CONFIG.BADGE_STAR_COLOR);
|
|
root.style.setProperty("--mjr-badge-image", APP_CONFIG.BADGE_IMAGE_COLOR);
|
|
root.style.setProperty("--mjr-badge-video", APP_CONFIG.BADGE_VIDEO_COLOR);
|
|
root.style.setProperty("--mjr-badge-audio", APP_CONFIG.BADGE_AUDIO_COLOR);
|
|
root.style.setProperty("--mjr-badge-model3d", APP_CONFIG.BADGE_MODEL3D_COLOR);
|
|
root.style.setProperty(
|
|
"--mjr-badge-duplicate-alert",
|
|
APP_CONFIG.BADGE_DUPLICATE_ALERT_COLOR,
|
|
);
|
|
root.style.setProperty("--mjr-card-hover-color", APP_CONFIG.UI_CARD_HOVER_COLOR);
|
|
root.style.setProperty(
|
|
"--mjr-card-selection-color",
|
|
APP_CONFIG.UI_CARD_SELECTION_COLOR,
|
|
);
|
|
root.style.setProperty("--mjr-rating-color", APP_CONFIG.UI_RATING_COLOR);
|
|
root.style.setProperty("--mjr-tag-color", APP_CONFIG.UI_TAG_COLOR);
|
|
}
|
|
} catch (e) {
|
|
console.debug?.(e);
|
|
}
|
|
|
|
APP_CONFIG.INFINITE_SCROLL_ENABLED = !!settings.infiniteScroll?.enabled;
|
|
APP_CONFIG.INFINITE_SCROLL_ROOT_MARGIN = String(
|
|
settings.infiniteScroll?.rootMargin || APP_DEFAULTS.INFINITE_SCROLL_ROOT_MARGIN,
|
|
);
|
|
APP_CONFIG.INFINITE_SCROLL_THRESHOLD = Math.max(
|
|
0,
|
|
Math.min(
|
|
1,
|
|
_safeNum(settings.infiniteScroll?.threshold, APP_DEFAULTS.INFINITE_SCROLL_THRESHOLD),
|
|
),
|
|
);
|
|
APP_CONFIG.BOTTOM_GAP_PX = Math.max(
|
|
0,
|
|
Math.min(
|
|
5000,
|
|
Math.round(_safeNum(settings.infiniteScroll?.bottomGapPx, APP_DEFAULTS.BOTTOM_GAP_PX)),
|
|
),
|
|
);
|
|
|
|
APP_CONFIG.VIEWER_ALLOW_PAN_AT_ZOOM_1 = !!settings.viewer?.allowPanAtZoom1;
|
|
APP_CONFIG.VIEWER_DISABLE_WEBGL_VIDEO = !!settings.viewer?.disableWebGL;
|
|
APP_CONFIG.VIEWER_PAUSE_DURING_EXECUTION = !!(
|
|
settings.viewer?.pauseDuringExecution ?? APP_DEFAULTS.VIEWER_PAUSE_DURING_EXECUTION
|
|
);
|
|
APP_CONFIG.FLOATING_VIEWER_PAUSE_DURING_EXECUTION = !!(
|
|
settings.viewer?.floatingPauseDuringExecution ??
|
|
APP_DEFAULTS.FLOATING_VIEWER_PAUSE_DURING_EXECUTION
|
|
);
|
|
APP_CONFIG.MFV_LIVE_DEFAULT =
|
|
settings.viewer?.mfvLiveDefault ?? APP_DEFAULTS.MFV_LIVE_DEFAULT;
|
|
APP_CONFIG.MFV_PREVIEW_DEFAULT =
|
|
settings.viewer?.mfvPreviewDefault ?? APP_DEFAULTS.MFV_PREVIEW_DEFAULT;
|
|
{
|
|
const previewMethod = String(
|
|
settings.viewer?.mfvPreviewMethod || APP_DEFAULTS.MFV_PREVIEW_METHOD,
|
|
).toLowerCase();
|
|
APP_CONFIG.MFV_PREVIEW_METHOD = ["default", "auto", "latent2rgb", "taesd", "none"].includes(
|
|
previewMethod,
|
|
)
|
|
? previewMethod
|
|
: APP_DEFAULTS.MFV_PREVIEW_METHOD;
|
|
}
|
|
{
|
|
const pos = String(settings.viewer?.mfvSidebarPosition || "right").toLowerCase();
|
|
APP_CONFIG.MFV_SIDEBAR_POSITION = ["left", "right", "bottom"].includes(pos) ? pos : "right";
|
|
}
|
|
APP_CONFIG.VIEWER_VIDEO_GRADE_THROTTLE_FPS = Math.max(
|
|
1,
|
|
Math.min(
|
|
60,
|
|
Math.round(
|
|
_safeNum(
|
|
settings.viewer?.videoGradeThrottleFps,
|
|
APP_DEFAULTS.VIEWER_VIDEO_GRADE_THROTTLE_FPS,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
APP_CONFIG.VIEWER_SCOPES_FPS = Math.max(
|
|
1,
|
|
Math.min(
|
|
60,
|
|
Math.round(_safeNum(settings.viewer?.scopesFps, APP_DEFAULTS.VIEWER_SCOPES_FPS)),
|
|
),
|
|
);
|
|
APP_CONFIG.VIEWER_META_TTL_MS = Math.max(
|
|
1000,
|
|
Math.min(
|
|
10 * 60_000,
|
|
Math.round(_safeNum(settings.viewer?.metaTtlMs, APP_DEFAULTS.VIEWER_META_TTL_MS)),
|
|
),
|
|
);
|
|
APP_CONFIG.VIEWER_META_MAX_ENTRIES = Math.max(
|
|
50,
|
|
Math.min(
|
|
5000,
|
|
Math.round(
|
|
_safeNum(settings.viewer?.metaMaxEntries, APP_DEFAULTS.VIEWER_META_MAX_ENTRIES),
|
|
),
|
|
),
|
|
);
|
|
|
|
APP_CONFIG.WORKFLOW_MINIMAP_ENABLED = !!(settings.workflowMinimap?.enabled ?? false);
|
|
|
|
APP_CONFIG.RT_HYDRATE_CONCURRENCY = Math.max(
|
|
1,
|
|
Math.min(
|
|
16,
|
|
Math.round(
|
|
_safeNum(settings.rtHydrate?.concurrency, APP_DEFAULTS.RT_HYDRATE_CONCURRENCY),
|
|
),
|
|
),
|
|
);
|
|
APP_CONFIG.RT_HYDRATE_QUEUE_MAX = Math.max(
|
|
10,
|
|
Math.min(
|
|
5000,
|
|
Math.round(_safeNum(settings.rtHydrate?.queueMax, APP_DEFAULTS.RT_HYDRATE_QUEUE_MAX)),
|
|
),
|
|
);
|
|
APP_CONFIG.RT_HYDRATE_SEEN_MAX = Math.max(
|
|
1000,
|
|
Math.min(
|
|
200_000,
|
|
Math.round(_safeNum(settings.rtHydrate?.seenMax, APP_DEFAULTS.RT_HYDRATE_SEEN_MAX)),
|
|
),
|
|
);
|
|
APP_CONFIG.RT_HYDRATE_PRUNE_BUDGET = Math.max(
|
|
10,
|
|
Math.min(
|
|
10_000,
|
|
Math.round(
|
|
_safeNum(settings.rtHydrate?.pruneBudget, APP_DEFAULTS.RT_HYDRATE_PRUNE_BUDGET),
|
|
),
|
|
),
|
|
);
|
|
APP_CONFIG.RT_HYDRATE_SEEN_TTL_MS = Math.max(
|
|
5_000,
|
|
Math.min(
|
|
6 * 60 * 60_000,
|
|
Math.round(
|
|
_safeNum(settings.rtHydrate?.seenTtlMs, APP_DEFAULTS.RT_HYDRATE_SEEN_TTL_MS),
|
|
),
|
|
),
|
|
);
|
|
|
|
APP_CONFIG.DELETE_CONFIRMATION = !!settings.safety?.confirmDeletion;
|
|
APP_CONFIG.DEBUG_VERBOSE_ERRORS = !!settings.observability?.verboseErrors;
|
|
APP_CONFIG.WATCHER_MAX_PENDING = Math.max(
|
|
10,
|
|
Math.min(5000, Math.round(_safeNum(settings.watcher?.maxPending, 500))),
|
|
);
|
|
APP_CONFIG.WATCHER_MIN_SIZE = Math.max(
|
|
0,
|
|
Math.min(1000000, Math.round(_safeNum(settings.watcher?.minSize, 100))),
|
|
);
|
|
APP_CONFIG.WATCHER_MAX_SIZE = Math.max(
|
|
100000,
|
|
Math.min(17179869184, Math.round(_safeNum(settings.watcher?.maxSize, 4294967296))),
|
|
);
|
|
APP_CONFIG.DB_TIMEOUT_MS = Math.max(
|
|
1000,
|
|
Math.min(30000, Math.round(_safeNum(settings.db?.timeoutMs, 5000))),
|
|
);
|
|
APP_CONFIG.DB_MAX_CONNECTIONS = Math.max(
|
|
1,
|
|
Math.min(100, Math.round(_safeNum(settings.db?.maxConnections, 10))),
|
|
);
|
|
APP_CONFIG.DB_QUERY_TIMEOUT_MS = Math.max(
|
|
500,
|
|
Math.min(10000, Math.round(_safeNum(settings.db?.queryTimeoutMs, 1000))),
|
|
);
|
|
// Search request limit (client-side); backend still enforces MAJOOR_SEARCH_MAX_LIMIT
|
|
APP_CONFIG.SEARCH_REQUEST_LIMIT = Math.max(
|
|
10,
|
|
Math.min(
|
|
APP_DEFAULTS.MAX_PAGE_SIZE || 2000,
|
|
Math.round(_safeNum(settings.search?.maxResults, APP_DEFAULTS.SEARCH_DEFAULT_LIMIT)),
|
|
),
|
|
);
|
|
};
|
|
|
|
// ─── Backend security sync ────────────────────────────────────────────────
|
|
|
|
export async function syncBackendSecuritySettings() {
|
|
try {
|
|
const res = await getSecuritySettings();
|
|
if (!res?.ok) return;
|
|
const prefs = res.data?.prefs;
|
|
if (!prefs || typeof prefs !== "object") return;
|
|
|
|
const settings = loadMajoorSettings();
|
|
settings.security = settings.security || {};
|
|
settings.security.safeMode = _safeBool(prefs.safe_mode, settings.security.safeMode);
|
|
settings.security.allowWrite = _safeBool(prefs.allow_write, settings.security.allowWrite);
|
|
settings.security.requireAuth = _safeBool(prefs.require_auth, settings.security.requireAuth);
|
|
settings.security.allowRemoteWrite = _safeBool(
|
|
prefs.allow_remote_write,
|
|
settings.security.allowRemoteWrite,
|
|
);
|
|
settings.security.allowInsecureTokenTransport = _safeBool(
|
|
prefs.allow_insecure_token_transport,
|
|
settings.security.allowInsecureTokenTransport,
|
|
);
|
|
settings.security.allowDelete = _safeBool(
|
|
prefs.allow_delete,
|
|
settings.security.allowDelete,
|
|
);
|
|
settings.security.allowRename = _safeBool(
|
|
prefs.allow_rename,
|
|
settings.security.allowRename,
|
|
);
|
|
settings.security.allowOpenInFolder = _safeBool(
|
|
prefs.allow_open_in_folder,
|
|
settings.security.allowOpenInFolder,
|
|
);
|
|
settings.security.allowResetIndex = _safeBool(
|
|
prefs.allow_reset_index,
|
|
settings.security.allowResetIndex,
|
|
);
|
|
settings.security.tokenConfigured = _safeBool(
|
|
prefs.token_configured,
|
|
settings.security.tokenConfigured,
|
|
);
|
|
settings.security.tokenHint = String(prefs.token_hint || "").trim();
|
|
// Security settings endpoint intentionally does not expose the token.
|
|
// Bootstrap it once so write actions work without manual user input.
|
|
if (!String(settings.security.apiToken || "").trim()) {
|
|
try {
|
|
const boot = await bootstrapSecurityToken();
|
|
const bootToken = String(boot?.data?.token || "").trim();
|
|
if (boot?.ok && bootToken) {
|
|
settings.security.apiToken = bootToken;
|
|
}
|
|
} catch (e) {
|
|
console.debug?.(e);
|
|
}
|
|
}
|
|
saveMajoorSettings(settings);
|
|
applySettingsToConfig(settings);
|
|
safeDispatchCustomEvent(
|
|
"mjr-settings-changed",
|
|
{ key: "security" },
|
|
{ warnPrefix: "[Majoor]" },
|
|
);
|
|
} catch (error) {
|
|
console.warn("[Majoor] failed to sync backend security settings", error);
|
|
}
|
|
}
|
|
|
|
export async function syncBackendVectorSearchSettings() {
|
|
try {
|
|
const res = await getVectorSearchSettings();
|
|
if (!res?.ok) return;
|
|
const prefs = res.data?.prefs;
|
|
if (!prefs || typeof prefs !== "object") return;
|
|
|
|
const settings = loadMajoorSettings();
|
|
settings.ai = settings.ai || {};
|
|
settings.ai.vectorSearchEnabled = _safeBool(
|
|
prefs.enabled,
|
|
settings.ai.vectorSearchEnabled ?? true,
|
|
);
|
|
saveMajoorSettings(settings);
|
|
applySettingsToConfig(settings);
|
|
safeDispatchCustomEvent(
|
|
"mjr-settings-changed",
|
|
{ key: "ai.vectorSearchEnabled" },
|
|
{ warnPrefix: "[Majoor]" },
|
|
);
|
|
} catch (error) {
|
|
console.warn("[Majoor] failed to sync backend vector search settings", error);
|
|
}
|
|
}
|
|
|
|
export async function syncBackendExecutionGroupingSettings() {
|
|
try {
|
|
const res = await getExecutionGroupingSettings();
|
|
if (!res?.ok) return;
|
|
const prefs = res.data?.prefs;
|
|
if (!prefs || typeof prefs !== "object") return;
|
|
|
|
const settings = loadMajoorSettings();
|
|
settings.executionGrouping = settings.executionGrouping || {};
|
|
settings.executionGrouping.enabled = _safeBool(
|
|
prefs.enabled,
|
|
settings.executionGrouping.enabled ?? APP_DEFAULTS.EXECUTION_GROUPING_ENABLED,
|
|
);
|
|
saveMajoorSettings(settings);
|
|
applySettingsToConfig(settings);
|
|
safeDispatchCustomEvent(
|
|
"mjr-settings-changed",
|
|
{ key: "executionGrouping.enabled" },
|
|
{ warnPrefix: "[Majoor]" },
|
|
);
|
|
} catch (error) {
|
|
console.warn("[Majoor] failed to sync backend execution grouping settings", error);
|
|
}
|
|
}
|