test: lock visual identity prompts
This commit is contained in:
parent
8181c491c0
commit
b6f4a9518b
@ -8,6 +8,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- **Visual identity regression coverage.** Portrait and concept-sheet prompt tests now verify shared identity anchors;
|
||||
completed pairs centralize prompt-version, identity-fingerprint, and asset-digest metadata.
|
||||
- **Frontend namespace migration completed.** The remaining utility, library, cast-compatibility, i18n, and settings/log
|
||||
surfaces now keep implementation state private while preserving explicit legacy compatibility exports.
|
||||
- **Conversation/log namespace migration.** Settings log state and About rendering helpers now use a private scope with
|
||||
|
||||
20
TODO.md
20
TODO.md
@ -261,6 +261,26 @@ architecture change: the larger Audiobook/Rehearser/style split.
|
||||
|
||||
## P1 — Speaker attribution: measured state and next steps (paused 2026-09-02)
|
||||
|
||||
### Ordered follow-up queue
|
||||
|
||||
These follow-ups are ordered by dependency and expected benefit. Estimates assume one developer familiar with this
|
||||
codebase; benefit is relative user/reliability impact.
|
||||
|
||||
- [x] **P1 / M — High benefit — Identity-anchor regression tests.** Add automated coverage proving avatar and concept-sheet
|
||||
requests share canonical identity anchors and persist the prompt version used for each generated asset.
|
||||
- [ ] **P1 / S–M — Medium benefit — Preserve dialogue-only merging.** Keep the dialogue-only merge for recast/verify
|
||||
outputs where adjacent pairs still occur, with regression coverage for the non-dialogue boundaries.
|
||||
- [ ] **P1 / L–XL — Very high benefit — Rework segmentation from Run 3.** Inspect the earlier dialogue split/fusion
|
||||
stages and correct the dominant unmatched-segment class using the measured Run 3 artifacts.
|
||||
- [ ] **P1 / M — High benefit — Re-run Book 1.** Re-measure the hand-corrected Book 1 reference after segmentation
|
||||
changes; do not treat a short or incomplete run as a valid result.
|
||||
- [ ] **P1 / L–XL — Very high benefit — Run Book 2 generalization.** Execute the backed-up Book 2 reference regression
|
||||
to measure whether segmentation and attribution rules generalize beyond Book 1.
|
||||
- [ ] **P1 / L — Very high benefit — Unify visual identity.** Ensure portrait and concept-sheet generation use one
|
||||
canonical identity description, stored prompt version, and stale-asset review path.
|
||||
- [ ] **P2 / M–L — Medium–high benefit — Enrich character-form UX.** Add richer inline form editing and per-form
|
||||
bulk-generation controls while preserving one character ID and one voice.
|
||||
|
||||
Three full regression runs of *Der Sturm* against the hand-corrected reference.
|
||||
Everything below is measured, not estimated. Artefacts live in
|
||||
`config/regression_snapshots/der_sturm/` (`*_manual.json` are the human
|
||||
|
||||
@ -87,6 +87,7 @@ Done incrementally as global-scope ordered files (no ES-module change needed)
|
||||
- ✅ Conversation settings/log state and About helpers use a private scope with compatibility exports and a namespace API.
|
||||
- ✅ All remaining application frontend modules are either privately scoped or intentionally stateless bootstrap/cache IIFEs;
|
||||
legacy globals are explicit compatibility exports and new integrations use `window.TTVC` namespaces.
|
||||
- ✅ Visual pair generation centralizes prompt-version, identity-fingerprint, and asset-digest metadata with regression coverage for shared anchors.
|
||||
- ✅ `static/css/resource-diagnostics.css` — isolated Performance/resource diagnostic panel styles.
|
||||
- ✅ `static/css/background-jobs.css` — isolated background-job history and retry-panel styles.
|
||||
- ✅ `static/css/voice-design.css` — isolated Voice Design form, samples, and preview-match styles.
|
||||
|
||||
@ -159,6 +159,17 @@ def _character_identity_fingerprint(sheet: dict) -> str:
|
||||
return format(value, "x")
|
||||
|
||||
|
||||
def _consistent_image_result_metadata(sheet: dict, result: dict | None) -> dict:
|
||||
"""Attach the identity contract to every completed visual pair."""
|
||||
payload = dict(result or {})
|
||||
payload["prompt_version"] = _CHARACTER_VISUAL_PROMPT_VERSION
|
||||
payload["identity_consistent"] = True
|
||||
payload["identity_fingerprint"] = _character_identity_fingerprint(sheet)
|
||||
payload["profile_asset_digest"] = _data_image_digest(payload.get("profile"))
|
||||
payload["reference_asset_digest"] = _data_image_digest(payload.get("reference"))
|
||||
return payload
|
||||
|
||||
|
||||
def _character_form_sheet(sheet: dict) -> dict:
|
||||
"""Overlay the selected appearance form without changing the character ID or voice."""
|
||||
if not isinstance(sheet, dict):
|
||||
@ -3478,12 +3489,7 @@ async def character_generate_consistent_images(request: Request):
|
||||
generation_key,
|
||||
progress=lambda elapsed: (_cij_check_cancelled(job_id), _cij_set(job_id)),
|
||||
)
|
||||
result = dict(result or {})
|
||||
result["prompt_version"] = _CHARACTER_VISUAL_PROMPT_VERSION
|
||||
result["identity_consistent"] = True
|
||||
result["identity_fingerprint"] = _character_identity_fingerprint(sheet)
|
||||
result["profile_asset_digest"] = _data_image_digest(result.get("profile"))
|
||||
result["reference_asset_digest"] = _data_image_digest(result.get("reference"))
|
||||
result = _consistent_image_result_metadata(sheet, result)
|
||||
result = _persist_consistent_image_artifacts(job_id, result)
|
||||
_cij_set(job_id, stage="done", detail="Done", done=True, result=result)
|
||||
except HTTPException as exc:
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="theme-color" content="#2563EB">
|
||||
<meta name="app-version" content="1.24.98">
|
||||
<meta name="app-version" content="1.24.99">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/static/icon.svg">
|
||||
@ -27,12 +27,12 @@
|
||||
|
||||
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
|
||||
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.24.98">
|
||||
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.24.98">
|
||||
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.24.98">
|
||||
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.24.98">
|
||||
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.24.98">
|
||||
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.24.98">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.24.99">
|
||||
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.24.99">
|
||||
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.24.99">
|
||||
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.24.99">
|
||||
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.24.99">
|
||||
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.24.99">
|
||||
|
||||
|
||||
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
||||
@ -397,7 +397,7 @@ window.toggleNavTree = function(treeId, chevronId) {
|
||||
</script>
|
||||
|
||||
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
|
||||
<script src="/static/loader.js?v=1.24.98"></script>
|
||||
<script src="/static/loader.js?v=1.24.99"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -13,6 +13,10 @@ from routes.conversation import (
|
||||
_attribution_parse,
|
||||
_character_form_sheet,
|
||||
_character_identity_fingerprint,
|
||||
_build_character_portrait_prompt,
|
||||
_build_character_sheet_prompt,
|
||||
_consistent_image_result_metadata,
|
||||
_CHARACTER_VISUAL_PROMPT_VERSION,
|
||||
_cij_check_cancelled,
|
||||
_cij_new,
|
||||
_consistent_image_cancel_events,
|
||||
@ -30,6 +34,29 @@ from routes.conversation import (
|
||||
|
||||
|
||||
class AttributionParserTests(unittest.TestCase):
|
||||
def test_visual_prompts_share_canonical_identity_anchors(self):
|
||||
sheet = {
|
||||
"gender": "female", "age_estimate": "late thirties", "race_species": "human",
|
||||
"physical": "olive skin, angular face, shoulder-length copper hair, green eyes",
|
||||
"clothing": "faded blue wool coat", "profession": "cartographer", "marks": "crescent scar on left cheek",
|
||||
}
|
||||
portrait = _build_character_portrait_prompt("Mara Venn", "The Survey", sheet, {"era": "early Renaissance"})
|
||||
reference = _build_character_sheet_prompt("Mara Venn", "The Survey", sheet, {"era": "early Renaissance"})
|
||||
for anchor in ("female", "late thirties", "human", "olive skin", "copper hair", "green eyes", "blue wool coat", "cartographer"):
|
||||
self.assertIn(anchor, portrait)
|
||||
self.assertIn(anchor, reference)
|
||||
|
||||
def test_visual_pair_metadata_persists_shared_prompt_version_and_identity(self):
|
||||
sheet = {"name": "Mara Venn", "gender": "female", "physical": "copper hair and green eyes"}
|
||||
profile = "data:image/png;base64,AA=="
|
||||
reference = "data:image/png;base64,AQ=="
|
||||
result = _consistent_image_result_metadata(sheet, {"profile": profile, "reference": reference})
|
||||
self.assertEqual(result["prompt_version"], _CHARACTER_VISUAL_PROMPT_VERSION)
|
||||
self.assertEqual(result["identity_fingerprint"], _character_identity_fingerprint(sheet))
|
||||
self.assertTrue(result["identity_consistent"])
|
||||
self.assertTrue(result["profile_asset_digest"])
|
||||
self.assertTrue(result["reference_asset_digest"])
|
||||
|
||||
def test_consistent_image_persistence_keeps_live_payload_and_adds_artifact_refs(self):
|
||||
from core.database import _BACKGROUND_ARTIFACT_DIR
|
||||
job_id = "live-image-artifact-test"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user