fix: adhere to system prompts
This commit is contained in:
@@ -483,6 +483,18 @@ class ApiService {
|
|||||||
'DEBUG: Parsed conversation $id: pinned=$pinned, archived=$archived',
|
'DEBUG: Parsed conversation $id: pinned=$pinned, archived=$archived',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
String? systemPrompt;
|
||||||
|
final chatObject = chatData['chat'] as Map<String, dynamic>?;
|
||||||
|
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
|
// For the list endpoint, we don't get the full chat messages
|
||||||
// We'll need to fetch individual chats later if needed
|
// We'll need to fetch individual chats later if needed
|
||||||
return Conversation(
|
return Conversation(
|
||||||
@@ -490,6 +502,7 @@ class ApiService {
|
|||||||
title: title,
|
title: title,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
|
systemPrompt: systemPrompt,
|
||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
archived: archived,
|
archived: archived,
|
||||||
shareId: shareId,
|
shareId: shareId,
|
||||||
@@ -532,6 +545,16 @@ class ApiService {
|
|||||||
|
|
||||||
// Parse messages from the 'chat' object or top-level messages
|
// Parse messages from the 'chat' object or top-level messages
|
||||||
final chatObject = chatData['chat'] as Map<String, dynamic>?;
|
final chatObject = chatData['chat'] as Map<String, dynamic>?;
|
||||||
|
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 = <ChatMessage>[];
|
final messages = <ChatMessage>[];
|
||||||
|
|
||||||
// Extract model from chat.models array
|
// Extract model from chat.models array
|
||||||
@@ -663,6 +686,7 @@ class ApiService {
|
|||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
model: model,
|
model: model,
|
||||||
|
systemPrompt: systemPrompt,
|
||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
archived: archived,
|
archived: archived,
|
||||||
shareId: shareId,
|
shareId: shareId,
|
||||||
@@ -1031,6 +1055,8 @@ class ApiService {
|
|||||||
'id': '',
|
'id': '',
|
||||||
'title': title,
|
'title': title,
|
||||||
'models': model != null ? [model] : [],
|
'models': model != null ? [model] : [],
|
||||||
|
if (systemPrompt != null && systemPrompt.trim().isNotEmpty)
|
||||||
|
'system': systemPrompt,
|
||||||
'params': {},
|
'params': {},
|
||||||
'history': {
|
'history': {
|
||||||
'messages': messagesMap,
|
'messages': messagesMap,
|
||||||
@@ -1155,6 +1181,8 @@ class ApiService {
|
|||||||
'chat': {
|
'chat': {
|
||||||
if (title != null) 'title': title, // Include the title if provided
|
if (title != null) 'title': title, // Include the title if provided
|
||||||
'models': model != null ? [model] : [],
|
'models': model != null ? [model] : [],
|
||||||
|
if (systemPrompt != null && systemPrompt.trim().isNotEmpty)
|
||||||
|
'system': systemPrompt,
|
||||||
'messages': messagesArray,
|
'messages': messagesArray,
|
||||||
'history': {
|
'history': {
|
||||||
'messages': messagesMap,
|
'messages': messagesMap,
|
||||||
|
|||||||
@@ -482,6 +482,7 @@ Future<String> _preseedAssistantAndPersist(
|
|||||||
dynamic ref, {
|
dynamic ref, {
|
||||||
String? existingAssistantId,
|
String? existingAssistantId,
|
||||||
required String modelId,
|
required String modelId,
|
||||||
|
String? systemPrompt,
|
||||||
}) async {
|
}) async {
|
||||||
final api = ref.read(apiServiceProvider);
|
final api = ref.read(apiServiceProvider);
|
||||||
final activeConv = ref.read(activeConversationProvider);
|
final activeConv = ref.read(activeConversationProvider);
|
||||||
@@ -525,11 +526,16 @@ Future<String> _preseedAssistantAndPersist(
|
|||||||
// Persist the skeleton to the server so the web client sees a correct chain
|
// Persist the skeleton to the server so the web client sees a correct chain
|
||||||
try {
|
try {
|
||||||
if (api != null && activeConv != null) {
|
if (api != null && activeConv != null) {
|
||||||
|
final resolvedSystemPrompt = (systemPrompt != null &&
|
||||||
|
systemPrompt.trim().isNotEmpty)
|
||||||
|
? systemPrompt.trim()
|
||||||
|
: activeConv.systemPrompt;
|
||||||
final current = ref.read(chatMessagesProvider);
|
final current = ref.read(chatMessagesProvider);
|
||||||
await api.updateConversationWithMessages(
|
await api.updateConversationWithMessages(
|
||||||
activeConv.id,
|
activeConv.id,
|
||||||
current,
|
current,
|
||||||
model: modelId,
|
model: modelId,
|
||||||
|
systemPrompt: resolvedSystemPrompt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@@ -711,7 +717,7 @@ Future<void> regenerateMessage(
|
|||||||
throw Exception('No API service or model selected');
|
throw Exception('No API service or model selected');
|
||||||
}
|
}
|
||||||
|
|
||||||
final activeConversation = ref.read(activeConversationProvider);
|
var activeConversation = ref.read(activeConversationProvider);
|
||||||
if (activeConversation == null) {
|
if (activeConversation == null) {
|
||||||
throw Exception('No active conversation');
|
throw Exception('No active conversation');
|
||||||
}
|
}
|
||||||
@@ -753,6 +759,28 @@ Future<void> regenerateMessage(
|
|||||||
|
|
||||||
// For real API, proceed with regeneration using existing conversation messages
|
// For real API, proceed with regeneration using existing conversation messages
|
||||||
try {
|
try {
|
||||||
|
Map<String, dynamic>? 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
|
// Include selected tool ids so provider-native tool calling is triggered
|
||||||
final selectedToolIds = ref.read(selectedToolIdsProvider);
|
final selectedToolIds = ref.read(selectedToolIdsProvider);
|
||||||
// Get conversation history for context (excluding the removed assistant message)
|
// Get conversation history for context (excluding the removed assistant message)
|
||||||
@@ -787,10 +815,28 @@ Future<void> 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
|
// Pre-seed assistant skeleton and persist chain
|
||||||
final String assistantMessageId = await _preseedAssistantAndPersist(
|
final String assistantMessageId = await _preseedAssistantAndPersist(
|
||||||
ref,
|
ref,
|
||||||
modelId: selectedModel.id,
|
modelId: selectedModel.id,
|
||||||
|
systemPrompt: effectiveSystemPrompt,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Feature toggles
|
// Feature toggles
|
||||||
@@ -912,14 +958,13 @@ Future<void> regenerateMessage(
|
|||||||
|
|
||||||
// Resolve tool servers from user settings (if any)
|
// Resolve tool servers from user settings (if any)
|
||||||
List<Map<String, dynamic>>? toolServers;
|
List<Map<String, dynamic>>? toolServers;
|
||||||
try {
|
final uiSettings = userSettingsData?['ui'] as Map<String, dynamic>?;
|
||||||
final userSettings = await api!.getUserSettings();
|
final rawServers = uiSettings != null ? (uiSettings['toolServers'] as List?) : null;
|
||||||
final ui = userSettings['ui'] as Map<String, dynamic>?;
|
if (rawServers != null && rawServers.isNotEmpty) {
|
||||||
final rawServers = ui != null ? (ui['toolServers'] as List?) : null;
|
try {
|
||||||
if (rawServers != null && rawServers.isNotEmpty) {
|
|
||||||
toolServers = await _resolveToolServers(rawServers, api);
|
toolServers = await _resolveToolServers(rawServers, api);
|
||||||
}
|
} catch (_) {}
|
||||||
} catch (_) {}
|
}
|
||||||
|
|
||||||
// Background tasks parity with Web client (safe defaults)
|
// Background tasks parity with Web client (safe defaults)
|
||||||
bool shouldGenerateTitle = false;
|
bool shouldGenerateTitle = false;
|
||||||
@@ -1056,6 +1101,21 @@ Future<void> _sendMessageInternal(
|
|||||||
throw Exception('No API service or model selected');
|
throw Exception('No API service or model selected');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic>? 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
|
// Check if we need to create a new conversation first
|
||||||
var activeConversation = ref.read(activeConversationProvider);
|
var activeConversation = ref.read(activeConversationProvider);
|
||||||
|
|
||||||
@@ -1077,6 +1137,7 @@ Future<void> _sendMessageInternal(
|
|||||||
title: 'New Chat',
|
title: 'New Chat',
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: DateTime.now(),
|
||||||
|
systemPrompt: userSystemPrompt,
|
||||||
messages: [userMessage], // Include the user message
|
messages: [userMessage], // Include the user message
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1091,9 +1152,11 @@ Future<void> _sendMessageInternal(
|
|||||||
title: 'New Chat',
|
title: 'New Chat',
|
||||||
messages: [userMessage], // Include the first message in creation
|
messages: [userMessage], // Include the first message in creation
|
||||||
model: selectedModel.id,
|
model: selectedModel.id,
|
||||||
|
systemPrompt: userSystemPrompt,
|
||||||
);
|
);
|
||||||
final updatedConversation = localConversation.copyWith(
|
final updatedConversation = localConversation.copyWith(
|
||||||
id: serverConversation.id,
|
id: serverConversation.id,
|
||||||
|
systemPrompt: serverConversation.systemPrompt ?? userSystemPrompt,
|
||||||
messages: serverConversation.messages.isNotEmpty
|
messages: serverConversation.messages.isNotEmpty
|
||||||
? serverConversation.messages
|
? serverConversation.messages
|
||||||
: [userMessage],
|
: [userMessage],
|
||||||
@@ -1131,6 +1194,15 @@ Future<void> _sendMessageInternal(
|
|||||||
ref.read(chatMessagesProvider.notifier).addMessage(userMessage);
|
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)
|
// 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)
|
// Immediately trigger title generation after user message is sent (first turn only)
|
||||||
@@ -1234,6 +1306,23 @@ Future<void> _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)
|
// Check feature toggles for API (gated by server availability)
|
||||||
final webSearchEnabled =
|
final webSearchEnabled =
|
||||||
ref.read(webSearchEnabledProvider) &&
|
ref.read(webSearchEnabledProvider) &&
|
||||||
@@ -1270,6 +1359,7 @@ Future<void> _sendMessageInternal(
|
|||||||
activeConvForSeed.id,
|
activeConvForSeed.id,
|
||||||
msgsForSeed,
|
msgsForSeed,
|
||||||
model: selectedModel.id,
|
model: selectedModel.id,
|
||||||
|
systemPrompt: effectiveSystemPrompt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@@ -1378,14 +1468,13 @@ Future<void> _sendMessageInternal(
|
|||||||
|
|
||||||
// Resolve tool servers from user settings (if any)
|
// Resolve tool servers from user settings (if any)
|
||||||
List<Map<String, dynamic>>? toolServers;
|
List<Map<String, dynamic>>? toolServers;
|
||||||
try {
|
final uiSettings = userSettingsData?['ui'] as Map<String, dynamic>?;
|
||||||
final userSettings = await api.getUserSettings();
|
final rawServers = uiSettings != null ? (uiSettings['toolServers'] as List?) : null;
|
||||||
final ui = userSettings['ui'] as Map<String, dynamic>?;
|
if (rawServers != null && rawServers.isNotEmpty) {
|
||||||
final rawServers = ui != null ? (ui['toolServers'] as List?) : null;
|
try {
|
||||||
if (rawServers != null && rawServers.isNotEmpty) {
|
|
||||||
toolServers = await _resolveToolServers(rawServers, api);
|
toolServers = await _resolveToolServers(rawServers, api);
|
||||||
}
|
} catch (_) {}
|
||||||
} catch (_) {}
|
}
|
||||||
|
|
||||||
// Background tasks parity with Web client (safe defaults)
|
// Background tasks parity with Web client (safe defaults)
|
||||||
// Enable title/tags generation on the very first user turn of a new chat.
|
// Enable title/tags generation on the very first user turn of a new chat.
|
||||||
|
|||||||
Reference in New Issue
Block a user