fix(pairs): honor recent sorting and conservative scan outcomes (#193)

This commit is contained in:
Vito Sansevero
2026-10-17 06:58:22 -07:00
parent e94e5a08cf
commit 930f5c72d0
6 changed files with 76 additions and 9 deletions
+9 -2
View File
@@ -191,8 +191,15 @@ class PairOperations:
include_archived: bool = False,
limit: int = 50,
offset: int = 0,
sort: str = "newest",
) -> Dict[str, Any]:
"""List pairs with all-tag filtering and stable newest-first pagination."""
"""List pairs with all-tag filtering and stable paginated ordering."""
orders = {
"newest": "pp.id DESC",
"last_used_desc": "julianday(pp.last_used_at) DESC, pp.id DESC",
}
if sort not in orders:
raise ValueError("Unknown pair sort order")
if type(limit) is not int or not 1 <= limit <= 500:
raise ValueError("limit must be between 1 and 500")
if type(offset) is not int or offset < 0:
@@ -227,7 +234,7 @@ class PairOperations:
conn,
where,
[*params, limit, offset],
" ORDER BY pp.id DESC LIMIT ? OFFSET ?",
f" ORDER BY {orders[sort]} LIMIT ? OFFSET ?",
)
return {
"pairs": pairs,
+19 -7
View File
@@ -1480,7 +1480,16 @@ class AdminRoutesMixin:
continue
counts["found"] += 1
status = outcome["status"] if isinstance(outcome, dict) else outcome
if status in counts:
if status in (
"added",
"linked",
"enriched",
"unchanged",
"ambiguous",
"unresolved",
"conflict",
"failed",
):
counts[status] += 1
if isinstance(outcome, dict):
counts["pairs_added"] += outcome.get("pairs_added", 0)
@@ -1492,11 +1501,12 @@ class AdminRoutesMixin:
if not metadata:
return None
canonical = dict(metadata)
if isinstance(canonical.get("prompt"), str):
try:
canonical["prompt"] = json.loads(canonical["prompt"])
except (ValueError, TypeError):
pass
for key in ("prompt", "workflow"):
if isinstance(canonical.get(key), str):
try:
canonical[key] = json.loads(canonical[key])
except (ValueError, TypeError):
pass
if not canonical.get("prompt") and not canonical.get("parameters"):
return None
resolution = resolve_prompt_pair(canonical)
@@ -1505,7 +1515,9 @@ class AdminRoutesMixin:
self.db, str(media_file), canonical, resolution
)
return result if detailed else result["status"]
if resolution["state"] in ("ambiguous", "unresolved"):
if resolution["state"] == "ambiguous":
return "ambiguous"
if resolution["state"] == "unresolved":
if canonical.get("prompt") or not isinstance(
canonical.get("parameters"), str
):
+1
View File
@@ -129,6 +129,7 @@ class PairRoutesMixin:
category=request.query.get("category"),
tags=request.query.getall("tag", []),
include_archived=archived == "true",
sort=request.query.get("sort", "newest"),
limit=limit,
offset=offset,
)
+8
View File
@@ -95,3 +95,11 @@ test('completion label reports pair enrichment, unresolved work and failures', (
assert.match(completionLabel(state),/4 need review, 1 failed/);
assert.match(completionLabel({...state,cancelled:true}),/^Scan cancelled!/);
});
test('older servers without pair counts retain a complete readable summary', () => {
const {completionLabel}=require('../../web/js/scan-progress.js');
const state=reduceScanEvent(initialState(),{type:'complete',processed:2,found:1,added:1,linked:0});
assert.match(completionLabel(initialState()),/0 new pairs, 0 pair links; 0 need review, 0 failed/);
assert.match(completionLabel(state),/0 enriched, 0 unchanged/);
assert.equal(fromStatus({running:true,last_event:{type:'unknown'}}).status,'running');
});
+9
View File
@@ -113,3 +113,12 @@ class TestPairAPI(PromptAPITestCase):
"/prompt_manager/bulk/delete", json={"prompt_ids": [prompt_id]}
)
self.assertEqual(response.status, 409)
async def test_recent_pairs_sort_by_use_and_reject_unknown_sort(self):
first = (await (await self._create()).json())["pair"]
await self._create(negative_text="noise")
self.api.db.record_prompt_pair_use(first["id"])
response = await self.client.get("/prompt_manager/pairs?sort=last_used_desc")
self.assertEqual((await response.json())["pairs"][0]["id"], first["id"])
response = await self.client.get("/prompt_manager/pairs?sort=invalid")
self.assertEqual(response.status, 400)
+30
View File
@@ -81,3 +81,33 @@ class TestPairScan(AdminAPITestCase):
)["unchanged"],
1,
)
def test_legacy_found_outcome_is_not_counted_twice(self):
result = self.api._ingest_scan_batch_sync(
[(self.output_dir / "blank.png", {"parameters": "Negative prompt: blur"})]
)
self.assertEqual(result["processed"], 1)
self.assertEqual(result["found"], 1)
def test_ambiguous_a1111_does_not_fall_back_to_legacy_guess(self):
meta = {
"parameters": (
"cat\nNegative prompt: blur\nNegative prompt: noise"
"\nSteps: 20, Sampler: Euler"
)
}
self.assertEqual(
self.api._ingest_scanned_file(self.output_dir / "ambiguous.png", meta),
"ambiguous",
)
self.assertEqual(self.api.db.get_recent_prompts()["prompts"], [])
def test_raw_png_workflow_is_stored_as_object_and_repeat_is_unchanged(self):
path = self.output_dir / "workflow.png"
workflow = {"nodes": [{"id": 1, "type": "PromptManagerPair"}]}
metadata = {"prompt": json.dumps(graph()), "workflow": json.dumps(workflow)}
self.assertEqual(self.api._ingest_scanned_file(path, metadata), "added")
self.assertEqual(
self.api.db.get_image_prompt_info(str(path))["workflow_data"], workflow
)
self.assertEqual(self.api._ingest_scanned_file(path, metadata), "unchanged")