From f73d308e06828b58247d511104cfc2fcb5d7496b Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Sat, 20 Sep 2025 18:28:12 +0530 Subject: [PATCH] fix: adhere to system prompts --- lib/core/services/api_service.dart | 28 +++++ .../chat/providers/chat_providers.dart | 119 +++++++++++++++--- 2 files changed, 132 insertions(+), 15 deletions(-) diff --git a/lib/core/services/api_service.dart b/lib/core/services/api_service.dart index abf6921..1233e10 100644 --- a/lib/core/services/api_service.dart +++ b/lib/core/services/api_service.dart @@ -483,6 +483,18 @@ class ApiService { 'DEBUG: Parsed conversation $id: pinned=$pinned, archived=$archived', ); + String? systemPrompt; + final chatObject = chatData['chat'] as Map?; + if (chatObject != null) { + final systemValue = chatObject['system']; + if (systemValue is String && systemValue.trim().isNotEmpty) { + systemPrompt = systemValue; + } + } else if (chatData['system'] is String) { + final systemValue = (chatData['system'] as String).trim(); + if (systemValue.isNotEmpty) systemPrompt = systemValue; + } + // For the list endpoint, we don't get the full chat messages // We'll need to fetch individual chats later if needed return Conversation( @@ -490,6 +502,7 @@ class ApiService { title: title, createdAt: createdAt, updatedAt: updatedAt, + systemPrompt: systemPrompt, pinned: pinned, archived: archived, shareId: shareId, @@ -532,6 +545,16 @@ class ApiService { // Parse messages from the 'chat' object or top-level messages final chatObject = chatData['chat'] as Map?; + String? systemPrompt; + if (chatObject != null) { + final systemValue = chatObject['system']; + if (systemValue is String && systemValue.trim().isNotEmpty) { + systemPrompt = systemValue; + } + } else if (chatData['system'] is String) { + final systemValue = (chatData['system'] as String).trim(); + if (systemValue.isNotEmpty) systemPrompt = systemValue; + } final messages = []; // Extract model from chat.models array @@ -663,6 +686,7 @@ class ApiService { createdAt: createdAt, updatedAt: updatedAt, model: model, + systemPrompt: systemPrompt, pinned: pinned, archived: archived, shareId: shareId, @@ -1031,6 +1055,8 @@ class ApiService { 'id': '', 'title': title, 'models': model != null ? [model] : [], + if (systemPrompt != null && systemPrompt.trim().isNotEmpty) + 'system': systemPrompt, 'params': {}, 'history': { 'messages': messagesMap, @@ -1155,6 +1181,8 @@ class ApiService { 'chat': { if (title != null) 'title': title, // Include the title if provided 'models': model != null ? [model] : [], + if (systemPrompt != null && systemPrompt.trim().isNotEmpty) + 'system': systemPrompt, 'messages': messagesArray, 'history': { 'messages': messagesMap, diff --git a/lib/features/chat/providers/chat_providers.dart b/lib/features/chat/providers/chat_providers.dart index 6bc0677..40028f8 100644 --- a/lib/features/chat/providers/chat_providers.dart +++ b/lib/features/chat/providers/chat_providers.dart @@ -482,6 +482,7 @@ Future _preseedAssistantAndPersist( dynamic ref, { String? existingAssistantId, required String modelId, + String? systemPrompt, }) async { final api = ref.read(apiServiceProvider); final activeConv = ref.read(activeConversationProvider); @@ -525,11 +526,16 @@ Future _preseedAssistantAndPersist( // Persist the skeleton to the server so the web client sees a correct chain try { if (api != null && activeConv != null) { + final resolvedSystemPrompt = (systemPrompt != null && + systemPrompt.trim().isNotEmpty) + ? systemPrompt.trim() + : activeConv.systemPrompt; final current = ref.read(chatMessagesProvider); await api.updateConversationWithMessages( activeConv.id, current, model: modelId, + systemPrompt: resolvedSystemPrompt, ); } } catch (_) {} @@ -711,7 +717,7 @@ Future regenerateMessage( throw Exception('No API service or model selected'); } - final activeConversation = ref.read(activeConversationProvider); + var activeConversation = ref.read(activeConversationProvider); if (activeConversation == null) { throw Exception('No active conversation'); } @@ -753,6 +759,28 @@ Future regenerateMessage( // For real API, proceed with regeneration using existing conversation messages try { + Map? userSettingsData; + String? userSystemPrompt; + try { + userSettingsData = await api!.getUserSettings(); + final systemValue = userSettingsData?['system']; + if (systemValue is String) { + final trimmed = systemValue.trim(); + if (trimmed.isNotEmpty) { + userSystemPrompt = trimmed; + } + } + } catch (_) {} + + if ((activeConversation.systemPrompt == null || + activeConversation.systemPrompt!.trim().isEmpty) && + (userSystemPrompt?.isNotEmpty ?? false)) { + final updated = + activeConversation.copyWith(systemPrompt: userSystemPrompt); + ref.read(activeConversationProvider.notifier).state = updated; + activeConversation = updated; + } + // Include selected tool ids so provider-native tool calling is triggered final selectedToolIds = ref.read(selectedToolIdsProvider); // Get conversation history for context (excluding the removed assistant message) @@ -787,10 +815,28 @@ Future regenerateMessage( } } + final conversationSystemPrompt = activeConversation.systemPrompt?.trim(); + final effectiveSystemPrompt = (conversationSystemPrompt != null && + conversationSystemPrompt.isNotEmpty) + ? conversationSystemPrompt + : userSystemPrompt; + if (effectiveSystemPrompt != null && effectiveSystemPrompt.isNotEmpty) { + final hasSystemMessage = conversationMessages.any( + (m) => (m['role']?.toString().toLowerCase() ?? '') == 'system', + ); + if (!hasSystemMessage) { + conversationMessages.insert( + 0, + {'role': 'system', 'content': effectiveSystemPrompt}, + ); + } + } + // Pre-seed assistant skeleton and persist chain final String assistantMessageId = await _preseedAssistantAndPersist( ref, modelId: selectedModel.id, + systemPrompt: effectiveSystemPrompt, ); // Feature toggles @@ -912,14 +958,13 @@ Future regenerateMessage( // Resolve tool servers from user settings (if any) List>? toolServers; - try { - final userSettings = await api!.getUserSettings(); - final ui = userSettings['ui'] as Map?; - final rawServers = ui != null ? (ui['toolServers'] as List?) : null; - if (rawServers != null && rawServers.isNotEmpty) { + final uiSettings = userSettingsData?['ui'] as Map?; + final rawServers = uiSettings != null ? (uiSettings['toolServers'] as List?) : null; + if (rawServers != null && rawServers.isNotEmpty) { + try { toolServers = await _resolveToolServers(rawServers, api); - } - } catch (_) {} + } catch (_) {} + } // Background tasks parity with Web client (safe defaults) bool shouldGenerateTitle = false; @@ -1056,6 +1101,21 @@ Future _sendMessageInternal( throw Exception('No API service or model selected'); } + Map? userSettingsData; + String? userSystemPrompt; + if (!reviewerMode && api != null) { + try { + userSettingsData = await api.getUserSettings(); + final systemValue = userSettingsData?['system']; + if (systemValue is String) { + final trimmed = systemValue.trim(); + if (trimmed.isNotEmpty) { + userSystemPrompt = trimmed; + } + } + } catch (_) {} + } + // Check if we need to create a new conversation first var activeConversation = ref.read(activeConversationProvider); @@ -1077,6 +1137,7 @@ Future _sendMessageInternal( title: 'New Chat', createdAt: DateTime.now(), updatedAt: DateTime.now(), + systemPrompt: userSystemPrompt, messages: [userMessage], // Include the user message ); @@ -1091,9 +1152,11 @@ Future _sendMessageInternal( title: 'New Chat', messages: [userMessage], // Include the first message in creation model: selectedModel.id, + systemPrompt: userSystemPrompt, ); final updatedConversation = localConversation.copyWith( id: serverConversation.id, + systemPrompt: serverConversation.systemPrompt ?? userSystemPrompt, messages: serverConversation.messages.isNotEmpty ? serverConversation.messages : [userMessage], @@ -1131,6 +1194,15 @@ Future _sendMessageInternal( ref.read(chatMessagesProvider.notifier).addMessage(userMessage); } + if (activeConversation != null && + (activeConversation.systemPrompt == null || + activeConversation.systemPrompt!.trim().isEmpty) && + (userSystemPrompt?.isNotEmpty ?? false)) { + final updated = activeConversation.copyWith(systemPrompt: userSystemPrompt); + ref.read(activeConversationProvider.notifier).state = updated; + activeConversation = updated; + } + // 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) @@ -1234,6 +1306,23 @@ Future _sendMessageInternal( } } + final conversationSystemPrompt = activeConversation?.systemPrompt?.trim(); + final effectiveSystemPrompt = (conversationSystemPrompt != null && + conversationSystemPrompt.isNotEmpty) + ? conversationSystemPrompt + : userSystemPrompt; + if (effectiveSystemPrompt != null && effectiveSystemPrompt.isNotEmpty) { + final hasSystemMessage = conversationMessages.any( + (m) => (m['role']?.toString().toLowerCase() ?? '') == 'system', + ); + if (!hasSystemMessage) { + conversationMessages.insert( + 0, + {'role': 'system', 'content': effectiveSystemPrompt}, + ); + } + } + // Check feature toggles for API (gated by server availability) final webSearchEnabled = ref.read(webSearchEnabledProvider) && @@ -1270,6 +1359,7 @@ Future _sendMessageInternal( activeConvForSeed.id, msgsForSeed, model: selectedModel.id, + systemPrompt: effectiveSystemPrompt, ); } } catch (_) {} @@ -1378,14 +1468,13 @@ Future _sendMessageInternal( // Resolve tool servers from user settings (if any) List>? toolServers; - try { - final userSettings = await api.getUserSettings(); - final ui = userSettings['ui'] as Map?; - final rawServers = ui != null ? (ui['toolServers'] as List?) : null; - if (rawServers != null && rawServers.isNotEmpty) { + final uiSettings = userSettingsData?['ui'] as Map?; + final rawServers = uiSettings != null ? (uiSettings['toolServers'] as List?) : null; + if (rawServers != null && rawServers.isNotEmpty) { + try { toolServers = await _resolveToolServers(rawServers, api); - } - } catch (_) {} + } catch (_) {} + } // Background tasks parity with Web client (safe defaults) // Enable title/tags generation on the very first user turn of a new chat.