6.4 KiB
6.4 KiB
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
CHAT_GRAPH_ROUTEwithNEW_CHAT_ROUTE(landing) andCHAT_ROUTE("chat/{conversationId}")PROMPTS_LIBRARY_ROUTEis co-located here for prompts library screen- When a new conversation starts from the landing page,
pendingNavigationConversationIdtriggers navigation tochat/{id}after the first stream completes (data persisted to Room), keepingnew_chatin the back stack navigateToChat()replaces the current chat entry when switching between chats so back returns tonew_chat
Message Tree
- Messages stored flat, linked by
parentMessageId(root =NO_PARENTUUID) MessageTree.ktbuilds tree viabuildActiveMessagePath(messages, activeBranches)activeBranches: Map<String, Int>tracks which sibling is displayed per parentswitchBranch(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
ContentPartRendererdispatches byContentPart.type:text->MarkdownContent(custom regex parser with LaTeX support)think-> collapsible thinking blocktool_call->StreamingToolCallCard(expandable, shows args/output)image_file/image_url-> inline AsyncImage, tap opensFullscreenImageViewererror-> red-styled error display
CodeBlockrenders fenced code with syntax highlighting, language badge, and copy button (3s checkmark)- LaTeX:
LatexBlock(block math) andLatexInline(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
- Supports
Streaming
ChatViewModel.sendMessage()callsChatRepository.startChat()which returnsFlow<StreamEvent>- Two-phase: POST /api/agents/chat -> streamId, then GET SSE stream
streamingBuffer: StringBuilderaccumulates text deltas- Tool calls tracked in
activeToolCallslist (start -> complete lifecycle) stopGeneration()cancels stream job and callschatRepository.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
PromptsLibraryScreenandPromptDetailScreenlive inchat/prompts/PromptsViewModelloads prompt groups fromPromptRepositoryhandlePromptMention()on ChatViewModel inserts prompt command text at@position
Chat Input Toolbar
ChatInputToolbarrenders toggles for enabled tools (web search, code interpreter, file search)- Tool state tracked as
Set<String>inChatUiState.enabledTools ToolsDropdownMenuprovides overflow for additional tool options- Toolbar wired into
ChatInputabove the text field
Content Cards
ImageGenCardrenders DALL-E / image generation results (spinner while generating, AsyncImage when done)LogContentCardrenders expandable log output blocks with monospace text- Both dispatched from
ContentPartRenderer— tool calls with name containingdallroute to ImageGenCard
Artifacts
ArtifactDetectorparses 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 MermaidWebContentrenders Mermaid diagrams via CDN mermaid.js with zoom controls and dark themeMarkdownWebContentrenders 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
ArtifactPanelsupports fullscreen Dialog mode, version switching, loading indicator, and WebView error overlayArtifactButtonshows type-specific icons and subtitle (e.g. "Mermaid Diagram", "React Component")ContentPartRendererwiresgroupArtifactVersions()to pass version lists to ArtifactButton/ArtifactPanelArtifactVersionNavprovides prev/next arrows with "v2/3" indicatorArtifactDownloadHelpershares artifacts via FileProvider temp file + system share sheet. Maps 25+ language extensions including.mmdfor Mermaid. Sanitizes filenames to 100 chars- Gotcha: FileProvider authority must match app's declared authority in AndroidManifest
Media Players
VideoContentPlayeruses ExoPlayer (media3) — 16:9 aspect ratio Card, lifecycle-aware releaseAudioContentPlayeruses MediaPlayer — play/pause + seekbar, 250ms polling for progressAudioContentPlayerFromBytesvariant writes bytes to temp file for MediaPlayer- Gotcha: Both players must release resources on dispose — use
DisposableEffect
Feedback Comment Dialog
- Thumbs-down opens
FeedbackCommentDialogbefore 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
MessageTimestampshows relative/absolute time, toggled on tap- Parses multiple ISO 8601 format variants; falls back gracefully on invalid input
- Integrated into
MessageBubbleaction row