Skip to content

Enhance KaiChat architecture with CoreMessageRepository and UI improvements#32

Merged
pckimlong merged 10 commits into
mainfrom
dev
Apr 11, 2026
Merged

Enhance KaiChat architecture with CoreMessageRepository and UI improvements#32
pckimlong merged 10 commits into
mainfrom
dev

Conversation

@pckimlong

Copy link
Copy Markdown
Owner

This pull request introduces a new, ready-to-use AI chat controller (KaiChatController) for easier integration of AI-powered conversations, and adds a flexible, callback-based message repository (CoreMessageRepository). It also includes various code style cleanups for improved readability and consistency.

Major new features:

AI Chat Controller:

  • Added KaiChatController, a concrete implementation of ChatControllerBase, providing a simple factory for initializing AI chat functionality with sensible defaults, support for persistent/in-memory storage, and extensible context and processing pipelines. Comprehensive documentation and usage examples are included.

Message Repository Enhancements:

  • Introduced CoreMessageRepository, a callback-based message repository that allows easy integration with custom persistence backends (such as Firebase or SQLite) by providing callbacks for loading, saving, updating, and removing messages. It maintains an internal state and ensures messages are sorted by timestamp.
  • Improved InMemoryMessageRepository to automatically sort messages by timestamp on initialization for consistent ordering.

Code style and consistency improvements:

Formatting and Readability:

  • Refactored multiple methods across chat_controller_base.dart, conversation_manager.dart, and message_repository_base.dart to use single-line expressions and more concise formatting, improving code readability and reducing unnecessary line breaks. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16]

Minor logic improvements:

  • Changed error state updates in ChatControllerBase to use the _setState method for consistency.

These changes make it much easier to integrate and extend AI chat functionality in applications, and provide a flexible foundation for message persistence and state management.

…stence

- Add CoreMessageRepository implementation with simplified callback API
- Add automatic timestamp-based sorting for InMemoryMessageRepository
- Add onInitial callback for loading messages from persistence
- Add onPut callback for persisting insert/update operations (upsert)
- Add optional onRemove callback for persisting deletions
- Add session caching and initialization state tracking
- Add comprehensive test coverage for both
refactor(kai_engine): add KaiChatController and improve code formatting

- Add KaiChatController as ready-to-use chat controller implementation
- Add comprehensive documentation with usage examples for basic, persistent storage, and custom context scenarios
- Add static create() factory method with async initialization support
- Add optional callbacks for onPromptsReady and onStateChange
- Add systemPrompt parameter to SimpleContextEngine for customization
- Update CoreMessageRepository callback
refactor(chat_ui): migrate KaiChatView to controller-based architecture with enhanced customization

- Replace direct message/state props with ChatControllerBase for reactive state management
- Add StreamBuilder integration for messages and generation state streams
- Add onMessageSent and onError callbacks for better event handling
- Add messageItemBuilder, transientItemBuilder, and functionCallBuilder for granular customization
- Add showTimeLabel parameter to display message timestamps
- Rename
refactor(adapters): improve code formatting and add timestamp preservation in FirebaseAiContentAdapter

- Format multi-line getters and factory methods to single lines in CoreMessage
- Format long class declarations and method calls to single lines
- Add timestamp preservation logic when converting Content to CoreMessage
- Extract contentJson variable for better readability
- Add timestamp parsing from stored content with fallback handling
- Store timestamp in extensions for future round-trip conversions
chore: bump version to 0.2.0 across all packages

