refactor: titles
This commit is contained in:
@@ -1819,107 +1819,6 @@ class ApiService {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generate title for conversation using dedicated endpoint
|
||||
Future<String?> generateTitle({
|
||||
required String conversationId,
|
||||
required List<Map<String, dynamic>> messages,
|
||||
required String model,
|
||||
}) async {
|
||||
try {
|
||||
debugPrint('DEBUG: Generating title for conversation: $conversationId');
|
||||
|
||||
final response = await _dio.post(
|
||||
'/api/v1/tasks/title/completions',
|
||||
data: {'chat_id': conversationId, 'messages': messages, 'model': model},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200 && response.data != null) {
|
||||
DebugLogger.log('Raw title response received successfully');
|
||||
|
||||
// Parse the complex response structure
|
||||
String? extractedTitle;
|
||||
|
||||
try {
|
||||
final responseData = response.data as Map<String, dynamic>;
|
||||
|
||||
// Check if there's a direct title field
|
||||
if (responseData.containsKey('title')) {
|
||||
extractedTitle = responseData['title']?.toString();
|
||||
}
|
||||
// Check if it's in choices format (OpenAI-style response)
|
||||
else if (responseData.containsKey('choices') &&
|
||||
responseData['choices'] is List) {
|
||||
final choices = responseData['choices'] as List;
|
||||
if (choices.isNotEmpty) {
|
||||
final firstChoice = choices[0] as Map<String, dynamic>;
|
||||
if (firstChoice.containsKey('message')) {
|
||||
final message = firstChoice['message'] as Map<String, dynamic>;
|
||||
final content = message['content']?.toString() ?? '';
|
||||
|
||||
// Extract title from JSON-formatted content
|
||||
if (content.contains('```json') && content.contains('```')) {
|
||||
// Extract JSON from markdown code block
|
||||
final jsonStart = content.indexOf('```json') + 7;
|
||||
final jsonEnd = content.lastIndexOf('```');
|
||||
if (jsonEnd > jsonStart) {
|
||||
final jsonString = content
|
||||
.substring(jsonStart, jsonEnd)
|
||||
.trim();
|
||||
try {
|
||||
final jsonData =
|
||||
jsonDecode(jsonString) as Map<String, dynamic>;
|
||||
extractedTitle = jsonData['title']?.toString();
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'DEBUG: Failed to parse JSON from title response: $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Try to parse the content directly as JSON
|
||||
try {
|
||||
final jsonData =
|
||||
jsonDecode(content) as Map<String, dynamic>;
|
||||
extractedTitle = jsonData['title']?.toString();
|
||||
} catch (e) {
|
||||
// If not JSON, use content as-is
|
||||
extractedTitle = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the extracted title
|
||||
if (extractedTitle != null && extractedTitle.isNotEmpty) {
|
||||
// Remove any remaining markdown formatting
|
||||
extractedTitle = extractedTitle
|
||||
.replaceAll(RegExp(r'```.*?```', dotAll: true), '')
|
||||
.trim();
|
||||
extractedTitle = extractedTitle
|
||||
.replaceAll(RegExp(r'^[{"]|["}]$'), '')
|
||||
.trim();
|
||||
|
||||
// Ensure it's not just "New Chat" or empty
|
||||
if (extractedTitle.isNotEmpty && extractedTitle != 'New Chat') {
|
||||
debugPrint(
|
||||
'DEBUG: Successfully extracted title: $extractedTitle',
|
||||
);
|
||||
return extractedTitle;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('DEBUG: Error parsing title response: $e');
|
||||
}
|
||||
|
||||
debugPrint('DEBUG: Could not extract valid title from response');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('DEBUG: Failed to generate title: $e');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Send chat completed notification
|
||||
Future<void> sendChatCompleted({
|
||||
required String chatId,
|
||||
|
||||
@@ -219,6 +219,57 @@ StreamSubscription<String> attachUnifiedChunkedStreaming({
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
bool refreshingSnapshot = false;
|
||||
Future<void> refreshConversationSnapshot() async {
|
||||
if (refreshingSnapshot) return;
|
||||
final chatId = activeConversationId;
|
||||
if (chatId == null || chatId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (api == null) return;
|
||||
|
||||
refreshingSnapshot = true;
|
||||
try {
|
||||
final conversation = await api.getConversation(chatId);
|
||||
|
||||
if (conversation.title.isNotEmpty && conversation.title != 'New Chat') {
|
||||
onChatTitleUpdated?.call(conversation.title);
|
||||
}
|
||||
|
||||
if (conversation.messages.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
ChatMessage? foundAssistant;
|
||||
for (final message in conversation.messages.reversed) {
|
||||
if (message.role == 'assistant') {
|
||||
foundAssistant = message;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final assistant = foundAssistant;
|
||||
if (assistant == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFollowUps(assistant.id, assistant.followUps);
|
||||
updateMessageById(assistant.id, (current) {
|
||||
return current.copyWith(
|
||||
followUps: List<String>.from(assistant.followUps),
|
||||
statusHistory: assistant.statusHistory,
|
||||
sources: assistant.sources,
|
||||
metadata: {...?current.metadata, ...?assistant.metadata},
|
||||
usage: assistant.usage,
|
||||
);
|
||||
});
|
||||
} catch (_) {
|
||||
// Best-effort refresh; ignore failures.
|
||||
} finally {
|
||||
refreshingSnapshot = false;
|
||||
}
|
||||
}
|
||||
|
||||
void channelLineHandlerFactory(String channel) {
|
||||
void handler(dynamic line) {
|
||||
try {
|
||||
@@ -446,6 +497,8 @@ StreamSubscription<String> attachUnifiedChunkedStreaming({
|
||||
);
|
||||
} catch (_) {}
|
||||
|
||||
Future.microtask(refreshConversationSnapshot);
|
||||
|
||||
final msgs = getMessages();
|
||||
if (msgs.isNotEmpty && msgs.last.role == 'assistant') {
|
||||
final lastContent = msgs.last.content.trim();
|
||||
@@ -897,6 +950,7 @@ StreamSubscription<String> attachUnifiedChunkedStreaming({
|
||||
// If SSE-driven (no dynamic channel/background flow), finish now
|
||||
if (!usingDynamicChannel && !isBackgroundFlow) {
|
||||
finishStreaming();
|
||||
Future.microtask(refreshConversationSnapshot);
|
||||
}
|
||||
socketWatchdog?.stop();
|
||||
},
|
||||
@@ -906,6 +960,7 @@ StreamSubscription<String> attachUnifiedChunkedStreaming({
|
||||
} catch (_) {}
|
||||
suppressSocketContent = false;
|
||||
finishStreaming();
|
||||
Future.microtask(refreshConversationSnapshot);
|
||||
socketWatchdog?.stop();
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user