4 changed files with 233 additions and 6 deletions
@ -0,0 +1,213 @@ |
|||||
|
package com.yunqiinnovation.chat_api |
||||
|
|
||||
|
import android.content.Context |
||||
|
import android.content.Intent |
||||
|
import android.util.Log |
||||
|
import com.yunqiinnovation.music_service.MusicSingleton |
||||
|
import org.json.JSONObject |
||||
|
import kotlin.coroutines.resume |
||||
|
import kotlin.coroutines.suspendCoroutine |
||||
|
import androidx.core.content.ContextCompat |
||||
|
import kotlinx.coroutines.launch |
||||
|
import kotlinx.coroutines.CoroutineScope |
||||
|
import kotlinx.coroutines.Dispatchers |
||||
|
import kotlinx.coroutines.SupervisorJob |
||||
|
import okhttp3.Call |
||||
|
import okhttp3.Callback |
||||
|
import okhttp3.MediaType.Companion.toMediaType |
||||
|
import okhttp3.OkHttpClient |
||||
|
import okhttp3.Request |
||||
|
import okhttp3.RequestBody.Companion.toRequestBody |
||||
|
import okhttp3.Response |
||||
|
import org.json.JSONArray |
||||
|
import java.io.IOException |
||||
|
import java.util.concurrent.TimeUnit |
||||
|
|
||||
|
/** |
||||
|
* QQ音乐系统功能处理器 |
||||
|
* 负责注册QQ音乐相关的MCP函数 |
||||
|
*/ |
||||
|
class ToolsSystemFunctionHandler(private val context: Context? = null,private val searchUrl:String,private val token:String) { |
||||
|
companion object { |
||||
|
const val TAG = "ToolsSystem" |
||||
|
} |
||||
|
|
||||
|
fun registerAllFunctions(client: MCPClient) { |
||||
|
try { |
||||
|
client.registerLocalFunction( |
||||
|
"web_search", |
||||
|
"通过搜索网络搜索回复用户相关问题,例如今日新闻、百科、医疗、股票、汇率等等", |
||||
|
mapOf( |
||||
|
"type" to "object", |
||||
|
"properties" to mapOf( |
||||
|
"keyword" to mapOf( |
||||
|
"type" to "string", |
||||
|
"description" to "搜索关键词,例如今日新闻、今日人民币汇率等等" |
||||
|
), |
||||
|
"count" to mapOf( |
||||
|
"type" to "integer", |
||||
|
"description" to "返回的搜索结果数量,默认值为5", |
||||
|
"default" to 5 |
||||
|
) |
||||
|
), |
||||
|
"required" to listOf("keyword") |
||||
|
), |
||||
|
SearchSearchHandler(context,searchUrl,token) |
||||
|
) |
||||
|
Log.d(TAG, "音乐所有功能注册完成") |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "注册音乐功能失败: ${e.message}", e) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 搜索系统函数处理类 |
||||
|
private class SearchSearchHandler(private val context: Context?,private val searchUrl:String,private val token:String) : FunctionHandler { |
||||
|
companion object { |
||||
|
const val TAG = "SearchSearchHandler" |
||||
|
} |
||||
|
private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) |
||||
|
/** |
||||
|
* 搜索网络内容 |
||||
|
*/ |
||||
|
fun webSearch( |
||||
|
keyword: String, |
||||
|
count: Int = 0, |
||||
|
callback: (Result<List<Map<String, Any?>>>) -> Unit |
||||
|
) { |
||||
|
// 使用协程异步处理,避免阻塞主线程 |
||||
|
coroutineScope.launch { |
||||
|
try { |
||||
|
// 配置带超时的 OkHttpClient |
||||
|
val client = OkHttpClient.Builder() |
||||
|
.connectTimeout(10, TimeUnit.SECONDS) |
||||
|
.writeTimeout(10, TimeUnit.SECONDS) |
||||
|
.readTimeout(30, TimeUnit.SECONDS) |
||||
|
.build() |
||||
|
|
||||
|
// 构建搜索请求的 JSON 数据 |
||||
|
val jsonObject = JSONObject().apply { |
||||
|
put("keywords", keyword) |
||||
|
put("count", count) |
||||
|
} |
||||
|
|
||||
|
val json = jsonObject.toString() |
||||
|
val mediaType = "application/json".toMediaType() |
||||
|
val body = json.toRequestBody(mediaType) |
||||
|
|
||||
|
// 构建请求 |
||||
|
val request = Request.Builder() |
||||
|
.url("$searchUrl/api/home/aitools_bochasearch") |
||||
|
.addHeader("Authorization", token) |
||||
|
.addHeader("Content-Type", "application/json") |
||||
|
.post(body) |
||||
|
.build() |
||||
|
|
||||
|
Log.d("SearchSearchHandler", "发送搜索请求: $json") |
||||
|
|
||||
|
// 使用异步调用 |
||||
|
client.newCall(request).enqueue(object : Callback { |
||||
|
override fun onFailure(call: Call, e: IOException) { |
||||
|
Log.e(TAG, "搜索歌曲请求失败: ${e.message}", e) |
||||
|
callback(Result.failure(e)) |
||||
|
} |
||||
|
|
||||
|
override fun onResponse(call: Call, response: Response) { |
||||
|
response.use { |
||||
|
try { |
||||
|
if (response.isSuccessful) { |
||||
|
val responseBody = response.body?.string() |
||||
|
Log.d(TAG, "搜索响应: $responseBody") |
||||
|
|
||||
|
if (responseBody != null) { |
||||
|
val jsonResponse = JSONObject(responseBody) |
||||
|
val results = parseResultsFromResponse(jsonResponse) |
||||
|
callback(Result.success(results)) |
||||
|
} else { |
||||
|
callback(Result.failure(Exception("响应体为空"))) |
||||
|
} |
||||
|
} else { |
||||
|
val errorMsg = "搜索失败,HTTP状态码: ${response.code}" |
||||
|
Log.w(TAG, errorMsg) |
||||
|
callback(Result.failure(Exception(errorMsg))) |
||||
|
} |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "解析搜索响应时发生异常: ${e.message}", e) |
||||
|
callback(Result.failure(e)) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "搜索歌曲时发生异常: ${e.message}", e) |
||||
|
callback(Result.failure(e)) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析服务器响应中的歌曲列表 |
||||
|
*/ |
||||
|
private fun parseResultsFromResponse(jsonResponse: JSONObject): List<Map<String, Any?>> { |
||||
|
val results = mutableListOf<Map<String, Any?>>() |
||||
|
try { |
||||
|
val data = jsonResponse.optJSONObject("data") |
||||
|
val resultsArray = data?.optJSONArray("results") |
||||
|
|
||||
|
resultsArray?.let { array -> |
||||
|
for (i in 0 until array.length()) { |
||||
|
val songObj = array.getJSONObject(i) |
||||
|
results.add(mapOf( |
||||
|
"role" to songObj.optLong("role"), |
||||
|
"type" to songObj.optString("type"), |
||||
|
"content_type" to songObj.optString("content_type"), |
||||
|
"content" to songObj.optString("content"), |
||||
|
)) |
||||
|
} |
||||
|
} |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "Failed to parse songs from response", e) |
||||
|
} |
||||
|
return results |
||||
|
} |
||||
|
|
||||
|
|
||||
|
override suspend fun handle(arguments: Map<String, Any>): String { |
||||
|
return try { |
||||
|
val keyword = arguments["keyword"] as? String |
||||
|
?: return "{\"success\": false, \"message\": \"缺少搜索关键词\"}" |
||||
|
val count = arguments["count"] as? Int ?: 5 // 默认搜索歌曲 |
||||
|
// 使用 suspendCoroutine 将回调转换为协程 |
||||
|
suspendCoroutine { continuation -> |
||||
|
webSearch(keyword, count) { searchResult -> |
||||
|
val result = if (searchResult.isSuccess) { |
||||
|
val datas = searchResult.getOrNull() ?: emptyList() |
||||
|
|
||||
|
val songsJson = datas.joinToString(",") { song -> |
||||
|
val resultMap = song as Map<String, Any?> |
||||
|
// 添加调试日志 |
||||
|
Log.d("SearchSearchHandler", "原始数据: $resultMap") |
||||
|
// 修正字段名映射 |
||||
|
val role = resultMap["role"] ?: 0 |
||||
|
val type = resultMap["type"] ?: "unknown" |
||||
|
val content_type = resultMap["content_type"] ?: "unknown" |
||||
|
val content = resultMap["content"] ?: "unknown" |
||||
|
"{\"role\": $role, \"type\": \"$type\", \"content_type\": \"$content_type\", \"content\": \"$content\"}" |
||||
|
} |
||||
|
"{\"success\": true, \"iscard\": true, \"broadcast\": true, \"message\": \"搜索成功,共找到数据:${datas.size}\", \"data\": [$songsJson]}" |
||||
|
} else { |
||||
|
val error = searchResult.exceptionOrNull() |
||||
|
"{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}" |
||||
|
} |
||||
|
// 恢复协程执行 |
||||
|
continuation.resume(result) |
||||
|
} |
||||
|
} |
||||
|
} catch (e: Exception) { |
||||
|
Log.e("QQMusicSearchHandler", "QQ音乐搜索失败", e) |
||||
|
"{\"success\": false, \"message\": \"搜索异常: ${e.message}\"}" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
Loading…
Reference in new issue