feat: settings web UI (models / STT / TTS speed / language / LLM instructions)

Adds /settings (served by the bridge) to change the LLM model (from installed
Ollama models), Whisper model, TTS engine + MeloTTS speed, output language,
agentic max-turns, thinking mode, and free-form LLM instructions — live, with a
'apply' that restarts the bridge + TTS worker. Settings persist to the runtime
config JSON; engine reads output_language + llm_instructions and the TTS worker
reads melo_speed from it. Bridge port publishable for access.
This commit is contained in:
javis-bot
2026-06-15 13:05:46 +09:00
parent 3bdc7d078a
commit 84e435f916
6 changed files with 233 additions and 2 deletions

View File

@@ -826,6 +826,20 @@ def _build_enrichment_context_hint(cfg, recent_messages: list) -> Optional[str]:
# Site tokens (proper nouns, not language patterns) → controlBrowser search site.
def _extra_config(key: str, default=""):
"""Read a key from the runtime config JSON (JARVIS_CONFIG_PATH) for settings
the settings-web UI manages but that aren't on the Settings dataclass
(llm_instructions, output_language override). Cheap + fail-open."""
try:
import json as _json
from pathlib import Path as _Path
p = os.environ.get("JARVIS_CONFIG_PATH")
path = _Path(p).expanduser() if p else (_Path.home() / ".config" / "jarvis" / "config.json")
return _json.loads(path.read_text("utf-8")).get(key, default) or default
except Exception:
return default
_SITE_TOKEN_MAP = {
"네이버": "naver", "naver": "naver",
"구글": "google", "google": "google",
@@ -1683,7 +1697,7 @@ def run_reply_engine(db: "Database", cfg, tts: Optional[Any],
# it primacy over the persona's "use the user's language" lines — a tail
# instruction loses to those when the query itself is in another language.
_lang_directive = reply_language_directive(
os.environ.get("OUTPUT_LANGUAGE"),
os.environ.get("OUTPUT_LANGUAGE") or _extra_config("output_language", ""),
getattr(cfg, "tts_engine", "piper"),
)
if _lang_directive:
@@ -1768,6 +1782,11 @@ def run_reply_engine(db: "Database", cfg, tts: Optional[Any],
# else: tools are passed via the native tools API parameter — do not include tools_desc
# here as well, since that confuses the model and causes it to not use tools properly.
# User-defined extra LLM instructions from the settings UI.
_user_instructions = str(_extra_config("llm_instructions", "")).strip()
if _user_instructions:
guidance.append("Additional instructions from the operator:\n" + _user_instructions)
# Recency reinforcement: repeat the language lock at the very END too.
# In a ~5k-token prompt the front-placed rule gets "lost in the middle";
# bigger models (qwen2.5:7b) otherwise leak Chinese/Cyrillic mid-reply.