refactor: create preseed helper
This commit is contained in:
@@ -293,6 +293,62 @@ class ChatMessagesNotifier extends StateNotifier<List<ChatMessage>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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<String> _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)
|
// Start a new chat (unified function for both "New Chat" button and home screen)
|
||||||
void startNewChat(dynamic ref) {
|
void startNewChat(dynamic ref) {
|
||||||
// Clear active conversation
|
// Clear active conversation
|
||||||
@@ -473,25 +529,11 @@ Future<void> regenerateMessage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-seed assistant skeleton
|
// Pre-seed assistant skeleton and persist chain
|
||||||
final String assistantMessageId = const Uuid().v4();
|
final String assistantMessageId = await _preseedAssistantAndPersist(
|
||||||
final assistantMessage = ChatMessage(
|
ref,
|
||||||
id: assistantMessageId,
|
modelId: selectedModel.id,
|
||||||
role: 'assistant',
|
|
||||||
content: '',
|
|
||||||
timestamp: DateTime.now(),
|
|
||||||
model: selectedModel.id,
|
|
||||||
isStreaming: true,
|
|
||||||
);
|
);
|
||||||
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)
|
// Stream response via background task (socket/dynamic channel or polling)
|
||||||
final response = api!.sendMessage(
|
final response = api!.sendMessage(
|
||||||
|
|||||||
@@ -1544,42 +1544,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
|||||||
); // ErrorBoundary
|
); // ErrorBoundary
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveConversationBeforeLeaving(WidgetRef ref) async {
|
// Removed legacy save-before-leave hook; server manages chat state via background pipeline.
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showModelDropdown(
|
void _showModelDropdown(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
|
|||||||
Reference in New Issue
Block a user