Files
playchat/backend/app/main.py
T
colcaroliandClaude Sonnet 5 d207537b53 Add GUI settings panel for API keys, model presets, and embedding model
- Add a password-protected /api/settings endpoint (HTTP Basic auth,
  backend/app/core/auth.py) backed by a DB-stored allowlist of
  connection settings (OPENAI_API_KEY, ANTHROPIC_API_KEY, AWS creds,
  OLLAMA_API_BASE, TAVILY_API_KEY, LOCAL_KB_EMBEDDING_MODEL). Saved
  values override .env and apply immediately via os.environ, so
  Lightsail deployments no longer need SSH + `.env` edits to rotate
  keys. Values are always returned masked to the frontend.
- Replace ModelSelector.vue's hardcoded model list with a DB-backed
  /api/models CRUD API (read is public, writes reuse the settings
  password) so the chat model dropdown can be edited from the GUI;
  seed it once with the previous hardcoded presets on first boot.
- Fix local_knowledge.py reading LOCAL_KB_EMBEDDING_MODEL once at
  import time instead of per-call, which would have silently ignored
  GUI/DB overrides for the embedding model.
- Add SettingsPanel.vue (API keys + model management UI) and document
  the new SETTINGS_PASSWORD env var in .env.example and the Lightsail
  deploy guide.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 01:27:08 +09:00

102 lines
2.8 KiB
Python

import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api import chat, logs, memory, system
from app.api import models as models_api
from app.api import settings as settings_api
from app.core import settings_store
from app.core.config import CORS_ORIGINS
from app.core.discord.discord_bot import start_discord_bot
from app.core.discord.scheduler import start_scheduler
from app.core.memory_kb.word_memory import seed_from_history
from app.db.database import SessionLocal, init_db
from app.db.models import Message, ModelPreset
# ModelSelector.vue に元々ハードコードされていたプリセット。初回起動時のみDBへシードする。
_DEFAULT_MODEL_PRESETS = [
("GPT-5.5", "gpt-5.5"),
("GPT-5.4", "gpt-5.4"),
("GPT-5.4 mini", "gpt-5.4-mini"),
("Claude Sonnet 4.6", "claude-sonnet-4-6"),
("Claude Haiku 4.5", "claude-haiku-4-5-20251001"),
("Ollama llama3", "ollama/llama3"),
("Ollama Gemma4", "ollama/gemma4"),
("Gemma 4 12B (llama-server)", "gpt-3.5-turbo"),
("Claude Sonnet 4.6 (Bedrock)", "bedrock/jp.anthropic.claude-sonnet-4-6"),
("Llama 3 70B (Bedrock)", "bedrock/meta.llama3-70b-instruct-v1:0"),
("Nova 2 Lite (Bedrock)", "bedrock/jp.amazon.nova-2-lite-v1:0"),
]
def _seed_word_memory_from_db() -> None:
db = SessionLocal()
try:
rows = (
db.query(Message.content)
.filter(Message.role.in_(["user", "assistant"]))
.order_by(Message.created_at.desc())
.limit(200)
.all()
)
seed_from_history([content for (content,) in rows])
finally:
db.close()
def _load_settings_into_env() -> None:
db = SessionLocal()
try:
settings_store.load_into_env(db)
finally:
db.close()
def _seed_default_models() -> None:
db = SessionLocal()
try:
if db.query(ModelPreset).first() is not None:
return
for label, value in _DEFAULT_MODEL_PRESETS:
db.add(ModelPreset(label=label, value=value))
db.commit()
finally:
db.close()
@asynccontextmanager
async def lifespan(app: FastAPI):
init_db()
_load_settings_into_env()
_seed_default_models()
_seed_word_memory_from_db()
start_scheduler()
asyncio.create_task(start_discord_bot())
yield
app = FastAPI(title="PlayChat API", version="0.1.0", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(chat.router)
app.include_router(memory.router)
app.include_router(logs.router)
app.include_router(system.router)
app.include_router(settings_api.router)
app.include_router(models_api.router)
@app.get("/health")
def health():
return {"status": "ok"}