Commit Graph

70 Commits

Author SHA1 Message Date
Ryan Chen 62416fcf2e Drop overlay sits above the artifact sheet below lg
Beside the panel (lg and up) it still covers only the chat column. Below
lg the panel is a fixed z-40 sheet, so the overlay covers the window at
z-[45] (under the z-50 mobile drawer) instead of being hidden by it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:45:56 -04:00
Ryan Chen 35faeb0fb7 Frontend: attach files by picker or drop, with an attachment chip
- pendingImage becomes pendingAttachment (image or PDF/TXT/MD/CSV), uploaded
  through POST /api/conversation/upload and sent as image_key or file_key.
- MessageInput shows an icon/name/size chip for files, an inline error for
  unsupported or oversized files, and a paperclip picker.
- DropOverlay covers the chat area with "Drop to attach" while files are
  dragged over the window; drags without files are ignored.
- QuestionBubble renders a saved trailing "📎 name" as a chip.
- test-fixtures/cowork/mock-server.mjs: dependency-free mock API (static
  dist/, conversations, contract-shaped SSE, uploads, artifacts) for
  Playwright runs without Docker or OIDC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:43:05 -04:00
Ryan Chen b6bb9ec8cf Merge artifact scenarios into the shared Cowork mock server
One mock server now serves both the timeline scenarios and the artifact
panel's: per-kind fixtures from artifacts/<kind>.json, an "everything"
conversation with one saved tool message per kind, keyword scenarios
(recipe, budget, trend, philz, everything, broken, mystery, ...), a
500ing "broken" artifact, and small delays on /api/artifacts* so the
panel's loading states are visible. SPENDING now uses positive amounts
like the backend.

Also: renderers reuse ArtifactSourceRef, and the collapsed rail's
buttons say "Show <title>" so they don't share the timeline chip's
"Open <title>" accessible name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:40:31 -04:00
Ryan Chen d065b70667 Reconcile artifact renderers and fixtures with the backend shapes
Workers A/B are merged, so match what blueprints/ynab/tools.py and
conversation/agents.py actually emit:

- ynab.spending_report rows and ynab.summary.spent are positive for
  spending; bars use the positive amount and a net refund (negative
  row) sorts last with no bar.
- ynab.transactions start_date may be null (open-ended search); the
  header reads "Through Oct 12, 2026". Amounts stay signed with
  outflows as −$, and total is the net.
- Recipe image_url is never sent, search.documents has no source_ref,
  memo/age_of_money/goal_target are optional; fixtures now match
  (backend titles, ordering, float quantities, no empty fields).
- Mock server: drop the placeholder recipe image, positive month
  spending, and a "philz" open-ended transactions scenario.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:37:36 -04:00
Ryan Chen 0ae87ee610 Add artifact panel with per-kind renderers
Right-hand pane (440px, lg and up) with tabs, collapse rail and a
library of the conversation's artifacts; a bottom sheet below lg.
useArtifactPanel holds tabs/active and caches fetched artifacts by id;
tabs reset on conversation change.

Artifacts from tool_end auto-open on windows >= 1280px (newest wins,
and a panel the user collapsed stays collapsed). ChatScreen exposes
openArtifact for the activity timeline's onOpenArtifact and adds an
Artifacts button to the chat header.

One renderer per v1 kind (YNAB summary, category budget, spending
report, transactions; Mealie recipe, meal plan, shopping lists;
Obsidian note; document search) using CSS bars only, plus a JSON
fallback for unknown kinds or renderer errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:37:33 -04:00
Ryan Chen 396e24f2f0 Add artifact API service, per-kind fixtures and Cowork mock server
artifactTypes.ts mirrors the contract (worker C creates the same file).
artifactService.ts wraps GET /api/artifacts and /api/artifacts/<id>
with the shared fetchWithAuth helper.

test-fixtures/cowork/artifacts/<kind>.json holds one ToolArtifact per
v1 kind. mock-server.mjs is a dependency-free Node server that serves
dist/ and speaks the contract (SSE v2 tool events, artifacts API) for
Playwright checks without Docker or OIDC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:37:07 -04:00
Ryan Chen e7bade119c Timeline: calendar icon for verb-first calendar tools
get_calendar_events / create_calendar_event never matched the "calendar"
prefix and fell back to the wrench icon; match "calendar" anywhere in the
tool name instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:34:06 -04:00
Ryan Chen f85607d62d Activity timeline replaces ToolBubble
Tool calls in a turn now render as one ActivityTimeline block: expanded
with "Simba is working…" while running (icon, label, input summary,
spinner/✓/✗, duration, Open chip for artifacts), collapsing to
"Simba worked · N steps · 4.2 s" when the turn finishes. useChat keys tool
rows by run_id (parallel calls), synthesises ids for legacy servers that
send only {tool}, and restores rows from the conversation payload.

