feat: Add status messaging and override management functionality with improved UI elements

This commit is contained in:
JB
2025-10-13 06:57:09 -07:00
parent ded405ff70
commit 3e54007baa
3 changed files with 196 additions and 5 deletions
+69
View File
@@ -2151,6 +2151,10 @@ def entities_page() -> ResponseReturnValue:
raw_language = (request.args.get("lang") or settings.get("language") or "a").strip().lower() raw_language = (request.args.get("lang") or settings.get("language") or "a").strip().lower()
language = raw_language if raw_language in languages_map else "a" language = raw_language if raw_language in languages_map else "a"
status_code = (request.args.get("status") or "").strip().lower()
status_token = (request.args.get("token") or "").strip()
status_error = (request.args.get("error") or "").strip()
query = (request.args.get("q") or "").strip() query = (request.args.get("q") or "").strip()
voice_filter = (request.args.get("voice") or "all").strip().lower() voice_filter = (request.args.get("voice") or "all").strip().lower()
pronunciation_filter = (request.args.get("pronunciation") or "all").strip().lower() pronunciation_filter = (request.args.get("pronunciation") or "all").strip().lower()
@@ -2207,6 +2211,12 @@ def entities_page() -> ResponseReturnValue:
{"value": "without-pronunciation", "label": "No pronunciation"}, {"value": "without-pronunciation", "label": "No pronunciation"},
] ]
status_message = ""
if status_code == "saved":
status_message = f"Updated override for {status_token or 'override'}."
elif status_code == "deleted":
status_message = f"Deleted override for {status_token or 'override'}."
context = { context = {
"options": options, "options": options,
"language": language, "language": language,
@@ -2220,10 +2230,69 @@ def entities_page() -> ResponseReturnValue:
"limit": limit_value, "limit": limit_value,
"overrides": display_rows, "overrides": display_rows,
"stats": stats, "stats": stats,
"status_message": status_message,
"status_error": status_error,
} }
return render_template("entities.html", **context) return render_template("entities.html", **context)
@web_bp.post("/entities/override")
def entities_override_update() -> ResponseReturnValue:
options = _template_options()
languages_map = options.get("languages", {})
raw_language = (request.form.get("lang") or "").strip().lower()
language = raw_language if raw_language in languages_map else "a"
token_value = (request.form.get("token") or "").strip()
action = (request.form.get("action") or "save").strip().lower()
pronunciation_value = (request.form.get("pronunciation") or "").strip()
voice_value = (request.form.get("voice") or "").strip()
notes_value = (request.form.get("notes") or "").strip()
redirect_params: Dict[str, Any] = {"lang": language}
state_mappings = (
("state_voice", "voice"),
("state_pronunciation", "pronunciation"),
("state_limit", "limit"),
("state_query", "q"),
)
for form_key, query_key in state_mappings:
value = (request.form.get(form_key) or "").strip()
if value:
redirect_params[query_key] = value
if not token_value:
redirect_params["status"] = "error"
redirect_params["error"] = "Missing override token."
return redirect(url_for("web.entities_page", **redirect_params))
status_code = "saved"
try:
if action == "delete":
delete_pronunciation_override(language=language, token=token_value)
status_code = "deleted"
else:
save_pronunciation_override(
language=language,
token=token_value,
pronunciation=pronunciation_value or None,
voice=voice_value or None,
notes=notes_value or None,
context=None,
)
status_code = "saved"
except Exception as exc: # pragma: no cover - defensive logging
current_app.logger.exception("Failed to %s override for token %s", action, token_value)
redirect_params["status"] = "error"
redirect_params["error"] = "Failed to update override."
return redirect(url_for("web.entities_page", **redirect_params))
redirect_params["status"] = status_code
redirect_params["token"] = token_value
return redirect(url_for("web.entities_page", **redirect_params))
@web_bp.route("/speakers", methods=["GET", "POST"]) @web_bp.route("/speakers", methods=["GET", "POST"])
def speaker_configs_page() -> ResponseReturnValue: def speaker_configs_page() -> ResponseReturnValue:
options = _template_options() options = _template_options()
+65
View File
@@ -25,6 +25,27 @@ body {
flex-direction: column; flex-direction: column;
} }
.notice {
margin: 0 0 1.5rem;
padding: 0.9rem 1.1rem;
border-radius: 16px;
border: 1px solid rgba(56, 189, 248, 0.32);
background: rgba(14, 165, 233, 0.18);
color: var(--text);
font-size: 0.95rem;
box-shadow: 0 12px 30px rgba(8, 15, 32, 0.22);
}
.notice--success {
border-color: rgba(52, 211, 153, 0.42);
background: rgba(34, 197, 94, 0.18);
}
.notice--error {
border-color: rgba(248, 113, 113, 0.48);
background: rgba(248, 113, 113, 0.18);
}
.top-bar { .top-bar {
backdrop-filter: blur(20px); backdrop-filter: blur(20px);
background: linear-gradient(90deg, rgba(15, 23, 42, 0.9), rgba(30, 41, 59, 0.7)); background: linear-gradient(90deg, rgba(15, 23, 42, 0.9), rgba(30, 41, 59, 0.7));
@@ -1194,6 +1215,50 @@ button.step-indicator__item:focus-visible {
background: rgba(56, 189, 248, 0.08); background: rgba(56, 189, 248, 0.08);
} }
.overrides-table__token {
font-weight: 600;
letter-spacing: 0.02em;
}
.overrides-table__input,
.overrides-table__select {
width: 100%;
box-sizing: border-box;
border-radius: 12px;
border: 1px solid rgba(148, 163, 184, 0.2);
background: rgba(15, 23, 42, 0.45);
color: var(--text);
padding: 0.6rem 0.75rem;
font-size: 0.95rem;
transition: border 0.2s ease, box-shadow 0.2s ease;
}
.overrides-table__input:focus,
.overrides-table__select:focus {
outline: none;
border-color: rgba(56, 189, 248, 0.55);
box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.18);
}
.overrides-table__select {
appearance: none;
}
.overrides-table__actions-heading {
text-align: right;
}
.overrides-table__actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 0.5rem;
}
.overrides-table__form {
margin: 0;
}
.progress-bar { .progress-bar {
width: 100%; width: 100%;
height: 9px; height: 9px;
+62 -5
View File
@@ -59,9 +59,14 @@
{{ stats.with_pronunciation }} with pronunciations · {{ stats.with_voice }} with assigned voices {{ stats.with_pronunciation }} with pronunciations · {{ stats.with_voice }} with assigned voices
</p> </p>
</header> </header>
{% if status_message or status_error %}
<div class="notice {% if status_error %}notice--error{% else %}notice--success{% endif %}">
{{ status_error or status_message }}
</div>
{% endif %}
{% if overrides %} {% if overrides %}
<div class="table-wrapper"> <div class="table-wrapper">
<table class="data-table"> <table class="jobs-table overrides-table">
<thead> <thead>
<tr> <tr>
<th scope="col">Token</th> <th scope="col">Token</th>
@@ -70,17 +75,69 @@
<th scope="col">Notes</th> <th scope="col">Notes</th>
<th scope="col">Usage</th> <th scope="col">Usage</th>
<th scope="col">Last updated</th> <th scope="col">Last updated</th>
<th scope="col" class="overrides-table__actions-heading">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for override in overrides %} {% for override in overrides %}
{% set form_id = "override-form-" ~ loop.index %}
<tr> <tr>
<td data-label="Token">{{ override.token }}</td> <td data-label="Token"><span class="overrides-table__token">{{ override.token }}</span></td>
<td data-label="Pronunciation">{{ override.pronunciation or "—" }}</td> <td data-label="Pronunciation">
<td data-label="Voice">{{ override.voice or "—" }}</td> <input
<td data-label="Notes">{{ override.notes or "" }}</td> class="overrides-table__input"
type="text"
name="pronunciation"
form="{{ form_id }}"
value="{{ override.pronunciation or '' }}"
placeholder="Add pronunciation"
>
</td>
<td data-label="Voice">
<select
class="overrides-table__select"
name="voice"
form="{{ form_id }}"
>
<option value="" {% if not override.voice %}selected{% endif %}>No override</option>
{% for voice in options.voice_catalog %}
<option value="{{ voice.id }}" {% if override.voice == voice.id %}selected{% endif %}>
{{ voice.display_name }} - {{ voice.language_label }} - {{ voice.gender }}
</option>
{% endfor %}
</select>
</td>
<td data-label="Notes">
<input
class="overrides-table__input"
type="text"
name="notes"
form="{{ form_id }}"
value="{{ override.notes or '' }}"
placeholder="Add notes"
>
</td>
<td data-label="Usage">{{ override.usage_count }}</td> <td data-label="Usage">{{ override.usage_count }}</td>
<td data-label="Last updated">{{ override.updated_at_label }}</td> <td data-label="Last updated">{{ override.updated_at_label }}</td>
<td data-label="Actions">
<form
id="{{ form_id }}"
class="overrides-table__form"
method="post"
action="{{ url_for('web.entities_override_update') }}"
>
<input type="hidden" name="lang" value="{{ language }}">
<input type="hidden" name="token" value="{{ override.token }}">
<input type="hidden" name="state_voice" value="{{ voice_filter }}">
<input type="hidden" name="state_pronunciation" value="{{ pronunciation_filter }}">
<input type="hidden" name="state_limit" value="{{ limit }}">
<input type="hidden" name="state_query" value="{{ query }}">
<div class="overrides-table__actions">
<button type="submit" class="button button--small" name="action" value="save">Save</button>
<button type="submit" class="button button--ghost button--small button--danger" name="action" value="delete">Delete</button>
</div>
</form>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>