diff --git a/lib/features/chat/providers/chat_providers.dart b/lib/features/chat/providers/chat_providers.dart index a648537..9e62349 100644 --- a/lib/features/chat/providers/chat_providers.dart +++ b/lib/features/chat/providers/chat_providers.dart @@ -293,6 +293,62 @@ class ChatMessagesNotifier extends StateNotifier> { } } +// Pre-seed an assistant skeleton message (with a given id or a new one), +// persist it to the server to keep the chain correct, and return the id. +Future _preseedAssistantAndPersist( + dynamic ref, { + String? existingAssistantId, + required String modelId, +}) async { + final api = ref.read(apiServiceProvider); + final activeConv = ref.read(activeConversationProvider); + + // Choose id: reuse existing if provided, else create new + final String assistantMessageId = + (existingAssistantId != null && existingAssistantId.isNotEmpty) + ? existingAssistantId + : const Uuid().v4(); + + // If the message with this id doesn't exist locally, add a placeholder + final msgs = ref.read(chatMessagesProvider); + final exists = msgs.any((m) => m.id == assistantMessageId); + if (!exists) { + final placeholder = ChatMessage( + id: assistantMessageId, + role: 'assistant', + content: '', + timestamp: DateTime.now(), + model: modelId, + isStreaming: true, + ); + ref.read(chatMessagesProvider.notifier).addMessage(placeholder); + } else { + // If it exists and is the last assistant, ensure we mark it streaming + try { + final last = msgs.isNotEmpty ? msgs.last : null; + if (last != null && last.id == assistantMessageId && last.role == 'assistant' && !last.isStreaming) { + ref.read(chatMessagesProvider.notifier).updateLastMessageWithFunction( + (m) => m.copyWith(isStreaming: true), + ); + } + } catch (_) {} + } + + // Persist the skeleton to the server so the web client sees a correct chain + try { + if (api != null && activeConv != null) { + final current = ref.read(chatMessagesProvider); + await api.updateConversationWithMessages( + activeConv.id, + current, + model: modelId, + ); + } + } catch (_) {} + + return assistantMessageId; +} + // Start a new chat (unified function for both "New Chat" button and home screen) void startNewChat(dynamic ref) { // Clear active conversation @@ -473,25 +529,11 @@ Future regenerateMessage( } } - // Pre-seed assistant skeleton - final String assistantMessageId = const Uuid().v4(); - final assistantMessage = ChatMessage( - id: assistantMessageId, - role: 'assistant', - content: '', - timestamp: DateTime.now(), - model: selectedModel.id, - isStreaming: true, + // Pre-seed assistant skeleton and persist chain + final String assistantMessageId = await _preseedAssistantAndPersist( + ref, + modelId: selectedModel.id, ); - ref.read(chatMessagesProvider.notifier).addMessage(assistantMessage); - try { - final msgsForSeed = ref.read(chatMessagesProvider); - await api!.updateConversationWithMessages( - activeConversation.id, - msgsForSeed, - model: selectedModel.id, - ); - } catch (_) {} // Stream response via background task (socket/dynamic channel or polling) final response = api!.sendMessage( diff --git a/lib/features/chat/views/chat_page.dart b/lib/features/chat/views/chat_page.dart index 6edb8e6..f9ea69c 100644 --- a/lib/features/chat/views/chat_page.dart +++ b/lib/features/chat/views/chat_page.dart @@ -1544,42 +1544,7 @@ class _ChatPageState extends ConsumerState { ); // ErrorBoundary } - Future _saveConversationBeforeLeaving(WidgetRef ref) async { - try { - final api = ref.read(apiServiceProvider); - final messages = ref.read(chatMessagesProvider); - final activeConversation = ref.read(activeConversationProvider); - final selectedModel = ref.read(selectedModelProvider); - - if (api == null || messages.isEmpty || activeConversation == null) { - return; - } - - // Remove trailing assistant message only if it has no text and no files - final lastMessage = messages.isNotEmpty ? messages.last : null; - if (lastMessage != null && - lastMessage.role == 'assistant' && - lastMessage.content.trim().isEmpty && - (lastMessage.files == null || lastMessage.files!.isEmpty) && - (lastMessage.attachmentIds == null || - lastMessage.attachmentIds!.isEmpty)) { - messages.removeLast(); - if (messages.isEmpty) return; - } - - // Update the existing conversation with all messages - await api.updateConversationWithMessages( - activeConversation.id, - messages, - model: selectedModel?.id, - ); - - debugPrint('DEBUG: Conversation saved before leaving'); - } catch (e) { - debugPrint('DEBUG: Failed to save conversation before leaving: $e'); - // Don't block navigation even if save fails - } - } + // Removed legacy save-before-leave hook; server manages chat state via background pipeline. void _showModelDropdown( BuildContext context,