diff --git a/lib/core/services/api_service.dart b/lib/core/services/api_service.dart index 97eea58..e1eeecb 100644 --- a/lib/core/services/api_service.dart +++ b/lib/core/services/api_service.dart @@ -91,38 +91,6 @@ class ApiService { _dio.interceptors.add( InterceptorsWrapper( onRequest: (options, handler) { - if (options.path == '/api/chat/completions') { - debugPrint('===== SSE REQUEST DEBUG ====='); - debugPrint('Path: ${options.path}'); - debugPrint('Method: ${options.method}'); - debugPrint('Headers: ${options.headers}'); - debugPrint('Content-Type: ${options.contentType}'); - - // Log the raw data being sent - if (options.data != null) { - if (options.data is Map) { - final dataMap = options.data as Map; - DebugLogger.log('Data type: Map'); - DebugLogger.log('Data keys: ${dataMap.keys.toList()}'); - DebugLogger.log( - 'Has background_tasks: ${dataMap.containsKey('background_tasks')}', - ); - DebugLogger.log( - 'Has session_id: ${dataMap.containsKey('session_id')}', - ); - DebugLogger.log('Has id: ${dataMap.containsKey('id')}'); - DebugLogger.log( - 'Data structure logged (full data suppressed)', - ); - } else { - DebugLogger.log('Data type: ${options.data.runtimeType}'); - DebugLogger.log( - 'Data structure logged (full data suppressed)', - ); - } - } - debugPrint('===== END SSE REQUEST DEBUG ====='); - } handler.next(options); }, ), @@ -134,9 +102,7 @@ class ApiService { // Initialize validation interceptor asynchronously validationInterceptor.initialize().catchError((error) { - debugPrint( - 'ApiService: Failed to initialize validation interceptor: $error', - ); + // Handle validation initialization errors silently }); } @@ -193,13 +159,11 @@ class ApiService { result['modelsAvailable'] = models != null && models.isNotEmpty; result['modelCount'] = models?.length ?? 0; } catch (e) { - debugPrint('DEBUG: Error checking models: $e'); result['modelsAvailable'] = false; } } } catch (e) { result['error'] = e.toString(); - debugPrint('DEBUG: Server status check failed: $e'); } return result; @@ -208,27 +172,18 @@ class ApiService { // Authentication Future> login(String username, String password) async { try { - debugPrint( - 'DEBUG: Attempting login to ${serverConfig.url}/api/v1/auths/signin', - ); final response = await _dio.post( '/api/v1/auths/signin', data: {'email': username, 'password': password}, ); - debugPrint('DEBUG: Login successful, status: ${response.statusCode}'); + return response.data; } catch (e) { if (e is DioException) { - debugPrint('DEBUG: Login DioException: ${e.type}'); - debugPrint('DEBUG: Response status: ${e.response?.statusCode}'); - debugPrint('DEBUG: Response headers: ${e.response?.headers}'); - debugPrint('DEBUG: Request URL: ${e.requestOptions.uri}'); - // Handle specific redirect cases if (e.response?.statusCode == 307 || e.response?.statusCode == 308) { final location = e.response?.headers.value('location'); if (location != null) { - debugPrint('DEBUG: Server redirecting to: $location'); throw Exception( 'Server redirect detected. Please check your server URL configuration. Redirect to: $location', ); @@ -274,7 +229,6 @@ class ApiService { // Get default model configuration from OpenWebUI user settings Future getDefaultModel() async { try { - debugPrint('DEBUG: Fetching default model from user settings'); final response = await _dio.get('/api/v1/users/user/settings'); DebugLogger.log('User settings retrieved successfully'); @@ -324,19 +278,14 @@ class ApiService { // Conversations - Updated to use correct OpenWebUI API Future> getConversations({int? limit, int? skip}) async { - debugPrint('DEBUG: Fetching conversations from OpenWebUI API'); - debugPrint('DEBUG: Making request to ${serverConfig.url}/api/v1/chats/'); - debugPrint('DEBUG: Auth token present: ${authToken != null}'); - List allRegularChats = []; if (limit == null) { // Fetch all conversations using pagination - debugPrint('DEBUG: Fetching ALL conversations using pagination'); + int currentPage = 0; while (true) { - debugPrint('DEBUG: Fetching page $currentPage'); final response = await _dio.get( '/api/v1/chats/', queryParameters: {'page': currentPage}, @@ -349,12 +298,8 @@ class ApiService { } final pageChats = response.data as List; - debugPrint( - 'DEBUG: Page $currentPage returned ${pageChats.length} conversations', - ); if (pageChats.isEmpty) { - debugPrint('DEBUG: No more conversations, stopping pagination'); break; } diff --git a/lib/core/utils/debug_logger.dart b/lib/core/utils/debug_logger.dart index 970449c..410d040 100644 --- a/lib/core/utils/debug_logger.dart +++ b/lib/core/utils/debug_logger.dart @@ -7,7 +7,7 @@ class DebugLogger { /// Log debug information static void log(String message) { if (_enabled) { - debugPrint('🔍 $message'); + debugPrint('DEBUG: $message'); } } @@ -15,9 +15,9 @@ class DebugLogger { static void error(String message, [Object? error]) { if (_enabled) { if (error != null) { - debugPrint('❌ $message: $error'); + debugPrint('ERROR: $message: $error'); } else { - debugPrint('❌ $message'); + debugPrint('ERROR: $message'); } } } @@ -25,49 +25,49 @@ class DebugLogger { /// Log warnings static void warning(String message) { if (_enabled) { - debugPrint('âš ī¸ $message'); + debugPrint('WARN: $message'); } } /// Log success/info messages static void info(String message) { if (_enabled) { - debugPrint('â„šī¸ $message'); + debugPrint('INFO: $message'); } } /// Log navigation events static void navigation(String message) { if (_enabled) { - debugPrint('🧭 $message'); + debugPrint('NAV: $message'); } } /// Log authentication events static void auth(String message) { if (_enabled) { - debugPrint('🔐 $message'); + debugPrint('AUTH: $message'); } } /// Log streaming events static void stream(String message) { if (_enabled) { - debugPrint('📡 $message'); + debugPrint('STREAM: $message'); } } /// Log validation events static void validation(String message) { if (_enabled) { - debugPrint('✅ $message'); + debugPrint('VALIDATION: $message'); } } /// Log storage events static void storage(String message) { if (_enabled) { - debugPrint('💾 $message'); + debugPrint('STORAGE: $message'); } } } diff --git a/lib/features/chat/providers/chat_providers.dart b/lib/features/chat/providers/chat_providers.dart index f601dd6..889a270 100644 --- a/lib/features/chat/providers/chat_providers.dart +++ b/lib/features/chat/providers/chat_providers.dart @@ -1,6 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:math' as math; + import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:uuid/uuid.dart'; @@ -33,9 +33,7 @@ class ChatMessagesNotifier extends StateNotifier> { previous, next, ) { - debugPrint( - 'DEBUG: Active conversation changed - Previous: ${previous?.id}, Next: ${next?.id}', - ); + debugPrint('Conversation changed: ${previous?.id} -> ${next?.id}'); // Only react when the conversation actually changes if (previous?.id == next?.id) { @@ -50,15 +48,11 @@ class ChatMessagesNotifier extends StateNotifier> { _cancelMessageStream(); if (next != null) { - debugPrint( - 'DEBUG: Loading ${next.messages.length} messages for conversation ${next.id}', - ); state = next.messages; // Update selected model if conversation has a different model _updateModelForConversation(next); } else { - debugPrint('DEBUG: Clearing messages - no active conversation'); state = []; } }); @@ -76,35 +70,20 @@ class ChatMessagesNotifier extends StateNotifier> { } Future _updateModelForConversation(Conversation conversation) async { - debugPrint( - 'DEBUG: _updateModelForConversation called for conversation ${conversation.id}', - ); - // Check if conversation has a model specified if (conversation.model == null || conversation.model!.isEmpty) { - debugPrint('DEBUG: Conversation has no model specified'); return; } - debugPrint('DEBUG: Conversation model: ${conversation.model}'); - final currentSelectedModel = _ref.read(selectedModelProvider); // If the conversation's model is different from the currently selected one if (currentSelectedModel?.id != conversation.model) { - debugPrint( - 'DEBUG: Conversation model (${conversation.model}) differs from selected model (${currentSelectedModel?.id})', - ); - // Get available models to find the matching one try { final models = await _ref.read(modelsProvider.future); - debugPrint('DEBUG: Available models count: ${models.length}'); if (models.isEmpty) { - debugPrint( - 'DEBUG: No models available, cannot update selected model', - ); return; } @@ -116,16 +95,11 @@ class ChatMessagesNotifier extends StateNotifier> { if (conversationModel != null) { // Update the selected model _ref.read(selectedModelProvider.notifier).state = conversationModel; - debugPrint( - 'DEBUG: Updated selected model to ${conversationModel.name} (${conversationModel.id}) for conversation ${conversation.id}', - ); } else { - debugPrint( - 'DEBUG: Conversation model ${conversation.model} not found in available models: ${models.map((m) => m.id).join(', ')}', - ); + // Model not found in available models - silently continue } } catch (e) { - debugPrint('DEBUG: Failed to update model for conversation: $e'); + // Model update failed - silently continue } } } @@ -180,25 +154,15 @@ class ChatMessagesNotifier extends StateNotifier> { } void appendToLastMessage(String content) { - debugPrint('DEBUG: appendToLastMessage called with: "$content"'); - if (state.isEmpty) { - debugPrint('DEBUG: No messages to append to'); return; } final lastMessage = state.last; if (lastMessage.role != 'assistant') { - debugPrint( - 'DEBUG: Last message is not assistant, role: ${lastMessage.role}', - ); return; } - debugPrint( - 'DEBUG: Appending to message ${lastMessage.id}, current length: ${lastMessage.content.length}', - ); - // If the current content is just the typing indicator, replace it instead of appending final newContent = lastMessage.content == '[TYPING_INDICATOR]' ? content @@ -208,30 +172,22 @@ class ChatMessagesNotifier extends StateNotifier> { ...state.sublist(0, state.length - 1), lastMessage.copyWith(content: newContent), ]; - debugPrint('DEBUG: New content length: ${state.last.content.length}'); } void replaceLastMessageContent(String content) { - debugPrint('DEBUG: replaceLastMessageContent called with: "$content"'); if (state.isEmpty) { - debugPrint('DEBUG: No messages to replace content for'); return; } final lastMessage = state.last; if (lastMessage.role != 'assistant') { - debugPrint( - 'DEBUG: Last message is not assistant, role: ${lastMessage.role}', - ); return; } - debugPrint('DEBUG: Replacing content for message ${lastMessage.id}'); state = [ ...state.sublist(0, state.length - 1), lastMessage.copyWith(content: content), ]; - debugPrint('DEBUG: Replaced content length: ${state.last.content.length}'); } void finishStreaming() { @@ -249,7 +205,7 @@ class ChatMessagesNotifier extends StateNotifier> { @override void dispose() { debugPrint( - 'DEBUG: ChatMessagesNotifier disposing - cancelling ${_subscriptions.length} subscriptions', + 'ChatMessagesNotifier disposing - ${_subscriptions.length} subscriptions', ); // Cancel all tracked subscriptions @@ -265,22 +221,17 @@ class ChatMessagesNotifier extends StateNotifier> { _conversationListener?.close(); _conversationListener = null; - debugPrint('DEBUG: ChatMessagesNotifier disposed successfully'); super.dispose(); } } // Start a new chat (unified function for both "New Chat" button and home screen) void startNewChat(dynamic ref) { - debugPrint('DEBUG: Starting new chat - clearing all state'); - // Clear active conversation ref.read(activeConversationProvider.notifier).state = null; // Clear messages ref.read(chatMessagesProvider.notifier).clearMessages(); - - debugPrint('DEBUG: New chat state cleared'); } // Available tools provider @@ -332,19 +283,14 @@ bool validateFileCount(int currentCount, int newFilesCount, int? maxCount) { // Helper function to get file content as base64 Future _getFileAsBase64(dynamic api, String fileId) async { - debugPrint('DEBUG: _getFileAsBase64 called for fileId: $fileId'); - // Check if this is already a data URL (for images) if (fileId.startsWith('data:')) { - debugPrint('DEBUG: FileId is already a data URL, returning as-is'); return fileId; } try { // First, get file info to determine if it's an image - debugPrint('DEBUG: Getting file info for $fileId'); final fileInfo = await api.getFileInfo(fileId); - debugPrint('DEBUG: File info received: $fileInfo'); // Try different fields for filename - check all possible field names final fileName = @@ -356,29 +302,19 @@ Future _getFileAsBase64(dynamic api, String fileId) async { fileInfo['original_filename'] ?? ''; - debugPrint('DEBUG: Processing file: $fileName (fileId: $fileId)'); - final ext = fileName.toLowerCase().split('.').last; - debugPrint('DEBUG: File extension: $ext'); // Only process image files if (!['jpg', 'jpeg', 'png', 'gif', 'webp'].contains(ext)) { - debugPrint('DEBUG: Skipping non-image file: $fileName (extension: $ext)'); return null; } - debugPrint('DEBUG: Getting base64 content for image: $fileName'); - // Get file content as base64 string final fileContent = await api.getFileContent(fileId); - debugPrint( - 'DEBUG: Got file content for $fileName, type: ${fileContent.runtimeType}, length: ${fileContent.length}', - ); // The API service returns base64 string directly return fileContent; } catch (e) { - debugPrint('DEBUG: Error getting file content for $fileId: $e'); return null; } } @@ -389,22 +325,16 @@ Future regenerateMessage( String userMessageContent, List? attachments, ) async { - debugPrint( - 'DEBUG: regenerateMessage called with content: $userMessageContent', - ); - final reviewerMode = ref.read(reviewerModeProvider); final api = ref.read(apiServiceProvider); final selectedModel = ref.read(selectedModelProvider); if ((!reviewerMode && api == null) || selectedModel == null) { - debugPrint('DEBUG: Missing API service or model for regeneration'); throw Exception('No API service or model selected'); } final activeConversation = ref.read(activeConversationProvider); if (activeConversation == null) { - debugPrint('DEBUG: No active conversation for regeneration'); throw Exception('No active conversation'); } @@ -503,7 +433,6 @@ Future regenerateMessage( ref.read(chatMessagesProvider.notifier).finishStreaming(); await _saveConversationLocally(ref); } catch (e) { - debugPrint('DEBUG: Error during message regeneration: $e'); rethrow; } } @@ -515,9 +444,6 @@ Future sendMessage( List? attachments, [ List? toolIds, ]) async { - debugPrint( - 'DEBUG: sendMessage called with message: $message, attachments: $attachments, tools: $toolIds', - ); await _sendMessageInternal(ref, message, attachments, toolIds); } @@ -528,33 +454,19 @@ Future _sendMessageInternal( List? attachments, [ List? toolIds, ]) async { - debugPrint('DEBUG: _sendMessageInternal called'); - debugPrint('DEBUG: Message: $message'); - debugPrint('DEBUG: Attachments: $attachments'); - final reviewerMode = ref.read(reviewerModeProvider); final api = ref.read(apiServiceProvider); final selectedModel = ref.read(selectedModelProvider); - debugPrint('DEBUG: API service: ${api != null ? 'available' : 'null'}'); - debugPrint('DEBUG: Selected model: ${selectedModel?.name ?? 'null'}'); - if ((!reviewerMode && api == null) || selectedModel == null) { - debugPrint('DEBUG: Missing API service or model'); throw Exception('No API service or model selected'); } // Check if we need to create a new conversation first var activeConversation = ref.read(activeConversationProvider); - debugPrint( - 'DEBUG: Active conversation before send: ${activeConversation?.id}', - ); - // Create user message first - debugPrint( - 'DEBUG: Creating user message with attachments: $attachments, tools: $toolIds', - ); + final userMessage = ChatMessage( id: const Uuid().v4(), role: 'user', @@ -565,9 +477,6 @@ Future _sendMessageInternal( if (activeConversation == null) { // Create new conversation with the first message included - debugPrint('DEBUG: Creating new conversation with first message'); - - // Create local conversation first final localConversation = Conversation( id: const Uuid().v4(), title: 'New Chat', @@ -602,22 +511,12 @@ Future _sendMessageInternal( ref.read(chatMessagesProvider.notifier).clearMessages(); ref.read(chatMessagesProvider.notifier).addMessage(userMessage); - debugPrint( - 'DEBUG: Created conversation ${serverConversation.id} on server with first message', - ); - debugPrint( - 'DEBUG: Server conversation ID: ${serverConversation.id}, Title: ${serverConversation.title}', - ); - // Invalidate conversations provider to refresh the list // Adding a small delay to prevent rapid invalidations that could cause duplicates Future.delayed(const Duration(milliseconds: 100), () { ref.invalidate(conversationsProvider); }); } catch (e) { - debugPrint( - 'DEBUG: Failed to create conversation on server, using local: $e', - ); // Still add the message locally ref.read(chatMessagesProvider.notifier).addMessage(userMessage); } @@ -628,7 +527,6 @@ Future _sendMessageInternal( } else { // Add user message to existing conversation ref.read(chatMessagesProvider.notifier).addMessage(userMessage); - debugPrint('DEBUG: User message added with ID: ${userMessage.id}'); } // We'll add the assistant message placeholder after we get the message ID from the API (or immediately in reviewer mode) @@ -687,27 +585,9 @@ Future _sendMessageInternal( // Skip only empty assistant message placeholders that are currently streaming // Include completed messages (both user and assistant) for conversation history if (msg.role.isNotEmpty && msg.content.isNotEmpty && !msg.isStreaming) { - debugPrint( - 'DEBUG: Processing message: role=${msg.role}, content=${msg.content.substring(0, msg.content.length > 50 ? 50 : msg.content.length)}..., attachments=${msg.attachmentIds}', - ); - // Check if message has attachments (images and non-images) if (msg.attachmentIds != null && msg.attachmentIds!.isNotEmpty) { - debugPrint( - 'DEBUG: Message has ${msg.attachmentIds!.length} attachments', - ); - - // Check if this is a Gemini model that requires special handling - final isGeminiModel = selectedModel.id.toLowerCase().contains('gemini'); - debugPrint('DEBUG: Is Gemini model: $isGeminiModel'); - debugPrint('DEBUG: Model ID: ${selectedModel.id}'); - debugPrint('DEBUG: Model name: ${selectedModel.name}'); - debugPrint( - 'DEBUG: Model ID lowercase: ${selectedModel.id.toLowerCase()}', - ); - debugPrint( - 'DEBUG: Contains gemini: ${selectedModel.id.toLowerCase().contains('gemini')}', - ); + // All models use the same content array format (OpenWebUI standard) // Use the same content array format for all models (OpenWebUI standard) final List> contentArray = []; @@ -717,26 +597,19 @@ Future _sendMessageInternal( // Add text content first if (msg.content.isNotEmpty) { contentArray.add({'type': 'text', 'text': msg.content}); - debugPrint('DEBUG: Added text content to array'); } // Add image attachments with proper MIME type handling; collect non-image attachments for (final attachmentId in msg.attachmentIds!) { - debugPrint('DEBUG: Processing attachment: $attachmentId'); try { final base64Data = await _getFileAsBase64(api, attachmentId); if (base64Data != null) { - debugPrint( - 'DEBUG: Got base64 data for attachment $attachmentId, length: ${base64Data.length}', - ); - // Check if this is already a data URL if (base64Data.startsWith('data:')) { contentArray.add({ 'type': 'image_url', 'image_url': {'url': base64Data}, }); - debugPrint('DEBUG: Added image with data URL'); } else { // For server files, determine MIME type from file extension // Only call getFileInfo if attachmentId is not a data URL @@ -754,46 +627,31 @@ Future _sendMessageInternal( mimeType = 'image/webp'; } - debugPrint( - 'DEBUG: Using MIME type: $mimeType for file: $fileName', - ); - contentArray.add({ 'type': 'image_url', 'image_url': {'url': 'data:$mimeType;base64,$base64Data'}, }); - debugPrint('DEBUG: Added image with MIME type: $mimeType'); - } else { - debugPrint('DEBUG: Skipping getFileInfo for data URL'); } } } else { - debugPrint( - 'DEBUG: No base64 data returned for attachment $attachmentId', - ); // Treat as non-image file; include minimal descriptor so server can resolve by id nonImageFiles.add({'id': attachmentId, 'type': 'file'}); } } catch (e) { - debugPrint('DEBUG: Failed to load attachment $attachmentId: $e'); + // Handle attachment processing errors silently } } - debugPrint('DEBUG: Final content array length: ${contentArray.length}'); final messageMap = { 'role': msg.role, 'content': contentArray, }; if (nonImageFiles.isNotEmpty) { - debugPrint( - 'DEBUG: Adding ${nonImageFiles.length} non-image file(s) to message map', - ); messageMap['files'] = nonImageFiles; } conversationMessages.add(messageMap); } else { // Regular text-only message - debugPrint('DEBUG: Regular text-only message'); conversationMessages.add({'role': msg.role, 'content': msg.content}); } } @@ -803,17 +661,10 @@ Future _sendMessageInternal( final webSearchEnabled = ref.read(webSearchEnabledProvider); final imageGenerationEnabled = ref.read(imageGenerationEnabledProvider); - // Debug log to track feature toggle states - debugPrint('DEBUG: Web search toggle state: $webSearchEnabled'); - debugPrint('DEBUG: Image generation toggle state: $imageGenerationEnabled'); - // Prepare tools list - pass tool IDs directly final List? toolIdsForApi = (toolIds != null && toolIds.isNotEmpty) ? toolIds : null; - if (toolIdsForApi != null) { - debugPrint('DEBUG: Including tool IDs: $toolIdsForApi'); - } try { // Use the model's actual supported parameters if available @@ -827,12 +678,6 @@ Future _sendMessageInternal( 'structured_outputs', ]; - debugPrint( - 'DEBUG: Model ${selectedModel.name} supported parameters: ${selectedModel.supportedParameters}', - ); - debugPrint('DEBUG: Model ID: ${selectedModel.id}'); - debugPrint('DEBUG: Is multimodal: ${selectedModel.isMultimodal}'); - // Create comprehensive model item matching OpenWebUI format exactly final modelItem = { 'id': selectedModel.id, @@ -917,29 +762,6 @@ Future _sendMessageInternal( 'tags': [], }; - debugPrint('DEBUG: Using basic model item for ${selectedModel.name}'); - - debugPrint('DEBUG: Final conversationMessages being sent to API:'); - debugPrint('DEBUG: Messages count: ${conversationMessages.length}'); - for (int i = 0; i < conversationMessages.length; i++) { - final msg = conversationMessages[i]; - debugPrint( - 'DEBUG: Message $i: role=${msg['role']}, content type=${msg['content'].runtimeType}', - ); - if (msg['content'] is List) { - final contentArray = msg['content'] as List; - debugPrint( - 'DEBUG: Message $i content array length: ${contentArray.length}', - ); - for (int j = 0; j < contentArray.length; j++) { - final item = contentArray[j]; - debugPrint( - 'DEBUG: Content item $j: type=${item['type']}, has_image_url=${item.containsKey('image_url')}', - ); - } - } - } - // If image generation is enabled and we want image-only, skip assistant SSE if (imageGenerationEnabled) { // Create assistant placeholder @@ -955,7 +777,6 @@ Future _sendMessageInternal( ref.read(chatMessagesProvider.notifier).addMessage(imageOnlyAssistant); try { - debugPrint('DEBUG: Image-only mode - triggering image generation'); final imageResponse = await api.generateImage(prompt: message); // Extract image URLs or base64 data URIs from response @@ -1075,7 +896,6 @@ Future _sendMessageInternal( ref.read(chatMessagesProvider.notifier).finishStreaming(); } } catch (e) { - debugPrint('DEBUG: Image-only mode generation failed: $e'); ref.read(chatMessagesProvider.notifier).finishStreaming(); } @@ -1100,10 +920,6 @@ Future _sendMessageInternal( final assistantMessageId = response.messageId; final sessionId = response.sessionId; - debugPrint( - 'DEBUG: Response IDs - Message: $assistantMessageId, Session: $sessionId', - ); - // Add assistant message placeholder with the generated ID and immediate typing indicator final assistantMessage = ChatMessage( id: assistantMessageId, @@ -1305,8 +1121,6 @@ Future _sendMessageInternal( final streamSubscription = persistentController.stream.listen( (chunk) { - debugPrint('DEBUG: Received stream chunk: "$chunk"'); - // Check for web search indicators in the stream if (webSearchEnabled && !isSearching) { // Check if this is the start of web search @@ -1359,7 +1173,6 @@ Future _sendMessageInternal( onDone: () async { // Unregister from persistent service persistentService.unregisterStream(streamId); - debugPrint('DEBUG: Stream completed in chat provider'); // Mark streaming as complete immediately for better UX ref.read(chatMessagesProvider.notifier).finishStreaming(); @@ -1402,15 +1215,6 @@ Future _sendMessageInternal( // Send chat completed notification to OpenWebUI first try { - debugPrint( - 'DEBUG: Sending chat completed notification to OpenWebUI', - ); - debugPrint( - 'DEBUG: Active conversation ID: ${activeConversation.id}', - ); - debugPrint( - 'DEBUG: Chat ID: ${activeConversation.id}, Message ID: $assistantMessageId, Messages: ${formattedMessages.length}', - ); await api.sendChatCompleted( chatId: activeConversation.id, messageId: assistantMessageId, // Use message ID from response @@ -1419,25 +1223,17 @@ Future _sendMessageInternal( modelItem: modelItem, // Include model metadata sessionId: sessionId, // Include session ID ); - debugPrint( - 'DEBUG: Chat completed notification sent successfully for chat ID: ${activeConversation.id}', - ); } catch (e) { - debugPrint('DEBUG: Chat completed notification failed: $e'); - debugPrint('DEBUG: Error details: $e'); // Continue even if this fails - it's non-critical } // Fetch the latest conversation state without waiting for title generation - debugPrint('DEBUG: Fetching latest conversation state...'); - debugPrint('DEBUG: Current message count: ${messages.length}'); try { // Quick fetch to get the current state - no waiting for title generation final updatedConv = await api.getConversation( activeConversation.id, ); - debugPrint('DEBUG: Current title: ${updatedConv.title}'); // Check if we should update the title (only on first response and if server has one) final shouldUpdateTitle = @@ -1476,20 +1272,8 @@ Future _sendMessageInternal( if (serverMsg != null && serverMsg.content.isNotEmpty) { // Use server content if available and non-empty // This replaces any temporary progress indicators with real content - debugPrint( - 'DEBUG: Replacing local content with server content for message ${localMsg.id}', - ); - debugPrint( - 'DEBUG: Local content: "${localMsg.content.substring(0, math.min(100, localMsg.content.length))}..."', - ); - debugPrint( - 'DEBUG: Server content: "${serverMsg.content.substring(0, math.min(100, serverMsg.content.length))}..."', - ); // Stream the server content through StreamChunker for word-by-word effect - debugPrint( - 'DEBUG: Streaming server content through chunker for word-by-word display', - ); // Clear only the last message content in-place to avoid list reset flicker final currentList = [...currentMessages]; @@ -1521,22 +1305,17 @@ Future _sendMessageInternal( // Process chunks chunkedStream.listen( (chunk) { - debugPrint('DEBUG: Server content chunk: "$chunk"'); ref .read(chatMessagesProvider.notifier) .appendToLastMessage(chunk); }, onDone: () { - debugPrint('DEBUG: Server content streaming completed'); // Mark streaming as complete ref .read(chatMessagesProvider.notifier) .finishStreaming(); }, onError: (error) { - debugPrint( - 'DEBUG: Server content streaming error: $error', - ); // Fall back to direct replacement final currentMessages = ref.read(chatMessagesProvider); if (currentMessages.isNotEmpty) { @@ -1559,9 +1338,6 @@ Future _sendMessageInternal( } else { // Handle case where streaming failed and we still have typing indicator if (localMsg.content == '[TYPING_INDICATOR]') { - debugPrint( - 'DEBUG: Found orphaned typing indicator for message ${localMsg.id} - replacing with empty content', - ); // Replace typing indicator with empty content so UI can show loading state updatedMessages.add( localMsg.copyWith(content: '', isStreaming: false), @@ -1574,10 +1350,6 @@ Future _sendMessageInternal( } if (shouldUpdateTitle) { - debugPrint( - 'DEBUG: Server generated title: ${updatedConv.title}', - ); - // Ensure the title is reasonable (not too long) final cleanTitle = updatedConv.title.length > 100 ? '${updatedConv.title.substring(0, 100)}...' @@ -1592,8 +1364,6 @@ Future _sendMessageInternal( ref.read(activeConversationProvider.notifier).state = updatedConversation; - - debugPrint('DEBUG: Conversation title updated successfully'); } else { // Update just the messages without changing title final updatedConversation = activeConversation.copyWith( @@ -1603,26 +1373,18 @@ Future _sendMessageInternal( ref.read(activeConversationProvider.notifier).state = updatedConversation; - - debugPrint( - 'DEBUG: Conversation content updated with server response', - ); } // Streaming already marked as complete when stream ended - debugPrint('DEBUG: Server content replacement completed'); // Start background title check for first message exchanges if (messages.length <= 2 && updatedConv.title == 'New Chat') { - debugPrint('DEBUG: Starting background title check...'); _checkForTitleInBackground(ref, activeConversation.id); } } catch (e) { - debugPrint('DEBUG: Failed to fetch server content: $e'); // Streaming already marked as complete when stream ended } } catch (e) { - debugPrint('DEBUG: Chat completed error: $e'); // Continue without failing the entire process // Note: Conversation still syncs via _saveConversationToServer // Streaming already marked as complete when stream ended @@ -1631,16 +1393,13 @@ Future _sendMessageInternal( } // Save conversation to OpenWebUI server only after streaming is complete - debugPrint('DEBUG: About to save conversation to server...'); // Add a small delay to ensure the last message content is fully updated await Future.delayed(const Duration(milliseconds: 100)); await _saveConversationToServer(ref); - debugPrint('DEBUG: Conversation save completed'); // If image generation is enabled, trigger image generation with the user's prompt if (imageGenerationEnabled) { try { - debugPrint('DEBUG: Image generation enabled - triggering request'); final imageResponse = await api.generateImage(prompt: message); // Extract image URLs or base64 data URIs from response @@ -1734,10 +1493,6 @@ Future _sendMessageInternal( final generatedFiles = extractGeneratedFiles(imageResponse); if (generatedFiles.isNotEmpty) { - debugPrint( - 'DEBUG: Image generation returned ${generatedFiles.length} file(s)', - ); - // Attach images to the last assistant message ref .read(chatMessagesProvider.notifier) @@ -1750,16 +1505,13 @@ Future _sendMessageInternal( // Save updated conversation with images await _saveConversationToServer(ref); - } else { - debugPrint('DEBUG: No images found in generation response'); } } catch (e) { - debugPrint('DEBUG: Image generation failed: $e'); + // Handle image generation errors silently } } }, onError: (error) { - debugPrint('DEBUG: Stream error in chat provider: $error'); // Mark streaming as complete on error ref.read(chatMessagesProvider.notifier).finishStreaming(); @@ -1768,12 +1520,6 @@ Future _sendMessageInternal( if (error.toString().contains( 'Socket.IO streaming not fully implemented', )) { - debugPrint( - 'DEBUG: Socket.IO streaming failed, but server may have generated response', - ); - debugPrint( - 'DEBUG: Keeping assistant message for server content replacement', - ); // Don't remove the message - let the server content replacement handle it // The onDone callback will fetch the actual response from the server return; // Exit early to avoid removing the message @@ -1785,9 +1531,6 @@ Future _sendMessageInternal( // Handle different types of errors if (error.toString().contains('400')) { // Bad request errors - likely malformed request format - debugPrint( - 'DEBUG: Bad request error (400) - malformed request format', - ); final errorMessage = ChatMessage( id: const Uuid().v4(), role: 'assistant', @@ -1814,7 +1557,6 @@ This might be because: ref.invalidate(authStateManagerProvider); } else if (error.toString().contains('500')) { // Server errors - add user-friendly error message - debugPrint('DEBUG: Server error (500) - OpenWebUI server issue'); final errorMessage = ChatMessage( id: const Uuid().v4(), role: 'assistant', @@ -1838,7 +1580,6 @@ This usually means: ref.read(chatMessagesProvider.notifier).addMessage(errorMessage); } else if (error.toString().contains('timeout')) { // Timeout errors - debugPrint('DEBUG: Request timeout error'); final errorMessage = ChatMessage( id: const Uuid().v4(), role: 'assistant', @@ -1864,7 +1605,6 @@ This might be because: // Don't throw the error to prevent unhandled exceptions // The error message has been added to the chat - debugPrint('DEBUG: Chat error handled gracefully: ${error.toString()}'); }, ); @@ -1878,7 +1618,6 @@ This might be because: // Add user-friendly error message instead of rethrowing if (e.toString().contains('400')) { - debugPrint('DEBUG: Bad request error (400) during initial request setup'); final errorMessage = ChatMessage( id: const Uuid().v4(), role: 'assistant', @@ -1895,7 +1634,6 @@ Please try sending the message again, or try without attachments.''', ); ref.read(chatMessagesProvider.notifier).addMessage(errorMessage); } else if (e.toString().contains('500')) { - debugPrint('DEBUG: Server error (500) during initial request setup'); final errorMessage = ChatMessage( id: const Uuid().v4(), role: 'assistant', @@ -1920,7 +1658,6 @@ Please try sending the message again, or try without attachments.''', ref.read(chatMessagesProvider.notifier).addMessage(errorMessage); } else { // For other errors, provide a generic message and rethrow - debugPrint('DEBUG: Unexpected error during chat request: $e'); final errorMessage = ChatMessage( id: const Uuid().v4(), role: 'assistant', @@ -1946,10 +1683,6 @@ Future _triggerTitleGeneration( final api = ref.read(apiServiceProvider); if (api == null) return; - debugPrint( - 'DEBUG: Requesting title generation for conversation $conversationId', - ); - // Call the title generation endpoint final generatedTitle = await api.generateTitle( conversationId: conversationId, @@ -1960,8 +1693,6 @@ Future _triggerTitleGeneration( if (generatedTitle != null && generatedTitle.isNotEmpty && generatedTitle != 'New Chat') { - debugPrint('DEBUG: Title generated successfully: $generatedTitle'); - // Update the active conversation with the new title final activeConversation = ref.read(activeConversationProvider); if (activeConversation?.id == conversationId) { @@ -1973,9 +1704,6 @@ Future _triggerTitleGeneration( // Save the updated title to the server try { - debugPrint( - 'DEBUG: Saving generated title to server: $generatedTitle', - ); final currentMessages = ref.read(chatMessagesProvider); await api.updateConversationWithMessages( conversationId, @@ -1983,21 +1711,18 @@ Future _triggerTitleGeneration( title: generatedTitle, model: model, ); - debugPrint('DEBUG: Title saved to server successfully'); } catch (e) { - debugPrint('DEBUG: Failed to save title to server: $e'); + // Handle title save errors silently } // Refresh the conversations list ref.invalidate(conversationsProvider); } } else { - debugPrint('DEBUG: Title generation did not return a valid title'); // Fall back to background checking _checkForTitleInBackground(ref, conversationId); } } catch (e) { - debugPrint('DEBUG: Title generation failed: $e'); // Fall back to background checking _checkForTitleInBackground(ref, conversationId); } @@ -2021,10 +1746,6 @@ Future _checkForTitleInBackground( final updatedConv = await api.getConversation(conversationId); if (updatedConv.title != 'New Chat' && updatedConv.title.isNotEmpty) { - debugPrint( - 'DEBUG: Background title update found: ${updatedConv.title}', - ); - // Update the active conversation with the new title final activeConversation = ref.read(activeConversationProvider); if (activeConversation?.id == conversationId) { @@ -2046,34 +1767,23 @@ Future _checkForTitleInBackground( await Future.delayed(Duration(seconds: 2 + (i * 2))); } } catch (e) { - debugPrint('DEBUG: Background title check error: $e'); break; // Stop on error } } - - debugPrint( - 'DEBUG: Background title check completed without finding generated title', - ); } catch (e) { - debugPrint('DEBUG: Background title check failed: $e'); + // Handle background title check errors silently } } // Save current conversation to OpenWebUI server Future _saveConversationToServer(dynamic ref) async { try { - debugPrint('DEBUG: _saveConversationToServer started'); final api = ref.read(apiServiceProvider); final messages = ref.read(chatMessagesProvider); final activeConversation = ref.read(activeConversationProvider); final selectedModel = ref.read(selectedModelProvider); - debugPrint( - 'DEBUG: Conversation save state - API: ${api != null}, Messages: ${messages.length}, Active: ${activeConversation?.id}, Model: ${selectedModel?.id}', - ); - if (api == null || messages.isEmpty || activeConversation == null) { - debugPrint('DEBUG: Skipping conversation save - missing required data'); return; } @@ -2084,20 +1794,10 @@ Future _saveConversationToServer(dynamic ref) async { (lastMessage.files == null || lastMessage.files!.isEmpty) && (lastMessage.attachmentIds == null || lastMessage.attachmentIds!.isEmpty)) { - debugPrint( - 'DEBUG: Skipping conversation save - assistant message has no content or files', - ); return; } // Update the existing conversation with all messages (including assistant response) - debugPrint( - 'DEBUG: Updating conversation ${activeConversation.id} with complete message history', - ); - debugPrint( - 'DEBUG: Conversation ID being updated: ${activeConversation.id}', - ); - debugPrint('DEBUG: Number of messages to save: ${messages.length}'); try { await api.updateConversationWithMessages( @@ -2113,31 +1813,18 @@ Future _saveConversationToServer(dynamic ref) async { ); ref.read(activeConversationProvider.notifier).state = updatedConversation; - debugPrint( - 'DEBUG: Successfully updated conversation on server: ${activeConversation.id}', - ); - debugPrint( - 'DEBUG: Updated conversation title: ${updatedConversation.title}', - ); } catch (e) { - debugPrint('DEBUG: Failed to update conversation on server: $e'); - debugPrint('DEBUG: Error details: $e'); // Fallback to local storage if server update fails await _saveConversationLocally(ref); return; } // Refresh conversations list to show the updated conversation - debugPrint( - 'DEBUG: Invalidating conversations provider after successful save', - ); // Adding a small delay to prevent rapid invalidations that could cause duplicates Future.delayed(const Duration(milliseconds: 100), () { ref.invalidate(conversationsProvider); - debugPrint('DEBUG: Conversations provider invalidated'); }); } catch (e) { - debugPrint('Error saving conversation to server: $e'); // Fallback to local storage await _saveConversationLocally(ref); } @@ -2186,7 +1873,7 @@ Future _saveConversationLocally(dynamic ref) async { ref.read(activeConversationProvider.notifier).state = updatedConversation; ref.invalidate(conversationsProvider); } catch (e) { - debugPrint('Error saving conversation locally: $e'); + // Handle local storage errors silently } } @@ -2358,9 +2045,6 @@ final regenerateLastMessageProvider = Provider Function()>((ref) { ref.read(chatMessagesProvider.notifier).addMessage(placeholder); try { - debugPrint( - 'DEBUG: Regenerate image-only - triggering image generation', - ); final imageResponse = await api.generateImage( prompt: lastUserMessage.content, ); @@ -2473,7 +2157,6 @@ final regenerateLastMessageProvider = Provider Function()>((ref) { ref.read(chatMessagesProvider.notifier).finishStreaming(); } } catch (e) { - debugPrint('DEBUG: Regenerate image-only failed: $e'); ref.read(chatMessagesProvider.notifier).finishStreaming(); } return; diff --git a/lib/features/chat/views/chat_page.dart b/lib/features/chat/views/chat_page.dart index 15b5e34..1346c65 100644 --- a/lib/features/chat/views/chat_page.dart +++ b/lib/features/chat/views/chat_page.dart @@ -195,7 +195,7 @@ class _ChatPageState extends ConsumerState { ); ref.read(activeConversationProvider.notifier).state = welcomeConv; - debugPrint('Auto-loaded demo conversation: ${welcomeConv.title}'); + debugPrint('Auto-loaded demo conversation'); return; } @@ -240,35 +240,20 @@ class _ChatPageState extends ConsumerState { } void _handleMessageSend(String text, dynamic selectedModel) async { - debugPrint('DEBUG: Starting message send process'); - debugPrint('DEBUG: Message text: $text'); - debugPrint('DEBUG: Selected model: ${selectedModel?.name ?? 'null'}'); - if (selectedModel == null) { - debugPrint('DEBUG: No model selected'); return; } final isOnline = ref.read(isOnlineProvider); final isReviewerMode = ref.read(reviewerModeProvider); - debugPrint( - 'DEBUG: Online status: $isOnline, Reviewer mode: $isReviewerMode', - ); + if (!isOnline && !isReviewerMode) { - debugPrint('DEBUG: Offline - cannot send message'); return; } try { // Get attached files and use uploadedFileIds when sendMessage is updated to accept file IDs final attachedFiles = ref.read(attachedFilesProvider); - debugPrint('DEBUG: Attached files count: ${attachedFiles.length}'); - - for (final file in attachedFiles) { - debugPrint( - 'DEBUG: File - Name: ${file.fileName}, Status: ${file.status}, FileId: ${file.fileId}', - ); - } final uploadedFileIds = attachedFiles .where( @@ -279,11 +264,8 @@ class _ChatPageState extends ConsumerState { .map((file) => file.fileId!) .toList(); - debugPrint('DEBUG: Uploaded file IDs: $uploadedFileIds'); - // Get selected tools final toolIds = ref.read(selectedToolIdsProvider); - debugPrint('DEBUG: Selected tool IDs: $toolIds'); // Send message with file attachments and tools using existing provider logic await sendMessage( @@ -293,11 +275,8 @@ class _ChatPageState extends ConsumerState { toolIds.isNotEmpty ? toolIds : null, ); - debugPrint('DEBUG: Message sent successfully'); - // Clear attachments after successful send ref.read(attachedFilesProvider.notifier).clearAll(); - debugPrint('DEBUG: Attachments cleared'); // Scroll to bottom after sending message (only if user was near bottom) WidgetsBinding.instance.addPostFrameCallback((_) { @@ -311,7 +290,7 @@ class _ChatPageState extends ConsumerState { } }); } catch (e) { - debugPrint('DEBUG: Message send error: $e'); + // Message send failed - error already handled by sendMessage } }