feat: version character visual identity
This commit is contained in:
parent
6c75acbf72
commit
94f09c110d
@ -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.
|
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
|
- **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.
|
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
|
- **Security Regression Coverage.** Added a Python test gate for character metadata validation, private-download URL
|
||||||
rejection and oversized JSON request rejection.
|
rejection and oversized JSON request rejection.
|
||||||
- **Navigation Reliability.** Rehearser phase requests now survive the feature-module initialization boundary.
|
- **Navigation Reliability.** Rehearser phase requests now survive the feature-module initialization boundary.
|
||||||
|
|||||||
4
TODO.md
4
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
|
- [ ] **M — Fix the Schwester Larissa unclosed-guillemet split.** Add a regression test
|
||||||
for the epigraph and verify the parser does not split mid-sentence.
|
for the epigraph and verify the parser does not split mid-sentence.
|
||||||
- [ ] **M — Make profile portraits and character sheets use one canonical identity.**
|
- [ ] **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
|
Shared structured prompts now version consistent pairs as v3 and flag older pairs; add a field fingerprint so
|
||||||
detect stale or inconsistent images.
|
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
|
- [ ] **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.
|
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
|
- [ ] **M — Improve large-table performance.** Virtualize/paginate character and voice
|
||||||
|
|||||||
@ -62,6 +62,7 @@ _consistent_image_jobs: dict[str, dict] = {}
|
|||||||
_consistent_image_jobs_lock = threading.Lock()
|
_consistent_image_jobs_lock = threading.Lock()
|
||||||
_consistent_image_idempotency: dict[str, str] = {}
|
_consistent_image_idempotency: dict[str, str] = {}
|
||||||
_consistent_image_slots = threading.BoundedSemaphore(2)
|
_consistent_image_slots = threading.BoundedSemaphore(2)
|
||||||
|
_CHARACTER_VISUAL_PROMPT_VERSION = "v3"
|
||||||
|
|
||||||
|
|
||||||
def _cij_new(job_id: str, name: str) -> None:
|
def _cij_new(job_id: str, name: str) -> None:
|
||||||
@ -3122,6 +3123,9 @@ async def character_generate_consistent_images(request: Request):
|
|||||||
generation_key,
|
generation_key,
|
||||||
progress=lambda elapsed: _cij_set(job_id),
|
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)
|
_cij_set(job_id, stage="done", detail="Done", done=True, result=result)
|
||||||
except HTTPException as exc:
|
except HTTPException as exc:
|
||||||
_cij_set(job_id, stage="error", detail="Failed", done=True, error=str(exc.detail))
|
_cij_set(job_id, stage="error", detail="Failed", done=True, error=str(exc.detail))
|
||||||
|
|||||||
@ -1230,6 +1230,9 @@ async function _charDetailPage(rec, allChars, opts) {
|
|||||||
+ '<span class="mdi mdi-creation"></span> ' + (sh.concept_art_image ? 'Neu generieren' : 'Generieren')
|
+ '<span class="mdi mdi-creation"></span> ' + (sh.concept_art_image ? 'Neu generieren' : 'Generieren')
|
||||||
+ '</button>'
|
+ '</button>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
|
+ (rec.image && sh.concept_art_image && sh.consistent_prompt_version !== 'v3'
|
||||||
|
? '<div class="note" style="color:var(--warning,#d97706);margin-top:6px"><span class="mdi mdi-alert-outline"></span> Dieses Bildpaar stammt aus einer älteren Prompt-Version und sollte für konsistente Identität neu erzeugt werden.</div>'
|
||||||
|
: '')
|
||||||
+ (sh.concept_art_image
|
+ (sh.concept_art_image
|
||||||
? '<div class="lcd-conceptart-img" title="Konzeptbild ansehen"><img loading="lazy" decoding="async" src="' + sh.concept_art_image + '" alt="Concept art — ' + escHtml(rec.name) + '"></div>'
|
? '<div class="lcd-conceptart-img" title="Konzeptbild ansehen"><img loading="lazy" decoding="async" src="' + sh.concept_art_image + '" alt="Concept art — ' + escHtml(rec.name) + '"></div>'
|
||||||
: '<div class="lcd-conceptart-empty' + (_libStr(sh.concept_art_prompt).trim() ? '' : ' is-noprompt') + '">'
|
: '<div class="lcd-conceptart-empty' + (_libStr(sh.concept_art_prompt).trim() ? '' : ' is-noprompt') + '">'
|
||||||
@ -3391,11 +3394,11 @@ async function _charAutoGenerateConsistentImages(rec, visualStyle) {
|
|||||||
const current = (typeof clGet === 'function') ? await clGet(rec.id).catch(function () { return null; }) : null;
|
const current = (typeof clGet === 'function') ? await clGet(rec.id).catch(function () { return null; }) : null;
|
||||||
const target = current || rec;
|
const target = current || rec;
|
||||||
target.image = d.profile;
|
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();
|
target.updated = new Date();
|
||||||
if (typeof clPut === 'function') await clPut(target);
|
if (typeof clPut === 'function') await clPut(target);
|
||||||
rec.image = d.profile;
|
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) {
|
document.querySelectorAll('.lib-char-avatar[data-char-id="' + CSS.escape(rec.id) + '"]').forEach(function (av) {
|
||||||
av.innerHTML = '<img src="' + d.profile + '" alt="' + escHtml(rec.name) + '">';
|
av.innerHTML = '<img src="' + d.profile + '" alt="' + escHtml(rec.name) + '">';
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user