From 94f09c110dbd202e64f38efc1977bdf4b6e03b49 Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Fri, 4 Sep 2026 07:58:16 +0200 Subject: [PATCH] feat: version character visual identity --- CHANGELOG.md | 2 ++ TODO.md | 4 ++-- routes/conversation.py | 4 ++++ static/js/library-characters.js | 7 +++++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c18f01..f909890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi excess requests receive a retryable 429 after a bounded wait instead of creating unbounded worker threads. - **STT Queue Bound.** Transcription and STT benchmark calls now share a single bounded process-local slot, preventing concurrent recognition requests from overcommitting the inference device. +- **Visual Identity Versioning.** Consistent profile/reference results now carry the standardized v3 prompt marker, + and older unmarked image pairs are visibly flagged for regeneration. - **Security Regression Coverage.** Added a Python test gate for character metadata validation, private-download URL rejection and oversized JSON request rejection. - **Navigation Reliability.** Rehearser phase requests now survive the feature-module initialization boundary. diff --git a/TODO.md b/TODO.md index da06367..380f494 100644 --- a/TODO.md +++ b/TODO.md @@ -49,8 +49,8 @@ the first block before investing in architecture or polish. - [ ] **M — Fix the Schwester Larissa unclosed-guillemet split.** Add a regression test for the epigraph and verify the parser does not split mid-sentence. - [ ] **M — Make profile portraits and character sheets use one canonical identity.** - Share age, hair, eyes, face, build, skin tone, marks and era; version the prompt and - detect stale or inconsistent images. + Shared structured prompts now version consistent pairs as v3 and flag older pairs; add a field fingerprint so + changes to age, hair, eyes, face, build, skin tone, marks or era also detect stale images. - [ ] **M — Add bounded duplicate-generation protection.** Consistent portrait/reference jobs now deduplicate repeated requests with `Idempotency-Key`; extend the same bounded contract to voice and casting jobs. - [ ] **M — Improve large-table performance.** Virtualize/paginate character and voice diff --git a/routes/conversation.py b/routes/conversation.py index 5f37df5..a2bbdc3 100644 --- a/routes/conversation.py +++ b/routes/conversation.py @@ -62,6 +62,7 @@ _consistent_image_jobs: dict[str, dict] = {} _consistent_image_jobs_lock = threading.Lock() _consistent_image_idempotency: dict[str, str] = {} _consistent_image_slots = threading.BoundedSemaphore(2) +_CHARACTER_VISUAL_PROMPT_VERSION = "v3" def _cij_new(job_id: str, name: str) -> None: @@ -3122,6 +3123,9 @@ async def character_generate_consistent_images(request: Request): generation_key, progress=lambda elapsed: _cij_set(job_id), ) + result = dict(result or {}) + result["prompt_version"] = _CHARACTER_VISUAL_PROMPT_VERSION + result["identity_consistent"] = True _cij_set(job_id, stage="done", detail="Done", done=True, result=result) except HTTPException as exc: _cij_set(job_id, stage="error", detail="Failed", done=True, error=str(exc.detail)) diff --git a/static/js/library-characters.js b/static/js/library-characters.js index ce61e6c..5a5c56a 100644 --- a/static/js/library-characters.js +++ b/static/js/library-characters.js @@ -1230,6 +1230,9 @@ async function _charDetailPage(rec, allChars, opts) { + ' ' + (sh.concept_art_image ? 'Neu generieren' : 'Generieren') + '' + '' + + (rec.image && sh.concept_art_image && sh.consistent_prompt_version !== 'v3' + ? '
Dieses Bildpaar stammt aus einer älteren Prompt-Version und sollte für konsistente Identität neu erzeugt werden.
' + : '') + (sh.concept_art_image ? '
Concept art — ' + escHtml(rec.name) + '
' : '
' @@ -3391,11 +3394,11 @@ async function _charAutoGenerateConsistentImages(rec, visualStyle) { const current = (typeof clGet === 'function') ? await clGet(rec.id).catch(function () { return null; }) : null; const target = current || rec; target.image = d.profile; - target.sheet = Object.assign({}, target.sheet || {}, { concept_art_image: d.reference }); + target.sheet = Object.assign({}, target.sheet || {}, { concept_art_image: d.reference, consistent_prompt_version: d.prompt_version || 'v3', identity_consistent: d.identity_consistent !== false }); target.updated = new Date(); if (typeof clPut === 'function') await clPut(target); rec.image = d.profile; - rec.sheet = Object.assign({}, sh, { concept_art_image: d.reference }); + rec.sheet = Object.assign({}, sh, { concept_art_image: d.reference, consistent_prompt_version: d.prompt_version || 'v3', identity_consistent: d.identity_consistent !== false }); document.querySelectorAll('.lib-char-avatar[data-char-id="' + CSS.escape(rec.id) + '"]').forEach(function (av) { av.innerHTML = '' + escHtml(rec.name) + ''; });