import 'dart:async'; import 'dart:convert'; import 'package:amap_flutter/amap_flutter.dart'; import '../../../data/services/spotify_service.dart'; import 'package:get/get.dart'; import 'package:flutter/widgets.dart'; import 'package:get_storage/get_storage.dart'; import 'package:haptic_feedback/haptic_feedback.dart'; import 'package:just_audio/just_audio.dart'; import 'package:chat_storage/chat_storage.dart'; import 'package:path/path.dart'; import '../../../data/services/ai_service.dart'; import '../../../data/services/deapsound_ai_service.dart'; import '../../../data/services/kouzi_ai_service.dart'; import '../../../data/services/speech_impl/voice_clone_tts_service.dart'; import '../models/message_model.dart'; import '../../../core/utils/logger.dart'; import '../../../data/services/tts_service.dart'; import '../../../data/providers/agent_provider.dart'; import '../../../data/models/agent.dart'; import '../../../data/services/asr_service.dart'; import '../../../data/services/open_ai_service_adapter.dart'; class ChatController extends GetxController { // 服务 late AiService _aiService; late TtsService _ttsService; final AsrService _asrService = Get.find(); late SpotifService _spotifService; // 添加ScrollController用于控制列表滚动 final ScrollController scrollController = ScrollController(); final _amapFlutter = AmapFlutter(); StreamSubscription? _locationStream; String _latLng = ''; String _city = '深圳市'; // Agent 信息 late final Agent agent; // 消息列表 final RxList messages = [].obs; // 输入控制 final RxString inputText = ''.obs; // 状态控制 final RxBool isLoading = false.obs; final RxBool isSending = false.obs; final RxBool isTtsEnabled = true.obs; final RxBool isVoiceRecognitionActive = false.obs; // 分页加载状态 final RxInt _currentPage = 1.obs; final RxInt _totalPages = 1.obs; final RxBool _isLoadingMore = false.obs; final RxBool _hasMoreHistory = true.obs; static const int _pageSize = 20; // 每页加载的消息数量 // 语音交互模式 bool _isVoiceInteractionMode = false; // 语音识别状态 final RxString recognizedText = ''.obs; final RxBool isListening = false.obs; final RxBool isSpeechDetected = false.obs; // 防抖计时器 Timer? _scrollDebounceTimer; // 流式响应控制 StreamSubscription? _aiResponseSubscription; StreamSubscription? _recognitionSubscription; StreamSubscription? _voiceInteractionSubscription; // 取消标志 bool _shouldCancelAiResponse = false; // 音频输入源 bool _audioSourceType = false; // 自动停止语音识别计时器 Timer? _autoStopTimer; // 记录最后一次活动时间 DateTime _lastActivityTime = DateTime.now(); // 音乐播放 final player = AudioPlayer(); final RxString audiourl = ''.obs; final RxBool isPlay = false.obs; // 按住说话、文字输入切换 final RxBool isTextInput = false.obs; final RxBool isHoldSpeak = false.obs; String finalResult = ''; // 按住说话防抖 bool isDebounce = false; Timer? _talkDebounceTimer; Timer? _talkStartDebounceTimer; @override Future onInit() async { super.onInit(); Logger.info('ChatController onInit'); // 从路由参数获取Agent信息 final Map args = Get.arguments ?? {}; final String agentId = args['agent'] ?? args['agentId'] ?? 'personal_assistant'; // 获取Agent final Agent? foundAgent = AgentProvider.getAgentById(agentId); if (foundAgent == null) { Get.back(); Logger.error('无法找到Agent: $agentId'); return; } agent = foundAgent; // 根据Agent ID选择不同的AI服务 switch (agent.id) { case 'cyber_girlfriend': // 亲子陪伴 Get.put(KouziAIService()); _aiService = Get.find(); break; case 'health_advisor': // 多功能AI聊天 Get.put(DeapsoundAIService()); _aiService = Get.find(); break; default: // 默认使用OpenAIServiceAdapter _aiService = Get.find(); Logger.info('使用OpenAIServiceAdapter'); } Get.put(SpotifService()); _spotifService = Get.find(); // 使用克隆音色语音合成 final GetStorage storage = GetStorage(); String timbre = storage.read('timbre') ?? '1'; if (timbre == '1') { _ttsService = Get.find(); } else { Get.put(VoiceCloneTtsService()); _ttsService = Get.find(); } _ttsService.initialize(); _asrService.initialize(); // 设置语音交互模式 _isVoiceInteractionMode = args['isVoiceInteraction'] == true; // 设置TTS服务 await _ttsService.setVoice(agent.voice); // 添加滚动监听器,用于触发加载更多 scrollController.addListener(_scrollListener); // 加载聊天历史 (加载第一页) await _loadChatHistory(); // 如果没有历史记录,添加欢迎消息 if (messages.isEmpty && agent.welcomeMessage.isNotEmpty) { messages.add(Message( role: 'assistant', content: agent.welcomeMessage, timestamp: DateTime.now(), )); // 如果启用了TTS,播放欢迎消息 // if (isTtsEnabled.value) { // _ttsService.speak(agent.welcomeMessage); // } } // 如果isVoiceInteraction为true,自动激活语音识别 if (args['isVoiceInteraction'] == true) { _ttsService.speakOnce(agent.voiceWelcomeMessage ?? agent.welcomeMessage); startVoiceRecognition(); // 自动停止计时器会在startVoiceRecognition中启动 } _getLocation(); player.playerStateStream.listen((state) async { if (state.processingState == ProcessingState.completed) { await player.seek(Duration.zero); player.pause(); isPlay.value = false; } }); // 使用微任务在布局完成后自动滚动到底部 WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); } void _getLocation() async { await _amapFlutter.initLocation(); _locationStream = _amapFlutter.locationStream().listen((data) async { _latLng = '${data['longitude']},${data['latitude']}}'; _city = data['city']; _amapFlutter.stopLocation(); }); _amapFlutter.startLocation(); } @override void onClose() { Logger.info('ChatController onClose'); // 清理资源 scrollController.removeListener(_scrollListener); // 移除监听器 scrollController.dispose(); _aiResponseSubscription?.cancel(); _recognitionSubscription?.cancel(); _voiceInteractionSubscription?.cancel(); _autoStopTimer?.cancel(); _scrollDebounceTimer?.cancel(); // 清理防抖计时器 _talkDebounceTimer?.cancel(); _talkStartDebounceTimer?.cancel(); _amapFlutter.dispose(); _locationStream?.cancel(); // 停止TTS _ttsService.stop(); if (isVoiceRecognitionActive.value) { _asrService.stopContinuousRecognition(); } // 保存当前聊天记录 if (agent.id != null) { // _historyService.saveHistory(agent.id!, messages.toList()); } player.dispose(); super.onClose(); } /// 滚动到列表底部方法,添加防抖功能 void _scrollToBottom() { if (scrollController.hasClients && messages.isNotEmpty) { try { scrollController.animateTo( scrollController.position.maxScrollExtent, duration: Duration(milliseconds: 300), curve: Curves.easeOut, ); } catch (e) { Logger.error('滚动到底部失败', e); } } } /// 中断当前响应 void _interruptCurrentResponse(String reason) { Logger.info('${reason}'); // 停止TTS _ttsService.stop(); // 取消AI响应流订阅 if (_aiResponseSubscription != null) { _shouldCancelAiResponse = true; _aiResponseSubscription!.cancel(); _aiResponseSubscription = null; // 将AI中间消息转换为正常消息,保留已输出的内容 final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); if (index != -1) { messages[index] = messages[index].copyWith( isIntermediate: false, ); messages.refresh(); } // 重置发送状态 isSending.value = false; } } // 发送消息 Future sendMessage(String text) async { if (text.trim().isEmpty) return; // 中断当前响应 _interruptCurrentResponse("发送新消息,中断当前响应"); // 添加用户消息 final userMessage = Message( role: 'user', content: text, timestamp: DateTime.now(), ); messages.add(userMessage); _saveMessageToDb(userMessage); // 清空输入框 inputText.value = ''; // 添加中间状态的AI消息 final intermediateMessage = Message.intermediate('assistant'); messages.add(intermediateMessage); messages.refresh(); WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); isSending.value = true; try { // 构建消息历史 final List> messageHistory = _buildMessageHistory(); // 重置取消标志 _shouldCancelAiResponse = false; String fullResponse = ''; // 获取AI响应流 final responseStream = _aiService.sendMessageStream( messages: messageHistory, systemPrompt: agent.systemPrompt, ); Logger.info( 'liwei----------- 发送用户提问 ${text}', ); // 订阅响应流 _aiResponseSubscription = responseStream.listen( (chunk) { if (_shouldCancelAiResponse) return; Logger.info('chat ${chunk}'); if (chunk is Map) { Logger.info( 'liwei-----------收到卡片消息 ${chunk}', ); _updateIntermediateMessage(meta: chunk); if (chunk['card_spotify'] != null) { play_spotify(chunk['card_spotify']); } if (chunk['card_music'] != null) { play_music(chunk['card_music']); } } else if (chunk is String) { fullResponse += chunk; // 更新中间状态的消息内容 _updateIntermediateMessage(content: fullResponse); // 如果TTS启用,使用流式处理发送到TTS if (isTtsEnabled.value) { String str = chunk.replaceAll('*', ''); _ttsService.speakStream(str); } } }, onDone: () { Logger.info( 'liwei-----------结束当前问题 ${text}', ); if (_shouldCancelAiResponse) return; // 完成响应,将isIntermediate设为false _finalizeResponse(fullResponse); // 如果TTS启用,处理缓冲区中的剩余文本 if (isTtsEnabled.value) { _ttsService.flushStream(); } }, onError: (error) { Logger.error('AI响应错误', error); // 更新中间状态的消息为错误消息 _handleResponseError(); }, ); } catch (e) { Logger.error('发送消息失败', e); _handleResponseError(); } } // 构建消息历史 List> _buildMessageHistory() { final List> messageHistory = []; // 只取最近的10条消息作为上下文 final recentMessages = messages.where((m) => !m.isIntermediate).toList(); final startIndex = recentMessages.length > 10 ? recentMessages.length - 10 : 0; messageHistory.add({ 'role': 'user', 'content': '我当前所在城市是$_city,我的经纬度是$_latLng', }); for (int i = startIndex; i < recentMessages.length; i++) { final m = recentMessages[i]; messageHistory.add({ 'role': m.role, 'content': m.content, }); } return messageHistory; } // 更新中间状态的消息 void _updateIntermediateMessage({Map? meta, String? content}) { final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); if (index != -1) { if (meta != null) { Map messagesData = messages[index].meta ?? {}; messages[index] = messages[index].copyWith( meta: {...messagesData, ...meta}, ); } messages[index] = messages[index].copyWith( content: content, ); messages.refresh(); _scrollToBottom(); } } // 播放音乐 void play_music(Map card) async { try { isPlay.value = false; if (audiourl.value != card['url']) { audiourl.value = ''; await player.setUrl(card['url']); audiourl.value = card['url']; player.play(); } else { player.play(); } isPlay.value = true; } catch (e) { Logger.error('加载异常: ${e.toString()}'); } } // 播放音乐 void pause_music() async { await player.pause(); isPlay.value = false; } // 播放音乐 void play_spotify(Map card) async { try { await _spotifService.initialize(); _spotifService.play(card['url'] as String); isPlay.value = true; audiourl.value = card['url'] as String; } catch (e) { Logger.error('加载异常: ${e.toString()}'); } } // 暂停播放音乐 void pause_spotify() async { try { _spotifService.pause(); isPlay.value = false; } catch (e) { Logger.error('加载异常: ${e.toString()}'); } } // 更新或添加最终响应 void _finalizeResponse(String content) { // 查找AI的中间状态消息 final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); if (index != -1) { // 更新中间状态消息为最终状态 messages[index] = messages[index].copyWith( content: content, isIntermediate: false, timestamp: DateTime.now(), // 更新时间戳为最终时间 ); // 保存最终的助手消息到数据库 _saveMessageToDb(messages[index]); } else { // 如果没有中间状态消息,直接添加新消息 (理论上不应发生) final assistantMsg = Message( role: 'assistant', content: content, timestamp: DateTime.now(), ); messages.add(assistantMsg); _saveMessageToDb(assistantMsg); // 保存到数据库 } isLoading.value = false; isSending.value = false; _aiResponseSubscription = null; // 清理订阅 _shouldCancelAiResponse = false; // 重置取消标志 // 响应完成后,重新启动语音识别的自动停止计时器 if (_isVoiceInteractionMode) { _startAutoStopTimer(); } } // 处理响应错误 void _handleResponseError() { // 查找AI的中间状态消息 final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); if (index != -1) { messages[index] = messages[index].copyWith( content: '抱歉,我遇到了一些问题,无法回应您的请求。', isIntermediate: false, ); messages.refresh(); } isSending.value = false; _aiResponseSubscription = null; } // 切换TTS状态 void toggleTts() { isTtsEnabled.toggle(); // 如果关闭TTS,停止当前播放 if (!isTtsEnabled.value) { _ttsService.stop(); } } // 按下开始说话 void talkLongPress() async { _talkStartDebounceTimer?.cancel(); _talkDebounceTimer?.cancel(); isHoldSpeak.value = true; Haptics.vibrate(HapticsType.success); _talkStartDebounceTimer = Timer(const Duration(milliseconds: 260), () async { if (isDebounce) return; // 中断当前响应 _interruptCurrentResponse("发送新消息,中断当前响应"); try { final recognitionStream = await _asrService.startContinuousRecognition(_audioSourceType); await Future.delayed(const Duration(milliseconds: 260)); isDebounce = true; recognizedText.value = ''; finalResult = ''; // 监听识别事件 _recognitionSubscription = recognitionStream.listen( _handleRecognitionEvent, onError: _handleRecognitionError, ); } catch (e) { Logger.error('启动语音识别失败', e); isHoldSpeak.value = false; } }); } // 松开停止说话 void talkLongPressUp() async { _talkStartDebounceTimer?.cancel(); _talkDebounceTimer?.cancel(); isHoldSpeak.value = false; _talkDebounceTimer = Timer(const Duration(milliseconds: 300), () async { if (!isDebounce) return; try { await _asrService.stopContinuousRecognition(); _recognitionSubscription?.cancel(); _recognitionSubscription = null; String sendText = ''; final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); if (intermediateIndex >= 0) { sendText = messages[intermediateIndex].content; messages.removeAt(intermediateIndex); } sendMessage(sendText); recognizedText.value = ''; finalResult = ''; isDebounce = false; } catch (e) { Logger.error('停止语音识别失败', e); } }); } // 开始语音识别 Future startVoiceRecognition() async { if (isVoiceRecognitionActive.value) return; try { // 启动连续语音识别 // 注意:在UI更新后,使用模拟波形动画替代实际麦克风输入的波形数据 final recognitionStream = await _asrService.startContinuousRecognition(_audioSourceType); isVoiceRecognitionActive.value = true; isListening.value = true; recognizedText.value = ''; // 启动自动停止计时器 _startAutoStopTimer(); // 监听识别事件 _recognitionSubscription = recognitionStream.listen( _handleRecognitionEvent, onError: _handleRecognitionError, ); } catch (e) { Logger.error('启动语音识别失败', e); isVoiceRecognitionActive.value = false; isListening.value = false; } } // 处理语音识别事件 void _handleRecognitionEvent(RecognitionEvent event) { // 更新活动时间 _updateActivityTime(); switch (event.type) { case RecognitionEventType.intermediateResult: _handleRecognizing(event.text); break; case RecognitionEventType.finalResult: _handleFinalResult(event.text); break; case RecognitionEventType.error: _handleRecognitionEventError(event.error); break; default: // 不做任何处理 break; } } // 更新最后活动时间 void _updateActivityTime() { _lastActivityTime = DateTime.now(); } // 启动自动停止计时器 void _startAutoStopTimer() { // 取消现有计时器 _autoStopTimer?.cancel(); // 初始化最后活动时间 _updateActivityTime(); // 根据模式设置超时时间 final int timeoutSeconds = _isVoiceInteractionMode ? 8 : 15; // 创建新的计时器,每秒检查一次 _autoStopTimer = Timer.periodic(Duration(seconds: 1), (timer) { // 检查是否有活动 bool isActive = _ttsService.isSpeaking || isSpeechDetected.value; // 如果有活动,更新时间并继续等待 if (isActive) { _updateActivityTime(); return; } // 检查空闲时间 int idleSeconds = DateTime.now().difference(_lastActivityTime).inSeconds; if (idleSeconds >= timeoutSeconds) { timer.cancel(); _stopVoiceRecognitionAutomatically(); } }); } // 处理语音识别中的状态 void _handleRecognizing(String text) { if (text.isEmpty) return; recognizedText.value = text; // 设置语音检测状态为true,用于控制波形动画 if (!isSpeechDetected.value) { isSpeechDetected.value = true; } // 更新活动时间 _updateActivityTime(); // 用户开始讲话时,立即停止当前TTS和中断AI响应 if (isSending.value || _aiResponseSubscription != null) { _interruptCurrentResponse("检测到用户开始讲话,中断当前响应"); } // 查找或创建用户的中间状态消息 final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); if (intermediateIndex >= 0) { // 更新已有的中间状态消息 messages[intermediateIndex] = messages[intermediateIndex].copyWith( content: isVoiceRecognitionActive.value ? text : finalResult + text, ); messages.refresh(); } else { // 添加新的中间状态消息 var intermediateMessage = Message.intermediate('user'); intermediateMessage = intermediateMessage.copyWith(content: text); messages.add(intermediateMessage); messages.refresh(); } WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); } // 处理语音识别最终结果 void _handleFinalResult(String text) { if (text.isEmpty) return; Logger.info('处理语音识别最终结果: $text'); // 更新活动时间 _updateActivityTime(); if (isVoiceRecognitionActive.value) { // 一直语音说话 recognizedText.value = text; // 查找并移除用户的中间状态消息 final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); if (intermediateIndex >= 0) { messages.removeAt(intermediateIndex); } // 发送识别到的文本 sendMessage(text); // 重置识别文本,但不关闭语音识别框 recognizedText.value = ''; // 重置语音检测状态 isSpeechDetected.value = false; } else { // 按住说话 finalResult += text; recognizedText.value = finalResult; final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); if (intermediateIndex >= 0) { // 更新已有的中间状态消息 messages[intermediateIndex] = messages[intermediateIndex].copyWith( content: finalResult, ); messages.refresh(); } } } // 处理语音识别事件错误 void _handleRecognitionEventError(String? error) { Logger.error('语音识别错误: ${error ?? "未知错误"}'); _cleanupRecognition(); } // 处理语音识别流错误 void _handleRecognitionError(dynamic error) { Logger.error('语音识别错误', error); _cleanupRecognition(); } // 清理语音识别资源 void _cleanupRecognition() { // 查找并移除用户的中间状态消息 final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); if (intermediateIndex >= 0) { if (messages[intermediateIndex].content.trim().isEmpty) { messages.removeAt(intermediateIndex); } else { // 如果中间状态消息有内容,将其转换为正式消息 messages[intermediateIndex] = messages[intermediateIndex].copyWith( isIntermediate: false, ); } messages.refresh(); } // 停止语音识别 isVoiceRecognitionActive.value = false; isListening.value = false; isSpeechDetected.value = false; _asrService.stopContinuousRecognition(); } // 停止语音识别 Future stopVoiceRecognition() async { if (!isVoiceRecognitionActive.value) return; try { await _asrService.stopContinuousRecognition(); _recognitionSubscription?.cancel(); _recognitionSubscription = null; isVoiceRecognitionActive.value = false; isListening.value = false; recognizedText.value = ''; isSpeechDetected.value = false; // 查找并处理用户的中间状态消息 final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); if (intermediateIndex >= 0) { if (messages[intermediateIndex].content.trim().isEmpty) { // 如果没有内容则移除 messages.removeAt(intermediateIndex); } else { // 有内容则转为正式消息 messages[intermediateIndex] = messages[intermediateIndex].copyWith( isIntermediate: false, ); } messages.refresh(); } } catch (e) { Logger.error('停止语音识别失败', e); } } // 清空聊天记录 void clearChat() { messages.clear(); _ttsService.stop(); // 添加欢迎消息 if (agent.welcomeMessage.isNotEmpty) { messages.add(Message( role: 'assistant', content: agent.welcomeMessage, timestamp: DateTime.now(), )); } // 删除数据库中的历史记录 ChatStorage.deleteMessages(sessionId: agent.id).catchError((e) { Logger.error('清空数据库聊天记录失败: ${e.toString()}'); }); } // 滚动监听,用于触发加载更多历史记录 void _scrollListener() { // 当滚动到顶部并且有更多历史记录且不在加载中时,加载更多 if (scrollController.position.pixels == scrollController.position.minScrollExtent && _hasMoreHistory.value && !_isLoadingMore.value) { _loadMoreHistory(); } } // 加载聊天历史 (首次加载或刷新) Future _loadChatHistory() async { isLoading.value = true; _currentPage.value = 1; // 重置为第一页 _hasMoreHistory.value = true; // 假设有更多 try { final pagedResult = await ChatStorage.getMessages( sessionId: agent.id, page: _currentPage.value, pageSize: _pageSize, ); _totalPages.value = pagedResult.totalPages; _hasMoreHistory.value = _currentPage.value < _totalPages.value; final historyMessages = pagedResult.messages.map((chatMsg) { return Message( role: chatMsg.sender, content: chatMsg.message, timestamp: DateTime.parse(chatMsg.timestamp), meta: chatMsg.metadata != null ? jsonDecode(chatMsg.metadata!) : null, ); }).toList(); messages.assignAll(historyMessages.reversed); // 最新的在底部,所以要反转 // 滚动到底部 (如果是首次加载) WidgetsBinding.instance.addPostFrameCallback((_) { if (scrollController.hasClients) { scrollController.jumpTo(scrollController.position.maxScrollExtent); } }); } catch (e) { Logger.error('加载聊天历史失败', e); } finally { isLoading.value = false; } } // 加载更多历史记录 Future _loadMoreHistory() async { if (_isLoadingMore.value || !_hasMoreHistory.value) return; _isLoadingMore.value = true; _currentPage.value++; try { final pagedResult = await ChatStorage.getMessages( sessionId: agent.id, page: _currentPage.value, pageSize: _pageSize, ); _totalPages.value = pagedResult.totalPages; _hasMoreHistory.value = _currentPage.value < _totalPages.value; final olderMessages = pagedResult.messages.map((chatMsg) { return Message( role: chatMsg.sender, content: chatMsg.message, timestamp: DateTime.parse(chatMsg.timestamp), meta: chatMsg.metadata != null ? jsonDecode(chatMsg.metadata!) : null, ); }).toList(); // 将旧消息插入到列表顶部 messages.insertAll(0, olderMessages.reversed); } catch (e) { Logger.error('加载更多聊天历史失败', e); _currentPage.value--; // 加载失败,恢复页码 } finally { _isLoadingMore.value = false; } } // 保存单条聊天消息到数据库 Future _saveMessageToDb(Message message) async { if (message.isIntermediate) return; // 不保存中间状态的消息 try { await ChatStorage.saveMessage( sessionId: agent.id, message: message.content, sender: message.role, metadata: jsonEncode(message.meta), ); } catch (e) { Logger.error('保存消息到数据库失败', e); } } // 添加消息,供外部调用 (如用户发送消息、AI回复) void addMessage(Message message) { messages.add(message); _saveMessageToDb(message); // 保存到数据库 WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); } void _stopVoiceRecognitionAutomatically() { final int timeoutSeconds = _isVoiceInteractionMode ? 8 : 15; Logger.info('${timeoutSeconds}秒无活动,自动停止语音识别'); stopVoiceRecognition(); } /// 检查是否为重复消息 bool _isDuplicateMessage(Message message) { return messages.any((m) => m.role == message.role && m.content == message.content && (m.timestamp.millisecondsSinceEpoch - message.timestamp.millisecondsSinceEpoch) .abs() < 5000); } }