|
|
|
@ -1,9 +1,11 @@ |
|
|
|
package com.yunqiinnovation.agent_service |
|
|
|
|
|
|
|
import android.content.Context |
|
|
|
import android.content.Intent |
|
|
|
import org.json.JSONArray |
|
|
|
import org.json.JSONObject |
|
|
|
import com.yunqiinnovation.azure_speech.AzureAsrHelper |
|
|
|
import com.yunqiinnovation.azure_speech.AzureTtsHelper |
|
|
|
import com.yunqiinnovation.chat_api.ChatApiService |
|
|
|
import com.yunqiinnovation.chat_api.StreamCallback |
|
|
|
import com.yunqiinnovation.chat_storage.ChatStorageHelper |
|
|
|
@ -23,6 +25,8 @@ import com.deep_voice.speech.tts.TtsEvent |
|
|
|
import com.deep_voice.speech.tts.TtsEventListener |
|
|
|
import com.deep_voice.speech.tts.TtsEventType |
|
|
|
import com.deep_voice.bytedance_speech.BytedanceTTS |
|
|
|
import java.util.UUID |
|
|
|
|
|
|
|
/** |
|
|
|
* 代理服务事件监听接口 |
|
|
|
*/ |
|
|
|
@ -56,7 +60,7 @@ object AgentService : CoroutineScope { |
|
|
|
private var azureAsrHelper: AzureAsrHelper? = null |
|
|
|
|
|
|
|
// 使用BytedanceTTS作为唯一的TTS实现 |
|
|
|
private var ttsService: BytedanceTTS? = null |
|
|
|
private var ttsService: AzureTtsHelper? = null |
|
|
|
|
|
|
|
// ChatAPI服务 - 使用新的ChatApiService |
|
|
|
private lateinit var chatApiService: ChatApiService |
|
|
|
@ -97,7 +101,7 @@ object AgentService : CoroutineScope { |
|
|
|
|
|
|
|
// AI流生成相关 |
|
|
|
private var currentAiJob: Job? = null |
|
|
|
|
|
|
|
private var currsessionId:String = "" |
|
|
|
// 空闲检测相关 |
|
|
|
private var idleCheckJob: Job? = null |
|
|
|
private val maxIdleSeconds = 10 // 最大空闲秒数 |
|
|
|
@ -244,14 +248,14 @@ object AgentService : CoroutineScope { |
|
|
|
val ttsLanguage = config["ttsLanguage"]?.toString() ?: "zh-CN" |
|
|
|
|
|
|
|
// 创建并初始化BytedanceTTS |
|
|
|
val bytedanceTts = BytedanceTTS(context) |
|
|
|
ttsService = bytedanceTts |
|
|
|
val azureTtsHelper = AzureTtsHelper(context) |
|
|
|
ttsService = azureTtsHelper |
|
|
|
|
|
|
|
// 初始化Bytedance TTS |
|
|
|
val success = bytedanceTts.initialize( |
|
|
|
ttsAppId = ttsAppId, |
|
|
|
ttsAppToken = ttsAppToken, |
|
|
|
ttsResource = "", // Bytedance TTS不需要resource参数 |
|
|
|
val success = azureTtsHelper.initialize( |
|
|
|
ttsAppId = "", // Azure TTS不需要appId |
|
|
|
ttsAppToken = config["azureSpeechKey"]?.toString() ?: "", // Azure需要subscription key |
|
|
|
ttsResource = config["azureSpeechRegion"]?.toString() ?: "", // Azure需要region信息 |
|
|
|
language = ttsLanguage |
|
|
|
) |
|
|
|
|
|
|
|
@ -278,13 +282,23 @@ object AgentService : CoroutineScope { |
|
|
|
restartIdleCheck() |
|
|
|
sendEvent("tts_canceled", mapOf("status" to "canceled")) |
|
|
|
} |
|
|
|
TtsEventType.PLAYBACK_STARTED -> { |
|
|
|
TtsEventType.PLAYBACK_STARTED -> { //开始AI播报内容 |
|
|
|
restartIdleCheck() |
|
|
|
sendEvent("playback_started", mapOf("status" to "playback_started")) |
|
|
|
// 发送AI说话开始广播 |
|
|
|
val intent = Intent("com.yunqiinnovation.music_service.AI_SAY_START").apply { |
|
|
|
setPackage(context.packageName) |
|
|
|
} |
|
|
|
context.sendBroadcast(intent) |
|
|
|
} |
|
|
|
TtsEventType.PLAYBACK_COMPLETED -> { |
|
|
|
TtsEventType.PLAYBACK_COMPLETED -> { //结束AI播报内容 |
|
|
|
restartIdleCheck() |
|
|
|
sendEvent("playback_completed", mapOf("status" to "playback_completed")) |
|
|
|
// 发送AI说话开始广播 |
|
|
|
val intent = Intent("com.yunqiinnovation.music_service.AI_SAY_END").apply { |
|
|
|
setPackage(context.packageName) |
|
|
|
} |
|
|
|
context.sendBroadcast(intent) |
|
|
|
} |
|
|
|
TtsEventType.ERROR -> { |
|
|
|
_isTtsSpeaking.set(false) |
|
|
|
@ -666,6 +680,7 @@ object AgentService : CoroutineScope { |
|
|
|
// 记录新的AI任务 |
|
|
|
currentAiJob = launch { |
|
|
|
try { |
|
|
|
currsessionId = UUID.randomUUID().toString() |
|
|
|
// 设置状态为正在流式输出 |
|
|
|
_isAiStreaming.set(true) |
|
|
|
audioPlayer?.playAudio(R.raw.await, true,0.3f) |
|
|
|
@ -692,8 +707,8 @@ object AgentService : CoroutineScope { |
|
|
|
) |
|
|
|
val _systemPrompt = Utils.fillTemplate(systemPrompt,location) |
|
|
|
messagesWithSystemPrompt.put(createSystemMessage(_systemPrompt)) |
|
|
|
// Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}") |
|
|
|
} |
|
|
|
// Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}") |
|
|
|
} |
|
|
|
|
|
|
|
// 再添加历史消息 |
|
|
|
for (i in 0 until historyMessages.length()) { |
|
|
|
@ -730,61 +745,84 @@ object AgentService : CoroutineScope { |
|
|
|
} |
|
|
|
// Log.e("ChatApiService", "liwei---------sendMessageStream messages:${messagesWithSystemPrompt}") |
|
|
|
chatApiService.sendMessageStream( |
|
|
|
currsessionId, |
|
|
|
messagesWithSystemPrompt, |
|
|
|
object : StreamCallback { |
|
|
|
override fun onToken(token: String) { |
|
|
|
responseBuilder.append(token) |
|
|
|
if (speakResponse && broadcast) { |
|
|
|
ttsService?.speakStream(token) |
|
|
|
if (token.length > 0){ |
|
|
|
audioPlayer?.stopAudio() |
|
|
|
override fun onToken(sessionid:String,token: String) { |
|
|
|
try { |
|
|
|
if (sessionid != currsessionId){ |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
if (broadcast){ |
|
|
|
// 发送流式回复token |
|
|
|
sendEvent("assistant_token", mapOf("token" to token)) |
|
|
|
responseBuilder.append(token) |
|
|
|
if (speakResponse && broadcast) { |
|
|
|
ttsService?.speakStream(token) |
|
|
|
if (token.length > 0) { |
|
|
|
audioPlayer?.stopAudio() |
|
|
|
} |
|
|
|
} |
|
|
|
if (broadcast) { |
|
|
|
// 发送流式回复token |
|
|
|
sendEvent("assistant_token", mapOf("token" to token)) |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "liwei--------------- AI Call onToken 异常", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onComplete() { |
|
|
|
// 视情况决定是否朗读回复 |
|
|
|
if (speakResponse && broadcast) { |
|
|
|
ttsService?.flushStream() |
|
|
|
} |
|
|
|
val response = responseBuilder.toString() |
|
|
|
// 发送完整回复,包含是否有图片的标记 |
|
|
|
val responseData = mutableMapOf<String, Any>( |
|
|
|
"text" to response, |
|
|
|
"userInput" to displayText |
|
|
|
) |
|
|
|
if (hasImage) { |
|
|
|
responseData["hasImage"] = true |
|
|
|
} |
|
|
|
sendEvent("assistant_response", responseData) |
|
|
|
if (response.isNotEmpty()) { |
|
|
|
// 添加AI回复到历史记录 |
|
|
|
addToHistoryMessages(createAssistantMessage(response)) |
|
|
|
override fun onComplete(sessionid:String) { |
|
|
|
try { |
|
|
|
// 视情况决定是否朗读回复 |
|
|
|
if (speakResponse && broadcast && sessionid == currsessionId){ |
|
|
|
ttsService?.flushStream() |
|
|
|
} |
|
|
|
val response = responseBuilder.toString() |
|
|
|
// 发送完整回复,包含是否有图片的标记 |
|
|
|
val responseData = mutableMapOf<String, Any>( |
|
|
|
"text" to response, |
|
|
|
"userInput" to displayText |
|
|
|
) |
|
|
|
if (hasImage) { |
|
|
|
responseData["hasImage"] = true |
|
|
|
} |
|
|
|
sendEvent("assistant_response", responseData) |
|
|
|
if (response.isNotEmpty()) { |
|
|
|
// 添加AI回复到历史记录 |
|
|
|
addToHistoryMessages(createAssistantMessage(response)) |
|
|
|
} |
|
|
|
// 保存聊天记录 |
|
|
|
saveChatMessage( |
|
|
|
displayText, |
|
|
|
response, |
|
|
|
aiMetadata, |
|
|
|
userMetadata.toString() |
|
|
|
) |
|
|
|
// 标记AI流式输出已完成 |
|
|
|
_isAiStreaming.set(false) |
|
|
|
currentAiJob = null |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "liwei--------------- AI Call onComplete 异常", e) |
|
|
|
} |
|
|
|
// 保存聊天记录 |
|
|
|
saveChatMessage(displayText, response,aiMetadata,userMetadata.toString()) |
|
|
|
// 标记AI流式输出已完成 |
|
|
|
_isAiStreaming.set(false) |
|
|
|
currentAiJob = null |
|
|
|
} |
|
|
|
|
|
|
|
override fun onError(e: Exception) { |
|
|
|
Log.e(TAG, "AI处理出错", e) |
|
|
|
sendEvent("error", mapOf( |
|
|
|
"code" to "AI_ERROR", |
|
|
|
"message" to e.message.toString() |
|
|
|
)) |
|
|
|
|
|
|
|
// 标记AI流式输出已完成 |
|
|
|
_isAiStreaming.set(false) |
|
|
|
currentAiJob = null |
|
|
|
override fun onError(sessionid:String,e: Exception) { |
|
|
|
try { |
|
|
|
Log.e(TAG, "AI处理出错", e) |
|
|
|
sendEvent( |
|
|
|
"error", mapOf( |
|
|
|
"code" to "AI_ERROR", |
|
|
|
"message" to e.message.toString() |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
// 标记AI流式输出已完成 |
|
|
|
_isAiStreaming.set(false) |
|
|
|
currentAiJob = null |
|
|
|
}catch (e: Exception) { |
|
|
|
Log.e(TAG, "liwei--------------- AI Call onError 异常", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onFunctionCall(call: JSONObject) { |
|
|
|
override fun onFunctionCall(sessionid:String,call: JSONObject) { |
|
|
|
try { |
|
|
|
// audioPlayer?.playAudio(R.raw.calling, true) |
|
|
|
val name = call.getString("name") |
|
|
|
@ -796,28 +834,34 @@ object AgentService : CoroutineScope { |
|
|
|
stopRecognition() |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "处理函数调用事件失败", e) |
|
|
|
Log.e(TAG, "liwei--------------- AI Call onFunctionCall 异常", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onFunctionCallResult(functionCall: JSONObject, functionCallResult: JSONObject) { |
|
|
|
audioPlayer?.stopAudio() |
|
|
|
val name = functionCall.get("name") as String; |
|
|
|
val resultmap = autoHandleFcunCallResult(name,functionCallResult); |
|
|
|
aiMetadata = resultmap["meta"] as String; |
|
|
|
broadcast = resultmap["broadcast"] as Boolean; |
|
|
|
sendEvent("function_call_result", mapOf( |
|
|
|
"function_call" to functionCall.toString(), |
|
|
|
"result" to functionCallResult.toString(), |
|
|
|
"meta" to aiMetadata, |
|
|
|
)) |
|
|
|
override fun onFunctionCallResult(sessionid:String,functionCall: JSONObject, functionCallResult: JSONObject) { |
|
|
|
try { |
|
|
|
audioPlayer?.stopAudio() |
|
|
|
val name = functionCall.get("name") as String; |
|
|
|
val resultmap = autoHandleFcunCallResult(name, functionCallResult); |
|
|
|
aiMetadata = resultmap["meta"] as String; |
|
|
|
broadcast = resultmap["broadcast"] as Boolean; |
|
|
|
sendEvent( |
|
|
|
"function_call_result", mapOf( |
|
|
|
"function_call" to functionCall.toString(), |
|
|
|
"result" to functionCallResult.toString(), |
|
|
|
"meta" to aiMetadata, |
|
|
|
) |
|
|
|
) |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "liwei--------------- AI Call onFunctionCallResult 异常", e) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
) |
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
val errorType = if (hasImage) "AI_IMAGE_PROCESS_ERROR" else "AI_PROCESS_ERROR" |
|
|
|
Log.e(TAG, "AI处理出错", e) |
|
|
|
Log.e(TAG, "liwei--------------- AI处理出错", e) |
|
|
|
sendEvent("error", mapOf( |
|
|
|
"code" to errorType, |
|
|
|
"message" to e.message.toString() |
|
|
|
@ -1119,6 +1163,7 @@ object AgentService : CoroutineScope { |
|
|
|
*/ |
|
|
|
class AudioPlayer(private val context: Context) { |
|
|
|
private var mediaPlayer: MediaPlayer? = null |
|
|
|
private var isInitialized = false |
|
|
|
|
|
|
|
/** |
|
|
|
* 播放音频资源 |
|
|
|
@ -1155,21 +1200,47 @@ object AgentService : CoroutineScope { |
|
|
|
release() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fun stopAudio() { |
|
|
|
mediaPlayer?.stop() |
|
|
|
try { |
|
|
|
mediaPlayer?.apply { |
|
|
|
when { |
|
|
|
isPlaying -> { |
|
|
|
stop() |
|
|
|
Log.d(TAG, "音频已停止") |
|
|
|
} |
|
|
|
else -> { |
|
|
|
Log.d(TAG, "音频未在播放状态,无需停止") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e: IllegalStateException) { |
|
|
|
Log.e(TAG, "MediaPlayer 状态异常,无法停止: ${e.message}", e) |
|
|
|
// 重置 MediaPlayer |
|
|
|
release() |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "停止音频播放异常: ${e.message}", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 释放资源 |
|
|
|
*/ |
|
|
|
fun release() { |
|
|
|
try { |
|
|
|
mediaPlayer?.apply { |
|
|
|
if (isPlaying) stop() |
|
|
|
if (isPlaying) { |
|
|
|
stop() |
|
|
|
} |
|
|
|
reset() |
|
|
|
release() |
|
|
|
} |
|
|
|
mediaPlayer = null |
|
|
|
isInitialized = false |
|
|
|
} catch (e: Exception) { |
|
|
|
// 忽略异常 |
|
|
|
Log.e(TAG, "释放音频资源异常: ${e.message}", e) |
|
|
|
mediaPlayer = null |
|
|
|
isInitialized = false |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|