|
|
|
@ -7,61 +7,61 @@ import 'package:flutter/services.dart'; |
|
|
|
enum AgentServiceEventType { |
|
|
|
/// 识别开始 |
|
|
|
recognitionStarted, |
|
|
|
|
|
|
|
|
|
|
|
/// 识别中 |
|
|
|
recognizing, |
|
|
|
|
|
|
|
|
|
|
|
/// 识别最终结果 |
|
|
|
recognitionResult, |
|
|
|
|
|
|
|
|
|
|
|
/// 识别停止 |
|
|
|
recognitionStopped, |
|
|
|
|
|
|
|
|
|
|
|
/// 识别取消 |
|
|
|
recognitionCanceled, |
|
|
|
|
|
|
|
|
|
|
|
/// 自动停止 |
|
|
|
autoStop, |
|
|
|
|
|
|
|
|
|
|
|
/// 识别错误 |
|
|
|
recognitionError, |
|
|
|
|
|
|
|
|
|
|
|
/// TTS开始 |
|
|
|
ttsStarted, |
|
|
|
|
|
|
|
|
|
|
|
/// TTS完成 |
|
|
|
ttsCompleted, |
|
|
|
|
|
|
|
|
|
|
|
/// TTS取消 |
|
|
|
ttsCanceled, |
|
|
|
|
|
|
|
|
|
|
|
/// TTS停止 |
|
|
|
ttsStopped, |
|
|
|
|
|
|
|
|
|
|
|
/// 响应中断 |
|
|
|
responseInterrupted, |
|
|
|
|
|
|
|
|
|
|
|
/// AI助手Token |
|
|
|
assistantToken, |
|
|
|
|
|
|
|
|
|
|
|
/// AI助手完整响应 |
|
|
|
assistantResponse, |
|
|
|
|
|
|
|
|
|
|
|
/// 函数调用 |
|
|
|
functionCall, |
|
|
|
|
|
|
|
|
|
|
|
/// 函数调用结果 |
|
|
|
functionCallResult, |
|
|
|
|
|
|
|
|
|
|
|
/// 图片处理中 |
|
|
|
imageProcessing, |
|
|
|
|
|
|
|
|
|
|
|
/// 图片准备就绪 |
|
|
|
imageReady, |
|
|
|
|
|
|
|
|
|
|
|
/// 错误 |
|
|
|
error, |
|
|
|
|
|
|
|
|
|
|
|
/// 未知事件 |
|
|
|
unknown |
|
|
|
} |
|
|
|
@ -70,12 +70,12 @@ enum AgentServiceEventType { |
|
|
|
class AgentServiceEvent { |
|
|
|
/// 事件类型 |
|
|
|
final AgentServiceEventType type; |
|
|
|
|
|
|
|
|
|
|
|
/// 事件数据 |
|
|
|
final Map<String, dynamic> data; |
|
|
|
|
|
|
|
|
|
|
|
AgentServiceEvent({required this.type, required this.data}); |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
String toString() => 'AgentServiceEvent(type: $type, data: $data)'; |
|
|
|
} |
|
|
|
@ -85,20 +85,22 @@ class AgentServiceException implements Exception { |
|
|
|
final String code; |
|
|
|
final String message; |
|
|
|
final dynamic details; |
|
|
|
|
|
|
|
|
|
|
|
AgentServiceException(this.code, this.message, [this.details]); |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
String toString() => '代理服务异常($code): $message'; |
|
|
|
} |
|
|
|
|
|
|
|
/// 代理服务插件 |
|
|
|
class AgentService { |
|
|
|
static const MethodChannel _channel = MethodChannel('com.yunqiinnovation.agent_service'); |
|
|
|
static const EventChannel _eventChannel = EventChannel('com.yunqiinnovation.agent_service/events'); |
|
|
|
|
|
|
|
static const MethodChannel _channel = |
|
|
|
MethodChannel('com.yunqiinnovation.agent_service'); |
|
|
|
static const EventChannel _eventChannel = |
|
|
|
EventChannel('com.yunqiinnovation.agent_service/events'); |
|
|
|
|
|
|
|
static Stream<AgentServiceEvent>? _eventStream; |
|
|
|
|
|
|
|
|
|
|
|
/// 获取事件流 |
|
|
|
static Stream<AgentServiceEvent> get events { |
|
|
|
_eventStream ??= _eventChannel.receiveBroadcastStream().map((event) { |
|
|
|
@ -106,22 +108,21 @@ class AgentService { |
|
|
|
final Map<String, dynamic> eventMap = jsonDecode(event); |
|
|
|
final String eventName = eventMap['event']; |
|
|
|
final Map<String, dynamic> eventData = eventMap['data']; |
|
|
|
|
|
|
|
|
|
|
|
// 将事件名称转换为枚举 |
|
|
|
final AgentServiceEventType type = _stringToEventType(eventName); |
|
|
|
|
|
|
|
|
|
|
|
return AgentServiceEvent(type: type, data: eventData); |
|
|
|
} catch (e) { |
|
|
|
return AgentServiceEvent( |
|
|
|
type: AgentServiceEventType.unknown, |
|
|
|
data: {'error': e.toString(), 'rawEvent': event} |
|
|
|
); |
|
|
|
type: AgentServiceEventType.unknown, |
|
|
|
data: {'error': e.toString(), 'rawEvent': event}); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
return _eventStream!; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 将字符串事件名转换为枚举类型 |
|
|
|
static AgentServiceEventType _stringToEventType(String eventName) { |
|
|
|
switch (eventName) { |
|
|
|
@ -165,9 +166,9 @@ class AgentService { |
|
|
|
return AgentServiceEventType.unknown; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 初始化代理服务 |
|
|
|
/// |
|
|
|
/// |
|
|
|
/// [config] 初始化配置,应包含以下参数: |
|
|
|
/// - azureSpeechKey: Azure语音服务密钥 |
|
|
|
/// - azureSpeechRegion: Azure语音服务区域 |
|
|
|
@ -176,7 +177,7 @@ class AgentService { |
|
|
|
/// - openaiModel: (可选) OpenAI模型名称,默认为"gpt-3.5-turbo" |
|
|
|
/// - mcpServer: (可选) MCP服务器地址 |
|
|
|
/// - systemPrompt: (可选) 自定义系统提示词,用于设置AI语音助手的行为和风格 |
|
|
|
/// |
|
|
|
/// |
|
|
|
/// 返回是否初始化成功 |
|
|
|
static Future<bool> initialize(Map<String, dynamic> config) async { |
|
|
|
try { |
|
|
|
@ -188,9 +189,9 @@ class AgentService { |
|
|
|
throw AgentServiceException(e.code, e.message ?? '初始化失败', e.details); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 启动语音助手服务 |
|
|
|
/// |
|
|
|
/// |
|
|
|
/// 启动常驻的语音助手服务,支持后台蓝牙设备唤醒和媒体按钮唤醒 |
|
|
|
/// [config] 配置参数,应包含以下参数: |
|
|
|
/// - azureSpeechKey: Azure语音服务密钥 |
|
|
|
@ -199,7 +200,7 @@ class AgentService { |
|
|
|
/// - openaiBaseUrl: (可选) OpenAI API 基础URL |
|
|
|
/// - openaiModel: (可选) OpenAI模型名称,默认为"gpt-3.5-turbo" |
|
|
|
/// - mcpServer: (可选) MCP服务器地址 |
|
|
|
/// |
|
|
|
/// |
|
|
|
/// 返回是否成功启动服务 |
|
|
|
static Future<bool> startAgentService(Map<String, dynamic> config) async { |
|
|
|
try { |
|
|
|
@ -211,9 +212,9 @@ class AgentService { |
|
|
|
throw AgentServiceException(e.code, e.message ?? '启动语音助手服务失败', e.details); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 停止语音助手服务 |
|
|
|
/// |
|
|
|
/// |
|
|
|
/// 停止常驻的语音助手服务 |
|
|
|
/// 返回是否成功停止服务 |
|
|
|
static Future<bool> stopAgentService() async { |
|
|
|
@ -224,9 +225,9 @@ class AgentService { |
|
|
|
throw AgentServiceException(e.code, e.message ?? '停止语音助手服务失败', e.details); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 开始对话 |
|
|
|
/// |
|
|
|
/// |
|
|
|
/// 启动语音识别,开始监听用户语音输入 |
|
|
|
/// 返回是否成功开始对话 |
|
|
|
static Future<bool> startConversation() async { |
|
|
|
@ -237,9 +238,9 @@ class AgentService { |
|
|
|
throw AgentServiceException(e.code, e.message ?? '启动对话失败', e.details); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 停止对话 |
|
|
|
/// |
|
|
|
/// |
|
|
|
/// 停止语音识别 |
|
|
|
/// 返回是否成功停止对话 |
|
|
|
static Future<bool> stopConversation() async { |
|
|
|
@ -250,13 +251,14 @@ class AgentService { |
|
|
|
throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 处理文本输入 |
|
|
|
/// |
|
|
|
/// |
|
|
|
/// [text] 文本内容 |
|
|
|
/// [speakResponse] 是否朗读响应 |
|
|
|
/// 返回是否成功处理文本 |
|
|
|
static Future<bool> processTextInput(String text, {bool speakResponse = false}) async { |
|
|
|
static Future<bool> processTextInput(String text, |
|
|
|
{bool speakResponse = false}) async { |
|
|
|
try { |
|
|
|
final bool result = await _channel.invokeMethod('processTextInput', { |
|
|
|
'text': text, |
|
|
|
@ -267,9 +269,9 @@ class AgentService { |
|
|
|
throw AgentServiceException(e.code, e.message ?? '处理文本输入失败', e.details); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 朗读文本 |
|
|
|
/// |
|
|
|
/// |
|
|
|
/// [text] 要朗读的文本 |
|
|
|
static Future<bool> speakText(String text) async { |
|
|
|
try { |
|
|
|
@ -281,7 +283,7 @@ class AgentService { |
|
|
|
throw AgentServiceException(e.code, e.message ?? '语音合成失败', e.details); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 停止语音合成 |
|
|
|
static Future<bool> stopTts() async { |
|
|
|
try { |
|
|
|
@ -291,7 +293,7 @@ class AgentService { |
|
|
|
throw AgentServiceException(e.code, e.message ?? '停止语音合成失败', e.details); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 清除聊天历史 |
|
|
|
static Future<bool> clearChatHistory() async { |
|
|
|
try { |
|
|
|
@ -301,15 +303,15 @@ class AgentService { |
|
|
|
throw AgentServiceException(e.code, e.message ?? '清除聊天历史失败', e.details); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 中断当前响应 |
|
|
|
/// |
|
|
|
/// |
|
|
|
/// 停止语音合成和AI流输出 |
|
|
|
static Future<bool> interruptCurrentResponse() async { |
|
|
|
try { |
|
|
|
// 首先尝试停止TTS播放 |
|
|
|
await stopTts(); |
|
|
|
|
|
|
|
|
|
|
|
// 根据原生层的实现,processTextInput可以被用来中断当前的AI流输出 |
|
|
|
// 这里我们通过发送一个特殊的空字符串来实现这一点 |
|
|
|
// 然后立即返回true,因为我们不需要真正处理任何文本 |
|
|
|
@ -318,14 +320,15 @@ class AgentService { |
|
|
|
throw AgentServiceException(e.code, e.message ?? '中断响应失败', e.details); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 处理图片输入 |
|
|
|
/// |
|
|
|
/// |
|
|
|
/// [imagePath] 图片文件路径 |
|
|
|
/// [text] 可选的文本描述或问题 |
|
|
|
/// [speakResponse] 是否朗读响应 |
|
|
|
/// 返回是否成功处理图片 |
|
|
|
static Future<bool> processImageInput(String imagePath, {String text = "", bool speakResponse = false}) async { |
|
|
|
static Future<bool> processImageInput(String imagePath, |
|
|
|
{String text = "", bool speakResponse = false}) async { |
|
|
|
try { |
|
|
|
final bool result = await _channel.invokeMethod('processImageInput', { |
|
|
|
'imagePath': imagePath, |
|
|
|
@ -337,7 +340,7 @@ class AgentService { |
|
|
|
throw AgentServiceException(e.code, e.message ?? '处理图片输入失败', e.details); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 释放资源 |
|
|
|
static Future<bool> dispose() async { |
|
|
|
try { |
|
|
|
@ -347,4 +350,4 @@ class AgentService { |
|
|
|
throw AgentServiceException(e.code, e.message ?? '释放资源失败', e.details); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|