14 changed files with 695 additions and 277 deletions
@ -0,0 +1,95 @@ |
|||||
|
import 'package:get/get.dart'; |
||||
|
import 'dart:async'; |
||||
|
import 'volcano_ai_service.dart'; |
||||
|
import 'volcano_tts_service.dart'; |
||||
|
import '../../modules/chat/models/message_model.dart'; |
||||
|
|
||||
|
class BackgroundAgentService extends GetxService { |
||||
|
static BackgroundAgentService get to => Get.find(); |
||||
|
|
||||
|
final VolcanoAIService _aiService; |
||||
|
final VolcanoTtsService _ttsService; |
||||
|
final List<Message> _messageHistory = []; |
||||
|
String _pendingTtsText = ''; |
||||
|
static const int _minTtsLength = 20; |
||||
|
bool _isProcessing = false; |
||||
|
|
||||
|
BackgroundAgentService() |
||||
|
: _aiService = VolcanoAIService(), |
||||
|
_ttsService = Get.find<VolcanoTtsService>(); |
||||
|
|
||||
|
Future<void> handleAgentInteraction(String systemPrompt) async { |
||||
|
if (_isProcessing) return; |
||||
|
_isProcessing = true; |
||||
|
|
||||
|
try { |
||||
|
// 构建用于生成回应的消息列表 |
||||
|
final messages = [ |
||||
|
{'role': 'system', 'content': systemPrompt}, |
||||
|
{'role': 'user', 'content': '请用一句简短的话回应我,要体现你的特点,不要超过12个字。'}, |
||||
|
]; |
||||
|
|
||||
|
String fullResponse = ''; |
||||
|
await for (final chunk in _aiService.sendMessageStream( |
||||
|
messages: messages, |
||||
|
systemPrompt: systemPrompt, |
||||
|
)) { |
||||
|
fullResponse += chunk; |
||||
|
|
||||
|
// 累积文本并处理TTS |
||||
|
_pendingTtsText += chunk; |
||||
|
if (_ttsService.isEnabled.value) { |
||||
|
if (_pendingTtsText.length >= _minTtsLength) { |
||||
|
int lastSentenceEnd = _findLastSentenceEnd(_pendingTtsText); |
||||
|
if (lastSentenceEnd > 0) { |
||||
|
String textToSpeak = _pendingTtsText.substring(0, lastSentenceEnd + 1); |
||||
|
await _ttsService.speak(textToSpeak); |
||||
|
_pendingTtsText = _pendingTtsText.substring(lastSentenceEnd + 1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 处理剩余的文本 |
||||
|
if (_ttsService.isEnabled.value && _pendingTtsText.isNotEmpty) { |
||||
|
await _ttsService.speak(_pendingTtsText); |
||||
|
} |
||||
|
_pendingTtsText = ''; |
||||
|
|
||||
|
// 保存对话历史 |
||||
|
_messageHistory.add(Message( |
||||
|
role: 'assistant', |
||||
|
content: fullResponse, |
||||
|
timestamp: DateTime.now(), |
||||
|
)); |
||||
|
|
||||
|
// 限制历史记录长度 |
||||
|
if (_messageHistory.length > 10) { |
||||
|
_messageHistory.removeAt(0); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
print('Background agent interaction failed: $e'); |
||||
|
} finally { |
||||
|
_isProcessing = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int _findLastSentenceEnd(String text) { |
||||
|
final sentenceEnds = [ |
||||
|
text.lastIndexOf('。'), |
||||
|
text.lastIndexOf('!'), |
||||
|
text.lastIndexOf('?'), |
||||
|
text.lastIndexOf('.'), |
||||
|
text.lastIndexOf('!'), |
||||
|
text.lastIndexOf('?'), |
||||
|
]; |
||||
|
|
||||
|
return sentenceEnds.reduce((max, pos) => pos > max ? pos : max); |
||||
|
} |
||||
|
|
||||
|
List<Message> get messageHistory => List.unmodifiable(_messageHistory); |
||||
|
|
||||
|
void clearHistory() { |
||||
|
_messageHistory.clear(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,147 @@ |
|||||
|
import 'dart:io'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; |
||||
|
import 'package:device_info_plus/device_info_plus.dart'; |
||||
|
import 'package:permission_handler/permission_handler.dart'; |
||||
|
import '../providers/agent_provider.dart'; |
||||
|
|
||||
|
class NotificationService extends GetxService { |
||||
|
static NotificationService get to => Get.find(); |
||||
|
|
||||
|
final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin(); |
||||
|
bool _notificationActive = false; |
||||
|
bool _isRequestingPermission = false; |
||||
|
|
||||
|
String get _defaultAgentName { |
||||
|
final agent = AgentProvider.getAgentById('personal_assistant'); |
||||
|
return agent?.name ?? 'Deep Voice'; |
||||
|
} |
||||
|
|
||||
|
Future<void> init() async { |
||||
|
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); |
||||
|
const iosSettings = DarwinInitializationSettings( |
||||
|
requestAlertPermission: false, // 初始化时不请求权限 |
||||
|
requestBadgePermission: false, |
||||
|
requestSoundPermission: false, |
||||
|
); |
||||
|
|
||||
|
const initSettings = InitializationSettings( |
||||
|
android: androidSettings, |
||||
|
iOS: iosSettings, |
||||
|
); |
||||
|
|
||||
|
await _notificationsPlugin.initialize( |
||||
|
initSettings, |
||||
|
onDidReceiveNotificationResponse: (details) { |
||||
|
if (Get.currentRoute != '/chat') { |
||||
|
Get.toNamed('/chat'); |
||||
|
} |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
// 创建通知渠道 |
||||
|
if (Platform.isAndroid) { |
||||
|
await _notificationsPlugin.resolvePlatformSpecificImplementation< |
||||
|
AndroidFlutterLocalNotificationsPlugin>()?.createNotificationChannel( |
||||
|
const AndroidNotificationChannel( |
||||
|
'foreground_service', // id |
||||
|
'Foreground Service', // name |
||||
|
description: 'Used for the foreground service notification', // description |
||||
|
importance: Importance.min, |
||||
|
playSound: false, |
||||
|
enableVibration: false, |
||||
|
showBadge: false, |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
await _requestPermissionIfNeeded(); |
||||
|
|
||||
|
// 在初始化完成后显示前台服务通知 |
||||
|
await showForegroundNotification(); |
||||
|
} |
||||
|
|
||||
|
Future<void> showForegroundNotification() async { |
||||
|
if (_notificationActive) return; |
||||
|
|
||||
|
// 在显示通知前确保有权限 |
||||
|
await _requestPermissionIfNeeded(); |
||||
|
|
||||
|
final androidDetails = AndroidNotificationDetails( |
||||
|
'foreground_service', |
||||
|
'Foreground Service', |
||||
|
channelDescription: 'Used for the foreground service notification', |
||||
|
importance: Importance.min, |
||||
|
priority: Priority.min, |
||||
|
ongoing: true, |
||||
|
autoCancel: false, |
||||
|
showWhen: false, |
||||
|
enableVibration: false, |
||||
|
playSound: false, |
||||
|
icon: '@mipmap/ic_launcher', |
||||
|
category: AndroidNotificationCategory.service, |
||||
|
actions: [ |
||||
|
const AndroidNotificationAction( |
||||
|
'chat', |
||||
|
'开始对话', |
||||
|
showsUserInterface: true, |
||||
|
), |
||||
|
], |
||||
|
); |
||||
|
|
||||
|
const iosDetails = DarwinNotificationDetails( |
||||
|
presentAlert: false, |
||||
|
presentBadge: false, |
||||
|
presentSound: false, |
||||
|
interruptionLevel: InterruptionLevel.passive, |
||||
|
); |
||||
|
|
||||
|
final notificationDetails = NotificationDetails( |
||||
|
android: androidDetails, |
||||
|
iOS: iosDetails, |
||||
|
); |
||||
|
|
||||
|
await _notificationsPlugin.show( |
||||
|
0, |
||||
|
_defaultAgentName, |
||||
|
'点击开始对话', |
||||
|
notificationDetails, |
||||
|
); |
||||
|
|
||||
|
_notificationActive = true; |
||||
|
} |
||||
|
|
||||
|
Future<void> cancelNotification() async { |
||||
|
if (!_notificationActive) return; |
||||
|
await _notificationsPlugin.cancel(0); |
||||
|
_notificationActive = false; |
||||
|
} |
||||
|
|
||||
|
Future<void> _requestPermissionIfNeeded() async { |
||||
|
if (_isRequestingPermission) return; |
||||
|
_isRequestingPermission = true; |
||||
|
|
||||
|
try { |
||||
|
if (Platform.isAndroid) { |
||||
|
final androidInfo = await DeviceInfoPlugin().androidInfo; |
||||
|
if (androidInfo.version.sdkInt >= 33) { |
||||
|
final status = await Permission.notification.status; |
||||
|
if (status.isDenied) { |
||||
|
await Permission.notification.request(); |
||||
|
} |
||||
|
} |
||||
|
} else if (Platform.isIOS) { |
||||
|
await _notificationsPlugin |
||||
|
.resolvePlatformSpecificImplementation< |
||||
|
IOSFlutterLocalNotificationsPlugin>() |
||||
|
?.requestPermissions( |
||||
|
alert: true, |
||||
|
badge: true, |
||||
|
sound: true, |
||||
|
); |
||||
|
} |
||||
|
} finally { |
||||
|
_isRequestingPermission = false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,25 +1,9 @@ |
|||||
import 'package:get/get.dart'; |
import 'package:get/get.dart'; |
||||
import '../controllers/chat_controller.dart'; |
import '../controllers/chat_controller.dart'; |
||||
|
|
||||
class ChatBinding extends Bindings { |
class ChatBinding implements Bindings { |
||||
@override |
@override |
||||
void dependencies() { |
void dependencies() { |
||||
// 获取传递的参数 |
Get.put(ChatController()); |
||||
final arguments = Get.arguments; |
|
||||
if (arguments == null || !(arguments is Map<String, dynamic>)) { |
|
||||
print('Chat binding: 未提供有效的参数'); |
|
||||
Get.back(); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
// 创建并注入控制器 |
|
||||
Get.put(ChatController( |
|
||||
agentId: arguments['agentId'], |
|
||||
agentName: arguments['agentName'], |
|
||||
agentAvatar: arguments['agentAvatar'], |
|
||||
agentSubtitle: arguments['agentSubtitle'], |
|
||||
systemPrompt: arguments['systemPrompt'], |
|
||||
welcomeMessage: arguments['welcomeMessage'], |
|
||||
)); |
|
||||
} |
} |
||||
} |
} |
||||
Loading…
Reference in new issue