diff --git a/.env.example b/.env.example index 4be537c..3a2c720 100644 --- a/.env.example +++ b/.env.example @@ -29,7 +29,7 @@ ABOGEN_GID=1000 # Optional: Seed the web UI with working defaults for the LLM-powered # text normalization features. Leave these blank to configure everything # from the Settings page. -ABOGEN_LLM_BASE_URL=https://localhost:11434/v1 +ABOGEN_LLM_BASE_URL=http://localhost:11434 # Supply the server root; /v1 is added automatically. ABOGEN_LLM_API_KEY=ollama ABOGEN_LLM_MODEL=llama3.1:8b ABOGEN_LLM_TIMEOUT=45 diff --git a/README.md b/README.md index fbf57b9..881fe2e 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Multiple jobs can run sequentially; the worker processes them in order. ## LLM-assisted text normalization Abogen can hand tricky apostrophes and contractions to an OpenAI-compatible large language model. Configure it from **Settings → LLM**: -1. Enter the base URL for your endpoint (Ollama, OpenAI proxy, etc.) and an API key if required. +1. Enter the base URL for your endpoint (Ollama, OpenAI proxy, etc.) and an API key if required. Use the server root (for Ollama: `http://localhost:11434`)—Abogen appends `/v1/...` automatically, but it also accepts inputs that already end in `/v1`. 2. Click **Refresh models** to load the catalog, pick a default model, and adjust the timeout or prompt template. 3. Use the preview box to test the prompt, then save the settings. The Normalization panel can synthesize a short audio preview with the current configuration. diff --git a/abogen/llm_client.py b/abogen/llm_client.py index 1855a5d..c5f92bb 100644 --- a/abogen/llm_client.py +++ b/abogen/llm_client.py @@ -38,7 +38,11 @@ def _normalized_base_url(base_url: str) -> str: def _build_url(base_url: str, path: str) -> str: normalized = _normalized_base_url(base_url) - return parse.urljoin(normalized, path.lstrip("/")) + trimmed_path = path.lstrip("/") + parsed = parse.urlparse(normalized) + if parsed.path.rstrip("/").lower().endswith("/v1") and trimmed_path.startswith("v1/"): + trimmed_path = trimmed_path[len("v1/"):] + return parse.urljoin(normalized, trimmed_path) def _build_headers(api_key: str) -> Dict[str, str]: diff --git a/abogen/web/static/settings.js b/abogen/web/static/settings.js index 826a94e..562323b 100644 --- a/abogen/web/static/settings.js +++ b/abogen/web/static/settings.js @@ -125,16 +125,36 @@ function updateModelOptions(models) { return; } const fragment = document.createDocumentFragment(); - models.forEach((modelName) => { + let matchedCurrent = false; + models.forEach((entry) => { + let identifier = ''; + let label = ''; + if (typeof entry === 'string') { + identifier = entry; + label = entry; + } else if (entry && typeof entry === 'object') { + identifier = String(entry.id || entry.name || entry.label || '').trim(); + label = String(entry.label || entry.name || identifier || '').trim(); + } + if (!identifier) { + return; + } + if (!label) { + label = identifier; + } const option = document.createElement('option'); - option.value = modelName; - option.textContent = modelName; - if (modelName === current) { + option.value = identifier; + option.textContent = label; + if (identifier === current) { option.selected = true; + matchedCurrent = true; } fragment.appendChild(option); }); select.appendChild(fragment); + if (!matchedCurrent && select.options.length) { + select.selectedIndex = 0; + } select.dataset.currentModel = select.value || ''; select.disabled = false; }