Adds src/api/artifactTypes.ts (shared with the artifact panel) and a
dependency-free mock API server under test-fixtures/cowork/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:34:06 -04:00
Ryan Chen 10f3f1bcab Merge origin/main into feat/ynab-mirror
Resolve the Makefile .PHONY conflict (desktop + ynab targets) and
regenerate the YNAB migration with aerich as 17 on top of main's
16_add_oidc_states, replacing the colliding 16_add_ynab_mirror.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:06:47 -04:00
Ryan Chen 9249f92db8 Fix YNAB mirror review findings
- Full resyncs mark mirror rows missing from the download as deleted;
  YNAB omits deleted entities from non-delta responses, so deletions
  made just before a full sync never reached the mirror.
- YNAB tools return errors as text instead of raising, since ToolNode
  re-raises and would fail the whole chat turn. ynab_sql catches all
  sqlglot errors (e.g. an unbalanced quote) and runs the SQL regenerated
  from the validated tree, which also drops comments that broke the
  LIMIT wrapper. Results keep duplicate column names.
- Sync fetches from YNAB before opening the DB transaction, discards the
  payload if the other container synced meanwhile, re-checks freshness
  under the lock so parallel tool calls share one sync, and backs off
  for 60s after a failure.
- The mirror is pinned to one budget; a changed YNAB_BUDGET_ID errors
  instead of mixing budgets, and an unset id stays on the budget the
  first sync resolved from "last-used".
- Change log: no entries for empty month rollover rows, and id fields
  are logged as names (category, payee, account). ynab_recent_changes
  accepts the names it prints ("assignment") and plurals.
- Outputs sized to the 4000-char tool result cap; ynab_sql reuses the
  shared admin message.
- YNAB API client moves back to utils/ynab_service.py, and the frontend
  tool status labels list the new tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:58:14 -04:00
Ryan Chen 004a9bc85d Frontend: send access token, refresh once on 401
Collapse fetchWithRefreshToken/fetchWithAuth into a single fetchWithAuth
that sends the access token, refreshes up front when none is stored, and
on a 401 refreshes once (deduplicated across concurrent calls) and retries.
The refresh token is now only sent to /api/user/refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:44:34 -04:00
Ryan Chen 9777eaf9e1 Fix ThinkingBubble position: render above streaming answer not below
The ThinkingBubble was rendering after the messages list, so it appeared
below the partial answer while streaming. Inject it inside the messages
map just before the last streaming simba message so it sits above the
answer text. Keep the post-list fallback for when thinking is active
but no content has arrived yet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-12 16:43:48 -04:00
Ryan Chen bb2103e81e Stream qwen3 thinking tokens with collapsible UI panel
Route <think>…</think> content from the llama-server stream into
thinking_delta SSE events instead of discarding it. A small look-ahead
buffer in _route_content() handles tags split across chunk boundaries.
Strip thinking tags from the persisted final_message so the DB stores
only the actual response.

Frontend: ThinkingBubble component accumulates the live reasoning text
in a scrollable panel that auto-collapses to "Thought for Xs" once the
first content token arrives. Falls back to the existing heartbeat
behaviour for models that don't emit <think> tags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-12 16:26:25 -04:00
Ryan Chen 8fd6fa9ece Add TTFT watchdog to detect and surface LLM stalls
When llama-server is queue-backed it accepts the connection but never
produces tokens; after ~60s Caddy drops the connection and the frontend
clears loading state with no response and no error shown to the user.

Backend: replace the async-for event loop with an explicit __anext__()
iterator wrapped in asyncio.wait_for(). A 30s timeout (TTFT_TIMEOUT_SECONDS)
applies only in the window between on_chat_model_start and the first content
token — tool calls are excluded. On timeout, emits a stalled error event and
logs it rather than waiting for the proxy to kill the connection.

Frontend: separates stall errors from generic errors; shows a "Simba didn't
respond in time. Retry" pill that re-sends the original query.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-12 16:10:44 -04:00
Ryan Chen b132efe6a0 Don't cancel stream on navigation away
Aborting the fetch on component unmount sent a disconnect to the server,
which cancelled the in-flight generation. Now the stream runs to
completion in the background (isMountedRef guards prevent stale state
updates), the response is persisted, and it's there when the user
returns to the conversation. stopGeneration() still aborts immediately
on explicit user cancel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-12 15:29:53 -04:00
Ryan Chen d7639d9d0a Show a thinking indicator while the model reasons
qwen3.8-27b is a reasoning model: it can think for hundreds of tokens
before any visible content streams. The SSE stream only forwarded
content deltas, so the UI showed dead air for the whole think and long
answers looked hung (users cancel ~1 min in).

