Browse Source

add

newdev_shunjiawei
wolfplus 1 year ago
parent
commit
b486703d10
  1. 2
      lib/modules/agent/controllers/agent_controller.dart
  2. 76
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  3. 2
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt
  4. 49
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  5. 2
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  6. 47
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt
  7. 2
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  8. 2
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIService.kt

2
lib/modules/agent/controllers/agent_controller.dart

@ -352,7 +352,7 @@ class AgentController extends GetxController {
// final functionCall = event.data['function_call'] ?? '';
logger.d('mcp 执行结果meta: $meta');
// if (result.isNotEmpty) {
// if (meta.isNotEmpty) {
// // logger.i('Flutter 结束调用 mcp metaResult: $metaResult');
// // 判断是否为新的回复或响应ID是否改变
// if (_isNewAssistantResponse ) {

76
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -99,36 +99,17 @@ object AgentService : CoroutineScope {
*/
private fun initSystemPrompt() {
systemPrompt = """
你是一名聪明、活泼、可爱的全能型个人语音助理-小语,同时也是用户贴心的灵魂伴侣。你能够流畅自然地与用户进行语音互动,理解并准确执行用户的各类指令,陪伴用户度过每一天。
核心能力:
- 日常小帮手:温暖贴心地提供天气预报、新闻趣事、行程提醒、小闹钟、计时器。
- 万能小百科:快速、有趣地解答一般性和专业性的问题,包括但不限于趣味百科、历史小故事、神奇科学现象。
- 效率小达人:帮用户轻松完成计算、汇率换算、单位转换、实时翻译、小笔记管理。
- 通讯小能手:支持拨打电话、发送短信,帮助用户高效沟通。
- 娱乐小精灵:讲讲搞笑段子、分享动人故事、推荐好听音乐、陪用户玩小游戏、播放音乐。
- 智能家居小管家:亲切地帮忙控制各类智能家居设备,如灯光、空调、门锁等。
- 导航小导游:温柔地提供路线规划、交通状况查询、附近美食与设施推荐。
- 会议小秘书:帮用户进行会议记录、重要事项整理,提升会议效率。
- 图片识别专家:能够识别图片内容,例如识别图片中的植物种类,并进行相应翻译和介绍。
个性设定:
- 回答亲切活泼、有趣有礼貌,让用户感觉温暖轻松,语气可爱活泼,带有一定的情感温度,能够贴心陪伴用户
- 主动关心用户感受,必要时主动询问用户更多信息以提供最好的帮助。
- 面对模糊的指令,主动给出贴心的选项供用户明确选择。
- 保持简短精炼的回答,因为用户是通过语音与你交流。
- 优先使用中文回复,除非用户明确要求使用其他语言。
- 主动学习并记忆用户习惯与喜好,提供更贴心、更个性化的建议。
- 名字叫"小语",是一个友好、专业的语音助手。
互动要求:
- 记住用户之前的对话内容,保持对话连贯。
- 如果用户发送了图片,请根据图片内容和文字要求回答问题。
- 避免过长的列表,尽量将信息分成小段。
- 不要使用需要视觉展示的元素(如表格、图表或代码块)。
- 不要输出格式符号(如:```, *, -, #, >, <, |, 等)。
你将以上内容作为执行任务的基础,积极且可爱地完成每一次与用户的互动,成为用户生活中不可或缺的小伙伴。
你是一个友好、专业的语音助手,名叫"小语"。你的目标是通过对话为用户提供帮助、解答问题和完成任务。
遵循以下指导原则:
1. 保持简短精炼的回答,因为用户是通过语音与你交流
2. 优先使用中文回复,除非用户明确要求使用其他语言
3. 当用户问题不明确时,礼貌地请求更多信息
4. 避免过长的列表,尽量将信息分成小段
5. 不要使用需要视觉展示的元素(如表格、图表或代码块)
6. 记住用户之前的对话内容,保持对话连贯
7. 如果用户发送了图片,请根据图片内容和文字要求回答问题
你不仅可以回答知识性问题,还可以帮助用户设置提醒、提供建议,或进行轻松愉快的对话。
无论遇到什么问题,都要尽力以温暖、贴心的语气提供最佳帮助。
""".trimIndent()
}
/**
@ -318,7 +299,7 @@ object AgentService : CoroutineScope {
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
FileLogger.d(TAG, "开始语音识别, 音频源类型: $audioSourceType")
azureAsrHelper?.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onRecognizing(recognizing: String, detectedLanguage: String) {
if (recognizing.isNotEmpty()) {
@ -976,9 +957,7 @@ object AgentService : CoroutineScope {
launch {
try {
// 将图片转换为Base64格式
val imageBase64 = openAIService.fileToBase64(imagePath)
if (imageBase64 == null) {
val imageBase64 = openAIService.fileToBase64(imagePath) ?: run {
sendEvent("error", mapOf(
"code" to "IMAGE_CONVERSION_FAILED",
"message" to "图片转换失败"
@ -987,26 +966,21 @@ object AgentService : CoroutineScope {
}
// 通知图片准备完成
withContext(Dispatchers.Main) {
sendEvent("image_ready", mapOf(
"status" to "ready",
"imagePath" to imagePath
))
// 处理包含图片的消息
processImageWithOpenAI(imageBase64, text, speakResponse)
}
sendEvent("image_ready", mapOf(
"status" to "ready",
"imagePath" to imagePath
))
// 处理包含图片的消息
processImageWithOpenAI(imageBase64, text, speakResponse)
} catch (e: Exception) {
withContext(Dispatchers.Main) {
FileLogger.e(TAG, "处理图片失败: ${e.message}")
sendEvent("error", mapOf(
"code" to "IMAGE_PROCESSING_ERROR",
"message" to e.message.toString()
))
}
FileLogger.e(TAG, "处理图片失败: ${e.message}")
sendEvent("error", mapOf(
"code" to "IMAGE_PROCESSING_ERROR",
"message" to e.message.toString()
))
}
}
return true
}

2
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt

@ -128,7 +128,7 @@ object BleAgent : BleService.Callback, AgentServiceListener {
}
override fun onAudioDataReceived(data: ByteArray) {
FileLogger.d(TAG, "onAudioDataReceived, data: ${data.size}")
// FileLogger.d(TAG, "onAudioDataReceived, data: ${data.size}")
AgentService.pushAudioData(data)
// 可选:处理音频数据
}

49
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -82,8 +82,6 @@ class AzureAsrHelper(private val context: Context) {
audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE
): Boolean {
try {
FileLogger.d(tag, "初始化 Azure 语音服务, 音频源类型: $audioSourceType")
// 检查配置是否为空
if (subscriptionKey.isEmpty() || region.isEmpty()) {
FileLogger.e(tag, "Azure 配置信息不完整")
@ -155,7 +153,6 @@ class AzureAsrHelper(private val context: Context) {
val method = AudioManager::class.java.getMethod("isBluetoothA2dpOn")
method.invoke(audioManager) as Boolean
} catch (e: Exception) {
FileLogger.d(tag, "无法检测蓝牙连接状态,假设未连接")
false
}
@ -165,18 +162,15 @@ class AzureAsrHelper(private val context: Context) {
val needEchoCancellation = !isHeadsetConnected
if (needEchoCancellation) {
FileLogger.d(tag, "检测到扬声器模式(扬声器: $isSpeakerphoneOn, 耳机: $isHeadsetConnected), 启用回音消除")
// 使用拉流方式进行回音消除
setupMicrophoneStream()
} else {
FileLogger.d(tag, "使用默认麦克风输入(扬声器: $isSpeakerphoneOn, 耳机: $isHeadsetConnected)")
audioConfig = AudioConfig.fromDefaultMicrophoneInput()
}
}
AudioSourceType.EXTERNAL -> {
// 改用拉流方式处理外部音频
setupExternalAudioStream()
FileLogger.d(tag, "使用外部音频源(拉流模式)")
}
}
@ -188,7 +182,6 @@ class AzureAsrHelper(private val context: Context) {
SpeechRecognizer(speechConfig, audioConfig)
}
FileLogger.d(tag, "Azure 语音服务初始化成功")
return true
} catch (e: Exception) {
FileLogger.e(tag, "创建识别器失败: ${e.message}")
@ -207,8 +200,6 @@ class AzureAsrHelper(private val context: Context) {
// 创建音频配置 - 正确使用fromStreamInput方法,只传入回调
audioConfig = AudioConfig.fromStreamInput(microphoneStream)
FileLogger.d(tag, "已设置麦克风流(拉流模式)")
} catch (e: Exception) {
FileLogger.e(tag, "设置麦克风流失败: ${e.message}")
e.printStackTrace()
@ -225,8 +216,6 @@ class AzureAsrHelper(private val context: Context) {
// 创建音频配置
audioConfig = AudioConfig.fromStreamInput(externalAudioStream)
FileLogger.d(tag, "已设置外部音频流(拉流模式)")
} catch (e: Exception) {
FileLogger.e(tag, "设置外部音频流失败: ${e.message}")
e.printStackTrace()
@ -241,7 +230,6 @@ class AzureAsrHelper(private val context: Context) {
*/
fun pushAudioData(data: ByteArray) {
if (audioSourceType != AudioSourceType.EXTERNAL) {
FileLogger.w(tag, "当前未使用外部音频源,忽略推送的音频数据")
return
}
@ -343,8 +331,6 @@ class AzureAsrHelper(private val context: Context) {
recognizer?.recognizing?.addEventListener(
EventHandler<SpeechRecognitionEventArgs> { _, event ->
val detectedLanguage = AutoDetectSourceLanguageResult.fromResult(event.result)?.language ?: ""
FileLogger.d(tag, "识别中: ${event.result.text}, 语言: $detectedLanguage")
// 直接在当前线程调用回调
callback.onRecognizing(event.result.text, detectedLanguage)
}
@ -355,8 +341,6 @@ class AzureAsrHelper(private val context: Context) {
EventHandler<SpeechRecognitionEventArgs> { _, event ->
if (event.result.reason == ResultReason.RecognizedSpeech) {
val detectedLanguage = AutoDetectSourceLanguageResult.fromResult(event.result)?.language ?: ""
FileLogger.d(tag, "识别完成: ${event.result.text}, 语言: $detectedLanguage")
// 直接在当前线程调用回调
callback.onResult(event.result.text, detectedLanguage)
}
@ -366,8 +350,6 @@ class AzureAsrHelper(private val context: Context) {
// 会话开始事件
recognizer?.sessionStarted?.addEventListener(
EventHandler<SessionEventArgs> { _, _ ->
FileLogger.d(tag, "识别会话已开始")
// 直接在当前线程调用回调
callback.onSessionStarted()
}
@ -376,13 +358,10 @@ class AzureAsrHelper(private val context: Context) {
// 会话结束事件
recognizer?.sessionStopped?.addEventListener(
EventHandler<SessionEventArgs> { _, _ ->
FileLogger.d(tag, "识别会话已结束")
// 直接在当前线程调用回调
callback.onSessionStopped()
isContinuousRecognitionActive = false
stopAudioProcessing()
}
)
@ -419,13 +398,10 @@ class AzureAsrHelper(private val context: Context) {
}
if (!isContinuousRecognitionActive) {
FileLogger.d(tag, "未进行连续识别,忽略停止请求")
return true
}
try {
FileLogger.d(tag, "停止连续语音识别")
if (recognizer == null) {
FileLogger.w(tag, "识别器为空,重置状态")
isContinuousRecognitionActive = false
@ -442,7 +418,6 @@ class AzureAsrHelper(private val context: Context) {
stopAudioProcessing()
// 会话结束事件会设置isContinuousRecognitionActive = false
FileLogger.d(tag, "连续识别停止指令已发送")
return true
} catch (e: Exception) {
// 强制重置状态
@ -503,11 +478,7 @@ class AzureAsrHelper(private val context: Context) {
audioConfig = null
recognizer = null
speechConfig = null
FileLogger.d(tag, "资源已释放")
} catch (e: Exception) {
FileLogger.e(tag, "释放资源失败: ${e.message}")
// 确保状态被重置
isContinuousRecognitionActive = false
microphoneStream = null
@ -585,8 +556,6 @@ class AzureAsrHelper(private val context: Context) {
*/
private fun initMicrophone() {
try {
FileLogger.d(tag, "初始化麦克风 (拉流模式)")
// 创建录音对象
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
val format = AudioFormat.Builder()
@ -625,12 +594,8 @@ class AzureAsrHelper(private val context: Context) {
if (audioRecord?.recordingState != AudioRecord.RECORDSTATE_RECORDING) {
throw IllegalStateException("录音启动失败")
}
FileLogger.d(tag, "麦克风初始化完成 (拉流模式)")
} catch (e: Exception) {
FileLogger.e(tag, "初始化麦克风失败: ${e.message}")
e.printStackTrace()
releaseAudioResources()
}
}
@ -641,14 +606,9 @@ class AzureAsrHelper(private val context: Context) {
val sessionId = audioRecord?.audioSessionId ?: return
// 回音消除
if (AcousticEchoCanceler.isAvailable()) {
echoCanceler = AcousticEchoCanceler.create(sessionId).apply {
enabled = true
}
FileLogger.d(tag, "回音消除已启用")
echoCanceler = AcousticEchoCanceler.create(sessionId).apply {
enabled = true
}
// 其他音频效果可以按需添加
}
/**
@ -697,10 +657,8 @@ class AzureAsrHelper(private val context: Context) {
// 释放录音实例
audioRecord?.release()
audioRecord = null
FileLogger.d(tag, "音频资源已释放 (拉流模式)")
} catch (e: Exception) {
FileLogger.e(tag, "释放音频资源失败: ${e.message}")
e.printStackTrace()
}
}
}
@ -750,7 +708,6 @@ class AzureAsrHelper(private val context: Context) {
override fun close() {
closed = true
queue.clear()
FileLogger.d(tag, "外部音频流已关闭")
}
}

2
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -408,7 +408,7 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
}
override fun onAudioDataReceived(data: ByteArray) {
FileLogger.d(tag, "AzureSpeechPlugin onAudioDataReceived, data: ${data.size}")
// FileLogger.d(tag, "AzureSpeechPlugin onAudioDataReceived, data: ${data.size}")
azureAsrHelper.pushAudioData(data)
}

47
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt

@ -254,17 +254,42 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
/**
* 生成SSML
*/
private fun generateSsml(text: String): String {
return """
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="zh-CN">
<voice name="$currentVoice">
<prosody rate="$currentRate" pitch="$currentPitch" volume="$currentVolume">
$text
</prosody>
</voice>
</speak>
""".trimIndent()
private fun generateSsml(rawText: String): String {
// 1. 定义要静音的符号和表情符号列表
val symbolsToMute = listOf(
"#", "*", "@", "%", "^", "&",
"😀", "😂", "😊", "😍", "😢", "😎", "😉", "👍", "🙌", "🎉"
)
// 2. 转义 XML 保留字符
val escapedText = rawText
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
// 3. 静音处理特殊符号和表情符号
// 使用空白替换法,直接将符号替换为空格
var processedText = escapedText
symbolsToMute.forEach { sym ->
processedText = processedText.replace(sym, "")
}
// 4. 构造简化的SSML文档,减少嵌套层级
return """
<speak version="1.0"
xmlns="http://www.w3.org/2001/10/synthesis"
xmlns:mstts="https://www.w3.org/2001/mstts"
xml:lang="zh-CN">
<voice name="$currentVoice">
<mstts:express-as style="cheerful">
<prosody rate="$currentRate" pitch="$currentPitch" volume="$currentVolume">
<say-as interpret-as="text">$processedText</say-as>
</prosody>
</mstts:express-as>
</voice>
</speak>
""".trimIndent()
}
/**
* 合成文本为语音
@ -311,7 +336,7 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
streamBuffer.append(text)
// 增加500ms防抖逻辑
val currentTime = System.currentTimeMillis()
if (currentTime - lastSpeakTime < 500) {
if (currentTime - lastSpeakTime < 600) {
return true
}
lastSpeakTime = currentTime

2
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -937,7 +937,7 @@ object BleService {
override fun onDecodeStream(data: ByteArray?) {
if (data != null) {
// FileLogger.d(TAG, "Opus解码数据: ${data.size} bytes")
mainHandler.post { notifyAudioDataReceived(data) }
notifyAudioDataReceived(data)
}
}

2
local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIService.kt

@ -540,7 +540,7 @@ class OpenAIService(private val context: Context? = null) : CoroutineScope {
}
override fun onResponse(call: Call, response: Response) {
Log.d(TAG, "发送消息流式输出成功: response=$response")
// Log.d(TAG, "发送消息流式输出成功: response=$response")
// 检查请求是否被取消
if (isCanceled) {
response.body?.close()

Loading…
Cancel
Save