tts-voice-creator-clone-and.../static/sections/s-settings.html
mARTin-B78 d99395480a Add Script Rehearser; clean Connect Apps; About changelog; rework Try It Out
## Script Rehearser (new feature)
- New section s-rehearser.html + rehearser.js + nav/loader wiring
- Phase 1: paste/upload script (.txt), auto-detect characters from
  'CHARACTER: dialog' or ALL-CAPS screenplay format
- Phase 2: assign a TTS voice per character, or mark 'I play this'
- Phase 3: step-through rehearsal — synthesizes other characters via TTS,
  shows level-meter + oscilloscope for your own lines, records them from mic
- Phase 4: session summary with per-line audio playback + download

## Connect Apps
- Removed duplicate standalone MCP/speak/hotkey full-width cards
- Kept the integration-grid cards (they use the real server URL from JS)
- Added Global Hotkey Daemon as a proper integration card with snippet-hotkey
  populated by integrations.js (uses proxyBase URL dynamically)

## About page
- GET /api/changelog endpoint reads CHANGELOG.md and returns it as text
- Collapsible 'Changelog' <details> card fetches and displays it lazily

## Try It Out
- Reorganised into three cards: Voice & backend / Text to synthesize / Generate
- Backend help panel moved below the voice row (not in the same flex row)
- Style instruction field gains a dynamic badge ('style-aware ✓' / 'weak style')
  and a yellow warning when a non-style-aware backend is selected while the
  field is filled — wired to both backend-select change and input events

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 20:09:59 +02:00

545 lines
31 KiB
HTML

