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>
This commit is contained in:
2026-08-18 01:27:08 +09:00
co-authored by Claude Sonnet 5
parent 8e01458bb2
commit d207537b53
16 changed files with 788 additions and 25 deletions
+15 -3
View File
@@ -5,12 +5,18 @@ import ChatWindow from './components/ChatWindow.vue'
import MessageInput from './components/MessageInput.vue'
import ModelSelector from './components/ModelSelector.vue'
import LogViewer from './components/LogViewer.vue'
import SettingsPanel from './components/SettingsPanel.vue'
import ToastList from './components/ToastList.vue'
import MemoryMonitor from './components/MemoryMonitor.vue'
import Clock from './components/Clock.vue'
import WordPool from './components/WordPool.vue'
const showLogs = ref(false)
type SidePanel = 'none' | 'logs' | 'settings'
const activePanel = ref<SidePanel>('none')
function togglePanel(panel: SidePanel) {
activePanel.value = activePanel.value === panel ? 'none' : panel
}
</script>
<template>
@@ -25,7 +31,10 @@ const showLogs = ref(false)
<span class="title">PlayChat</span>
<ModelSelector />
<Clock />
<button class="log-toggle" :class="{ active: showLogs }" @click="showLogs = !showLogs">
<button class="log-toggle" :class="{ active: activePanel === 'settings' }" @click="togglePanel('settings')">
設定
</button>
<button class="log-toggle" :class="{ active: activePanel === 'logs' }" @click="togglePanel('logs')">
ログ
</button>
</header>
@@ -34,9 +43,12 @@ const showLogs = ref(false)
<ChatWindow />
<MessageInput />
</div>
<div v-if="showLogs" class="log-pane">
<div v-if="activePanel === 'logs'" class="log-pane">
<LogViewer />
</div>
<div v-else-if="activePanel === 'settings'" class="log-pane">
<SettingsPanel />
</div>
</div>
</div>
</div>