{% set designations = speaker_designations(request) %} {% set _ap = active_project(request) %}
{# The active project is a per-browser cookie. Two browsers could sit on different books and their speaker lists are completely unrelated, which made "51 need checking here, 20 there" look like a bug. Name the book on the page itself, next to the counts it qualifies. #}
Reviewing {{ _ap.name }} โ€” this list is only this book; another browser or tab may be on a different one

Speakers ({{ speakers|length }}) {% set _clear = designations.values()|selectattr('status', 'equalto', 'clear')|list|length %} {% set _needs = designations.values()|selectattr('status', 'equalto', 'needs_review')|list|length %} {% if designations %} {{ _clear }} clear {{ _needs }} need checking {% endif %}

๐Ÿท Name Tokens ๐Ÿšซ Orphaned
{% if _needs %}
โš  {{ _needs }} speaker{{ 's' if _needs != 1 }} need{{ '' if _needs != 1 }} checking โ€” these were not auto-accepted as distinct speakers. For each one:
  1. Hover the โš  needs check badge on the row to see why โ€” e.g. named only by a role, no dialogue attributed, or a likely duplicate.
  2. If the speaker is correct as-is (a real character, name is fine) โ€” press โœ“ Approve on its row. That records your decision and clears the badge immediately; the page does not reload.
  3. If the speaker is wrong โ€” use Edit to correct it, or Merge it into an existing speaker, then re-scan Name Tokens.
Hover a row's badge for the full reason list.
Show the list
{% for _n in designations.values()|sort(attribute='confidence')|selectattr('status', 'equalto', 'needs_review')|list %}
{{ _n.name }} ({{ '%.0f'|format(_n.confidence * 100) }}%)
{% for r in _n.reasons %}โ€ข {{ r }}{% if not loop.last %}; {% endif %}{% endfor %}
{% endfor %}
{% endif %} {% set fallback_count = speakers|selectattr('engine_fallback')|list|length %} {% if not qwen_up %}
โš  Qwen3-TTS server unreachable โ€” {{ qwen_url or 'no qwen URL configured' }}. Voice generation will fall back to Edge-TTS, which loses raspy/texture and may pick an arbitrary voice. Restart the Qwen server first for real Qwen3 voices.
{% elif fallback_count %}
โš  {{ fallback_count }} voice{{ 's' if fallback_count != 1 }} were generated with Edge-TTS fallback โ€” those rows show a red badge. Regenerate them now with a running Qwen3-TTS server to get the intended voices.
{% endif %}
{% for speaker in speakers %} {% set speaker_idx = loop.index0 %} {% include "_speaker_row.html" %} {% endfor %}
Speaker Aliases (get these right first) Demographics Vocal Traits Voice Sample Actions