build: Bump Android Gradle Plugin to 8.9.1

fix(chat): Improve Markdown formatter state management

- Explicitly clear the streaming formatter when switching conversations to prevent state leakage.
- Implement defensive checks to ensure the formatter is always associated with the correct message, resetting it if a mismatch is detected.
- Refine the formatter seeding logic to only use existing content in resume scenarios, preventing content duplication.
- Add detailed logging to the chat provider for better debugging of streaming and formatter behavior.
This commit is contained in:
cogwheel0
2025-10-14 22:16:32 +05:30
parent c07ac6b427
commit 14af2a100a
2 changed files with 54 additions and 2 deletions

View File

@@ -18,7 +18,7 @@ pluginManagement {
plugins { plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.3" apply false id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false
} }

View File

@@ -153,6 +153,7 @@ class ChatMessagesNotifier extends Notifier<List<ChatMessage>> {
// Cancel any existing message stream when switching conversations // Cancel any existing message stream when switching conversations
_cancelMessageStream(); _cancelMessageStream();
_clearStreamingFormatter(); // Explicitly clear formatter on conversation switch
_stopRemoteTaskMonitor(); _stopRemoteTaskMonitor();
if (next != null) { if (next != null) {
@@ -268,15 +269,34 @@ class ChatMessagesNotifier extends Notifier<List<ChatMessage>> {
} }
void _ensureFormatterForMessage(ChatMessage message) { void _ensureFormatterForMessage(ChatMessage message) {
// If we're switching to a different message, clear the old formatter first
if (_markdownFormatter != null && _activeStreamingMessageId != message.id) {
DebugLogger.log(
'Clearing formatter for message switch: $_activeStreamingMessageId -> ${message.id}',
scope: 'chat/providers',
);
_clearStreamingFormatter();
}
// If formatter already exists for this message, reuse it
if (_markdownFormatter != null && _activeStreamingMessageId == message.id) { if (_markdownFormatter != null && _activeStreamingMessageId == message.id) {
return; return;
} }
// Create new formatter
final formatter = MarkdownStreamFormatter(); final formatter = MarkdownStreamFormatter();
// Only seed with existing content if this is a resume scenario
// For new messages (empty content), start fresh to avoid duplication
final seed = _stripStreamingPlaceholders(message.content); final seed = _stripStreamingPlaceholders(message.content);
if (seed.isNotEmpty) { if (seed.isNotEmpty && message.content.isNotEmpty) {
DebugLogger.log(
'Seeding formatter with existing content (${seed.length} chars) for message ${message.id}',
scope: 'chat/providers',
);
formatter.seed(seed); formatter.seed(seed);
} }
_markdownFormatter = formatter; _markdownFormatter = formatter;
_activeStreamingMessageId = message.id; _activeStreamingMessageId = message.id;
} }
@@ -548,10 +568,25 @@ class ChatMessagesNotifier extends Notifier<List<ChatMessage>> {
} }
if (!lastMessage.isStreaming) { if (!lastMessage.isStreaming) {
// Ignore late chunks when streaming already finished // Ignore late chunks when streaming already finished
DebugLogger.log(
'Ignoring late chunk for finished message: ${lastMessage.id}',
scope: 'chat/providers',
);
return; return;
} }
_ensureFormatterForMessage(lastMessage); _ensureFormatterForMessage(lastMessage);
// Defensive check: ensure the formatter is for the correct message
// This prevents cross-message pollution when messages change rapidly
if (_activeStreamingMessageId != lastMessage.id) {
DebugLogger.warning(
'Formatter message ID mismatch: active=$_activeStreamingMessageId, last=${lastMessage.id}. Resetting formatter.',
);
_clearStreamingFormatter();
_ensureFormatterForMessage(lastMessage);
}
final formatter = _markdownFormatter!; final formatter = _markdownFormatter!;
final preview = formatter.ingest(content); final preview = formatter.ingest(content);
@@ -572,7 +607,24 @@ class ChatMessagesNotifier extends Notifier<List<ChatMessage>> {
return; return;
} }
// Log content replacement for debugging
DebugLogger.log(
'Replacing message content: messageId=${lastMessage.id}, '
'oldLength=${lastMessage.content.length}, newLength=${content.length}',
scope: 'chat/providers',
);
_ensureFormatterForMessage(lastMessage); _ensureFormatterForMessage(lastMessage);
// Defensive check: ensure the formatter is for the correct message
if (_activeStreamingMessageId != lastMessage.id) {
DebugLogger.warning(
'Formatter message ID mismatch in replace: active=$_activeStreamingMessageId, last=${lastMessage.id}. Resetting formatter.',
);
_clearStreamingFormatter();
_ensureFormatterForMessage(lastMessage);
}
final formatter = _markdownFormatter!; final formatter = _markdownFormatter!;
final sanitized = formatter.replace(_stripStreamingPlaceholders(content)); final sanitized = formatter.replace(_stripStreamingPlaceholders(content));