File path refacto
This commit is contained in:
@@ -179,6 +179,14 @@ export function registerSidebarTab(tabDef) {
|
||||
}
|
||||
}
|
||||
|
||||
export function registerSidebarTabForApp(app, tabDef) {
|
||||
try {
|
||||
return registerSidebarTabCompat(app || _app || getComfyApp(), tabDef);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a bottom panel tab.
|
||||
*
|
||||
@@ -193,6 +201,14 @@ export function registerBottomPanelTab(tabDef) {
|
||||
}
|
||||
}
|
||||
|
||||
export function registerBottomPanelTabForApp(app, tabDef) {
|
||||
try {
|
||||
return registerBottomPanelTabCompat(app || _app || getComfyApp(), tabDef);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatically activate a sidebar tab.
|
||||
*
|
||||
@@ -207,6 +223,14 @@ export function activateSidebarTab(tabId) {
|
||||
}
|
||||
}
|
||||
|
||||
export function activateSidebarTabForApp(app, tabId) {
|
||||
try {
|
||||
return activateSidebarTabCompat(app || _app || getComfyApp(), tabId);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── commands & keybindings ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -223,6 +247,14 @@ export function registerCommand(commandDef) {
|
||||
}
|
||||
}
|
||||
|
||||
export function registerCommandForApp(app, commandDef) {
|
||||
try {
|
||||
return registerCommandCompat(app || _app || getComfyApp(), commandDef);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a keybinding.
|
||||
*
|
||||
@@ -237,6 +269,14 @@ export function registerKeybinding(keybindingDef) {
|
||||
}
|
||||
}
|
||||
|
||||
export function registerKeybindingForApp(app, keybindingDef) {
|
||||
try {
|
||||
return registerKeybindingCompat(app || _app || getComfyApp(), keybindingDef);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── extension manager (escape hatch) ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -311,10 +351,15 @@ export const hostAdapter = {
|
||||
getSetting,
|
||||
getHostSettingsApi,
|
||||
registerSidebarTab,
|
||||
registerSidebarTabForApp,
|
||||
registerBottomPanelTab,
|
||||
registerBottomPanelTabForApp,
|
||||
activateSidebarTab,
|
||||
activateSidebarTabForApp,
|
||||
registerCommand,
|
||||
registerCommandForApp,
|
||||
registerKeybinding,
|
||||
registerKeybindingForApp,
|
||||
getHostExtensionManager,
|
||||
getRawHostApp,
|
||||
getRawHostApi,
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
|
||||
import { runStartupWarmup } from "../../app/bootstrap.js";
|
||||
import {
|
||||
activateSidebarTabCompat,
|
||||
registerBottomPanelTabCompat,
|
||||
registerCommandCompat,
|
||||
registerKeybindingCompat,
|
||||
registerSidebarTabCompat,
|
||||
} from "../../app/comfyApiBridge.js";
|
||||
activateSidebarTabForApp,
|
||||
registerBottomPanelTabForApp,
|
||||
registerCommandForApp,
|
||||
registerKeybindingForApp,
|
||||
registerSidebarTabForApp,
|
||||
} from "../../app/hostAdapter.js";
|
||||
import { EVENTS } from "../../app/events.js";
|
||||
import { openMajoorSettings } from "../../app/openMajoorSettings.js";
|
||||
import { comfyToast } from "../../app/toast.js";
|
||||
@@ -91,7 +91,7 @@ export { teardownTopBarMfvButton };
|
||||
// ── sidebar helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
export function openAssetsManagerPanel(runtimeApp, sidebarTabId) {
|
||||
const opened = activateSidebarTabCompat(runtimeApp, sidebarTabId);
|
||||
const opened = activateSidebarTabForApp(runtimeApp, sidebarTabId);
|
||||
if (!opened) {
|
||||
try {
|
||||
window.dispatchEvent(new Event(EVENTS.OPEN_ASSETS_MANAGER));
|
||||
@@ -281,13 +281,13 @@ export function buildAboutPageBadges() {
|
||||
/** Imperatively registers commands — used as a fallback for older ComfyUI. */
|
||||
export function registerNativeCommands(runtimeApp, options) {
|
||||
for (const command of buildNativeCommands(runtimeApp, options)) {
|
||||
registerCommandCompat(runtimeApp, command);
|
||||
registerCommandForApp(runtimeApp, command);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerNativeKeybindings(runtimeApp) {
|
||||
for (const keybinding of buildNativeKeybindings()) {
|
||||
registerKeybindingCompat(runtimeApp, keybinding);
|
||||
registerKeybindingForApp(runtimeApp, keybinding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +303,7 @@ export function registerNativeKeybindings(runtimeApp) {
|
||||
* Full teardown only happens when the extension is unregistered.
|
||||
*/
|
||||
export function registerAssetsSidebar(runtimeApp, { sidebarTabId }) {
|
||||
return registerSidebarTabCompat(runtimeApp, {
|
||||
return registerSidebarTabForApp(runtimeApp, {
|
||||
id: sidebarTabId,
|
||||
icon: "pi pi-folder",
|
||||
title: t("manager.title"),
|
||||
@@ -415,7 +415,7 @@ export function buildBottomPanelTabs() {
|
||||
}
|
||||
|
||||
export function registerGeneratedBottomPanel(runtimeApp) {
|
||||
return registerBottomPanelTabCompat(runtimeApp, getGeneratedFeedBottomPanelTab());
|
||||
return registerBottomPanelTabForApp(runtimeApp, getGeneratedFeedBottomPanelTab());
|
||||
}
|
||||
|
||||
export function teardownGeneratedFeed() {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import SidebarFileInfoSection from "../vue/components/panel/sidebar/SidebarFileInfoSection.vue";
|
||||
|
||||
describe("SidebarFileInfoSection", () => {
|
||||
it("shows execution provenance fields from top-level asset data", () => {
|
||||
const wrapper = mount(SidebarFileInfoSection, {
|
||||
props: {
|
||||
asset: {
|
||||
id: 9,
|
||||
job_id: "prompt-1",
|
||||
source_node_id: "7",
|
||||
source_node_type: "SaveImage",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const text = wrapper.text();
|
||||
expect(text).toContain("Job ID");
|
||||
expect(text).toContain("prompt-1");
|
||||
expect(text).toContain("Source Node");
|
||||
expect(text).toContain("7");
|
||||
expect(text).toContain("Node Type");
|
||||
expect(text).toContain("SaveImage");
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it("shows execution provenance fields from nested file_info", () => {
|
||||
const wrapper = mount(SidebarFileInfoSection, {
|
||||
props: {
|
||||
asset: {
|
||||
file_info: {
|
||||
job_id: "prompt-2",
|
||||
source_node_id: "12",
|
||||
source_node_type: "VHS_VideoCombine",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const text = wrapper.text();
|
||||
expect(text).toContain("prompt-2");
|
||||
expect(text).toContain("12");
|
||||
expect(text).toContain("VHS_VideoCombine");
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
@@ -31,6 +31,10 @@ function isAnimatedMedia(asset) {
|
||||
}
|
||||
}
|
||||
|
||||
function readAssetField(asset, key) {
|
||||
return asset?.[key] ?? asset?.file_info?.[key] ?? "";
|
||||
}
|
||||
|
||||
const rows = computed(() => {
|
||||
const asset = props.asset || {};
|
||||
const fileData = [];
|
||||
@@ -87,8 +91,17 @@ const rows = computed(() => {
|
||||
if (asset.id != null) {
|
||||
fileData.push({ label: "Asset ID", value: String(asset.id), tooltip: "Internal database asset identifier" });
|
||||
}
|
||||
if (asset.job_id) {
|
||||
fileData.push({ label: "Job ID", value: String(asset.job_id), tooltip: "Workflow execution job identifier (prompt_id)" });
|
||||
const jobId = String(readAssetField(asset, "job_id") || "").trim();
|
||||
if (jobId) {
|
||||
fileData.push({ label: "Job ID", value: jobId, tooltip: "Workflow execution job identifier (prompt_id)" });
|
||||
}
|
||||
const sourceNodeId = String(readAssetField(asset, "source_node_id") || "").trim();
|
||||
if (sourceNodeId) {
|
||||
fileData.push({ label: "Source Node", value: sourceNodeId, tooltip: "ComfyUI node id that produced this file" });
|
||||
}
|
||||
const sourceNodeType = String(readAssetField(asset, "source_node_type") || "").trim();
|
||||
if (sourceNodeType) {
|
||||
fileData.push({ label: "Node Type", value: sourceNodeType, tooltip: "ComfyUI node class that produced this file" });
|
||||
}
|
||||
return fileData;
|
||||
});
|
||||
|
||||
@@ -121,7 +121,12 @@ function hasFileInfoData(asset) {
|
||||
timestamp ||
|
||||
(target.size && target.size > 0) ||
|
||||
target.id != null ||
|
||||
target.job_id,
|
||||
target.job_id ||
|
||||
target.file_info?.job_id ||
|
||||
target.source_node_id ||
|
||||
target.file_info?.source_node_id ||
|
||||
target.source_node_type ||
|
||||
target.file_info?.source_node_type,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { p as Be, M as Je, c as $e, q as tn, t as en, u as nn, v as sn, A as gt, x as Q, y as on, z as rn, B as an, C as ce, D as Le, F as ln, G as R, H as Et, I as cn, J as Ie, K as dn, L as un, N as Nt, E as dt, O as pn, n as mn, s as kt, Q as hn, R as fn, S as Pt, T as _n, U as Ut, V as Yt, d as Ft, W as gn, l as bn, X as yn } from "./entry-GhzfvnC5.js";
|
||||
import { ensureViewerMetadataAsset as Lt } from "./genInfo-BgI38YPf.js";
|
||||
import { p as Be, M as Je, c as $e, q as tn, t as en, u as nn, v as sn, A as gt, x as Q, y as on, z as rn, B as an, C as ce, D as Le, F as ln, G as R, H as Et, I as cn, J as Ie, K as dn, L as un, N as Nt, E as dt, O as pn, n as mn, s as kt, Q as hn, R as fn, S as Pt, T as _n, U as Ut, V as Yt, d as Ft, W as gn, l as bn, X as yn } from "./entry-B5M8pQrP.js";
|
||||
import { ensureViewerMetadataAsset as Lt } from "./genInfo-h4muYyLg.js";
|
||||
const L = Object.freeze({
|
||||
SIMPLE: "simple",
|
||||
AB: "ab",
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { E as g, w as E, e as l } from "./entry-GhzfvnC5.js";
|
||||
import { E as g, w as E, e as l } from "./entry-B5M8pQrP.js";
|
||||
let p = !1, i = null, u = null, c = null, d = null, w = null, v = 0, _ = 0;
|
||||
const f = 400, M = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".avif", ".gif", ".bmp"]), S = /* @__PURE__ */ new Set([".mp4", ".webm", ".mov", ".avi", ".mkv", ".m4v"]), L = /* @__PURE__ */ new Set([".mp3", ".wav", ".flac", ".ogg", ".m4a", ".aac", ".opus"]), I = /* @__PURE__ */ new Set([".glb", ".gltf", ".obj", ".fbx", ".stl", ".usdz"]);
|
||||
function T(e) {
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
||||
import { _ as nn, j as on, k as tn, A as B, l as en, m as rn, o as cn } from "./entry-GhzfvnC5.js";
|
||||
import { _ as nn, j as on, k as tn, A as B, l as en, m as rn, o as cn } from "./entry-B5M8pQrP.js";
|
||||
import { o as E, c as R, t as $, a as O, b as I, n as ln, d as z, e as j } from "./mjr-vue-vendor-LmyMh4Y4.js";
|
||||
const un = { style: { display: "flex", "flex-direction": "column", gap: "10px", "margin-bottom": "14px" } }, fn = {
|
||||
key: 0,
|
||||
@@ -12,7 +12,7 @@ const un = { style: { display: "flex", "flex-direction": "column", gap: "10px",
|
||||
}, yn = {
|
||||
key: 7,
|
||||
style: { border: "1px solid rgba(255,255,255,0.10)", "border-radius": "10px", background: "rgba(255,255,255,0.04)", overflow: "hidden" }
|
||||
}, an = { style: { margin: "0", padding: "10px 12px", "max-height": "280px", overflow: "auto", "font-size": "11px", "line-height": "1.35", color: "rgba(255,255,255,0.86)" } }, _n = {
|
||||
}, _n = { style: { margin: "0", padding: "10px 12px", "max-height": "280px", overflow: "auto", "font-size": "11px", "line-height": "1.35", color: "rgba(255,255,255,0.86)" } }, an = {
|
||||
__name: "ViewerMetadataBlock",
|
||||
props: {
|
||||
title: { type: String, default: "" },
|
||||
@@ -39,8 +39,8 @@ const un = { style: { display: "flex", "flex-direction": "column", gap: "10px",
|
||||
const y = r.trim();
|
||||
if (!y) return null;
|
||||
try {
|
||||
const _ = JSON.parse(y);
|
||||
return _ && typeof _ == "object" ? _ : null;
|
||||
const a = JSON.parse(y);
|
||||
return a && typeof a == "object" ? a : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -50,8 +50,8 @@ const un = { style: { display: "flex", "flex-direction": "column", gap: "10px",
|
||||
const r = Object.entries(i || {});
|
||||
if (!r.length) return !1;
|
||||
let y = 0;
|
||||
for (const [, _] of r.slice(0, 50))
|
||||
if (!(!_ || typeof _ != "object") && (_.inputs && typeof _.inputs == "object" && (y += 1), y >= 2))
|
||||
for (const [, a] of r.slice(0, 50))
|
||||
if (!(!a || typeof a != "object") && (a.inputs && typeof a.inputs == "object" && (y += 1), y >= 2))
|
||||
return !0;
|
||||
} catch {
|
||||
return !1;
|
||||
@@ -63,10 +63,10 @@ const un = { style: { display: "flex", "flex-direction": "column", gap: "10px",
|
||||
if (!y) return null;
|
||||
if (typeof y == "object") return y;
|
||||
if (typeof y != "string") return null;
|
||||
const _ = y.trim();
|
||||
if (!_) return null;
|
||||
const a = y.trim();
|
||||
if (!a) return null;
|
||||
try {
|
||||
return JSON.parse(_);
|
||||
return JSON.parse(a);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -76,10 +76,10 @@ const un = { style: { display: "flex", "flex-direction": "column", gap: "10px",
|
||||
if (!y) return null;
|
||||
if (typeof y == "object") return u(y) ? y : null;
|
||||
if (typeof y != "string") return null;
|
||||
const _ = y.trim();
|
||||
if (!_) return null;
|
||||
const a = y.trim();
|
||||
if (!a) return null;
|
||||
try {
|
||||
const f = JSON.parse(_);
|
||||
const f = JSON.parse(a);
|
||||
return u(f) ? f : null;
|
||||
} catch {
|
||||
return null;
|
||||
@@ -89,10 +89,11 @@ const un = { style: { display: "flex", "flex-direction": "column", gap: "10px",
|
||||
return !!(d(i) || l(i));
|
||||
}
|
||||
function g(i) {
|
||||
var a, f, h;
|
||||
const r = i || {}, y = r.generation_time || r.file_creation_time || r.mtime || r.created_at;
|
||||
return !!(r.width && r.height || r.duration && r.duration > 0 || y || r.size && r.size > 0 || r.id != null || r.job_id);
|
||||
return !!(r.width && r.height || r.duration && r.duration > 0 || y || r.size && r.size > 0 || r.id != null || r.job_id || (a = r.file_info) != null && a.job_id || r.source_node_id || (f = r.file_info) != null && f.source_node_id || r.source_node_type || (h = r.file_info) != null && h.source_node_type);
|
||||
}
|
||||
function a(i) {
|
||||
function _(i) {
|
||||
if (i == null) return "";
|
||||
const r = typeof i == "string" ? i : JSON.stringify(i, null, 2);
|
||||
return r ? r.length > 4e4 ? `${r.slice(0, 4e4)}
|
||||
@@ -105,17 +106,17 @@ const un = { style: { display: "flex", "flex-direction": "column", gap: "10px",
|
||||
() => B.WORKFLOW_MINIMAP_ENABLED !== !1 && o(t.asset)
|
||||
), N = j(
|
||||
() => b.value && typeof b.value == "object" && b.value.kind === "fetch_error"
|
||||
), x = j(() => {
|
||||
), k = j(() => {
|
||||
var i;
|
||||
return a((i = t.asset) == null ? void 0 : i.metadata_raw);
|
||||
}), h = j(
|
||||
return _((i = t.asset) == null ? void 0 : i.metadata_raw);
|
||||
}), x = j(
|
||||
() => !t.loading && !N.value && !T.value && !G.value && !m.value
|
||||
), S = j(() => {
|
||||
var y, _, f, k;
|
||||
var y, a, f, h;
|
||||
if (!N.value) return "";
|
||||
const i = String(
|
||||
((y = b.value) == null ? void 0 : y.message) || ((_ = b.value) == null ? void 0 : _.error) || "Failed to load generation data."
|
||||
), r = String(((f = b.value) == null ? void 0 : f.code) || ((k = b.value) == null ? void 0 : k.stage) || "").trim();
|
||||
((y = b.value) == null ? void 0 : y.message) || ((a = b.value) == null ? void 0 : a.error) || "Failed to load generation data."
|
||||
), r = String(((f = b.value) == null ? void 0 : f.code) || ((h = b.value) == null ? void 0 : h.stage) || "").trim();
|
||||
return r ? `${i}
|
||||
|
||||
Code: ${r}
|
||||
@@ -149,10 +150,10 @@ Click to retry.`;
|
||||
key: 5,
|
||||
asset: t.asset
|
||||
}, null, 8, ["asset"])) : O("", !0),
|
||||
h.value ? (E(), R("div", gn, " No generation data found for this file. ")) : O("", !0),
|
||||
x.value ? (E(), R("details", yn, [
|
||||
x.value ? (E(), R("div", gn, " No generation data found for this file. ")) : O("", !0),
|
||||
k.value ? (E(), R("details", yn, [
|
||||
r[2] || (r[2] = I("summary", { style: { cursor: "pointer", padding: "10px 12px", color: "rgba(255,255,255,0.78)", "user-select": "none" } }, " Raw metadata ", -1)),
|
||||
I("pre", an, $(x.value), 1)
|
||||
I("pre", _n, $(k.value), 1)
|
||||
])) : O("", !0)
|
||||
]));
|
||||
}
|
||||
@@ -169,23 +170,23 @@ const P = ((U = B) == null ? void 0 : U.VIEWER_GENINFO_MAX_ENTRIES) ?? 300, V =
|
||||
var c, u;
|
||||
try {
|
||||
const d = Date.now();
|
||||
for (const [g, a] of n.entries()) {
|
||||
if (!a) {
|
||||
for (const [g, _] of n.entries()) {
|
||||
if (!_) {
|
||||
n.delete(g);
|
||||
continue;
|
||||
}
|
||||
d - (a.at || 0) > t && n.delete(g);
|
||||
d - (_.at || 0) > t && n.delete(g);
|
||||
}
|
||||
if (n.size <= e) return;
|
||||
const l = Array.from(n.entries()).sort(
|
||||
(g, a) => {
|
||||
(g, _) => {
|
||||
var p, b;
|
||||
return (((p = g == null ? void 0 : g[1]) == null ? void 0 : p.at) || 0) - (((b = a == null ? void 0 : a[1]) == null ? void 0 : b.at) || 0);
|
||||
return (((p = g == null ? void 0 : g[1]) == null ? void 0 : p.at) || 0) - (((b = _ == null ? void 0 : _[1]) == null ? void 0 : b.at) || 0);
|
||||
}
|
||||
), o = n.size - e;
|
||||
for (let g = 0; g < o; g++) {
|
||||
const a = (c = l[g]) == null ? void 0 : c[0];
|
||||
a != null && n.delete(a);
|
||||
const _ = (c = l[g]) == null ? void 0 : c[0];
|
||||
_ != null && n.delete(_);
|
||||
}
|
||||
} catch (d) {
|
||||
(u = console.debug) == null || u.call(console, d);
|
||||
@@ -332,7 +333,7 @@ const P = ((U = B) == null ? void 0 : U.VIEWER_GENINFO_MAX_ENTRIES) ?? 300, V =
|
||||
(t = console.debug) == null || t.call(console, e);
|
||||
}
|
||||
}, kn = (n, t) => {
|
||||
var c, u, d, l, o, g, a;
|
||||
var c, u, d, l, o, g, _;
|
||||
const e = t && typeof t == "object" ? t : null;
|
||||
if (!e) return n;
|
||||
try {
|
||||
@@ -378,7 +379,7 @@ const P = ((U = B) == null ? void 0 : U.VIEWER_GENINFO_MAX_ENTRIES) ?? 300, V =
|
||||
}
|
||||
}
|
||||
} catch (p) {
|
||||
(a = console.debug) == null || a.call(console, p);
|
||||
(_ = console.debug) == null || _.call(console, p);
|
||||
}
|
||||
return n;
|
||||
};
|
||||
@@ -390,10 +391,10 @@ async function jn(n, { getAssetMetadata: t, getFileMetadataScoped: e, metadataCa
|
||||
const g = l ? D(V, l, A) : null;
|
||||
if (g && typeof g == "object")
|
||||
return { ...n, ...g };
|
||||
const a = l ? D(W, l, M) : null;
|
||||
if (a) {
|
||||
const _ = l ? D(W, l, M) : null;
|
||||
if (_) {
|
||||
try {
|
||||
J(o, a);
|
||||
J(o, _);
|
||||
} catch (m) {
|
||||
(b = console.debug) == null || b.call(console, m);
|
||||
}
|
||||
@@ -408,23 +409,23 @@ async function jn(n, { getAssetMetadata: t, getFileMetadataScoped: e, metadataCa
|
||||
(v = console.debug) == null || v.call(console, m);
|
||||
}
|
||||
const p = async () => {
|
||||
var S, i, r, y, _;
|
||||
var S, i, r, y, a;
|
||||
const m = d != null ? L(() => {
|
||||
var f, k;
|
||||
return ((k = (f = c == null ? void 0 : c.getCached) == null ? void 0 : f.call(c, d)) == null ? void 0 : k.data) || null;
|
||||
var f, h;
|
||||
return ((h = (f = c == null ? void 0 : c.getCached) == null ? void 0 : f.call(c, d)) == null ? void 0 : h.data) || null;
|
||||
}, null) : null;
|
||||
m && typeof m == "object" && (o = { ...n, ...m });
|
||||
const N = !!(o != null && o.has_generation_data || o != null && o.has_workflow || o != null && o.has_generation || o != null && o.has_generation_info), x = !!(o != null && o.geninfo || o != null && o.prompt || o != null && o.workflow || o != null && o.metadata);
|
||||
let h = null;
|
||||
if (d != null && (!F(o) || N && !x)) {
|
||||
const N = !!(o != null && o.has_generation_data || o != null && o.has_workflow || o != null && o.has_generation || o != null && o.has_generation_info), k = !!(o != null && o.geninfo || o != null && o.prompt || o != null && o.workflow || o != null && o.metadata);
|
||||
let x = null;
|
||||
if (d != null && (!F(o) || N && !k)) {
|
||||
const f = await L(
|
||||
() => t == null ? void 0 : t(d, u ? { signal: u } : {}),
|
||||
null
|
||||
);
|
||||
f != null && f.ok && f.data && typeof f.data == "object" ? (o = { ...o, ...f.data }, L(() => {
|
||||
var k;
|
||||
return (k = c == null ? void 0 : c.setCached) == null ? void 0 : k.call(c, d, f.data);
|
||||
})) : f && (f == null ? void 0 : f.code) !== "ABORTED" && (h = {
|
||||
var h;
|
||||
return (h = c == null ? void 0 : c.setCached) == null ? void 0 : h.call(c, d, f.data);
|
||||
})) : f && (f == null ? void 0 : f.code) !== "ABORTED" && (x = {
|
||||
kind: "fetch_error",
|
||||
stage: "asset",
|
||||
code: (f == null ? void 0 : f.code) || "FETCH_ERROR",
|
||||
@@ -433,7 +434,7 @@ async function jn(n, { getAssetMetadata: t, getFileMetadataScoped: e, metadataCa
|
||||
}
|
||||
if (!F(o))
|
||||
try {
|
||||
const f = String((o == null ? void 0 : o.source) || (o == null ? void 0 : o.type) || "output").trim().toLowerCase() || "output", k = String(
|
||||
const f = String((o == null ? void 0 : o.source) || (o == null ? void 0 : o.type) || "output").trim().toLowerCase() || "output", h = String(
|
||||
(o == null ? void 0 : o.filename) || (o == null ? void 0 : o.name) || ((S = o == null ? void 0 : o.file_info) == null ? void 0 : S.filename) || ""
|
||||
).trim(), Y = String(
|
||||
(o == null ? void 0 : o.subfolder) || ((i = o == null ? void 0 : o.file_info) == null ? void 0 : i.subfolder) || ""
|
||||
@@ -442,12 +443,12 @@ async function jn(n, { getAssetMetadata: t, getFileMetadataScoped: e, metadataCa
|
||||
).trim(), s = String(
|
||||
(o == null ? void 0 : o.filepath) || (o == null ? void 0 : o.path) || ((y = o == null ? void 0 : o.file_info) == null ? void 0 : y.filepath) || ""
|
||||
).trim();
|
||||
if (k) {
|
||||
if (h) {
|
||||
const w = await L(
|
||||
() => e == null ? void 0 : e(
|
||||
{
|
||||
type: f,
|
||||
filename: k,
|
||||
filename: h,
|
||||
subfolder: Y,
|
||||
root_id: Z,
|
||||
filepath: s
|
||||
@@ -456,7 +457,7 @@ async function jn(n, { getAssetMetadata: t, getFileMetadataScoped: e, metadataCa
|
||||
),
|
||||
null
|
||||
);
|
||||
w != null && w.ok && w.data ? o = kn({ ...o }, w.data) : w && (w == null ? void 0 : w.code) !== "ABORTED" && (h = {
|
||||
w != null && w.ok && w.data ? o = kn({ ...o }, w.data) : w && (w == null ? void 0 : w.code) !== "ABORTED" && (x = {
|
||||
kind: "fetch_error",
|
||||
stage: "file_scoped",
|
||||
code: (w == null ? void 0 : w.code) || "FETCH_ERROR",
|
||||
@@ -464,40 +465,40 @@ async function jn(n, { getAssetMetadata: t, getFileMetadataScoped: e, metadataCa
|
||||
});
|
||||
}
|
||||
} catch (f) {
|
||||
(_ = console.debug) == null || _.call(console, f);
|
||||
(a = console.debug) == null || a.call(console, f);
|
||||
}
|
||||
if (xn(o), !F(o) && h) {
|
||||
if (xn(o), !F(o) && x) {
|
||||
const f = wn(o);
|
||||
f && f.kind === "media_pipeline" || J(o, h);
|
||||
f && f.kind === "media_pipeline" || J(o, x);
|
||||
}
|
||||
return F(o) && l ? H(V, l, o, A, P) : h && l && H(
|
||||
return F(o) && l ? H(V, l, o, A, P) : x && l && H(
|
||||
W,
|
||||
l,
|
||||
h,
|
||||
x,
|
||||
M,
|
||||
P
|
||||
), o;
|
||||
};
|
||||
if (l) {
|
||||
const m = () => {
|
||||
var x;
|
||||
var k;
|
||||
try {
|
||||
C.delete(l);
|
||||
} catch (h) {
|
||||
(x = console.debug) == null || x.call(console, h);
|
||||
} catch (x) {
|
||||
(k = console.debug) == null || k.call(console, x);
|
||||
}
|
||||
};
|
||||
try {
|
||||
(G = u == null ? void 0 : u.addEventListener) == null || G.call(u, "abort", m, { once: !0 });
|
||||
} catch (x) {
|
||||
(T = console.debug) == null || T.call(console, x);
|
||||
} catch (k) {
|
||||
(T = console.debug) == null || T.call(console, k);
|
||||
}
|
||||
const N = p().finally(() => {
|
||||
var x, h, S;
|
||||
var k, x, S;
|
||||
try {
|
||||
(x = u == null ? void 0 : u.removeEventListener) == null || x.call(u, "abort", m);
|
||||
(k = u == null ? void 0 : u.removeEventListener) == null || k.call(u, "abort", m);
|
||||
} catch (i) {
|
||||
(h = console.debug) == null || h.call(console, i);
|
||||
(x = console.debug) == null || x.call(console, i);
|
||||
}
|
||||
try {
|
||||
C.delete(l);
|
||||
@@ -513,7 +514,7 @@ function Nn({ title: n, asset: t, ui: e } = {}) {
|
||||
var l;
|
||||
const c = document.createElement("div");
|
||||
try {
|
||||
const { app: o } = cn(_n, {
|
||||
const { app: o } = cn(an, {
|
||||
title: n,
|
||||
asset: t,
|
||||
loading: !!(e != null && e.loading),
|
||||
@@ -523,8 +524,8 @@ function Nn({ title: n, asset: t, ui: e } = {}) {
|
||||
var g;
|
||||
try {
|
||||
o.unmount();
|
||||
} catch (a) {
|
||||
(g = console.debug) == null || g.call(console, a);
|
||||
} catch (_) {
|
||||
(g = console.debug) == null || g.call(console, _);
|
||||
}
|
||||
}, c;
|
||||
} catch (o) {
|
||||
@@ -541,11 +542,11 @@ function Nn({ title: n, asset: t, ui: e } = {}) {
|
||||
o.style.cssText = "border: 1px solid rgba(255,255,255,0.10); border-radius: 10px; background: rgba(255,255,255,0.04); overflow: hidden;";
|
||||
const g = document.createElement("summary");
|
||||
g.textContent = "Raw metadata", g.style.cssText = "cursor: pointer; padding: 10px 12px; color: rgba(255,255,255,0.78); user-select: none;";
|
||||
const a = document.createElement("pre");
|
||||
a.style.cssText = "margin:0; padding: 10px 12px; max-height: 280px; overflow:auto; font-size: 11px; line-height: 1.35; color: rgba(255,255,255,0.86);";
|
||||
const _ = document.createElement("pre");
|
||||
_.style.cssText = "margin:0; padding: 10px 12px; max-height: 280px; overflow:auto; font-size: 11px; line-height: 1.35; color: rgba(255,255,255,0.86);";
|
||||
let p = typeof d == "string" ? d : JSON.stringify(d, null, 2);
|
||||
p.length > 4e4 && (p = `${p.slice(0, 4e4)}
|
||||
...(truncated)`), a.textContent = p, o.appendChild(g), o.appendChild(a), u.appendChild(o);
|
||||
...(truncated)`), _.textContent = p, o.appendChild(g), o.appendChild(_), u.appendChild(o);
|
||||
}
|
||||
return u;
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { M as s, a as o, P as D, b as t, c as M, f as d, g as l, i as E, d as r, r as i } from "./entry-GhzfvnC5.js";
|
||||
import { M as s, a as o, P as D, b as t, c as M, f as d, g as l, i as E, d as r, r as i } from "./entry-B5M8pQrP.js";
|
||||
export {
|
||||
s as MODEL3D_EXTS,
|
||||
o as MODEL3D_EXT_TO_LOADER,
|
||||
@@ -1,4 +1,4 @@
|
||||
import { h as C } from "./entry-GhzfvnC5.js";
|
||||
import { h as C } from "./entry-B5M8pQrP.js";
|
||||
function H(t, n, o) {
|
||||
return 0.2126 * t + 0.7152 * n + 0.0722 * o;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { i as q, n as Y, s as k } from "./entry-GhzfvnC5.js";
|
||||
import { i as q, n as Y, s as k } from "./entry-B5M8pQrP.js";
|
||||
function Z(o) {
|
||||
if (!o) return null;
|
||||
try {
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
import "./chunks/entry-GhzfvnC5.js";
|
||||
import "./chunks/entry-B5M8pQrP.js";
|
||||
import "../../scripts/app.js";
|
||||
|
||||
@@ -176,6 +176,88 @@ async def fetch_by_job_id(job_id: str) -> list[CoreAssetInfo]:
|
||||
return []
|
||||
|
||||
|
||||
def _clean_sync_tags(tags: list[str] | tuple[str, ...] | None) -> list[str]:
|
||||
out: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for tag in tags or []:
|
||||
text = str(tag or "").strip()
|
||||
if not text:
|
||||
continue
|
||||
key = text.casefold()
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
out.append(text)
|
||||
return out
|
||||
|
||||
|
||||
async def _asset_filepath_by_id(db: Any, asset_id: int) -> str:
|
||||
try:
|
||||
res = await db.aquery("SELECT filepath FROM assets WHERE id = ?", (int(asset_id),))
|
||||
if res.ok and res.data:
|
||||
return str((res.data[0] or {}).get("filepath") or "").strip()
|
||||
except Exception as exc:
|
||||
logger.debug("Core asset sync filepath lookup failed for asset %s: %s", asset_id, exc)
|
||||
return ""
|
||||
|
||||
|
||||
async def sync_user_metadata_by_asset_id(
|
||||
db: Any,
|
||||
asset_id: int,
|
||||
*,
|
||||
rating: int | None = None,
|
||||
tags: list[str] | tuple[str, ...] | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> bool:
|
||||
"""Best-effort write-through of Majoor metadata into Comfy core assets."""
|
||||
if not is_available():
|
||||
return False
|
||||
filepath = await _asset_filepath_by_id(db, asset_id)
|
||||
if not filepath:
|
||||
return False
|
||||
info = await fetch_by_path(filepath)
|
||||
if not info:
|
||||
return False
|
||||
payload: dict[str, Any] = {}
|
||||
if rating is not None:
|
||||
payload["rating"] = max(0, min(5, int(rating or 0)))
|
||||
if tags is not None:
|
||||
payload["tags"] = _clean_sync_tags(tags)
|
||||
if metadata:
|
||||
payload["metadata"] = dict(metadata)
|
||||
if not payload:
|
||||
return False
|
||||
|
||||
try:
|
||||
from app.assets import services as asset_services # type: ignore
|
||||
except Exception as exc:
|
||||
logger.debug("Core asset services unavailable for metadata sync: %s", exc)
|
||||
return False
|
||||
|
||||
for name in (
|
||||
"update_asset_user_metadata",
|
||||
"update_asset_metadata",
|
||||
"update_asset",
|
||||
"set_asset_metadata",
|
||||
):
|
||||
fn = getattr(asset_services, name, None)
|
||||
if not callable(fn):
|
||||
continue
|
||||
for args, kwargs in (
|
||||
((info.reference_id,), payload),
|
||||
((), {"asset_id": info.reference_id, **payload}),
|
||||
((), {"reference_id": info.reference_id, **payload}),
|
||||
):
|
||||
try:
|
||||
result = fn(*args, **kwargs)
|
||||
if hasattr(result, "__await__"):
|
||||
await result
|
||||
return True
|
||||
except Exception as exc:
|
||||
logger.debug("Core asset metadata sync via %s failed: %s", name, exc)
|
||||
return False
|
||||
|
||||
|
||||
def _paths_equal(a: str, b: str) -> bool:
|
||||
"""Case-insensitive, separator-normalised path comparison."""
|
||||
import os
|
||||
|
||||
@@ -374,9 +374,9 @@ def _resolve_builtin_roots() -> tuple[Path | None, Path | None]:
|
||||
except Exception:
|
||||
output_root = None
|
||||
try:
|
||||
import folder_paths
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory
|
||||
|
||||
input_root = Path(folder_paths.get_input_directory()).resolve()
|
||||
input_root = Path(str(get_input_directory() or "")).resolve()
|
||||
except Exception:
|
||||
input_root = None
|
||||
return output_root, input_root
|
||||
|
||||
@@ -5,21 +5,14 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
from ...adapters.comfy_core import get_input_directory as get_comfy_input_directory
|
||||
from ...shared import Result
|
||||
from ..index.scan_batch_utils import normalize_filepath_str
|
||||
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
except Exception: # pragma: no cover
|
||||
|
||||
class _FolderPathsStub:
|
||||
@staticmethod
|
||||
def get_input_directory() -> str:
|
||||
return str((Path(__file__).resolve().parents[3] / "input").resolve())
|
||||
|
||||
folder_paths = _FolderPathsStub() # type: ignore
|
||||
folder_paths = SimpleNamespace(get_input_directory=get_comfy_input_directory)
|
||||
|
||||
|
||||
async def load_asset_filepath_by_id(services: dict[str, Any], asset_id: int) -> Result[str]:
|
||||
@@ -325,7 +318,7 @@ async def resolve_or_create_asset_id(
|
||||
normalize_path: Callable[[str], Path | None],
|
||||
is_within_root: Callable[[Path, Path], bool],
|
||||
get_runtime_output_root_fn: Callable[[], str],
|
||||
get_input_directory: Callable[[], str] | None = None,
|
||||
get_input_directory: Callable[[], str | None] | None = None,
|
||||
list_custom_roots_fn: Callable[[], Result[Any]],
|
||||
resolve_custom_root_fn: Callable[[str], Result[Any]],
|
||||
safe_error_message: Callable[[Exception, str], str],
|
||||
@@ -348,10 +341,10 @@ async def resolve_or_create_asset_id(
|
||||
source = _normalize_asset_source(file_type)
|
||||
|
||||
if get_input_directory is None:
|
||||
get_input_directory = folder_paths.get_input_directory
|
||||
get_input_directory = get_comfy_input_directory
|
||||
|
||||
out_root = Path(get_runtime_output_root_fn()).resolve(strict=False)
|
||||
in_root = Path(get_input_directory()).resolve(strict=False)
|
||||
in_root = Path(str(get_input_directory() or "")).resolve(strict=False)
|
||||
source, base_dir, resolved_root_id = _resolve_asset_root(
|
||||
resolved,
|
||||
source=source,
|
||||
@@ -391,7 +384,7 @@ async def infer_source_and_root_id_from_path(
|
||||
*,
|
||||
is_within_root: Callable[[Path, Path], bool],
|
||||
get_runtime_output_root_fn: Callable[[], str],
|
||||
get_input_directory: Callable[[], str] | None = None,
|
||||
get_input_directory: Callable[[], str | None] | None = None,
|
||||
list_custom_roots_fn: Callable[[], Result[Any]],
|
||||
logger: Any,
|
||||
) -> tuple[str, str | None]:
|
||||
@@ -400,9 +393,9 @@ async def infer_source_and_root_id_from_path(
|
||||
except Exception:
|
||||
out_root = Path(get_runtime_output_root_fn()).resolve(strict=False)
|
||||
if get_input_directory is None:
|
||||
get_input_directory = folder_paths.get_input_directory
|
||||
get_input_directory = get_comfy_input_directory
|
||||
try:
|
||||
in_root = Path(get_input_directory()).resolve(strict=False)
|
||||
in_root = Path(str(get_input_directory() or "")).resolve(strict=False)
|
||||
except Exception:
|
||||
in_root = (Path(__file__).resolve().parents[3] / "input").resolve(strict=False)
|
||||
|
||||
@@ -437,4 +430,4 @@ __all__ = [
|
||||
"load_asset_filepath_by_id",
|
||||
"load_asset_row_by_id",
|
||||
"resolve_or_create_asset_id",
|
||||
]
|
||||
]
|
||||
|
||||
@@ -958,7 +958,7 @@ class MetadataHelpers:
|
||||
same_quality = _metadata_quality_is_equal_sql()
|
||||
replace_raw_on_equal = _metadata_raw_should_replace_on_equal_quality_sql()
|
||||
|
||||
return await db.aexecute(
|
||||
result = await db.aexecute(
|
||||
f"""
|
||||
INSERT INTO asset_metadata
|
||||
(
|
||||
@@ -1053,6 +1053,23 @@ class MetadataHelpers:
|
||||
asset_id,
|
||||
),
|
||||
)
|
||||
if result.ok:
|
||||
try:
|
||||
from ...adapters.core_assets import sync_user_metadata_by_asset_id
|
||||
|
||||
await sync_user_metadata_by_asset_id(
|
||||
db,
|
||||
asset_id,
|
||||
metadata={
|
||||
"metadata_quality": metadata_quality,
|
||||
"workflow_type": workflow_type,
|
||||
"generation_time_ms": generation_time_ms,
|
||||
"positive_prompt": positive_prompt,
|
||||
},
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
async def refresh_metadata_if_needed(
|
||||
|
||||
@@ -4,6 +4,7 @@ Asset updater - handles rating and tag updates.
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from ...adapters.core_assets import sync_user_metadata_by_asset_id
|
||||
from ...adapters.db.sqlite import Sqlite
|
||||
from ...shared import Result
|
||||
|
||||
@@ -67,6 +68,7 @@ class AssetUpdater:
|
||||
if not result.ok:
|
||||
return Result.Err("UPDATE_FAILED", result.error or "Failed to update rating")
|
||||
|
||||
await sync_user_metadata_by_asset_id(self.db, asset_id, rating=rating)
|
||||
return Result.Ok({"asset_id": asset_id, "rating": rating})
|
||||
|
||||
async def update_asset_tags(self, asset_id: int, tags: list[str]) -> Result[dict[str, Any]]:
|
||||
@@ -95,6 +97,7 @@ class AssetUpdater:
|
||||
if not result.ok:
|
||||
return Result.Err("UPDATE_FAILED", result.error or "Failed to update tags")
|
||||
|
||||
await sync_user_metadata_by_asset_id(self.db, asset_id, tags=sanitized)
|
||||
return Result.Ok({"asset_id": asset_id, "tags": sanitized})
|
||||
|
||||
def _sanitize_tags(self, tags: list[str]) -> list[str]:
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
Asset management endpoints: ratings, tags, service retry.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import web
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory as _get_comfy_input_directory
|
||||
from mjr_am_backend.config import TO_THREAD_TIMEOUT_S, get_runtime_output_root
|
||||
from mjr_am_backend.custom_roots import list_custom_roots, resolve_custom_root
|
||||
from mjr_am_backend.features.assets import (
|
||||
@@ -45,9 +47,6 @@ from mjr_am_backend.features.assets.delete_service import (
|
||||
from mjr_am_backend.features.assets.filename_validator import (
|
||||
validate_filename as _validate_filename,
|
||||
)
|
||||
from mjr_am_backend.features.assets.lookup_service import (
|
||||
folder_paths,
|
||||
)
|
||||
from mjr_am_backend.features.assets.lookup_service import (
|
||||
infer_source_and_root_id_from_path as _infer_source_and_root_id,
|
||||
)
|
||||
@@ -81,6 +80,11 @@ from ..core import (
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
folder_paths = SimpleNamespace(get_input_directory=_get_comfy_input_directory)
|
||||
|
||||
|
||||
def get_input_directory() -> str:
|
||||
return str(folder_paths.get_input_directory())
|
||||
|
||||
MAX_TAGS_PER_ASSET = 50
|
||||
MAX_TAG_LENGTH = 100
|
||||
@@ -170,7 +174,7 @@ def _wired_resolve_or_create(**kwargs: Any) -> Any:
|
||||
normalize_path=_normalize_path,
|
||||
is_within_root=_is_within_root,
|
||||
get_runtime_output_root_fn=get_runtime_output_root,
|
||||
get_input_directory=folder_paths.get_input_directory,
|
||||
get_input_directory=get_input_directory,
|
||||
list_custom_roots_fn=list_custom_roots,
|
||||
resolve_custom_root_fn=resolve_custom_root,
|
||||
safe_error_message=_safe_error_message,
|
||||
@@ -382,7 +386,7 @@ def register_asset_routes(routes: web.RouteTableDef) -> None:
|
||||
infer_source_and_root_id_from_path=_infer_source_and_root_id,
|
||||
is_within_root=_is_within_root,
|
||||
get_runtime_output_root=get_runtime_output_root,
|
||||
get_input_directory=folder_paths.get_input_directory,
|
||||
get_input_directory=get_input_directory,
|
||||
list_custom_roots=list_custom_roots,
|
||||
to_thread_timeout_s=TO_THREAD_TIMEOUT_S,
|
||||
safe_error_message=_safe_error_message,
|
||||
|
||||
@@ -21,6 +21,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import web
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory
|
||||
from mjr_am_backend.config import OUTPUT_ROOT_PATH
|
||||
from mjr_am_backend.custom_roots import resolve_custom_root
|
||||
from mjr_am_backend.features.assets.download_service import (
|
||||
@@ -39,11 +40,6 @@ from mjr_am_backend.routes.core.security import (
|
||||
from mjr_am_backend.routes.core.services import _require_services
|
||||
from mjr_am_backend.shared import Result, get_logger, sanitize_error_message
|
||||
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
except Exception:
|
||||
folder_paths = None # type: ignore
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
_BATCH_DIR = Path(
|
||||
@@ -365,9 +361,7 @@ def _resolve_base_dir(item: dict[str, Any], typ: str) -> Path | None:
|
||||
|
||||
def _resolve_input_base_dir() -> Path | None:
|
||||
try:
|
||||
if folder_paths is None:
|
||||
return None
|
||||
return Path(str(folder_paths.get_input_directory())).resolve(strict=True)
|
||||
return Path(str(get_input_directory())).resolve(strict=True)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
@@ -641,8 +641,9 @@ def register_db_maintenance_routes(routes: web.RouteTableDef) -> None:
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
input_path = str(Path(folder_paths.get_input_directory()).resolve())
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory
|
||||
|
||||
input_path = str(Path(str(get_input_directory() or "")).resolve())
|
||||
_spawn_background_task(
|
||||
index_service.scan_directory(
|
||||
input_path,
|
||||
@@ -789,8 +790,9 @@ def register_db_maintenance_routes(routes: web.RouteTableDef) -> None:
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
input_path = str(Path(folder_paths.get_input_directory()).resolve())
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory
|
||||
|
||||
input_path = str(Path(str(get_input_directory() or "")).resolve())
|
||||
_spawn_background_task(
|
||||
index_service.scan_directory(
|
||||
input_path,
|
||||
@@ -1379,8 +1381,9 @@ def register_db_maintenance_routes(routes: web.RouteTableDef) -> None:
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
input_path = str(Path(folder_paths.get_input_directory()).resolve())
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory
|
||||
|
||||
input_path = str(Path(str(get_input_directory() or "")).resolve())
|
||||
_spawn_background_task(
|
||||
index_service.scan_directory(
|
||||
input_path,
|
||||
|
||||
@@ -10,17 +10,7 @@ from pathlib import Path
|
||||
|
||||
from aiohttp import web
|
||||
from mjr_am_backend.adapters.comfy_core import get_capabilities as get_comfy_core_capabilities
|
||||
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
except Exception:
|
||||
class _FolderPathsStub:
|
||||
@staticmethod
|
||||
def get_input_directory() -> str:
|
||||
return str((Path(__file__).resolve().parents[3] / "input").resolve())
|
||||
|
||||
folder_paths = _FolderPathsStub() # type: ignore
|
||||
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory
|
||||
from mjr_am_backend.config import (
|
||||
EXECUTION_IDLE_GRACE_SECONDS,
|
||||
MEDIA_PROBE_BACKEND,
|
||||
@@ -494,11 +484,11 @@ def register_health_routes(routes: web.RouteTableDef) -> None:
|
||||
if scope == "output":
|
||||
roots = [out_root]
|
||||
elif scope == "input":
|
||||
roots = [str(Path(folder_paths.get_input_directory()).resolve(strict=False))]
|
||||
roots = [str(Path(get_input_directory()).resolve(strict=False))]
|
||||
elif scope == "all":
|
||||
roots = [
|
||||
out_root,
|
||||
str(Path(folder_paths.get_input_directory()).resolve(strict=False)),
|
||||
str(Path(get_input_directory()).resolve(strict=False)),
|
||||
]
|
||||
elif scope == "custom":
|
||||
if not _is_valid_custom_root_id(custom_root_id):
|
||||
@@ -1725,7 +1715,7 @@ def register_health_routes(routes: web.RouteTableDef) -> None:
|
||||
|
||||
roots = {
|
||||
"output_directory": await _runtime_output_root(svc),
|
||||
"input_directory": str(Path(folder_paths.get_input_directory()).resolve()),
|
||||
"input_directory": str(Path(get_input_directory()).resolve()),
|
||||
}
|
||||
|
||||
custom = list_custom_roots()
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
Metadata extraction endpoint.
|
||||
"""
|
||||
import asyncio
|
||||
from types import SimpleNamespace
|
||||
|
||||
from aiohttp import web
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory as _get_comfy_input_directory
|
||||
from mjr_am_backend.config import TO_THREAD_TIMEOUT_S, get_runtime_output_root
|
||||
from mjr_am_backend.custom_roots import resolve_custom_root
|
||||
from mjr_am_backend.shared import Result, sanitize_error_message
|
||||
@@ -18,21 +20,13 @@ from ..core import (
|
||||
_safe_rel_path,
|
||||
)
|
||||
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
except Exception:
|
||||
from pathlib import Path
|
||||
|
||||
class _FolderPathsStub:
|
||||
@staticmethod
|
||||
def get_input_directory() -> str:
|
||||
return str((Path(__file__).resolve().parents[3] / "input").resolve())
|
||||
|
||||
folder_paths = _FolderPathsStub() # type: ignore
|
||||
|
||||
|
||||
_METADATA_RATE_LIMIT_MAX_REQUESTS = 10
|
||||
_METADATA_RATE_LIMIT_WINDOW_SECONDS = 60
|
||||
folder_paths = SimpleNamespace(get_input_directory=_get_comfy_input_directory)
|
||||
|
||||
|
||||
def get_input_directory() -> str:
|
||||
return str(folder_paths.get_input_directory())
|
||||
|
||||
|
||||
def register_metadata_routes(routes: web.RouteTableDef) -> None:
|
||||
@@ -91,7 +85,7 @@ def register_metadata_routes(routes: web.RouteTableDef) -> None:
|
||||
|
||||
# Resolve base root
|
||||
if file_type == "input":
|
||||
base_root = folder_paths.get_input_directory()
|
||||
base_root = get_input_directory()
|
||||
elif file_type == "custom":
|
||||
if not root_id:
|
||||
return _json_response(Result.Err("INVALID_INPUT", "Missing custom root_id"))
|
||||
|
||||
@@ -4,20 +4,11 @@ Directory scanning and file indexing endpoints.
|
||||
import asyncio
|
||||
import time
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
except Exception:
|
||||
class _FolderPathsStub:
|
||||
@staticmethod
|
||||
def get_input_directory() -> str:
|
||||
return str((Path(__file__).resolve().parents[3] / "input").resolve())
|
||||
|
||||
folder_paths = _FolderPathsStub() # type: ignore
|
||||
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory as _get_comfy_input_directory
|
||||
from mjr_am_backend.adapters.db.schema import purge_orphan_vec_embeddings, rebuild_fts
|
||||
from mjr_am_backend.config import (
|
||||
COLLECTIONS_DIR_PATH,
|
||||
@@ -92,6 +83,11 @@ from .scan_watcher import (
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
folder_paths = SimpleNamespace(get_input_directory=_get_comfy_input_directory)
|
||||
|
||||
|
||||
def get_input_directory() -> str:
|
||||
return str(folder_paths.get_input_directory())
|
||||
|
||||
|
||||
def _send_prompt_event(event: str, payload: Any) -> None:
|
||||
@@ -337,7 +333,7 @@ def register_scan_routes(routes: web.RouteTableDef) -> None:
|
||||
scan_source = "output"
|
||||
custom_root_id = None
|
||||
elif scope in ("input", "inputs"):
|
||||
directory = folder_paths.get_input_directory()
|
||||
directory = get_input_directory()
|
||||
scan_source = "input"
|
||||
custom_root_id = None
|
||||
elif scope == "custom":
|
||||
@@ -351,7 +347,7 @@ def register_scan_routes(routes: web.RouteTableDef) -> None:
|
||||
all_scope_result = await run_all_scope_scan(
|
||||
services=svc,
|
||||
output_root=output_root,
|
||||
input_root=folder_paths.get_input_directory(),
|
||||
input_root=get_input_directory(),
|
||||
recursive=payload.recursive,
|
||||
incremental=payload.incremental,
|
||||
fast=payload.fast,
|
||||
@@ -395,7 +391,7 @@ def register_scan_routes(routes: web.RouteTableDef) -> None:
|
||||
base_root_res = _resolve_scan_root(str(output_root))
|
||||
allowed = base_root_res.ok and _is_within_root(normalized_dir, base_root_res.data)
|
||||
elif scan_source == "input":
|
||||
base_root_res = _resolve_scan_root(str(folder_paths.get_input_directory()))
|
||||
base_root_res = _resolve_scan_root(str(get_input_directory()))
|
||||
allowed = base_root_res.ok and _is_within_root(normalized_dir, base_root_res.data)
|
||||
elif scan_source == "custom":
|
||||
root_result = resolve_custom_root(str(custom_root_id or scan_root_id or ""))
|
||||
@@ -407,7 +403,7 @@ def register_scan_routes(routes: web.RouteTableDef) -> None:
|
||||
else:
|
||||
# No explicit scope: allow only within configured output/input or registered custom roots.
|
||||
out_root = _resolve_scan_root(str(output_root))
|
||||
in_root = _resolve_scan_root(str(folder_paths.get_input_directory()))
|
||||
in_root = _resolve_scan_root(str(get_input_directory()))
|
||||
allowed = (
|
||||
(out_root.ok and _is_within_root(normalized_dir, out_root.data))
|
||||
or (in_root.ok and _is_within_root(normalized_dir, in_root.data))
|
||||
@@ -581,7 +577,7 @@ def register_scan_routes(routes: web.RouteTableDef) -> None:
|
||||
except Exception:
|
||||
out_root = None
|
||||
try:
|
||||
in_root = Path(folder_paths.get_input_directory()).resolve(strict=True)
|
||||
in_root = Path(get_input_directory()).resolve(strict=True)
|
||||
except Exception:
|
||||
in_root = None
|
||||
|
||||
@@ -643,7 +639,7 @@ def register_scan_routes(routes: web.RouteTableDef) -> None:
|
||||
continue
|
||||
|
||||
if file_type == "input":
|
||||
base_root = folder_paths.get_input_directory()
|
||||
base_root = get_input_directory()
|
||||
elif file_type == "custom":
|
||||
root_result = resolve_custom_root(str(root_id or ""))
|
||||
if not root_result.ok:
|
||||
@@ -1332,7 +1328,7 @@ def register_scan_routes(routes: web.RouteTableDef) -> None:
|
||||
scope=scope,
|
||||
custom_root_id=custom_root_id,
|
||||
output_root=output_root,
|
||||
input_root=folder_paths.get_input_directory(),
|
||||
input_root=get_input_directory(),
|
||||
resolve_custom_root=resolve_custom_root,
|
||||
)
|
||||
if not target_roots_res.ok:
|
||||
|
||||
@@ -394,15 +394,9 @@ def _resolve_scan_root(directory: str) -> Result[Path]:
|
||||
|
||||
def _resolve_input_directory() -> Path:
|
||||
try:
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
except Exception:
|
||||
class _FolderPathsStub:
|
||||
@staticmethod
|
||||
def get_input_directory() -> str:
|
||||
return str((Path(__file__).resolve().parents[3] / "input").resolve())
|
||||
folder_paths = _FolderPathsStub() # type: ignore
|
||||
return Path(folder_paths.get_input_directory()).resolve()
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory
|
||||
|
||||
return Path(str(get_input_directory() or "")).resolve()
|
||||
except Exception:
|
||||
return (Path(__file__).parent.parent.parent.parent / "input").resolve()
|
||||
|
||||
|
||||
@@ -9,17 +9,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
except Exception:
|
||||
class _FolderPathsStub:
|
||||
@staticmethod
|
||||
def get_input_directory() -> str:
|
||||
return str((Path(__file__).resolve().parents[3] / "input").resolve())
|
||||
|
||||
folder_paths = _FolderPathsStub() # type: ignore
|
||||
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory
|
||||
from mjr_am_backend.config import TO_THREAD_TIMEOUT_S
|
||||
from mjr_am_backend.custom_roots import resolve_custom_root
|
||||
from mjr_am_backend.shared import Result, get_logger, sanitize_error_message
|
||||
@@ -114,7 +104,7 @@ def register_staging_routes(routes: web.RouteTableDef, *, deps: dict | None = No
|
||||
_runtime_output_root_ = _d.get("_runtime_output_root", _runtime_output_root) if _d is not None else _runtime_output_root
|
||||
_resolve_stage_destination_ = _d.get("_resolve_stage_destination", _resolve_stage_destination) if _d is not None else _resolve_stage_destination
|
||||
_resolve_custom_root_ = _d.get("resolve_custom_root", resolve_custom_root) if _d is not None else resolve_custom_root
|
||||
_folder_paths_ = _d.get("folder_paths", folder_paths) if _d is not None else folder_paths
|
||||
_get_input_directory_ = _d.get("get_input_directory", get_input_directory) if _d is not None else get_input_directory
|
||||
_is_path_allowed_ = _d.get("_is_path_allowed", _is_path_allowed) if _d is not None else _is_path_allowed
|
||||
_schedule_index_task_ = _d.get("_schedule_index_task", _schedule_index_task) if _d is not None else _schedule_index_task
|
||||
_audit_log_write_ = _d.get("audit_log_write", audit_log_write) if _d is not None else audit_log_write
|
||||
@@ -165,7 +155,7 @@ def register_staging_routes(routes: web.RouteTableDef, *, deps: dict | None = No
|
||||
await _audit_stage(result, requested_files=0, purpose=purpose, index=bool(index_staged))
|
||||
return _json_response(result)
|
||||
|
||||
input_root = Path(_folder_paths_.get_input_directory()).resolve()
|
||||
input_root = Path(_get_input_directory_()).resolve()
|
||||
output_root = await _runtime_output_root_(svc)
|
||||
staged = []
|
||||
staged_paths = []
|
||||
@@ -219,7 +209,7 @@ def register_staging_routes(routes: web.RouteTableDef, *, deps: dict | None = No
|
||||
continue
|
||||
|
||||
if file_type == "input":
|
||||
base_root = _folder_paths_.get_input_directory()
|
||||
base_root = _get_input_directory_()
|
||||
elif file_type == "custom":
|
||||
root_result = _resolve_custom_root_(str(root_id or ""))
|
||||
if not root_result.ok:
|
||||
|
||||
@@ -4,17 +4,7 @@ Upload handler for the /mjr/am/upload_input endpoint.
|
||||
from pathlib import Path
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
except Exception:
|
||||
class _FolderPathsStub:
|
||||
@staticmethod
|
||||
def get_input_directory() -> str:
|
||||
return str((Path(__file__).resolve().parents[3] / "input").resolve())
|
||||
|
||||
folder_paths = _FolderPathsStub() # type: ignore
|
||||
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory
|
||||
from mjr_am_backend.shared import Result, get_logger
|
||||
|
||||
from ..core import (
|
||||
@@ -77,7 +67,7 @@ def register_upload_routes(routes: web.RouteTableDef, *, deps: dict | None = Non
|
||||
_require_services_ = _d.get("_require_services", _require_services) if _d is not None else _require_services
|
||||
_write_multipart_file_atomic_ = _d.get("_write_multipart_file_atomic", _write_multipart_file_atomic) if _d is not None else _write_multipart_file_atomic
|
||||
_schedule_index_task_ = _d.get("_schedule_index_task", _schedule_index_task) if _d is not None else _schedule_index_task
|
||||
_folder_paths_ = _d.get("folder_paths", folder_paths) if _d is not None else folder_paths
|
||||
_get_input_directory_ = _d.get("get_input_directory", get_input_directory) if _d is not None else get_input_directory
|
||||
_audit_log_write_ = _d.get("audit_log_write", audit_log_write) if _d is not None else audit_log_write
|
||||
|
||||
async def _audit_upload(result: Result, **details: object) -> None:
|
||||
@@ -127,7 +117,7 @@ def register_upload_routes(routes: web.RouteTableDef, *, deps: dict | None = Non
|
||||
|
||||
skip_index = _upload_skip_index(request)
|
||||
|
||||
input_dir = Path(_folder_paths_.get_input_directory()).resolve()
|
||||
input_dir = Path(_get_input_directory_()).resolve()
|
||||
|
||||
write_res = await _write_multipart_file_atomic_(input_dir, filename, field)
|
||||
if not write_res.ok:
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
"""
|
||||
Search and list endpoints.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
except Exception:
|
||||
class _FolderPathsStub:
|
||||
@staticmethod
|
||||
def get_input_directory() -> str:
|
||||
return str((Path(__file__).resolve().parents[3] / "input").resolve())
|
||||
|
||||
folder_paths = _FolderPathsStub() # type: ignore
|
||||
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory as _get_comfy_input_directory
|
||||
from mjr_am_backend.config import TO_THREAD_TIMEOUT_S
|
||||
from mjr_am_backend.custom_roots import resolve_custom_root
|
||||
from mjr_am_backend.features.index.metadata_helpers import MetadataHelpers
|
||||
@@ -64,6 +54,11 @@ AUTOCOMPLETE_RATE_LIMIT_MAX_REQUESTS = 40
|
||||
AUTOCOMPLETE_RATE_LIMIT_WINDOW_SECONDS = 60
|
||||
|
||||
logger = get_logger(__name__)
|
||||
folder_paths = SimpleNamespace(get_input_directory=_get_comfy_input_directory)
|
||||
|
||||
|
||||
def get_input_directory() -> str:
|
||||
return str(folder_paths.get_input_directory())
|
||||
|
||||
|
||||
def _safe_error_message_for_base_exception(exc: BaseException, fallback: str) -> str:
|
||||
@@ -190,7 +185,7 @@ async def list_assets(request: web.Request) -> web.Response:
|
||||
normalize_sort_key=_normalize_sort_key,
|
||||
require_services=_require_services,
|
||||
touch_enrichment_pause=_touch_enrichment_pause,
|
||||
get_input_directory=folder_paths.get_input_directory,
|
||||
get_input_directory=get_input_directory,
|
||||
kickoff_background_scan=_kickoff_background_scan,
|
||||
list_filesystem_assets=_list_filesystem_assets,
|
||||
dedupe_result_assets_payload=_dedupe_result_assets_payload,
|
||||
|
||||
@@ -12,6 +12,7 @@ from pathlib import Path
|
||||
from urllib.parse import unquote
|
||||
|
||||
from aiohttp import web
|
||||
from mjr_am_backend.adapters.comfy_core import get_input_directory
|
||||
from mjr_am_backend.config import get_runtime_output_root
|
||||
from mjr_am_backend.custom_roots import list_custom_roots, resolve_custom_root
|
||||
from mjr_am_backend.features.viewer.info import build_viewer_media_info
|
||||
@@ -59,18 +60,6 @@ _ALLOWED_VIEWER_RESOURCE_EXTS = {
|
||||
".spz",
|
||||
}
|
||||
|
||||
try:
|
||||
import folder_paths # type: ignore
|
||||
except Exception:
|
||||
|
||||
class _FolderPathsStub:
|
||||
@staticmethod
|
||||
def get_input_directory() -> str:
|
||||
return str((Path(__file__).resolve().parents[3] / "input").resolve())
|
||||
|
||||
folder_paths = _FolderPathsStub() # type: ignore
|
||||
|
||||
|
||||
def _runtime_output_root() -> Path | None:
|
||||
try:
|
||||
return Path(get_runtime_output_root()).resolve(strict=False)
|
||||
@@ -80,7 +69,7 @@ def _runtime_output_root() -> Path | None:
|
||||
|
||||
def _input_root() -> Path | None:
|
||||
try:
|
||||
return Path(folder_paths.get_input_directory()).resolve(strict=False)
|
||||
return Path(str(get_input_directory() or "")).resolve(strict=False)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@@ -322,7 +311,7 @@ def _resolve_base_root(root_id: str, asset_type: str) -> tuple[Path | None, Resu
|
||||
|
||||
if asset_type == "input":
|
||||
try:
|
||||
return Path(folder_paths.get_input_directory()).resolve(strict=False), None
|
||||
return Path(str(get_input_directory() or "")).resolve(strict=False), None
|
||||
except Exception:
|
||||
return None, Result.Err("VIEW_FAILED", "Input directory is unavailable")
|
||||
|
||||
|
||||
@@ -84,6 +84,9 @@ async def query_browser_rows(db: Any, filepaths: list[str]) -> list[dict] | None
|
||||
SELECT
|
||||
a.id,
|
||||
a.filepath,
|
||||
a.job_id,
|
||||
a.source_node_id,
|
||||
a.source_node_type,
|
||||
COALESCE(m.rating, 0) AS rating,
|
||||
COALESCE(m.tags, '[]') AS tags,
|
||||
m.has_workflow AS has_workflow,
|
||||
@@ -196,6 +199,13 @@ def hydrate_asset_from_row(asset: dict, by_fp: dict[str, dict]) -> None:
|
||||
rid = row.get("id")
|
||||
if rid is not None:
|
||||
asset["id"] = int(rid)
|
||||
for key in ("job_id", "source_node_id", "source_node_type"):
|
||||
value = str(row.get(key) or "").strip()
|
||||
if value:
|
||||
asset[key] = value
|
||||
file_info = asset.get("file_info")
|
||||
if isinstance(file_info, dict):
|
||||
file_info[key] = value
|
||||
asset["rating"] = int(row.get("rating") or 0)
|
||||
asset["tags"] = coerce_browser_tags(row.get("tags"))
|
||||
asset["has_workflow"] = row.get("has_workflow")
|
||||
|
||||
@@ -60,11 +60,17 @@ def test_collect_hydration_paths_skips_folder_assets() -> None:
|
||||
|
||||
|
||||
def test_apply_hydration_rows_sets_id_rating_tags() -> None:
|
||||
assets = [{"filepath": "/a/x.png", "kind": "image"}, {"filepath": "/a/folder", "kind": "folder"}]
|
||||
assets: list[dict] = [
|
||||
{"filepath": "/a/x.png", "kind": "image", "file_info": {}},
|
||||
{"filepath": "/a/folder", "kind": "folder"},
|
||||
]
|
||||
rows = [
|
||||
{
|
||||
"id": "7",
|
||||
"filepath": "/a/x.png",
|
||||
"job_id": "prompt-1",
|
||||
"source_node_id": "7",
|
||||
"source_node_type": "SaveImage",
|
||||
"rating": 5,
|
||||
"tags": '["tag1"]',
|
||||
"positive_prompt": "studio portrait",
|
||||
@@ -77,6 +83,12 @@ def test_apply_hydration_rows_sets_id_rating_tags() -> None:
|
||||
assert assets[0]["tags"] == ["tag1"]
|
||||
assert assets[0]["positive_prompt"] == "studio portrait"
|
||||
assert assets[0]["generation_time_ms"] == 8421
|
||||
assert assets[0]["job_id"] == "prompt-1"
|
||||
assert assets[0]["source_node_id"] == "7"
|
||||
assert assets[0]["source_node_type"] == "SaveImage"
|
||||
assert assets[0]["file_info"]["job_id"] == "prompt-1"
|
||||
assert assets[0]["file_info"]["source_node_id"] == "7"
|
||||
assert assets[0]["file_info"]["source_node_type"] == "SaveImage"
|
||||
assert "id" not in assets[1]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user