Librechat-Mobile/feature/chat/CLAUDE.md

102 lines
6.6 KiB
Markdown

# feature:chat
## Screen States
`ChatScreenState` enum: `LANDING` | `LOADING` | `ACTIVE`
- **Landing**: no conversation selected, shows greeting + model icon + optional conversation starters
- **Loading**: spinner while fetching messages for a conversation
- **Active**: message list + streaming content + input area
## Navigation
- Sealed interface: `ChatRoute : NavKey` with typed route classes
- Routes: `NewChat` (landing), `Chat(conversationId: String? = null)`, `PromptsLibrary`, `PromptEditor(groupId: String? = null)` (all `@Serializable`)
- Feature entries registered via `EntryProviderScope<NavKey>.chatEntries()`
- When a new conversation starts from the landing page, `pendingNavigationConversationId` triggers navigation to `Chat(id)` after the first stream completes (data persisted to Room), keeping `NewChat` in the back stack
- `navigateToChat()` replaces the current chat entry on the `NavBackStack` when switching between chats so back returns to `NewChat`
## Message Tree
- Messages stored flat, linked by `parentMessageId` (root = `NO_PARENT` UUID)
- `MessageTree.kt` builds tree via `buildActiveMessagePath(messages, activeBranches)`
- `activeBranches: Map<String, Int>` tracks which sibling is displayed per parent
- `switchBranch(parentMessageId, siblingIndex)` updates branch selection and rebuilds display list
- Editing a user message creates a new sibling (new branch); regenerating an AI message also creates a sibling
## Content Rendering
- `ContentPartRenderer` dispatches by `ContentPart.type`:
- `text` -> `MarkdownContent` (custom regex parser with LaTeX support)
- `think` -> collapsible thinking block
- `tool_call` -> `StreamingToolCallCard` (expandable, shows args/output)
- `image_file` / `image_url` -> inline AsyncImage, tap opens `FullscreenImageViewer`
- `error` -> red-styled error display
- `CodeBlock` renders fenced code with syntax highlighting, language badge, and copy button (3s checkmark)
- LaTeX: `LatexBlock` (block math) and `LatexInline` (inline math) via AndroidMath native rendering
- Supports `$$...$$`, `\[...\]` (block) and `$...$`, `\(...\)` (inline)
- `$...$` requires heuristic check (`looksLikeLatex()`) to avoid false positives on dollar amounts
- Falls back to monospace text if AndroidMath can't parse the expression
## Streaming
- `ChatViewModel.sendMessage()` calls `ChatRepository.startChat()` which returns `Flow<StreamEvent>`
- Two-phase: POST /api/agents/chat -> streamId, then GET SSE stream
- `streamingBuffer: StringBuilder` accumulates text deltas
- Tool calls tracked in `activeToolCalls` list (start -> complete lifecycle)
- `stopGeneration()` cancels stream job and calls `chatRepository.abortChat()`
- `onPause()`/`onResume()` handle app backgrounding: checks stream status, resumes if still active
## Key Components
| Component | Purpose |
|-----------|---------|
| `ChatInput` | Auto-growing text field, send/stop button, file attach, voice |
| `MessageBubble` | Icon + content column + action bar + sibling nav |
| `MessageList` | LazyColumn of MessageBubbles with scroll-to-bottom FAB |
| `ModelSelectorSheet` | Bottom sheet: search + endpoint-grouped model list |
| `ModelSelectorButton` | Header chip showing current model |
| `SiblingNavigator` | "1/3" with chevrons for branch switching |
| `LandingContent` | Greeting, entity icon, conversation starters |
| `StreamingIndicator` | Blinking cursor during active generation |
| `PresetPicker` | Preset selection menu |
| `SavePresetDialog` | Dialog to name and save current config as preset |
## Prompts
- `PromptsLibraryScreen` and `PromptDetailScreen` live in `chat/prompts/`
- `PromptsViewModel` loads prompt groups from `PromptRepository`
- `handlePromptMention()` on ChatViewModel inserts prompt command text at `@` position
## Chat Input Toolbar
- `ChatInputToolbar` renders toggles for enabled tools (web search, code interpreter, file search)
- Tool state tracked as `Set<String>` in `ChatUiState.enabledTools`
- `ToolsDropdownMenu` provides overflow for additional tool options
- Toolbar wired into `ChatInput` above the text field
## Content Cards
- `ImageGenCard` renders DALL-E / image generation results (spinner while generating, AsyncImage when done)
- `LogContentCard` renders expandable log output blocks with monospace text
- Both dispatched from `ContentPartRenderer` — tool calls with name containing `dall` route to ImageGenCard
## Artifacts
- `ArtifactDetector` parses remark-directive format `:::artifact{identifier="id" type="..." title="..."}` with backtick-fenced content; `groupArtifactVersions()` groups by identifier
- Supported types: `text/html`, `image/svg+xml`, `application/vnd.react`, `application/vnd.mermaid`, `text/markdown`/`text/md`, `text/plain`, `application/vnd.code-html`
- `MermaidWebContent` renders Mermaid diagrams via CDN mermaid.js with zoom controls and dark theme
- `MarkdownWebContent` renders Markdown via CDN marked.js + highlight.js with GFM and syntax highlighting
- HTML/React/SVG templates include Tailwind CDN, theme CSS vars, and error handling
- React template preprocesses import/export statements for browser-compatible execution
- `ArtifactPanel` supports fullscreen Dialog mode, version switching, loading indicator, and WebView error overlay
- `ArtifactButton` shows type-specific icons and subtitle (e.g. "Mermaid Diagram", "React Component")
- `ContentPartRenderer` wires `groupArtifactVersions()` to pass version lists to ArtifactButton/ArtifactPanel
- `ArtifactVersionNav` provides prev/next arrows with "v2/3" indicator
- `ArtifactDownloadHelper` shares artifacts via FileProvider temp file + system share sheet. Maps 25+ language extensions including `.mmd` for Mermaid. Sanitizes filenames to 100 chars
- **Gotcha**: FileProvider authority must match app's declared authority in AndroidManifest
## Media Players
- `VideoContentPlayer` uses ExoPlayer (media3) — 16:9 aspect ratio Card, lifecycle-aware release
- `AudioContentPlayer` uses MediaPlayer — play/pause + seekbar, 250ms polling for progress
- `AudioContentPlayerFromBytes` variant writes bytes to temp file for MediaPlayer
- **Gotcha**: Both players must release resources on dispose — use `DisposableEffect`
## Feedback Comment Dialog
- Thumbs-down opens `FeedbackCommentDialog` before submitting; thumbs-up fires immediately
- Comment is optional — empty string is a valid submission
- Toggling off an existing thumbs-down (already selected) calls `onFeedback(null)` directly, skipping the dialog
## Message Timestamps
- `MessageTimestamp` shows relative/absolute time, toggled on tap
- Parses multiple ISO 8601 format variants; falls back gracefully on invalid input
- Integrated into `MessageBubble` action row