Parity with the hosted omnivoice.app describe field, implemented fully locally: a deterministic, ordered synonym-table mapper (no model, no network, stdlib only) projects a natural-language description onto the existing six-category design space (Gender/Age/Pitch/Style/EnglishAccent/ ChineseDialect). Every emitted token is validated at import time against the engine taxonomy, so the mapper can never produce an instruct item the engine validator would reject; Chinese token forms are derived from the taxonomy, never hardcoded (the one functional pinyin->dialect mapping is allowlisted in test_no_hardcoded_cjk.py with justification). UI: a describe textarea in the Design tab fills the attribute picker live (hand-tuning still possible afterwards); parts of the description the taxonomy can't express are listed back to the user as 'ignored' instead of failing silently. New i18n keys in all 21 locales. Fixes #317 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
36 lines
1.1 KiB
Python
36 lines
1.1 KiB
Python
"""Voice-design "describe your voice" API (issue #317).
|
|
|
|
Maps a free-text voice description onto the existing design parameter space
|
|
via the deterministic keyword mapper in ``core.describe_voice``. Pure CPU +
|
|
stdlib — no model, no network — so it imports and responds instantly in any
|
|
environment, including test/CI without model weights.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter
|
|
from pydantic import BaseModel, Field
|
|
|
|
from core.describe_voice import parse_description
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class DescribeRequest(BaseModel):
|
|
description: str = Field(default="", max_length=2000)
|
|
|
|
|
|
@router.post("/design/describe")
|
|
def describe_voice(req: DescribeRequest) -> dict:
|
|
"""Parse a free-text description into design attrs + a validator-safe instruct.
|
|
|
|
Response shape::
|
|
|
|
{
|
|
"attrs": {"Gender": "female", "Age": "elderly", ... or "Auto"},
|
|
"instruct": "female, elderly, low pitch, british accent",
|
|
"matched": [{"category": "Age", "token": "elderly", "phrase": "elderly"}, ...],
|
|
"unmatched": ["slightly raspy"]
|
|
}
|
|
"""
|
|
return parse_description(req.description)
|