The stream now emits a throttled {"type": "thinking"} heartbeat while
the current model turn has produced no content yet, and the frontend
renders a pulsing "Thinking..." pill until content or a tool call
arrives. The heartbeat also keeps bytes flowing on the SSE connection
during long thinks, so idle-timeout disconnects in the proxy chain
can't kill the stream.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-12 15:28:43 -04:00
Ryan Chen 1e4942e0b8 Allow pasting images into the chat input 2026-09-11 17:25:13 -04:00
Ryan Chen bddd741fcc Fix missing React import in AnswerBubble 2026-09-11 16:40:48 -04:00
Ryan Chen b23fd19c4b Copy button + hover highlight on assistant messages 2026-09-11 16:27:51 -04:00
Ryan Chen 20d44bb3b3 Autofocus textarea on new conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-10 14:14:59 -04:00
Ryan Chen 3318839551 Sort conversations by last activity
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>
2026-09-10 01:33:44 -04:00
Ryan Chen 4fe5b8daea Persist streamed assistant answers even if the client disconnects
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>
2026-09-10 01:33:44 -04:00
Ryan Chen b918522816 Stream assistant answers token-by-token over SSE
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>
2026-09-09 23:14:08 -04:00
Ryan Chen 09dd983c4e Replace inline mobile menu with slide-in drawer and paginate conversations
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>
2026-09-09 22:32:19 -04:00
Ryan Chen 530a90f237 Render assistant replies as full-width text instead of bubbles
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>
2026-09-09 22:19:58 -04:00
Ryan Chen 6a62db2e85 Add per-conversation URLs with client-side routing
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>
2026-09-09 22:10:47 -04:00
Ryan Chen 32285891fc Clean up frontend chat interactions and scope conversation access to owner
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>
2026-09-09 09:53:53 -04:00
Ryan Chen 7aef6484c3 Render GFM tables in chat replies
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>
2026-09-09 09:38:54 -04:00
Ryan Chen 9bccac82f3 Add "Ask Simba" option to scheduled messages
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>
2026-06-04 11:15:11 -04:00
Ryan Chen 467e752629 Add recurring scheduled messages (daily, weekly, monthly)
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>
2026-06-03 23:30:30 -04:00
Ryan Chen f5203e0466 Add scheduled messages and strip markdown from iMessage responses
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>
2026-06-03 23:25:10 -04:00
Ryan Chen 4ac0754ea7 Refactor frontend to hook-based architecture
Extract logic from god components into custom hooks (useAuthCheck,
useConversations, useChat, usePresignedUrl, useAdminUsers, useOIDCAuth).
Eliminate unnecessary useEffects per React guidelines — scroll is now
imperative, isAdmin comes from useAuthCheck instead of a separate fetch.
ConversationList becomes a pure presentational component. Wrap bubble
components in React.memo.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 09:11:57 -04:00
Ryan Chen f3ae76ce68 Fix mobile conversation launch resetting to homepage
Remove the useEffect on selectedConversation.id that race-conditions
with handleQuestionSubmit — it fetches the (still-empty) conversation
and wipes messages, sending the user back to the empty state. Refresh
conversation list after streaming completes instead to pick up the
auto-generated title.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 22:08:26 -04:00
Ryan Chen e644def141 Fix mobile performance degradation during typing and after image upload
Memoize blob URL creation to prevent leak on every keystroke, wrap
MessageInput in React.memo with stable useCallback props, remove
expensive backdrop-blur-sm from chat footer, and use instant scroll
during streaming to avoid queuing smooth scroll animations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 06:58:53 -04:00
ryan be600e78d6 Merge pull request 'Fix images not sending in existing conversations' (#19) from fix/image-in-existing-conversations into main
Reviewed-on: #19
2026-04-04 09:08:46 -04:00
Ryan Chen b6576fb2fd Fix images not sending in existing conversations
Add missing pendingImage, onImageSelect, and onClearImage props to the
MessageInput rendered in the active chat footer, matching the homepage version.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 09:07:21 -04:00
Ryan Chen 30db71d134 Clean up presigned URL implementation: remove dead fields, fix error handling
- Remove unused image_url from upload response and TS type
- Remove bare except in serve_image that masked config errors as 404s
- Add error state and broken-image placeholder in QuestionBubble

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:52:26 -04:00
Ryan Chen 167d014ca5 Use presigned S3 URLs for serving images instead of proxying bytes
Browser <img> tags can't attach JWT headers, causing 401s. The image
endpoint now returns a time-limited presigned S3 URL via authenticated
API call, which the frontend fetches and uses directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:52:26 -04:00
Ryan Chen 0415610d64 Add image upload and vision analysis to Ask Simba chat
Users can now attach images in the web chat for Simba to analyze using
Ollama's gemma3 vision model. Images are stored in Garage (S3-compatible)
and displayed in chat history.

Also fixes aerich migration config by extracting TORTOISE_CONFIG into a
standalone config/db.py module, removing the stale aerich_config.py, and
adding missing MODELS_STATE to migration 3.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:03:19 -04:00
ryan 0f88d211de Add PWA support for install-to-home-screen
Adds manifest.json, service worker with static asset caching,
resized cat icons, and meta tags for iOS/Android installability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 20:16:27 -04:00
ryan 6a7b1369ad Add email channel via Mailgun for Ask Simba
Users can now receive a unique email address (ask+<token>@domain) and
interact with Simba by sending emails. Inbound emails hit a Mailgun
webhook, are authenticated via HMAC token lookup, processed through the
LangChain agent, and replied to via the Mailgun API.

- Extract shared SIMBA_SYSTEM_PROMPT to blueprints/conversation/prompts.py
- Add email_enabled and email_hmac_token fields to User model
- Create blueprints/email with webhook, signature validation, rate limiting
- Add admin endpoints to enable/disable email per user
- Update AdminPanel with Email column, toggle, and copy-address button
- Add Mailgun env vars to .env.example
- Include database migration for new fields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 16:21:18 -04:00
ryan 4621755c54 Make cat icons even bigger across all views
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 16:05:53 -04:00
ryan b6cd4e85f0 Fix mobile viewport scroll and enlarge cat icons
Use 100dvh for proper mobile browser chrome handling and increase
cat icon sizes across sidebar, mobile header, and empty state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 16:03:40 -04:00
ryan 30d7f0a060 Fix YNAB transaction fetching for SDK v2 compatibility
- Convert date strings to datetime.date objects before passing to API (strict Pydantic validation rejects strings)
- Use txn.var_date instead of txn.date (renamed in SDK v2 to avoid Python builtin conflict)
- Migrate BudgetsApi → PlansApi and update method names accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 18:54:20 -04:00
ryan da9b52dda1 Add Claude.ai-style homepage with centered empty state
Show centered cat icon + "Ask me anything" + input when no messages
exist. Transition to scrollable messages + bottom input once chat
starts. Auto-create a conversation on first message if none selected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 09:47:37 -04:00
ryan d1cb55ff1a Frontend revamp: Animal Crossing × Claude design with shadcn components
- New palette: deep nook green sidebar, sage user bubbles, warm cream answer cards
- shadcn-style UI primitives: Button (CVA variants), Textarea, Input, Badge, Table
- cn() utility (clsx + tailwind-merge)
- lucide-react icons throughout (no more text-only buttons)
- Simba mode: custom CSS toggle switch
- Send button: circular amber button with arrow icon
- AnswerBubble: amber gradient accent bar, loading dots animation
- QuestionBubble: sage green pill with rounded-3xl
- ToolBubble: centered leaf-green badge pill
- ConversationList: active item highlighting, proper selectedId prop
- Sidebar: collapsible with PanelLeftClose/Open icons, icon-only collapsed state
- LoginScreen: decorative background blobs, refined rounded card
- AdminPanel: proper icon buttons, leaf-green save confirmation
- Fonts: Playfair Display (brand) + Nunito 800 weight added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 09:22:34 -04:00
ryan 53b2b3b366 Add admin panel and fix simba mode response display
- Add /me, /admin/users, and WhatsApp link/unlink endpoints
- Add AdminPanel component with user management UI
- Add userService methods for admin API calls
- Fix simba mode so cat responses appear in the message list
- Fetch userinfo endpoint for groups on OIDC callback (Authelia compat)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 09:06:59 -04:00
ryan 97be5262a8 new feature 2026-03-03 08:23:31 -05:00
ryan 86cc269b3a yeet 2026-03-03 08:23:31 -05:00
ryan 6d7d713532 Replace blue/indigo sidebar colors with warm stone neutrals
Align ConversationList colors with Anthropic design guidelines,
replacing indigo-300/blue-400 with stone-200/stone-300.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 08:18:08 -05:00