* chore: start 3.2.4 * feat: track prompt usage (run_count, last_used_at) with server-side sorting - Migration adds last_used_at and run_count; existing prompts are estimated from linked images (count, newest image time, else created_at). The backfill only touches NULL rows, so it self-heals rows written after a downgrade or an interrupted upgrade. - Every node run records a use, including re-runs of an existing prompt; a tracking failure is logged and never blocks generation. - search_prompts/get_recent_prompts accept a whitelisted sort key (last_used, created, run_count, rating, text); default stays newest first. Refs #90 * feat: sort param on /recent and /search; microsecond last_used_at - /prompt_manager/recent and /search accept ?sort= (whitelisted in the DB layer; unknown values fall back to newest first). - last_used_at is written with microseconds from Python so a re-run in the same millisecond as another save still sorts first; ordering stays correct against millisecond values from the backfill. Refs #90 * feat: server-side sort in admin UI with Recently Used / Most Used and run badges - New web/js/prompt-list-sort.js owns the sort options, validation and request URLs; admin.js fills the dropdown from it and re-fetches from the server on change, so every sort spans all pages (the old client-side sort only reordered the current page). - Default sort is Recently Used; prompt cards show a run-count badge with the last-used time on hover. - The node's Recent button now lists recently used prompts. - Contract test keeps the UI sort keys and the node's key in sync with the server whitelist. Refs #90 * fix: link images and count runs by the positive prompt, not node execution With a negative-prompt PromptManager node in the workflow, every executed PromptManager node pushed onto the image-linking queue, so images could be linked to the negative prompt or to a previous run. ComfyUI also skips unchanged nodes, so re-runs of the same prompt were never counted. - utils/prompt_graph.py finds the PromptManager nodes feeding a sampler's positive input (KSampler, CFG/Basic guiders, ControlNet and combine pass-throughs, PromptManagerText into encoders), falling back to all PromptManager nodes when none can be traced. - Runs are counted by a ComfyUI on-prompt hook when a workflow is queued; prompts not yet saved are counted when the node first saves them. - Nodes receive the queued graph (hidden PROMPT/UNIQUE_ID). Only the run's positive prompt becomes the current prompt; only batch items with linked text are pushed onto the FIFO queue. - The image monitor links from the image's own metadata first, using the positive node, and falls back to the queue for batch items. Refs #90 * fix: address review — count cached runs of linked text, keep repeated pending runs - Text from known pure string nodes (core PrimitiveString/StringConcatenate, WAS Text Concatenate/Multiline, pysssss ShowText) is resolved at queue time, so cached re-runs of such prompts are counted and their images link from metadata. Unknown nodes are never guessed; the node compares its real text with the queue-time resolution and falls back to counting and queueing itself on any mismatch. - A new prompt queued several times before its first run keeps every pending run; the first save records all of them. - Registration flag is a threading.Event; consistent unittest imports; logging instead of print in the init fallback. * refactor: unquote PendingFirstUse annotation so Tuple usage is visible
65 lines
2.5 KiB
JavaScript
65 lines
2.5 KiB
JavaScript
/**
|
|
* Prompt list sorting for the admin dashboard (3.2.4, #90.1).
|
|
*
|
|
* Sorting happens on the server so it spans every page; this module only owns the
|
|
* option list, validation and request URLs. The option values must match
|
|
* SORT_ORDERS in database/operations.py (enforced by tests/test_sort_contract.py).
|
|
*
|
|
* Loadable in the browser (window.PromptListSort) and in Node for tests.
|
|
*/
|
|
(function (root) {
|
|
"use strict";
|
|
|
|
const SORT_OPTIONS = Object.freeze(
|
|
[
|
|
{ value: "last_used_desc", label: "Recently Used" },
|
|
{ value: "created_desc", label: "Newest First" },
|
|
{ value: "created_asc", label: "Oldest First" },
|
|
{ value: "run_count_desc", label: "Most Used" },
|
|
{ value: "rating_desc", label: "Highest Rated" },
|
|
{ value: "rating_asc", label: "Lowest Rated" },
|
|
{ value: "text_asc", label: "A-Z" },
|
|
{ value: "text_desc", label: "Z-A" },
|
|
].map((option) => Object.freeze(option)),
|
|
);
|
|
|
|
const DEFAULT_SORT = "last_used_desc";
|
|
const DEFAULT_LIMIT = 50;
|
|
const KNOWN = new Set(SORT_OPTIONS.map((o) => o.value));
|
|
|
|
/** A known sort key, or DEFAULT_SORT for anything else. */
|
|
function normalizeSort(value) {
|
|
return typeof value === "string" && KNOWN.has(value) ? value : DEFAULT_SORT;
|
|
}
|
|
|
|
/** URL for one page of /prompt_manager/recent. */
|
|
function buildRecentUrl({ page = 1, limit = DEFAULT_LIMIT, sort } = {}) {
|
|
const params = new URLSearchParams({
|
|
limit: String(limit),
|
|
offset: String((page - 1) * limit),
|
|
page: String(page),
|
|
sort: normalizeSort(sort),
|
|
});
|
|
return `/prompt_manager/recent?${params}`;
|
|
}
|
|
|
|
/** Copy of `params` with a validated sort key; the input is not modified. */
|
|
function withSort(params, sort) {
|
|
const copy = new URLSearchParams(params);
|
|
copy.set("sort", normalizeSort(sort));
|
|
return copy;
|
|
}
|
|
|
|
/** Badge text for a prompt's run count; empty when the count is unknown. */
|
|
function formatRunCount(count) {
|
|
if (!Number.isInteger(count) || count < 0) return "";
|
|
if (count === 0) return "Never run";
|
|
if (count === 1) return "1 run";
|
|
return `${count.toLocaleString("en-US")} runs`;
|
|
}
|
|
|
|
const api = { SORT_OPTIONS, DEFAULT_SORT, normalizeSort, buildRecentUrl, withSort, formatRunCount };
|
|
if (typeof module !== "undefined" && module.exports) module.exports = api;
|
|
else root.PromptListSort = api;
|
|
})(typeof window !== "undefined" ? window : globalThis);
|