From a7ed838c9296822a7e060804d89718577897052d Mon Sep 17 00:00:00 2001 From: "guangfei.zhao" Date: Mon, 29 Sep 2025 18:33:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Eai=5Fchat=5Fcore?= =?UTF-8?q?=EF=BC=8C=E5=B0=86core=20=E5=92=8C=20widget=20=E5=88=86?= =?UTF-8?q?=E7=A6=BB=EF=BC=88=E6=9C=AA=E5=AE=8C=E6=88=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 迁移了 models enums, utils, http 封装,还有一些extensions;service 只迁移了 sse service --- lib/ai_chat_assistant.dart | 9 +- lib/bloc/ai_chat_cubit.dart | 2 - lib/bloc/command_state.dart | 3 +- lib/enums/message_service_state.dart | 6 - lib/manager.dart | 2 +- lib/pages/full_screen.dart | 3 +- lib/services/chat_sse_service.dart | 2 +- lib/services/command_service.dart | 4 +- lib/services/control_recognition_service.dart | 4 +- lib/services/message_service.dart | 11 +- lib/services/platform_tts_service.dart | 36 +++ lib/services/tts_service.dart | 1 + lib/services/voice_recognition_service.dart | 1 + lib/widgets/chat_box.dart | 2 +- lib/widgets/chat_bubble.dart | 3 +- packages/ai_chat_core/lib/ai_chat_core.dart | 90 +++++++ .../lib/src/apis/assistant_api.dart | 9 + .../lib/src/enums/message_service_state.dart | 11 + .../lib/src}/enums/message_status.dart | 3 +- .../lib/src}/enums/vehicle_command_type.dart | 25 +- .../lib/src/extensions/color_extension.dart | 8 + .../lib/src/extensions/string_extension.dart | 55 ++++ .../lib/src}/models/chat_message.dart | 0 .../lib/src}/models/vehicle_cmd.dart | 0 .../lib/src}/models/vehicle_cmd_response.dart | 2 +- .../lib/src}/models/vehicle_status_info.dart | 0 .../lib/src/network/api_config.dart | 22 ++ .../lib/src/network/header_interceptor.dart | 19 ++ .../lib/src/network/http_client.dart | 151 +++++++++++ .../lib/src/network/http_exception.dart | 17 ++ .../lib/src/network/interceptor.dart | 32 +++ .../lib/src/network/log_interceptor.dart | 37 +++ .../lib/src/services/channel_provider.dart | 44 ++++ .../lib/src/services/chat_sse_service.dart | 234 ++++++++++++++++++ .../lib/src/services/tts_service.dart | 21 ++ .../lib/src/utils/markdown_cleaner.dart | 46 ++++ packages/ai_chat_core/pubspec.lock | 50 +++- packages/ai_chat_core/pubspec.yaml | 3 + .../flutter_vosk_wakeword_method_channel.dart | 1 - pubspec.yaml | 2 +- 40 files changed, 933 insertions(+), 38 deletions(-) delete mode 100644 lib/enums/message_service_state.dart create mode 100644 lib/services/platform_tts_service.dart create mode 100644 packages/ai_chat_core/lib/ai_chat_core.dart create mode 100644 packages/ai_chat_core/lib/src/apis/assistant_api.dart create mode 100644 packages/ai_chat_core/lib/src/enums/message_service_state.dart rename {lib => packages/ai_chat_core/lib/src}/enums/message_status.dart (95%) rename {lib => packages/ai_chat_core/lib/src}/enums/vehicle_command_type.dart (71%) create mode 100644 packages/ai_chat_core/lib/src/extensions/color_extension.dart create mode 100644 packages/ai_chat_core/lib/src/extensions/string_extension.dart rename {lib => packages/ai_chat_core/lib/src}/models/chat_message.dart (100%) rename {lib => packages/ai_chat_core/lib/src}/models/vehicle_cmd.dart (100%) rename {lib => packages/ai_chat_core/lib/src}/models/vehicle_cmd_response.dart (74%) rename {lib => packages/ai_chat_core/lib/src}/models/vehicle_status_info.dart (100%) create mode 100644 packages/ai_chat_core/lib/src/network/api_config.dart create mode 100644 packages/ai_chat_core/lib/src/network/header_interceptor.dart create mode 100644 packages/ai_chat_core/lib/src/network/http_client.dart create mode 100644 packages/ai_chat_core/lib/src/network/http_exception.dart create mode 100644 packages/ai_chat_core/lib/src/network/interceptor.dart create mode 100644 packages/ai_chat_core/lib/src/network/log_interceptor.dart create mode 100644 packages/ai_chat_core/lib/src/services/channel_provider.dart create mode 100644 packages/ai_chat_core/lib/src/services/chat_sse_service.dart create mode 100644 packages/ai_chat_core/lib/src/services/tts_service.dart create mode 100644 packages/ai_chat_core/lib/src/utils/markdown_cleaner.dart diff --git a/lib/ai_chat_assistant.dart b/lib/ai_chat_assistant.dart index ce3c6ff..cd0d634 100644 --- a/lib/ai_chat_assistant.dart +++ b/lib/ai_chat_assistant.dart @@ -16,14 +16,7 @@ export 'services/message_service.dart'; export 'services/command_service.dart'; // types && enums && models -export 'models/vehicle_cmd.dart'; -export 'models/vehicle_cmd_response.dart'; -export 'models/chat_message.dart'; -export 'models/vehicle_status_info.dart'; - -export 'enums/vehicle_command_type.dart'; -export 'enums/message_status.dart'; -export 'enums/message_service_state.dart'; +export 'package:ai_chat_core/ai_chat_core.dart'; // utils export 'utils/assets_util.dart'; \ No newline at end of file diff --git a/lib/bloc/ai_chat_cubit.dart b/lib/bloc/ai_chat_cubit.dart index 080dc48..46ec617 100644 --- a/lib/bloc/ai_chat_cubit.dart +++ b/lib/bloc/ai_chat_cubit.dart @@ -1,5 +1,3 @@ -import '../enums/vehicle_command_type.dart'; -import '../services/command_service.dart'; import 'easy_bloc.dart'; import 'command_state.dart'; diff --git a/lib/bloc/command_state.dart b/lib/bloc/command_state.dart index f3557b8..f2e79d9 100644 --- a/lib/bloc/command_state.dart +++ b/lib/bloc/command_state.dart @@ -1,6 +1,7 @@ -import '../enums/vehicle_command_type.dart'; // AI Chat Command States +import 'package:ai_chat_core/ai_chat_core.dart'; + enum AIChatCommandStatus { idle, executing, diff --git a/lib/enums/message_service_state.dart b/lib/enums/message_service_state.dart deleted file mode 100644 index 2318272..0000000 --- a/lib/enums/message_service_state.dart +++ /dev/null @@ -1,6 +0,0 @@ -enum MessageServiceState { - idle, - recording, - recognizing, - replying, -} \ No newline at end of file diff --git a/lib/manager.dart b/lib/manager.dart index 0525c14..d99586f 100644 --- a/lib/manager.dart +++ b/lib/manager.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:ai_chat_core/ai_chat_core.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_vosk_wakeword/flutter_vosk_wakeword.dart'; @@ -10,7 +11,6 @@ import 'package:porcupine_flutter/porcupine_manager.dart'; import 'bloc/ai_chat_cubit.dart'; import 'bloc/command_state.dart'; -import 'models/vehicle_cmd.dart'; /// 车辆命令处理器抽象类 abstract class VehicleCommandHandler { diff --git a/lib/pages/full_screen.dart b/lib/pages/full_screen.dart index 944277f..df9fb0b 100644 --- a/lib/pages/full_screen.dart +++ b/lib/pages/full_screen.dart @@ -1,7 +1,6 @@ -import 'package:ai_chat_assistant/models/chat_message.dart'; +import 'package:ai_chat_core/ai_chat_core.dart'; import 'package:t_basic_intl/intl.dart'; import 'package:flutter/material.dart'; -import '../enums/message_status.dart'; import '../widgets/chat_box.dart'; import '../widgets/gradient_background.dart'; import '../services/message_service.dart'; diff --git a/lib/services/chat_sse_service.dart b/lib/services/chat_sse_service.dart index c95680b..2c31b32 100644 --- a/lib/services/chat_sse_service.dart +++ b/lib/services/chat_sse_service.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:math'; -import 'package:ai_chat_assistant/utils/tts_util.dart'; +import '../utils/tts_util.dart'; import '../utils/common_util.dart'; diff --git a/lib/services/command_service.dart b/lib/services/command_service.dart index d2c066d..f049d33 100644 --- a/lib/services/command_service.dart +++ b/lib/services/command_service.dart @@ -1,5 +1,5 @@ -import '../enums/vehicle_command_type.dart'; -import '../models/vehicle_status_info.dart'; +import 'package:ai_chat_core/ai_chat_core.dart'; + /// 命令处理回调函数定义 typedef CommandCallback = Future<(bool, Map? params)> Function( diff --git a/lib/services/control_recognition_service.dart b/lib/services/control_recognition_service.dart index fad8c63..7a7129e 100644 --- a/lib/services/control_recognition_service.dart +++ b/lib/services/control_recognition_service.dart @@ -1,8 +1,6 @@ import 'dart:convert'; +import 'package:ai_chat_core/ai_chat_core.dart'; import 'package:http/http.dart' as http; -import '../models/vehicle_cmd.dart'; -import '../models/vehicle_cmd_response.dart'; -import 'vehicle_state_service.dart'; /// 车辆命令服务 - 负责与后端交互以获取和处理车辆控制命令 class VehicleCommandService { diff --git a/lib/services/message_service.dart b/lib/services/message_service.dart index 67dec1b..d7de224 100644 --- a/lib/services/message_service.dart +++ b/lib/services/message_service.dart @@ -8,14 +8,17 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:uuid/uuid.dart'; -import '../services/chat_sse_service.dart'; +import '../services/chat_sse_service.dart' as Service; import '../services/classification_service.dart'; import '../services/control_recognition_service.dart'; // import '../services/audio_recorder_service.dart'; // import '../services/voice_recognition_service.dart'; -import 'command_service.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'platform_tts_service.dart'; + +const aliSdkChannelName = 'com.example.ai_chat_assistant/ali_sdk'; + // 用单例的模式创建 class MessageService extends ChangeNotifier { static const MethodChannel _asrChannel = MethodChannel('com.example.ai_chat_assistant/ali_sdk'); @@ -77,7 +80,9 @@ class MessageService extends ChangeNotifier { } } - final ChatSseService _chatSseService = ChatSseService(); + // final ChatSseService _chatSseService = ChatSseService(PlatformTtsService(aliSdkChannelName)); + final Service.ChatSseService _chatSseService = Service.ChatSseService(); + // final LocalTtsService _ttsService = LocalTtsService(); // final AudioRecorderService _audioService = AudioRecorderService(); // final VoiceRecognitionService _recognitionService = VoiceRecognitionService(); diff --git a/lib/services/platform_tts_service.dart b/lib/services/platform_tts_service.dart new file mode 100644 index 0000000..abe8d7f --- /dev/null +++ b/lib/services/platform_tts_service.dart @@ -0,0 +1,36 @@ +import 'package:ai_chat_core/ai_chat_core.dart'; +import 'package:flutter/services.dart'; + +/// TtsService 的平台特定实现,通过 MethodChannel 调用原生代码 +class PlatformTtsService implements TtsService { + @override + final String channelName; + + final MethodChannel _channel; + + PlatformTtsService(this.channelName): _channel = MethodChannel(channelName); + + Future _execute(String method, [Map? arguments]) { + return _channel.invokeMethod(method, arguments); + } + + @override + Future start(bool isChinese) async { + return await _execute('startTts', {'isChinese': isChinese}) ?? false; + } + + @override + Future send(String text) async { + await _execute('sendTts', {'text': text}); + } + + @override + Future complete() async { + await _execute('completeTts'); + } + + @override + Future stop() async { + await _execute('stopTts'); + } +} diff --git a/lib/services/tts_service.dart b/lib/services/tts_service.dart index 1c1cd51..be82557 100644 --- a/lib/services/tts_service.dart +++ b/lib/services/tts_service.dart @@ -6,6 +6,7 @@ import 'package:ai_chat_assistant/utils/common_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter_tts/flutter_tts.dart'; +@Deprecated('Use TtsService interface and its implementations instead') class LocalTtsService { final FlutterTts _flutterTts = FlutterTts(); bool _isPlaying = false; diff --git a/lib/services/voice_recognition_service.dart b/lib/services/voice_recognition_service.dart index 6b26e3b..36a0c08 100644 --- a/lib/services/voice_recognition_service.dart +++ b/lib/services/voice_recognition_service.dart @@ -2,6 +2,7 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:http_parser/http_parser.dart'; +@Deprecated('VoiceRecognitionService is deprecated, please use the new implementation if available.') class VoiceRecognitionService { Future recognizeSpeech(List audioBytes, {String lang = 'cn'}) async { diff --git a/lib/widgets/chat_box.dart b/lib/widgets/chat_box.dart index 44d84d9..8a7426c 100644 --- a/lib/widgets/chat_box.dart +++ b/lib/widgets/chat_box.dart @@ -1,5 +1,5 @@ +import 'package:ai_chat_core/ai_chat_core.dart'; import 'package:flutter/material.dart'; -import '../models/chat_message.dart'; import 'chat_bubble.dart'; class ChatBox extends StatelessWidget { diff --git a/lib/widgets/chat_bubble.dart b/lib/widgets/chat_bubble.dart index ca726d1..319e803 100644 --- a/lib/widgets/chat_bubble.dart +++ b/lib/widgets/chat_bubble.dart @@ -1,10 +1,9 @@ import 'package:ai_chat_assistant/utils/common_util.dart'; import 'package:ai_chat_assistant/widgets/rotating_image.dart'; +import 'package:ai_chat_core/ai_chat_core.dart'; import 'package:t_basic_intl/intl.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown_plus/flutter_markdown_plus.dart'; -import '../enums/message_status.dart'; -import '../models/chat_message.dart'; import 'package:provider/provider.dart'; import '../services/message_service.dart'; import 'package:flutter/services.dart'; diff --git a/packages/ai_chat_core/lib/ai_chat_core.dart b/packages/ai_chat_core/lib/ai_chat_core.dart new file mode 100644 index 0000000..6f7b61c --- /dev/null +++ b/packages/ai_chat_core/lib/ai_chat_core.dart @@ -0,0 +1,90 @@ +/* +建议目录结构: +``` +packages/ + ai_chat_core/ + lib/ + ai_chat_core.dart # 对外统一入口 + src/ # 代码目录 + models/ # models + message.dart # 消息和消息块 + message_chunk.dart + vehicle_command.dart # 车控 + vehicle_command_response.dart + command_result.dart + enums/ # enums + message_type.dart + message_status.dart + command_type.dart + service_state.dart # 服务状态 + input/ + audio_recorder_service.dart # 录音以及保存文件 + voice_recognition_service.dart # 目前项目未使用 + wake_word_service.dart # 预留(可选) + nlp/ # 自然语言处理相关 + text_classification_service.dart + vehicle_command_service.dart + dialog/ #对话相关的 + chat_channel/ + chat_sse_service.dart # SSE + chat_ws_service.dart # 预留 WebSocket(可选) + chat_http_service.dart # 批量 HTTP 方案(可选) + message_service.dart # 核心编排/状态更新 + tts/ # TTS + tts_service.dart + tts_task_queue.dart + command/ # 车控相关 + command_service.dart # Command Register Callback(已更新为抽象类的方式) + vehicle_command_handler.dart # 自定义实现车控命令 + persistence/ # 持久化 + persistence_service.dart # 提供持久化服务 + dao/ # dao + message_dao.dart # 消息体的 dao 对象 + infra/ # infra 的服务 + config_service.dart + redis_service.dart + location_service.dart + vehicle_state_service.dart # 预留 + logger.dart + error_mapper.dart + utils/ # 工具类 + markdown_cleaner.dart + id_generator.dart + throttle.dart +``` + +对外暴露(ai_chat_core.dart)仅导出: +- models / enums +- MessageService(主入口) +- VehicleCommandHandler 抽象 +- 配置初始化(initCore / setConfig) +*/ + +// ignore: unnecessary_library_name +library ai_chat_core; + +// enums +export 'src/enums/message_status.dart'; +export 'src/enums/message_service_state.dart'; +export 'src/enums/vehicle_command_type.dart'; + +// models +export 'src/models/chat_message.dart'; +export 'src/models/vehicle_status_info.dart'; +export 'src/models/vehicle_cmd.dart'; +export 'src/models/vehicle_cmd_response.dart'; + +// extensions +export 'src/extensions/string_extension.dart'; +export 'src/extensions/color_extension.dart'; + +// utils +export 'src/utils/markdown_cleaner.dart'; + +// services +export 'src/services/chat_sse_service.dart'; +export 'src/services/tts_service.dart'; + +// http client +export 'src/network/api_config.dart'; +export 'src/network/http_client.dart'; diff --git a/packages/ai_chat_core/lib/src/apis/assistant_api.dart b/packages/ai_chat_core/lib/src/apis/assistant_api.dart new file mode 100644 index 0000000..600657a --- /dev/null +++ b/packages/ai_chat_core/lib/src/apis/assistant_api.dart @@ -0,0 +1,9 @@ + +// assistant_api.dart +class AssistantAPI { + static const String baseUrl = 'https://api.example.com'; + static const String chatEndpoint = '/v1/chat'; + static const String ttsEndpoint = '/v1/tts'; + static const String imageEndpoint = '/v1/image'; + static const String videoEndpoint = '/v1/video'; +} diff --git a/packages/ai_chat_core/lib/src/enums/message_service_state.dart b/packages/ai_chat_core/lib/src/enums/message_service_state.dart new file mode 100644 index 0000000..031c571 --- /dev/null +++ b/packages/ai_chat_core/lib/src/enums/message_service_state.dart @@ -0,0 +1,11 @@ +/// 消息服务状态 +enum MessageServiceState { + // 空闲 + idle, + // 录音中 + recording, + // 识别中 + recognizing, + // 回复中 + replying, +} diff --git a/lib/enums/message_status.dart b/packages/ai_chat_core/lib/src/enums/message_status.dart similarity index 95% rename from lib/enums/message_status.dart rename to packages/ai_chat_core/lib/src/enums/message_status.dart index 1692036..7182c7a 100644 --- a/lib/enums/message_status.dart +++ b/packages/ai_chat_core/lib/src/enums/message_status.dart @@ -1,3 +1,4 @@ +// Message状态枚举 enum MessageStatus { normal('普通消息', 'Normal'), listening('聆听中', 'Listening'), @@ -13,4 +14,4 @@ enum MessageStatus { final String chinese; final String english; -} +} \ No newline at end of file diff --git a/lib/enums/vehicle_command_type.dart b/packages/ai_chat_core/lib/src/enums/vehicle_command_type.dart similarity index 71% rename from lib/enums/vehicle_command_type.dart rename to packages/ai_chat_core/lib/src/enums/vehicle_command_type.dart index 56af941..b32bb25 100644 --- a/lib/enums/vehicle_command_type.dart +++ b/packages/ai_chat_core/lib/src/enums/vehicle_command_type.dart @@ -1,29 +1,52 @@ +// 车控指令类型 enum VehicleCommandType { + // 未知 unknown('未知', 'unknown'), + // 上锁车门 lock('上锁车门', 'lock'), + // 解锁车门 unlock('解锁车门', 'unlock'), + // 打开车窗 openWindow('打开车窗', 'open window'), + // 关闭车窗 closeWindow('关闭车窗', 'close window'), + // 预约空调 appointAC('预约空调', 'appoint AC'), + // 打开空调 openAC('打开空调', 'open AC'), + // 关闭空调 closeAC('关闭空调', 'close AC'), + // 修改空调温度 changeACTemp('修改空调温度', 'change AC temperature'), + // 极速降温 coolSharply('极速降温', 'cool sharply'), + // 一键备车 prepareCar('一键备车', 'prepare car'), + // 一键融雪 meltSnow('一键融雪', 'melt snow'), + // 打开后备箱 openTrunk('打开后备箱', 'open trunk'), + // 关闭后备箱 closeTrunk('关闭后备箱', 'close trunk'), + // 鸣笛 honk('鸣笛', 'honk'), + // 定位车辆 locateCar('定位车辆', 'locate car'), + // 开启方向盘加热 openWheelHeat('开启方向盘加热', 'open wheel heat'), + // 关闭方向盘加热 closeWheelHeat('关闭方向盘加热', 'close wheel heat'), + // 开启主座椅加热 openMainSeatHeat('开启主座椅加热', 'open main seat heat'), + // 关闭主座椅加热 closeMainSeatHeat('关闭主座椅加热', 'close main seat heat'), + // 开启副座椅加热 openMinorSeatHeat('开启副座椅加热', 'open minor seat heat'), + // 关闭副座椅加热 closeMinorSeatHeat('关闭副座椅加热', 'close minor seat heat'); const VehicleCommandType(this.chinese, this.english); final String chinese; final String english; -} \ No newline at end of file +} diff --git a/packages/ai_chat_core/lib/src/extensions/color_extension.dart b/packages/ai_chat_core/lib/src/extensions/color_extension.dart new file mode 100644 index 0000000..324110e --- /dev/null +++ b/packages/ai_chat_core/lib/src/extensions/color_extension.dart @@ -0,0 +1,8 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +extension ColorExtension on Color { + static const Color commonColor = Color(0xFF6F72F1); + +} diff --git a/packages/ai_chat_core/lib/src/extensions/string_extension.dart b/packages/ai_chat_core/lib/src/extensions/string_extension.dart new file mode 100644 index 0000000..195c606 --- /dev/null +++ b/packages/ai_chat_core/lib/src/extensions/string_extension.dart @@ -0,0 +1,55 @@ +extension StringExtension on String { + bool get isChinese { + final chineseRegex = RegExp(r'^[\u4e00-\u9fa5]+$'); + return chineseRegex.hasMatch(this); + } + + bool get isEmail { + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'); + return emailRegex.hasMatch(this); + } + + bool get isPhoneNumber { + final phoneRegex = RegExp(r'^\+?[0-9]{7,15}$'); + return phoneRegex.hasMatch(this); + } + + bool get isValidUrl { + final urlRegex = RegExp( + r'^(https?:\/\/)?' // 协议 + r'(([a-zA-Z0-9_-]+\.)+[a-zA-Z]{2,6})' // 域名 + r'(\/[a-zA-Z0-9._%+-]*)*\/?' // 路径 + r'(\?[a-zA-Z0-9=&%+-]*)?' // 查询参数 + r'(#[a-zA-Z0-9_-]*)?$'); // 锚点 + return urlRegex.hasMatch(this); + } + + String capitalize() { + if (isEmpty) return this; + return this[0].toUpperCase() + substring(1); + } + + String toSnakeCase() { + return replaceAllMapped( + RegExp(r'[A-Z]'), (Match match) => '_${match.group(0)!.toLowerCase()}') + .replaceFirst('_', ''); + } + + String toKebabCase() { + return replaceAllMapped( + RegExp(r'[A-Z]'), (Match match) => '-${match.group(0)!.toLowerCase()}') + .replaceFirst('-', ''); + } + + String toTitleCase() { + return split(' ') + .map((word) => + word.isEmpty ? word : word[0].toUpperCase() + word.substring(1).toLowerCase()) + .join(' '); + } + + String reverse() { + return split('').reversed.join(); + } +} \ No newline at end of file diff --git a/lib/models/chat_message.dart b/packages/ai_chat_core/lib/src/models/chat_message.dart similarity index 100% rename from lib/models/chat_message.dart rename to packages/ai_chat_core/lib/src/models/chat_message.dart diff --git a/lib/models/vehicle_cmd.dart b/packages/ai_chat_core/lib/src/models/vehicle_cmd.dart similarity index 100% rename from lib/models/vehicle_cmd.dart rename to packages/ai_chat_core/lib/src/models/vehicle_cmd.dart diff --git a/lib/models/vehicle_cmd_response.dart b/packages/ai_chat_core/lib/src/models/vehicle_cmd_response.dart similarity index 74% rename from lib/models/vehicle_cmd_response.dart rename to packages/ai_chat_core/lib/src/models/vehicle_cmd_response.dart index e5c18dd..26c110a 100644 --- a/lib/models/vehicle_cmd_response.dart +++ b/packages/ai_chat_core/lib/src/models/vehicle_cmd_response.dart @@ -1,4 +1,4 @@ -import 'package:ai_chat_assistant/models/vehicle_cmd.dart'; +import 'vehicle_cmd.dart'; class VehicleCommandResponse { final String? tips; diff --git a/lib/models/vehicle_status_info.dart b/packages/ai_chat_core/lib/src/models/vehicle_status_info.dart similarity index 100% rename from lib/models/vehicle_status_info.dart rename to packages/ai_chat_core/lib/src/models/vehicle_status_info.dart diff --git a/packages/ai_chat_core/lib/src/network/api_config.dart b/packages/ai_chat_core/lib/src/network/api_config.dart new file mode 100644 index 0000000..a18e570 --- /dev/null +++ b/packages/ai_chat_core/lib/src/network/api_config.dart @@ -0,0 +1,22 @@ + +// API配置文件 +class ApiConfig { + // API基础URL + static const String baseURL = 'http://143.64.185.20:18606'; + /// 请求超时时间 + static const Duration timeout = Duration(seconds: 30); + /// 默认请求头 + static const Map defaultHeaders = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }; + + /// 重试次数 + static const int maxRetries = 2; + + /// 每次重试间隔(毫秒) + static const int retryDelayMs = 1000; + + // 私有构造函数,防止实例化 + ApiConfig._(); +} \ No newline at end of file diff --git a/packages/ai_chat_core/lib/src/network/header_interceptor.dart b/packages/ai_chat_core/lib/src/network/header_interceptor.dart new file mode 100644 index 0000000..9187794 --- /dev/null +++ b/packages/ai_chat_core/lib/src/network/header_interceptor.dart @@ -0,0 +1,19 @@ +import 'package:http/http.dart' as http; +import 'api_config.dart'; +import 'interceptor.dart'; + +class HeaderInterceptor extends Interceptor { + @override + Future onRequest(InterceptorChain chain) async { + chain.request.headers.addAll(ApiConfig.defaultHeaders); + return await chain.next(chain.request); + } + + @override + Future onResponse(http.StreamedResponse response) async { + return response; + } + + @override + Future onError(Object error) async {} +} \ No newline at end of file diff --git a/packages/ai_chat_core/lib/src/network/http_client.dart b/packages/ai_chat_core/lib/src/network/http_client.dart new file mode 100644 index 0000000..dc6f24d --- /dev/null +++ b/packages/ai_chat_core/lib/src/network/http_client.dart @@ -0,0 +1,151 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:http/http.dart' as http; +import 'api_config.dart'; +import 'header_interceptor.dart'; +import 'http_exception.dart'; +import 'interceptor.dart'; +import 'log_interceptor.dart'; + +class HttpClient extends http.BaseClient { + final http.Client _inner; + final List _interceptors; + + static List defaultInterceptors = [ + HeaderInterceptor(), + LogInterceptor(), + ]; + + HttpClient({ + http.Client? inner, + List? interceptors, + }) : _inner = inner ?? http.Client(), + _interceptors = interceptors ?? defaultInterceptors; + + @override + Future send(http.BaseRequest request) async { + int retries = 0; + while (true) { + try { + // 1请求拦截器链 + var response = await _executeRequestChain(request); + + // 响应拦截器链 + response = await _executeResponseChain(response); + + // 响应校验 + if (response.statusCode >= 200 && response.statusCode < 300) { + return response; + } else { + // 服务器返回错误码,包装成 HttpException 并抛出 + // 为了获取 body,我们需要读取流 + final bodyBytes = await response.stream.toBytes(); + final bodyString = utf8.decode(bodyBytes, allowMalformed: true); + final data = bodyString.isNotEmpty ? json.decode(bodyString) : null; + throw HttpException( + code: response.statusCode, + message: 'Request failed with status: ${response.statusCode}', + data: data, + ); + } + } catch (e) { + // 错误拦截器链 + await _executeErrorChain(e); + + // 如果是可重试的错误 (例如网络问题),并且未达到最大次数 + if (e is SocketException && retries < ApiConfig.maxRetries) { + retries++; + print('⚠️ Network error, retrying... ($retries/${ApiConfig.maxRetries})'); + await Future.delayed(const Duration(milliseconds: ApiConfig.retryDelayMs)); + continue; // 继续下一次循环 + } + + // 如果是 HttpException (服务器错误) 或其他不可重试的错误,或已达最大重试次数,则统一处理后抛出 + throw _handleError(e); + } + } + } + + /// 递归执行请求拦截器链 + Future _executeRequestChain(http.BaseRequest request, [int index = 0]) { + if (index >= _interceptors.length) { + // 拦截器链执行完毕,发出真正的网络请求 + return _inner.send(request); + } + // 获取当前拦截器 + final interceptor = _interceptors[index]; + // ignore: prefer_function_declarations_over_variables + Next nextStep = (req) => _executeRequestChain(req, index + 1); + + // 创建 InterceptorChain 实例,传入 request 和“下一步”的逻辑 + final chain = InterceptorChain(request, nextStep); + + // 调用当前拦截器的 onRequest 方法 + return interceptor.onRequest(chain); + } + + /// 顺序执行响应拦截器链 + Future _executeResponseChain(http.StreamedResponse response) async { + var res = response; + for (final interceptor in _interceptors) { + res = await interceptor.onResponse(res); + } + return res; + } + + /// 逆序执行错误拦截器链 + Future _executeErrorChain(Object error) async { + for (final interceptor in _interceptors.reversed) { + await interceptor.onError(error); + } + } + + /// 它负责将各种原始错误标准化为 HttpException + HttpException _handleError(Object e) { + if (e is HttpException) { + return e; // 如果已经是 HttpException,直接返回 + } else if (e is SocketException) { + return HttpException(message: '网络连接错误,请检查网络设置'); + } else if (e is TimeoutException) { + return HttpException(message: '请求超时,请稍后重试'); + } else { + return HttpException(message: '发生未知错误: $e'); + } + } + + /// 串行请求 + Future> runSequential(List Function()> tasks) async { + final results = []; + for (final task in tasks) { + try { + final res = await task(); + results.add(res); + } catch (e) { + results.add(e); + } + } + return results; + } + + /// 并行请求 + Future> runParallel(List> tasks) async { + final results = []; + final completer = Completer>(); + int completed = 0; + + for (final task in tasks) { + task.then((result) { + results.add(result); + }).catchError((error) { + results.add(error); + }).whenComplete(() { + completed++; + if (completed == tasks.length) { + completer.complete(results); + } + }); + } + return completer.future; + } +} diff --git a/packages/ai_chat_core/lib/src/network/http_exception.dart b/packages/ai_chat_core/lib/src/network/http_exception.dart new file mode 100644 index 0000000..bd55377 --- /dev/null +++ b/packages/ai_chat_core/lib/src/network/http_exception.dart @@ -0,0 +1,17 @@ +class HttpException implements Exception { + + final int? code; + final String message; + final dynamic data; + + HttpException({this.code, required this.message, this.data}); + + @override + String toString() { + if (code != null) { + return 'HttpException: $message (code: $code)'; + } + return 'HttpException: $message'; + } + +} \ No newline at end of file diff --git a/packages/ai_chat_core/lib/src/network/interceptor.dart b/packages/ai_chat_core/lib/src/network/interceptor.dart new file mode 100644 index 0000000..e6f7dff --- /dev/null +++ b/packages/ai_chat_core/lib/src/network/interceptor.dart @@ -0,0 +1,32 @@ +import 'package:http/http.dart' as http; + +typedef Next = Future Function(http.BaseRequest request); + +/// 拦截器处理器,用于将请求/响应/错误传递给链中的下一个环节 +class InterceptorChain { + final http.BaseRequest request; + final Next _next; + // 构造函数 + InterceptorChain(this.request, this._next); + + /// 调用链中的下一个环节 + Future next(http.BaseRequest req) { + // 调用传入的 _next 函数 + return _next(req); + } +} + +/// 拦截器抽象基类 +abstract class Interceptor { + /// 请求被发送前调用 + /// 你可以在这里修改请求,例如添加 headers,然后必须调用 `chain.next(request)` + Future onRequest(InterceptorChain chain); + + /// 收到响应后调用 + /// 你可以在这里读取和转换响应,然后返回一个新的响应 + Future onResponse(http.StreamedResponse response); + + /// 发生错误时调用 + /// 你可以在这里处理错误,例如重试,或者转换错误类型 + Future onError(Object error); +} \ No newline at end of file diff --git a/packages/ai_chat_core/lib/src/network/log_interceptor.dart b/packages/ai_chat_core/lib/src/network/log_interceptor.dart new file mode 100644 index 0000000..8e1d9f8 --- /dev/null +++ b/packages/ai_chat_core/lib/src/network/log_interceptor.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'interceptor.dart'; + +class LogInterceptor extends Interceptor { + @override + Future onRequest(InterceptorChain chain) async { + final request = chain.request; + print('➡️ [${request.method}] ${request.url}'); + print('Headers: ${request.headers}'); + // 注意:对于流式请求,我们无法在这里直接打印 body + return await chain.next(request); + } + + @override + Future onResponse(http.StreamedResponse response) async { + // 为了打印 body,我们需要读取流,然后重新创建一个 + final bodyBytes = await response.stream.toBytes(); + final bodyString = utf8.decode(bodyBytes, allowMalformed: true); + print('⬅️ [${response.statusCode}] ${response.request?.url}'); + print('Response Body: $bodyString'); + + // 重新创建流并返回新的响应 + return http.StreamedResponse( + Stream.value(bodyBytes), + response.statusCode, + headers: response.headers, + request: response.request, + reasonPhrase: response.reasonPhrase, + ); + } + + @override + Future onError(Object error) async { + print(' http Error: $error'); + } +} \ No newline at end of file diff --git a/packages/ai_chat_core/lib/src/services/channel_provider.dart b/packages/ai_chat_core/lib/src/services/channel_provider.dart new file mode 100644 index 0000000..5e5035c --- /dev/null +++ b/packages/ai_chat_core/lib/src/services/channel_provider.dart @@ -0,0 +1,44 @@ +import 'package:flutter/services.dart'; + +/// 一个通用的服务定位器,用于管理和提供所有类型的平台通道。 +class ChannelProvider { + ChannelProvider._(); + + static final ChannelProvider instance = ChannelProvider._(); + + // 【修改】使用 Object 作为 value 类型,使其可以存储任何类型的 Channel + final Map _channels = {}; + + /// 注册一个平台通道。 + /// 可以是 MethodChannel, EventChannel, 或 BasicMessageChannel。 + void register(String name, Object channel) { + if (channel is! MethodChannel && + channel is! EventChannel && + channel is! BasicMessageChannel) { + throw ArgumentError('The provided channel must be a MethodChannel, EventChannel, or BasicMessageChannel.'); + } + _channels[name] = channel; + } + + /// 【修改】使用泛型方法获取一个已注册的平台通道,并进行类型检查。 + /// + /// 示例: + /// final methodChannel = ChannelProvider.instance.get('my_method_channel'); + /// final eventChannel = ChannelProvider.instance.get('my_event_channel'); + T? get(String name) { + final channel = _channels[name]; + if (channel is T) { + return channel; + } + // 如果找到了通道但类型不匹配,可以打印一个警告 + if (channel != null) { + print('Warning: Channel with name "$name" was found, but its type (${channel.runtimeType}) does not match the requested type ($T).'); + } + return null; + } + + /// 移除一个通道(可选功能) + void unregister(String name) { + _channels.remove(name); + } +} \ No newline at end of file diff --git a/packages/ai_chat_core/lib/src/services/chat_sse_service.dart b/packages/ai_chat_core/lib/src/services/chat_sse_service.dart new file mode 100644 index 0000000..0afbbfa --- /dev/null +++ b/packages/ai_chat_core/lib/src/services/chat_sse_service.dart @@ -0,0 +1,234 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; +import 'package:http/http.dart' as http; +import '../network/http_client.dart'; +import 'tts_service.dart'; +import '../utils/markdown_cleaner.dart'; + +class ChatSseService { + // 依赖注入 TtsService + final TtsService _ttsService; + + // 缓存用户ID和会话ID + String? _cachedUserId; + String? _cachedConversationId; + + // 使用我们封装的 HttpClient + HttpClient? _currentClient; + http.StreamedResponse? _currentResponse; + StreamSubscription? _streamSubscription; + + bool _isAborted = true; + bool _isTtsStarted = false; + + String? get conversationId => _cachedConversationId; + + Future? _startTtsFuture; + + // 构造函数,接收 TtsService 实例 + ChatSseService(this._ttsService); + + void request({ + required String messageId, + required String text, + required bool isChinese, + required Function(String, String, bool) onStreamResponse, + }) async { + print("----------------------SSE Start"); + _isAborted = false; + if (_cachedUserId == null) { + _cachedUserId = _generateRandomUserId(6); + print('初始化用户ID: $_cachedUserId'); + } + String responseText = ''; + StringBuffer buffer = StringBuffer(); + final enEnders = RegExp(r'[.!?]'); + final zhEnders = RegExp(r'[。!?]'); + + // 【逻辑保留】processBuffer 的逻辑与你原来完全一致 + Future processBuffer(bool isChinese) async { + String txt = buffer.toString(); + // 先过滤 markdown 图片语法 + int imgStart = txt.indexOf('!['); + while (imgStart != -1) { + int imgEnd = txt.indexOf(')', imgStart); + if (imgEnd == -1) { + // 图片语法未闭合,缓存剩余部分 + buffer.clear(); + buffer.write(txt.substring(imgStart)); + return; + } + // 移除图片语法 + txt = txt.substring(0, imgStart) + txt.substring(imgEnd + 1); + imgStart = txt.indexOf('!['); + } + // 彻底移除 markdown 有序/无序列表序号(如 1.、2.、-、*、+) + txt = txt.replaceAll(RegExp(r'(^|\n)[ \t]*[0-9]+\.[ \t]*'), '\n'); + txt = txt.replaceAll(RegExp(r'(^|\n)[ \t]*[-\*\+][ \t]+'), '\n'); + // 分句符 + RegExp enders = isChinese ? zhEnders : enEnders; + int lastEnd = 0; + // 本地缓存句子 + List sentences = []; + for (final match in enders.allMatches(txt)) { + int endIndex = match.end; + String sentence = txt.substring(lastEnd, endIndex).trim(); + if (sentence.isNotEmpty) { + sentences.add(sentence); + } + lastEnd = endIndex; + } + // 只在达到完整句子时调用 TtsUtil.send + for (final s in sentences) { + // 【已迁移】使用 MarkdownCleaner 替代 CommonUtil + String ttsStr = MarkdownCleaner.cleanText(s, true); + // print("发送数据到TTS: $ttsStr"); + // 【已迁移】调用注入的 _ttsService + _ttsService.send(ttsStr); + } + // 缓存剩余不完整部分 + String remain = txt.substring(lastEnd).trim(); + buffer.clear(); + if (remain.isNotEmpty) { + buffer.write(remain); + } + } + + _currentClient = HttpClient(); + try { + final chatUri = Uri.parse('http://143.64.185.20:18606/chat'); + final request = http.Request('POST', chatUri) + ..headers['Content-Type'] = 'application/json' + ..headers['Accept'] = 'text/event-stream'; + + final body = {'message': text, 'user': _cachedUserId}; + if (_cachedConversationId != null) { + body['conversation_id'] = _cachedConversationId; + } + request.body = json.encode(body); + + _currentResponse = await _currentClient!.send(request); + + if (_currentResponse!.statusCode == 200) { + _startTtsFuture = startTts(isChinese); + _streamSubscription = _currentResponse!.stream + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen( + (line) { + if (_isAborted) return; + if (line.startsWith('data:')) { + final jsonStr = line.substring(5).trim(); + if (jsonStr == '[DONE]' || jsonStr.contains('message_end')) { + // 【已迁移】调用注入的 _ttsService + _ttsService.complete(); + onStreamResponse(messageId, responseText, true); + // 这里不需要 break,onDone 会被调用 + return; + } + try { + // 【逻辑保留】这里是原来 await for 循环中的核心逻辑,已完整迁移 + final jsonData = json.decode(jsonStr); + if (jsonData.containsKey('conversation_id') && + _cachedConversationId == null) { + _cachedConversationId = jsonData['conversation_id']; + } + if (jsonData['event'].toString().contains('message')) { + final textChunk = + jsonData.containsKey('answer') ? jsonData['answer'] : ''; + if (_isAborted) { + return; + } + buffer.write(textChunk); + processBuffer(isChinese); // 注意:这里不再需要 await + responseText += textChunk; + onStreamResponse(messageId, responseText, false); + } + } catch (e) { + print('解析 SSE 数据出错: $e, 原始数据: $jsonStr'); + } + } + }, + onDone: () { + print('SSE stream closed.'); + if (!_isAborted) { + // 【已迁移】调用注入的 _ttsService + _ttsService.complete(); + } + // onDone 意味着流正常结束,也需要重置资源 + resetRequest(); + }, + onError: (error) { + print('SSE stream error: $error'); + // onError 意味着流异常结束,需要重置资源 + resetRequest(); + }, + cancelOnError: true, + ); + } else { + print('SSE 连接失败,状态码: ${_currentResponse!.statusCode}'); + // 【逻辑保留】连接失败也需要重置资源 + resetRequest(); + } + } catch (e) { + print('SSE request failed: $e'); + // 【逻辑保留】捕获到任何其他异常时,也需要重置资源 + resetRequest(); + } + } + + Future startTts(bool isChinese) async { + print("----------------------TTS Start"); + if (!_isTtsStarted) { + // 【已迁移】调用注入的 _ttsService + if (await _ttsService.start(isChinese) == true) { + _isTtsStarted = true; + } + } + } + + Future abort() async { + _isAborted = true; + // 【已迁移】调用注入的 _ttsService + _ttsService.stop(); + resetRequest(); + } + + void resetRequest() { + // 【逻辑保留】重置资源,适配新的 HttpClient 和 StreamSubscription + _streamSubscription?.cancel(); + _streamSubscription = null; + + // package:http 的 client 只需要调用 close() + _currentClient?.close(); + _currentClient = null; + _currentResponse = null; + _isTtsStarted = false; + _startTtsFuture = null; + } + + // 【逻辑保留】_getCompleteTextEndIndex 方法保持不变 + int _getCompleteTextEndIndex(String buffer) { + // 支持句号、问号、感叹号和换行符作为分割依据 + final sentenceEnders = RegExp(r'[,.!?:,。!?:\n]'); + final matches = sentenceEnders.allMatches(buffer); + return matches.isEmpty ? 0 : matches.last.end; + } + + // 【逻辑保留】resetSession 方法保持不变 + void resetSession() { + _cachedUserId = null; + _cachedConversationId = null; + print('SSE会话已重置'); + } + + // 【逻辑保留】_generateRandomUserId 方法保持不变 + String _generateRandomUserId(int length) { + const chars = + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + final random = Random(); + return String.fromCharCodes(Iterable.generate( + length, (_) => chars.codeUnitAt(random.nextInt(chars.length)))); + } +} \ No newline at end of file diff --git a/packages/ai_chat_core/lib/src/services/tts_service.dart b/packages/ai_chat_core/lib/src/services/tts_service.dart new file mode 100644 index 0000000..5d3556a --- /dev/null +++ b/packages/ai_chat_core/lib/src/services/tts_service.dart @@ -0,0 +1,21 @@ +import 'package:flutter/services.dart'; + +/// TTS 服务的抽象接口 +abstract class TtsService { + + final String channelName; + + TtsService(this.channelName); + + /// 启动TTS + Future start(bool isChinese); + + /// 发送文本到TTS + Future send(String text); + + /// 通知TTS所有文本已发送完毕 + Future complete(); + + /// 停止TTS + Future stop(); +} \ No newline at end of file diff --git a/packages/ai_chat_core/lib/src/utils/markdown_cleaner.dart b/packages/ai_chat_core/lib/src/utils/markdown_cleaner.dart new file mode 100644 index 0000000..d994301 --- /dev/null +++ b/packages/ai_chat_core/lib/src/utils/markdown_cleaner.dart @@ -0,0 +1,46 @@ +class MarkdownCleaner { + /// 清理文本中的 Markdown 语法 + static String cleanText(String text, bool forTts) { + String cleanedText = text + .replaceAllMapped(RegExp(r'\*\*(.*?)\*\*'), (m) => m.group(1) ?? '') // 粗体 + .replaceAllMapped(RegExp(r'\*(.*?)\*'), (m) => m.group(1) ?? '') // 斜体 + .replaceAllMapped(RegExp(r'`([^`]+)`'), (m) => m.group(1) ?? '') // 行内代码 + .replaceAll(RegExp(r'```[^`]*```', multiLine: true), '') // 完整代码块 + .replaceAll(RegExp(r'```[^`]*$', multiLine: true), '') // 不完整代码块开始 + .replaceAll(RegExp(r'^[^`]*```', multiLine: true), '') // 不完整代码块结束 + .replaceAll(RegExp(r'^\s*\|.*\|\s*$', multiLine: true), '') // 表格行 + .replaceAll(RegExp(r'^\s*[\|\-\:\+\=\s]+\s*$', multiLine: true), '') // 表格分隔符 + .replaceAll(RegExp(r'\|'), ' ') // 清理残留的竖线 + .replaceAllMapped(RegExp(r'^#{1,6}\s+(.*)$', multiLine: true), (m) => m.group(1) ?? '') + .replaceAll(RegExp(r'\[([^\]]+)\]\([^\)]+\)'), '') // 完整超链接 + .replaceAll(RegExp(r'\[([^\]]*)\](?!\()'), r'$1') // 只有方括号 + .replaceAll(RegExp(r'\]\([^\)]*\)'), '') // 只有圆括号 + .replaceAll(RegExp(r'!\[([^\]]*)\]\([^\)]+\)'), '') // 图片链接 + .replaceAllMapped(RegExp(r'^>\s*(.*)$', multiLine: true), (m) => m.group(1) ?? '') + .replaceAllMapped(RegExp(r'^\s*[–—\-*+-]+\s*(.*)$', multiLine: true), (m) => m.group(1) ?? '') + .replaceAllMapped(RegExp(r'^\s*\d+\.\s+(.*)$', multiLine: true), (m) => m.group(1) ?? '') + .replaceAll(RegExp(r'^\s*[-*]{3,}\s*$', multiLine: true), '') + .replaceAll(RegExp(r'\*+'), '') // 清理残留星号 + .replaceAll(RegExp(r'`+'), '') // 清理残留反引号 + .replaceAll(RegExp(r'#+'), '') // 清理残留井号 + .replaceAll(RegExp(r'\n\s*\n'), '\n') + .replaceAll(RegExp(r'\n'), ' ') + .replaceAll(RegExp(r'!\$\d'), '') // 清理占位符 + .replaceAll(RegExp(r'\$\d+'), '') // 清理所有 $数字 占位符 + .trim(); + + if (forTts) { + if (cleanedText.contains("ID.UNYX")) { + cleanedText = cleanedText.replaceAllMapped( + RegExp(r'ID\.UNYX', caseSensitive: false), (m) => 'ID Unix'); + } else { + cleanedText = cleanedText.replaceAllMapped( + RegExp(r'UNYX', caseSensitive: false), (m) => 'Unix'); + } + cleanedText = cleanedText.replaceAllMapped( + RegExp(r'400-023-4567', caseSensitive: false), (m) => '4 0 0 - 0 2 3 - 4 5 6 7'); + } + + return cleanedText; + } +} \ No newline at end of file diff --git a/packages/ai_chat_core/pubspec.lock b/packages/ai_chat_core/pubspec.lock index ab42029..48e4150 100644 --- a/packages/ai_chat_core/pubspec.lock +++ b/packages/ai_chat_core/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.19.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.6" fake_async: dependency: transitive description: @@ -59,6 +67,22 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.2" leak_tracker: dependency: transitive description: @@ -168,6 +192,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.7.3" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.7" vector_math: dependency: transitive description: @@ -184,6 +224,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "14.3.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" sdks: - dart: ">=3.4.0 <4.0.0" + dart: ">=3.5.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/packages/ai_chat_core/pubspec.yaml b/packages/ai_chat_core/pubspec.yaml index 4ad5fe8..db707bb 100644 --- a/packages/ai_chat_core/pubspec.yaml +++ b/packages/ai_chat_core/pubspec.yaml @@ -10,6 +10,9 @@ dependencies: flutter: sdk: flutter + http: ^1.5.0 + uuid: ^3.0.5 + dev_dependencies: flutter_test: sdk: flutter \ No newline at end of file diff --git a/packages/flutter_vosk_wakeword/lib/src/channel/flutter_vosk_wakeword_method_channel.dart b/packages/flutter_vosk_wakeword/lib/src/channel/flutter_vosk_wakeword_method_channel.dart index 6360bff..6875294 100644 --- a/packages/flutter_vosk_wakeword/lib/src/channel/flutter_vosk_wakeword_method_channel.dart +++ b/packages/flutter_vosk_wakeword/lib/src/channel/flutter_vosk_wakeword_method_channel.dart @@ -16,4 +16,3 @@ class MethodChannelFlutterVoskWakeword extends FlutterVoskWakewordPlatform { } } - diff --git a/pubspec.yaml b/pubspec.yaml index f9a581b..adb13dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: meta: ^1.15.0 fluttertoast: ^8.2.12 record: ^6.0.0 - http: ^1.4.0 + http: ^1.5.0 path_provider: ^2.1.5 flutter_markdown : ^0.7.7 flutter_markdown_plus : ^1.0.1