feat: implement full context-scoped AI chat UI in AIChatPanel

- Added AIChatPanel component with context header, user and AI message handling.
- Integrated streaming responses via IPC and error handling for chat mutations.
- Enhanced user experience with input handling and auto-scrolling features.
- Updated AppShell to derive AI chat context from the current route.
- Introduced ScrollArea component for better scrolling behavior in various dialogs.
- Added support for Tailwind typography and improved global styles.
- Updated project and task dialogs to utilize ScrollArea for better UX.
This commit is contained in:
Roberto Musso
2026-02-24 12:02:06 +01:00
parent 00a43e0fbc
commit 5eb19e022e
20 changed files with 962 additions and 91 deletions

View File

@@ -416,3 +416,29 @@
- The graph is compiled once via `buildGraph()` singleton — no per-request overhead for graph construction
- Architecture: agent logic (LangGraph) is now fully decoupled from the LLM provider. Adding a new provider only requires a new factory function in `llm.ts`
---
## 2026-02-23 - US-020
- What was implemented:
- Full context-scoped AI chat UI in `AIChatPanel` component, replacing the "coming soon" placeholder
- Two-mode layout: empty state (centered input) and chat state (messages + pinned bottom input)
- Context header: `Badge` (variant=outline) showing "Chatting about: [Project Name]" or "Global workspace"
- Context derived in AppShell from `currentPath` + `searchObj['projectId']`; project name fetched via `trpc.projects.get` query
- User messages: right-aligned `Card` components
- AI messages: left-aligned plain text (no Card) with `Sparkles` icon + bold "Adiuva" header line
- Streaming: subscribes to `window.electronAI.onStreamChunk` IPC channel before firing `trpc.ai.chat.mutate()`; tokens accumulate in `streamingContent` state via `useRef` pattern
- Loading indicator: `Skeleton` lines (w-48 + w-32) shown below Adiuva header while waiting for first token
- Error handling: mutation errors and `{ error }` responses display in `Card` with `border-destructive` styling
- Session-only history: `useEffect` on `curtainOpen` prop clears all messages, input, and streaming state when curtain closes
- Scroll behavior: after user sends, scrolls user message to top of visible area; does NOT auto-scroll during AI streaming
- Input: `Textarea` matching Figma (white bg, border #d4d4d4, shadow-lg, min-h 109px, "Ask me anything..."), Send `Button` (default variant, Send icon + label) absolute bottom-right
- Enter sends, Shift+Enter for newline
- Extracted `ChatInput` sub-component for reuse between empty and chat states
- Typecheck passes (zero errors), no new lint errors introduced
- Files changed: `src/renderer/components/ai/AIChatPanel.tsx`, `src/renderer/components/layout/AppShell.tsx`, `prd.json`, `progress.txt`
- **Learnings for future iterations:**
- `window.electronAI.onStreamChunk` returns an unsubscribe function — subscribe before firing the mutation, unsubscribe in error/completion handlers
- Use `useRef` for accumulating streaming content (`streamingContentRef.current += token`) to avoid stale closure issues in the stream callback, then sync to state with `setStreamingContent(streamingContentRef.current)`
- `trpc.ai.chat.mutate()` returns `{ response, error? }` — the `error` field is `string | undefined`, so must narrow before passing to typed state (assign to a `const` first)
- `trpc.projects.get` query with `enabled: !!projectId` + `id: projectId ?? ''` avoids both the non-null assertion lint warning and unnecessary queries
- For scroll-to-user-message UX: track the last user message with a ref and use `scrollIntoView({ behavior: 'smooth', block: 'start' })` — do NOT auto-scroll on AI streaming to let the user read from the top
---