Browse Source

Merge commit '5d7816a934c39c1df5f47ba371de36710555ffc1' into lxm

newdev_shunjiawei
lxm 2 years ago
parent
commit
0f6c63bcbb
  1. 11
      .mcp.json
  2. 1
      amap_flutter/android/src/main/java/com/example/amap_flutter/AmapFlutterPlugin.java
  3. 17
      lib/data/services/open_ai_service_adapter.dart
  4. 198
      lib/data/services/spotify_service.dart
  5. 76
      lib/modules/chat/controllers/chat_controller.dart
  6. 12
      lib/modules/chat/models/message_model.dart
  7. 36
      lib/modules/chat/views/chat_music_card.dart
  8. 16
      lib/modules/chat/views/chat_navigation_card.dart
  9. 15
      lib/modules/chat/views/chat_view.dart
  10. 16
      lib/modules/chat/views/weather_day_card.dart
  11. 1
      local_plugins/chat_storage/lib/chat_storage.dart
  12. 1
      local_plugins/open_ai_service/android/build.gradle.kts
  13. 4
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt
  14. 38
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt
  15. 7
      local_plugins/open_ai_service/lib/open_ai_service.dart

11
.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"
}
}
}
}

1
amap_flutter/android/src/main/java/com/example/amap_flutter/AmapFlutterPlugin.java

@ -152,6 +152,7 @@ public class AmapFlutterPlugin implements FlutterPlugin, MethodCallHandler {
Map<String, Object> 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);
}

17
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<String, dynamic>?));
// _tokenStreamController.add(StreamEvent(
// StreamEventType.error,
// content: '收到函数调用,该流仅支持文本响应'
@ -170,7 +174,7 @@ class OpenAIServiceAdapter implements AiService {
/// 发送消息并获取流式回复
@override
Stream<String> sendMessageStream({
Stream sendMessageStream({
required List<Map<String, String>> messages,
required String systemPrompt,
}) async* {
@ -180,7 +184,7 @@ class OpenAIServiceAdapter implements AiService {
messages.map((m) => Map<String, dynamic>.from(m)).toList();
// 创建用于接收token的控制器
final localController = StreamController<String>();
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();

198
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<bool> initialize() async {
if (!isInitialized) {
await getAccessToken();
await connectToSpotifyRemote();
isInitialized = true;
printInfo(info: "SpotifService----------- initialize");
}
return true;
}
/// 获取Spotify SDK的版本信息
Future<void> 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<String> 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<bool> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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");
}
}
}

76
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<AsrService>();
final VoiceInteractionService _voiceInteractionService =
Get.find<VoiceInteractionService>();
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<OpenAIServiceAdapter>();
Logger.info('使用OpenAIServiceAdapter');
}
Get.put(SpotifService());
_spotifService = Get.find<SpotifService>();
// 使用克隆音色语音合成
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);

12
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,
);

36
lib/modules/chat/views/chat_music_card.dart

@ -12,8 +12,8 @@ class ChatMusicCard extends GetView<ChatController> {
@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<ChatController> {
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<ChatController> {
),
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<ChatController> {
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 38.w,
height: 38.w,
child: StreamBuilder<Duration>(
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<Color>(
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<ChatController> {
)
: GestureDetector(
onTap: () {
controller.play(musicaudio);
controller.play(data!);
},
child: Container(
width: 38.w,

16
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<ChatNavigationCard> createState() => _ChatNavigationCardState();
@ -41,7 +44,7 @@ class _ChatNavigationCardState extends State<ChatNavigationCard> {
}
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<ChatNavigationCard> {
_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,
),
),
],
);
}

15
lib/modules/chat/views/chat_view.dart

@ -355,15 +355,16 @@ class ChatView extends GetView<ChatController> {
),
);
}
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;

16
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,

1
local_plugins/chat_storage/lib/chat_storage.dart

@ -77,6 +77,7 @@ class ChatStorage {
'sessionId': sessionId,
'message': message,
'sender': sender,
'metadata': metadata,
});
return Map<String, dynamic>.from(result);
} catch (e) {

1
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")
}

4
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}")}
}
}

38
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<String, Any>? = 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<String, Any>? = 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))
}
}
}
)

7
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<String, dynamic>? meta;
OpenAIEvent({required this.type, this.content, this.meta});
factory OpenAIEvent.fromMap(Map<String, dynamic> 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<dynamic, dynamic>) {
final eventMap = Map<String, dynamic>.from(event);
final openAIEvent = OpenAIEvent.fromMap(eventMap);

Loading…
Cancel
Save