Chat & Conversations
Last verified: 2026-03-20
Kai's chat system manages the message history, conversation persistence, image attachments, and speech output. Conversations are service-independent — switching providers does not affect which conversation is loaded or restored. Multiple conversations are persisted and browsable via a history sheet.
Concepts
Conversation
A persisted chat session containing an id (UUID), message list, timestamps (createdAt, updatedAt), a title, and a type (chat or heartbeat). Conversations are stored in an encrypted file and restored across app launches.
History
The in-memory message list that drives the UI. Each entry has a role: USER, ASSISTANT, TOOL, or TOOL_EXECUTING. History is the source of truth during a session; it is written to a Conversation on save.
Conversation Title
Auto-derived from the first user message when a conversation is saved for the first time. Truncated to ~50 characters at a word boundary. Once set, titles are not updated.
Conversation Lifecycle
- On launch, the latest conversation (by
updatedAt) is restored automatically - "New Chat" clears history and unsets the current conversation ID
- A new conversation ID (UUID) is generated on first successful save (after the first assistant response)
- Conversations are saved after each assistant response
- Only the most recent 20 exchanges are persisted per conversation
- Multiple conversations are persisted — starting a new chat preserves previous conversations
- Conversations are service-independent — switching services does not affect which conversation is loaded
Chat History
- A history icon appears in the top bar when saved conversations other than the current one exist
- Tapping it opens a bottom sheet listing all chat conversations sorted by last updated (newest first)
- Each item shows the title and formatted date
- The active conversation is highlighted with the primary color
- Tapping an item loads that conversation and dismisses the sheet
- Each item has a delete button that defers deletion with a snackbar "Undo" option (~4 seconds) before the conversation is permanently removed
- Deleting the active conversation clears the chat
- Heartbeat conversations are included in the history list with a "Heartbeat" label badge, and can also be accessed via the heartbeat banner
Message Sending
- User message is added to history, then an API call is made via the fallback chain
- Tool calls are executed inline (TOOL_EXECUTING shown during execution, TOOL result stored after)
- On success, the conversation is saved
- On failure, an error is displayed with a retry button
Cancel
- While a request is in progress, a stop button replaces the send button in the input field
- Clicking stop cancels the ongoing API request and any in-flight tool executions
- After cancellation, the loading state clears and the send button reappears when typing
Retry & Regenerate
- Retry resends the current prompt
- Regenerate removes all messages after the last user message, then resends
File Attachments
Three categories of files can be attached:
Images
- Attach via file picker or drag-and-drop
- Compressed to JPEG and Base64-encoded
- Sent as
image_url(OpenAI),imageblock (Anthropic), orinline_data(Gemini) - Shown as a preview thumbnail (max 200dp wide) inside the user message bubble
Text files
- Supports
.txt,.md,.json,.csv,.xml,.yaml,.html,.css,.js,.ts,.kt,.py,.rs,.go,.c,.cpp,.swift,.sh,.sql,.toml,.ini,.log,.gradle, and more - Maximum size: 100 KB
- Content is decoded from base64 at serialization time and prepended to the message as plain text with a filename header
- Works with all providers (content is inlined as text)
- Shown as a filename chip in the user message bubble
PDFs
- Base64-encoded without compression
- Sent as
documentblock (Anthropic),fileblock (OpenAI), orinline_data(Gemini) - Shown as a filename chip in the user message bubble
General behavior
- The attachment button is always shown (text files work with all models)
- Unsupported file types (e.g.,
.zip) show an error message - Text files exceeding the size limit show a size error
- File attachments persist across conversation save/restore via
fileNamefield
Speech Output (TTS)
- Toggle in the top bar enables auto-play of new assistant messages
- Per-message play button on assistant messages
- Markdown is stripped before speaking
Conversation Storage
- Conversations are stored in an encrypted file (
conversations.enc) - XOR-based encryption with a 32-byte random key stored in app settings
- The full conversation list is serialized as
ConversationsData(versioned, currently v2) - Conversations are upserted — updating a conversation replaces the existing entry by ID, new conversations are appended
UI Elements
- Top bar: New Chat, Chat History, TTS toggle, Settings (on mobile; on non-mobile, Settings is in the navigation tab bar)
- Scroll to bottom: a small floating action button (down arrow) appears when the user has scrolled up past the latest messages; tapping it animates back to the bottom
- Messages: user (right-aligned, with optional image preview), assistant (Markdown-rendered + action buttons), tool executing (spinner), loading indicator, error with retry
- Input: text field, send/stop button, attachment button, file chip
- Empty state: animated logo + welcome message
- Drag-and-drop: supported for file attachments
- History sheet: bottom sheet listing saved conversations with title, date, active highlight, and delete
Key Files
| File | Purpose |
|---|---|
composeApp/src/commonMain/.../data/Conversation.kt |
Conversation and message data classes, type constants |
composeApp/src/commonMain/.../data/ConversationStorage.kt |
Serialization, settings-backed persistence, legacy migration |
composeApp/src/commonMain/.../data/FileClassification.kt |
File category enum, MIME/extension classifier, size constants, file exceptions |
composeApp/src/commonMain/.../data/RemoteDataRepository.kt |
History management, conversation save/restore/delete, title derivation, message sending |
composeApp/src/commonMain/.../ui/chat/ChatViewModel.kt |
Chat UI state, send/retry/regenerate/cancel/loadConversation/deleteConversation actions |
composeApp/src/commonMain/.../ui/chat/ChatScreen.kt |
Chat UI composables, history sheet and heartbeat banner wiring |
composeApp/src/commonMain/.../ui/chat/composables/ChatHistorySheet.kt |
Bottom sheet listing saved conversations |
composeApp/src/commonMain/.../ui/chat/composables/HeartbeatBanner.kt |
Dismissable banner for heartbeat notifications |
composeApp/src/commonMain/.../ui/chat/composables/TopBar.kt |
Top bar with new chat, history, TTS, and settings icons |
composeApp/src/commonMain/.../ui/chat/composables/QuestionInput.kt |
Text input with send/stop button |