Commit Graph
14 Commits
Author SHA1 Message Date
colcaroliandClaude Opus 5 c1aba42cf8 Schedule reminders by mentioning the Discord bot
メンション本文にリマインド系のキーワードが含まれていたとき、LLM に
create_reminder / list_reminders / cancel_reminder の3ツールを渡し、
自然言語から日時と用件を取り出して reminders テーブルに保存する。
system メッセージに現在時刻を入れてあるので『明日』『30分後』のような
相対表現はモデル側が絶対日時に直す。扱うのは一回きりの通知のみ。

配信は APScheduler の discord_reminders ジョブ(既定30秒間隔)が担い、
Webhook ではなく Bot クライアントから登録元チャンネルへ依頼者を
メンションして送るため、専用の Webhook URL 設定は要らない。

- 日時は他テーブルと同じく UTC で保存し、ユーザー・LLM とのやり取りは
  ローカル時刻で行って reminders.py の境界で変換する
- 過去・REMINDER_MAX_AHEAD_DAYS より先・解釈不能な日時はツール結果として
  エラー文をモデルに返し、聞き直させる
- 一覧・取り消しは依頼者の user_id で絞るため他人のリマインドには触れない
- 配信に5回失敗したら notified_at を立てて諦める(チャンネル削除などで
  ポーリングのたびにエラーログを出し続けるのを防ぐ)。カウンタは
  プロセス内のみなので、再起動すればまた試す

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 23:29:55 +09:00
colcaroliandClaude Opus 5 97568ba90e Re-embed the knowledge base when the embedding model changes
Vectors from two different embedding models are not comparable — Titan v2 has
1024 dimensions where nomic-embed-text has 768 — and _cosine_similarity zips
them, so a mismatch produced quietly meaningless scores rather than an error.
Nothing forced a re-embed either: sync_local_kb only looked at the content
hash, so switching models left every cached vector in place.

local_knowledge_docs now records the model each vector came from, and a row is
re-embedded when its content hash OR its model differs. Saving a new
LOCAL_KB_EMBEDDING_MODEL from the settings panel starts a background pass over
every file; the knowledge base panel also has a button for it. While that pass
runs, the per-request sync steps aside so no file is embedded twice.

The panel now shows what the embedding model is doing: model name, vector
size, how many files are embedded with the current model, how many are still
on an older one, a progress bar while a pass runs, and the last error. A
"接続テスト" button embeds one short string so a broken or unreachable model
can be told apart from one that is merely slow to warm up.

Also read TAVILY_API_KEY from os.environ at call time. It was imported as a
config constant, which is evaluated before load_into_env() runs, so a key
saved from the settings panel was stored but never used — not even after a
restart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 22:56:12 +09:00
colcaroliandClaude Opus 5 cd38cbf19d Set the search summary model from the settings panel
Summarizing web search results falls back to DEFAULT_MODEL, so a local Ollama
model ends up doing it and the call can fail — which surfaces only as
"保存できる要約なし" in the chat. Make SEARCH_SUMMARY_MODEL settable at runtime:
add it to the settings allowlist and read it from os.environ on each call
(knowledge._summary_model), the way LOCAL_KB_EMBEDDING_MODEL already works.
Importing it as a config constant would keep the old value until a restart.

Show the current value of the settings that are not secrets. A model name
rendered as ••••qwen3 gives no way to tell what is in effect, so the store now
marks those keys (NON_SECRET_KEYS) and returns them unmasked; the API field is
renamed masked -> display_value and carries a secret flag that the panel uses
to pick the input type, replacing the list it kept on its own side.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 22:10:36 +09:00
colcaroliandClaude Opus 5 0e58a07af4 Pin notes to a chat, and import notes from files
Two additions to the shared notes, both about getting content in and out of
the prompt.

Per-chat pinning. Notes normally reach the model as a title list only, and
the body arrives just if the model decides to call read_note. A 📌 on the tab
now puts a page's full body in the system prompt for the chat that is open,
every turn, no tool round trip. The selection is per session (new table
session_note_pages, unique on session_id + page_id), so different
conversations carry different pages. The title list marks pinned pages so the
model does not read them twice, and bodies are capped per page by
SESSION_NOTES_CONTENT_LIMIT.

A new chat has no session row until its first message, so the front end
cannot PUT the selection yet. POST /chat/stream therefore accepts
note_page_ids and applies it before building the context, which makes the
pins count from the very first turn.

File import. Dropping .md/.txt on the editor - or picking them with the new
⬆ button - creates pages, and a .zip is unpacked into one page per text
entry with the first path component as the section. That is exactly the
shape the export writes, so a downloaded zip round-trips back in. Titles that
collide get a " (2)" suffix rather than overwriting, since restoring a backup
into a live workspace should not destroy anything. UploadFile needs
python-multipart, added to the lockfile.

Toasts grew a kind: the import success message was rendering in the red error
toast, as was the existing session-import message. Both are green now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 16:04:41 +09:00
colcaroliandClaude Opus 5 be3af44891 Store timestamps as UTC-aware and slim the knowledge context to headlines
タイムゾーン:
SQLite の DATETIME は tzinfo を保存できず、DateTime(timezone=True) を
指定してもバインド時に捨てられるため、API のレスポンスがオフセット無しの
ISO 文字列になりフロントがローカル時刻として誤解していた。UtcDateTime
(TypeDecorator) を追加し、書き込み時は naive な UTC に正規化、読み出し時は
UTC の aware として返すようにする。格納フォーマットは変わらないので既存
データはそのまま正しく解釈でき、マイグレーションは不要。
コンテナは TZ 未設定で UTC だったため、天気通知の cron 時刻・深夜スキップ
判定・「明日」の日付計算が9時間ずれていた。TZ=Asia/Tokyo を指定し、slim
イメージに無い tzdata を導入する。

