mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-07-16 20:50:18 +00:00
* stage-3097: advanced model options (re-push fixes both no-ops), rebased onto master * Release PA: advanced per-model options + auxiliary model routing (#3097) ai-ag2026's #3097, re-pushed to fix both prior silent no-ops: - base_url no longer clobbered (advanced applied after resolver write) - extra_body now reaches the agent via request_overrides (+ cache-key); main modal hides unsupported timing fields (aux-only). Functionally verified live (gear→modal→save→persists). Codex SAFE + Opus SHIP + suite green. Note: also adds 3 aux task slots (kanban_decomposer/profile_describer/triage_specifier). Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com> --------- Co-authored-by: nesquena-hermes <agent@nesquena-hermes> Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
This commit is contained in:
+5
-1
@@ -3,7 +3,11 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.51.439] — 2026-06-15 — Release OZ (Insights dashboard polish)
|
||||
## [v0.51.440] — 2026-06-15 — Release PA (advanced per-model options + auxiliary model routing)
|
||||
|
||||
### Added
|
||||
|
||||
- **Advanced model options and auxiliary model routing in Settings → Preferences.** A gear button next to the main model selector opens a "Main model options" dialog (Base URL override, Extra body JSON merged into the request, API key override — all the fields that actually take effect). An "Auxiliary Models" section lets you route side-tasks (vision, compression, web-extract, session-search, approval, MCP, title-generation, skills-hub, curator, kanban-decomposer, profile-describer, triage-specifier) to specific models/providers, each with its own advanced options (including per-task timeout / download-timeout / max-concurrency). API keys are write-only (never echoed back), `extra_body` must be a valid JSON object (malformed input is rejected with a 400), and the main model's `extra_body` is now passed through to the agent at request time via `request_overrides`. (#3097)
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
+113
-3
@@ -3056,7 +3056,78 @@ def set_reasoning_effort(
|
||||
)
|
||||
|
||||
|
||||
def set_hermes_default_model(model_id: str) -> dict:
|
||||
def _public_advanced_model_options(model_cfg: dict) -> dict:
|
||||
"""Return write-only-safe advanced options from a model config block."""
|
||||
if not isinstance(model_cfg, dict):
|
||||
model_cfg = {}
|
||||
return {
|
||||
"base_url": str(model_cfg.get("base_url") or "").strip(),
|
||||
"timeout": model_cfg.get("timeout", ""),
|
||||
"download_timeout": model_cfg.get("download_timeout", ""),
|
||||
"max_concurrency": model_cfg.get("max_concurrency", ""),
|
||||
"extra_body": model_cfg.get("extra_body") if isinstance(model_cfg.get("extra_body"), dict) else {},
|
||||
"api_key_set": bool(str(model_cfg.get("api_key") or "").strip()),
|
||||
}
|
||||
|
||||
|
||||
def _main_model_request_overrides(config_data: dict) -> dict:
|
||||
"""Return supported runtime request overrides for the main chat model."""
|
||||
if not isinstance(config_data, dict):
|
||||
return {}
|
||||
model_cfg = config_data.get("model", {})
|
||||
if not isinstance(model_cfg, dict):
|
||||
return {}
|
||||
overrides = {}
|
||||
extra_body = model_cfg.get("extra_body")
|
||||
if isinstance(extra_body, dict) and extra_body:
|
||||
overrides["extra_body"] = copy.deepcopy(extra_body)
|
||||
return overrides
|
||||
|
||||
|
||||
def _apply_advanced_model_options(model_cfg: dict, advanced: dict | None) -> None:
|
||||
"""Apply supported advanced model options to a config block in-place."""
|
||||
if advanced is None:
|
||||
return
|
||||
if not isinstance(advanced, dict):
|
||||
raise ValueError("advanced model options must be an object")
|
||||
if "base_url" in advanced:
|
||||
base_url = str(advanced.get("base_url") or "").strip().rstrip("/")
|
||||
if base_url:
|
||||
model_cfg["base_url"] = base_url
|
||||
else:
|
||||
model_cfg.pop("base_url", None)
|
||||
for field in ("timeout", "download_timeout", "max_concurrency"):
|
||||
if field in advanced:
|
||||
coerced = _coerce_optional_positive_int(advanced.get(field), field)
|
||||
if coerced == "":
|
||||
model_cfg.pop(field, None)
|
||||
elif coerced is not None:
|
||||
model_cfg[field] = coerced
|
||||
if "extra_body" in advanced:
|
||||
extra_body = advanced.get("extra_body")
|
||||
if isinstance(extra_body, str):
|
||||
text = extra_body.strip()
|
||||
try:
|
||||
extra_body = json.loads(text) if text else {}
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError("extra_body must be valid JSON") from exc
|
||||
if extra_body in (None, ""):
|
||||
model_cfg.pop("extra_body", None)
|
||||
elif isinstance(extra_body, dict):
|
||||
if extra_body:
|
||||
model_cfg["extra_body"] = extra_body
|
||||
else:
|
||||
model_cfg.pop("extra_body", None)
|
||||
else:
|
||||
raise ValueError("extra_body must be a JSON object")
|
||||
if advanced.get("api_key_clear"):
|
||||
model_cfg.pop("api_key", None)
|
||||
api_key = str(advanced.get("api_key") or "").strip()
|
||||
if api_key:
|
||||
model_cfg["api_key"] = api_key
|
||||
|
||||
|
||||
def set_hermes_default_model(model_id: str, advanced: dict | None = None) -> dict:
|
||||
"""Persist the Hermes default model in config.yaml and reload runtime config."""
|
||||
selected_model = str(model_id or "").strip()
|
||||
if not selected_model:
|
||||
@@ -3107,6 +3178,8 @@ def set_hermes_default_model(model_id: str) -> dict:
|
||||
elif not persisted_provider.startswith("custom:"):
|
||||
model_cfg.pop("base_url", None)
|
||||
|
||||
_apply_advanced_model_options(model_cfg, advanced)
|
||||
|
||||
config_data["model"] = model_cfg
|
||||
_save_yaml_config_file(config_path, config_data)
|
||||
# Reload outside the lock — reload_config() acquires _cfg_lock itself.
|
||||
@@ -3133,6 +3206,9 @@ AUX_TASK_SLOTS: tuple[str, ...] = (
|
||||
"mcp",
|
||||
"title_generation",
|
||||
"curator",
|
||||
"kanban_decomposer",
|
||||
"profile_describer",
|
||||
"triage_specifier",
|
||||
)
|
||||
|
||||
|
||||
@@ -3169,18 +3245,46 @@ def get_auxiliary_models() -> dict:
|
||||
"provider": str(entry.get("provider") or "auto").strip(),
|
||||
"model": str(entry.get("model") or "").strip(),
|
||||
"base_url": str(entry.get("base_url") or "").strip(),
|
||||
"timeout": entry.get("timeout", ""),
|
||||
"download_timeout": entry.get("download_timeout", ""),
|
||||
"max_concurrency": entry.get("max_concurrency", ""),
|
||||
"extra_body": entry.get("extra_body") if isinstance(entry.get("extra_body"), dict) else {},
|
||||
"api_key_set": bool(str(entry.get("api_key") or "").strip()),
|
||||
})
|
||||
|
||||
return {
|
||||
"tasks": tasks,
|
||||
"main": {"provider": main_provider, "model": main_model},
|
||||
"main": {
|
||||
"provider": main_provider,
|
||||
"model": main_model,
|
||||
**_public_advanced_model_options(model_cfg),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def set_auxiliary_model(task: str, provider: str, model: str) -> dict:
|
||||
def _coerce_optional_positive_int(value, field: str):
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, str):
|
||||
value = value.strip()
|
||||
if value == "":
|
||||
return ""
|
||||
try:
|
||||
number = int(value)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError(f"{field} must be a positive integer") from exc
|
||||
if number < 1:
|
||||
raise ValueError(f"{field} must be a positive integer")
|
||||
return number
|
||||
|
||||
|
||||
def set_auxiliary_model(task: str, provider: str, model: str, advanced: dict | None = None) -> dict:
|
||||
"""Persist an auxiliary model assignment in config.yaml.
|
||||
|
||||
Special case: task='__reset__' clears all auxiliary slots.
|
||||
``advanced`` may update per-slot fields surfaced behind the WebUI gear menu.
|
||||
Sensitive api_key values are write-only: get_auxiliary_models() only reports
|
||||
whether one is set.
|
||||
"""
|
||||
if task != "__reset__" and task not in AUX_TASK_SLOTS:
|
||||
raise ValueError(
|
||||
@@ -3220,6 +3324,12 @@ def set_auxiliary_model(task: str, provider: str, model: str) -> dict:
|
||||
slot_cfg["base_url"] = str(resolved_base_url).strip().rstrip("/")
|
||||
except Exception:
|
||||
pass
|
||||
if advanced is not None:
|
||||
try:
|
||||
_apply_advanced_model_options(slot_cfg, advanced)
|
||||
except ValueError as exc:
|
||||
msg = str(exc).replace("advanced model options", "advanced auxiliary options")
|
||||
raise ValueError(msg) from exc
|
||||
aux_cfg[task] = slot_cfg
|
||||
config_data["auxiliary"] = aux_cfg
|
||||
|
||||
|
||||
+5
-3
@@ -8207,7 +8207,8 @@ def handle_post(handler, parsed) -> bool:
|
||||
|
||||
if parsed.path == "/api/default-model":
|
||||
try:
|
||||
return j(handler, set_hermes_default_model(body.get("model")))
|
||||
advanced = body.get("advanced") if isinstance(body, dict) else None
|
||||
return j(handler, set_hermes_default_model(body.get("model"), advanced=advanced))
|
||||
except ValueError as e:
|
||||
return bad(handler, str(e))
|
||||
except RuntimeError as e:
|
||||
@@ -8219,15 +8220,16 @@ def handle_post(handler, parsed) -> bool:
|
||||
task = str(body.get("task") or "").strip()
|
||||
provider = str(body.get("provider") or "auto").strip()
|
||||
model = str(body.get("model") or "").strip()
|
||||
advanced = body.get("advanced") if isinstance(body, dict) else None
|
||||
if scope == "auxiliary":
|
||||
from api.config import set_auxiliary_model
|
||||
try:
|
||||
return j(handler, set_auxiliary_model(task, provider, model))
|
||||
return j(handler, set_auxiliary_model(task, provider, model, advanced=advanced))
|
||||
except Exception as exc:
|
||||
return bad(handler, str(exc), status=400)
|
||||
if scope == "main":
|
||||
try:
|
||||
return j(handler, set_hermes_default_model(model))
|
||||
return j(handler, set_hermes_default_model(model, advanced=advanced))
|
||||
except ValueError as exc:
|
||||
return bad(handler, str(exc), status=400)
|
||||
return bad(handler, f"unknown scope: {scope}", status=400)
|
||||
|
||||
@@ -38,6 +38,7 @@ from api.config import (
|
||||
load_settings,
|
||||
parse_reasoning_effort,
|
||||
coerce_reasoning_effort_for_model,
|
||||
_main_model_request_overrides,
|
||||
)
|
||||
from api.helpers import redact_session_data, _redact_text
|
||||
from api.compression_anchor import is_context_compression_marker, visible_messages_for_anchor
|
||||
@@ -6189,6 +6190,7 @@ def _run_agent_streaming(
|
||||
_prefill_context = _load_webui_prefill_context(_cfg)
|
||||
_prefill_messages = _prefill_messages_with_webui_context(_prefill_context, _cfg)
|
||||
_prefill_messages = _normalize_prefill_messages_before_user_turn(_prefill_messages)
|
||||
_main_request_overrides = _main_model_request_overrides(_cfg)
|
||||
put('context_status', {
|
||||
'session_id': session_id,
|
||||
'prefill': _public_prefill_context_status(_prefill_context),
|
||||
@@ -6370,6 +6372,8 @@ def _run_agent_streaming(
|
||||
_agent_kwargs['max_iterations'] = _max_iterations_cfg
|
||||
if 'max_tokens' in _agent_params and _max_tokens_cfg is not None:
|
||||
_agent_kwargs['max_tokens'] = _max_tokens_cfg
|
||||
if 'request_overrides' in _agent_params and _main_request_overrides:
|
||||
_agent_kwargs['request_overrides'] = _main_request_overrides
|
||||
# Params added in newer hermes-agent — skip if not supported
|
||||
if 'api_mode' in _agent_params:
|
||||
_agent_kwargs['api_mode'] = _rt.get('api_mode')
|
||||
@@ -6411,6 +6415,7 @@ def _run_agent_streaming(
|
||||
_fallback_resolved or {},
|
||||
sorted(_toolsets) if _toolsets else [],
|
||||
_reasoning_config or {},
|
||||
_main_request_overrides or {},
|
||||
_public_prefill_context_status(_prefill_context),
|
||||
# #1897: profile_home is part of the agent's identity because
|
||||
# AIAgent caches `_cached_system_prompt` from `load_soul_md()`
|
||||
|
||||
+678
@@ -609,6 +609,11 @@ const LOCALES = {
|
||||
settings_save_btn: 'Save Settings',
|
||||
settings_label_model: 'Default Model',
|
||||
settings_desc_model: 'Used for new conversations. Existing conversations keep their selected model.',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_label_auxiliary_models: 'Auxiliary Models',
|
||||
settings_desc_auxiliary_models: 'Side-task routing for vision, compression, title generation, etc. "Auto" uses your main chat model.',
|
||||
settings_btn_reset_aux_models: 'Reset all to auto',
|
||||
@@ -625,6 +630,53 @@ const LOCALES = {
|
||||
settings_aux_save_failed: 'Failed to save auxiliary model',
|
||||
settings_aux_saved: 'Auxiliary models updated',
|
||||
settings_aux_no_changes: 'No changes to apply',
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'image/screenshot analysis',
|
||||
settings_aux_task_compression: 'Compression',
|
||||
settings_aux_task_compression_desc: 'context summarization',
|
||||
settings_aux_task_web_extract: 'Web extract',
|
||||
settings_aux_task_web_extract_desc: 'web page summarization',
|
||||
settings_aux_task_session_search: 'Session search',
|
||||
settings_aux_task_session_search_desc: 'past-conversation recall',
|
||||
settings_aux_task_approval: 'Approval',
|
||||
settings_aux_task_approval_desc: 'smart command approval',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP tool reasoning',
|
||||
settings_aux_task_title_generation: 'Title generation',
|
||||
settings_aux_task_title_generation_desc: 'session titles',
|
||||
settings_aux_task_skills_hub: 'Skills hub',
|
||||
settings_aux_task_skills_hub_desc: 'skills search/install',
|
||||
settings_aux_task_curator: 'Curator',
|
||||
settings_aux_task_curator_desc: 'skill-usage review pass',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban decomposer',
|
||||
settings_aux_task_kanban_decomposer_desc: 'task decomposition',
|
||||
settings_aux_task_profile_describer: 'Profile describer',
|
||||
settings_aux_task_profile_describer_desc: 'profile summaries',
|
||||
settings_aux_task_triage_specifier: 'Triage specifier',
|
||||
settings_aux_task_triage_specifier_desc: 'issue/task triage specs',
|
||||
settings_aux_advanced_button_title: 'Advanced options',
|
||||
settings_aux_advanced_button_aria: 'Advanced options for {task}',
|
||||
settings_aux_advanced_title: '{task} options',
|
||||
settings_aux_advanced_subtitle: 'Advanced config for auxiliary.',
|
||||
settings_aux_advanced_save: 'Save options',
|
||||
settings_aux_advanced_base_url: 'Base URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optional provider endpoint override.',
|
||||
settings_aux_advanced_timeout: 'Timeout seconds',
|
||||
settings_aux_advanced_timeout_desc: 'Request timeout for this auxiliary task. Blank uses Hermes default.',
|
||||
settings_aux_advanced_download_timeout: 'Download timeout seconds',
|
||||
settings_aux_advanced_download_timeout_desc: 'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.',
|
||||
settings_aux_advanced_max_concurrency: 'Max concurrency',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optional per-task concurrency limit. Blank uses default.',
|
||||
settings_aux_advanced_extra_body: 'Extra body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optional JSON object merged into the model request body.',
|
||||
settings_aux_advanced_api_key: 'API key override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API key is set. Leave blank to keep it, or use clear to remove it.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leave blank to use provider/default credentials.',
|
||||
settings_aux_advanced_api_key_clear: 'Clear existing API key override',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra body must be valid JSON',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra body must be a JSON object',
|
||||
settings_aux_advanced_saved: 'Auxiliary options saved',
|
||||
settings_aux_advanced_save_failed: 'Failed to save auxiliary options',
|
||||
settings_label_send_key: 'Send Key',
|
||||
settings_label_theme: 'Theme',
|
||||
settings_label_skin: 'Skin',
|
||||
@@ -2035,6 +2087,11 @@ const LOCALES = {
|
||||
settings_save_btn: 'Salva Impostazioni',
|
||||
settings_label_model: 'Modello Predefinito',
|
||||
settings_desc_model: 'Usato per le nuove conversazioni. Le conversazioni esistenti mantengono il modello selezionato.',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_label_auxiliary_models: 'Modelli Ausiliari',
|
||||
settings_desc_auxiliary_models: 'Routing per attività secondarie come visione, compressione, generazione titoli, ecc. "Auto" utilizza il modello chat principale.',
|
||||
settings_btn_reset_aux_models: 'Ripristina tutto ad auto',
|
||||
@@ -2051,6 +2108,53 @@ const LOCALES = {
|
||||
settings_aux_save_failed: 'Salvataggio del modello ausiliario non riuscito',
|
||||
settings_aux_saved: 'Modelli ausiliari aggiornati',
|
||||
settings_aux_no_changes: 'Nessuna modifica da applicare',
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'image/screenshot analysis',
|
||||
settings_aux_task_compression: 'Compression',
|
||||
settings_aux_task_compression_desc: 'context summarization',
|
||||
settings_aux_task_web_extract: 'Web extract',
|
||||
settings_aux_task_web_extract_desc: 'web page summarization',
|
||||
settings_aux_task_session_search: 'Session search',
|
||||
settings_aux_task_session_search_desc: 'past-conversation recall',
|
||||
settings_aux_task_approval: 'Approval',
|
||||
settings_aux_task_approval_desc: 'smart command approval',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP tool reasoning',
|
||||
settings_aux_task_title_generation: 'Title generation',
|
||||
settings_aux_task_title_generation_desc: 'session titles',
|
||||
settings_aux_task_skills_hub: 'Skills hub',
|
||||
settings_aux_task_skills_hub_desc: 'skills search/install',
|
||||
settings_aux_task_curator: 'Curator',
|
||||
settings_aux_task_curator_desc: 'skill-usage review pass',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban decomposer',
|
||||
settings_aux_task_kanban_decomposer_desc: 'task decomposition',
|
||||
settings_aux_task_profile_describer: 'Profile describer',
|
||||
settings_aux_task_profile_describer_desc: 'profile summaries',
|
||||
settings_aux_task_triage_specifier: 'Triage specifier',
|
||||
settings_aux_task_triage_specifier_desc: 'issue/task triage specs',
|
||||
settings_aux_advanced_button_title: 'Advanced options',
|
||||
settings_aux_advanced_button_aria: 'Advanced options for {task}',
|
||||
settings_aux_advanced_title: '{task} options',
|
||||
settings_aux_advanced_subtitle: 'Advanced config for auxiliary.',
|
||||
settings_aux_advanced_save: 'Save options',
|
||||
settings_aux_advanced_base_url: 'Base URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optional provider endpoint override.',
|
||||
settings_aux_advanced_timeout: 'Timeout seconds',
|
||||
settings_aux_advanced_timeout_desc: 'Request timeout for this auxiliary task. Blank uses Hermes default.',
|
||||
settings_aux_advanced_download_timeout: 'Download timeout seconds',
|
||||
settings_aux_advanced_download_timeout_desc: 'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.',
|
||||
settings_aux_advanced_max_concurrency: 'Max concurrency',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optional per-task concurrency limit. Blank uses default.',
|
||||
settings_aux_advanced_extra_body: 'Extra body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optional JSON object merged into the model request body.',
|
||||
settings_aux_advanced_api_key: 'API key override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API key is set. Leave blank to keep it, or use clear to remove it.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leave blank to use provider/default credentials.',
|
||||
settings_aux_advanced_api_key_clear: 'Clear existing API key override',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra body must be valid JSON',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra body must be a JSON object',
|
||||
settings_aux_advanced_saved: 'Auxiliary options saved',
|
||||
settings_aux_advanced_save_failed: 'Failed to save auxiliary options',
|
||||
settings_label_send_key: 'Tasto Invio',
|
||||
settings_label_theme: 'Tema',
|
||||
settings_label_skin: 'Skin',
|
||||
@@ -3452,6 +3556,11 @@ const LOCALES = {
|
||||
settings_save_btn: '設定を保存',
|
||||
settings_label_model: 'デフォルトモデル',
|
||||
settings_desc_model: '新しい会話で使用されます。既存の会話は選択済みモデルを保持します。',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_label_auxiliary_models: '補助モデル',
|
||||
settings_desc_auxiliary_models: 'ビジョン、圧縮、タイトル生成などの補助タスクのルーティング。「自動」はメインチャットモデルを使用します。',
|
||||
settings_btn_reset_aux_models: 'すべて自動にリセット',
|
||||
@@ -3468,6 +3577,53 @@ const LOCALES = {
|
||||
settings_aux_save_failed: '補助モデルの保存に失敗しました',
|
||||
settings_aux_saved: '補助モデルを更新しました',
|
||||
settings_aux_no_changes: '適用する変更はありません',
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'image/screenshot analysis',
|
||||
settings_aux_task_compression: 'Compression',
|
||||
settings_aux_task_compression_desc: 'context summarization',
|
||||
settings_aux_task_web_extract: 'Web extract',
|
||||
settings_aux_task_web_extract_desc: 'web page summarization',
|
||||
settings_aux_task_session_search: 'Session search',
|
||||
settings_aux_task_session_search_desc: 'past-conversation recall',
|
||||
settings_aux_task_approval: 'Approval',
|
||||
settings_aux_task_approval_desc: 'smart command approval',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP tool reasoning',
|
||||
settings_aux_task_title_generation: 'Title generation',
|
||||
settings_aux_task_title_generation_desc: 'session titles',
|
||||
settings_aux_task_skills_hub: 'Skills hub',
|
||||
settings_aux_task_skills_hub_desc: 'skills search/install',
|
||||
settings_aux_task_curator: 'Curator',
|
||||
settings_aux_task_curator_desc: 'skill-usage review pass',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban decomposer',
|
||||
settings_aux_task_kanban_decomposer_desc: 'task decomposition',
|
||||
settings_aux_task_profile_describer: 'Profile describer',
|
||||
settings_aux_task_profile_describer_desc: 'profile summaries',
|
||||
settings_aux_task_triage_specifier: 'Triage specifier',
|
||||
settings_aux_task_triage_specifier_desc: 'issue/task triage specs',
|
||||
settings_aux_advanced_button_title: 'Advanced options',
|
||||
settings_aux_advanced_button_aria: 'Advanced options for {task}',
|
||||
settings_aux_advanced_title: '{task} options',
|
||||
settings_aux_advanced_subtitle: 'Advanced config for auxiliary.',
|
||||
settings_aux_advanced_save: 'Save options',
|
||||
settings_aux_advanced_base_url: 'Base URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optional provider endpoint override.',
|
||||
settings_aux_advanced_timeout: 'Timeout seconds',
|
||||
settings_aux_advanced_timeout_desc: 'Request timeout for this auxiliary task. Blank uses Hermes default.',
|
||||
settings_aux_advanced_download_timeout: 'Download timeout seconds',
|
||||
settings_aux_advanced_download_timeout_desc: 'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.',
|
||||
settings_aux_advanced_max_concurrency: 'Max concurrency',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optional per-task concurrency limit. Blank uses default.',
|
||||
settings_aux_advanced_extra_body: 'Extra body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optional JSON object merged into the model request body.',
|
||||
settings_aux_advanced_api_key: 'API key override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API key is set. Leave blank to keep it, or use clear to remove it.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leave blank to use provider/default credentials.',
|
||||
settings_aux_advanced_api_key_clear: 'Clear existing API key override',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra body must be valid JSON',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra body must be a JSON object',
|
||||
settings_aux_advanced_saved: 'Auxiliary options saved',
|
||||
settings_aux_advanced_save_failed: 'Failed to save auxiliary options',
|
||||
settings_label_send_key: '送信キー',
|
||||
settings_label_theme: 'テーマ',
|
||||
settings_label_skin: 'スキン',
|
||||
@@ -4661,6 +4817,11 @@ const LOCALES = {
|
||||
settings_save_btn: 'Сохранить настройки',
|
||||
settings_label_model: 'Модель по умолчанию',
|
||||
settings_desc_model: 'Используется для новых бесед. Существующие беседы сохраняют выбранную модель.',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_label_auxiliary_models: 'Вспомогательные модели',
|
||||
settings_desc_auxiliary_models: 'Маршрутизация побочных задач: зрение, сжатие, генерация заголовков и т.д. «Авто» использует основную модель чата.',
|
||||
settings_btn_reset_aux_models: 'Сбросить всё на авто',
|
||||
@@ -4677,6 +4838,53 @@ const LOCALES = {
|
||||
settings_aux_save_failed: 'Не удалось сохранить вспомогательную модель',
|
||||
settings_aux_saved: 'Вспомогательные модели обновлены',
|
||||
settings_aux_no_changes: 'Нет изменений для применения',
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'image/screenshot analysis',
|
||||
settings_aux_task_compression: 'Compression',
|
||||
settings_aux_task_compression_desc: 'context summarization',
|
||||
settings_aux_task_web_extract: 'Web extract',
|
||||
settings_aux_task_web_extract_desc: 'web page summarization',
|
||||
settings_aux_task_session_search: 'Session search',
|
||||
settings_aux_task_session_search_desc: 'past-conversation recall',
|
||||
settings_aux_task_approval: 'Approval',
|
||||
settings_aux_task_approval_desc: 'smart command approval',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP tool reasoning',
|
||||
settings_aux_task_title_generation: 'Title generation',
|
||||
settings_aux_task_title_generation_desc: 'session titles',
|
||||
settings_aux_task_skills_hub: 'Skills hub',
|
||||
settings_aux_task_skills_hub_desc: 'skills search/install',
|
||||
settings_aux_task_curator: 'Curator',
|
||||
settings_aux_task_curator_desc: 'skill-usage review pass',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban decomposer',
|
||||
settings_aux_task_kanban_decomposer_desc: 'task decomposition',
|
||||
settings_aux_task_profile_describer: 'Profile describer',
|
||||
settings_aux_task_profile_describer_desc: 'profile summaries',
|
||||
settings_aux_task_triage_specifier: 'Triage specifier',
|
||||
settings_aux_task_triage_specifier_desc: 'issue/task triage specs',
|
||||
settings_aux_advanced_button_title: 'Advanced options',
|
||||
settings_aux_advanced_button_aria: 'Advanced options for {task}',
|
||||
settings_aux_advanced_title: '{task} options',
|
||||
settings_aux_advanced_subtitle: 'Advanced config for auxiliary.',
|
||||
settings_aux_advanced_save: 'Save options',
|
||||
settings_aux_advanced_base_url: 'Base URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optional provider endpoint override.',
|
||||
settings_aux_advanced_timeout: 'Timeout seconds',
|
||||
settings_aux_advanced_timeout_desc: 'Request timeout for this auxiliary task. Blank uses Hermes default.',
|
||||
settings_aux_advanced_download_timeout: 'Download timeout seconds',
|
||||
settings_aux_advanced_download_timeout_desc: 'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.',
|
||||
settings_aux_advanced_max_concurrency: 'Max concurrency',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optional per-task concurrency limit. Blank uses default.',
|
||||
settings_aux_advanced_extra_body: 'Extra body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optional JSON object merged into the model request body.',
|
||||
settings_aux_advanced_api_key: 'API key override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API key is set. Leave blank to keep it, or use clear to remove it.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leave blank to use provider/default credentials.',
|
||||
settings_aux_advanced_api_key_clear: 'Clear existing API key override',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra body must be valid JSON',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra body must be a JSON object',
|
||||
settings_aux_advanced_saved: 'Auxiliary options saved',
|
||||
settings_aux_advanced_save_failed: 'Failed to save auxiliary options',
|
||||
settings_label_send_key: 'Клавиша отправки',
|
||||
settings_label_theme: 'Тема',
|
||||
settings_label_language: 'Язык',
|
||||
@@ -5995,6 +6203,11 @@ const LOCALES = {
|
||||
settings_save_btn: 'Guardar configuración',
|
||||
settings_label_model: 'Modelo predeterminado',
|
||||
settings_desc_model: 'Se usa para conversaciones nuevas. Las conversaciones existentes conservan su modelo seleccionado.',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_label_auxiliary_models: 'Modelos auxiliares',
|
||||
settings_desc_auxiliary_models: 'Enrutamiento para tareas secundarias como visión, compresión, generación de títulos, etc. «Auto» usa el modelo de chat principal.',
|
||||
settings_btn_reset_aux_models: 'Restablecer todo a auto',
|
||||
@@ -6011,6 +6224,53 @@ const LOCALES = {
|
||||
settings_aux_save_failed: 'Error al guardar el modelo auxiliar',
|
||||
settings_aux_saved: 'Modelos auxiliares actualizados',
|
||||
settings_aux_no_changes: 'Sin cambios para aplicar',
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'image/screenshot analysis',
|
||||
settings_aux_task_compression: 'Compression',
|
||||
settings_aux_task_compression_desc: 'context summarization',
|
||||
settings_aux_task_web_extract: 'Web extract',
|
||||
settings_aux_task_web_extract_desc: 'web page summarization',
|
||||
settings_aux_task_session_search: 'Session search',
|
||||
settings_aux_task_session_search_desc: 'past-conversation recall',
|
||||
settings_aux_task_approval: 'Approval',
|
||||
settings_aux_task_approval_desc: 'smart command approval',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP tool reasoning',
|
||||
settings_aux_task_title_generation: 'Title generation',
|
||||
settings_aux_task_title_generation_desc: 'session titles',
|
||||
settings_aux_task_skills_hub: 'Skills hub',
|
||||
settings_aux_task_skills_hub_desc: 'skills search/install',
|
||||
settings_aux_task_curator: 'Curator',
|
||||
settings_aux_task_curator_desc: 'skill-usage review pass',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban decomposer',
|
||||
settings_aux_task_kanban_decomposer_desc: 'task decomposition',
|
||||
settings_aux_task_profile_describer: 'Profile describer',
|
||||
settings_aux_task_profile_describer_desc: 'profile summaries',
|
||||
settings_aux_task_triage_specifier: 'Triage specifier',
|
||||
settings_aux_task_triage_specifier_desc: 'issue/task triage specs',
|
||||
settings_aux_advanced_button_title: 'Advanced options',
|
||||
settings_aux_advanced_button_aria: 'Advanced options for {task}',
|
||||
settings_aux_advanced_title: '{task} options',
|
||||
settings_aux_advanced_subtitle: 'Advanced config for auxiliary.',
|
||||
settings_aux_advanced_save: 'Save options',
|
||||
settings_aux_advanced_base_url: 'Base URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optional provider endpoint override.',
|
||||
settings_aux_advanced_timeout: 'Timeout seconds',
|
||||
settings_aux_advanced_timeout_desc: 'Request timeout for this auxiliary task. Blank uses Hermes default.',
|
||||
settings_aux_advanced_download_timeout: 'Download timeout seconds',
|
||||
settings_aux_advanced_download_timeout_desc: 'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.',
|
||||
settings_aux_advanced_max_concurrency: 'Max concurrency',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optional per-task concurrency limit. Blank uses default.',
|
||||
settings_aux_advanced_extra_body: 'Extra body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optional JSON object merged into the model request body.',
|
||||
settings_aux_advanced_api_key: 'API key override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API key is set. Leave blank to keep it, or use clear to remove it.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leave blank to use provider/default credentials.',
|
||||
settings_aux_advanced_api_key_clear: 'Clear existing API key override',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra body must be valid JSON',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra body must be a JSON object',
|
||||
settings_aux_advanced_saved: 'Auxiliary options saved',
|
||||
settings_aux_advanced_save_failed: 'Failed to save auxiliary options',
|
||||
settings_label_send_key: 'Tecla de envío',
|
||||
settings_label_theme: 'Tema',
|
||||
settings_label_skin: 'Piel',
|
||||
@@ -7333,6 +7593,11 @@ const LOCALES = {
|
||||
settings_save_btn: 'Einstellungen speichern',
|
||||
settings_label_model: 'Standard-Modell',
|
||||
settings_desc_model: 'Wird für neue Chats verwendet. Bestehende Chats behalten ihr ausgewähltes Modell.',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_label_auxiliary_models: 'Hilfsmodelle',
|
||||
settings_desc_auxiliary_models: 'Routing für Nebenaufgaben wie Vision, Komprimierung, Titelgenerierung usw. „Auto" verwendet das Haupt-Chat-Modell.',
|
||||
settings_btn_reset_aux_models: 'Alle auf auto zurücksetzen',
|
||||
@@ -7349,6 +7614,53 @@ const LOCALES = {
|
||||
settings_aux_save_failed: 'Hilfsmodell konnte nicht gespeichert werden',
|
||||
settings_aux_saved: 'Hilfsmodelle aktualisiert',
|
||||
settings_aux_no_changes: 'Keine Änderungen anzuwenden',
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'Bild-/Screenshot-Analyse',
|
||||
settings_aux_task_compression: 'Komprimierung',
|
||||
settings_aux_task_compression_desc: 'Kontext-Zusammenfassung',
|
||||
settings_aux_task_web_extract: 'Web-Extraktion',
|
||||
settings_aux_task_web_extract_desc: 'Webseiten-Zusammenfassung',
|
||||
settings_aux_task_session_search: 'Sitzungssuche',
|
||||
settings_aux_task_session_search_desc: 'Erinnerung an frühere Gespräche',
|
||||
settings_aux_task_approval: 'Freigabe',
|
||||
settings_aux_task_approval_desc: 'intelligente Befehlsfreigabe',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP-Tool-Reasoning',
|
||||
settings_aux_task_title_generation: 'Titelgenerierung',
|
||||
settings_aux_task_title_generation_desc: 'Sitzungstitel',
|
||||
settings_aux_task_skills_hub: 'Skills Hub',
|
||||
settings_aux_task_skills_hub_desc: 'Skill-Suche/-Installation',
|
||||
settings_aux_task_curator: 'Kurator',
|
||||
settings_aux_task_curator_desc: 'Skill-Nutzungsprüfung',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban-Zerleger',
|
||||
settings_aux_task_kanban_decomposer_desc: 'Aufgabenzerlegung',
|
||||
settings_aux_task_profile_describer: 'Profilbeschreiber',
|
||||
settings_aux_task_profile_describer_desc: 'Profil-Zusammenfassungen',
|
||||
settings_aux_task_triage_specifier: 'Triage-Spezifizierer',
|
||||
settings_aux_task_triage_specifier_desc: 'Issue-/Aufgaben-Triage-Spezifikationen',
|
||||
settings_aux_advanced_button_title: 'Erweiterte Optionen',
|
||||
settings_aux_advanced_button_aria: 'Erweiterte Optionen für {task}',
|
||||
settings_aux_advanced_title: '{task}-Optionen',
|
||||
settings_aux_advanced_subtitle: 'Erweiterte Konfiguration für diese Hilfsaufgabe.',
|
||||
settings_aux_advanced_save: 'Optionen speichern',
|
||||
settings_aux_advanced_base_url: 'Basis-URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optionaler Provider-Endpunkt-Override.',
|
||||
settings_aux_advanced_timeout: 'Timeout in Sekunden',
|
||||
settings_aux_advanced_timeout_desc: 'Request-Timeout für diese Hilfsaufgabe. Leer nutzt den Hermes-Standard.',
|
||||
settings_aux_advanced_download_timeout: 'Download-Timeout in Sekunden',
|
||||
settings_aux_advanced_download_timeout_desc: 'Nur relevant für Aufgaben, die Medien/Inhalte herunterladen, z. B. Vision. Leer nutzt den Standard.',
|
||||
settings_aux_advanced_max_concurrency: 'Maximale Parallelität',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optionale Parallelitätsgrenze pro Aufgabe. Leer nutzt den Standard.',
|
||||
settings_aux_advanced_extra_body: 'Extra-Body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optionales JSON-Objekt, das in den Model-Request-Body gemischt wird.',
|
||||
settings_aux_advanced_api_key: 'API-Key-Override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API-Key ist gesetzt. Leer lassen, um ihn zu behalten, oder Löschen aktivieren.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leer lassen, um Provider-/Standard-Zugangsdaten zu verwenden.',
|
||||
settings_aux_advanced_api_key_clear: 'Vorhandenen API-Key-Override löschen',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra-Body muss gültiges JSON sein',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra-Body muss ein JSON-Objekt sein',
|
||||
settings_aux_advanced_saved: 'Hilfsoptionen gespeichert',
|
||||
settings_aux_advanced_save_failed: 'Hilfsoptionen konnten nicht gespeichert werden',
|
||||
settings_label_send_key: 'Sende-Taste',
|
||||
settings_label_theme: 'Theme',
|
||||
settings_label_skin: 'Skin',
|
||||
@@ -8724,6 +9036,11 @@ const LOCALES = {
|
||||
settings_save_btn: '保存设置',
|
||||
settings_label_model: '默认模型',
|
||||
settings_desc_model: '用于新对话。现有对话保持各自选定的模型。',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_label_auxiliary_models: '辅助模型',
|
||||
settings_desc_auxiliary_models: '视觉分析、上下文压缩、标题生成等辅助任务的路由。"自动"表示使用主聊天模型。',
|
||||
settings_btn_reset_aux_models: '全部重置为自动',
|
||||
@@ -8740,6 +9057,53 @@ const LOCALES = {
|
||||
settings_aux_save_failed: '辅助模型保存失败',
|
||||
settings_aux_saved: '辅助模型已更新',
|
||||
settings_aux_no_changes: '没有需要应用的更改',
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'image/screenshot analysis',
|
||||
settings_aux_task_compression: 'Compression',
|
||||
settings_aux_task_compression_desc: 'context summarization',
|
||||
settings_aux_task_web_extract: 'Web extract',
|
||||
settings_aux_task_web_extract_desc: 'web page summarization',
|
||||
settings_aux_task_session_search: 'Session search',
|
||||
settings_aux_task_session_search_desc: 'past-conversation recall',
|
||||
settings_aux_task_approval: 'Approval',
|
||||
settings_aux_task_approval_desc: 'smart command approval',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP tool reasoning',
|
||||
settings_aux_task_title_generation: 'Title generation',
|
||||
settings_aux_task_title_generation_desc: 'session titles',
|
||||
settings_aux_task_skills_hub: 'Skills hub',
|
||||
settings_aux_task_skills_hub_desc: 'skills search/install',
|
||||
settings_aux_task_curator: 'Curator',
|
||||
settings_aux_task_curator_desc: 'skill-usage review pass',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban decomposer',
|
||||
settings_aux_task_kanban_decomposer_desc: 'task decomposition',
|
||||
settings_aux_task_profile_describer: 'Profile describer',
|
||||
settings_aux_task_profile_describer_desc: 'profile summaries',
|
||||
settings_aux_task_triage_specifier: 'Triage specifier',
|
||||
settings_aux_task_triage_specifier_desc: 'issue/task triage specs',
|
||||
settings_aux_advanced_button_title: 'Advanced options',
|
||||
settings_aux_advanced_button_aria: 'Advanced options for {task}',
|
||||
settings_aux_advanced_title: '{task} options',
|
||||
settings_aux_advanced_subtitle: 'Advanced config for auxiliary.',
|
||||
settings_aux_advanced_save: 'Save options',
|
||||
settings_aux_advanced_base_url: 'Base URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optional provider endpoint override.',
|
||||
settings_aux_advanced_timeout: 'Timeout seconds',
|
||||
settings_aux_advanced_timeout_desc: 'Request timeout for this auxiliary task. Blank uses Hermes default.',
|
||||
settings_aux_advanced_download_timeout: 'Download timeout seconds',
|
||||
settings_aux_advanced_download_timeout_desc: 'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.',
|
||||
settings_aux_advanced_max_concurrency: 'Max concurrency',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optional per-task concurrency limit. Blank uses default.',
|
||||
settings_aux_advanced_extra_body: 'Extra body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optional JSON object merged into the model request body.',
|
||||
settings_aux_advanced_api_key: 'API key override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API key is set. Leave blank to keep it, or use clear to remove it.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leave blank to use provider/default credentials.',
|
||||
settings_aux_advanced_api_key_clear: 'Clear existing API key override',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra body must be valid JSON',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra body must be a JSON object',
|
||||
settings_aux_advanced_saved: 'Auxiliary options saved',
|
||||
settings_aux_advanced_save_failed: 'Failed to save auxiliary options',
|
||||
settings_label_send_key: '发送快捷键',
|
||||
settings_label_theme: '主题',
|
||||
settings_label_skin: '皮肤',
|
||||
@@ -10286,6 +10650,11 @@ const LOCALES = {
|
||||
settings_save_btn: '儲存設定',
|
||||
settings_label_model: '預設模型',
|
||||
settings_desc_model: '用於新對話。現有對話會保留各自選定的模型。',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_label_auxiliary_models: '輔助模型',
|
||||
settings_desc_auxiliary_models: '視覺分析、脈絡壓縮、標題產生等輔助任務的路由。「自動」表示使用主要聊天模型。',
|
||||
settings_btn_reset_aux_models: '全部重置為自動',
|
||||
@@ -10302,8 +10671,57 @@ const LOCALES = {
|
||||
settings_aux_save_failed: '輔助模型儲存失敗',
|
||||
settings_aux_saved: '輔助模型已更新',
|
||||
settings_aux_no_changes: '沒有需要應用的變更',
|
||||
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'image/screenshot analysis',
|
||||
settings_aux_task_compression: 'Compression',
|
||||
settings_aux_task_compression_desc: 'context summarization',
|
||||
settings_aux_task_web_extract: 'Web extract',
|
||||
settings_aux_task_web_extract_desc: 'web page summarization',
|
||||
settings_aux_task_session_search: 'Session search',
|
||||
settings_aux_task_session_search_desc: 'past-conversation recall',
|
||||
settings_aux_task_approval: 'Approval',
|
||||
settings_aux_task_approval_desc: 'smart command approval',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP tool reasoning',
|
||||
settings_aux_task_title_generation: 'Title generation',
|
||||
settings_aux_task_title_generation_desc: 'session titles',
|
||||
settings_aux_task_skills_hub: 'Skills hub',
|
||||
settings_aux_task_skills_hub_desc: 'skills search/install',
|
||||
settings_aux_task_curator: 'Curator',
|
||||
settings_aux_task_curator_desc: 'skill-usage review pass',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban decomposer',
|
||||
settings_aux_task_kanban_decomposer_desc: 'task decomposition',
|
||||
settings_aux_task_profile_describer: 'Profile describer',
|
||||
settings_aux_task_profile_describer_desc: 'profile summaries',
|
||||
settings_aux_task_triage_specifier: 'Triage specifier',
|
||||
settings_aux_task_triage_specifier_desc: 'issue/task triage specs',
|
||||
settings_aux_advanced_button_title: 'Advanced options',
|
||||
settings_aux_advanced_button_aria: 'Advanced options for {task}',
|
||||
settings_aux_advanced_title: '{task} options',
|
||||
settings_aux_advanced_subtitle: 'Advanced config for auxiliary.',
|
||||
settings_aux_advanced_save: 'Save options',
|
||||
settings_aux_advanced_base_url: 'Base URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optional provider endpoint override.',
|
||||
settings_aux_advanced_timeout: 'Timeout seconds',
|
||||
settings_aux_advanced_timeout_desc: 'Request timeout for this auxiliary task. Blank uses Hermes default.',
|
||||
settings_aux_advanced_download_timeout: 'Download timeout seconds',
|
||||
settings_aux_advanced_download_timeout_desc: 'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.',
|
||||
settings_aux_advanced_max_concurrency: 'Max concurrency',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optional per-task concurrency limit. Blank uses default.',
|
||||
settings_aux_advanced_extra_body: 'Extra body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optional JSON object merged into the model request body.',
|
||||
settings_aux_advanced_api_key: 'API key override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API key is set. Leave blank to keep it, or use clear to remove it.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leave blank to use provider/default credentials.',
|
||||
settings_aux_advanced_api_key_clear: 'Clear existing API key override',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra body must be valid JSON',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra body must be a JSON object',
|
||||
settings_aux_advanced_saved: 'Auxiliary options saved',
|
||||
settings_aux_advanced_save_failed: 'Failed to save auxiliary options',
|
||||
settings_label_send_key: '送出按鍵',
|
||||
settings_label_theme: '主題',
|
||||
|
||||
settings_label_skin: '佈景',
|
||||
settings_label_font_size: '字型大小',
|
||||
font_size_small: '小',
|
||||
@@ -11593,6 +12011,11 @@ const LOCALES = {
|
||||
settings_save_btn: 'Salvar Configurações',
|
||||
settings_label_model: 'Modelo Padrão',
|
||||
settings_desc_model: 'Usado para novas conversas. Conversas existentes mantêm o modelo selecionado.',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_label_auxiliary_models: 'Modelos auxiliares',
|
||||
settings_desc_auxiliary_models: 'Roteamento para tarefas secundárias como visão, compressão, geração de títulos, etc. "Auto" usa o modelo de chat principal.',
|
||||
settings_btn_reset_aux_models: 'Restaurar tudo para auto',
|
||||
@@ -11609,6 +12032,53 @@ const LOCALES = {
|
||||
settings_aux_save_failed: 'Falha ao salvar o modelo auxiliar',
|
||||
settings_aux_saved: 'Modelos auxiliares atualizados',
|
||||
settings_aux_no_changes: 'Nenhuma alteração para aplicar',
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'image/screenshot analysis',
|
||||
settings_aux_task_compression: 'Compression',
|
||||
settings_aux_task_compression_desc: 'context summarization',
|
||||
settings_aux_task_web_extract: 'Web extract',
|
||||
settings_aux_task_web_extract_desc: 'web page summarization',
|
||||
settings_aux_task_session_search: 'Session search',
|
||||
settings_aux_task_session_search_desc: 'past-conversation recall',
|
||||
settings_aux_task_approval: 'Approval',
|
||||
settings_aux_task_approval_desc: 'smart command approval',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP tool reasoning',
|
||||
settings_aux_task_title_generation: 'Title generation',
|
||||
settings_aux_task_title_generation_desc: 'session titles',
|
||||
settings_aux_task_skills_hub: 'Skills hub',
|
||||
settings_aux_task_skills_hub_desc: 'skills search/install',
|
||||
settings_aux_task_curator: 'Curator',
|
||||
settings_aux_task_curator_desc: 'skill-usage review pass',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban decomposer',
|
||||
settings_aux_task_kanban_decomposer_desc: 'task decomposition',
|
||||
settings_aux_task_profile_describer: 'Profile describer',
|
||||
settings_aux_task_profile_describer_desc: 'profile summaries',
|
||||
settings_aux_task_triage_specifier: 'Triage specifier',
|
||||
settings_aux_task_triage_specifier_desc: 'issue/task triage specs',
|
||||
settings_aux_advanced_button_title: 'Advanced options',
|
||||
settings_aux_advanced_button_aria: 'Advanced options for {task}',
|
||||
settings_aux_advanced_title: '{task} options',
|
||||
settings_aux_advanced_subtitle: 'Advanced config for auxiliary.',
|
||||
settings_aux_advanced_save: 'Save options',
|
||||
settings_aux_advanced_base_url: 'Base URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optional provider endpoint override.',
|
||||
settings_aux_advanced_timeout: 'Timeout seconds',
|
||||
settings_aux_advanced_timeout_desc: 'Request timeout for this auxiliary task. Blank uses Hermes default.',
|
||||
settings_aux_advanced_download_timeout: 'Download timeout seconds',
|
||||
settings_aux_advanced_download_timeout_desc: 'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.',
|
||||
settings_aux_advanced_max_concurrency: 'Max concurrency',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optional per-task concurrency limit. Blank uses default.',
|
||||
settings_aux_advanced_extra_body: 'Extra body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optional JSON object merged into the model request body.',
|
||||
settings_aux_advanced_api_key: 'API key override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API key is set. Leave blank to keep it, or use clear to remove it.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leave blank to use provider/default credentials.',
|
||||
settings_aux_advanced_api_key_clear: 'Clear existing API key override',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra body must be valid JSON',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra body must be a JSON object',
|
||||
settings_aux_advanced_saved: 'Auxiliary options saved',
|
||||
settings_aux_advanced_save_failed: 'Failed to save auxiliary options',
|
||||
settings_label_send_key: 'Tecla de Envio',
|
||||
settings_label_theme: 'Tema',
|
||||
settings_label_skin: 'Skin',
|
||||
@@ -12911,6 +13381,11 @@ const LOCALES = {
|
||||
settings_save_btn: '설정 저장',
|
||||
settings_label_model: '기본 모델',
|
||||
settings_desc_model: '새 대화에 사용됩니다. 기존 대화는 선택된 모델을 유지합니다.',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_label_auxiliary_models: '보조 모델',
|
||||
settings_desc_auxiliary_models: '비전, 압축, 제목 생성 등 보조 작업 라우팅. "자동"은 기본 채팅 모델을 사용합니다.',
|
||||
settings_btn_reset_aux_models: '모두 자동으로 재설정',
|
||||
@@ -12927,6 +13402,53 @@ const LOCALES = {
|
||||
settings_aux_save_failed: '보조 모델 저장 실패',
|
||||
settings_aux_saved: '보조 모델 업데이트됨',
|
||||
settings_aux_no_changes: '적용할 변경 사항 없음',
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'image/screenshot analysis',
|
||||
settings_aux_task_compression: 'Compression',
|
||||
settings_aux_task_compression_desc: 'context summarization',
|
||||
settings_aux_task_web_extract: 'Web extract',
|
||||
settings_aux_task_web_extract_desc: 'web page summarization',
|
||||
settings_aux_task_session_search: 'Session search',
|
||||
settings_aux_task_session_search_desc: 'past-conversation recall',
|
||||
settings_aux_task_approval: 'Approval',
|
||||
settings_aux_task_approval_desc: 'smart command approval',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP tool reasoning',
|
||||
settings_aux_task_title_generation: 'Title generation',
|
||||
settings_aux_task_title_generation_desc: 'session titles',
|
||||
settings_aux_task_skills_hub: 'Skills hub',
|
||||
settings_aux_task_skills_hub_desc: 'skills search/install',
|
||||
settings_aux_task_curator: 'Curator',
|
||||
settings_aux_task_curator_desc: 'skill-usage review pass',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban decomposer',
|
||||
settings_aux_task_kanban_decomposer_desc: 'task decomposition',
|
||||
settings_aux_task_profile_describer: 'Profile describer',
|
||||
settings_aux_task_profile_describer_desc: 'profile summaries',
|
||||
settings_aux_task_triage_specifier: 'Triage specifier',
|
||||
settings_aux_task_triage_specifier_desc: 'issue/task triage specs',
|
||||
settings_aux_advanced_button_title: 'Advanced options',
|
||||
settings_aux_advanced_button_aria: 'Advanced options for {task}',
|
||||
settings_aux_advanced_title: '{task} options',
|
||||
settings_aux_advanced_subtitle: 'Advanced config for auxiliary.',
|
||||
settings_aux_advanced_save: 'Save options',
|
||||
settings_aux_advanced_base_url: 'Base URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optional provider endpoint override.',
|
||||
settings_aux_advanced_timeout: 'Timeout seconds',
|
||||
settings_aux_advanced_timeout_desc: 'Request timeout for this auxiliary task. Blank uses Hermes default.',
|
||||
settings_aux_advanced_download_timeout: 'Download timeout seconds',
|
||||
settings_aux_advanced_download_timeout_desc: 'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.',
|
||||
settings_aux_advanced_max_concurrency: 'Max concurrency',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optional per-task concurrency limit. Blank uses default.',
|
||||
settings_aux_advanced_extra_body: 'Extra body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optional JSON object merged into the model request body.',
|
||||
settings_aux_advanced_api_key: 'API key override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API key is set. Leave blank to keep it, or use clear to remove it.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leave blank to use provider/default credentials.',
|
||||
settings_aux_advanced_api_key_clear: 'Clear existing API key override',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra body must be valid JSON',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra body must be a JSON object',
|
||||
settings_aux_advanced_saved: 'Auxiliary options saved',
|
||||
settings_aux_advanced_save_failed: 'Failed to save auxiliary options',
|
||||
settings_label_send_key: '전송 키',
|
||||
settings_label_theme: '테마',
|
||||
settings_label_skin: '스킨',
|
||||
@@ -14246,6 +14768,11 @@ const LOCALES = {
|
||||
settings_save_btn: 'Enregistrer les paramètres',
|
||||
settings_label_model: 'Modèle par défaut',
|
||||
settings_desc_model: 'Utilisé pour les nouvelles conversations. Les conversations existantes conservent leur modèle sélectionné.',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_label_auxiliary_models: 'Modèles auxiliaires',
|
||||
settings_desc_auxiliary_models: 'Routage des tâches secondaires : vision, compression, génération de titres, etc. « Auto » utilise le modèle de chat principal.',
|
||||
settings_btn_reset_aux_models: 'Tout réinitialiser à auto',
|
||||
@@ -14262,6 +14789,53 @@ const LOCALES = {
|
||||
settings_aux_save_failed: 'Échec de la sauvegarde du modèle auxiliaire',
|
||||
settings_aux_saved: 'Modèles auxiliaires mis à jour',
|
||||
settings_aux_no_changes: 'Aucune modification à appliquer',
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'image/screenshot analysis',
|
||||
settings_aux_task_compression: 'Compression',
|
||||
settings_aux_task_compression_desc: 'context summarization',
|
||||
settings_aux_task_web_extract: 'Web extract',
|
||||
settings_aux_task_web_extract_desc: 'web page summarization',
|
||||
settings_aux_task_session_search: 'Session search',
|
||||
settings_aux_task_session_search_desc: 'past-conversation recall',
|
||||
settings_aux_task_approval: 'Approval',
|
||||
settings_aux_task_approval_desc: 'smart command approval',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP tool reasoning',
|
||||
settings_aux_task_title_generation: 'Title generation',
|
||||
settings_aux_task_title_generation_desc: 'session titles',
|
||||
settings_aux_task_skills_hub: 'Skills hub',
|
||||
settings_aux_task_skills_hub_desc: 'skills search/install',
|
||||
settings_aux_task_curator: 'Curator',
|
||||
settings_aux_task_curator_desc: 'skill-usage review pass',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban decomposer',
|
||||
settings_aux_task_kanban_decomposer_desc: 'task decomposition',
|
||||
settings_aux_task_profile_describer: 'Profile describer',
|
||||
settings_aux_task_profile_describer_desc: 'profile summaries',
|
||||
settings_aux_task_triage_specifier: 'Triage specifier',
|
||||
settings_aux_task_triage_specifier_desc: 'issue/task triage specs',
|
||||
settings_aux_advanced_button_title: 'Advanced options',
|
||||
settings_aux_advanced_button_aria: 'Advanced options for {task}',
|
||||
settings_aux_advanced_title: '{task} options',
|
||||
settings_aux_advanced_subtitle: 'Advanced config for auxiliary.',
|
||||
settings_aux_advanced_save: 'Save options',
|
||||
settings_aux_advanced_base_url: 'Base URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optional provider endpoint override.',
|
||||
settings_aux_advanced_timeout: 'Timeout seconds',
|
||||
settings_aux_advanced_timeout_desc: 'Request timeout for this auxiliary task. Blank uses Hermes default.',
|
||||
settings_aux_advanced_download_timeout: 'Download timeout seconds',
|
||||
settings_aux_advanced_download_timeout_desc: 'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.',
|
||||
settings_aux_advanced_max_concurrency: 'Max concurrency',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optional per-task concurrency limit. Blank uses default.',
|
||||
settings_aux_advanced_extra_body: 'Extra body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optional JSON object merged into the model request body.',
|
||||
settings_aux_advanced_api_key: 'API key override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API key is set. Leave blank to keep it, or use clear to remove it.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leave blank to use provider/default credentials.',
|
||||
settings_aux_advanced_api_key_clear: 'Clear existing API key override',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra body must be valid JSON',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra body must be a JSON object',
|
||||
settings_aux_advanced_saved: 'Auxiliary options saved',
|
||||
settings_aux_advanced_save_failed: 'Failed to save auxiliary options',
|
||||
settings_label_send_key: 'Envoyer la clé',
|
||||
settings_label_theme: 'Thème',
|
||||
settings_label_skin: 'Peau',
|
||||
@@ -15690,6 +16264,53 @@ const LOCALES = {
|
||||
settings_aux_model_custom: 'Özel model…',
|
||||
settings_aux_model_custom_prompt: 'Model kimliğini girin:',
|
||||
settings_aux_no_changes: 'Uygulanacak değişiklik yok',
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'image/screenshot analysis',
|
||||
settings_aux_task_compression: 'Compression',
|
||||
settings_aux_task_compression_desc: 'context summarization',
|
||||
settings_aux_task_web_extract: 'Web extract',
|
||||
settings_aux_task_web_extract_desc: 'web page summarization',
|
||||
settings_aux_task_session_search: 'Session search',
|
||||
settings_aux_task_session_search_desc: 'past-conversation recall',
|
||||
settings_aux_task_approval: 'Approval',
|
||||
settings_aux_task_approval_desc: 'smart command approval',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP tool reasoning',
|
||||
settings_aux_task_title_generation: 'Title generation',
|
||||
settings_aux_task_title_generation_desc: 'session titles',
|
||||
settings_aux_task_skills_hub: 'Skills hub',
|
||||
settings_aux_task_skills_hub_desc: 'skills search/install',
|
||||
settings_aux_task_curator: 'Curator',
|
||||
settings_aux_task_curator_desc: 'skill-usage review pass',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban decomposer',
|
||||
settings_aux_task_kanban_decomposer_desc: 'task decomposition',
|
||||
settings_aux_task_profile_describer: 'Profile describer',
|
||||
settings_aux_task_profile_describer_desc: 'profile summaries',
|
||||
settings_aux_task_triage_specifier: 'Triage specifier',
|
||||
settings_aux_task_triage_specifier_desc: 'issue/task triage specs',
|
||||
settings_aux_advanced_button_title: 'Advanced options',
|
||||
settings_aux_advanced_button_aria: 'Advanced options for {task}',
|
||||
settings_aux_advanced_title: '{task} options',
|
||||
settings_aux_advanced_subtitle: 'Advanced config for auxiliary.',
|
||||
settings_aux_advanced_save: 'Save options',
|
||||
settings_aux_advanced_base_url: 'Base URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optional provider endpoint override.',
|
||||
settings_aux_advanced_timeout: 'Timeout seconds',
|
||||
settings_aux_advanced_timeout_desc: 'Request timeout for this auxiliary task. Blank uses Hermes default.',
|
||||
settings_aux_advanced_download_timeout: 'Download timeout seconds',
|
||||
settings_aux_advanced_download_timeout_desc: 'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.',
|
||||
settings_aux_advanced_max_concurrency: 'Max concurrency',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optional per-task concurrency limit. Blank uses default.',
|
||||
settings_aux_advanced_extra_body: 'Extra body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optional JSON object merged into the model request body.',
|
||||
settings_aux_advanced_api_key: 'API key override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API key is set. Leave blank to keep it, or use clear to remove it.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leave blank to use provider/default credentials.',
|
||||
settings_aux_advanced_api_key_clear: 'Clear existing API key override',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra body must be valid JSON',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra body must be a JSON object',
|
||||
settings_aux_advanced_saved: 'Auxiliary options saved',
|
||||
settings_aux_advanced_save_failed: 'Failed to save auxiliary options',
|
||||
settings_aux_saved: 'Yardımcı modeller güncellendi',
|
||||
settings_aux_save_failed: 'Yardımcı model kaydedilemedi',
|
||||
settings_aux_reset_confirm_title: 'Yardımcı modeller sıfırlansın mı?',
|
||||
@@ -15698,6 +16319,11 @@ const LOCALES = {
|
||||
settings_btn_apply_aux_models: 'Değişiklikleri uygula',
|
||||
settings_btn_reset_aux_models: 'Tümünü otomatiğe sıfırla',
|
||||
settings_desc_model: 'Yeni konuşmalar için kullanılır. Mevcut konuşmalar seçilen modellerini korur.',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_label_send_key: 'Anahtar Gönder',
|
||||
settings_label_theme: 'Tema',
|
||||
settings_label_skin: 'Deri',
|
||||
@@ -17128,6 +17754,58 @@ const LOCALES = {
|
||||
settings_aux_save_failed: 'Nie udało się zapisać modelu pomocniczego',
|
||||
settings_aux_saved: 'Zaktualizowano modele pomocnicze',
|
||||
settings_aux_no_changes: 'Brak zmian do zastosowania',
|
||||
settings_aux_task_vision: 'Vision',
|
||||
settings_aux_task_vision_desc: 'image/screenshot analysis',
|
||||
settings_aux_task_compression: 'Compression',
|
||||
settings_aux_task_compression_desc: 'context summarization',
|
||||
settings_aux_task_web_extract: 'Web extract',
|
||||
settings_aux_task_web_extract_desc: 'web page summarization',
|
||||
settings_aux_task_session_search: 'Session search',
|
||||
settings_aux_task_session_search_desc: 'past-conversation recall',
|
||||
settings_aux_task_approval: 'Approval',
|
||||
settings_aux_task_approval_desc: 'smart command approval',
|
||||
settings_aux_task_mcp: 'MCP',
|
||||
settings_aux_task_mcp_desc: 'MCP tool reasoning',
|
||||
settings_aux_task_title_generation: 'Title generation',
|
||||
settings_aux_task_title_generation_desc: 'session titles',
|
||||
settings_aux_task_skills_hub: 'Skills hub',
|
||||
settings_aux_task_skills_hub_desc: 'skills search/install',
|
||||
settings_aux_task_curator: 'Curator',
|
||||
settings_aux_task_curator_desc: 'skill-usage review pass',
|
||||
settings_aux_task_kanban_decomposer: 'Kanban decomposer',
|
||||
settings_aux_task_kanban_decomposer_desc: 'task decomposition',
|
||||
settings_aux_task_profile_describer: 'Profile describer',
|
||||
settings_aux_task_profile_describer_desc: 'profile summaries',
|
||||
settings_aux_task_triage_specifier: 'Triage specifier',
|
||||
settings_aux_task_triage_specifier_desc: 'issue/task triage specs',
|
||||
settings_main_advanced_button_aria: 'Advanced options for main model',
|
||||
settings_main_advanced_title: 'Main model options',
|
||||
settings_main_advanced_subtitle: 'Advanced config for the default chat model.',
|
||||
settings_main_advanced_saved: 'Main model options saved',
|
||||
settings_main_advanced_save_failed: 'Failed to save main model options',
|
||||
settings_aux_advanced_button_title: 'Advanced options',
|
||||
settings_aux_advanced_button_aria: 'Advanced options for {task}',
|
||||
settings_aux_advanced_title: '{task} options',
|
||||
settings_aux_advanced_subtitle: 'Advanced config for auxiliary.',
|
||||
settings_aux_advanced_save: 'Save options',
|
||||
settings_aux_advanced_base_url: 'Base URL',
|
||||
settings_aux_advanced_base_url_desc: 'Optional provider endpoint override.',
|
||||
settings_aux_advanced_timeout: 'Timeout seconds',
|
||||
settings_aux_advanced_timeout_desc: 'Request timeout for this auxiliary task. Blank uses Hermes default.',
|
||||
settings_aux_advanced_download_timeout: 'Download timeout seconds',
|
||||
settings_aux_advanced_download_timeout_desc: 'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.',
|
||||
settings_aux_advanced_max_concurrency: 'Max concurrency',
|
||||
settings_aux_advanced_max_concurrency_desc: 'Optional per-task concurrency limit. Blank uses default.',
|
||||
settings_aux_advanced_extra_body: 'Extra body JSON',
|
||||
settings_aux_advanced_extra_body_desc: 'Optional JSON object merged into the model request body.',
|
||||
settings_aux_advanced_api_key: 'API key override',
|
||||
settings_aux_advanced_api_key_set_hint: 'API key is set. Leave blank to keep it, or use clear to remove it.',
|
||||
settings_aux_advanced_api_key_empty_hint: 'Leave blank to use provider/default credentials.',
|
||||
settings_aux_advanced_api_key_clear: 'Clear existing API key override',
|
||||
settings_aux_advanced_extra_body_invalid_json: 'Extra body must be valid JSON',
|
||||
settings_aux_advanced_extra_body_object_required: 'Extra body must be a JSON object',
|
||||
settings_aux_advanced_saved: 'Auxiliary options saved',
|
||||
settings_aux_advanced_save_failed: 'Failed to save auxiliary options',
|
||||
settings_label_send_key: 'Klawisz wysyłania',
|
||||
settings_label_theme: 'Motyw',
|
||||
settings_label_skin: 'Skórka',
|
||||
|
||||
+4
-1
@@ -1056,7 +1056,10 @@
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
<label for="settingsModel" data-i18n="settings_label_model">Default Model</label>
|
||||
<select id="settingsModel" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px"></select>
|
||||
<div class="model-advanced-row">
|
||||
<select id="settingsModel" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px"></select>
|
||||
<button type="button" id="mainAdvancedBtn" class="model-advanced-btn" title="Advanced options" aria-label="Advanced options for main model"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button>
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--muted);margin-top:4px" data-i18n="settings_desc_model">Used for new conversations. Existing conversations keep their selected model.</div>
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
|
||||
+183
-13
@@ -6706,6 +6706,7 @@ async function loadSettingsPanel(){
|
||||
modelSel.addEventListener('change',_markSettingsDirty,{once:false});
|
||||
}
|
||||
// Auxiliary models — load task assignments and provider/model options
|
||||
_bindMainAdvancedOptionsButton();
|
||||
_loadAuxiliaryModels();
|
||||
// Send key preference
|
||||
const sendKeySel=$('settingsSendKey');
|
||||
@@ -8009,19 +8010,23 @@ async function checkUpdatesNow(){
|
||||
// Canonical auxiliary task slots with display names.
|
||||
// Keep in sync with hermes_cli/main.py _AUX_TASKS and hermes_cli/web_server.py _AUX_TASK_SLOTS.
|
||||
const _AUX_TASK_SLOTS=[
|
||||
{key:'vision',name:'Vision',desc:'image/screenshot analysis'},
|
||||
{key:'compression',name:'Compression',desc:'context summarization'},
|
||||
{key:'web_extract',name:'Web extract',desc:'web page summarization'},
|
||||
{key:'session_search',name:'Session search',desc:'past-conversation recall'},
|
||||
{key:'approval',name:'Approval',desc:'smart command approval'},
|
||||
{key:'mcp',name:'MCP',desc:'MCP tool reasoning'},
|
||||
{key:'title_generation',name:'Title generation',desc:'session titles'},
|
||||
{key:'skills_hub',name:'Skills hub',desc:'skills search/install'},
|
||||
{key:'curator',name:'Curator',desc:'skill-usage review pass'},
|
||||
{key:'vision',nameKey:'settings_aux_task_vision',descKey:'settings_aux_task_vision_desc'},
|
||||
{key:'compression',nameKey:'settings_aux_task_compression',descKey:'settings_aux_task_compression_desc'},
|
||||
{key:'web_extract',nameKey:'settings_aux_task_web_extract',descKey:'settings_aux_task_web_extract_desc'},
|
||||
{key:'session_search',nameKey:'settings_aux_task_session_search',descKey:'settings_aux_task_session_search_desc'},
|
||||
{key:'approval',nameKey:'settings_aux_task_approval',descKey:'settings_aux_task_approval_desc'},
|
||||
{key:'mcp',nameKey:'settings_aux_task_mcp',descKey:'settings_aux_task_mcp_desc'},
|
||||
{key:'title_generation',nameKey:'settings_aux_task_title_generation',descKey:'settings_aux_task_title_generation_desc'},
|
||||
{key:'skills_hub',nameKey:'settings_aux_task_skills_hub',descKey:'settings_aux_task_skills_hub_desc'},
|
||||
{key:'curator',nameKey:'settings_aux_task_curator',descKey:'settings_aux_task_curator_desc'},
|
||||
{key:'kanban_decomposer',nameKey:'settings_aux_task_kanban_decomposer',descKey:'settings_aux_task_kanban_decomposer_desc'},
|
||||
{key:'profile_describer',nameKey:'settings_aux_task_profile_describer',descKey:'settings_aux_task_profile_describer_desc'},
|
||||
{key:'triage_specifier',nameKey:'settings_aux_task_triage_specifier',descKey:'settings_aux_task_triage_specifier_desc'},
|
||||
];
|
||||
|
||||
let _auxProviders=[]; // cached provider list from /api/model/options
|
||||
let _auxProviders=[]; // cached provider list from /api/models
|
||||
let _auxOriginalConfig=null; // snapshot of initial config for dirty detection
|
||||
let _mainAdvancedConfig=null; // current advanced config for the default chat model
|
||||
|
||||
function _auxSelectStyle(){
|
||||
return 'width:100%;padding:6px 8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px;font-size:12px;box-sizing:border-box';
|
||||
@@ -8109,6 +8114,154 @@ function _markAuxDirty(){
|
||||
_markSettingsDirty();
|
||||
}
|
||||
|
||||
function _auxAdvancedValue(cfg,key){
|
||||
const v=cfg&&Object.prototype.hasOwnProperty.call(cfg,key)?cfg[key]:'';
|
||||
return v===null||v===undefined?'':String(v);
|
||||
}
|
||||
|
||||
function _ensureAuxAdvancedModal(){
|
||||
let overlay=$('auxAdvancedOverlay');
|
||||
if(overlay) return overlay;
|
||||
overlay=document.createElement('div');
|
||||
overlay.id='auxAdvancedOverlay';
|
||||
overlay.style.cssText='position:fixed;inset:0;z-index:9999;background:rgba(5,7,15,.68);backdrop-filter:blur(4px);display:none;align-items:center;justify-content:center;padding:20px';
|
||||
const neutralBtn='font-size:12px;padding:7px 12px;border-radius:8px;border:1px solid var(--border);background:var(--surface);color:var(--text);cursor:pointer;font-weight:600';
|
||||
const primaryBtn='font-size:12px;padding:7px 12px;border-radius:8px;border:1px solid var(--accent);background:var(--accent);color:#1a1a1a;cursor:pointer;font-weight:700';
|
||||
overlay.innerHTML=`<div role="dialog" aria-modal="true" aria-labelledby="auxAdvancedTitle" style="width:min(620px,calc(100vw - 32px));max-height:calc(100vh - 48px);overflow:auto;background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:14px;box-shadow:0 18px 60px rgba(0,0,0,.45);padding:16px">
|
||||
<style>#auxAdvancedOverlay input:-webkit-autofill,#auxAdvancedOverlay textarea:-webkit-autofill{box-shadow:0 0 0 1000px var(--code-bg) inset!important;-webkit-box-shadow:0 0 0 1000px var(--code-bg) inset!important;-webkit-text-fill-color:var(--text)!important;caret-color:var(--text)!important}</style>
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px">
|
||||
<div><div id="auxAdvancedTitle" style="font-weight:700;font-size:16px"></div><div id="auxAdvancedSubtitle" style="font-size:11px;color:var(--muted);margin-top:2px"></div></div>
|
||||
<button type="button" id="auxAdvancedClose" aria-label="${esc(t('terminal_close')||'Close')}" style="width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border-radius:8px;border:1px solid var(--border);background:var(--input-bg);color:var(--text);cursor:pointer;font-size:18px;line-height:1">×</button>
|
||||
</div>
|
||||
<div id="auxAdvancedBody" style="display:grid;gap:10px"></div>
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:14px">
|
||||
<button type="button" id="auxAdvancedCancel" style="${neutralBtn}">${esc(t('cancel')||'Cancel')}</button>
|
||||
<button type="button" id="auxAdvancedSave" style="${primaryBtn}">${esc(t('settings_aux_advanced_save')||'Save options')}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.appendChild(overlay);
|
||||
const close=()=>{overlay.style.display='none';overlay.dataset.task='';};
|
||||
$('auxAdvancedClose')?.addEventListener('click',close);
|
||||
$('auxAdvancedCancel')?.addEventListener('click',close);
|
||||
overlay.addEventListener('click',ev=>{if(ev.target===overlay) close();});
|
||||
return overlay;
|
||||
}
|
||||
|
||||
function _auxAdvancedInputHtml(id,label,value,desc,type='text',extraAttrs='',extraStyle=''){
|
||||
const fieldName=id==='auxAdvancedApiKey'?'aux-manual-override-value':('aux-field-'+id.replace(/^auxAdvanced/,'').toLowerCase());
|
||||
const autocompleteAttr=/\bautocomplete=/.test(extraAttrs)?'':'autocomplete="off"';
|
||||
const inputAttrs=`id="${id}" name="${fieldName}" type="${type}" value="${esc(value)}" ${autocompleteAttr} autocapitalize="off" autocorrect="off" spellcheck="false" data-lpignore="true" data-1p-ignore="true" ${extraAttrs}`;
|
||||
return `<label style="display:grid;gap:4px;font-size:12px;color:var(--text)"><span style="font-weight:600">${esc(label)}</span><input ${inputAttrs} style="width:100%;box-sizing:border-box;padding:7px 8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px;font-size:12px${extraStyle}"><span style="font-size:10px;color:var(--muted);line-height:1.35">${esc(desc)}</span></label>`;
|
||||
}
|
||||
|
||||
function _openAuxAdvancedOptions(taskKey,cfg){
|
||||
const isMain=taskKey==='__main__';
|
||||
const slot=isMain?{key:taskKey,nameKey:'settings_label_model',descKey:'settings_desc_model'}:(_AUX_TASK_SLOTS.find(s=>s.key===taskKey)||{key:taskKey,nameKey:'',descKey:''});
|
||||
const overlay=_ensureAuxAdvancedModal();
|
||||
overlay.dataset.task=taskKey;
|
||||
const title=$('auxAdvancedTitle'),sub=$('auxAdvancedSubtitle'),body=$('auxAdvancedBody');
|
||||
const slotName=t(slot.nameKey)||slot.key;
|
||||
if(title) title.textContent=isMain?(t('settings_main_advanced_title')||'Main model options'):((t('settings_aux_advanced_title')||'{task} options').replace('{task}',slotName));
|
||||
if(sub) sub.textContent=isMain?(t('settings_main_advanced_subtitle')||'Advanced config for the default chat model.'):(t('settings_aux_advanced_subtitle')||'Advanced config for auxiliary.');
|
||||
const extraBody=cfg&&cfg.extra_body&&typeof cfg.extra_body==='object'&&Object.keys(cfg.extra_body).length?JSON.stringify(cfg.extra_body,null,2):'';
|
||||
const apiKeyHint=cfg&&cfg.api_key_set?(t('settings_aux_advanced_api_key_set_hint')||'API key is set. Leave blank to keep it, or use clear to remove it.'):(t('settings_aux_advanced_api_key_empty_hint')||'Leave blank to use provider/default credentials.');
|
||||
if(body){
|
||||
const timingFields=isMain?'':(
|
||||
_auxAdvancedInputHtml('auxAdvancedTimeout',t('settings_aux_advanced_timeout')||'Timeout seconds',_auxAdvancedValue(cfg,'timeout'),t('settings_aux_advanced_timeout_desc')||'Request timeout for this auxiliary task. Blank uses Hermes default.','number','inputmode="numeric" min="1" step="1"')+
|
||||
_auxAdvancedInputHtml('auxAdvancedDownloadTimeout',t('settings_aux_advanced_download_timeout')||'Download timeout seconds',_auxAdvancedValue(cfg,'download_timeout'),t('settings_aux_advanced_download_timeout_desc')||'Only relevant for tasks that download media/content, e.g. vision. Blank uses default.','number','inputmode="numeric" min="1" step="1"')+
|
||||
_auxAdvancedInputHtml('auxAdvancedMaxConcurrency',t('settings_aux_advanced_max_concurrency')||'Max concurrency',_auxAdvancedValue(cfg,'max_concurrency'),t('settings_aux_advanced_max_concurrency_desc')||'Optional per-task concurrency limit. Blank uses default.','number','inputmode="numeric" min="1" step="1"'));
|
||||
body.innerHTML=
|
||||
_auxAdvancedInputHtml('auxAdvancedBaseUrl',t('settings_aux_advanced_base_url')||'Base URL',_auxAdvancedValue(cfg,'base_url'),t('settings_aux_advanced_base_url_desc')||'Optional provider endpoint override.','text','inputmode="url"')+
|
||||
timingFields+
|
||||
`<label style="display:grid;gap:4px;font-size:12px;color:var(--text)"><span style="font-weight:600">${esc(t('settings_aux_advanced_extra_body')||'Extra body JSON')}</span><textarea id="auxAdvancedExtraBody" rows="6" style="width:100%;box-sizing:border-box;padding:7px 8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px;font-size:12px;font-family:var(--mono,monospace)">${esc(extraBody)}</textarea><span style="font-size:10px;color:var(--muted);line-height:1.35">${esc(t('settings_aux_advanced_extra_body_desc')||'Optional JSON object merged into the model request body.')}</span></label>`+
|
||||
_auxAdvancedInputHtml('auxAdvancedApiKey',t('settings_aux_advanced_api_key')||'API key override','',apiKeyHint,'text','autocomplete="one-time-code" inputmode="text" readonly onfocus="this.removeAttribute("readonly")"',';-webkit-text-security:disc')+
|
||||
`<label style="display:${cfg&&cfg.api_key_set?'flex':'none'};align-items:center;gap:8px;font-size:12px;color:var(--text)"><input id="auxAdvancedApiKeyClear" type="checkbox" style="width:15px;height:15px;accent-color:var(--accent)"><span>${esc(t('settings_aux_advanced_api_key_clear')||'Clear existing API key override')}</span></label>`;
|
||||
}
|
||||
const save=$('auxAdvancedSave');
|
||||
if(save){
|
||||
save.onclick=async()=>{
|
||||
let extra={};
|
||||
const extraText=($('auxAdvancedExtraBody')?.value||'').trim();
|
||||
if(extraText){
|
||||
try{extra=JSON.parse(extraText);}catch(e){if(typeof showToast==='function') showToast(t('settings_aux_advanced_extra_body_invalid_json')||'Extra body must be valid JSON');return;}
|
||||
if(!extra||Array.isArray(extra)||typeof extra!=='object'){if(typeof showToast==='function') showToast(t('settings_aux_advanced_extra_body_object_required')||'Extra body must be a JSON object');return;}
|
||||
}
|
||||
const provSel=isMain?null:$('aux-prov-'+taskKey),modelSel=isMain?$('settingsModel'):$('aux-model-'+taskKey);
|
||||
const provider=isMain?((cfg&&cfg.provider)||''):(provSel?provSel.value:((cfg&&cfg.provider)||'auto'));
|
||||
const model=modelSel&&modelSel.value!=='__custom__'?(modelSel.value||''):((cfg&&cfg.model)||'');
|
||||
const advanced={
|
||||
base_url:$('auxAdvancedBaseUrl')?.value||'',
|
||||
extra_body:extra,
|
||||
api_key:$('auxAdvancedApiKey')?.value||'',
|
||||
api_key_clear:!!($('auxAdvancedApiKeyClear')&&$('auxAdvancedApiKeyClear').checked),
|
||||
};
|
||||
if(!isMain){
|
||||
advanced.timeout=$('auxAdvancedTimeout')?.value||'';
|
||||
advanced.download_timeout=$('auxAdvancedDownloadTimeout')?.value||'';
|
||||
advanced.max_concurrency=$('auxAdvancedMaxConcurrency')?.value||'';
|
||||
}
|
||||
try{
|
||||
await api('/api/model/set',{method:'POST',body:JSON.stringify({scope:isMain?'main':'auxiliary',task:isMain?'':taskKey,provider,model,advanced})});
|
||||
if(typeof showToast==='function') showToast(isMain?(t('settings_main_advanced_saved')||'Main model options saved'):(t('settings_aux_advanced_saved')||'Auxiliary options saved'));
|
||||
overlay.style.display='none';
|
||||
_loadAuxiliaryModels();
|
||||
}catch(e){
|
||||
if(typeof showToast==='function') showToast(isMain?(t('settings_main_advanced_save_failed')||'Failed to save main model options'):(t('settings_aux_advanced_save_failed')||'Failed to save auxiliary options'));
|
||||
}
|
||||
};
|
||||
}
|
||||
overlay.style.display='flex';
|
||||
setTimeout(()=>$('auxAdvancedBaseUrl')?.focus(),0);
|
||||
}
|
||||
|
||||
function _bindMainAdvancedOptionsButton(){
|
||||
const modelSel=$('settingsModel');
|
||||
let btn=$('mainAdvancedBtn');
|
||||
if(modelSel){
|
||||
const parent=modelSel.parentElement;
|
||||
let row=parent&&parent.classList&&parent.classList.contains('model-advanced-row')?parent:null;
|
||||
if(!row){
|
||||
row=document.createElement('div');
|
||||
row.className='model-advanced-row';
|
||||
parent.insertBefore(row,modelSel);
|
||||
row.appendChild(modelSel);
|
||||
}
|
||||
if(!btn){
|
||||
btn=document.createElement('button');
|
||||
btn.type='button';
|
||||
btn.id='mainAdvancedBtn';
|
||||
}
|
||||
if(btn.parentElement!==row) row.appendChild(btn);
|
||||
row.style.cssText='display:grid;grid-template-columns:minmax(0,1fr) 34px;gap:8px;align-items:center';
|
||||
modelSel.style.width='100%';
|
||||
modelSel.style.minWidth='0';
|
||||
modelSel.style.boxSizing='border-box';
|
||||
}
|
||||
if(!btn) return;
|
||||
btn.classList.add('model-advanced-btn');
|
||||
if(!btn.querySelector('svg')&&typeof li==='function') btn.innerHTML=li('settings',15);
|
||||
btn.style.position='';
|
||||
btn.style.right='';
|
||||
btn.style.top='';
|
||||
btn.style.transform='';
|
||||
btn.style.width='32px';
|
||||
btn.style.height='32px';
|
||||
btn.style.display='flex';
|
||||
btn.style.alignItems='center';
|
||||
btn.style.justifyContent='center';
|
||||
btn.style.flex='0 0 32px';
|
||||
btn.style.boxSizing='border-box';
|
||||
const title=t('settings_aux_advanced_button_title')||'Advanced options';
|
||||
btn.title=title;
|
||||
btn.setAttribute('aria-label',t('settings_main_advanced_button_aria')||'Advanced options for main model');
|
||||
btn.disabled=_mainAdvancedConfig===null;
|
||||
btn.style.opacity='';
|
||||
btn.style.cursor='';
|
||||
if(btn._bound) return;
|
||||
btn._bound=true;
|
||||
btn.addEventListener('click',()=>{if(_mainAdvancedConfig!==null)_openAuxAdvancedOptions('__main__',_mainAdvancedConfig||{});});
|
||||
}
|
||||
|
||||
async function _loadAuxiliaryModels(){
|
||||
const container=$('auxModelsContainer');
|
||||
if(!container) return;
|
||||
@@ -8128,6 +8281,12 @@ async function _loadAuxiliaryModels(){
|
||||
name:g.provider,
|
||||
models:g.models.map(m=>m.id),
|
||||
}));
|
||||
if(auxData&&Object.prototype.hasOwnProperty.call(auxData,'main')){
|
||||
_mainAdvancedConfig=auxData.main||{};
|
||||
}else{
|
||||
_mainAdvancedConfig=null;
|
||||
}
|
||||
_bindMainAdvancedOptionsButton();
|
||||
const tasks=(auxData&&auxData.tasks)||[];
|
||||
// Build a quick lookup: taskKey → {provider, model}
|
||||
const taskMap={};
|
||||
@@ -8138,12 +8297,12 @@ async function _loadAuxiliaryModels(){
|
||||
for(const slot of _AUX_TASK_SLOTS){
|
||||
const cfg=taskMap[slot.key]||{provider:'auto',model:''};
|
||||
const row=document.createElement('div');
|
||||
row.style.cssText='display:grid;grid-template-columns:120px 1fr 1fr;gap:8px;align-items:center;margin-bottom:8px';
|
||||
row.style.cssText='display:grid;grid-template-columns:120px 1fr 1fr 34px;gap:8px;align-items:center;margin-bottom:8px';
|
||||
|
||||
// Task name + description
|
||||
const label=document.createElement('div');
|
||||
label.style.cssText='font-size:12px;font-weight:500;color:var(--text);line-height:1.3';
|
||||
label.innerHTML=esc(slot.name)+'<div style="font-size:10px;color:var(--muted);font-weight:400">'+esc(slot.desc)+'</div>';
|
||||
label.innerHTML=esc(t(slot.nameKey)||slot.key)+'<div style="font-size:10px;color:var(--muted);font-weight:400">'+esc(t(slot.descKey)||'')+'</div>';
|
||||
row.appendChild(label);
|
||||
|
||||
// Provider select
|
||||
@@ -8162,6 +8321,17 @@ async function _loadAuxiliaryModels(){
|
||||
modelSel.addEventListener('change',()=>_onAuxModelChange(slot.key));
|
||||
row.appendChild(modelSel);
|
||||
|
||||
const advancedBtn=document.createElement('button');
|
||||
advancedBtn.type='button';
|
||||
advancedBtn.className='aux-advanced-btn model-advanced-btn';
|
||||
const advTitle=t('settings_aux_advanced_button_title')||'Advanced options';
|
||||
const slotName=t(slot.nameKey)||slot.key;
|
||||
advancedBtn.title=advTitle;
|
||||
advancedBtn.setAttribute('aria-label',(t('settings_aux_advanced_button_aria')||'Advanced options for {task}').replace('{task}',slotName));
|
||||
advancedBtn.innerHTML=typeof li==='function'?li('settings',15):'⚙';
|
||||
advancedBtn.addEventListener('click',()=>_openAuxAdvancedOptions(slot.key,cfg));
|
||||
row.appendChild(advancedBtn);
|
||||
|
||||
container.appendChild(row);
|
||||
}
|
||||
// Hide apply button (no changes yet)
|
||||
@@ -8211,7 +8381,7 @@ async function _applyAuxModels(){
|
||||
saved++;
|
||||
}catch(e){
|
||||
console.warn('[settings] failed to save aux task',slot.key,e);
|
||||
if(typeof showToast==='function') showToast(t('settings_aux_save_failed')||'Failed to save auxiliary model for '+slot.name);
|
||||
if(typeof showToast==='function') showToast(t('settings_aux_save_failed')||'Failed to save auxiliary model');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4055,6 +4055,49 @@ main.main > #mainPlugin{display:none;}
|
||||
#mainSettings .settings-field > div[style*="color:var(--muted)"],
|
||||
#mainSettings .settings-field > div[style*="color: var(--muted)"]{font-size:12px;color:var(--muted);line-height:1.5;}
|
||||
|
||||
.model-advanced-row{
|
||||
display:grid;
|
||||
grid-template-columns:minmax(0,1fr) 34px;
|
||||
gap:8px;
|
||||
align-items:center;
|
||||
}
|
||||
.model-advanced-row #settingsModel{
|
||||
width:100%!important;
|
||||
min-width:0;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
.model-advanced-btn{
|
||||
width:32px;
|
||||
height:32px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
border-radius:8px;
|
||||
border:1px solid var(--border);
|
||||
background:var(--input-bg);
|
||||
color:var(--muted);
|
||||
cursor:pointer;
|
||||
line-height:1;
|
||||
padding:0;
|
||||
flex:0 0 32px;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
.model-advanced-btn svg{display:block;flex-shrink:0;}
|
||||
.model-advanced-btn:hover:not(:disabled),
|
||||
.model-advanced-btn:focus-visible:not(:disabled){
|
||||
color:var(--accent);
|
||||
border-color:var(--accent);
|
||||
background:var(--accent-bg);
|
||||
}
|
||||
.model-advanced-btn:focus-visible{
|
||||
outline:2px solid var(--focus-ring);
|
||||
outline-offset:2px;
|
||||
}
|
||||
.model-advanced-btn:disabled{
|
||||
opacity:.7;
|
||||
cursor:not-allowed;
|
||||
}
|
||||
|
||||
/* Sidebar tab visibility — hide nav-tab and matching rail-btn when toggled off by user */
|
||||
.nav-tab-hidden{display:none!important;}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ ROOT = Path(__file__).parent.parent
|
||||
PANELS_JS = (ROOT / "static" / "panels.js").read_text(encoding="utf-8")
|
||||
INDEX_HTML = (ROOT / "static" / "index.html").read_text(encoding="utf-8")
|
||||
I18N_JS = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8")
|
||||
STREAMING_PY = (ROOT / "api" / "streaming.py").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
class TestAuxiliaryModelsHTML:
|
||||
@@ -62,17 +63,107 @@ class TestAuxiliaryModelsJS:
|
||||
)
|
||||
|
||||
def test_aux_task_slots_defined(self):
|
||||
"""_AUX_TASK_SLOTS must list the 9 canonical task slots."""
|
||||
"""_AUX_TASK_SLOTS must list the 12 canonical task slots."""
|
||||
assert "_AUX_TASK_SLOTS" in PANELS_JS, (
|
||||
"Missing _AUX_TASK_SLOTS constant in panels.js"
|
||||
)
|
||||
# Verify all 9 tasks are present
|
||||
# Verify all 12 tasks are present
|
||||
for key in ("vision", "compression", "web_extract", "session_search",
|
||||
"approval", "mcp", "title_generation", "skills_hub", "curator"):
|
||||
"approval", "mcp", "title_generation", "skills_hub", "curator",
|
||||
"kanban_decomposer", "profile_describer", "triage_specifier"):
|
||||
assert f"key:'{key}'" in PANELS_JS, (
|
||||
f"Missing auxiliary task slot '{key}' in _AUX_TASK_SLOTS"
|
||||
)
|
||||
|
||||
def test_advanced_options_button_and_modal_wiring(self):
|
||||
"""Each auxiliary row and the main model should expose gear-driven advanced config editing."""
|
||||
for marker in (
|
||||
"aux-advanced-btn",
|
||||
"model-advanced-row",
|
||||
"model-advanced-btn",
|
||||
"mainAdvancedBtn",
|
||||
"_bindMainAdvancedOptionsButton",
|
||||
"document.createElement('button')",
|
||||
"row.appendChild(btn)",
|
||||
"_openAuxAdvancedOptions",
|
||||
"_mainAdvancedConfig=null",
|
||||
"btn.disabled=_mainAdvancedConfig===null",
|
||||
"if(_mainAdvancedConfig!==null)",
|
||||
"Object.prototype.hasOwnProperty.call(auxData,'main')",
|
||||
"_mainAdvancedConfig=null;",
|
||||
"auxAdvancedOverlay",
|
||||
"auxAdvancedBaseUrl",
|
||||
"auxAdvancedTimeout",
|
||||
"auxAdvancedDownloadTimeout",
|
||||
"auxAdvancedMaxConcurrency",
|
||||
"auxAdvancedExtraBody",
|
||||
"auxAdvancedApiKey",
|
||||
"api_key_clear",
|
||||
"Object.keys(cfg.extra_body).length",
|
||||
):
|
||||
assert marker in PANELS_JS
|
||||
|
||||
def test_main_advanced_modal_hides_unsupported_timing_fields_but_keeps_request_body(self):
|
||||
"""Main-model modal should not advertise timing knobs that the chat agent cannot apply."""
|
||||
open_idx = PANELS_JS.find("function _openAuxAdvancedOptions")
|
||||
assert open_idx >= 0
|
||||
modal_body = PANELS_JS[open_idx:open_idx + 3200]
|
||||
assert "const timingFields=isMain?'':(" in modal_body
|
||||
assert "auxAdvancedExtraBody" in modal_body
|
||||
assert "auxAdvancedBaseUrl" in modal_body
|
||||
|
||||
def test_main_advanced_save_omits_unsupported_timing_keys(self):
|
||||
"""Saving main-model options must not send blank timing keys that backend treats as clears."""
|
||||
save_idx = PANELS_JS.find("const advanced={")
|
||||
assert save_idx >= 0
|
||||
save_body = PANELS_JS[save_idx:save_idx + 900]
|
||||
object_literal = save_body[:save_body.find("};") + 2]
|
||||
assert "timeout:" not in object_literal
|
||||
assert "download_timeout:" not in object_literal
|
||||
assert "max_concurrency:" not in object_literal
|
||||
assert "if(!isMain){" in save_body
|
||||
assert "advanced.timeout=$('auxAdvancedTimeout')?.value||''" in save_body
|
||||
assert "advanced.download_timeout=$('auxAdvancedDownloadTimeout')?.value||''" in save_body
|
||||
assert "advanced.max_concurrency=$('auxAdvancedMaxConcurrency')?.value||''" in save_body
|
||||
|
||||
def test_main_extra_body_flows_to_agent_request_overrides(self):
|
||||
"""Persisted main extra_body must be passed to AIAgent, not only shown in Settings."""
|
||||
assert "_main_model_request_overrides" in STREAMING_PY
|
||||
assert "'request_overrides' in _agent_params" in STREAMING_PY
|
||||
assert "_agent_kwargs['request_overrides'] = _main_request_overrides" in STREAMING_PY
|
||||
assert "_main_request_overrides or {}" in STREAMING_PY
|
||||
|
||||
def test_advanced_modal_uses_defined_theme_tokens_and_inline_button_styles(self):
|
||||
"""The modal is appended outside #mainSettings, so scoped button CSS must not be required."""
|
||||
modal_idx = PANELS_JS.find("function _ensureAuxAdvancedModal")
|
||||
assert modal_idx >= 0
|
||||
modal_body = PANELS_JS[modal_idx:modal_idx + 2400]
|
||||
assert "var(--panel)" not in modal_body, "--panel is not a defined WebUI theme token"
|
||||
assert "class=\"settings-btn\"" not in modal_body, "settings-btn is scoped under #mainSettings"
|
||||
assert "background:var(--surface)" in modal_body
|
||||
assert "background:var(--input-bg)" in modal_body
|
||||
assert ":-webkit-autofill" in PANELS_JS
|
||||
assert "settings_aux_advanced_title" in PANELS_JS
|
||||
assert "settings_aux_advanced_button_aria" in PANELS_JS
|
||||
|
||||
def test_advanced_modal_inputs_disable_browser_autofill(self):
|
||||
"""Advanced modal fields must not be mistaken for browser login/password fields."""
|
||||
input_helper_idx = PANELS_JS.find("function _auxAdvancedInputHtml")
|
||||
assert input_helper_idx >= 0
|
||||
input_helper = PANELS_JS[input_helper_idx:input_helper_idx + 1200]
|
||||
assert 'autocomplete="off"' in input_helper
|
||||
assert 'data-lpignore="true"' in input_helper
|
||||
assert 'data-1p-ignore="true"' in input_helper
|
||||
assert "aux-manual-override-value" in input_helper
|
||||
assert "aux-${id}" not in input_helper
|
||||
assert "autocompleteAttr" in input_helper
|
||||
api_key_idx = PANELS_JS.find("_auxAdvancedInputHtml('auxAdvancedApiKey'")
|
||||
assert api_key_idx >= 0
|
||||
api_key_call = PANELS_JS[api_key_idx:api_key_idx + 450]
|
||||
assert "'password'" not in api_key_call
|
||||
assert 'autocomplete="one-time-code"' in api_key_call
|
||||
assert "-webkit-text-security:disc" in api_key_call
|
||||
|
||||
def test_calls_model_auxiliary_api(self):
|
||||
"""_loadAuxiliaryModels must call /api/model/auxiliary."""
|
||||
assert "/api/model/auxiliary" in PANELS_JS, (
|
||||
@@ -149,6 +240,58 @@ class TestAuxiliaryModelsI18n:
|
||||
"settings_aux_save_failed",
|
||||
"settings_aux_saved",
|
||||
"settings_aux_no_changes",
|
||||
"settings_aux_advanced_button_title",
|
||||
"settings_aux_advanced_button_aria",
|
||||
"settings_aux_advanced_title",
|
||||
"settings_aux_advanced_subtitle",
|
||||
"settings_aux_advanced_save",
|
||||
"settings_aux_advanced_base_url",
|
||||
"settings_aux_advanced_base_url_desc",
|
||||
"settings_aux_advanced_timeout",
|
||||
"settings_aux_advanced_timeout_desc",
|
||||
"settings_aux_advanced_download_timeout",
|
||||
"settings_aux_advanced_download_timeout_desc",
|
||||
"settings_aux_advanced_max_concurrency",
|
||||
"settings_aux_advanced_max_concurrency_desc",
|
||||
"settings_aux_advanced_extra_body",
|
||||
"settings_aux_advanced_extra_body_desc",
|
||||
"settings_aux_advanced_api_key",
|
||||
"settings_aux_advanced_api_key_set_hint",
|
||||
"settings_aux_advanced_api_key_empty_hint",
|
||||
"settings_aux_advanced_api_key_clear",
|
||||
"settings_aux_advanced_extra_body_invalid_json",
|
||||
"settings_aux_advanced_extra_body_object_required",
|
||||
"settings_aux_advanced_saved",
|
||||
"settings_aux_advanced_save_failed",
|
||||
"settings_main_advanced_button_aria",
|
||||
"settings_main_advanced_title",
|
||||
"settings_main_advanced_subtitle",
|
||||
"settings_main_advanced_saved",
|
||||
"settings_main_advanced_save_failed",
|
||||
"settings_aux_task_vision",
|
||||
"settings_aux_task_vision_desc",
|
||||
"settings_aux_task_compression",
|
||||
"settings_aux_task_compression_desc",
|
||||
"settings_aux_task_web_extract",
|
||||
"settings_aux_task_web_extract_desc",
|
||||
"settings_aux_task_session_search",
|
||||
"settings_aux_task_session_search_desc",
|
||||
"settings_aux_task_approval",
|
||||
"settings_aux_task_approval_desc",
|
||||
"settings_aux_task_mcp",
|
||||
"settings_aux_task_mcp_desc",
|
||||
"settings_aux_task_title_generation",
|
||||
"settings_aux_task_title_generation_desc",
|
||||
"settings_aux_task_skills_hub",
|
||||
"settings_aux_task_skills_hub_desc",
|
||||
"settings_aux_task_curator",
|
||||
"settings_aux_task_curator_desc",
|
||||
"settings_aux_task_kanban_decomposer",
|
||||
"settings_aux_task_kanban_decomposer_desc",
|
||||
"settings_aux_task_profile_describer",
|
||||
"settings_aux_task_profile_describer_desc",
|
||||
"settings_aux_task_triage_specifier",
|
||||
"settings_aux_task_triage_specifier_desc",
|
||||
]
|
||||
|
||||
def test_all_i18n_keys_present(self):
|
||||
@@ -191,6 +334,71 @@ class TestAuxiliaryModelsBackend:
|
||||
"Missing get_auxiliary_models() in api/config.py"
|
||||
)
|
||||
|
||||
def test_backend_aux_task_slots_include_agent_defaults(self):
|
||||
"""Backend allow-list must include newer Hermes auxiliary slots."""
|
||||
for key in ("kanban_decomposer", "profile_describer", "triage_specifier"):
|
||||
assert f'"{key}"' in self.CONFIG_PY
|
||||
|
||||
def test_backend_surfaces_advanced_fields_without_api_key_value(self, monkeypatch):
|
||||
"""Advanced fields should be visible, but API keys remain write-only."""
|
||||
from api import config
|
||||
|
||||
monkeypatch.setattr(config, "reload_config", lambda: None)
|
||||
monkeypatch.setattr(config, "cfg", {
|
||||
"model": {"provider": "openai", "default": "gpt-5.5"},
|
||||
"auxiliary": {
|
||||
"vision": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-5.5",
|
||||
"base_url": "https://example.invalid/v1",
|
||||
"timeout": 42,
|
||||
"download_timeout": 7,
|
||||
"max_concurrency": 2,
|
||||
"extra_body": {"reasoning_effort": "none"},
|
||||
"api_key": "DUMMY_KEY_DO_NOT_RETURN",
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
data = config.get_auxiliary_models()
|
||||
vision = next(t for t in data["tasks"] if t["task"] == "vision")
|
||||
assert vision["base_url"] == "https://example.invalid/v1"
|
||||
assert vision["timeout"] == 42
|
||||
assert vision["download_timeout"] == 7
|
||||
assert vision["max_concurrency"] == 2
|
||||
assert vision["extra_body"] == {"reasoning_effort": "none"}
|
||||
assert vision["api_key_set"] is True
|
||||
assert "api_key" not in vision
|
||||
|
||||
def test_backend_surfaces_main_advanced_fields_without_api_key_value(self, monkeypatch):
|
||||
"""Main model advanced fields should be visible, but API keys remain write-only."""
|
||||
from api import config
|
||||
|
||||
monkeypatch.setattr(config, "reload_config", lambda: None)
|
||||
monkeypatch.setattr(config, "cfg", {
|
||||
"model": {
|
||||
"provider": "openai",
|
||||
"default": "gpt-5.5",
|
||||
"base_url": "https://example.invalid/v1",
|
||||
"timeout": 42,
|
||||
"download_timeout": 7,
|
||||
"max_concurrency": 2,
|
||||
"extra_body": {"reasoning_effort": "none"},
|
||||
"api_key": "DUMMY_KEY_DO_NOT_RETURN",
|
||||
},
|
||||
"auxiliary": {},
|
||||
})
|
||||
|
||||
data = config.get_auxiliary_models()
|
||||
main = data["main"]
|
||||
assert main["base_url"] == "https://example.invalid/v1"
|
||||
assert main["timeout"] == 42
|
||||
assert main["download_timeout"] == 7
|
||||
assert main["max_concurrency"] == 2
|
||||
assert main["extra_body"] == {"reasoning_effort": "none"}
|
||||
assert main["api_key_set"] is True
|
||||
assert "api_key" not in main
|
||||
|
||||
def test_set_auxiliary_model_function_exists(self):
|
||||
"""set_auxiliary_model() must exist in api/config.py."""
|
||||
assert "def set_auxiliary_model" in self.CONFIG_PY, (
|
||||
@@ -234,6 +442,171 @@ class TestAuxiliaryModelsBackend:
|
||||
|
||||
assert "arbitrary_key" not in config_path.read_text(encoding="utf-8")
|
||||
|
||||
def test_set_hermes_default_model_persists_advanced_options(self, monkeypatch, tmp_path):
|
||||
"""Main-model gear-modal payload should persist supported model options."""
|
||||
from api import config
|
||||
|
||||
config_path = tmp_path / "config.yaml"
|
||||
config_path.write_text("model:\n provider: openai\n default: gpt-5.5\n", encoding="utf-8")
|
||||
monkeypatch.setattr(config, "_get_config_path", lambda: config_path)
|
||||
monkeypatch.setattr(config, "reload_config", lambda: None)
|
||||
monkeypatch.setattr(config, "invalidate_models_cache", lambda: None)
|
||||
monkeypatch.setattr(config, "resolve_model_provider", lambda model: (model, "openai", None))
|
||||
|
||||
result = config.set_hermes_default_model(
|
||||
"gpt-5.5",
|
||||
advanced={
|
||||
"base_url": "https://example.invalid/v1/",
|
||||
"timeout": "45",
|
||||
"download_timeout": "9",
|
||||
"max_concurrency": "2",
|
||||
"extra_body": {"reasoning_effort": "none"},
|
||||
"api_key": "DUMMY_KEY_DO_NOT_PRINT",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["ok"] is True
|
||||
text = config_path.read_text(encoding="utf-8")
|
||||
assert "https://example.invalid/v1" in text
|
||||
assert "timeout: 45" in text
|
||||
assert "download_timeout: 9" in text
|
||||
assert "max_concurrency: 2" in text
|
||||
assert "reasoning_effort: none" in text
|
||||
assert "DUMMY_KEY_DO_NOT_PRINT" in text
|
||||
|
||||
def test_set_auxiliary_model_persists_advanced_options(self, monkeypatch, tmp_path):
|
||||
"""Gear-modal payload should persist supported per-slot options."""
|
||||
from api import config
|
||||
|
||||
config_path = tmp_path / "config.yaml"
|
||||
config_path.write_text("auxiliary:\n vision:\n provider: auto\n model: ''\n", encoding="utf-8")
|
||||
monkeypatch.setattr(config, "_get_config_path", lambda: config_path)
|
||||
monkeypatch.setattr(config, "reload_config", lambda: None)
|
||||
|
||||
result = config.set_auxiliary_model(
|
||||
"vision",
|
||||
"openai",
|
||||
"gpt-5.5",
|
||||
advanced={
|
||||
"base_url": "https://example.invalid/v1/",
|
||||
"timeout": "45",
|
||||
"download_timeout": "9",
|
||||
"max_concurrency": "2",
|
||||
"extra_body": {"reasoning_effort": "none"},
|
||||
"api_key": "DUMMY_KEY_DO_NOT_PRINT",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["ok"] is True
|
||||
text = config_path.read_text(encoding="utf-8")
|
||||
assert "https://example.invalid/v1" in text
|
||||
assert "timeout: 45" in text
|
||||
assert "download_timeout: 9" in text
|
||||
assert "max_concurrency: 2" in text
|
||||
assert "reasoning_effort: none" in text
|
||||
assert "DUMMY_KEY_DO_NOT_PRINT" in text
|
||||
|
||||
def test_set_auxiliary_model_explicit_advanced_base_url_wins_over_custom_resolution(self, monkeypatch, tmp_path):
|
||||
"""Custom-provider auto-resolution must not clobber an explicit gear base_url."""
|
||||
from api import config
|
||||
|
||||
config_path = tmp_path / "config.yaml"
|
||||
config_path.write_text("auxiliary:\n vision:\n provider: auto\n model: ''\n", encoding="utf-8")
|
||||
monkeypatch.setattr(config, "_get_config_path", lambda: config_path)
|
||||
monkeypatch.setattr(config, "reload_config", lambda: None)
|
||||
monkeypatch.setattr(
|
||||
config,
|
||||
"resolve_model_provider",
|
||||
lambda model: (model, "custom:demo", "https://resolved.invalid/v1"),
|
||||
)
|
||||
|
||||
result = config.set_auxiliary_model(
|
||||
"vision",
|
||||
"custom:demo",
|
||||
"demo/model",
|
||||
advanced={"base_url": "https://manual.invalid/v1/"},
|
||||
)
|
||||
|
||||
assert result["ok"] is True
|
||||
text = config_path.read_text(encoding="utf-8")
|
||||
assert "https://manual.invalid/v1" in text
|
||||
assert "https://resolved.invalid/v1" not in text
|
||||
|
||||
def test_main_extra_body_becomes_runtime_request_overrides(self):
|
||||
"""The main-model extra_body option is live only if it reaches request_overrides."""
|
||||
from api import config
|
||||
|
||||
cfg = {
|
||||
"model": {
|
||||
"provider": "openai",
|
||||
"default": "gpt-5.5",
|
||||
"extra_body": {"reasoning_effort": "none"},
|
||||
}
|
||||
}
|
||||
|
||||
overrides = config._main_model_request_overrides(cfg)
|
||||
|
||||
assert overrides == {"extra_body": {"reasoning_effort": "none"}}
|
||||
assert overrides["extra_body"] is not cfg["model"]["extra_body"]
|
||||
|
||||
|
||||
|
||||
def test_set_hermes_default_model_clear_api_key_removes_key(self, monkeypatch, tmp_path):
|
||||
"""Clearing a write-only API key override should remove the key, not persist api_key: ''."""
|
||||
from api import config
|
||||
|
||||
config_path = tmp_path / "config.yaml"
|
||||
config_path.write_text(
|
||||
"model:\n provider: openai\n default: gpt-5.5\n api_key: old-secret\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setattr(config, "_get_config_path", lambda: config_path)
|
||||
monkeypatch.setattr(config, "reload_config", lambda: None)
|
||||
monkeypatch.setattr(config, "invalidate_models_cache", lambda: None)
|
||||
monkeypatch.setattr(config, "resolve_model_provider", lambda model: (model, "openai", None))
|
||||
|
||||
result = config.set_hermes_default_model(
|
||||
"gpt-5.5",
|
||||
advanced={"api_key_clear": True, "api_key": ""},
|
||||
)
|
||||
|
||||
assert result["ok"] is True
|
||||
text = config_path.read_text(encoding="utf-8")
|
||||
assert "old-secret" not in text
|
||||
assert "api_key" not in text
|
||||
|
||||
def test_set_auxiliary_model_clears_empty_extra_body(self, monkeypatch, tmp_path):
|
||||
"""Blank extra_body from the modal should remove config noise instead of writing {}."""
|
||||
from api import config
|
||||
|
||||
config_path = tmp_path / "config.yaml"
|
||||
config_path.write_text(
|
||||
"auxiliary:\n vision:\n provider: openai\n model: gpt-5.5\n extra_body:\n reasoning_effort: none\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setattr(config, "_get_config_path", lambda: config_path)
|
||||
monkeypatch.setattr(config, "reload_config", lambda: None)
|
||||
|
||||
result = config.set_auxiliary_model("vision", "openai", "gpt-5.5", advanced={"extra_body": {}})
|
||||
|
||||
assert result["ok"] is True
|
||||
assert "extra_body" not in config_path.read_text(encoding="utf-8")
|
||||
|
||||
def test_set_auxiliary_model_validates_extra_body_object(self, monkeypatch, tmp_path):
|
||||
"""extra_body must stay an object, not arbitrary JSON."""
|
||||
from api import config
|
||||
|
||||
config_path = tmp_path / "config.yaml"
|
||||
config_path.write_text("auxiliary: {}\n", encoding="utf-8")
|
||||
monkeypatch.setattr(config, "_get_config_path", lambda: config_path)
|
||||
|
||||
try:
|
||||
config.set_auxiliary_model("vision", "openai", "gpt-5.5", advanced={"extra_body": ["bad"]})
|
||||
except ValueError as exc:
|
||||
assert "extra_body must be a JSON object" in str(exc)
|
||||
else:
|
||||
raise AssertionError("set_auxiliary_model accepted non-object extra_body")
|
||||
|
||||
def test_model_set_route_returns_400_for_unknown_auxiliary_task(self, monkeypatch):
|
||||
"""The route should surface invalid auxiliary task names as a client error."""
|
||||
from types import SimpleNamespace
|
||||
|
||||
Reference in New Issue
Block a user