tts-voice-creator-clone-and.../routes/admin.py
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

66 lines
1.8 KiB
Python

"""Admin routes: index, favicon, browse-dirs, robots."""
from __future__ import annotations
from pathlib import Path
from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse, PlainTextResponse, Response
from core.constants import STATIC_DIR, __version__
router = APIRouter()
_BROWSE_BLOCKED: frozenset[str] = frozenset({
"/proc", "/sys", "/dev", "/run", "/boot",
})
@router.get("/")
async def index():
return FileResponse(
STATIC_DIR / "index.html",
headers={"Cache-Control": "no-store, max-age=0"},
)
@router.get("/favicon.ico")
async def favicon():
return Response(status_code=204)
@router.get("/api/browse-dirs")
async def browse_dirs(path: str = "/"):
p = Path(path).resolve()
p_str = str(p)
if any(p_str == b or p_str.startswith(b + "/") for b in _BROWSE_BLOCKED):
raise HTTPException(403, "Access to this path is not permitted")
if not p.is_dir():
raise HTTPException(404, "Not a directory")
try:
entries = sorted(
[d.name for d in p.iterdir() if d.is_dir() and not d.name.startswith(".")],
key=str.lower,
)
except PermissionError:
raise HTTPException(403, "Permission denied")
parent = str(p.parent) if p.parent != p else None
return {"path": str(p), "parent": parent, "dirs": entries}
@router.get("/api/version")
async def get_version():
return {"version": __version__}
@router.get("/api/changelog", response_class=PlainTextResponse)
async def get_changelog():
cl = Path(__file__).parent.parent / "CHANGELOG.md"
if not cl.is_file():
raise HTTPException(404, "CHANGELOG.md not found")
return cl.read_text(encoding="utf-8")
@router.get("/robots.txt", response_class=PlainTextResponse)
async def robots_txt():
return "User-agent: *\nDisallow: /"