-
Notifications
You must be signed in to change notification settings - Fork 0
Enhance KaiChat architecture with CoreMessageRepository and UI improvements #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2dee88f
49a07fa
b552cdd
5eb3fc0
fbee811
d947542
1b8a618
08dc1eb
0ca72e8
f4b0ff8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,9 +24,11 @@ abstract interface class CoreMessageRepositoryBase | |
| extends MessageRepositoryBase<CoreMessage> {} | ||
|
|
||
| /// Prebuilt repository for memory persistence. | ||
| /// Messages are automatically sorted by timestamp on initialization. | ||
| final class InMemoryMessageRepository implements CoreMessageRepositoryBase { | ||
| InMemoryMessageRepository({Iterable<CoreMessage>? initialMessages}) | ||
| : _messages = initialMessages?.toList() ?? []; | ||
| : _messages = (initialMessages?.toList() ?? []) | ||
| ..sort((a, b) => a.timestamp.compareTo(b.timestamp)); | ||
|
|
||
| final List<CoreMessage> _messages; | ||
|
|
||
|
|
@@ -62,3 +64,93 @@ final class InMemoryMessageRepository implements CoreMessageRepositoryBase { | |
| return Future.value(messages); | ||
| } | ||
| } | ||
|
|
||
| /// Prebuilt repository for callback-based persistence. | ||
| /// | ||
| /// Simplified API with only 2 required callbacks: | ||
| /// - [onInitial]: Load initial messages from persistence | ||
| /// - [onPut]: Persist insert or update operations (upsert) | ||
| /// - [onRemove]: Optional callback for persisting deletions | ||
| /// | ||
| /// Note: Callbacks are for persistence only. The repository maintains | ||
| /// its own internal state for immediate read operations. Messages are | ||
| /// automatically sorted by timestamp on initial load. | ||
| final class CoreMessageRepository implements CoreMessageRepositoryBase { | ||
| final Future<Iterable<CoreMessage>> Function(ConversationSession session) | ||
| onInitial; | ||
| final Future<void> Function( | ||
| ConversationSession session, | ||
| Iterable<CoreMessage> messages, | ||
| ) | ||
| onPut; | ||
| final Future<void> Function(Iterable<CoreMessage> messages)? onRemove; | ||
|
|
||
| final List<CoreMessage> _messages = []; | ||
| ConversationSession? _cachedSession; | ||
| bool _isInitialized = false; | ||
|
|
||
| CoreMessageRepository({ | ||
| required this.onInitial, | ||
| required this.onPut, | ||
| this.onRemove, | ||
| }); | ||
|
|
||
| @override | ||
| Future<Iterable<CoreMessage>> getMessages(ConversationSession session) async { | ||
| if (!_isInitialized || _cachedSession != session) { | ||
| _cachedSession = session; | ||
| final loaded = await onInitial(session); | ||
| _messages.clear(); | ||
| _messages.addAll(loaded); | ||
| _messages.sort((a, b) => a.timestamp.compareTo(b.timestamp)); | ||
| _isInitialized = true; | ||
| } | ||
| return _messages; | ||
| } | ||
|
|
||
| @override | ||
| Future<Iterable<CoreMessage>> saveMessages({ | ||
| required ConversationSession session, | ||
| required Iterable<CoreMessage> messages, | ||
| }) async { | ||
| if (_cachedSession != null && _cachedSession != session) { | ||
| _cachedSession = session; | ||
| _messages.clear(); | ||
| } else { | ||
| _cachedSession = session; | ||
| } | ||
| _isInitialized = true; | ||
| _messages.addAll(messages); | ||
| await onPut(session, messages); | ||
| return messages; | ||
| } | ||
|
|
||
| @override | ||
| Future<Iterable<CoreMessage>> updateMessages( | ||
| Iterable<CoreMessage> messages, | ||
| ) async { | ||
| if (_cachedSession == null) { | ||
| throw StateError( | ||
| 'Session not initialized. Call getMessages or saveMessages first.', | ||
| ); | ||
| } | ||
| for (var message in messages) { | ||
| final index = _messages.indexWhere( | ||
| (m) => m.messageId == message.messageId, | ||
| ); | ||
| if (index != -1) { | ||
| _messages[index] = message; | ||
| } | ||
| } | ||
|
Comment on lines
+137
to
+144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 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)); |
||
| await onPut(_cachedSession!, messages); | ||
| return messages; | ||
| } | ||
|
|
||
| @override | ||
| Future<void> removeMessages(Iterable<CoreMessage> messages) async { | ||
| _messages.removeWhere( | ||
| (message) => messages.map((e) => e.messageId).contains(message.messageId), | ||
| ); | ||
| await onRemove?.call(messages); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation of
saveMessageshas a bug in state management. By manually setting_isInitialized = trueand clearing/updating_messageswithout callingonInitial, it preventsgetMessagesfrom ever loading existing persisted messages if a save occurs before the first load. Additionally, it doesn't ensure the internal list remains sorted. CallinggetMessages(session)at the start ofsaveMessageswould correctly handle initialization, session switching, and loading existing data.