- Added new AI_FEATURES.md guide covering all AI-powered capabilities - Updated DOCUMENTATION_INDEX.md to include new AI features guide - Added AI Features section to README.md with overview and models used - Improved backfill warning information in README.md - Updated reset index / delete DB procedures in DB_MAINTENANCE.md - Added configuration options for AI features in README.md
594 lines
27 KiB
JavaScript
594 lines
27 KiB
JavaScript
/**
|
|
* Settings section: Advanced (Language, ProbeBackend, MetadataFallback,
|
|
* OutputDirectory, Database, Observability).
|
|
*/
|
|
|
|
import { APP_DEFAULTS } from "../config.js";
|
|
import {
|
|
setProbeBackendMode,
|
|
getMetadataFallbackSettings,
|
|
setMetadataFallbackSettings,
|
|
getOutputDirectorySetting,
|
|
setOutputDirectorySetting,
|
|
vectorStats,
|
|
vectorBackfill,
|
|
getHuggingFaceSettings,
|
|
setHuggingFaceSettings,
|
|
getAiLoggingSettings,
|
|
setAiLoggingSettings,
|
|
} from "../../api/client.js";
|
|
import { comfyToast } from "../toast.js";
|
|
import { t, initI18n, setLang, getCurrentLang, getSupportedLanguages, setFollowComfyLanguage } from "../i18n.js";
|
|
import { _safeNum, _safeOneOf } from "./settingsUtils.js";
|
|
import { DEFAULT_SETTINGS, saveMajoorSettings, applySettingsToConfig } from "./settingsCore.js";
|
|
|
|
const SETTINGS_PREFIX = "Majoor";
|
|
const SETTINGS_CATEGORY = "Majoor Assets Manager";
|
|
|
|
/**
|
|
* Register all Advanced settings.
|
|
*
|
|
* @param {Function} safeAddSetting - Wrapped addSetting function from the parent.
|
|
* @param {object} settings - Live settings object (mutated on change).
|
|
* @param {Function} notifyApplied - Callback(key) to schedule change notifications.
|
|
* @param {object} app - ComfyUI app reference (needed for initI18n).
|
|
*/
|
|
export function registerAdvancedSettings(safeAddSetting, settings, notifyApplied, app) {
|
|
const cat = (section, label) => [SETTINGS_CATEGORY, section, label];
|
|
|
|
// ── Language ──────────────────────────────────────────────────────────
|
|
|
|
const languages = getSupportedLanguages();
|
|
const langOptions = languages.map(l => l.code);
|
|
const languageModeOptions = ["auto", ...langOptions];
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.Language`,
|
|
category: cat(t("cat.advanced"), t("setting.language.name", "Language")),
|
|
name: t("setting.language.name", "Majoor: Language"),
|
|
tooltip: "Use auto to detect and follow ComfyUI language. Or choose a fixed language for Majoor only.",
|
|
type: "combo",
|
|
defaultValue: settings.i18n?.followComfyLanguage ? "auto" : getCurrentLang(),
|
|
options: languageModeOptions,
|
|
onChange: (value) => {
|
|
settings.i18n = settings.i18n || {};
|
|
if (value === "auto") {
|
|
settings.i18n.followComfyLanguage = true;
|
|
setFollowComfyLanguage(true);
|
|
initI18n(app);
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("language");
|
|
return;
|
|
}
|
|
if (!langOptions.includes(value)) return;
|
|
settings.i18n.followComfyLanguage = false;
|
|
setFollowComfyLanguage(false);
|
|
setLang(value);
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("language");
|
|
},
|
|
});
|
|
|
|
// ── ProbeBackend ──────────────────────────────────────────────────────
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.ProbeBackend.Mode`,
|
|
category: cat(t("cat.advanced"), t("setting.probe.mode.name").replace("Majoor: ", "")),
|
|
name: t("setting.probe.mode.name"),
|
|
tooltip: t("setting.probe.mode.desc") + " (env: MAJOOR_MEDIA_PROBE_BACKEND)",
|
|
type: "combo",
|
|
defaultValue: settings.probeBackend?.mode || DEFAULT_SETTINGS.probeBackend.mode,
|
|
options: ["auto", "exiftool", "ffprobe", "both"],
|
|
onChange: (value) => {
|
|
const mode = _safeOneOf(value, ["auto", "exiftool", "ffprobe", "both"], DEFAULT_SETTINGS.probeBackend.mode);
|
|
settings.probeBackend = settings.probeBackend || {};
|
|
settings.probeBackend.mode = mode;
|
|
saveMajoorSettings(settings);
|
|
applySettingsToConfig(settings);
|
|
notifyApplied("probeBackend.mode");
|
|
setProbeBackendMode(mode).catch(() => {});
|
|
},
|
|
});
|
|
|
|
// ── MetadataFallback ──────────────────────────────────────────────────
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.MetadataFallback.Image`,
|
|
category: cat(t("cat.advanced"), "Metadata"),
|
|
name: "Majoor: Metadata Fallback (Images)",
|
|
tooltip: "Enable Pillow fallback when ExifTool is missing or fails.",
|
|
type: "boolean",
|
|
defaultValue: settings.metadataFallback?.image ?? DEFAULT_SETTINGS.metadataFallback.image,
|
|
onChange: async (value) => {
|
|
const next = !!value;
|
|
const previous = !!(settings.metadataFallback?.image ?? DEFAULT_SETTINGS.metadataFallback.image);
|
|
settings.metadataFallback = settings.metadataFallback || {};
|
|
settings.metadataFallback.image = next;
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("metadataFallback.image");
|
|
try {
|
|
const res = await setMetadataFallbackSettings({
|
|
image: next,
|
|
media: settings.metadataFallback?.media ?? DEFAULT_SETTINGS.metadataFallback.media,
|
|
});
|
|
if (!res?.ok) throw new Error(res?.error || t("toast.failedUpdateMetadataFallback", "Failed to update metadata fallback settings"));
|
|
} catch (error) {
|
|
settings.metadataFallback.image = previous;
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("metadataFallback.image");
|
|
comfyToast(error?.message || t("toast.failedUpdateMetadataFallback", "Failed to update metadata fallback settings"), "error");
|
|
}
|
|
},
|
|
});
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.MetadataFallback.Media`,
|
|
category: cat(t("cat.advanced"), "Metadata"),
|
|
name: "Majoor: Metadata Fallback (Audio/Video)",
|
|
tooltip: "Enable hachoir fallback when ffprobe is missing or fails.",
|
|
type: "boolean",
|
|
defaultValue: settings.metadataFallback?.media ?? DEFAULT_SETTINGS.metadataFallback.media,
|
|
onChange: async (value) => {
|
|
const next = !!value;
|
|
const previous = !!(settings.metadataFallback?.media ?? DEFAULT_SETTINGS.metadataFallback.media);
|
|
settings.metadataFallback = settings.metadataFallback || {};
|
|
settings.metadataFallback.media = next;
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("metadataFallback.media");
|
|
try {
|
|
const res = await setMetadataFallbackSettings({
|
|
image: settings.metadataFallback?.image ?? DEFAULT_SETTINGS.metadataFallback.image,
|
|
media: next,
|
|
});
|
|
if (!res?.ok) throw new Error(res?.error || t("toast.failedUpdateMetadataFallback", "Failed to update metadata fallback settings"));
|
|
} catch (error) {
|
|
settings.metadataFallback.media = previous;
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("metadataFallback.media");
|
|
comfyToast(error?.message || t("toast.failedUpdateMetadataFallback", "Failed to update metadata fallback settings"), "error");
|
|
}
|
|
},
|
|
});
|
|
|
|
try {
|
|
getMetadataFallbackSettings()
|
|
.then((res) => {
|
|
if (!res?.ok || !res?.data?.prefs) return;
|
|
const prefs = res.data.prefs || {};
|
|
const image = !!(prefs.image ?? DEFAULT_SETTINGS.metadataFallback.image);
|
|
const media = !!(prefs.media ?? DEFAULT_SETTINGS.metadataFallback.media);
|
|
settings.metadataFallback = settings.metadataFallback || {};
|
|
let changed = false;
|
|
if (settings.metadataFallback.image !== image) {
|
|
settings.metadataFallback.image = image;
|
|
changed = true;
|
|
}
|
|
if (settings.metadataFallback.media !== media) {
|
|
settings.metadataFallback.media = media;
|
|
changed = true;
|
|
}
|
|
if (changed) {
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("metadataFallback");
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
} catch (e) { console.debug?.(e); }
|
|
|
|
// ── OutputDirectory ───────────────────────────────────────────────────
|
|
|
|
let _outputDirCommittedValue = String(settings.paths?.outputDirectory || "");
|
|
let _outputDirSaveTimer = null;
|
|
let _outputDirSaveSeq = 0;
|
|
let _outputDirSaveAbort = null;
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.Paths.OutputDirectory`,
|
|
category: cat(t("cat.advanced"), "Paths"),
|
|
name: "Majoor: Output Directory Override",
|
|
tooltip: "Override ComfyUI output directory used by Majoor (equivalent to --output-directory). Leave empty to keep current backend default.",
|
|
type: "text",
|
|
defaultValue: String(settings.paths?.outputDirectory || ""),
|
|
attrs: {
|
|
placeholder: "D:\\\\____COMFY_OUTPUTS",
|
|
},
|
|
onChange: async (value) => {
|
|
const next = String(value || "").trim();
|
|
settings.paths = settings.paths || {};
|
|
settings.paths.outputDirectory = next;
|
|
saveMajoorSettings(settings);
|
|
|
|
// The Comfy settings text input can fire on each keystroke.
|
|
// Debounce + cancel in-flight requests to prevent backend/UI stalls.
|
|
try {
|
|
if (_outputDirSaveTimer) {
|
|
clearTimeout(_outputDirSaveTimer);
|
|
_outputDirSaveTimer = null;
|
|
}
|
|
} catch (e) { console.debug?.(e); }
|
|
_outputDirSaveTimer = setTimeout(async () => {
|
|
_outputDirSaveTimer = null;
|
|
const seq = ++_outputDirSaveSeq;
|
|
try {
|
|
_outputDirSaveAbort?.abort?.();
|
|
} catch (e) { console.debug?.(e); }
|
|
_outputDirSaveAbort = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
try {
|
|
const res = await setOutputDirectorySetting(
|
|
next,
|
|
_outputDirSaveAbort ? { signal: _outputDirSaveAbort.signal } : {}
|
|
);
|
|
if (seq !== _outputDirSaveSeq) return;
|
|
if (!res?.ok) {
|
|
throw new Error(res?.error || t("toast.failedSetOutputDirectory", "Failed to set output directory"));
|
|
}
|
|
const serverValue = String(res?.data?.output_directory || next).trim();
|
|
settings.paths.outputDirectory = serverValue;
|
|
_outputDirCommittedValue = serverValue;
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("paths.outputDirectory");
|
|
} catch (error) {
|
|
if (seq !== _outputDirSaveSeq) return;
|
|
const aborted = String(error?.name || "") === "AbortError" || String(error?.code || "") === "ABORTED";
|
|
if (aborted) return;
|
|
settings.paths.outputDirectory = _outputDirCommittedValue;
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("paths.outputDirectory");
|
|
comfyToast(error?.message || t("toast.failedSetOutputDirectory", "Failed to set output directory"), "error");
|
|
}
|
|
}, 700);
|
|
},
|
|
});
|
|
|
|
try {
|
|
getOutputDirectorySetting()
|
|
.then((res) => {
|
|
if (!res?.ok) return;
|
|
const serverValue = String(res?.data?.output_directory || "").trim();
|
|
settings.paths = settings.paths || {};
|
|
if (settings.paths.outputDirectory !== serverValue) {
|
|
settings.paths.outputDirectory = serverValue;
|
|
_outputDirCommittedValue = serverValue;
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("paths.outputDirectory");
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
} catch (e) { console.debug?.(e); }
|
|
|
|
// ── Database ──────────────────────────────────────────────────────────
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.Db.Timeout`,
|
|
category: cat(t("cat.advanced"), "Database"),
|
|
name: "DB Timeout (ms)",
|
|
tooltip: "Client-side DB timeout preference (stored locally).",
|
|
type: "number",
|
|
defaultValue: Number(settings.db?.timeoutMs || 5000),
|
|
attrs: { min: 1000, max: 30000, step: 1000 },
|
|
onChange: (value) => {
|
|
settings.db = settings.db || {};
|
|
settings.db.timeoutMs = Math.max(1000, Math.min(30000, Math.round(_safeNum(value, 5000))));
|
|
saveMajoorSettings(settings);
|
|
applySettingsToConfig(settings);
|
|
notifyApplied("db.timeoutMs");
|
|
},
|
|
});
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.Db.MaxConnections`,
|
|
category: cat(t("cat.advanced"), "Database"),
|
|
name: "DB Max Connections",
|
|
tooltip: "Client-side DB max connections preference (stored locally).",
|
|
type: "number",
|
|
defaultValue: Number(settings.db?.maxConnections || 10),
|
|
attrs: { min: 1, max: 100, step: 1 },
|
|
onChange: (value) => {
|
|
settings.db = settings.db || {};
|
|
settings.db.maxConnections = Math.max(1, Math.min(100, Math.round(_safeNum(value, 10))));
|
|
saveMajoorSettings(settings);
|
|
applySettingsToConfig(settings);
|
|
notifyApplied("db.maxConnections");
|
|
},
|
|
});
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.Db.QueryTimeout`,
|
|
category: cat(t("cat.advanced"), "Database"),
|
|
name: "DB Query Timeout (ms)",
|
|
tooltip: "Client-side DB query timeout preference (stored locally).",
|
|
type: "number",
|
|
defaultValue: Number(settings.db?.queryTimeoutMs || 1000),
|
|
attrs: { min: 500, max: 10000, step: 500 },
|
|
onChange: (value) => {
|
|
settings.db = settings.db || {};
|
|
settings.db.queryTimeoutMs = Math.max(500, Math.min(10000, Math.round(_safeNum(value, 1000))));
|
|
saveMajoorSettings(settings);
|
|
applySettingsToConfig(settings);
|
|
notifyApplied("db.queryTimeoutMs");
|
|
},
|
|
});
|
|
|
|
// ── Observability ─────────────────────────────────────────────────────
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.Observability.Enabled`,
|
|
category: cat(t("cat.advanced"), t("setting.obs.enabled.name").replace("Majoor: ", "")),
|
|
name: t("setting.obs.enabled.name"),
|
|
tooltip: t("setting.obs.enabled.desc"),
|
|
type: "boolean",
|
|
defaultValue: !!settings.observability?.enabled,
|
|
onChange: (value) => {
|
|
settings.observability = settings.observability || {};
|
|
settings.observability.enabled = !!value;
|
|
saveMajoorSettings(settings);
|
|
applySettingsToConfig(settings);
|
|
notifyApplied("observability.enabled");
|
|
},
|
|
});
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.Observability.VerboseErrors`,
|
|
category: cat(t("cat.advanced"), "Verbose error logging"),
|
|
name: "Verbose error logging",
|
|
tooltip: "Show detailed error messages in toasts and console. Useful for debugging.",
|
|
type: "boolean",
|
|
defaultValue: !!settings.observability?.verboseErrors,
|
|
onChange: (value) => {
|
|
settings.observability = settings.observability || {};
|
|
settings.observability.verboseErrors = !!value;
|
|
saveMajoorSettings(settings);
|
|
applySettingsToConfig(settings);
|
|
notifyApplied("observability.verboseErrors");
|
|
},
|
|
});
|
|
|
|
// ── AI / Vector Search ────────────────────────────────────────────────
|
|
|
|
{
|
|
const hfCategoryLabel = "HuggingFace Token";
|
|
let _hfTokenCommittedValue = "";
|
|
let _hfTokenSaveTimer = null;
|
|
let _hfTokenSaveSeq = 0;
|
|
let _hfTokenVisible = !!settings.ai?.huggingFaceTokenVisible;
|
|
|
|
const _applyHfTokenVisibility = () => {
|
|
try {
|
|
const inputs = Array.from(document.querySelectorAll('input[data-mjr-hf-token="1"]'));
|
|
for (const el of inputs) {
|
|
try {
|
|
el.type = _hfTokenVisible ? "text" : "password";
|
|
} catch (e) { console.debug?.(e); }
|
|
}
|
|
} catch (e) { console.debug?.(e); }
|
|
};
|
|
|
|
const _applyHfTokenPlaceholder = (placeholderText) => {
|
|
try {
|
|
const value = String(placeholderText || "").trim();
|
|
if (!value) return;
|
|
const inputs = Array.from(document.querySelectorAll('input[data-mjr-hf-token="1"]'));
|
|
for (const el of inputs) {
|
|
try {
|
|
el.placeholder = value;
|
|
} catch (e) { console.debug?.(e); }
|
|
}
|
|
} catch (e) { console.debug?.(e); }
|
|
};
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.AI.HuggingFaceTokenVisible`,
|
|
category: cat(t("cat.advanced"), hfCategoryLabel),
|
|
name: "Show HuggingFace token",
|
|
tooltip: "Show or hide the HuggingFace token while editing.",
|
|
type: "boolean",
|
|
defaultValue: _hfTokenVisible,
|
|
onChange: (value) => {
|
|
const next = !!value;
|
|
_hfTokenVisible = next;
|
|
settings.ai = settings.ai || {};
|
|
settings.ai.huggingFaceTokenVisible = next;
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("ai.huggingFaceTokenVisible");
|
|
setTimeout(_applyHfTokenVisibility, 0);
|
|
},
|
|
});
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.AI.HuggingFaceToken`,
|
|
category: cat(t("cat.advanced"), hfCategoryLabel),
|
|
name: "HuggingFace Token",
|
|
tooltip: [
|
|
"Optional token for HuggingFace Hub downloads (higher rate limits).",
|
|
"Saved server-side and used by CLIP model loading.",
|
|
"Leave empty to clear the stored token.",
|
|
].join("\n"),
|
|
type: "text",
|
|
defaultValue: "",
|
|
attrs: {
|
|
placeholder: "Paste HuggingFace token (hf_...)",
|
|
type: _hfTokenVisible ? "text" : "password",
|
|
autocomplete: "new-password",
|
|
name: "mjr_huggingface_token",
|
|
"data-mjr-hf-token": "1",
|
|
},
|
|
onChange: (value) => {
|
|
const next = String(value || "").trim();
|
|
try {
|
|
if (_hfTokenSaveTimer) {
|
|
clearTimeout(_hfTokenSaveTimer);
|
|
_hfTokenSaveTimer = null;
|
|
}
|
|
} catch (e) { console.debug?.(e); }
|
|
|
|
_hfTokenSaveTimer = setTimeout(async () => {
|
|
_hfTokenSaveTimer = null;
|
|
const seq = ++_hfTokenSaveSeq;
|
|
try {
|
|
const res = await setHuggingFaceSettings(next);
|
|
if (seq !== _hfTokenSaveSeq) return;
|
|
if (!res?.ok) {
|
|
throw new Error(res?.error || "Failed to update HuggingFace token");
|
|
}
|
|
_hfTokenCommittedValue = next;
|
|
notifyApplied("ai.huggingFaceToken");
|
|
comfyToast(next ? "HuggingFace token saved" : "HuggingFace token cleared", "success");
|
|
} catch (error) {
|
|
if (seq !== _hfTokenSaveSeq) return;
|
|
comfyToast(error?.message || "Failed to update HuggingFace token", "error");
|
|
}
|
|
}, 900);
|
|
},
|
|
});
|
|
|
|
setTimeout(_applyHfTokenVisibility, 0);
|
|
|
|
(async () => {
|
|
try {
|
|
const res = await getHuggingFaceSettings();
|
|
const prefs = res?.data?.prefs || {};
|
|
const hasToken = !!prefs?.has_token;
|
|
const tokenHint = String(prefs?.token_hint || "").trim();
|
|
const placeholder = hasToken
|
|
? `Configured ${tokenHint || "(saved)"}`
|
|
: "Paste HuggingFace token (hf_...)";
|
|
_applyHfTokenPlaceholder(placeholder);
|
|
} catch (e) { console.debug?.(e); }
|
|
})();
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.AI.VerboseLogs`,
|
|
category: cat(t("cat.advanced"), hfCategoryLabel),
|
|
name: "Majoor: Verbose AI logs",
|
|
tooltip: "Enable detailed HuggingFace/SigLIP2/X-CLIP logs and progress bars during model download/loading.",
|
|
type: "boolean",
|
|
defaultValue: !!(settings.ai?.verboseAiLogs ?? DEFAULT_SETTINGS.ai?.verboseAiLogs ?? false),
|
|
onChange: async (value) => {
|
|
const next = !!value;
|
|
const previous = !!(settings.ai?.verboseAiLogs ?? DEFAULT_SETTINGS.ai?.verboseAiLogs ?? false);
|
|
settings.ai = settings.ai || {};
|
|
settings.ai.verboseAiLogs = next;
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("ai.verboseAiLogs");
|
|
try {
|
|
const res = await setAiLoggingSettings(next);
|
|
if (!res?.ok) throw new Error(res?.error || "Failed to update AI logging settings");
|
|
} catch (error) {
|
|
settings.ai.verboseAiLogs = previous;
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("ai.verboseAiLogs");
|
|
comfyToast(error?.message || "Failed to update AI logging settings", "error");
|
|
}
|
|
},
|
|
});
|
|
|
|
(async () => {
|
|
try {
|
|
const res = await getAiLoggingSettings();
|
|
const enabled = !!(res?.data?.prefs?.enabled);
|
|
settings.ai = settings.ai || {};
|
|
if (settings.ai.verboseAiLogs !== enabled) {
|
|
settings.ai.verboseAiLogs = enabled;
|
|
saveMajoorSettings(settings);
|
|
notifyApplied("ai.verboseAiLogs");
|
|
}
|
|
} catch (e) { console.debug?.(e); }
|
|
})();
|
|
}
|
|
|
|
// Vector status field must be registered synchronously so it never disappears
|
|
// when async calls fail or time out during startup.
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.AI.VectorStats`,
|
|
category: cat(t("cat.advanced"), "AI / Vector Search"),
|
|
name: "Vector Index Status",
|
|
tooltip: "Current status of the SigLIP2/X-CLIP vector index used for semantic search",
|
|
type: "text",
|
|
defaultValue: "Loading vector status...",
|
|
});
|
|
|
|
// Fetch status in the background (best effort).
|
|
(async () => {
|
|
try {
|
|
const stats = await vectorStats();
|
|
if (stats?.ok) {
|
|
console.info(
|
|
"[Majoor] Vector status:",
|
|
`${stats.data?.total || 0} assets indexed | Model: ${stats.data?.model || "N/A"}`
|
|
);
|
|
} else {
|
|
console.info("[Majoor] Vector status unavailable");
|
|
}
|
|
} catch (err) {
|
|
console.debug?.("[Majoor] Vector status fetch failed", err);
|
|
}
|
|
})();
|
|
|
|
safeAddSetting({
|
|
id: `${SETTINGS_PREFIX}.AI.VectorBackfillAction`,
|
|
category: cat(t("cat.advanced"), "AI / Vector Search"),
|
|
name: "Vector Index Action",
|
|
tooltip: [
|
|
"Compute CLIP embeddings for all assets that don't have them yet.",
|
|
"This is required for AI semantic search to work.",
|
|
"",
|
|
"Choose 'Run backfill now' to start indexing.",
|
|
"This may take several minutes for large libraries.",
|
|
"",
|
|
"Note: New assets are indexed automatically during scanning.",
|
|
].join("\n"),
|
|
type: "combo",
|
|
defaultValue: "Idle",
|
|
options: ["Idle", "Run backfill now"],
|
|
onChange: async (value) => {
|
|
if (String(value || "") !== "Run backfill now") return;
|
|
|
|
try {
|
|
comfyToast(t("toast.vectorBackfillStarting", "Starting vector backfill... This may take a while."), "info");
|
|
const result = await vectorBackfill(64);
|
|
|
|
if (result?.ok) {
|
|
const data = result.data || {};
|
|
const state = String(data?.status || "").toLowerCase();
|
|
const pending = !!data?.pending || ["queued", "running", "pending"].includes(state);
|
|
const progress = data?.progress || {};
|
|
const processed = Number(data?.processed ?? progress?.candidates ?? 0);
|
|
const indexed = Number(data?.indexed ?? progress?.indexed ?? 0);
|
|
const skipped = Number(data?.skipped ?? progress?.skipped ?? 0);
|
|
if (pending) {
|
|
const jobId = String(data?.job_id || "").trim();
|
|
comfyToast(
|
|
t(
|
|
"toast.vectorBackfillRunning",
|
|
"Vector backfill still running in background{job}.",
|
|
{ job: jobId ? ` (job ${jobId.slice(0, 8)})` : "" }
|
|
),
|
|
"info",
|
|
);
|
|
} else {
|
|
const msg = t(
|
|
"toast.vectorBackfillComplete",
|
|
"Vector backfill complete! Processed: {processed}, Indexed: {indexed}, Skipped: {skipped}",
|
|
{ processed, indexed, skipped }
|
|
);
|
|
comfyToast(msg, "success");
|
|
}
|
|
try {
|
|
const stats = await vectorStats();
|
|
if (stats?.ok) {
|
|
console.log("[Majoor] Vector stats after backfill:", stats.data);
|
|
}
|
|
} catch (statsErr) {
|
|
console.warn("[Majoor] Failed to refresh vector stats:", statsErr);
|
|
}
|
|
} else {
|
|
throw new Error(result?.error || t("toast.vectorBackfillFailedGeneric", "Backfill failed"));
|
|
}
|
|
} catch (error) {
|
|
const errMsg = error?.message || String(error || t("status.unknown", "unknown"));
|
|
comfyToast(t("toast.vectorBackfillFailedDetail", "Vector backfill failed: {error}", { error: errMsg }), "error");
|
|
console.error("[Majoor] Vector backfill error:", error);
|
|
}
|
|
},
|
|
});
|
|
}
|