From 6a362d58a952c2de0d3097b03b212388a6bc35e4 Mon Sep 17 00:00:00 2001 From: tanaka takashi Date: Sat, 27 Jun 2026 17:00:42 +0900 Subject: 一般設定の追加(UI/Ollama設定) settings.jsonに設定を保存 UIから言語設定対応 Ollama設定でシステムプロンプト, temperature, コンテキスト長などのパラメータを設定可能 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ollama_web/routes/chat.py | 25 +++++++++++++++++-------- src/ollama_web/routes/pages.py | 16 ++++++++++++++-- src/ollama_web/routes/settings_route.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 src/ollama_web/routes/settings_route.py (limited to 'src/ollama_web/routes') diff --git a/src/ollama_web/routes/chat.py b/src/ollama_web/routes/chat.py index 30794be..c8cbcff 100644 --- a/src/ollama_web/routes/chat.py +++ b/src/ollama_web/routes/chat.py @@ -20,6 +20,7 @@ from ..i18n import t from ..mcp import collect_mcp_tools, make_mcp_executor from ..prompts import get_prompt from ..sessions import SessionStore, is_valid_id +from ..settings_store import load_app_settings from ..tools.fetch import pop_fetched_files from ..tools.helper.image import resize_image from ..tools.registry import ToolRegistry, default_registry @@ -27,17 +28,18 @@ from ..tools.registry import ToolRegistry, default_registry logger = logging.getLogger("ollama_web.chat") -# System prompt injected when tools are enabled. Encourages the model to use -# tools iteratively rather than firing many searches at once. -_TOOL_SYSTEM_PROMPT = get_prompt("tool_system") - - def _limit_text(text: str, limit: int) -> str: if len(text) <= limit: return text return text[:limit] + "\n…[truncated]" +def _compose_system_prompt(language: str, custom_prompt: str) -> str: + """Combine the localized built-in tool prompt with the user prompt.""" + tool_prompt = get_prompt("tool_system", lang=language) + return f"{tool_prompt}\n\n{custom_prompt}" if custom_prompt else tool_prompt + + def _parse_messages(raw: list[dict[str, Any]]) -> list[Message]: """Convert raw JSON message dicts into ``ollama.Message`` objects.""" out: list[Message] = [] @@ -153,6 +155,13 @@ async def _chat_event_stream( """Core chat loop yielding events. Exceptions are converted to SSE error events.""" model = payload.get("model") or request.app.state.settings.default_model think = payload.get("think") + runtime_settings = request.app.state.settings + app_settings = load_app_settings(runtime_settings.data_dir, runtime_settings.language) + language = str(app_settings["ui"]["language"]) + ollama_settings = app_settings["ollama"] + ollama_options = dict(ollama_settings["options"]) + custom_prompt = str(ollama_settings["system_prompt"]) + system_prompt = _compose_system_prompt(language, custom_prompt) store: SessionStore = request.app.state.session_store file_ids: list[str] = [ @@ -202,7 +211,7 @@ async def _chat_event_stream( # are available. Avoid duplicate injection if the session already has # a system message. if not any(getattr(m, "role", None) == "system" for m in messages): - messages.insert(0, Message(role="system", content=_TOOL_SYSTEM_PROMPT)) + messages.insert(0, Message(role="system", content=system_prompt)) host = request.app.state.settings.ollama_host @@ -238,7 +247,7 @@ async def _chat_event_stream( async for event in llm.astream_chat_with_tools( messages, model, think=think, host=host, registry=registry, - capabilities=capabilities, + capabilities=capabilities, options=ollama_options, language=language, ): if event.get("type") == "delta": assistant_text = _limit_text( @@ -255,7 +264,7 @@ async def _chat_event_stream( # and re-renders in the chat pane. assistant_text = ( f"[ERROR] {error_msg}\n\n" - f"{t('errors.vision_model_required')}" + f"{t('errors.vision_model_required', lang=language)}" ) finally: diff --git a/src/ollama_web/routes/pages.py b/src/ollama_web/routes/pages.py index 45a3a01..7bf81e1 100644 --- a/src/ollama_web/routes/pages.py +++ b/src/ollama_web/routes/pages.py @@ -10,24 +10,34 @@ from starlette.templating import Jinja2Templates from ollama_web import llm from ollama_web.i18n import get_messages, t +from ollama_web.settings_store import load_app_settings _TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "templates" templates = Jinja2Templates(directory=str(_TEMPLATES_DIR)) templates.env.globals["t"] = t +def _page_i18n(request: Request) -> tuple[str, dict[str, object]]: + runtime = request.app.state.settings + app_settings = load_app_settings(runtime.data_dir, runtime.language) + language = str(app_settings["ui"]["language"]) + return language, get_messages(language) + + async def index(request: Request) -> Response: """Render the chat page with the available model list.""" host = request.app.state.settings.ollama_host models = llm.list_models(host=host) default_model = request.app.state.settings.default_model + language, messages = _page_i18n(request) return templates.TemplateResponse( request, "index.html", { "models": models, "default_model": default_model, - "messages": get_messages(), + "messages": messages, + "t": lambda key, fallback=None: t(key, lang=language, fallback=fallback), }, media_type="text/html; charset=utf-8", headers={"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0"}, @@ -36,11 +46,13 @@ async def index(request: Request) -> Response: async def login(request: Request) -> Response: """Render the PIN login page.""" + language, messages = _page_i18n(request) return templates.TemplateResponse( request, "login.html", { - "messages": get_messages(), + "messages": messages, + "t": lambda key, fallback=None: t(key, lang=language, fallback=fallback), }, media_type="text/html; charset=utf-8", headers={"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0"}, diff --git a/src/ollama_web/routes/settings_route.py b/src/ollama_web/routes/settings_route.py new file mode 100644 index 0000000..560f8d9 --- /dev/null +++ b/src/ollama_web/routes/settings_route.py @@ -0,0 +1,30 @@ +"""Application settings API endpoints.""" + +from __future__ import annotations + +from starlette.requests import Request +from starlette.responses import JSONResponse + +from ..settings_store import ( + SettingsValidationError, + load_app_settings, + save_app_settings, +) + + +async def get_settings(request: Request) -> JSONResponse: + """GET /api/settings: return effective persisted application settings.""" + runtime = request.app.state.settings + return JSONResponse(load_app_settings(runtime.data_dir, runtime.language)) + + +async def put_settings(request: Request) -> JSONResponse: + """PUT /api/settings: validate and persist application settings.""" + try: + body = await request.json() + cleaned = save_app_settings(request.app.state.settings.data_dir, body) + except SettingsValidationError as exc: + return JSONResponse({"error": str(exc)}, status_code=400) + except Exception: # noqa: BLE001 + return JSONResponse({"error": "Invalid JSON body"}, status_code=400) + return JSONResponse(cleaned) -- cgit v1.2.3