diff --git a/.mcp.json b/.mcp.json index a344e9c9a..4651d9677 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,12 +1,13 @@ { "mcpServers": { - - "amap-amap-sse": { - "url": "https://mcp.amap.com/sse?key=e5fdc9605eabdeb5626f18f5721f343d" + "deepsound-mcp": { + "url": "https://api.deapsound.com/sse" + }, + "voitrans-mcp": { + "url": "https://www.voitrans.net/sse" }, "web-search": { "url": "http://mcp.ideapsound.com:8000/sse" } - } - } \ No newline at end of file +} \ No newline at end of file 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/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..983868394 --- /dev/null +++ b/lib/data/services/spotify_service.dart @@ -0,0 +1,198 @@ +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) { + await getAccessToken(); + await 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 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 { + 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..74ea42005 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -1,9 +1,13 @@ 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'; 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,10 +29,16 @@ class ChatController extends GetxController { final AsrService _asrService = Get.find(); final VoiceInteractionService _voiceInteractionService = Get.find(); + late SpotifService _spotifService; // 添加ScrollController用于控制列表滚动 final ScrollController scrollController = ScrollController(); + final _amapFlutter = AmapFlutter(); + StreamSubscription? _locationStream; + String _latLng = ''; + String _city = '深圳市'; + // Agent 信息 late final Agent agent; @@ -117,7 +127,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'; @@ -166,6 +177,8 @@ class ChatController extends GetxController { // 订阅VoiceInteractionService事件 _subscribeToVoiceInteractionEvents(); + _getLocation(); + player.playerStateStream.listen((state) async { if (state.processingState == ProcessingState.completed) { await player.seek(Duration.zero); @@ -180,6 +193,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) { @@ -261,6 +284,9 @@ class ChatController extends GetxController { _autoStopTimer?.cancel(); _scrollDebounceTimer?.cancel(); // 清理防抖计时器 + _amapFlutter.dispose(); + _locationStream?.cancel(); + // 停止TTS _ttsService.stop(); @@ -337,6 +363,7 @@ class ChatController extends GetxController { timestamp: DateTime.now(), ); messages.add(userMessage); + _saveMessageToDb(userMessage); // 清空输入框 inputText.value = ''; @@ -370,11 +397,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']); + _updateIntermediateMessage(meta: chunk); + if (chunk['card_spotify'] != null) { + play(chunk['card_spotify']); } } else { fullResponse += chunk; @@ -428,20 +455,26 @@ class ChatController extends GetxController { 'role': m.role, 'content': m.content, }); + if (i == recentMessages.length - 2) { + messageHistory.add({ + 'role': 'user', + 'content': '当需要获取我的位置时,我当前在$_city,我的经纬度是$_latLng', + }); + } } return messageHistory; } // 更新中间状态的消息 - 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( @@ -453,18 +486,12 @@ 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(); - } + await _spotifService.initialize(); + _spotifService.play(card['url'] as String); isPlay.value = true; + audiourl.value = card['url'] as String; } catch (e) { Logger.error('加载异常: $e'); } @@ -472,8 +499,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'); + } } // 更新或添加最终响应 @@ -816,6 +847,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(); @@ -856,6 +888,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(); @@ -877,6 +910,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/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_music_card.dart b/lib/modules/chat/views/chat_music_card.dart index 9cade7e3e..5fcce956e 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,7 +43,7 @@ class ChatMusicCard extends GetView { ), 10.horizontalSpace, Obx( - () => controller.audiourl == musicaudio['audiourl'] && + () => controller.audiourl.value == data?["url"] && controller.isPlay.value ? GestureDetector( onTap: () { @@ -52,30 +52,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 +61,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_navigation_card.dart b/lib/modules/chat/views/chat_navigation_card.dart index 5583f0564..72ac80077 100644 --- a/lib/modules/chat/views/chat_navigation_card.dart +++ b/lib/modules/chat/views/chat_navigation_card.dart @@ -5,13 +5,16 @@ 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 { + 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 +44,7 @@ class _ChatNavigationCardState extends State { } void _getLocation() async { - List endPointList = widget.data?['navigationcard']['location'].split(','); + List endPointList = widget.data?['destination'].split(','); endPoint = LatLng( double.parse(endPointList[0]), double.parse(endPointList[1]), @@ -193,6 +196,15 @@ class _ChatNavigationCardState extends State { _mapUrl.isNotEmpty ? Image.network(_mapUrl) : const CircularProgressIndicator(), + 10.verticalSpace, + Text( + widget.content, + style: TextStyle( + color: widget.isDarkMode ? Colors.white : Colors.black, + fontSize: 16.sp, + height: 1.5, + ), + ), ], ); } diff --git a/lib/modules/chat/views/chat_view.dart b/lib/modules/chat/views/chat_view.dart index f46f8f3c8..0e8f53e56 100644 --- a/lib/modules/chat/views/chat_view.dart +++ b/lib/modules/chat/views/chat_view.dart @@ -355,15 +355,16 @@ class ChatView extends GetView { ), ); } - if (message.data != null) { - if (message.data?['weatherDays'] != null) { - box = WeatherDayCard(message.data, isDarkMode); + if (message.meta != null) { + if (message.meta?['card_weather'] != null) { + box = WeatherDayCard(message.meta?['card_weather'], isDarkMode); } - if (message.data?['musicaudio'] != null) { - box = ChatMusicCard(message.data, isDarkMode); + if (message.meta?['card_spotify'] != null) { + box = ChatMusicCard(message.meta?['card_spotify'], isDarkMode); } - if (message.data?['navigationcard'] != null) { - box = ChatNavigationCard(message.data, isDarkMode); + if (message.meta?['card_navigation'] != null) { + box = ChatNavigationCard( + 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 72d7c0f10..afb14dd70 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?['city'], 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,7 +41,7 @@ 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, @@ -52,7 +50,7 @@ class WeatherDayCard extends StatelessWidget { Row( children: [ Text( - '最\n高', + '湿\n度', style: TextStyle( color: isDarkMode ? Colors.white : Colors.black, fontSize: 10.sp, @@ -61,7 +59,7 @@ class WeatherDayCard extends StatelessWidget { ), 5.horizontalSpace, Text( - '${weatherDays['maxTemperature']}°', + '${data?['humidity']}', style: TextStyle( color: isDarkMode ? Colors.white : Colors.black, fontSize: 20.sp, @@ -69,7 +67,7 @@ class WeatherDayCard extends StatelessWidget { ), 10.horizontalSpace, Text( - '最\n低', + '风\n力', style: TextStyle( color: isDarkMode ? Colors.white : Colors.black, fontSize: 10.sp, @@ -78,7 +76,7 @@ class WeatherDayCard extends StatelessWidget { ), 5.horizontalSpace, Text( - '${weatherDays['minTemperature']}°', + '${data?['windForce']}', style: TextStyle( color: isDarkMode ? Colors.white : Colors.black, fontSize: 20.sp, 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 +} 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 15f0dc761..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,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); 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..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,8 @@ 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 */ class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler { @@ -164,24 +166,34 @@ 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 = 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.toString())) + } + if (metaJsonObject.containsKey("card_spotify")) { + mainHandler.post { + eventSink?.success(mapOf("type" to "complete")) + } } } - 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..20934fd07 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'] as String?; + final meta = rawMeta != null ? jsonDecode(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);