- Update kai_engine from 0.1.1 to 0.2.0
- Update kai_engine_chat_ui from 0.1.1 to 0.2.0
- Update kai_engine_firebase_ai from 0.1.1 to 0.2.0
- Update prompt_block from 0.1.1 to 0.2.0
```
- Change _formatTime to display HH:mm format instead of relative time
- Use _setState instead of direct stream add in error handling for consistency
…ty state support

- Reduce default bubble widths (mobile: 320→280, desktop: 480→400)
- Reduce bubble and list padding for tighter layout
- Change bottom item spacing from theme.itemSpacing to 4px for first item
- Adjust bubble corner radius from 6 to 4 for sharper corners
- Add inline send/stop button option to KaiComposer with customization (icon, size, color)
- Add avatarBuilder support for custom message avatars
- Add em

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request upgrades the kai_engine suite to version 0.2.0, introducing significant architectural improvements and UI enhancements. Key changes include the addition of KaiChatController for simplified initialization, a new callback-based CoreMessageRepository for persistence, and a major refactor of KaiChatView to support extensive customization via new builders for avatars, function calls, and empty states. The UI now integrates gpt_markdown for message rendering and features an improved composer with an inline button mode. My feedback focuses on addressing state management bugs in the new repository implementation, ensuring correct timestamp preservation during message conversion, and preventing potential data loss in the composer if asynchronous send operations fail.

Comment on lines +106 to +114
if (_cachedSession != null && _cachedSession != session) {
_cachedSession = session;
_messages.clear();
} else {
_cachedSession = session;
}
_isInitialized = true;
_messages.addAll(messages);
await onPut(session, messages);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of saveMessages has a bug in state management. By manually setting _isInitialized = true and clearing/updating _messages without calling onInitial, it prevents getMessages from ever loading existing persisted messages if a save occurs before the first load. Additionally, it doesn't ensure the internal list remains sorted. Calling getMessages(session) at the start of saveMessages would correctly handle initialization, session switching, and loading existing data.

    await getMessages(session);
    _messages.addAll(messages);
    _messages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
    await onPut(session, messages);

extensions['_timestamp'] = timestamp.toIso8601String();
}

return CoreMessage.create(type: messageType, content: combinedText, extensions: extensions);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The timestamp preservation logic is currently broken because CoreMessage.create ignores any external timestamp and always uses DateTime.now(). This will result in incorrect message ordering when reloading history from Firebase. You should apply the parsed timestamp to the created message using copyWith.

Suggested change
return CoreMessage.create(type: messageType, content: combinedText, extensions: extensions);
return CoreMessage.create(
type: messageType,
content: combinedText,
extensions: extensions,
).copyWith(timestamp: timestamp ?? DateTime.now());

Comment on lines +123 to +128
for (var message in messages) {
final index = _messages.indexWhere((m) => m.messageId == message.messageId);
if (index != -1) {
_messages[index] = message;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The updateMessages method only updates existing messages in the local state but passes all messages to onPut. Since onPut is intended for upsert operations, the local state should also reflect this by adding messages that aren't already present. Also, the list should be re-sorted to maintain the timestamp order invariant.

    for (var message in messages) {
      final index = _messages.indexWhere((m) => m.messageId == message.messageId);
      if (index != -1) {
        _messages[index] = message;
      } else {
        _messages.add(message);
      }
    }
    _messages.sort((a, b) => a.timestamp.compareTo(b.timestamp));

Comment on lines +181 to 188
// Clear text immediately for responsive UX (like Resonate pattern)
_controller.clear();
setState(() => _sending = true);

try {
await widget.onSend(text);
if (mounted) _controller.clear();
} catch (e, s) {
widget.onError?.call(e, s);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Clearing the text controller immediately before the asynchronous onSend call can lead to data loss if the operation fails. If a network error occurs, the user's input is gone and they must re-type it. It's better to store the original text and restore it in the catch block to provide a better user experience.

Suggested change
// Clear text immediately for responsive UX (like Resonate pattern)
_controller.clear();
setState(() => _sending = true);
try {
await widget.onSend(text);
if (mounted) _controller.clear();
} catch (e, s) {
widget.onError?.call(e, s);
final originalText = _controller.text;
_controller.clear();
setState(() => _sending = true);
try {
await widget.onSend(text);
} catch (e, s) {
_controller.text = originalText;
widget.onError?.call(e, s);

@pckimlong pckimlong merged commit 3088d78 into main Apr 11, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant