mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
feat: Add status messaging and override management functionality with improved UI elements
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user