lxm 1 year ago
parent
commit
b5e6ff1084
  1. 4
      lib/data/models/appconfig_model.dart
  2. 6
      lib/data/models/appconfig_model.g.dart
  3. 5
      lib/data/models/user_Info.dart
  4. 1
      lib/modules/login/controllers/login_controller.dart
  5. 54
      lib/modules/pairing/controllers/pairing_controller.dart
  6. 15
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIService.kt
  7. 76
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/mcp/CustomSseClientTransport.kt

4
lib/data/models/appconfig_model.dart

@ -27,7 +27,7 @@ class DBAgent {
final String? welcomeMessage;
final String? voiceWelcomeMessage;
final String? systemPrompt;
final List<String>? tools;
final String? tools;
DBAgent({
required this.id,
@ -52,7 +52,7 @@ class DBAgent {
class DBMCPServer {
final String servername;
final String url;
final List<String>? tools;
final String? tools;
DBMCPServer({
required this.servername,
required this.url,

6
lib/data/models/appconfig_model.g.dart

@ -37,8 +37,7 @@ DBAgent _$DBAgentFromJson(Map<String, dynamic> json) => DBAgent(
welcomeMessage: json['welcomeMessage'] as String?,
voiceWelcomeMessage: json['voiceWelcomeMessage'] as String?,
systemPrompt: json['systemPrompt'] as String?,
tools:
(json['tools'] as List<dynamic>?)?.map((e) => e as String).toList(),
tools: json['tools'] as String?,
);
Map<String, dynamic> _$DBAgentToJson(DBAgent instance) => <String, dynamic>{
@ -58,8 +57,7 @@ Map<String, dynamic> _$DBAgentToJson(DBAgent instance) => <String, dynamic>{
DBMCPServer _$DBMCPServerFromJson(Map<String, dynamic> json) => DBMCPServer(
servername: json['servername'] as String,
url: json['url'] as String,
tools:
(json['tools'] as List<dynamic>?)?.map((e) => e as String).toList(),
tools: json['tools'] as String?,
);
Map<String, dynamic> _$DBMCPServerToJson(DBMCPServer instance) =>

5
lib/data/models/user_Info.dart

@ -1,6 +1,3 @@
import 'dart:convert';
import 'dart:ffi';
class User {
// 单例实例
static User? _instance;
@ -24,7 +21,7 @@ class User {
String phone;
String phonemac;
String password;
int membershipLevel=0;//添加会员等级字段,0表示普通用户
int membershipLevel = 0; //添加会员等级字段,0表示普通用户
// final Int64 lastbettime;
// final Int64 createtime;
// final String wxopenid;

1
lib/modules/login/controllers/login_controller.dart

@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:logger/web.dart';
import '../../../routes/app_routes.dart';
import 'dart:convert';
import 'dart:ffi';
import 'facebookLogin.dart';
import 'googleLogin.dart';
import 'weixinLogin.dart';

54
lib/modules/pairing/controllers/pairing_controller.dart

@ -201,10 +201,10 @@ class PairingController extends GetxController {
// iOS需要请求的权限
permissionsToRequest.add(Permission.bluetooth);
permissionsToRequest.add(Permission.location);
// 如果需要麦克风权限(例如语音功能)
// permissionsToRequest.add(Permission.microphone);
try {
// 先尝试请求权限
if (permissionsToRequest.isNotEmpty) {
@ -214,17 +214,18 @@ class PairingController extends GetxController {
if (!status.isGranted) {
allGranted = false;
Logger.w(_tag, 'iOS权限 ${permission.toString()} 未被授予: $status');
// 特殊处理蓝牙权限
if (permission == Permission.bluetooth) {
Logger.w(_tag, 'iOS蓝牙权限未授予,状态: $status');
}
}
});
// 如果有权限未授予,但不是永久拒绝,继续尝试
if (!allGranted) {
bool hasPermanentlyDenied = statuses.values.any((status) => status.isPermanentlyDenied);
bool hasPermanentlyDenied =
statuses.values.any((status) => status.isPermanentlyDenied);
if (hasPermanentlyDenied) {
// 有权限被永久拒绝,需要引导用户去设置
_showPermissionSettingsDialog('部分权限被永久拒绝,请在设置中启用');
@ -232,15 +233,16 @@ class PairingController extends GetxController {
}
}
}
// 直接尝试使用蓝牙功能,这会触发iOS原生权限请求
bool isEnabled = await _bluetoothService.isBluetoothEnabled();
Logger.i(_tag, 'iOS蓝牙状态检查: ${isEnabled ? "已开启" : "未开启"}');
// 检测AccessorySetupKit支持情况(iOS 18+特性)
bool companionSupported = await _bleService.isCompanionSupported();
Logger.i(_tag, 'iOS Companion支持检查: ${companionSupported ? "支持" : "不支持"}');
Logger.i(
_tag, 'iOS Companion支持检查: ${companionSupported ? "支持" : "不支持"}');
if (companionSupported) {
// 针对iOS 18+,尝试预热AccessorySetupKit框架
try {
@ -251,23 +253,23 @@ class PairingController extends GetxController {
Logger.w(_tag, 'iOS 18+ AccessorySetupKit初始化失败: ${e.toString()}');
}
}
// 在iOS上视为权限已授予
_hasPermissions.value = true;
return true;
} catch (e) {
Logger.e(_tag, "iOS蓝牙初始化失败: ${e.toString()}");
// 显示自定义对话框引导用户
_showPermissionSettingsDialog('需要开启蓝牙权限才能配对设备');
return false;
}
}
// 如果还有其他权限需要请求
if (permissionsToRequest.isNotEmpty) {
statuses = await permissionsToRequest.request();
bool allGranted = true;
statuses.forEach((permission, status) {
if (!status.isGranted) {
@ -275,7 +277,7 @@ class PairingController extends GetxController {
Logger.w(_tag, '权限 ${permission.toString()} 未被授予: $status');
}
});
if (!allGranted) {
return false;
}
@ -284,7 +286,7 @@ class PairingController extends GetxController {
_hasPermissions.value = true;
return true;
}
/// 显示权限设置对话框
void _showPermissionSettingsDialog(String message) {
Get.dialog(
@ -537,27 +539,27 @@ class PairingController extends GetxController {
try {
isLoading.value = true;
statusDescription.value = '正在重新初始化蓝牙...';
// iOS上尝试重置权限状态
if (Platform.isIOS) {
await Future.delayed(Duration(milliseconds: 500));
// 检查蓝牙权限
PermissionStatus bluetoothStatus = await Permission.bluetooth.status;
Logger.i(_tag, "iOS蓝牙权限当前状态: $bluetoothStatus");
if (bluetoothStatus.isPermanentlyDenied) {
Logger.i(_tag, "iOS蓝牙权限处于永久拒绝状态,提示用户手动开启");
_showPermissionSettingsDialog('蓝牙权限被拒绝,请在设置中手动开启');
return;
}
// 尝试重新请求权限
if (!bluetoothStatus.isGranted) {
bluetoothStatus = await Permission.bluetooth.request();
Logger.i(_tag, "iOS蓝牙权限请求结果: $bluetoothStatus");
}
// 尝试检查蓝牙状态,这会重新触发系统权限弹窗
try {
bool isEnabled = await _bluetoothService.isBluetoothEnabled();
@ -565,7 +567,7 @@ class PairingController extends GetxController {
} catch (e) {
Logger.e(_tag, "iOS蓝牙状态检查失败: ${e.toString()}");
}
// 检查Companion功能(iOS 18+)
try {
bool isSupported = await _bleService.isCompanionSupported();
@ -578,7 +580,7 @@ class PairingController extends GetxController {
Logger.w(_tag, "iOS Companion功能检查失败: ${e.toString()}");
}
}
// 重新检查权限
if (await _checkAndRequestPermissions()) {
// 重新检查蓝牙状态
@ -591,7 +593,7 @@ class PairingController extends GetxController {
_updateStatusDescription();
}
}
/// 打开应用设置
Future<void> openAppSettings() async {
try {
@ -617,10 +619,10 @@ class PairingController extends GetxController {
// 即使没有设备连接,也启动BLE后台服务
await _startAgentService();
_isNavigatingToHome = true;
// 记录跳过配对的日志
Logger.i(_tag, '用户跳过设备配对,直接跳转到主页');
Get.offAllNamed(Routes.home);
}
}
}

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

@ -514,10 +514,9 @@ class OpenAIService(private val context: Context? = null) : CoroutineScope {
if (tools.length() > 0) {
put("tools", tools)
}
Log.i(TAG, "请求Ai对话: 携带工具数据 ${tools.length()}")
Log.i(TAG, "liwei------------------- 请求Ai对话: context:${messages.getJSONObject(messages.length() - 1).get("content").toString()} 携带工具数据 ${tools.length()}")
}
val mediaType = "application/json".toMediaTypeOrNull()
val request = Request.Builder()
.url(baseUrl)
@ -680,6 +679,7 @@ class OpenAIService(private val context: Context? = null) : CoroutineScope {
}
}
}
Log.d(TAG, "liwei---------------------- AI发起工具调用: tool=$toolCallsArray")
}
}
@ -706,7 +706,7 @@ class OpenAIService(private val context: Context? = null) : CoroutineScope {
put("arguments", firstToolCall.arguments)
put("id", firstToolCall.id)
}
Log.d(TAG, "工具调用: id=${firstToolCall.id}, name=${firstToolCall.name} arguments=${firstToolCall.arguments}")
Log.d(TAG, "liwei---------------------- 工具调用: id=${firstToolCall.id}, name=${firstToolCall.name} arguments=${firstToolCall.arguments}")
// 通知上层工具调用事件
callback.onFunctionCall(functionCall)
@ -721,11 +721,12 @@ class OpenAIService(private val context: Context? = null) : CoroutineScope {
val args = mcpClient?.parseJsonArguments(firstToolCall.arguments) ?: mapOf()
mcpClient?.callTool(firstToolCall.name, args)
} catch (e: Exception) {
Log.e(TAG, "工具调用错误: ${e.message}")
Log.e(TAG, "liwei----------------------- 工具调用错误: $e")
JSONObject().put("context", "工具调用失败: ${e.message}")
}
if (!isCanceled) {
// 处理结果
val resultObj = result ?: JSONObject().put("context", "工具调用失败")
callback.onFunctionCallResult(functionCall, resultObj)
@ -733,7 +734,7 @@ class OpenAIService(private val context: Context? = null) : CoroutineScope {
val context = resultObj.optString("context", "工具调用失败").let {
if (it.isNullOrEmpty()) "工具调用失败" else it
}
Log.d(TAG, "mcp工具调用完成: name=${firstToolCall.name}, context=$context")
Log.d(TAG, "liwei----------------------- mcp工具调用完成: name=${firstToolCall.name}, context=$context")
// 将结果发送回OpenAI继续对话
sendFunctionCallResult(
@ -746,7 +747,7 @@ class OpenAIService(private val context: Context? = null) : CoroutineScope {
}
} catch (e: Exception) {
if (!isCanceled) {
Log.e(TAG, "处理工具调用异常: ${e.message}")
Log.e(TAG, "liwei----------------------- 处理工具调用异常: $e")
try {
val errorMessage = "工具调用处理失败: ${e.message}"
sendFunctionCallResult(

76
local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/mcp/CustomSseClientTransport.kt

@ -1,5 +1,7 @@
package com.yunqiinnovation.open_ai_service.mcp
import android.os.Parcel
import android.os.Parcelable
import android.util.Log
import io.ktor.client.*
import io.ktor.client.plugins.sse.*
@ -7,6 +9,7 @@ import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
import kotlinx.coroutines.*
import kotlinx.serialization.encodeToString
@ -16,6 +19,40 @@ import kotlin.properties.Delegates
import kotlin.time.Duration
import java.util.concurrent.atomic.AtomicBoolean
import org.json.JSONObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
//@Serializable
//data class JSONRPCRequest(
// val jsonrpc: String = "2.0",
// val method: String,
// val params: JsonElement? = null,
// val id: JsonPrimitive? = null
//) : JSONRPCMessage, Parcelable {
//
// constructor(parcel: Parcel) : this(
// parcel.readString() ?: "2.0",
// parcel.readString() ?: "",
// parcel.readString()?.let { Json.decodeFromString<JsonElement>(it) },
// parcel.readString()?.let { Json.decodeFromString<JsonPrimitive>(it) }
// )
//
// override fun writeToParcel(parcel: Parcel, flags: Int) {
// parcel.writeString(jsonrpc)
// parcel.writeString(method)
// parcel.writeString(params?.let { Json.encodeToString(it) })
// parcel.writeString(id?.let { Json.encodeToString(it) })
// }
//
// override fun describeContents(): Int = 0
//
// companion object CREATOR : Parcelable.Creator<JSONRPCRequest> {
// override fun createFromParcel(parcel: Parcel): JSONRPCRequest = JSONRPCRequest(parcel)
// override fun newArray(size: Int): Array<JSONRPCRequest?> = arrayOfNulls(size)
// }
//}
/**
* 自定义SSE客户端传输层,修复原始SseClientTransport中的URL拼接问题
@ -39,7 +76,12 @@ class CustomSseClientTransport(
private val endpoint = CompletableDeferred<String>()
private var job: Job? = null
private val heartbeatIntervalMs = 30_000L // 30秒
private var heartbeatJob: Job? = null
// 创建JSON解析器,增强灵活性设置
private val json = Json {
ignoreUnknownKeys = true // 忽略未知字段
@ -299,6 +341,9 @@ class CustomSseClientTransport(
// 收集SSE事件
collectEvents()
// 启动心跳任务
startHeartbeat()
// 等待endpoint就绪
endpoint.await()
Log.d(TAG, "传输层启动完成,消息端点已就绪")
@ -346,7 +391,7 @@ class CustomSseClientTransport(
Log.e(TAG, "关闭失败: 传输层未初始化")
error("CustomSseClientTransport is not initialized!")
}
heartbeatJob?.cancel()
session.cancel()
_onClose()
job?.cancelAndJoin()
@ -359,4 +404,29 @@ class CustomSseClientTransport(
fun isInitialized(): Boolean {
return initialized.get()
}
}
private fun startHeartbeat() {
heartbeatJob = scope.launch {
while (isActive) {
delay(heartbeatIntervalMs)
try {
sendHeartbeat()
} catch (e: Exception) {
Log.e(TAG, "心跳发送失败: ${e.message}", e)
_onError(e)
}
}
}
}
private suspend fun sendHeartbeat() {
val pingMessage = JSONRPCRequest(
method = "ping",
jsonrpc = "2.0"
)
send(pingMessage)
}
}
Loading…
Cancel
Save