fix(pairs): honor recent sorting and conservative scan outcomes (#193)
This commit is contained in:
+9
-2
@@ -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
@@ -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
|
||||
):
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user