mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
feat: Add entity pronunciation preview API and enhance job management pages
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
from typing import Any, Dict, Mapping, List, Optional
|
from typing import Any, Dict, Mapping, List, Optional
|
||||||
|
import base64
|
||||||
|
|
||||||
from flask import Blueprint, request, jsonify, send_file
|
from flask import Blueprint, request, jsonify, send_file
|
||||||
from flask.typing import ResponseReturnValue
|
from flask.typing import ResponseReturnValue
|
||||||
@@ -6,13 +7,14 @@ from flask.typing import ResponseReturnValue
|
|||||||
from abogen.web.routes.utils.settings import (
|
from abogen.web.routes.utils.settings import (
|
||||||
load_settings,
|
load_settings,
|
||||||
coerce_float,
|
coerce_float,
|
||||||
|
coerce_bool,
|
||||||
)
|
)
|
||||||
from abogen.voice_profiles import (
|
from abogen.voice_profiles import (
|
||||||
load_profiles,
|
load_profiles,
|
||||||
save_profiles,
|
save_profiles,
|
||||||
delete_profile,
|
delete_profile,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.preview import synthesize_preview
|
from abogen.web.routes.utils.preview import synthesize_preview, generate_preview_audio
|
||||||
from abogen.normalization_settings import (
|
from abogen.normalization_settings import (
|
||||||
build_llm_configuration,
|
build_llm_configuration,
|
||||||
build_apostrophe_config,
|
build_apostrophe_config,
|
||||||
@@ -211,3 +213,37 @@ def api_normalization_preview() -> ResponseReturnValue:
|
|||||||
"text": sample_text,
|
"text": sample_text,
|
||||||
"normalized_text": normalized_text,
|
"normalized_text": normalized_text,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@api_bp.post("/entity-pronunciation/preview")
|
||||||
|
def api_entity_pronunciation_preview() -> ResponseReturnValue:
|
||||||
|
payload = request.get_json(force=True, silent=True) or {}
|
||||||
|
token = payload.get("token", "").strip()
|
||||||
|
pronunciation = payload.get("pronunciation", "").strip()
|
||||||
|
voice = payload.get("voice", "").strip()
|
||||||
|
language = payload.get("language", "a").strip()
|
||||||
|
|
||||||
|
if not token and not pronunciation:
|
||||||
|
return jsonify({"error": "Token or pronunciation required"}), 400
|
||||||
|
|
||||||
|
text_to_speak = pronunciation if pronunciation else token
|
||||||
|
|
||||||
|
if not voice:
|
||||||
|
settings = load_settings()
|
||||||
|
voice = settings.get("default_voice", "af_heart")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Check GPU setting
|
||||||
|
settings = load_settings()
|
||||||
|
use_gpu = coerce_bool(settings.get("use_gpu"), False)
|
||||||
|
|
||||||
|
audio_bytes = generate_preview_audio(
|
||||||
|
text=text_to_speak,
|
||||||
|
voice_spec=voice,
|
||||||
|
language=language,
|
||||||
|
speed=1.0,
|
||||||
|
use_gpu=use_gpu,
|
||||||
|
)
|
||||||
|
audio_base64 = base64.b64encode(audio_bytes).decode("utf-8")
|
||||||
|
return jsonify({"audio_base64": audio_base64})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 400
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from typing import Mapping
|
from typing import Mapping
|
||||||
from flask import Blueprint, request, jsonify, abort
|
from flask import Blueprint, request, jsonify, abort, render_template
|
||||||
from flask.typing import ResponseReturnValue
|
from flask.typing import ResponseReturnValue
|
||||||
|
|
||||||
from abogen.web.routes.utils.service import require_pending_job, get_service
|
from abogen.web.routes.utils.service import require_pending_job, get_service
|
||||||
@@ -10,7 +10,8 @@ from abogen.web.routes.utils.entity import (
|
|||||||
delete_manual_override,
|
delete_manual_override,
|
||||||
search_manual_override_candidates,
|
search_manual_override_candidates,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.settings import coerce_int
|
from abogen.web.routes.utils.settings import coerce_int, load_settings
|
||||||
|
from abogen.web.routes.utils.voice import template_options
|
||||||
|
|
||||||
entities_bp = Blueprint("entities", __name__)
|
entities_bp = Blueprint("entities", __name__)
|
||||||
|
|
||||||
@@ -94,3 +95,14 @@ def search_candidates(pending_id: str) -> ResponseReturnValue:
|
|||||||
|
|
||||||
results = search_manual_override_candidates(pending, query, limit=limit_value)
|
results = search_manual_override_candidates(pending, query, limit=limit_value)
|
||||||
return jsonify({"query": query, "limit": limit_value, "results": results})
|
return jsonify({"query": query, "limit": limit_value, "results": results})
|
||||||
|
|
||||||
|
@entities_bp.get("/")
|
||||||
|
def entities_page() -> str:
|
||||||
|
settings = load_settings()
|
||||||
|
lang = request.args.get("lang") or settings.get("language", "en")
|
||||||
|
options = template_options()
|
||||||
|
return render_template(
|
||||||
|
"entities.html",
|
||||||
|
language=lang,
|
||||||
|
languages=options["languages"].items(),
|
||||||
|
)
|
||||||
|
|||||||
@@ -274,3 +274,14 @@ def stream_logs(job_id: str) -> ResponseReturnValue:
|
|||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
return Response(generate(), mimetype="text/event-stream")
|
return Response(generate(), mimetype="text/event-stream")
|
||||||
|
|
||||||
|
@jobs_bp.get("/queue")
|
||||||
|
def queue_page() -> str:
|
||||||
|
return render_template(
|
||||||
|
"queue.html",
|
||||||
|
jobs_panel=render_jobs_panel(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@jobs_bp.get("/partial")
|
||||||
|
def jobs_partial() -> str:
|
||||||
|
return render_jobs_panel()
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ def get_preview_pipeline(language: str, device: str) -> Any:
|
|||||||
_preview_pipelines[key] = pipeline
|
_preview_pipelines[key] = pipeline
|
||||||
return pipeline
|
return pipeline
|
||||||
|
|
||||||
def synthesize_preview(
|
def generate_preview_audio(
|
||||||
text: str,
|
text: str,
|
||||||
voice_spec: str,
|
voice_spec: str,
|
||||||
language: str,
|
language: str,
|
||||||
speed: float,
|
speed: float,
|
||||||
use_gpu: bool,
|
use_gpu: bool,
|
||||||
max_seconds: float = 8.0,
|
max_seconds: float = 8.0,
|
||||||
) -> ResponseReturnValue:
|
) -> bytes:
|
||||||
if not text.strip():
|
if not text.strip():
|
||||||
raise ValueError("Preview text is required")
|
raise ValueError("Preview text is required")
|
||||||
|
|
||||||
@@ -92,8 +92,29 @@ def synthesize_preview(
|
|||||||
audio_data = np.concatenate(audio_chunks)
|
audio_data = np.concatenate(audio_chunks)
|
||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
sf.write(buffer, audio_data, SAMPLE_RATE, format="WAV")
|
sf.write(buffer, audio_data, SAMPLE_RATE, format="WAV")
|
||||||
buffer.seek(0)
|
return buffer.getvalue()
|
||||||
|
|
||||||
|
def synthesize_preview(
|
||||||
|
text: str,
|
||||||
|
voice_spec: str,
|
||||||
|
language: str,
|
||||||
|
speed: float,
|
||||||
|
use_gpu: bool,
|
||||||
|
max_seconds: float = 8.0,
|
||||||
|
) -> ResponseReturnValue:
|
||||||
|
try:
|
||||||
|
audio_bytes = generate_preview_audio(
|
||||||
|
text=text,
|
||||||
|
voice_spec=voice_spec,
|
||||||
|
language=language,
|
||||||
|
speed=speed,
|
||||||
|
use_gpu=use_gpu,
|
||||||
|
max_seconds=max_seconds,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
buffer = io.BytesIO(audio_bytes)
|
||||||
response = send_file(
|
response = send_file(
|
||||||
buffer,
|
buffer,
|
||||||
mimetype="audio/wav",
|
mimetype="audio/wav",
|
||||||
|
|||||||
@@ -22,11 +22,8 @@ from abogen.constants import VOICES_INTERNAL
|
|||||||
voices_bp = Blueprint("voices", __name__)
|
voices_bp = Blueprint("voices", __name__)
|
||||||
|
|
||||||
@voices_bp.get("/")
|
@voices_bp.get("/")
|
||||||
def voices_list() -> ResponseReturnValue:
|
def voice_profiles() -> ResponseReturnValue:
|
||||||
# This might not be a standalone page in the original app, but useful to have.
|
return render_template("voices.html")
|
||||||
# Or maybe it redirects to settings or something.
|
|
||||||
# For now, I'll just redirect to settings as voices are managed there usually.
|
|
||||||
return redirect(url_for("settings.settings_page"))
|
|
||||||
|
|
||||||
@voices_bp.post("/test")
|
@voices_bp.post("/test")
|
||||||
def test_voice() -> ResponseReturnValue:
|
def test_voice() -> ResponseReturnValue:
|
||||||
|
|||||||
@@ -17,12 +17,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<nav class="top-actions">
|
<nav class="top-actions">
|
||||||
{% set endpoint = request.endpoint or '' %}
|
{% set endpoint = request.endpoint or '' %}
|
||||||
<a href="{{ url_for('web.index') }}" class="btn{% if endpoint == 'web.index' %} is-active{% endif %}">Dashboard</a>
|
<a href="{{ url_for('main.index') }}" class="btn{% if endpoint == 'main.index' %} is-active{% endif %}">Dashboard</a>
|
||||||
<a href="{{ url_for('web.voice_profiles_page') }}" class="btn{% if endpoint == 'web.voice_profiles_page' %} is-active{% endif %}">Voice Mixer</a>
|
<a href="{{ url_for('voices.voice_profiles') }}" class="btn{% if endpoint == 'voices.voice_profiles' %} is-active{% endif %}">Voice Mixer</a>
|
||||||
<a href="{{ url_for('web.entities_page') }}" class="btn{% if endpoint == 'web.entities_page' %} is-active{% endif %}">Entities</a>
|
<a href="{{ url_for('entities.entities_page') }}" class="btn{% if endpoint == 'entities.entities_page' %} is-active{% endif %}">Entities</a>
|
||||||
<a href="{{ url_for('web.find_books_page') }}" class="btn{% if endpoint == 'web.find_books_page' %} is-active{% endif %}">Find Books</a>
|
<a href="{{ url_for('books.find_books_page') }}" class="btn{% if endpoint == 'books.find_books_page' %} is-active{% endif %}">Find Books</a>
|
||||||
<a href="{{ url_for('web.queue_page') }}" class="btn{% if endpoint in ['web.queue_page', 'web.job_detail'] %} is-active{% endif %}">Queue</a>
|
<a href="{{ url_for('jobs.queue_page') }}" class="btn{% if endpoint in ['jobs.queue_page', 'jobs.job_detail'] %} is-active{% endif %}">Queue</a>
|
||||||
<a href="{{ url_for('web.settings_page') }}" class="btn{% if endpoint == 'web.settings_page' %} is-active{% endif %}">Settings</a>
|
<a href="{{ url_for('settings.settings_page') }}" class="btn{% if endpoint == 'settings.settings_page' %} is-active{% endif %}">Settings</a>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<main class="main">
|
<main class="main">
|
||||||
|
|||||||
@@ -44,19 +44,19 @@
|
|||||||
<p>Characters: {{ job.processed_characters }} / {{ job.total_characters or '—' }}</p>
|
<p>Characters: {{ job.processed_characters }} / {{ job.total_characters or '—' }}</p>
|
||||||
{% set flags = downloads or {} %}
|
{% set flags = downloads or {} %}
|
||||||
{% if flags.get('m4b') %}
|
{% if flags.get('m4b') %}
|
||||||
<p><a class="button" href="{{ url_for('web.download_job_m4b', job_id=job.id) }}">Download M4B</a></p>
|
<p><a class="button" href="{{ url_for('jobs.download_file', job_id=job.id, file_type='audio') }}">Download M4B</a></p>
|
||||||
{% elif flags.get('audio') %}
|
{% elif flags.get('audio') %}
|
||||||
<p><a class="button" href="{{ url_for('web.download_job', job_id=job.id) }}">Download audio</a></p>
|
<p><a class="button" href="{{ url_for('jobs.download_file', job_id=job.id, file_type='audio') }}">Download audio</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if flags.get('epub3') %}
|
{% if flags.get('epub3') %}
|
||||||
<p><a class="button button--ghost" href="{{ url_for('web.download_job_epub3', job_id=job.id) }}">Download EPUB 3</a></p>
|
<p><a class="button button--ghost" href="{{ url_for('jobs.job_epub', job_id=job.id) }}">Download EPUB 3</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if job.status in [JobStatus.PENDING, JobStatus.RUNNING, JobStatus.PAUSED] %}
|
{% if job.status in [JobStatus.PENDING, JobStatus.RUNNING, JobStatus.PAUSED] %}
|
||||||
<form action="{{ url_for('web.cancel_job', job_id=job.id) }}" method="post">
|
<form action="{{ url_for('jobs.cancel_job', job_id=job.id) }}" method="post">
|
||||||
<button type="submit" class="button button--ghost">Cancel job</button>
|
<button type="submit" class="button button--ghost">Cancel job</button>
|
||||||
</form>
|
</form>
|
||||||
{% elif job.status in [JobStatus.COMPLETED, JobStatus.FAILED, JobStatus.CANCELLED] %}
|
{% elif job.status in [JobStatus.COMPLETED, JobStatus.FAILED, JobStatus.CANCELLED] %}
|
||||||
<form action="{{ url_for('web.retry_job', job_id=job.id) }}" method="post">
|
<form action="{{ url_for('jobs.retry_job', job_id=job.id) }}" method="post">
|
||||||
<button type="submit" class="button">Retry job</button>
|
<button type="submit" class="button">Retry job</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<li class="job-card" data-status="{{ job.status.value }}">
|
<li class="job-card" data-status="{{ job.status.value }}">
|
||||||
<div class="job-card__header">
|
<div class="job-card__header">
|
||||||
<div>
|
<div>
|
||||||
<a class="job-card__title" href="{{ url_for('web.job_detail', job_id=job.id) }}">{{ job.original_filename }}</a>
|
<a class="job-card__title" href="{{ url_for('jobs.job_detail', job_id=job.id) }}">{{ job.original_filename }}</a>
|
||||||
<div class="job-card__meta">
|
<div class="job-card__meta">
|
||||||
{% if job.queue_position %}Position #{{ job.queue_position }} · {% endif %}
|
{% if job.queue_position %}Position #{{ job.queue_position }} · {% endif %}
|
||||||
{% if job.voice_profile %}Profile: {{ job.voice_profile }}{% else %}Voice: {{ job.voice }}{% endif %} · {{ job.language }}
|
{% if job.voice_profile %}Profile: {{ job.voice_profile }}{% else %}Voice: {{ job.voice }}{% endif %} · {{ job.language }}
|
||||||
@@ -29,16 +29,16 @@
|
|||||||
<small>{{ progress_value }}% · {{ job.processed_characters }} / {{ job.total_characters or '—' }}</small>
|
<small>{{ progress_value }}% · {{ job.processed_characters }} / {{ job.total_characters or '—' }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="job-card__footer">
|
<div class="job-card__footer">
|
||||||
<a class="button button--ghost" href="{{ url_for('web.job_detail', job_id=job.id) }}">Details</a>
|
<a class="button button--ghost" href="{{ url_for('jobs.job_detail', job_id=job.id) }}">Details</a>
|
||||||
{% if job.status == JobStatus.RUNNING %}
|
{% if job.status == JobStatus.RUNNING %}
|
||||||
<button type="button" class="button button--ghost" hx-post="{{ url_for('web.pause_job', job_id=job.id) }}" hx-target="#jobs-panel" hx-swap="innerHTML">Pause</button>
|
<button type="button" class="button button--ghost" hx-post="{{ url_for('jobs.pause_job', job_id=job.id) }}" hx-target="#jobs-panel" hx-swap="innerHTML">Pause</button>
|
||||||
{% elif job.status == JobStatus.PAUSED %}
|
{% elif job.status == JobStatus.PAUSED %}
|
||||||
<button type="button" class="button button--ghost" hx-post="{{ url_for('web.resume_job', job_id=job.id) }}" hx-target="#jobs-panel" hx-swap="innerHTML">Resume</button>
|
<button type="button" class="button button--ghost" hx-post="{{ url_for('jobs.resume_job', job_id=job.id) }}" hx-target="#jobs-panel" hx-swap="innerHTML">Resume</button>
|
||||||
{% elif job.status == JobStatus.PENDING %}
|
{% elif job.status == JobStatus.PENDING %}
|
||||||
<button type="button" class="button button--ghost" hx-post="{{ url_for('web.pause_job', job_id=job.id) }}" hx-target="#jobs-panel" hx-swap="innerHTML">Pause</button>
|
<button type="button" class="button button--ghost" hx-post="{{ url_for('jobs.pause_job', job_id=job.id) }}" hx-target="#jobs-panel" hx-swap="innerHTML">Pause</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if job.status in [JobStatus.PENDING, JobStatus.RUNNING, JobStatus.PAUSED] %}
|
{% if job.status in [JobStatus.PENDING, JobStatus.RUNNING, JobStatus.PAUSED] %}
|
||||||
<button type="button" class="button button--ghost" hx-post="{{ url_for('web.cancel_job', job_id=job.id) }}" hx-target="#jobs-panel" hx-swap="innerHTML" hx-confirm="Cancel this conversion?">Cancel</button>
|
<button type="button" class="button button--ghost" hx-post="{{ url_for('jobs.cancel_job', job_id=job.id) }}" hx-target="#jobs-panel" hx-swap="innerHTML" hx-confirm="Cancel this conversion?">Cancel</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
<header class="queue-section__header">
|
<header class="queue-section__header">
|
||||||
<h3>Recent results</h3>
|
<h3>Recent results</h3>
|
||||||
{% if total_finished > 0 %}
|
{% if total_finished > 0 %}
|
||||||
<button type="button" class="button button--ghost" hx-post="{{ url_for('web.clear_finished_jobs') }}" hx-target="#jobs-panel" hx-swap="innerHTML" hx-confirm="Remove completed jobs from this list?">Clear finished</button>
|
<button type="button" class="button button--ghost" hx-post="{{ url_for('jobs.clear_finished_jobs') }}" hx-target="#jobs-panel" hx-swap="innerHTML" hx-confirm="Remove completed jobs from this list?">Clear finished</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</header>
|
</header>
|
||||||
{% if finished_jobs %}
|
{% if finished_jobs %}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
<div class="card__title-row">
|
<div class="card__title-row">
|
||||||
<div class="card__title">Live log</div>
|
<div class="card__title">Live log</div>
|
||||||
{% if not is_static %}
|
{% if not is_static %}
|
||||||
<a class="button button--ghost button--small" href="{{ url_for('web.job_logs_static', job_id=job.id) }}" target="_blank" rel="noopener">Open static view</a>
|
<a class="button button--ghost button--small" href="{{ url_for('jobs.job_logs', job_id=job.id) }}" target="_blank" rel="noopener">Open static view</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="button button--ghost button--small" href="{{ url_for('web.job_detail', job_id=job.id) }}">Back to job</a>
|
<a class="button button--ghost button--small" href="{{ url_for('jobs.job_detail', job_id=job.id) }}">Back to job</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if job.logs %}
|
{% if job.logs %}
|
||||||
|
|||||||
@@ -249,7 +249,7 @@
|
|||||||
{% if notice %}
|
{% if notice %}
|
||||||
<div class="alert alert--info">{{ notice }}</div>
|
<div class="alert alert--info">{{ notice }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form action="{{ url_for('web.enqueue_job') if not readonly else '#' }}"
|
<form action="{{ url_for('main.wizard_upload') if not readonly else '#' }}"
|
||||||
method="post"
|
method="post"
|
||||||
id="new-job-book-form"
|
id="new-job-book-form"
|
||||||
class="upload-form"
|
class="upload-form"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<form method="post"
|
<form method="post"
|
||||||
action="{{ url_for('web.finalize_job', pending_id=pending.id) }}"
|
action="{{ url_for('main.wizard_finish', pending_id=pending.id) }}"
|
||||||
class="prepare-form"
|
class="prepare-form"
|
||||||
id="prepare-form"
|
id="prepare-form"
|
||||||
data-role="prepare-form"
|
data-role="prepare-form"
|
||||||
data-wizard-form="true"
|
data-wizard-form="true"
|
||||||
data-step="chapters"
|
data-step="chapters"
|
||||||
data-pending-id="{{ pending.id }}"
|
data-pending-id="{{ pending.id }}"
|
||||||
data-analyze-url="{{ url_for('web.analyze_pending_job', pending_id=pending.id) }}">
|
data-analyze-url="{{ url_for('main.wizard_update', pending_id=pending.id) }}">
|
||||||
<input type="hidden" name="active_step" value="chapters" data-role="active-step-input">
|
<input type="hidden" name="active_step" value="chapters" data-role="active-step-input">
|
||||||
<div class="wizard-hidden-inputs" aria-hidden="true">
|
<div class="wizard-hidden-inputs" aria-hidden="true">
|
||||||
<input type="hidden" name="chunk_level" value="{{ pending.chunk_level }}">
|
<input type="hidden" name="chunk_level" value="{{ pending.chunk_level }}">
|
||||||
|
|||||||
@@ -24,9 +24,9 @@
|
|||||||
{% set total_steps = 3 %}
|
{% set total_steps = 3 %}
|
||||||
{% set current_index = step_number[current_step] %}
|
{% set current_index = step_number[current_step] %}
|
||||||
{% set is_open = open if open is defined else False %}
|
{% set is_open = open if open is defined else False %}
|
||||||
{% set prepare_url_template = url_for('web.prepare_job', pending_id='__pending__') %}
|
{% set prepare_url_template = url_for('main.wizard_step', pending_id='__pending__') %}
|
||||||
{% set cancel_url_template = url_for('web.cancel_pending_job', pending_id='__pending__') %}
|
{% set cancel_url_template = url_for('main.wizard_cancel', pending_id='__pending__') %}
|
||||||
{% set analyze_url_template = url_for('web.analyze_pending_job', pending_id='__pending__') %}
|
{% set analyze_url_template = url_for('main.wizard_update', pending_id='__pending__') %}
|
||||||
<div class="modal"
|
<div class="modal"
|
||||||
data-role="new-job-modal"
|
data-role="new-job-modal"
|
||||||
data-step="{{ current_step }}"
|
data-step="{{ current_step }}"
|
||||||
|
|||||||
@@ -21,13 +21,13 @@
|
|||||||
</button>
|
</button>
|
||||||
<a class="step-indicator__item is-active"
|
<a class="step-indicator__item is-active"
|
||||||
aria-current="step"
|
aria-current="step"
|
||||||
href="{{ url_for('web.prepare_job', pending_id=pending.id, step='chapters') }}">
|
href="{{ url_for('main.wizard_step', pending_id=pending.id, step='chapters') }}">
|
||||||
<span class="step-indicator__index">2</span>
|
<span class="step-indicator__index">2</span>
|
||||||
<span class="step-indicator__label">Chapters</span>
|
<span class="step-indicator__label">Chapters</span>
|
||||||
</a>
|
</a>
|
||||||
{% if is_multi_speaker %}
|
{% if is_multi_speaker %}
|
||||||
<a class="step-indicator__item"
|
<a class="step-indicator__item"
|
||||||
href="{{ url_for('web.prepare_job', pending_id=pending.id, step='entities') }}">
|
href="{{ url_for('main.wizard_step', pending_id=pending.id, step='entities') }}">
|
||||||
<span class="step-indicator__index">3</span>
|
<span class="step-indicator__index">3</span>
|
||||||
<span class="step-indicator__label">Entities</span>
|
<span class="step-indicator__label">Entities</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -41,11 +41,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<form method="post"
|
<form method="post"
|
||||||
action="{{ url_for('web.finalize_job', pending_id=pending.id) }}"
|
action="{{ url_for('main.wizard_finish', pending_id=pending.id) }}"
|
||||||
class="prepare-form"
|
class="prepare-form"
|
||||||
id="prepare-form"
|
id="prepare-form"
|
||||||
data-speaker-mode="{{ pending.speaker_mode }}"
|
data-speaker-mode="{{ pending.speaker_mode }}"
|
||||||
data-analyze-url="{{ url_for('web.analyze_pending_job', pending_id=pending.id) }}">
|
data-analyze-url="{{ url_for('main.wizard_update', pending_id=pending.id) }}">
|
||||||
<input type="hidden" name="active_step" value="chapters" data-role="active-step-input">
|
<input type="hidden" name="active_step" value="chapters" data-role="active-step-input">
|
||||||
<div class="wizard-hidden-inputs" aria-hidden="true">
|
<div class="wizard-hidden-inputs" aria-hidden="true">
|
||||||
<input type="hidden" name="chunk_level" value="{{ pending.chunk_level }}">
|
<input type="hidden" name="chunk_level" value="{{ pending.chunk_level }}">
|
||||||
@@ -159,7 +159,7 @@
|
|||||||
class="button"
|
class="button"
|
||||||
data-role="submit-speaker-analysis"
|
data-role="submit-speaker-analysis"
|
||||||
data-step-toggle="analysis"
|
data-step-toggle="analysis"
|
||||||
formaction="{{ url_for('web.analyze_pending_job', pending_id=pending.id) }}"
|
formaction="{{ url_for('main.wizard_update', pending_id=pending.id) }}"
|
||||||
formmethod="post">
|
formmethod="post">
|
||||||
Continue to entities
|
Continue to entities
|
||||||
</button>
|
</button>
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</form>
|
</form>
|
||||||
<form method="post" action="{{ url_for('web.cancel_pending_job', pending_id=pending.id) }}" id="cancel-form"></form>
|
<form method="post" action="{{ url_for('main.wizard_cancel', pending_id=pending.id) }}" id="cancel-form"></form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<button type="button" class="settings-nav__item" data-section="normalization">Text Normalization</button>
|
<button type="button" class="settings-nav__item" data-section="normalization">Text Normalization</button>
|
||||||
<button type="button" class="settings-nav__item" data-section="integrations">Integrations</button>
|
<button type="button" class="settings-nav__item" data-section="integrations">Integrations</button>
|
||||||
</nav>
|
</nav>
|
||||||
<form action="{{ url_for('web.settings_page') }}" method="post" class="settings__form">
|
<form action="{{ url_for('settings.settings_page') }}" method="post" class="settings__form">
|
||||||
<div class="settings-panels">
|
<div class="settings-panels">
|
||||||
<section class="settings-panel is-active" data-section="narration">
|
<section class="settings-panel is-active" data-section="narration">
|
||||||
<fieldset class="settings__section">
|
<fieldset class="settings__section">
|
||||||
|
|||||||
Reference in New Issue
Block a user