蓄積知識:
層1が最新15件の要約全文を毎リクエスト無条件に注入しており、同じ内容が
ローカルKB(層3)にも保存されているため重複していた。層1を「クエリ+
一行要約」の目次に変え、本文は read_knowledge ツールで読み出す形にする。
一行要約は要約生成時の既存 LLM 呼び出しに相乗りするので呼び出し回数は
増えない。headline を持たない既存エントリは本文の先頭行から導出する。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 14:22:59 +09:00
colcaroliandClaude Opus 5 a08b1785a4 Add shared notes with tabbed pages editable by user and AI
Adds a note_pages table backing a tabbed notes panel where the user and
the model can both read and write. Only page titles (plus a short
preview) are injected into the system prompt on every turn; full page
content is kept out of context and fetched on demand via a read_note
tool, with write_note letting the model append to, replace, or create a
page.

Generalizes llm.resolve_web_search into resolve_tool_calls so the
tool-calling loop handles web_search and the new note tools through one
path. The chat stream emits a notes_updated SSE event when the model
edits a note so the frontend panel can refresh; local edits are tracked
as dirty and never clobbered by that refresh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 01:27:31 +09:00
colcaroliandClaude Sonnet 5 1ad8b130c7 Add site-wide login gate protecting the whole app
Chat/memory/logs/models APIs were reachable without any auth even
though the app is deployed publicly behind Caddy HTTPS; only the
settings screen had a password. Add an optional SITE_PASSWORD-backed
session cookie gate in front of everything (settings included, on
top of its existing SETTINGS_PASSWORD), with a matching login screen
on the frontend. Stays fully backward compatible: with SITE_PASSWORD
unset the app behaves exactly as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 23:04:01 +09:00
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
colcaroliandClaude Sonnet 5 8e01458bb2 Add Docker/Lightsail deployment, web search summarization, and fix vite peer dep
- Add backend/frontend Dockerfiles, nginx reverse proxy config, and
  docker-compose.yml to run the app on AWS Lightsail; document the setup
  in docs/deploy-lightsail.md. Fix a .gitignore path anchoring bug that
  left backend/knowledge_base/web_search/ untracked instead of ignored.
- Summarize web search results into structured Markdown via LLM before
  saving them to the local knowledge base, and support regenerating
  existing entries from their source URLs (backend/app/core/memory_kb).
- Downgrade vite to ^6 to match @vitejs/plugin-vue's peer dependency
  range, fixing a failing `npm ci` (found while testing the Docker build).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 02:45:09 +09:00
colcaroli 347230e3c8 Reorganize core into subpackages, add local KB, ruff/pyright tooling
- Split backend/app/core into weather/, discord/, memory_kb/, search/
  subpackages by responsibility, keeping config.py and llm.py as the
  shared foundation; updated all import sites accordingly.
- Add local Markdown knowledge base support (local_knowledge.py,
  knowledge_base/fgo.md, LOCAL_KB_* settings) that embeds and injects
  relevant docs into the system prompt by topic similarity.
- Add ruff and pyright as backend dev dependencies with basic config
  in pyproject.toml for linting and type checking.
- Document local machine quirks in CLAUDE.md (Postgres on port 8080,
  SSLKEYLOGFILE breaking litellm/pyright via OPENSSL_Uplink crashes).
- Add backend/run_dev.ps1 dev launcher and docs/discord-bot.md.
2026-06-21 11:44:11 +09:00
colcaroli 234ebba103 Add web search knowledge base, message tagging, and word-memory tip topics
Introduces websearch.py (Tavily) + knowledge.py to ground chat responses
in search results, tagging.py to tag user and assistant messages via LLM
and pick related topics for the Discord useless-tips job, and
word_memory.py to track recent conversation nouns (surfaced in the new
WordPool.vue sidebar) used to pick tip topics close to a related tag.
2026-06-20 18:06:06 +09:00
colcaroliandClaude Sonnet 4.6 4e837d1419 Add weather caching, useless-tip redirect, clock widget, and IAM Bedrock auth
- Cache the latest fetched tomorrow weather and expose it via /api/system/weather/latest
- Add a header Clock widget showing date/time plus the cached weather summary
- Redirect useless-talk mentions from the weather bot to the useless-tips webhook
- Switch Bedrock auth from bearer token to standard IAM access key/secret

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 15:15:11 +09:00
colcaroliandClaude Sonnet 4.6 4a08ba29fa Add system memory monitoring, Bedrock support, and Discord integrations
- Add CPU/GPU memory monitoring endpoint and frontend widget
- Add Amazon Bedrock model support (API key / inference profiles) plus model selector entries
- Add Discord weather/laundry notification job (JMA forecast + LLM-generated message)
- Add Discord bot that replies to mentions via the configured LLM, with weather-aware context
- Add scheduled "useless tips" job with separate webhook and quiet-hours skip
- Switch FastAPI startup to lifespan handler, log viewer, markdown rendering, and toast UI additions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 00:59:38 +09:00
colcaroli 903d34761c initial_commit 2026-06-01 20:16:53 +09:00