|
|
|
@ -58,7 +58,7 @@ object AgentService : CoroutineScope { |
|
|
|
private val listeners = Collections.synchronizedSet(mutableSetOf<AgentServiceListener>()) |
|
|
|
|
|
|
|
// Azure服务 |
|
|
|
var azureAsrHelper: AzureAsrHelper? = null |
|
|
|
private var azureAsrHelper: AzureAsrHelper? = null |
|
|
|
|
|
|
|
// 使用BytedanceTTS作为唯一的TTS实现 |
|
|
|
private var ttsService: AzureTtsHelper? = null |
|
|
|
@ -102,7 +102,8 @@ object AgentService : CoroutineScope { |
|
|
|
|
|
|
|
// AI流生成相关 |
|
|
|
private var currentAiJob: Job? = null |
|
|
|
private var currsessionId:String = "" |
|
|
|
private var currsessionId: String = "" |
|
|
|
|
|
|
|
// 空闲检测相关 |
|
|
|
private var idleCheckJob: Job? = null |
|
|
|
private val maxIdleSeconds = 10 // 最大空闲秒数 |
|
|
|
@ -111,7 +112,8 @@ object AgentService : CoroutineScope { |
|
|
|
private var lastInterruptTime = 0L |
|
|
|
private val INTERRUPT_DEBOUNCE_MS = 300L |
|
|
|
|
|
|
|
private var audioSourceType= AzureAsrHelper.AudioSourceType.MICROPHONE |
|
|
|
var audioSourceType = AzureAsrHelper.AudioSourceType.MICROPHONE |
|
|
|
|
|
|
|
// 语气词过滤列表(中英文) |
|
|
|
private val FILLER_WORDS = setOf( |
|
|
|
// 中文语气词 |
|
|
|
@ -122,6 +124,7 @@ object AgentService : CoroutineScope { |
|
|
|
"um", "uh", "ah", "oh", "eh", "er", "hmm", "huh", "wow", |
|
|
|
"well", "like", "you know", "i mean", "so", "actually" |
|
|
|
) |
|
|
|
|
|
|
|
/** |
|
|
|
* 初始化系统提示词 |
|
|
|
*/ |
|
|
|
@ -142,6 +145,7 @@ object AgentService : CoroutineScope { |
|
|
|
你与用户的交流风格应始终温暖、亲切、充满同理心,随时表达关怀与鼓励,让用户感受到真诚的陪伴与支持。 |
|
|
|
""".trimIndent() |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 初始化 |
|
|
|
* @param appContext 应用上下文 |
|
|
|
@ -149,7 +153,11 @@ object AgentService : CoroutineScope { |
|
|
|
* @param config 配置参数,包含所需的所有API密钥和设置 |
|
|
|
* @return 是否初始化成功 |
|
|
|
*/ |
|
|
|
fun initialize(appContext: Context, config: Map<String, Any>, isExternalActive: Boolean): Boolean { |
|
|
|
fun initialize( |
|
|
|
appContext: Context, |
|
|
|
config: Map<String, Any>, |
|
|
|
isExternalActive: Boolean |
|
|
|
): Boolean { |
|
|
|
if (isInitialized) return true |
|
|
|
|
|
|
|
try { |
|
|
|
@ -169,13 +177,24 @@ object AgentService : CoroutineScope { |
|
|
|
} |
|
|
|
// 初始化Azure ASR |
|
|
|
azureAsrHelper = AzureAsrHelper(context).apply { |
|
|
|
initialize( |
|
|
|
subscriptionKey = config["azureSpeechKey"]?.toString() ?: "", |
|
|
|
region = config["azureSpeechRegion"]?.toString() ?: "", |
|
|
|
audioSourceType = if (isExternalActive) AzureAsrHelper.AudioSourceType.EXTERNAL |
|
|
|
else AzureAsrHelper.AudioSourceType.MICROPHONE |
|
|
|
) |
|
|
|
// 使用协程处理异步初始化 |
|
|
|
CoroutineScope(Dispatchers.IO).launch { |
|
|
|
val asrInitSuccess = initialize( |
|
|
|
subscriptionKey = config["azureSpeechKey"]?.toString() ?: "", |
|
|
|
region = config["azureSpeechRegion"]?.toString() ?: "", |
|
|
|
audioSourceType = if (isExternalActive) AzureAsrHelper.AudioSourceType.EXTERNAL |
|
|
|
else AzureAsrHelper.AudioSourceType.MICROPHONE |
|
|
|
) |
|
|
|
|
|
|
|
if (asrInitSuccess) { |
|
|
|
withContext(Dispatchers.Main) { |
|
|
|
recognizeCallback() |
|
|
|
} |
|
|
|
} else { |
|
|
|
Log.w(TAG, "Azure ASR初始化失败") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 初始化TTS服务 |
|
|
|
initializeTtsEngine(config) |
|
|
|
@ -189,6 +208,8 @@ object AgentService : CoroutineScope { |
|
|
|
config["mcpServer"]?.toString() ?: "" |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 加载最近的聊天记录 |
|
|
|
loadChatHistory() |
|
|
|
|
|
|
|
@ -274,45 +295,55 @@ object AgentService : CoroutineScope { |
|
|
|
restartIdleCheck() |
|
|
|
sendEvent("tts_started", mapOf("status" to "started")) |
|
|
|
} |
|
|
|
|
|
|
|
TtsEventType.SYNTHESIS_COMPLETED -> { |
|
|
|
_isTtsSpeaking.set(false) |
|
|
|
restartIdleCheck() |
|
|
|
sendEvent("tts_completed", mapOf("status" to "completed")) |
|
|
|
} |
|
|
|
|
|
|
|
TtsEventType.SYNTHESIS_CANCELED -> { |
|
|
|
_isTtsSpeaking.set(false) |
|
|
|
restartIdleCheck() |
|
|
|
sendEvent("tts_canceled", mapOf("status" to "canceled")) |
|
|
|
} |
|
|
|
|
|
|
|
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) |
|
|
|
} |
|
|
|
val intent = |
|
|
|
Intent("com.yunqiinnovation.music_service.AI_SAY_START").apply { |
|
|
|
setPackage(context.packageName) |
|
|
|
} |
|
|
|
context.sendBroadcast(intent) |
|
|
|
} |
|
|
|
|
|
|
|
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) |
|
|
|
} |
|
|
|
val intent = |
|
|
|
Intent("com.yunqiinnovation.music_service.AI_SAY_END").apply { |
|
|
|
setPackage(context.packageName) |
|
|
|
} |
|
|
|
context.sendBroadcast(intent) |
|
|
|
} |
|
|
|
|
|
|
|
TtsEventType.ERROR -> { |
|
|
|
_isTtsSpeaking.set(false) |
|
|
|
restartIdleCheck() |
|
|
|
val params = event.params |
|
|
|
val code = params["errorCode"] as? String ?: "UNKNOWN_ERROR" |
|
|
|
val message = params["errorMessage"] as? String ?: "未知错误" |
|
|
|
sendEvent("error", mapOf( |
|
|
|
"code" to "TTS_ERROR", |
|
|
|
"message" to "语音合成错误: $code, $message" |
|
|
|
)) |
|
|
|
sendEvent( |
|
|
|
"error", mapOf( |
|
|
|
"code" to "TTS_ERROR", |
|
|
|
"message" to "语音合成错误: $code, $message" |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
else -> { |
|
|
|
// 处理其他类型的事件 |
|
|
|
} |
|
|
|
@ -359,8 +390,11 @@ object AgentService : CoroutineScope { |
|
|
|
delay(maxIdleSeconds * 1000L) |
|
|
|
|
|
|
|
if (isActive && isRecognitionActive && !hasSpeechDetected && !isTtsSpeaking && !isAiStreaming) { |
|
|
|
//stopRecognition() |
|
|
|
sendEvent("auto_stop", mapOf("reason" to "idle_timeout", "seconds" to maxIdleSeconds)) |
|
|
|
stopRecognition() |
|
|
|
sendEvent( |
|
|
|
"auto_stop", |
|
|
|
mapOf("reason" to "idle_timeout", "seconds" to maxIdleSeconds) |
|
|
|
) |
|
|
|
} |
|
|
|
} catch (e: CancellationException) { |
|
|
|
// 协程被取消,正常行为 |
|
|
|
@ -396,14 +430,26 @@ object AgentService : CoroutineScope { |
|
|
|
Log.d(TAG, "服务未初始化") |
|
|
|
return false |
|
|
|
} |
|
|
|
if(!(azureAsrHelper!!.isPaused)&&isExternalActive) |
|
|
|
{ |
|
|
|
Log.d(TAG, "麦克风音频未暂停,无法设置") |
|
|
|
BleService.closeCodec() |
|
|
|
return false |
|
|
|
} |
|
|
|
if (isRecognitionActive) return true |
|
|
|
Log.d("TAG", "开始语音识别startRecognition?") |
|
|
|
stopRecognition() |
|
|
|
BleService.openEncoder() |
|
|
|
// if (isRecognitionActive&&audioSourceType==AzureAsrHelper.AudioSourceType.MICROPHONE){ |
|
|
|
// Log.d(TAG, "开始语音识别stopRecognition") |
|
|
|
// // BleService.closeCodec() |
|
|
|
// stopRecognition() |
|
|
|
|
|
|
|
// } |
|
|
|
// if((isExternalActive&&audioSourceType==AzureAsrHelper.AudioSourceType.MICROPHONE)||!isRecognitionActive) |
|
|
|
// { |
|
|
|
// Log.d(TAG, "开始语音识别BleService.openEncoder") |
|
|
|
// BleService.openEncoder() |
|
|
|
|
|
|
|
// } |
|
|
|
// if(isRecognitionActive) |
|
|
|
// { |
|
|
|
// Log.d(TAG, "开始语音识别azureAsrHelper?.startContinuousRecognition(audioSourceType)") |
|
|
|
// return true |
|
|
|
// } |
|
|
|
// 检查录音权限 |
|
|
|
if (!isExternalActive && !checkRecordAudioPermission()) { |
|
|
|
Log.e(TAG, "需要录音权限") |
|
|
|
@ -415,12 +461,33 @@ object AgentService : CoroutineScope { |
|
|
|
_hasSpeechDetected.set(false) |
|
|
|
|
|
|
|
try { |
|
|
|
audioSourceType = if (isExternalActive) { |
|
|
|
audioSourceType = if (isExternalActive) { |
|
|
|
AzureAsrHelper.AudioSourceType.EXTERNAL |
|
|
|
} else { |
|
|
|
AzureAsrHelper.AudioSourceType.MICROPHONE |
|
|
|
} |
|
|
|
azureAsrHelper?.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback { |
|
|
|
Log.d("TAG", "azureAsrHelper?.startContinuousRecognition") |
|
|
|
azureAsrHelper?.startContinuousRecognition(audioSourceType) |
|
|
|
|
|
|
|
return true |
|
|
|
} catch (e: Exception) { |
|
|
|
_isRecognitionActive.set(false) |
|
|
|
Log.e(TAG, "启动语音识别失败: ${e.message}") |
|
|
|
sendEvent( |
|
|
|
"error", mapOf( |
|
|
|
"code" to "RECOGNITION_START_ERROR", |
|
|
|
"message" to e.message.toString() |
|
|
|
) |
|
|
|
) |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fun recognizeCallback(): Boolean { |
|
|
|
try { |
|
|
|
Log.d(TAG, "recognizeCallback") |
|
|
|
val success = azureAsrHelper?.setupEventListeners(object : |
|
|
|
AzureAsrHelper.ContinuousRecognizeCallback { |
|
|
|
override fun onRecognizing(recognizing: String, detectedLanguage: String) { |
|
|
|
if (recognizing.isNotEmpty()) { |
|
|
|
// 检测到语音,更新状态 |
|
|
|
@ -431,10 +498,12 @@ object AgentService : CoroutineScope { |
|
|
|
restartIdleCheck() |
|
|
|
} |
|
|
|
|
|
|
|
sendEvent("recognizing", mapOf( |
|
|
|
"text" to recognizing, |
|
|
|
"language" to detectedLanguage |
|
|
|
)) |
|
|
|
sendEvent( |
|
|
|
"recognizing", mapOf( |
|
|
|
"text" to recognizing, |
|
|
|
"language" to detectedLanguage |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
// 如果TTS正在播放或AI正在生成,检查是否需要打断 |
|
|
|
if (isTtsSpeaking || isAiStreaming) { |
|
|
|
@ -465,10 +534,12 @@ object AgentService : CoroutineScope { |
|
|
|
|
|
|
|
override fun onResult(result: String, detectedLanguage: String) { |
|
|
|
if (result.isNotEmpty()) { |
|
|
|
sendEvent("recognition_result", mapOf( |
|
|
|
"text" to result, |
|
|
|
"language" to detectedLanguage |
|
|
|
)) |
|
|
|
sendEvent( |
|
|
|
"recognition_result", mapOf( |
|
|
|
"text" to result, |
|
|
|
"language" to detectedLanguage |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
processWithChatApi(result) |
|
|
|
} |
|
|
|
@ -485,14 +556,12 @@ object AgentService : CoroutineScope { |
|
|
|
override fun onSessionStarted() { |
|
|
|
sendEvent("recognition_started", mapOf("status" to "started")) |
|
|
|
startIdleCheck() |
|
|
|
Log.d(TAG, "onSessionStarted") |
|
|
|
audioPlayer?.playAudio(R.raw.start) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
override fun onSessionStopped() { |
|
|
|
sendEvent("recognition_stopped", mapOf("status" to "stopped")) |
|
|
|
|
|
|
|
_isRecognitionActive.set(false) |
|
|
|
stopIdleCheck() |
|
|
|
audioPlayer?.playAudio(R.raw.stop) |
|
|
|
@ -501,41 +570,41 @@ object AgentService : CoroutineScope { |
|
|
|
override fun onCanceled(reason: String, errorDetails: String) { |
|
|
|
_isRecognitionActive.set(false) |
|
|
|
stopIdleCheck() |
|
|
|
|
|
|
|
Log.d(TAG, "recognition_canceled:") |
|
|
|
recognizeCallback() |
|
|
|
BleService.closeCodec() |
|
|
|
sendEvent("recognition_canceled", mapOf( |
|
|
|
"reason" to reason, |
|
|
|
"details" to errorDetails |
|
|
|
)) |
|
|
|
sendEvent( |
|
|
|
"recognition_canceled", mapOf( |
|
|
|
"reason" to reason, |
|
|
|
"details" to errorDetails |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onError(error: String) { |
|
|
|
_isRecognitionActive.set(false) |
|
|
|
stopIdleCheck() |
|
|
|
|
|
|
|
recognizeCallback() |
|
|
|
BleService.closeCodec() |
|
|
|
Log.e(TAG, "语音识别错误: $error") |
|
|
|
sendEvent("error", mapOf( |
|
|
|
"code" to "RECOGNITION_ERROR", |
|
|
|
"message" to error |
|
|
|
)) |
|
|
|
Log.d(TAG, "语音识别错误: $error") |
|
|
|
sendEvent( |
|
|
|
"error", mapOf( |
|
|
|
"code" to "RECOGNITION_ERROR", |
|
|
|
"message" to error |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
}, audioSourceType) |
|
|
|
}) |
|
|
|
return true |
|
|
|
} catch (e: Exception) { |
|
|
|
_isRecognitionActive.set(false) |
|
|
|
Log.e(TAG, "启动语音识别失败: ${e.message}") |
|
|
|
sendEvent("error", mapOf( |
|
|
|
"code" to "RECOGNITION_START_ERROR", |
|
|
|
"message" to e.message.toString() |
|
|
|
)) |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fun pushAudioData(data: ByteArray) { |
|
|
|
azureAsrHelper?.pushAudioData(data) |
|
|
|
azureAsrHelper?.audioStream?.saveAudioDataTo(data) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 停止语音识别 |
|
|
|
*/ |
|
|
|
@ -547,67 +616,22 @@ object AgentService : CoroutineScope { |
|
|
|
BleService.closeCodec() |
|
|
|
_isRecognitionActive.set(false) |
|
|
|
stopIdleCheck() |
|
|
|
return |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "停止语音识别异常: ${e.message}") |
|
|
|
_isRecognitionActive.set(false) |
|
|
|
stopIdleCheck() |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
/** |
|
|
|
* 继续音频输入 |
|
|
|
*/ |
|
|
|
fun audioContinue() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
if(audioSourceType== AzureAsrHelper.AudioSourceType.EXTERNAL) |
|
|
|
{ |
|
|
|
|
|
|
|
BleService.closeCodec() |
|
|
|
stopRecognition() |
|
|
|
startRecognition(false) |
|
|
|
} |
|
|
|
|
|
|
|
// if (!isRecognitionActive) { |
|
|
|
|
|
|
|
|
|
|
|
// } |
|
|
|
|
|
|
|
azureAsrHelper?.audioContinue() |
|
|
|
//BleService.closeCodec() |
|
|
|
//_isRecognitionActive.set(false) |
|
|
|
//stopIdleCheck() |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "暂停音频输入异常: ${e.message}") |
|
|
|
// _isRecognitionActive.set(false) |
|
|
|
//stopIdleCheck() |
|
|
|
} |
|
|
|
} |
|
|
|
/** |
|
|
|
* 暂停音频输入 |
|
|
|
*/ |
|
|
|
fun audioPause() { |
|
|
|
if (!isRecognitionActive) return |
|
|
|
|
|
|
|
try { |
|
|
|
azureAsrHelper?.audioPause() |
|
|
|
Log.d(TAG, "暂停音频输入") |
|
|
|
BleService.closeCodec() |
|
|
|
//_isRecognitionActive.set(false) |
|
|
|
//stopIdleCheck() |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "暂停音频输入异常: ${e.message}") |
|
|
|
// _isRecognitionActive.set(false) |
|
|
|
//stopIdleCheck() |
|
|
|
} |
|
|
|
} |
|
|
|
/** |
|
|
|
* 打断当前响应 |
|
|
|
* 停止TTS播放和AI流输出 |
|
|
|
*/ |
|
|
|
fun interruptCurrentResponse() { |
|
|
|
if(isAiStreaming || isTtsSpeaking) { |
|
|
|
if (isAiStreaming || isTtsSpeaking) { |
|
|
|
val startTime = System.currentTimeMillis() |
|
|
|
|
|
|
|
// 并行执行停止操作,加快响应速度 |
|
|
|
@ -617,10 +641,12 @@ object AgentService : CoroutineScope { |
|
|
|
// 记录打断耗时 |
|
|
|
|
|
|
|
val duration = System.currentTimeMillis() - startTime |
|
|
|
sendEvent("response_interrupted", mapOf( |
|
|
|
"status" to "interrupted", |
|
|
|
"duration_ms" to duration |
|
|
|
)) |
|
|
|
sendEvent( |
|
|
|
"response_interrupted", mapOf( |
|
|
|
"status" to "interrupted", |
|
|
|
"duration_ms" to duration |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
@ -628,7 +654,7 @@ object AgentService : CoroutineScope { |
|
|
|
/** |
|
|
|
* 停止AI流输出 |
|
|
|
*/ |
|
|
|
fun stopAiStream() { |
|
|
|
fun stopAiStream() { |
|
|
|
if (isAiStreaming) { |
|
|
|
try { |
|
|
|
// 先更新状态,避免回调时的状态不一致 |
|
|
|
@ -743,11 +769,11 @@ object AgentService : CoroutineScope { |
|
|
|
currsessionId = UUID.randomUUID().toString() |
|
|
|
// 设置状态为正在流式输出 |
|
|
|
_isAiStreaming.set(true) |
|
|
|
Log.d(TAG, "processWithChatApiInternal") |
|
|
|
audioPlayer?.playAudio(R.raw.await, true,0.3f) |
|
|
|
Log.d(TAG, "processWithChatApiInternal") |
|
|
|
audioPlayer?.playAudio(R.raw.await, true, 0.3f) |
|
|
|
// 使用历史记录作为上下文发送到OpenAI |
|
|
|
val responseBuilder = StringBuilder() |
|
|
|
var aiMetadata:String = "" |
|
|
|
var aiMetadata: String = "" |
|
|
|
var userMetadata = JSONObject() |
|
|
|
var reply = true; //是否回复 |
|
|
|
var broadcast = true //是否播发 |
|
|
|
@ -761,16 +787,16 @@ object AgentService : CoroutineScope { |
|
|
|
val messagesWithSystemPrompt = JSONArray() |
|
|
|
|
|
|
|
// 先添加系统提示词 |
|
|
|
if (systemPrompt.isNotEmpty()) { |
|
|
|
val location = LocationService.getCachedLocation()?: mapOf( |
|
|
|
"user_location" to "定位未开启", |
|
|
|
"user_lat" to "定位未开启", |
|
|
|
"user_lng" to "定位未开启", |
|
|
|
) |
|
|
|
val _systemPrompt = Utils.fillTemplate(systemPrompt,location) |
|
|
|
messagesWithSystemPrompt.put(createSystemMessage(_systemPrompt)) |
|
|
|
// Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}") |
|
|
|
} |
|
|
|
if (systemPrompt.isNotEmpty()) { |
|
|
|
val location = LocationService.getCachedLocation() ?: mapOf( |
|
|
|
"user_location" to "定位未开启", |
|
|
|
"user_lat" to "定位未开启", |
|
|
|
"user_lng" to "定位未开启", |
|
|
|
) |
|
|
|
val _systemPrompt = Utils.fillTemplate(systemPrompt, location) |
|
|
|
messagesWithSystemPrompt.put(createSystemMessage(_systemPrompt)) |
|
|
|
// Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}") |
|
|
|
} |
|
|
|
|
|
|
|
// 再添加历史消息 |
|
|
|
for (i in 0 until historyMessages.length()) { |
|
|
|
@ -810,9 +836,9 @@ object AgentService : CoroutineScope { |
|
|
|
currsessionId, |
|
|
|
messagesWithSystemPrompt, |
|
|
|
object : StreamCallback { |
|
|
|
override fun onToken(sessionid:String,token: String) { |
|
|
|
override fun onToken(sessionid: String, token: String) { |
|
|
|
try { |
|
|
|
if (sessionid != currsessionId){ |
|
|
|
if (sessionid != currsessionId) { |
|
|
|
return |
|
|
|
} |
|
|
|
responseBuilder.append(token) |
|
|
|
@ -828,7 +854,7 @@ object AgentService : CoroutineScope { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onComplete(sessionid:String) { |
|
|
|
override fun onComplete(sessionid: String) { |
|
|
|
try { |
|
|
|
// 视情况决定是否朗读回复 |
|
|
|
if (speakResponse && reply && broadcast && sessionid == currsessionId){ |
|
|
|
@ -867,7 +893,7 @@ object AgentService : CoroutineScope { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onError(sessionid:String,e: Exception) { |
|
|
|
override fun onError(sessionid: String, e: Exception) { |
|
|
|
try { |
|
|
|
Log.e(TAG, "AI处理出错", e) |
|
|
|
sendEvent( |
|
|
|
@ -880,20 +906,22 @@ object AgentService : CoroutineScope { |
|
|
|
// 标记AI流式输出已完成 |
|
|
|
_isAiStreaming.set(false) |
|
|
|
currentAiJob = null |
|
|
|
}catch (e: Exception) { |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "liwei--------------- AI Call onError 异常", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onFunctionCall(sessionid:String,call: JSONObject) { |
|
|
|
override fun onFunctionCall(sessionid: String, call: JSONObject) { |
|
|
|
try { |
|
|
|
// audioPlayer?.playAudio(R.raw.calling, true) |
|
|
|
val name = call.getString("name") |
|
|
|
sendEvent("function_call", mapOf( |
|
|
|
"name" to name, |
|
|
|
"arguments" to call.toString() |
|
|
|
)) |
|
|
|
if(name == "exit_interaction") { |
|
|
|
sendEvent( |
|
|
|
"function_call", mapOf( |
|
|
|
"name" to name, |
|
|
|
"arguments" to call.toString() |
|
|
|
) |
|
|
|
) |
|
|
|
if (name == "exit_interaction") { |
|
|
|
stopRecognition() |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
@ -901,7 +929,11 @@ object AgentService : CoroutineScope { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onFunctionCallResult(sessionid:String,functionCall: JSONObject, functionCallResult: JSONObject) { |
|
|
|
override fun onFunctionCallResult( |
|
|
|
sessionid: String, |
|
|
|
functionCall: JSONObject, |
|
|
|
functionCallResult: JSONObject |
|
|
|
) { |
|
|
|
try { |
|
|
|
audioPlayer?.stopAudio() |
|
|
|
val name = functionCall.get("name") as String; |
|
|
|
@ -917,7 +949,11 @@ object AgentService : CoroutineScope { |
|
|
|
) |
|
|
|
) |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "liwei--------------- AI Call onFunctionCallResult 异常", e) |
|
|
|
Log.e( |
|
|
|
TAG, |
|
|
|
"liwei--------------- AI Call onFunctionCallResult 异常", |
|
|
|
e |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -926,10 +962,12 @@ object AgentService : CoroutineScope { |
|
|
|
} catch (e: Exception) { |
|
|
|
val errorType = if (hasImage) "AI_IMAGE_PROCESS_ERROR" else "AI_PROCESS_ERROR" |
|
|
|
Log.e(TAG, "liwei--------------- AI处理出错", e) |
|
|
|
sendEvent("error", mapOf( |
|
|
|
"code" to errorType, |
|
|
|
"message" to e.message.toString() |
|
|
|
)) |
|
|
|
sendEvent( |
|
|
|
"error", mapOf( |
|
|
|
"code" to errorType, |
|
|
|
"message" to e.message.toString() |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
// 确保状态被重置 |
|
|
|
_isAiStreaming.set(false) |
|
|
|
@ -1019,7 +1057,12 @@ object AgentService : CoroutineScope { |
|
|
|
/** |
|
|
|
* 保存聊天消息 |
|
|
|
*/ |
|
|
|
private fun saveChatMessage(userMessage: String, assistantMessage: String,aiMetadata : String,userMetadata : String) { |
|
|
|
private fun saveChatMessage( |
|
|
|
userMessage: String, |
|
|
|
assistantMessage: String, |
|
|
|
aiMetadata: String, |
|
|
|
userMetadata: String |
|
|
|
) { |
|
|
|
// 显式指定IO调度器,数据库操作应在后台线程执行,即使父协程在Main上下文中 |
|
|
|
launch(Dispatchers.IO) { |
|
|
|
try { |
|
|
|
@ -1167,7 +1210,12 @@ object AgentService : CoroutineScope { |
|
|
|
/** |
|
|
|
* 处理图片输入 |
|
|
|
*/ |
|
|
|
fun processImageInput(imagePath: String, imageUrl: String,text: String = "", speakResponse: Boolean = false): Boolean { |
|
|
|
fun processImageInput( |
|
|
|
imagePath: String, |
|
|
|
imageUrl: String, |
|
|
|
text: String = "", |
|
|
|
speakResponse: Boolean = false |
|
|
|
): Boolean { |
|
|
|
if (!isInitialized) { |
|
|
|
Log.e(TAG, "服务未初始化") |
|
|
|
sendEvent("error", mapOf("code" to "NOT_INITIALIZED", "message" to "服务未初始化")) |
|
|
|
@ -1181,10 +1229,12 @@ object AgentService : CoroutineScope { |
|
|
|
} |
|
|
|
|
|
|
|
// 通知开始处理图片 |
|
|
|
sendEvent("image_processing", mapOf( |
|
|
|
"status" to "processing", |
|
|
|
"imagePath" to imagePath |
|
|
|
)) |
|
|
|
sendEvent( |
|
|
|
"image_processing", mapOf( |
|
|
|
"status" to "processing", |
|
|
|
"imagePath" to imagePath |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
// 处理图片 |
|
|
|
launch { |
|
|
|
@ -1204,19 +1254,23 @@ object AgentService : CoroutineScope { |
|
|
|
val displayText = text.ifEmpty { "[图片]" } |
|
|
|
|
|
|
|
// 通知图片准备完成 |
|
|
|
sendEvent("image_ready", mapOf( |
|
|
|
"status" to "ready", |
|
|
|
"imagePath" to imagePath |
|
|
|
)) |
|
|
|
sendEvent( |
|
|
|
"image_ready", mapOf( |
|
|
|
"status" to "ready", |
|
|
|
"imagePath" to imagePath |
|
|
|
) |
|
|
|
) |
|
|
|
Log.d(TAG, "liwei--------- 图像Mesage: ${userMessage}") |
|
|
|
// 处理消息并传入存储路径 |
|
|
|
processWithChatApiInternal(userMessage, displayText, speakResponse, true, imagePath) |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "处理图片失败: ${e.message}") |
|
|
|
sendEvent("error", mapOf( |
|
|
|
"code" to "IMAGE_PROCESSING_ERROR", |
|
|
|
"message" to e.message.toString() |
|
|
|
)) |
|
|
|
sendEvent( |
|
|
|
"error", mapOf( |
|
|
|
"code" to "IMAGE_PROCESSING_ERROR", |
|
|
|
"message" to e.message.toString() |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
return true |
|
|
|
@ -1254,7 +1308,7 @@ object AgentService : CoroutineScope { |
|
|
|
fun playAudio(resId: Int, isLooping: Boolean = false, volume: Float = 1.0f) { |
|
|
|
synchronized(playbackLock) { |
|
|
|
try { |
|
|
|
Log.d(TAG, "playAudio${isLooping}") |
|
|
|
Log.d(TAG, "playAudio${isLooping}") |
|
|
|
// 强制停止并释放之前的资源 |
|
|
|
forceStop() |
|
|
|
|
|
|
|
@ -1298,7 +1352,7 @@ object AgentService : CoroutineScope { |
|
|
|
fun stopAudio() { |
|
|
|
synchronized(playbackLock) { |
|
|
|
try { |
|
|
|
Log.d(TAG, "stopAudio") |
|
|
|
Log.d(TAG, "stopAudio") |
|
|
|
mediaPlayer?.apply { |
|
|
|
if (isMediaPlayerPlaying()) { |
|
|
|
stop() |
|
|
|
@ -1422,7 +1476,10 @@ object AgentService : CoroutineScope { |
|
|
|
/* |
|
|
|
* 自动播放音乐 |
|
|
|
* */ |
|
|
|
fun autoHandleFcunCallResult(toolname:String,functionCallResult: JSONObject): Map<String,Any>{ |
|
|
|
fun autoHandleFcunCallResult( |
|
|
|
toolname: String, |
|
|
|
functionCallResult: JSONObject |
|
|
|
): Map<String, Any> { |
|
|
|
println("协议工具返回数据 检查是否存在卡片! functionCallResult: ${functionCallResult}") |
|
|
|
try { |
|
|
|
val contentStr = functionCallResult.optString("context") |
|
|
|
@ -1435,10 +1492,10 @@ object AgentService : CoroutineScope { |
|
|
|
var notreply = false; |
|
|
|
if (textStr.isNotEmpty()) { |
|
|
|
val meta = JSONObject(textStr) |
|
|
|
if (meta.has("broadcast")){ |
|
|
|
if (meta.has("broadcast")) { |
|
|
|
broadcast = meta["broadcast"] as Boolean |
|
|
|
} |
|
|
|
if (meta.has("notreply")){ |
|
|
|
if (meta.has("notreply")) { |
|
|
|
notreply = meta["notreply"] as Boolean |
|
|
|
} |
|
|
|
metadata[toolname] = meta // 现在可以赋值 |
|
|
|
@ -1501,13 +1558,13 @@ object AgentService : CoroutineScope { |
|
|
|
val end = cardNavigation.optString("end", "") |
|
|
|
processNavigation(start, end); |
|
|
|
} |
|
|
|
return mapOf("meta" to metaStr, "broadcast" to broadcast,"notreply" to notreply); |
|
|
|
return mapOf("meta" to metaStr, "broadcast" to broadcast, "notreply" to notreply); |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
// 捕获其他类型的异常(可选) |
|
|
|
println("为解析到卡片数据!") |
|
|
|
} |
|
|
|
return mapOf("meta" to "", "broadcast" to true,"notreply" to false); |
|
|
|
return mapOf("meta" to "", "broadcast" to true, "notreply" to false); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -1520,7 +1577,7 @@ object AgentService : CoroutineScope { |
|
|
|
* @param singer 歌手名称,默认为空 |
|
|
|
* @return 是否成功开始处理 |
|
|
|
*/ |
|
|
|
fun processMusicPlay(song: Map<String,Any>){ |
|
|
|
fun processMusicPlay(song: Map<String, Any>) { |
|
|
|
// 在其他 Service、BroadcastReceiver 或 Application 中调用 |
|
|
|
Log.i(TAG, "播放音乐: ${song}") |
|
|
|
MusicServiceStarter.startServiceWithCommand(context, command = "play", song = song) |
|
|
|
@ -1535,10 +1592,14 @@ object AgentService : CoroutineScope { |
|
|
|
* @param singer 歌手,默认为false |
|
|
|
* @return 是否成功开始处理 |
|
|
|
*/ |
|
|
|
fun processMusicPlayList(songs:List<Map<String,Any>>){ |
|
|
|
fun processMusicPlayList(songs: List<Map<String, Any>>) { |
|
|
|
// 如果 playlist 中有有效的歌曲,开始播放 |
|
|
|
if (songs.isNotEmpty()) { |
|
|
|
MusicServiceStarter.startServiceWithPlaylist(context, command = "playlist", songs = songs) |
|
|
|
MusicServiceStarter.startServiceWithPlaylist( |
|
|
|
context, |
|
|
|
command = "playlist", |
|
|
|
songs = songs |
|
|
|
) |
|
|
|
} else { |
|
|
|
Log.e(TAG, "音乐列表为空,未启动播放服务") |
|
|
|
} |
|
|
|
@ -1552,10 +1613,15 @@ object AgentService : CoroutineScope { |
|
|
|
* @param singer 歌手,默认为false |
|
|
|
* @return 是否成功开始处理 |
|
|
|
*/ |
|
|
|
fun processNavigation(start:String,end:String){ |
|
|
|
fun processNavigation(start: String, end: String) { |
|
|
|
// 如果 playlist 中有有效的歌曲,开始播放 |
|
|
|
if (!start.isNullOrEmpty() && !end.isNullOrEmpty()) { |
|
|
|
NavigationServiceHelper.startNavigation(context,"start", startpos = start, endpos = end) |
|
|
|
NavigationServiceHelper.startNavigation( |
|
|
|
context, |
|
|
|
"start", |
|
|
|
startpos = start, |
|
|
|
endpos = end |
|
|
|
) |
|
|
|
} else { |
|
|
|
Log.i(TAG, "启动导航服务失败") |
|
|
|
} |
|
|
|
|