Chat & Conversations
Last verified: 2026-03-14
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 message send
- 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 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 removes the conversation from storage
- Deleting the active conversation clears the chat
- Heartbeat conversations are excluded from the history list (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
Image Attachments
- Attach via file picker or drag-and-drop
- Images are compressed and Base64-encoded
- Sent as
image_url(OpenAI) orinline_data(Gemini) - The attachment button is only shown when the current model supports images
- Attached images are shown as a preview thumbnail (max 200dp wide) inside the user message bubble
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)
- 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 |
Encryption, serialization, file I/O, upsert logic |
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 |