From 494427dd04950d1a432eb5db645480d9e8907fa8 Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Mon, 25 Aug 2025 22:14:40 +0530 Subject: [PATCH] refactor: streamline title and image generation handling in chat message processing --- .../chat/providers/chat_providers.dart | 163 ++++-------------- lib/features/chat/views/chat_page.dart | 37 +--- 2 files changed, 35 insertions(+), 165 deletions(-) diff --git a/lib/features/chat/providers/chat_providers.dart b/lib/features/chat/providers/chat_providers.dart index 871b6c1..bc48ab1 100644 --- a/lib/features/chat/providers/chat_providers.dart +++ b/lib/features/chat/providers/chat_providers.dart @@ -531,6 +531,34 @@ Future _sendMessageInternal( // We'll add the assistant message placeholder after we get the message ID from the API (or immediately in reviewer mode) + // Immediately trigger title generation after user message is sent (first turn only) + try { + final currentConversation = ref.read(activeConversationProvider); + if (currentConversation != null && + currentConversation.title == 'New Chat') { + final currentMessages = ref.read(chatMessagesProvider); + if (currentMessages.length == 1 && currentMessages.first.role == 'user') { + final List> formatted = [ + { + 'id': currentMessages.first.id, + 'role': currentMessages.first.role, + 'content': currentMessages.first.content, + 'timestamp': + currentMessages.first.timestamp.millisecondsSinceEpoch ~/ 1000, + }, + ]; + _triggerTitleGeneration( + ref, + currentConversation.id, + formatted, + selectedModel.id, + ); + } + } + } catch (e) { + // Silent fail for early title generation + } + // Reviewer mode: simulate a response locally and return if (reviewerMode) { // Add assistant message placeholder @@ -1229,7 +1257,7 @@ Future _sendMessageInternal( // Continue even if this fails - it's non-critical } - // Fetch the latest conversation state without waiting for title generation + // Fetch the latest conversation state try { // Quick fetch to get the current state - no waiting for title generation @@ -1243,19 +1271,6 @@ Future _sendMessageInternal( updatedConv.title != 'New Chat' && updatedConv.title.isNotEmpty; - // If title is still "New Chat" and this is the first exchange, trigger title generation - if (messages.length <= 2 && updatedConv.title == 'New Chat') { - debugPrint( - 'DEBUG: Triggering title generation for conversation ${activeConversation.id}', - ); - _triggerTitleGeneration( - ref, - activeConversation.id, - formattedMessages, - selectedModel.id, - ); - } - // Always combine current local messages with updated server content final currentMessages = ref.read(chatMessagesProvider); final serverMessages = updatedConv.messages; @@ -1378,11 +1393,7 @@ Future _sendMessageInternal( } // Streaming already marked as complete when stream ended - - // Start background title check for first message exchanges - if (messages.length <= 2 && updatedConv.title == 'New Chat') { - _checkForTitleInBackground(ref, activeConversation.id); - } + // Removed post-assistant title trigger/background check; handled right after user message } catch (e) { // Streaming already marked as complete when stream ended } @@ -1399,119 +1410,7 @@ Future _sendMessageInternal( await Future.delayed(const Duration(milliseconds: 100)); await _saveConversationToServer(ref); - // If image generation is enabled, trigger image generation with the user's prompt - if (imageGenerationEnabled) { - try { - final imageResponse = await api.generateImage(prompt: message); - - // Extract image URLs or base64 data URIs from response - List> extractGeneratedFiles(dynamic resp) { - final results = >[]; - - // If it's already a list (e.g., list of URLs or file maps) - if (resp is List) { - for (final item in resp) { - if (item is String && item.isNotEmpty) { - results.add({'type': 'image', 'url': item}); - } else if (item is Map) { - final url = item['url']; - final b64 = item['b64_json'] ?? item['b64']; - if (url is String && url.isNotEmpty) { - results.add({'type': 'image', 'url': url}); - } else if (b64 is String && b64.isNotEmpty) { - results.add({ - 'type': 'image', - 'url': 'data:image/png;base64,$b64', - }); - } - } - } - return results; - } - - if (resp is! Map) { - return results; - } - - // Common patterns: { data: [ { url }, { b64_json } ] } - final data = resp['data']; - if (data is List) { - for (final item in data) { - if (item is Map) { - final url = item['url']; - final b64 = item['b64_json'] ?? item['b64']; - if (url is String && url.isNotEmpty) { - results.add({'type': 'image', 'url': url}); - } else if (b64 is String && b64.isNotEmpty) { - // Default to PNG for base64 images - results.add({ - 'type': 'image', - 'url': 'data:image/png;base64,$b64', - }); - } - } else if (item is String && item.isNotEmpty) { - // Some servers may return a list of URLs - results.add({'type': 'image', 'url': item}); - } - } - } - - // Alternative patterns - final images = resp['images']; - if (images is List) { - for (final item in images) { - if (item is String && item.isNotEmpty) { - results.add({'type': 'image', 'url': item}); - } else if (item is Map) { - final url = item['url']; - final b64 = item['b64_json'] ?? item['b64']; - if (url is String && url.isNotEmpty) { - results.add({'type': 'image', 'url': url}); - } else if (b64 is String && b64.isNotEmpty) { - results.add({ - 'type': 'image', - 'url': 'data:image/png;base64,$b64', - }); - } - } - } - } - - // Single fields - final singleUrl = resp['url']; - if (singleUrl is String && singleUrl.isNotEmpty) { - results.add({'type': 'image', 'url': singleUrl}); - } - final singleB64 = resp['b64_json'] ?? resp['b64']; - if (singleB64 is String && singleB64.isNotEmpty) { - results.add({ - 'type': 'image', - 'url': 'data:image/png;base64,$singleB64', - }); - } - - return results; - } - - final generatedFiles = extractGeneratedFiles(imageResponse); - if (generatedFiles.isNotEmpty) { - // Attach images to the last assistant message - ref - .read(chatMessagesProvider.notifier) - .updateLastMessageWithFunction((ChatMessage m) { - final currentFiles = m.files ?? >[]; - return m.copyWith( - files: [...currentFiles, ...generatedFiles], - ); - }); - - // Save updated conversation with images - await _saveConversationToServer(ref); - } - } catch (e) { - // Handle image generation errors silently - } - } + // Removed post-assistant image generation; images are handled immediately after user message }, onError: (error) { // Mark streaming as complete on error diff --git a/lib/features/chat/views/chat_page.dart b/lib/features/chat/views/chat_page.dart index 3b60983..2e46152 100644 --- a/lib/features/chat/views/chat_page.dart +++ b/lib/features/chat/views/chat_page.dart @@ -1144,42 +1144,13 @@ class _ChatPageState extends ConsumerState { mainAxisSize: MainAxisSize.min, children: [ Transform.translate( - offset: const Offset(-6, 0), - child: Center( + offset: const Offset(0, 0), + child: SizedBox( + height: 28, child: Row( + mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, children: [ - Opacity( - opacity: 0.0, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: Spacing.xs, - vertical: Spacing.xxs, - ), - decoration: BoxDecoration( - color: context - .conduitTheme - .surfaceBackground - .withValues(alpha: 0.3), - borderRadius: BorderRadius.circular( - AppBorderRadius.badge, - ), - border: Border.all( - color: - context.conduitTheme.dividerColor, - width: BorderWidth.thin, - ), - ), - child: Icon( - Platform.isIOS - ? CupertinoIcons.chevron_down - : Icons.keyboard_arrow_down, - size: IconSize.small, - ), - ), - ), - const SizedBox(width: Spacing.xs), Flexible( child: Text( 'Choose Model',