Split the raggr service into raggr-blue (8080) and raggr-green (8081)
behind compose profiles, add a /api/health endpoint and container
healthcheck, and add scripts/deploy.sh to build the inactive color,
wait for it to become healthy, then gracefully stop the old one.
Makefile targets resolve the active color automatically.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bump Conversation.updated_at on every message (not just the first) so
actively-used conversations rise to the top of the list endpoint's
existing -updated_at sort. Refresh the sidebar after every send so the
just-used conversation reorders live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first message in a new conversation navigated to /c/:id, which remounted
ChatScreen (two separate routes) and aborted the in-flight /stream-query fetch.
Server-side that surfaced as asyncio.CancelledError, which isn't an Exception,
so the except branch missed it and skipped the add_message_to_conversation
save — the fully-streamed answer was never persisted.
Backend: handle CancelledError and persist via a detached, shielded task so a
disconnect at any point (navigation, tab close, network blip) still commits the
answer. Frontend: serve "/" and "/c/:id" from a single catch-all ChatScreen
route so first-message navigation no longer remounts and aborts the stream;
derive the conversation id via useMatch instead of useParams.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The /stream-query endpoint previously emitted only tool-lifecycle events plus
one final complete message, so the answer still appeared all at once. Handle
on_chat_model_stream events to emit incremental `content` deltas, accumulate
them for persistence fallback, and consume them on the frontend to build a live
assistant bubble (reconciled with the authoritative final text). Hide the
redundant loading indicator once tokens start arriving.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rework mobile navigation into a proper left slide-in drawer with a dimmed
backdrop, sharing a new SidebarContent component with the desktop sidebar so
mobile users can reach Admin/Scheduler/Sign out. The sidebar now loads only the
10 most recent conversations; a dedicated /conversations page browses the full
list with server-side search and "Load more" pagination.
- Backend: limit/offset/search query params on GET /api/conversation/
- SidebarContent shared by desktop sidebar and mobile drawer
- useConversationBrowser hook powers the dedicated page
- Remove duplicated inline conversation lists from ChatScreen
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Strip the card, border, shadow, and accent bar from AnswerBubble so
Simba's replies read like Claude/ChatGPT — plain full-width markdown on
the page. User messages stay bubbled for turn contrast. Widen the reading
column to max-w-3xl with roomier spacing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Give each conversation a deep-linkable URL (/c/:conversationId) so threads
survive refresh, support browser back/forward, and are shareable within a
session. The app was already an SPA but had no router.
- Add react-router-dom; wrap the app in BrowserRouter and define routes
("/" for the empty/new state, "/c/:conversationId" for a thread)
- Make the URL the source of truth for selection: selectedConversation is
derived from the :conversationId param; create/delete navigate instead of
mutating local selection state
- Load a thread's messages in a URL-keyed effect that clears immediately for
an instant switch, with a stale-response guard
- Preserve the optimistic message + in-flight stream when the first message
lazily creates a conversation and navigates into it (skip-ref keyed on the
new id, guarded against the null/home-route collision)
No backend changes: app.py already serves index.html for unmatched paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frontend:
- Add stop-generation button (wire up the unused AbortController)
- Surface stream/network errors in a dismissible banner instead of console
- Add rename and delete for conversations via a per-thread menu
- Make simba mode a single controlled source; fix miscast keydown handler
- Auto-grow the message textarea; remove dead ConversationMenu component
Backend:
- Scope get/query/stream-query/rename/delete conversation routes to the
authenticated owner via a shared _get_owned_conversation helper (fixes IDOR)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enable remark-gfm in AnswerBubble so pipe-table syntax parses instead
of showing as raw text, and add table styling to markdown-content.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Upgrades pypetkitapi to 1.28.0, which adds the Eversweet Ultra (W7H
fountain with AI camera). The poller now extracts per-pet drinking
events (drink_over/pet_detect records) into a new drinking_events
table, plus Ultra-specific alerts for waste tank full and filter
expiry. Uncertain state fields (cwt/wt/pump) are logged for threshold
tuning against real data.
Exposes the data via a new /api/pets/drinking-events endpoint, a
Fountain Activity dashboard section, and a simbakit_drinking_activity
LangChain tool so the agent can answer hydration questions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wires up the existing MealieService to the conversational agent with tools
for querying today's meals, weekly meal plans, full recipes, shopping lists,
and creating new meal plan entries. Adds env vars to docker-compose and
updates the system prompt so the agent knows when to use each tool.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds simbakit/ as a subfolder service that polls PetKit smart devices
(litter box, fountain, feeder) and exposes pet weight, litter activity,
and device health data via three new LangChain tools.
Also fixes stale alerts bug where cleared conditions (e.g. waste bin
full) persisted because the query used max alert time instead of the
device's last_seen_at timestamp.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Markdown-aware chunking (split on headers before size-based splitting)
- Prepend note filename to each chunk for self-contained context
- Source-filtered retrieval (obsidian/paperless queries stay isolated)
- MMR search with k=8, fetch_k=24 for better recall and diversity
- Add source metadata to Paperless docs and folder metadata to Obsidian docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When use_agent is enabled, the scheduler runs the message content as a
prompt through the LangChain agent and sends Simba's response instead of
the raw content. Frontend adds an Ask Simba toggle with visual indicator.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend scheduled messages with a recurrence field. After sending a
recurring message, the scheduler automatically creates the next pending
occurrence. Frontend adds repeat toggle (Once/Daily/Weekly/Monthly) and
displays recurrence in the messages table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Strip markdown formatting (bold, italic, headers, code, links, lists) from
LLM responses before sending via iMessage. Add scheduled messages feature
with CRUD API, background scheduler loop, and admin frontend panel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Revert get_conversation_for_user to use Conversation.get() with
MultipleObjectsReturned fallback. Add channel field to Conversation
model and get_conversation_for_channel helper so each messaging
channel gets its own isolated conversation per user.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Validates sb-signing-secret header against SENDBLUE_WEBHOOK_SECRET env var.
Can be disabled with SENDBLUE_SIGNATURE_VALIDATION=false for development.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New imessage blueprint: webhook receives inbound iMessages, runs through
LangChain agent, replies via SendBlue REST API
- Admin-only: only users with lldap_admin group can use iMessage channel
- Admin endpoints to link/unlink imessage_number on user accounts
- Add imessage_number field to User model (needs aerich migration)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch from gws calendar +agenda to gws calendar events list with
explicit timeMin/timeMax and singleEvents=true to include all-day events.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a get_calendar_events agent tool that shells out to `gws calendar +agenda`
for admin users. Controlled by GOOGLE_CALENDAR_ENABLED env var, with OAuth
credentials mounted from credentials.json.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The async/sync engine split caused visibility issues where newly indexed
files weren't found on the next cycle, triggering re-indexing of all 36
files every 60 seconds. Replace with a module-level dict that loads from
DB on cold start and stays in sync via cache updates after each indexing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes:
- Convert wikilinks to display text instead of stripping them entirely.
[[Noah]] becomes "Noah", [[target|display]] becomes "display". This
was causing names and references in wikilinks to be invisible to search.
- Switch _get_obsidian_indexed_files to async engine to avoid stale reads
from the separate sync engine, which caused files to be re-indexed
every cycle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Empty documents after sanitization caused aadd_documents to issue a
DEFAULT VALUES insert. Guard with an emptiness check. Also increase
similarity search k from 2 to 6 so multi-word queries like full names
have better recall.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
YAML frontmatter can contain datetime objects which aren't JSON
serializable. Add _make_serializable() to coerce all metadata values
before storing in pgvector.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace full delete-and-reindex with mtime-based incremental sync that
only re-indexes changed/new files and removes deleted ones. A background
polling task keeps the vector store up-to-date automatically when
OBSIDIAN_CONTINUOUS_SYNC=true.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update from journal/YYYY/YYYY-MM-DD.md to
50 - Journal/YYYY/MM/YYYY-MM-DD.md to match the actual Obsidian vault
folder layout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ob CLI uses a directory lock at .obsidian/.sync.lock that persists
across container restarts via the volume mount, causing "Another sync
instance is already running" errors. Remove it before starting sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ob login blocks waiting for interactive input when OBSIDIAN_EMAIL or
OBSIDIAN_PASSWORD is empty. Check required env vars before attempting
login to skip sync gracefully with a warning instead of hanging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Run ob login and sync-setup in foreground before backgrounding sync to
prevent "Another sync instance is already running" error. Restrict the
catch-all route to only serve whitelisted static file extensions to
prevent sensitive files like credentials.json from being exposed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace OBSIDIAN_AUTH_TOKEN with OBSIDIAN_EMAIL/OBSIDIAN_PASSWORD and run
the full ob login → sync-setup → sync sequence on container startup so
fresh containers authenticate properly instead of failing with
"Run 'ob sync-setup' first".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Disable tiktoken pre-encoding for custom embedding servers. LangChain
was encoding text into OpenAI token IDs then sending them to llama-server
which has a different vocabulary, causing "invalid tokens" errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Indexes chunks one at a time with error logging to identify which
document/chunk causes embedding failures. Also strips Unicode surrogates
and replacement characters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Strips null bytes, control characters, and excessive whitespace from
document content before sending to embedding models. Fixes 400 errors
from BERT-based tokenizers (e.g. nomic-embed-text) on PDF-extracted text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>