<div class="section-head">
<span class="section-icon"><span class="mdi mdi-cog-outline"></span></span>
<div class="section-title">
<h2>Settings</h2>
<p>Configure backend URLs, API keys, voice folders, and playback preferences.</p>
</div>
</div>
<div class="tab-content" id="tab-settings">
<!-- ── General ─────────────────────────────────────────────── -->
<div class="s-settings-page" data-page="general">
<div class="card">
<div class="s-page-head">
<h3>General</h3>
<p>Interface preferences.</p>
</div>
<div class="settings-grid settings-behavior-grid">
<div class="s-field">
<label>Theme</label>
<select id="s-theme-select">
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<span class="s-hint">Switch between light and dark interface. Saved instantly.</span>
</div>
</div>
</div>
</div>
<!-- ── Connections ─────────────────────────────────────────── -->
<div class="s-settings-page" data-page="connections">
<div class="card">
<div class="s-page-head">
<div class="s-page-head-row">
<div>
<h3>Connections</h3>
<p>Service URLs for each backend. Change these first when setting up.</p>
</div>
<span class="s-stack-badge"><span class="mdi mdi-check-circle-outline"></span> Local stack</span>
</div>
</div>
<div class="s-group">
<div class="s-group-head">
<strong>TTS — Text to Speech</strong>
<span>Qwen3 engines: clone, design, custom, streaming &middot; Kokoro FastAPI</span>
</div>
<div class="settings-grid compact">
<div class="s-field">
<label>Voice Clone / Base URL <span class="s-label-note">(WAV voices)</span></label>
<input type="text" id="s-tts-url" placeholder="http://host.docker.internal:8020">
<span class="s-hint">Uploaded/cloned WAV voices. Expected: <code>POST /v1/audio/speech</code>.</span>
</div>
<div class="s-field">
<label>Voice Design URL <span class="s-label-note">(instruction voices)</span></label>
<input type="text" id="s-voice-design-url" placeholder="http://host.docker.internal:8021">
<span class="s-hint">Prompt-designed voices and <code>vd_...</code> virtual voices.</span>
</div>
<div class="s-field">
<label>CustomVoice URL</label>
<input type="text" id="s-customvoice-url" placeholder="http://host.docker.internal:8022">
<span class="s-hint">Style over configured speakers such as Ryan, Vivian, Serena.</span>
</div>
<div class="s-field">
<label>Streaming URL</label>
<input type="text" id="s-tts-stream-url" placeholder="http://host.docker.internal:8023">
<span class="s-hint">Progressive low-latency WAV playback.</span>
</div>
<div class="s-field">
<label>Kokoro FastAPI URL <span class="s-label-note">(82M &middot; CPU-friendly)</span></label>
<input type="text" id="s-kokoro-url" placeholder="http://host.docker.internal:8880/v1">
<span class="s-hint">OpenAI-compatible TTS. Built-in voices: af_bella, bf_emma, am_adam&hellip; ~300 MB RAM.</span>
</div>
<div class="s-field">
<label>VibeVoice URL <span class="s-label-note">(vibevoice-service)</span></label>
<input type="text" id="s-vibevoice-url" placeholder="http://192.168.178.8:8027">
<span class="s-hint">Simple text-in audio-out TTS. POST /tts with {"text":"..."}.</span>
</div>
<div class="s-field">
<label>XTTS v2 URL <span class="s-label-note">(xtts-api-server)</span></label>
<input type="text" id="s-xtts-url" placeholder="http://host.docker.internal:8024">
<span class="s-hint">XTTS v2 via daswer123/xtts-api-server. Supports <code>GET /speakers</code> and <code>POST /v1/audio/speech</code>.</span>
</div>
</div>
</div>
<div class="s-group">
<div class="s-group-head">
<strong>NVIDIA speech stack</strong>
<span>router, Magpie TTS, Parakeet ASR, clone NIM</span>
</div>
<div class="settings-grid compact">
<div class="s-field">
<label>NVIDIA router URL <span class="s-label-note">(TTS + STT)</span></label>
<input type="text" id="s-nvidia-router-url" placeholder="http://host.docker.internal:8090">
<span class="s-hint">OpenAI-compatible base URL for the NVIDIA speech router.</span>
</div>
<div class="s-field">
<label>NVIDIA Magpie TTS URL</label>
<input type="text" id="s-nvidia-tts-url" placeholder="http://host.docker.internal:8091">
<span class="s-hint">Direct Magpie endpoint. Fixed speakers, not WAV cloning.</span>
</div>
<div class="s-field">
<label>NVIDIA Parakeet ASR URL</label>
<input type="text" id="s-nvidia-asr-url" placeholder="http://host.docker.internal:8092">
<span class="s-hint">Direct Parakeet endpoint for transcription.</span>
</div>
<div class="s-field">
<label>NVIDIA Zeroshot NIM URL</label>
<input type="text" id="s-nvidia-zeroshot-url" placeholder="http://host.docker.internal:8093">
<span class="s-hint">Magpie Zeroshot clone endpoint with <code>audio_prompt</code>.</span>
</div>
<div class="s-field">
<label>NVIDIA Flow NIM URL</label>
<input type="text" id="s-nvidia-flow-url" placeholder="http://host.docker.internal:8094">
<span class="s-hint">Magpie Flow clone endpoint with <code>audio_prompt</code> and transcript.</span>
</div>
</div>
</div>
<div class="s-group">
<div class="s-group-head">
<strong>STT — Speech to Text</strong>
<span>Whisper, faster-whisper, whisper.cpp, Groq, Parakeet, NVIDIA router</span>
</div>
<div class="settings-grid stt-settings-grid">
<div class="s-field stt-url-field">
<label>Active STT URL</label>
<input type="text" id="s-whisper-url" placeholder="http://host.docker.internal:8010">
<span class="s-hint">Default recognition endpoint. Expected: <code>POST /v1/audio/transcriptions</code>.</span>
<div class="btn-row settings-mini-actions">
<button class="btn-secondary" id="s-use-parakeet-asr" type="button">Use Parakeet</button>
<button class="btn-secondary" id="s-use-nvidia-router" type="button">Use router</button>
<button class="btn-secondary" id="s-use-faster-whisper" type="button">Use faster-whisper</button>
<button class="btn-secondary" id="s-use-whisper-cpp" type="button">Use whisper.cpp</button>
</div>
</div>
<div class="s-field">
<label>faster-whisper-server URL <span class="s-label-note">(CTranslate2 GPU)</span></label>
<input type="text" id="s-faster-whisper-url" placeholder="http://host.docker.internal:8000">
<span class="s-hint">GPU-accelerated Whisper via CTranslate2. ~70&times; RT &middot; 1.5 GB VRAM.</span>
</div>
<div class="s-field">
<label>whisper.cpp URL <span class="s-label-note">(CPU/CUDA)</span></label>
<input type="text" id="s-whisper-cpp-url" placeholder="http://host.docker.internal:8080">
<span class="s-hint">Lightweight C++ Whisper server. ~8&ndash;15&times; RT CPU &middot; ~1 GB RAM.</span>
</div>
<div class="s-field">
<label>Groq API key <span class="s-label-note">(Groq Whisper &amp; LLM)</span></label>
<div class="s-key-row">
<input type="password" id="s-groq-api-key" placeholder="gsk_&hellip;" autocomplete="off">
<button type="button" class="s-eye-btn" data-target="s-groq-api-key"><span class="mdi mdi-eye-outline"></span></button>
</div>
<span class="s-hint">Used for Groq Whisper STT and LLM. Free: 2 000 req/day.</span>
</div>
</div>
</div>
<div class="s-page-actions">
<button class="btn-primary s-save-btn">Save settings</button>
<button class="btn-secondary s-reload-btn">Reload settings</button>
</div>
</div>
</div>
<!-- ── Playback ─────────────────────────────────────────────── -->
<div class="s-settings-page" data-page="playback">
<div class="card">
<div class="s-page-head">
<h3>Playback</h3>
<p>Controls how previews play and how OpenAI-compatible requests are shaped.</p>
</div>
<div class="settings-grid settings-behavior-grid">
<div class="s-field">
<label>TTS preview playback</label>
<select id="s-tts-stream-mode">
<option value="auto">Stream when available, then fall back</option>
<option value="streaming">Streaming only</option>
<option value="buffered">Buffered WAV only</option>
</select>
<span class="s-hint">Buffered keeps Save WAV available. Streaming starts sooner.</span>
</div>
<div class="s-field">
<label>OpenAI-compatible request style</label>
<select id="s-tts-backend">
<option value="openai">Qwen3-TTS / OpenAI</option>
<option value="localai">LocalAI (/tts)</option>
<option value="pocket">Pocket-TTS</option>
<option value="nvidia_magpie">NVIDIA Magpie / OpenAI</option>
</select>
<span class="s-hint">Controls payload shape for the normal TTS API URL.</span>
</div>
</div>
<div class="s-page-actions">
<button class="btn-primary s-save-btn">Save settings</button>
<button class="btn-secondary s-reload-btn">Reload settings</button>
</div>
</div>
</div>
<!-- ── Captures ────────────────────────────────────────────── -->
<div class="s-settings-page" data-page="captures">
<div class="card">
<div class="s-page-head">
<h3>Captures</h3>
<p>Default behaviour for STT transcription, LLM text refinement, and playback.</p>
</div>
<div class="s-group">
<div class="s-group-head">
<strong>Transcription</strong>
<span>Default language and preferred STT backend</span>
</div>
<div class="settings-grid settings-behavior-grid">
<div class="s-field">
<label>Default language</label>
<select id="s-stt-language">
<option value="">Auto-detect</option>
<option value="en">English</option>
<option value="de">German</option>
<option value="fr">French</option>
<option value="es">Spanish</option>
<option value="it">Italian</option>
<option value="pt">Portuguese</option>
<option value="nl">Dutch</option>
<option value="ru">Russian</option>
<option value="ja">Japanese</option>
<option value="zh">Chinese</option>
<option value="hi">Hindi</option>
</select>
<span class="s-hint">Language hint passed to the STT backend. Auto-detect works well in most cases.</span>
</div>
<div class="s-field">
<label>Preferred STT backend</label>
<select id="s-stt-preferred-backend">
<option value="">Use active STT URL (from Connections)</option>
<option value="groq">Groq Whisper — cloud, fastest</option>
<option value="faster-whisper">faster-whisper-server — GPU local</option>
<option value="whisper-cpp">whisper.cpp — CPU/CUDA local</option>
<option value="parakeet">NVIDIA Parakeet — GPU local</option>
</select>
<span class="s-hint">Overrides the active STT URL for STT-TTS panel captures.</span>
</div>
</div>
</div>
<div class="s-group">
<div class="s-group-head">
<strong>LLM server</strong>
<span>Base URL for persona rewrite, text refinement, and other local LLM features</span>
</div>
<div class="settings-grid compact">
<div class="s-field">
<label>LLM base URL</label>
<input type="text" id="s-llm-url" placeholder="http://localhost:11434/v1">
<span class="s-hint">OpenAI-compatible endpoint. Used for persona rewrites, voice design, and auto-refine.</span>
</div>
</div>
</div>
<div class="s-group">
<div class="s-group-head">
<strong>LLM refinement defaults</strong>
<span>Automatic text cleanup after transcription</span>
</div>
<div class="settings-grid settings-behavior-grid">
<div class="s-field">
<label>Auto-refine after transcription</label>
<select id="s-auto-refine">
<option value="off">Off — show raw transcription</option>
<option value="on">On — refine automatically</option>
</select>
<span class="s-hint">When on, the LLM refinement runs immediately after each capture.</span>
</div>
<div class="s-field">
<label>Refinement model</label>
<input type="text" id="s-refine-model" placeholder="e.g. llama3.2, qwen3:8b, gpt-4o-mini">
<span class="s-hint">Model name sent to the LLM URL. Leave empty to use the app's current default.</span>
</div>
</div>
<div class="s-toggle-grid">
<label class="s-toggle-row">
<input type="checkbox" id="s-refine-fillers">
<div>
<span class="s-toggle-title">Remove filler words</span>
<span class="s-hint">Strips um, uh, like, you know, sort of.</span>
</div>
</label>
<label class="s-toggle-row">
<input type="checkbox" id="s-refine-repetitions">
<div>
<span class="s-toggle-title">Remove repetitions</span>
<span class="s-hint">Collapses repeated words and phrases.</span>
</div>
</label>
<label class="s-toggle-row">
<input type="checkbox" id="s-refine-corrections">
<div>
<span class="s-toggle-title">Fix grammar &amp; spelling</span>
<span class="s-hint">Light corrections without changing meaning.</span>
</div>
</label>
<label class="s-toggle-row">
<input type="checkbox" id="s-refine-punctuation">
<div>
<span class="s-toggle-title">Fix punctuation</span>
<span class="s-hint">Adds missing commas, periods, and sentence casing.</span>
</div>
</label>
</div>
</div>
<div class="s-group">
<div class="s-group-head">
<strong>Default playback voice</strong>
<span>Pre-selected voice in the STT-TTS panel</span>
</div>
<div class="settings-grid settings-behavior-grid">
<div class="s-field">
<label>Default voice</label>
<select id="s-captures-default-voice">
<option value="">None — select manually</option>
</select>
<span class="s-hint">Pre-selects this voice in the STT-TTS panel on load.</span>
</div>
</div>
</div>
<div class="s-page-actions">
<button class="btn-primary s-save-btn">Save settings</button>
<button class="btn-secondary s-reload-btn">Reload settings</button>
</div>
</div>
</div>
<!-- ── Payloads ─────────────────────────────────────────────── -->
<div class="s-settings-page" data-page="payloads">
<div class="card">
<div class="s-page-head">
<h3>Payloads</h3>
<p>Extra JSON fields sent alongside each backend request. Usually only needed for advanced tuning.</p>
</div>
<div class="settings-grid three settings-param-grid">
<div class="s-field">
<label>Voice Clone / Base params</label>
<textarea id="s-tts-extra-voice-clone" spellcheck="false" placeholder='{"temperature":0.1,"top_p":0.8,"seed":0}'></textarea>
<span class="s-hint">Extra fields for the 8020 WAV voice clone/base model.</span>
</div>
<div class="s-field">
<label>Streaming params</label>
<textarea id="s-tts-extra-streaming" spellcheck="false" placeholder='{"temperature":0.1,"top_p":0.8,"seed":0}'></textarea>
<span class="s-hint">Extra fields for the 8023 streaming model.</span>
</div>
<div class="s-field">
<label>CustomVoice params</label>
<textarea id="s-tts-extra-customvoice" spellcheck="false" placeholder='{"temperature":0.1,"top_p":0.8,"seed":0}'></textarea>
<span class="s-hint">Extra fields for the CustomVoice backend.</span>
</div>
<div class="s-field">
<label>Voice Design params</label>
<textarea id="s-tts-extra-voice-design" spellcheck="false" placeholder='{"temperature":0.1,"top_p":0.8,"seed":0}'></textarea>
<span class="s-hint">Extra fields for Voice Design and virtual <code>vd_...</code> voices.</span>
</div>
<div class="s-field">
<label>NVIDIA Magpie params</label>
<textarea id="s-tts-extra-nvidia-magpie" spellcheck="false" placeholder="{}"></textarea>
<span class="s-hint">Usually empty. Magpie accepts fixed speakers: sofia, aria, jason, leo, john.</span>
</div>
<div class="s-field">
<label>NVIDIA Zeroshot params</label>
<textarea id="s-tts-extra-nvidia-zeroshot" spellcheck="false" placeholder='{"zero_shot_quality":20}'></textarea>
<span class="s-hint">Optional multipart fields. The app supplies <code>text</code>, <code>language</code>, <code>audio_prompt</code>.</span>
</div>
<div class="s-field">
<label>NVIDIA Flow params</label>
<textarea id="s-tts-extra-nvidia-flow" spellcheck="false" placeholder="{}"></textarea>
<span class="s-hint">Optional multipart fields. The app also sends the saved reference transcript.</span>
</div>
<div class="s-field">
<label>Kokoro FastAPI params</label>
<textarea id="s-tts-extra-kokoro" spellcheck="false" placeholder="{}"></textarea>
<span class="s-hint">Extra JSON fields for Kokoro. Usually empty — voice is selected from the dropdown.</span>
</div>
<div class="s-field">
<label>VibeVoice params</label>
<textarea id="s-tts-extra-vibevoice" spellcheck="false" placeholder="{}"></textarea>
<span class="s-hint">Extra JSON fields for VibeVoice. Usually empty — service only requires text.</span>
</div>
</div>
<div class="s-page-actions">
<button class="btn-primary s-save-btn">Save settings</button>
<button class="btn-secondary s-reload-btn">Reload settings</button>
</div>
</div>
</div>
<!-- ── Storage ─────────────────────────────────────────────── -->
<div class="s-settings-page" data-page="storage">
<div class="card">
<div class="s-page-head">
<h3>Storage</h3>
<p>Container paths for voice files. Match these to your Portainer volume mounts.</p>
</div>
<div class="settings-grid">
<div class="s-field">
<label>Voice scan directory</label>
<input type="text" id="s-voices-scan-dir" placeholder="/voices">
<span class="s-hint">Contains <code>active_voices</code>, <code>hidden_voices</code>, sounds, and metadata.</span>
</div>
<div class="s-field">
<label>Active voices directory</label>
<input type="text" id="s-output-dir" placeholder="/voices/active_voices">
<span class="s-hint">New cloned/exported voices are saved here.</span>
</div>
</div>
<div class="s-page-actions">
<button class="btn-primary s-save-btn">Save settings</button>
<button class="btn-secondary s-reload-btn">Reload settings</button>
</div>
</div>
</div>
<!-- ── API Keys ─────────────────────────────────────────────── -->
<div class="s-settings-page" data-page="apikeys">
<div class="card">
<div class="s-page-head">
<h3>API Keys</h3>
<p>Usually empty for local containers. Set a dummy value like <code>sk-local</code> if a backend requires an Authorization header.</p>
</div>
<div class="settings-grid three">
<div class="s-field">
<label>TTS API key <span class="s-label-note">(optional)</span></label>
<div class="s-key-row">
<input type="password" id="s-tts-key" placeholder="dummy, sk-local, or leave empty" autocomplete="off">
<button type="button" class="s-eye-btn" data-target="s-tts-key"><span class="mdi mdi-eye-outline"></span></button>
</div>
</div>
<div class="s-field">
<label>Voice Design API key <span class="s-label-note">(optional)</span></label>
<div class="s-key-row">
<input type="password" id="s-vd-key" placeholder="dummy, sk-local, or leave empty" autocomplete="off">
<button type="button" class="s-eye-btn" data-target="s-vd-key"><span class="mdi mdi-eye-outline"></span></button>
</div>
</div>
<div class="s-field">
<label>Whisper API key <span class="s-label-note">(optional)</span></label>
<div class="s-key-row">
<input type="password" id="s-whisper-key" placeholder="dummy, sk-local, or leave empty" autocomplete="off">
<button type="button" class="s-eye-btn" data-target="s-whisper-key"><span class="mdi mdi-eye-outline"></span></button>
</div>
</div>
</div>
<div class="s-page-actions">
<button class="btn-primary s-save-btn">Save settings</button>
<button class="btn-secondary s-reload-btn">Reload settings</button>
</div>
</div>
</div>
<!-- ── Backup ─────────────────────────────────────────────── -->
<div class="s-settings-page" data-page="backup">
<div class="card">
<div class="s-page-head">
<h3>Backup &amp; restore</h3>
<p>Export all voices and settings as a ZIP for backup or migration. API keys are excluded from exports.</p>
</div>
<div class="btn-row" style="gap:10px;flex-wrap:wrap">
<a class="btn-secondary" id="s-export-voices-btn" href="/api/voices/export" download><span class="mdi mdi-download"></span> Export voices</a>
<label class="btn-secondary" style="cursor:pointer" title="Import a voices ZIP">
<span class="mdi mdi-upload"></span> Import voices
<input type="file" id="s-import-voices-file" accept=".zip" style="display:none">
</label>
<span class="note" id="s-import-status"></span>
</div>
</div>
</div>
<!-- ── Logs ─────────────────────────────────────────────────── -->
<div class="s-settings-page" data-page="logs">
<div class="card">
<div class="s-page-head">
<h3>Logs</h3>
<p>Recent server activity. Useful for debugging backend connections and API errors.</p>
</div>
<div class="s-log-toolbar">
<button class="btn-secondary" id="s-logs-refresh-btn" type="button"><span class="mdi mdi-refresh"></span> Refresh</button>
<button class="btn-secondary" id="s-logs-clear-btn" type="button"><span class="mdi mdi-trash-can-outline"></span> Clear</button>
<label class="s-log-live-label">
<input type="checkbox" id="s-logs-live-toggle">
<span>Auto-refresh</span>
</label>
<span class="s-log-count" id="s-log-count"></span>
</div>
<div class="s-log-filters">
<button class="s-log-filter is-active" data-log-level="">All</button>
<button class="s-log-filter" data-log-level="ERROR">Error</button>
<button class="s-log-filter" data-log-level="WARNING">Warning</button>
<button class="s-log-filter" data-log-level="INFO">Info</button>
</div>
<div class="s-log-viewer" id="s-log-viewer">
<div class="s-log-empty">Click Refresh to load logs</div>
</div>
</div>
</div>
<!-- ── About ─────────────────────────────────────────────────── -->
<div class="s-settings-page" data-page="about">
<div class="card">
<div class="s-page-head">
<h3>About</h3>
</div>
<div class="s-about-block">
<div class="s-about-name">
<span class="mdi mdi-microphone-variant"></span> TTS Voice Creator
<span id="s-about-version" style="font-size:13px;font-weight:500;color:var(--subtext);margin-left:8px;vertical-align:middle"></span>
</div>
<p class="s-about-desc">Clone, design, and deploy custom voices using local AI backends. Compatible with Qwen3-TTS, Kokoro FastAPI, NVIDIA Magpie, and any OpenAI-compatible TTS/STT endpoint.</p>
<div class="s-about-stack">
<span class="s-about-chip"><span class="mdi mdi-language-python"></span> FastAPI</span>
<span class="s-about-chip"><span class="mdi mdi-language-javascript"></span> Vanilla JS</span>
<span class="s-about-chip"><span class="mdi mdi-music-note"></span> WaveSurfer.js v7</span>
<span class="s-about-chip"><span class="mdi mdi-vector-square"></span> MDI v7.4.47</span>
</div>
<div class="s-about-backends" id="s-about-backends"></div>
<div style="margin-top:14px;display:flex;gap:10px;flex-wrap:wrap">
<a href="https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/releases" target="_blank" rel="noopener" class="btn-secondary" style="text-decoration:none;display:inline-flex;align-items:center;gap:6px"><span class="mdi mdi-tag-outline"></span> Releases &amp; Changelog</a>
<a href="https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2" target="_blank" rel="noopener" class="btn-secondary" style="text-decoration:none;display:inline-flex;align-items:center;gap:6px"><span class="mdi mdi-github"></span> GitHub</a>
</div>
<p class="note" style="margin-top:10px">MCP server, REST /speak endpoint, and global hotkey daemon are documented under <strong>Connect Apps</strong>.</p>
</div>
</div>
<div class="card" style="margin-top:0">
<details id="about-changelog-details">
<summary style="cursor:pointer;font-weight:600;font-size:15px;padding:4px 0;list-style:none;display:flex;align-items:center;gap:8px">
<span class="mdi mdi-history"></span> Changelog
<span id="about-changelog-status" style="font-size:12px;color:var(--subtext);font-weight:400;margin-left:auto"></span>
</summary>
<pre id="about-changelog-content" style="margin-top:12px;white-space:pre-wrap;font-size:12px;line-height:1.6;color:var(--text);font-family:monospace;max-height:400px;overflow-y:auto;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:12px"></pre>
</details>
</div>
</div>
</div><!-- /tab-settings -->