From 343fc876066598a7c3bb5cbd6435f507a13ce9cc Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Wed, 16 Apr 2025 18:25:58 +0800 Subject: [PATCH 01/15] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=9F=B3=E4=B9=90?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mcp.json | 8 +- .../services/open_ai_service_adapter.dart | 17 +- lib/data/services/spotify_service.dart | 176 ++++++++++++++++++ .../chat/controllers/chat_controller.dart | 26 ++- lib/modules/chat/views/chat_music_card.dart | 37 +--- lib/modules/chat/views/chat_view.dart | 6 +- .../open_ai_service/MCPClient.kt | 4 +- .../open_ai_service/OpenAIServicePlugin.kt | 38 ++-- .../open_ai_service/lib/open_ai_service.dart | 7 +- 9 files changed, 243 insertions(+), 76 deletions(-) create mode 100644 lib/data/services/spotify_service.dart diff --git a/.mcp.json b/.mcp.json index a344e9c9a..dddcc8d13 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,11 +1,7 @@ { "mcpServers": { - - "amap-amap-sse": { - "url": "https://mcp.amap.com/sse?key=e5fdc9605eabdeb5626f18f5721f343d" - }, - "web-search": { - "url": "http://mcp.ideapsound.com:8000/sse" + "deepsound-mcp": { + "url": "https://api.deapsound.com/sse" } } diff --git a/lib/data/services/open_ai_service_adapter.dart b/lib/data/services/open_ai_service_adapter.dart index 7052a7157..83fcc1f79 100644 --- a/lib/data/services/open_ai_service_adapter.dart +++ b/lib/data/services/open_ai_service_adapter.dart @@ -9,14 +9,15 @@ import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; /// 定义流事件类型,用于区分不同类型的事件 -enum StreamEventType { token, complete, error } +enum StreamEventType { token, functionCall, complete, error } /// 流事件包装类 class StreamEvent { final StreamEventType type; final String? content; + final Map? meta; - StreamEvent(this.type, {this.content}); + StreamEvent(this.type, {this.content, this.meta}); } /// OpenAI服务适配器 - 连接AiService接口与OpenAIService插件 @@ -63,6 +64,9 @@ class OpenAIServiceAdapter implements AiService { printInfo( info: '接收到 functionCall content: ${event.content} meta: ${event.meta}'); + _tokenStreamController.add(StreamEvent( + StreamEventType.functionCall, + meta: event.meta as Map?)); // _tokenStreamController.add(StreamEvent( // StreamEventType.error, // content: '收到函数调用,该流仅支持文本响应' @@ -170,7 +174,7 @@ class OpenAIServiceAdapter implements AiService { /// 发送消息并获取流式回复 @override - Stream sendMessageStream({ + Stream sendMessageStream({ required List> messages, required String systemPrompt, }) async* { @@ -180,7 +184,7 @@ class OpenAIServiceAdapter implements AiService { messages.map((m) => Map.from(m)).toList(); // 创建用于接收token的控制器 - final localController = StreamController(); + final localController = StreamController(); // 添加从广播流到本地流的订阅 final subscription = _tokenStreamController.stream.listen((streamEvent) { @@ -190,6 +194,11 @@ class OpenAIServiceAdapter implements AiService { localController.add(streamEvent.content!); } break; + case StreamEventType.functionCall: + if (streamEvent.meta != null && !localController.isClosed) { + localController.add(streamEvent.meta!); + } + break; case StreamEventType.complete: if (!localController.isClosed) { localController.close(); diff --git a/lib/data/services/spotify_service.dart b/lib/data/services/spotify_service.dart new file mode 100644 index 000000000..6ccf56389 --- /dev/null +++ b/lib/data/services/spotify_service.dart @@ -0,0 +1,176 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:convert'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:spotify_sdk/spotify_sdk.dart'; +import 'package:get/get.dart'; +import 'ai_service.dart'; +import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; + +/// OpenAI服务适配器 - 连接AiService接口与OpenAIService插件 +class SpotifService { + bool isInitialized = false; + + /// 初始化OpenAI服务 + Future initialize() async { + if (!isInitialized) { + getAccessToken(); + connectToSpotifyRemote(); + isInitialized = true; + printInfo(info: "SpotifService----------- initialize"); + } + + return true; + } + + /// 获取Spotify SDK的版本信息 + Future connectToSpotifyRemote() async { + try { + var result = await SpotifySdk.connectToSpotifyRemote( + clientId: dotenv.env['SPOTIFY_CLIENT_ID'].toString(), + redirectUrl: dotenv.env['SPOTIFY_REDIRECT_URL'].toString(), + scope: + 'user-read-private user-read-email streaming app-remote-control user-modify-playback-state', + ); + printInfo( + info: "SpotifService----------- connectToSpotifyRemote $result"); + } on PlatformException catch (e) { + printError(info: "SpotifService------------ PlatformException$e"); + } on MissingPluginException { + printError(info: "SpotifService------------ MissingPluginException"); + } + } + + /// 获取Spotify的访问令牌 + Future getAccessToken() async { + try { + var authenticationToken = await SpotifySdk.getAccessToken( + clientId: dotenv.env['SPOTIFY_CLIENT_ID'].toString(), + redirectUrl: dotenv.env['SPOTIFY_REDIRECT_URL'].toString(), + scope: 'app-remote-control, ' + 'user-modify-playback-state, ' + 'playlist-read-private, ' + 'playlist-modify-public,user-read-currently-playing', + ); + printInfo( + info: "SpotifService----------- getAccessToken $authenticationToken"); + return authenticationToken; + } on PlatformException catch (e) { + printError( + info: + "SpotifService------------getAccessToken ${e.code}: ${e.message}"); + return Future.error('$e.code: $e.message'); + } on MissingPluginException { + printError( + info: "SpotifService------------getAccessToken not implemented"); + return Future.error('not implemented'); + } + } + + //播放 音乐 + Future play(String url) async { + try { + await SpotifySdk.play(spotifyUri: url); + printInfo(info: "SpotifService----------- play $url"); + } on PlatformException catch (e) { + printError(info: "SpotifService------------play ${e.code}: ${e.message}"); + } on MissingPluginException { + printError( + info: "SpotifService------------getAccessToken not implemented"); + } + } + + //暂停 音乐 + Future pause() async { + try { + await SpotifySdk.pause(); + printInfo(info: "SpotifService----------- pause"); + } on PlatformException catch (e) { + printError( + info: "SpotifService------------pause ${e.code}: ${e.message}"); + } on MissingPluginException { + printError( + info: "SpotifService------------getAccessToken not implemented"); + } + } + + //停止 音乐 + Future resume() async { + try { + await SpotifySdk.resume(); + printInfo(info: "SpotifService----------- resume"); + } on PlatformException catch (e) { + printError( + info: "SpotifService------------pause ${e.code}: ${e.message}"); + } on MissingPluginException { + printError( + info: "SpotifService------------getAccessToken not implemented"); + } + } + + //下一首 音乐 + Future skipNext() async { + try { + await SpotifySdk.skipNext(); + } on PlatformException catch (e) { + printError( + info: "SpotifService------------pause ${e.code}: ${e.message}"); + } on MissingPluginException { + printError( + info: "SpotifService------------getAccessToken not implemented"); + } + } + + //跳过上一首 + Future skipPrevious() async { + try { + await SpotifySdk.skipPrevious(); + } on PlatformException catch (e) { + printError( + info: "SpotifService------------pause ${e.code}: ${e.message}"); + } on MissingPluginException { + printError( + info: "SpotifService------------getAccessToken not implemented"); + } + } + + //获取当前播放状态 + Future seekTo() async { + try { + await SpotifySdk.seekTo(positionedMilliseconds: 20000); + } on PlatformException catch (e) { + printError( + info: "SpotifService------------pause ${e.code}: ${e.message}"); + } on MissingPluginException { + printError( + info: "SpotifService------------getAccessToken not implemented"); + } + } + + //获取当前播放状态 + Future seekToRelative() async { + try { + await SpotifySdk.seekToRelativePosition(relativeMilliseconds: 20000); + } on PlatformException catch (e) { + printError( + info: "SpotifService------------pause ${e.code}: ${e.message}"); + } on MissingPluginException { + printError( + info: "SpotifService------------getAccessToken not implemented"); + } + } + + //获取当前播放状态 + Future switchToLocalDevice() async { + try { + await SpotifySdk.switchToLocalDevice(); + } on PlatformException catch (e) { + printError( + info: "SpotifService------------pause ${e.code}: ${e.message}"); + } on MissingPluginException { + printError( + info: "SpotifService------------getAccessToken not implemented"); + } + } +} diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index c08f6a51b..2cc4856f5 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -1,9 +1,11 @@ import 'dart:async'; +import 'package:deep_voice/data/services/spotify_service.dart'; import 'package:get/get.dart'; import 'package:flutter/widgets.dart'; import 'package:get_storage/get_storage.dart'; import 'package:just_audio/just_audio.dart'; import 'package:chat_storage/chat_storage.dart'; +import 'package:path/path.dart'; import '../../../data/services/ai_service.dart'; import '../../../data/services/deapsound_ai_service.dart'; import '../../../data/services/kouzi_ai_service.dart'; @@ -25,6 +27,7 @@ class ChatController extends GetxController { final AsrService _asrService = Get.find(); final VoiceInteractionService _voiceInteractionService = Get.find(); + late SpotifService _spotifService; // 添加ScrollController用于控制列表滚动 final ScrollController scrollController = ScrollController(); @@ -117,7 +120,8 @@ class ChatController extends GetxController { _aiService = Get.find(); Logger.info('使用OpenAIServiceAdapter'); } - + Get.put(SpotifService()); + _spotifService = Get.find(); // 使用克隆音色语音合成 final GetStorage storage = GetStorage(); String timbre = storage.read('timbre') ?? '1'; @@ -370,11 +374,11 @@ class ChatController extends GetxController { _aiResponseSubscription = responseStream.listen( (chunk) { if (_shouldCancelAiResponse) return; - + Logger.info('chat ${chunk}'); if (chunk is Map) { _updateIntermediateMessage(data: chunk); - if (chunk['musicaudio'] != null) { - play(chunk['musicaudio']); + if (chunk['card_spotify'] != null) { + play(chunk['card_spotify']); } } else { fullResponse += chunk; @@ -453,18 +457,10 @@ class ChatController extends GetxController { } // 播放音乐 - void play(Map musicaudio) async { + void play(Map card) async { try { - isPlay.value = false; - if (audiourl.value != musicaudio['audiourl']) { - audiourl.value = ''; - await player.setUrl(musicaudio['audiourl']); - audiourl.value = musicaudio['audiourl']; - player.play(); - } else { - player.play(); - } - isPlay.value = true; + await _spotifService.initialize(); + _spotifService.play(card['url'] as String); } catch (e) { Logger.error('加载异常: $e'); } diff --git a/lib/modules/chat/views/chat_music_card.dart b/lib/modules/chat/views/chat_music_card.dart index 9cade7e3e..5519c7143 100644 --- a/lib/modules/chat/views/chat_music_card.dart +++ b/lib/modules/chat/views/chat_music_card.dart @@ -12,8 +12,8 @@ class ChatMusicCard extends GetView { @override Widget build(BuildContext context) { - final Map musiccards = data?['musiccards'].first; - final Map musicaudio = data?['musicaudio']; + // final Map musiccards = data?['musiccards'].first; + // final Map musicaudio = data?['musicaudio']; return Row( children: [ @@ -25,14 +25,14 @@ class ChatMusicCard extends GetView { borderRadius: BorderRadius.circular(6.r), ), child: Image.network( - musiccards['image'], + data?['image'], fit: BoxFit.cover, ), ), 10.horizontalSpace, Expanded( child: Text( - musiccards['name'], + data?['name'], overflow: TextOverflow.ellipsis, style: TextStyle( color: isDarkMode ? Colors.white : Colors.black, @@ -43,8 +43,7 @@ class ChatMusicCard extends GetView { ), 10.horizontalSpace, Obx( - () => controller.audiourl == musicaudio['audiourl'] && - controller.isPlay.value + () => controller.audiourl == data?['url'] && controller.isPlay.value ? GestureDetector( onTap: () { controller.pause(); @@ -52,30 +51,6 @@ class ChatMusicCard extends GetView { child: Stack( alignment: Alignment.center, children: [ - SizedBox( - width: 38.w, - height: 38.w, - child: StreamBuilder( - stream: controller.player.positionStream, - builder: (context, snapshot) { - final position = snapshot.data ?? Duration.zero; - final total = - controller.player.duration ?? Duration.zero; - final progress = total.inSeconds > 0 - ? position.inSeconds / total.inSeconds - : 0.0; - return CircularProgressIndicator( - value: progress, - backgroundColor: isDarkMode - ? Colors.grey[700]! - : Colors.grey[300]!, - valueColor: AlwaysStoppedAnimation( - isDarkMode ? Colors.white : Colors.black), - strokeWidth: 2, // 线条粗细 - ); - }, - ), - ), Icon( Icons.pause_rounded, color: isDarkMode ? Colors.white : Colors.black, @@ -85,7 +60,7 @@ class ChatMusicCard extends GetView { ) : GestureDetector( onTap: () { - controller.play(musicaudio); + controller.play(data!); }, child: Container( width: 38.w, diff --git a/lib/modules/chat/views/chat_view.dart b/lib/modules/chat/views/chat_view.dart index f46f8f3c8..7318bbd39 100644 --- a/lib/modules/chat/views/chat_view.dart +++ b/lib/modules/chat/views/chat_view.dart @@ -356,11 +356,11 @@ class ChatView extends GetView { ); } if (message.data != null) { - if (message.data?['weatherDays'] != null) { + if (message.data?['card_weather'] != null) { box = WeatherDayCard(message.data, isDarkMode); } - if (message.data?['musicaudio'] != null) { - box = ChatMusicCard(message.data, isDarkMode); + if (message.data?['card_spotify'] != null) { + box = ChatMusicCard(message.data?['card_spotify'], isDarkMode); } if (message.data?['navigationcard'] != null) { box = ChatNavigationCard(message.data, isDarkMode); diff --git a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt index 15f0dc761..60acd48db 100644 --- a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt +++ b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt @@ -391,10 +391,10 @@ class MCPSubClient( else -> "" } } - JSONObject().apply {put("meta",result?._meta?.toMap()); put("context",context) } + JSONObject().apply { put("name",name);put("meta",result?._meta?.toMap()); put("context",context) } } catch (e: Exception) { Log.e(TAG, "调用MCP工具失败: ${e.message}", e) - JSONObject().apply { put("context", "调用MCP工具失败: ${e.message}") } + JSONObject().apply { put("context", "调用MCP工具失败: ${e.message}")} } } diff --git a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt index 40af80aa9..1948a2f13 100644 --- a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt +++ b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlin.math.min /** OpenAIServicePlugin */ class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler { @@ -164,24 +165,35 @@ class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler { } override fun onFunctionCall(functionCall: JSONObject) { - Log.e("OpenAIServicePlugin", "工具调用结果: ${functionCall.toString()}") + Log.e("OpenAIServicePlugin", "context:${functionCall.toString()}") val context = functionCall["context"] -// val meta = functionCall["meta"] - val meta: Map? = functionCall.optJSONObject("meta")?.let { metaJson -> - metaJson.keys().asSequence().associateWith { key -> - metaJson.get(key) // 或 opt(key),更安全 + if (functionCall.has("meta")) { + val meta = mapOf( + "card_spotify" to mapOf( + "name" to "泡沫", + "image" to "https://i.scdn.co/image/ab67616d0000b2739f6a38e4bb9c2f824c215e58", + "url" to "spotify:track:2IfSPLPDW6RGosdB5huwAA", + "sgener" to "Xposed", + ) + ) + Log.e("OpenAIServicePlugin", "工具调用结果 context:${context} meta:${meta.toString()}") + // 发送函数调用事件 + mainHandler.post { + eventSink?.success(mapOf("type" to "functionCall", "content" to context,"meta" to meta)) } } - val card_spotifyKey = meta?.containsKey("card_spotify") ?: false - if (card_spotifyKey) { //是否是音乐卡片 - } +// val meta: Map? = functionCall.optJSONObject("meta")?.let { metaJson -> +// metaJson.keys().asSequence().associateWith { key -> +// metaJson.get(key) // 或 opt(key),更安全 +// } +// } ?: mapOf( +// // 如果 null,就用默认值 +// "card_spotify" to { +// "url" to "spotify:track:58kNJana4w5BIjlZE2wq5m" +// }, +// ) - // 发送函数调用事件 - mainHandler.post { -// val functionCallMap = functionCall.toMap() - eventSink?.success(mapOf("type" to "functionCall", "content" to context,"meta" to meta)) - } } } ) diff --git a/local_plugins/open_ai_service/lib/open_ai_service.dart b/local_plugins/open_ai_service/lib/open_ai_service.dart index 022d53dbd..f67d16169 100644 --- a/local_plugins/open_ai_service/lib/open_ai_service.dart +++ b/local_plugins/open_ai_service/lib/open_ai_service.dart @@ -25,17 +25,19 @@ enum OpenAIEventType { class OpenAIEvent { final OpenAIEventType type; final dynamic content; - final dynamic meta; + final Map? meta; OpenAIEvent({required this.type, this.content, this.meta}); factory OpenAIEvent.fromMap(Map map) { final typeStr = map['type'] as String; final content = map['content']; - + final rawMeta = map['meta']; + final meta = rawMeta is Map ? Map.from(rawMeta) : null; return OpenAIEvent( type: _typeFromString(typeStr), content: content, + meta: meta, ); } @@ -77,6 +79,7 @@ class OpenAIService { // 监听原生事件 _eventChannel.receiveBroadcastStream().listen( (dynamic event) { + print("收到事件 OpenAIEvent: $event"); if (event is Map) { final eventMap = Map.from(event); final openAIEvent = OpenAIEvent.fromMap(eventMap); From da1118e180cc08890abf26ff9ec621947c790540 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Wed, 16 Apr 2025 19:17:19 +0800 Subject: [PATCH 02/15] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=9F=B3=E9=A2=91?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E6=92=AD=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/spotify_service.dart | 26 +++++++++++++++++-- .../chat/controllers/chat_controller.dart | 9 +++++-- lib/modules/chat/views/chat_music_card.dart | 2 +- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/lib/data/services/spotify_service.dart b/lib/data/services/spotify_service.dart index 6ccf56389..983868394 100644 --- a/lib/data/services/spotify_service.dart +++ b/lib/data/services/spotify_service.dart @@ -15,8 +15,8 @@ class SpotifService { /// 初始化OpenAI服务 Future initialize() async { if (!isInitialized) { - getAccessToken(); - connectToSpotifyRemote(); + await getAccessToken(); + await connectToSpotifyRemote(); isInitialized = true; printInfo(info: "SpotifService----------- initialize"); } @@ -68,6 +68,28 @@ class SpotifService { } } + //获取当前播放状态 + Future isPaused() async { + try { + var state = await SpotifySdk.getPlayerState(); + if (state != null) { + printInfo(info: "SpotifService----------- isPaused $state"); + return state.isPaused; + } else { + printError(info: "SpotifService----------- isPaused null"); + return true; + } + } on PlatformException catch (e) { + printError( + info: + "SpotifService------------getPlayerState ${e.code}: ${e.message}"); + } on MissingPluginException { + printError( + info: "SpotifService------------getPlayerState not implemented"); + } + return false; + } + //播放 音乐 Future play(String url) async { try { diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index 2cc4856f5..4b30db2af 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -461,6 +461,7 @@ class ChatController extends GetxController { try { await _spotifService.initialize(); _spotifService.play(card['url'] as String); + isPlay.value = true; } catch (e) { Logger.error('加载异常: $e'); } @@ -468,8 +469,12 @@ class ChatController extends GetxController { // 暂停播放音乐 void pause() async { - await player.pause(); - isPlay.value = false; + try { + _spotifService.pause(); + isPlay.value = false; + } catch (e) { + Logger.error('加载异常: $e'); + } } // 更新或添加最终响应 diff --git a/lib/modules/chat/views/chat_music_card.dart b/lib/modules/chat/views/chat_music_card.dart index 5519c7143..57ac11c18 100644 --- a/lib/modules/chat/views/chat_music_card.dart +++ b/lib/modules/chat/views/chat_music_card.dart @@ -43,7 +43,7 @@ class ChatMusicCard extends GetView { ), 10.horizontalSpace, Obx( - () => controller.audiourl == data?['url'] && controller.isPlay.value + () => controller.isPlay.value ? GestureDetector( onTap: () { controller.pause(); From 0a50ca213315b107a55fa1fda2412bfde7639385 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Wed, 16 Apr 2025 20:27:21 +0800 Subject: [PATCH 03/15] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/chat/views/chat_navigation_card.dart | 2 +- lib/modules/chat/views/chat_view.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/modules/chat/views/chat_navigation_card.dart b/lib/modules/chat/views/chat_navigation_card.dart index 5583f0564..4a01a71d8 100644 --- a/lib/modules/chat/views/chat_navigation_card.dart +++ b/lib/modules/chat/views/chat_navigation_card.dart @@ -41,7 +41,7 @@ class _ChatNavigationCardState extends State { } void _getLocation() async { - List endPointList = widget.data?['navigationcard']['location'].split(','); + List endPointList = widget.data?['location'].split(','); endPoint = LatLng( double.parse(endPointList[0]), double.parse(endPointList[1]), diff --git a/lib/modules/chat/views/chat_view.dart b/lib/modules/chat/views/chat_view.dart index 7318bbd39..605524c12 100644 --- a/lib/modules/chat/views/chat_view.dart +++ b/lib/modules/chat/views/chat_view.dart @@ -362,8 +362,8 @@ class ChatView extends GetView { if (message.data?['card_spotify'] != null) { box = ChatMusicCard(message.data?['card_spotify'], isDarkMode); } - if (message.data?['navigationcard'] != null) { - box = ChatNavigationCard(message.data, isDarkMode); + if (message.data?['card_navigation'] != null) { + box = ChatNavigationCard(message.data?['card_navigation'], isDarkMode); } } return box; From d9cb33fa629964a50f524aff744f48382d9d835f Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Wed, 16 Apr 2025 21:23:13 +0800 Subject: [PATCH 04/15] =?UTF-8?q?ai=E8=81=8A=E5=A4=A9=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E5=BD=93=E5=89=8D=E7=BB=8F=E7=BA=AC=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../amap_flutter/AmapFlutterPlugin.java | 1 + .../chat/controllers/chat_controller.dart | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/amap_flutter/android/src/main/java/com/example/amap_flutter/AmapFlutterPlugin.java b/amap_flutter/android/src/main/java/com/example/amap_flutter/AmapFlutterPlugin.java index ba72d7ac6..08bc360e9 100644 --- a/amap_flutter/android/src/main/java/com/example/amap_flutter/AmapFlutterPlugin.java +++ b/amap_flutter/android/src/main/java/com/example/amap_flutter/AmapFlutterPlugin.java @@ -152,6 +152,7 @@ public class AmapFlutterPlugin implements FlutterPlugin, MethodCallHandler { Map locationData = new HashMap<>(); locationData.put("latitude", amapLocation.getLatitude()); locationData.put("longitude", amapLocation.getLongitude()); + locationData.put("city", amapLocation.getCity()); locationData.put("address", amapLocation.getAddress()); eventSink.success(locationData); } diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index 4b30db2af..c9c5f0177 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:amap_flutter/amap_flutter.dart'; import 'package:deep_voice/data/services/spotify_service.dart'; import 'package:get/get.dart'; import 'package:flutter/widgets.dart'; @@ -32,6 +33,11 @@ class ChatController extends GetxController { // 添加ScrollController用于控制列表滚动 final ScrollController scrollController = ScrollController(); + final _amapFlutter = AmapFlutter(); + StreamSubscription? _locationStream; + String _latLng = ''; + String _city = ''; + // Agent 信息 late final Agent agent; @@ -170,6 +176,8 @@ class ChatController extends GetxController { // 订阅VoiceInteractionService事件 _subscribeToVoiceInteractionEvents(); + _getLocation(); + player.playerStateStream.listen((state) async { if (state.processingState == ProcessingState.completed) { await player.seek(Duration.zero); @@ -184,6 +192,16 @@ class ChatController extends GetxController { }); } + void _getLocation() async { + await _amapFlutter.initLocation(); + _locationStream = _amapFlutter.locationStream().listen((data) async { + _latLng = '${data['longitude']},${data['latitude']}}'; + _city = data['city']; + _amapFlutter.stopLocation(); + }); + _amapFlutter.startLocation(); + } + /// 订阅VoiceInteractionService事件 void _subscribeToVoiceInteractionEvents() { if (_voiceInteractionService.eventStream != null) { @@ -265,6 +283,9 @@ class ChatController extends GetxController { _autoStopTimer?.cancel(); _scrollDebounceTimer?.cancel(); // 清理防抖计时器 + _amapFlutter.dispose(); + _locationStream?.cancel(); + // 停止TTS _ttsService.stop(); @@ -432,6 +453,12 @@ class ChatController extends GetxController { 'role': m.role, 'content': m.content, }); + if (i == recentMessages.length - 2) { + messageHistory.add({ + 'role': 'user', + 'content': '我当前在$_city,我的经纬度是$_latLng', + }); + } } return messageHistory; From 25e0f86f987a3e0c13b9d4f581d0a27e10105eb7 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Wed, 16 Apr 2025 21:49:37 +0800 Subject: [PATCH 05/15] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../open_ai_service/android/build.gradle.kts | 1 + .../open_ai_service/MCPClient.kt | 2 +- .../open_ai_service/OpenAIServicePlugin.kt | 21 ++++++++++++------- .../open_ai_service/lib/open_ai_service.dart | 4 ++-- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/local_plugins/open_ai_service/android/build.gradle.kts b/local_plugins/open_ai_service/android/build.gradle.kts index 1f079fa6c..58c28775b 100644 --- a/local_plugins/open_ai_service/android/build.gradle.kts +++ b/local_plugins/open_ai_service/android/build.gradle.kts @@ -57,4 +57,5 @@ dependencies { implementation("io.ktor:ktor-client-cio:3.1.2") implementation("io.ktor:ktor-client-okhttp:3.1.2") implementation("io.ktor:ktor-client-logging:3.1.2") + implementation("com.google.code.gson:gson:2.10.1") } \ No newline at end of file diff --git a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt index 60acd48db..78e607ef2 100644 --- a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt +++ b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt @@ -391,7 +391,7 @@ class MCPSubClient( else -> "" } } - JSONObject().apply { put("name",name);put("meta",result?._meta?.toMap()); put("context",context) } + JSONObject().apply { put("name",name);put("meta",result?._meta.toString()); put("context",context) } } catch (e: Exception) { Log.e(TAG, "调用MCP工具失败: ${e.message}", e) JSONObject().apply { put("context", "调用MCP工具失败: ${e.message}")} diff --git a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt index 1948a2f13..a558fc6cf 100644 --- a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt +++ b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt @@ -168,14 +168,21 @@ class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler { Log.e("OpenAIServicePlugin", "context:${functionCall.toString()}") val context = functionCall["context"] if (functionCall.has("meta")) { - val meta = mapOf( - "card_spotify" to mapOf( - "name" to "泡沫", - "image" to "https://i.scdn.co/image/ab67616d0000b2739f6a38e4bb9c2f824c215e58", - "url" to "spotify:track:2IfSPLPDW6RGosdB5huwAA", - "sgener" to "Xposed", - ) + Log.e("OpenAIServicePlugin", + """ + 工具调用结果 meta 类型: ${functionCall["meta"]?.javaClass?.canonicalName} + 值内容: ${functionCall["meta"]} + """.trimIndent() ) + val meta = functionCall["meta"].toString() +// val meta = mapOf( +// "card_spotify" to mapOf( +// "name" to "泡沫", +// "image" to "https://i.scdn.co/image/ab67616d0000b2739f6a38e4bb9c2f824c215e58", +// "url" to "spotify:track:2IfSPLPDW6RGosdB5huwAA", +// "sgener" to "Xposed", +// ) +// ) Log.e("OpenAIServicePlugin", "工具调用结果 context:${context} meta:${meta.toString()}") // 发送函数调用事件 mainHandler.post { diff --git a/local_plugins/open_ai_service/lib/open_ai_service.dart b/local_plugins/open_ai_service/lib/open_ai_service.dart index f67d16169..20934fd07 100644 --- a/local_plugins/open_ai_service/lib/open_ai_service.dart +++ b/local_plugins/open_ai_service/lib/open_ai_service.dart @@ -32,8 +32,8 @@ class OpenAIEvent { factory OpenAIEvent.fromMap(Map map) { final typeStr = map['type'] as String; final content = map['content']; - final rawMeta = map['meta']; - final meta = rawMeta is Map ? Map.from(rawMeta) : null; + final rawMeta = map['meta'] as String?; + final meta = rawMeta != null ? jsonDecode(rawMeta) : null; return OpenAIEvent( type: _typeFromString(typeStr), content: content, From 120e8f8e12908744c852189660c449f7ef63ca63 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 17 Apr 2025 01:06:53 +0800 Subject: [PATCH 06/15] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=A4=A9=E6=B0=94?= =?UTF-8?q?=E5=8D=A1=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mcp.json | 6 +- .../chat/controllers/chat_controller.dart | 4 +- .../chat/views/chat_navigation_card.dart | 2 +- lib/modules/chat/views/chat_view.dart | 2 +- lib/modules/chat/views/weather_day_card.dart | 82 +++++++++---------- 5 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.mcp.json b/.mcp.json index dddcc8d13..2e07dbf0a 100644 --- a/.mcp.json +++ b/.mcp.json @@ -2,7 +2,9 @@ "mcpServers": { "deepsound-mcp": { "url": "https://api.deapsound.com/sse" + }, + "voitrans-mcp": { + "url": "https://www.voitrans.net/sse" } - } - } \ No newline at end of file +} \ No newline at end of file diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index c9c5f0177..6a58b3044 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -36,7 +36,7 @@ class ChatController extends GetxController { final _amapFlutter = AmapFlutter(); StreamSubscription? _locationStream; String _latLng = ''; - String _city = ''; + String _city = '深圳市'; // Agent 信息 late final Agent agent; @@ -456,7 +456,7 @@ class ChatController extends GetxController { if (i == recentMessages.length - 2) { messageHistory.add({ 'role': 'user', - 'content': '我当前在$_city,我的经纬度是$_latLng', + 'content': '当需要获取我的位置时,我当前在$_city,我的经纬度是$_latLng', }); } } diff --git a/lib/modules/chat/views/chat_navigation_card.dart b/lib/modules/chat/views/chat_navigation_card.dart index 4a01a71d8..b9ee31ab4 100644 --- a/lib/modules/chat/views/chat_navigation_card.dart +++ b/lib/modules/chat/views/chat_navigation_card.dart @@ -41,7 +41,7 @@ class _ChatNavigationCardState extends State { } void _getLocation() async { - List endPointList = widget.data?['location'].split(','); + List endPointList = widget.data?['companyName'].split(','); endPoint = LatLng( double.parse(endPointList[0]), double.parse(endPointList[1]), diff --git a/lib/modules/chat/views/chat_view.dart b/lib/modules/chat/views/chat_view.dart index 605524c12..0832d70a6 100644 --- a/lib/modules/chat/views/chat_view.dart +++ b/lib/modules/chat/views/chat_view.dart @@ -357,7 +357,7 @@ class ChatView extends GetView { } if (message.data != null) { if (message.data?['card_weather'] != null) { - box = WeatherDayCard(message.data, isDarkMode); + box = WeatherDayCard(message.data?['card_weather'], isDarkMode); } if (message.data?['card_spotify'] != null) { box = ChatMusicCard(message.data?['card_spotify'], isDarkMode); diff --git a/lib/modules/chat/views/weather_day_card.dart b/lib/modules/chat/views/weather_day_card.dart index 72d7c0f10..8773145b2 100644 --- a/lib/modules/chat/views/weather_day_card.dart +++ b/lib/modules/chat/views/weather_day_card.dart @@ -9,8 +9,6 @@ class WeatherDayCard extends StatelessWidget { @override Widget build(BuildContext context) { - final Map weatherDays = data?['weatherDays'].first; - return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, @@ -19,14 +17,14 @@ class WeatherDayCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - weatherDays['date'], + data?['date'], style: TextStyle( color: isDarkMode ? Colors.white : Colors.black, fontSize: 10.sp, ), ), Text( - '${weatherDays['maxTemperature']}°', + '${data?['temperature']}°', style: TextStyle( color: isDarkMode ? Colors.white : Colors.black, fontSize: 30.sp, @@ -43,49 +41,49 @@ class WeatherDayCard extends StatelessWidget { size: 18.w, ), Text( - '${weatherDays['weatherCondition']},${weatherDays['windDirection']}', + '${data?['weatherCondition']},${data?['windDirection']}', style: TextStyle( color: isDarkMode ? Colors.white : Colors.black, fontSize: 14.sp, ), ), - Row( - children: [ - Text( - '最\n高', - style: TextStyle( - color: isDarkMode ? Colors.white : Colors.black, - fontSize: 10.sp, - height: 1, - ), - ), - 5.horizontalSpace, - Text( - '${weatherDays['maxTemperature']}°', - style: TextStyle( - color: isDarkMode ? Colors.white : Colors.black, - fontSize: 20.sp, - ), - ), - 10.horizontalSpace, - Text( - '最\n低', - style: TextStyle( - color: isDarkMode ? Colors.white : Colors.black, - fontSize: 10.sp, - height: 1, - ), - ), - 5.horizontalSpace, - Text( - '${weatherDays['minTemperature']}°', - style: TextStyle( - color: isDarkMode ? Colors.white : Colors.black, - fontSize: 20.sp, - ), - ), - ], - ), + // Row( + // children: [ + // Text( + // '最\n高', + // style: TextStyle( + // color: isDarkMode ? Colors.white : Colors.black, + // fontSize: 10.sp, + // height: 1, + // ), + // ), + // 5.horizontalSpace, + // Text( + // '${data?['maxTemperature']}°', + // style: TextStyle( + // color: isDarkMode ? Colors.white : Colors.black, + // fontSize: 20.sp, + // ), + // ), + // 10.horizontalSpace, + // Text( + // '最\n低', + // style: TextStyle( + // color: isDarkMode ? Colors.white : Colors.black, + // fontSize: 10.sp, + // height: 1, + // ), + // ), + // 5.horizontalSpace, + // Text( + // '${data?['minTemperature']}°', + // style: TextStyle( + // color: isDarkMode ? Colors.white : Colors.black, + // fontSize: 20.sp, + // ), + // ), + // ], + // ), ], ), ], From 47f7838da579a837837129f728e951f86c9a73dc Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Thu, 17 Apr 2025 03:21:55 +0800 Subject: [PATCH 07/15] =?UTF-8?q?s=E4=B8=8A=E4=BC=A0=E5=9C=B0=E9=93=81?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/chat/views/chat_navigation_card.dart | 7 +++++-- lib/modules/chat/views/chat_view.dart | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/modules/chat/views/chat_navigation_card.dart b/lib/modules/chat/views/chat_navigation_card.dart index b9ee31ab4..445891513 100644 --- a/lib/modules/chat/views/chat_navigation_card.dart +++ b/lib/modules/chat/views/chat_navigation_card.dart @@ -8,10 +8,12 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; class ChatNavigationCard extends StatefulWidget { + final String content; final Map? data; final bool isDarkMode; - const ChatNavigationCard(this.data, this.isDarkMode, {super.key}); + const ChatNavigationCard(this.data, this.content, this.isDarkMode, + {super.key}); @override State createState() => _ChatNavigationCardState(); @@ -41,7 +43,7 @@ class _ChatNavigationCardState extends State { } void _getLocation() async { - List endPointList = widget.data?['companyName'].split(','); + List endPointList = widget.data?['destination'].split(','); endPoint = LatLng( double.parse(endPointList[0]), double.parse(endPointList[1]), @@ -193,6 +195,7 @@ class _ChatNavigationCardState extends State { _mapUrl.isNotEmpty ? Image.network(_mapUrl) : const CircularProgressIndicator(), + Text(widget.content) ], ); } diff --git a/lib/modules/chat/views/chat_view.dart b/lib/modules/chat/views/chat_view.dart index 0832d70a6..be89683fb 100644 --- a/lib/modules/chat/views/chat_view.dart +++ b/lib/modules/chat/views/chat_view.dart @@ -363,7 +363,8 @@ class ChatView extends GetView { box = ChatMusicCard(message.data?['card_spotify'], isDarkMode); } if (message.data?['card_navigation'] != null) { - box = ChatNavigationCard(message.data?['card_navigation'], isDarkMode); + box = ChatNavigationCard( + message.data?['card_navigation'], message.content, isDarkMode); } } return box; From 9286a834f27ec779671a4aa0121dcac2414b98d5 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 17 Apr 2025 08:43:38 +0800 Subject: [PATCH 08/15] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=A4=A9=E6=B0=94?= =?UTF-8?q?=E5=8D=A1=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/chat/views/weather_day_card.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/chat/views/weather_day_card.dart b/lib/modules/chat/views/weather_day_card.dart index 8773145b2..5bef62f1d 100644 --- a/lib/modules/chat/views/weather_day_card.dart +++ b/lib/modules/chat/views/weather_day_card.dart @@ -41,7 +41,7 @@ class WeatherDayCard extends StatelessWidget { size: 18.w, ), Text( - '${data?['weatherCondition']},${data?['windDirection']}', + '${data?['weatherCondition']},${data?['windDirection']}风', style: TextStyle( color: isDarkMode ? Colors.white : Colors.black, fontSize: 14.sp, From d94ec97631bd8912a38761cf17c1de187262ff87 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Thu, 17 Apr 2025 10:45:35 +0800 Subject: [PATCH 09/15] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=9F=B3=E9=A2=91?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=90=8C=E6=AD=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/chat/controllers/chat_controller.dart | 1 + lib/modules/chat/views/chat_music_card.dart | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index 6a58b3044..8bafd6ac3 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -489,6 +489,7 @@ class ChatController extends GetxController { await _spotifService.initialize(); _spotifService.play(card['url'] as String); isPlay.value = true; + audiourl.value = card['url'] as String; } catch (e) { Logger.error('加载异常: $e'); } diff --git a/lib/modules/chat/views/chat_music_card.dart b/lib/modules/chat/views/chat_music_card.dart index 57ac11c18..5fcce956e 100644 --- a/lib/modules/chat/views/chat_music_card.dart +++ b/lib/modules/chat/views/chat_music_card.dart @@ -43,7 +43,8 @@ class ChatMusicCard extends GetView { ), 10.horizontalSpace, Obx( - () => controller.isPlay.value + () => controller.audiourl.value == data?["url"] && + controller.isPlay.value ? GestureDetector( onTap: () { controller.pause(); From 618a8e77dcd2bfd3c04d24a98b7ba3ba39630d49 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 17 Apr 2025 14:32:59 +0800 Subject: [PATCH 10/15] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E5=8D=A1=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/chat/views/chat_navigation_card.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/modules/chat/views/chat_navigation_card.dart b/lib/modules/chat/views/chat_navigation_card.dart index 445891513..72ac80077 100644 --- a/lib/modules/chat/views/chat_navigation_card.dart +++ b/lib/modules/chat/views/chat_navigation_card.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'package:amap_flutter/amap_flutter.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:http/http.dart' as http; class ChatNavigationCard extends StatefulWidget { @@ -195,7 +196,15 @@ class _ChatNavigationCardState extends State { _mapUrl.isNotEmpty ? Image.network(_mapUrl) : const CircularProgressIndicator(), - Text(widget.content) + 10.verticalSpace, + Text( + widget.content, + style: TextStyle( + color: widget.isDarkMode ? Colors.white : Colors.black, + fontSize: 16.sp, + height: 1.5, + ), + ), ], ); } From 016a5ee4c3a917022ff029f8a5633a95b510019d Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Thu, 17 Apr 2025 16:06:53 +0800 Subject: [PATCH 11/15] =?UTF-8?q?=E4=B8=8A=E4=BC=A0mcp=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mcp.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.mcp.json b/.mcp.json index 2e07dbf0a..4651d9677 100644 --- a/.mcp.json +++ b/.mcp.json @@ -5,6 +5,9 @@ }, "voitrans-mcp": { "url": "https://www.voitrans.net/sse" + }, + "web-search": { + "url": "http://mcp.ideapsound.com:8000/sse" } } } \ No newline at end of file From 81ed7477cb48135ed22a10516d9d057d078a3396 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 17 Apr 2025 16:21:03 +0800 Subject: [PATCH 12/15] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=9F=B3=E4=B9=90?= =?UTF-8?q?=E6=92=AD=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../open_ai_service/MCPClient.kt | 2 +- .../open_ai_service/OpenAIServicePlugin.kt | 25 +++++++------------ 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt index 78e607ef2..45b5a8810 100644 --- a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt +++ b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt @@ -391,7 +391,7 @@ class MCPSubClient( else -> "" } } - JSONObject().apply { put("name",name);put("meta",result?._meta.toString()); put("context",context) } + JSONObject().apply { put("name",name);put("meta",result?._meta); put("context",context) } } catch (e: Exception) { Log.e(TAG, "调用MCP工具失败: ${e.message}", e) JSONObject().apply { put("context", "调用MCP工具失败: ${e.message}")} diff --git a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt index a558fc6cf..185989ee5 100644 --- a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt +++ b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.serialization.json.JsonObject import kotlin.math.min /** OpenAIServicePlugin */ @@ -168,25 +169,17 @@ class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler { Log.e("OpenAIServicePlugin", "context:${functionCall.toString()}") val context = functionCall["context"] if (functionCall.has("meta")) { - Log.e("OpenAIServicePlugin", - """ - 工具调用结果 meta 类型: ${functionCall["meta"]?.javaClass?.canonicalName} - 值内容: ${functionCall["meta"]} - """.trimIndent() - ) - val meta = functionCall["meta"].toString() -// val meta = mapOf( -// "card_spotify" to mapOf( -// "name" to "泡沫", -// "image" to "https://i.scdn.co/image/ab67616d0000b2739f6a38e4bb9c2f824c215e58", -// "url" to "spotify:track:2IfSPLPDW6RGosdB5huwAA", -// "sgener" to "Xposed", -// ) -// ) + val meta = functionCall.get("meta") + val metaJsonObject:JsonObject = meta as JsonObject Log.e("OpenAIServicePlugin", "工具调用结果 context:${context} meta:${meta.toString()}") // 发送函数调用事件 mainHandler.post { - eventSink?.success(mapOf("type" to "functionCall", "content" to context,"meta" to meta)) + eventSink?.success(mapOf("type" to "functionCall", "content" to context,"meta" to meta.toString())) + } + if (metaJsonObject.containsKey("card_spotify")) { + mainHandler.post { + eventSink?.success(mapOf("type" to "complete")) + } } } From db60ab0a53b6cea0a0a8c08243795136b929dacc Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 17 Apr 2025 16:52:43 +0800 Subject: [PATCH 13/15] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=9F=B3=E4=B9=90?= =?UTF-8?q?=E6=92=AD=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/controllers/chat_controller.dart | 10 +-- lib/modules/chat/models/message_model.dart | 12 +-- lib/modules/chat/views/chat_view.dart | 14 ++-- lib/modules/chat/views/weather_day_card.dart | 76 +++++++++---------- 4 files changed, 56 insertions(+), 56 deletions(-) diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index 8bafd6ac3..20cd15eea 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -397,7 +397,7 @@ class ChatController extends GetxController { if (_shouldCancelAiResponse) return; Logger.info('chat ${chunk}'); if (chunk is Map) { - _updateIntermediateMessage(data: chunk); + _updateIntermediateMessage(meta: chunk); if (chunk['card_spotify'] != null) { play(chunk['card_spotify']); } @@ -465,14 +465,14 @@ class ChatController extends GetxController { } // 更新中间状态的消息 - void _updateIntermediateMessage({Map? data, String? content}) { + void _updateIntermediateMessage({Map? meta, String? content}) { final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); if (index != -1) { - if (data != null) { - Map messagesData = messages[index].data ?? {}; + if (meta != null) { + Map messagesData = messages[index].meta ?? {}; messages[index] = messages[index].copyWith( - data: {...messagesData, ...data}, + meta: {...messagesData, ...meta}, ); } messages[index] = messages[index].copyWith( diff --git a/lib/modules/chat/models/message_model.dart b/lib/modules/chat/models/message_model.dart index 58b8e7076..bb1b0cce3 100644 --- a/lib/modules/chat/models/message_model.dart +++ b/lib/modules/chat/models/message_model.dart @@ -1,7 +1,7 @@ class Message { final String role; // 'user' or 'assistant' final String content; - final Map? data; + final Map? meta; final DateTime timestamp; final bool isIntermediate; // 中间状态标记,替代isLoading @@ -9,7 +9,7 @@ class Message { required this.role, required this.content, required this.timestamp, - this.data, + this.meta, this.isIntermediate = false, // 默认不是中间状态 }); @@ -18,7 +18,7 @@ class Message { return Message( role: json['role'] as String, content: json['content'] as String, - data: json['data'], + meta: json['meta'], timestamp: DateTime.parse(json['timestamp'] as String), isIntermediate: json['isIntermediate'] as bool? ?? false, ); @@ -29,7 +29,7 @@ class Message { return { 'role': role, 'content': content, - 'data': data, + 'meta': meta, 'timestamp': timestamp.toIso8601String(), 'isIntermediate': isIntermediate, }; @@ -49,14 +49,14 @@ class Message { Message copyWith({ String? role, String? content, - Map? data, + Map? meta, DateTime? timestamp, bool? isIntermediate, }) { return Message( role: role ?? this.role, content: content ?? this.content, - data: data ?? this.data, + meta: meta ?? this.meta, timestamp: timestamp ?? this.timestamp, isIntermediate: isIntermediate ?? this.isIntermediate, ); diff --git a/lib/modules/chat/views/chat_view.dart b/lib/modules/chat/views/chat_view.dart index be89683fb..0e8f53e56 100644 --- a/lib/modules/chat/views/chat_view.dart +++ b/lib/modules/chat/views/chat_view.dart @@ -355,16 +355,16 @@ class ChatView extends GetView { ), ); } - if (message.data != null) { - if (message.data?['card_weather'] != null) { - box = WeatherDayCard(message.data?['card_weather'], isDarkMode); + if (message.meta != null) { + if (message.meta?['card_weather'] != null) { + box = WeatherDayCard(message.meta?['card_weather'], isDarkMode); } - if (message.data?['card_spotify'] != null) { - box = ChatMusicCard(message.data?['card_spotify'], isDarkMode); + if (message.meta?['card_spotify'] != null) { + box = ChatMusicCard(message.meta?['card_spotify'], isDarkMode); } - if (message.data?['card_navigation'] != null) { + if (message.meta?['card_navigation'] != null) { box = ChatNavigationCard( - message.data?['card_navigation'], message.content, isDarkMode); + message.meta?['card_navigation'], message.content, isDarkMode); } } return box; diff --git a/lib/modules/chat/views/weather_day_card.dart b/lib/modules/chat/views/weather_day_card.dart index 5bef62f1d..afb14dd70 100644 --- a/lib/modules/chat/views/weather_day_card.dart +++ b/lib/modules/chat/views/weather_day_card.dart @@ -17,7 +17,7 @@ class WeatherDayCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - data?['date'], + data?['city'], style: TextStyle( color: isDarkMode ? Colors.white : Colors.black, fontSize: 10.sp, @@ -47,43 +47,43 @@ class WeatherDayCard extends StatelessWidget { fontSize: 14.sp, ), ), - // Row( - // children: [ - // Text( - // '最\n高', - // style: TextStyle( - // color: isDarkMode ? Colors.white : Colors.black, - // fontSize: 10.sp, - // height: 1, - // ), - // ), - // 5.horizontalSpace, - // Text( - // '${data?['maxTemperature']}°', - // style: TextStyle( - // color: isDarkMode ? Colors.white : Colors.black, - // fontSize: 20.sp, - // ), - // ), - // 10.horizontalSpace, - // Text( - // '最\n低', - // style: TextStyle( - // color: isDarkMode ? Colors.white : Colors.black, - // fontSize: 10.sp, - // height: 1, - // ), - // ), - // 5.horizontalSpace, - // Text( - // '${data?['minTemperature']}°', - // style: TextStyle( - // color: isDarkMode ? Colors.white : Colors.black, - // fontSize: 20.sp, - // ), - // ), - // ], - // ), + Row( + children: [ + Text( + '湿\n度', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black, + fontSize: 10.sp, + height: 1, + ), + ), + 5.horizontalSpace, + Text( + '${data?['humidity']}', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black, + fontSize: 20.sp, + ), + ), + 10.horizontalSpace, + Text( + '风\n力', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black, + fontSize: 10.sp, + height: 1, + ), + ), + 5.horizontalSpace, + Text( + '${data?['windForce']}', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black, + fontSize: 20.sp, + ), + ), + ], + ), ], ), ], From 23215cfc065c1d711579cf67fed43e898d493b16 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 17 Apr 2025 17:44:56 +0800 Subject: [PATCH 14/15] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/chat/controllers/chat_controller.dart | 4 ++++ local_plugins/chat_storage/lib/chat_storage.dart | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index 20cd15eea..8814dc6fc 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:amap_flutter/amap_flutter.dart'; import 'package:deep_voice/data/services/spotify_service.dart'; import 'package:get/get.dart'; @@ -845,6 +846,7 @@ class ChatController extends GetxController { role: chatMsg.sender, content: chatMsg.message, timestamp: DateTime.parse(chatMsg.timestamp), + meta: chatMsg.metadata != null ? jsonDecode(chatMsg.metadata!) : null, ); }).toList(); @@ -885,6 +887,7 @@ class ChatController extends GetxController { role: chatMsg.sender, content: chatMsg.message, timestamp: DateTime.parse(chatMsg.timestamp), + meta: chatMsg.metadata != null ? jsonDecode(chatMsg.metadata!) : null, ); }).toList(); @@ -906,6 +909,7 @@ class ChatController extends GetxController { sessionId: agent.id, message: message.content, sender: message.role, + metadata: jsonEncode(message.meta), ); } catch (e) { Logger.error('保存消息到数据库失败', e); diff --git a/local_plugins/chat_storage/lib/chat_storage.dart b/local_plugins/chat_storage/lib/chat_storage.dart index 2119dd84b..66e229b5f 100644 --- a/local_plugins/chat_storage/lib/chat_storage.dart +++ b/local_plugins/chat_storage/lib/chat_storage.dart @@ -77,6 +77,7 @@ class ChatStorage { 'sessionId': sessionId, 'message': message, 'sender': sender, + 'metadata': metadata, }); return Map.from(result); } catch (e) { @@ -99,7 +100,7 @@ class ChatStorage { final Map data = jsonDecode(result); final List messagesList = data['messages']; - + List messages = messagesList.map((item) { return ChatMessage.fromMap(Map.from(item)); }).toList(); @@ -151,4 +152,4 @@ class ChatStorage { return {'success': false, 'error': e.toString()}; } } -} \ No newline at end of file +} From 5678e850055b6b40c832c5fb4a1d71c32a4d9f76 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 17 Apr 2025 18:35:47 +0800 Subject: [PATCH 15/15] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/chat/controllers/chat_controller.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index 8814dc6fc..74ea42005 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -363,6 +363,7 @@ class ChatController extends GetxController { timestamp: DateTime.now(), ); messages.add(userMessage); + _saveMessageToDb(userMessage); // 清空输入框 inputText.value = '';