summaryrefslogtreecommitdiff
path: root/src/ollama_web/routes
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-06-27 17:00:42 +0900
committertanaka takashi <isazaka@gmail.com>2026-06-27 17:00:42 +0900
commit6a362d58a952c2de0d3097b03b212388a6bc35e4 (patch)
tree0034d4e8dee2a1dcac2a1c2c1d71ad41c0e455ea /src/ollama_web/routes
parentff09d6ed60998e31f8baf0846b9bdd31282979ed (diff)
一般設定の追加(UI/Ollama設定) settings.jsonに設定を保存 UIから言語設定対応 Ollama設定でシステムプロンプト, temperature, コンテキスト長などのパラメータを設定可能
Diffstat (limited to 'src/ollama_web/routes')
-rw-r--r--src/ollama_web/routes/chat.py25
-rw-r--r--src/ollama_web/routes/pages.py16
-rw-r--r--src/ollama_web/routes/settings_route.py30
3 files changed, 61 insertions, 10 deletions
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)