6.6 KiB
6.6 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
- Sealed interface:
ChatRoute : NavKeywith 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,
pendingNavigationConversationIdtriggers navigation toChat(id)after the first stream completes (data persisted to Room), keepingNewChatin the back stack navigateToChat()replaces the current chat entry on theNavBackStackwhen switching between chats so back returns toNewChat
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