fix: flag inconsistent current image pairs

This commit is contained in:
mARTin-B78 2026-09-04 08:39:01 +02:00
parent cb653d15d0
commit 0b51a76c21
4 changed files with 35 additions and 3 deletions

View File

@ -50,7 +50,9 @@ the first block before investing in architecture or polish.
split before mixed German guillemet closes (`»…‹` / `«…›`), with an epigraph regression test.
- [ ] **M — Make profile portraits and character sheets use one canonical identity.**
Shared structured prompts now version consistent pairs as v3 and fingerprint canonical appearance fields; add
cross-record validation so mismatched profile/reference files are detected even when both are current-version.
cross-record validation so mismatched profile/reference files are detected even when both are current-version. The
UI now flags missing, stale, changed, or explicitly inconsistent pair metadata; visual similarity itself still needs
an image-level review workflow.
- [x] **M — Add bounded duplicate-generation protection.** Consistent portrait/reference, voice-design, and casting
jobs now deduplicate repeated requests with bounded, expiring `Idempotency-Key` contracts.
- [ ] **M — Improve large-table performance.** Character productions and the flat voice library now paginate at 50

File diff suppressed because one or more lines are too long

View File

@ -1238,6 +1238,12 @@ async function _charDetailPage(rec, allChars, opts) {
// in the Generation Prompts section further down, or squeezed into the
// header at thumbnail size (confirmed live: 110px was too small to read
// any actual detail in a multi-pose design sheet).
const visualPairNeedsReview = !!(rec.image && sh.concept_art_image && (
sh.consistent_prompt_version !== 'v3'
|| !sh.consistent_identity_fingerprint
|| sh.consistent_identity_fingerprint !== identityFingerprintShort
|| sh.identity_consistent === false
));
const conceptArtHtml = '<div class="lcd-section-full lcd-conceptart">'
+ '<div class="lcd-conceptart-headrow">'
+ '<div class="lcd-section-label"><span class="mdi mdi-image-frame"></span> Konzeptbild</div>'
@ -1252,7 +1258,7 @@ async function _charDetailPage(rec, allChars, opts) {
+ '<span class="mdi mdi-creation"></span> ' + (sh.concept_art_image ? 'Neu generieren' : 'Generieren')
+ '</button>'
+ '</div>'
+ (rec.image && sh.concept_art_image && (sh.consistent_prompt_version !== 'v3' || (sh.consistent_identity_fingerprint && sh.consistent_identity_fingerprint !== identityFingerprintShort))
+ (visualPairNeedsReview
? '<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

View File

@ -89,3 +89,27 @@ test('consistent image generation can be cancelled from its progress panel', asy
await expect.poll(() => cancelRequests).toBe(1);
await expect(page.locator('#cij-progress-panel')).toHaveClass(/cij-progress-err/, { timeout: 5_000 });
});
test('current-version image pairs marked inconsistent show a review warning', async ({ page }) => {
await page.route('**/api/characters*', route => route.fulfill({ json: {
characters: [{
id: 'c-mismatch', book: 'Smoke Book', name: 'Mismatch', image: '/profile.png',
sheet: {
image_prompt: 'portrait', concept_art_prompt: 'reference', concept_art_image: '/reference.png',
consistent_prompt_version: 'v3', consistent_identity_fingerprint: 'same-current-version',
identity_consistent: false,
},
}],
} }));
await page.route('**/api/voices*', route => route.fulfill({ json: [] }));
await page.route('**/api/settings*', route => route.fulfill({ json: {} }));
await page.route('**/api/book-profile*', route => route.fulfill({ json: { profile: {} } }));
await page.goto('/', { waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => typeof window.libraryRenderCharacters === 'function', null, { timeout: 15_000 });
await page.waitForSelector('#lib-chars-list', { state: 'attached', timeout: 15_000 });
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
await page.locator('#nav-library-head').evaluate(el => el.click());
await page.locator('.lib-tab[data-library-view="characters"]').evaluate(el => el.click());
await page.locator('.lib-char-card[data-char-id="c-mismatch"]').click();
await expect(page.locator('.lcd-conceptart .note')).toContainText('konsistente Identität', { timeout: 15_000 });
});