diff --git a/CODE_ANALYSIS.md b/CODE_ANALYSIS.md new file mode 100644 index 0000000..213931c --- /dev/null +++ b/CODE_ANALYSIS.md @@ -0,0 +1,425 @@ +# AI Chat Assistant Flutter Plugin - 详细代码逻辑文档 + +## 📋 项目概述 + +这是一个专为车载系统设计的AI聊天助手Flutter插件,采用插件化架构设计,支持语音识别、AI对话、车控命令执行等功能。项目从原有的App结构重构为Plugin结构,便于集成到其他Flutter应用中。 + +## 🏗️ 核心架构分析 + +### 1. 架构模式 + +- **插件化架构**: 采用Flutter Plugin架构,便于第三方应用集成 +- **单例模式**: 核心服务类(MessageService)采用单例模式确保全局状态一致 +- **观察者模式**: 基于Provider的状态管理,实现响应式UI更新 +- **回调机制**: 通过CommandCallback实现车控命令的解耦处理 + +### 2. 目录结构与职责 + +``` +lib/ +├── app.dart # 插件主入口,提供初始化接口 +├── enums/ # 枚举定义层 +├── models/ # 数据模型层 +├── services/ # 业务逻辑服务层 +├── screens/ # UI界面层 +├── widgets/ # UI组件层 +├── utils/ # 工具类层 +└── themes/ # 主题样式层 +``` + +## 🔧 核心枚举定义 (Enums) + +### MessageServiceState +```dart +enum MessageServiceState { + idle, // 空闲状态 + recording, // 录音中 + recognizing, // 识别中 + replying, // 回复中 +} +``` + +### MessageStatus +```dart +enum MessageStatus { + normal, // 普通消息 + listening, // 聆听中 + recognizing, // 识别中 + thinking, // 思考中 + completed, // 完成回答 + executing, // 执行中 + success, // 执行成功 + failure, // 执行失败 + aborted, // 已中止 +} +``` + +### VehicleCommandType +支持22种车控命令类型: +- 车门控制:lock, unlock +- 车窗控制:openWindow, closeWindow +- 空调控制:openAC, closeAC, changeACTemp, coolSharply +- 特殊功能:prepareCar, meltSnow, honk, locateCar +- 加热功能:座椅加热、方向盘加热等 + +## 📊 数据模型层 (Models) + +### ChatMessage +```dart +class ChatMessage { + final String id; // 消息唯一标识 + final String text; // 消息内容 + final bool isUser; // 是否为用户消息 + final DateTime timestamp; // 时间戳 + MessageStatus status; // 消息状态 +} +``` + +### VehicleCommand +```dart +class VehicleCommand { + final VehicleCommandType type; // 命令类型 + final Map? params; // 命令参数 + final String error; // 错误信息 +} +``` + +## 🔄 核心服务层 (Services) + +### 1. MessageService - 核心消息管理服务 + +**设计模式**: 单例模式 + 观察者模式 + +**主要职责**: +- 统一管理聊天消息 +- 协调语音识别、AI对话、车控命令执行流程 +- 维护应用状态机 + +**核心方法分析**: + +#### 语音输入流程 +```dart +// 开始语音输入 +Future startVoiceInput() async { + // 1. 权限检查 + // 2. 状态验证 + // 3. 初始化录音 + // 4. 调用原生ASR服务 +} + +// 停止并处理语音输入 +Future stopAndProcessVoiceInput() async { + // 1. 停止录音 + // 2. 等待ASR结果 + // 3. 调用reply()处理识别结果 +} +``` + +#### 智能回复流程 +```dart +Future reply(String text) async { + // 1. 文本分类(TextClassificationService) + // 2. 根据分类结果路由到不同处理逻辑: + // - 车控命令 (category=2) -> handleVehicleControl() + // - 普通问答 (default) -> answerQuestion() + // - 错误问题 (category=4) -> answerWrongQuestion() + // - 系统错误 (category=-1) -> occurError() +} +``` + +#### 车控命令处理 +```dart +Future handleVehicleControl(String text, bool isChinese) async { + // 1. 调用VehicleCommandService解析命令 + // 2. 执行TTS播报 + // 3. 逐个执行车控命令 + // 4. 处理命令执行结果 + // 5. 生成执行反馈 +} +``` + +**状态管理机制**: +- 使用`ChangeNotifier`实现响应式状态更新 +- 通过`_state`维护服务状态机 +- 使用`_isReplyAborted`实现流程中断控制 + +### 2. CommandService - 车控命令处理服务 + +**设计模式**: 静态方法 + 回调机制 + +**职责**: +- 提供车控命令执行接口 +- 维护主应用注册的回调函数 +- 实现命令执行的解耦 + +```dart +// 命令回调函数定义 +typedef CommandCallback = Future<(bool, Map? params)> Function( + VehicleCommandType type, Map? params); + +// 执行车控命令 +static Future<(bool, Map? params)> executeCommand( + VehicleCommandType type, {Map? params}) async { + // 调用注册的回调函数执行实际车控逻辑 +} +``` + +### 3. TextClassificationService - 文本分类服务 + +**职责**: 对用户输入文本进行意图分类 + +```dart +Future classifyText(String text) async { + // HTTP POST请求到分类服务 + // 返回分类结果: + // -1: 错误 + // 2: 车控命令 + // 4: 错误问题 + // 其他: 普通问答 +} +``` + +### 4. VehicleCommandService - 车控命令解析服务 + +**职责**: +- 解析自然语言为车控命令 +- 生成命令执行反馈 + +```dart +Future getCommandFromText(String text) async { + // 1. 发送文本到车控解析服务 + // 2. 解析返回的命令列表 + // 3. 返回VehicleCommandResponse对象 +} + +Future getControlResponse(List successCommandList) async { + // 根据成功执行的命令列表生成友好的反馈文本 +} +``` + +## 🎨 UI层架构分析 + +### 1. 主界面 (MainScreen) + +**设计**: 采用Stack布局,背景图片 + 浮动图标 + +```dart +Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + // 背景图片 + Container(decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/bg.jpg', package: 'ai_chat_assistant'), + ), + )), + // 浮动AI助手图标 + FloatingIcon(), + ], + ), + ); +} +``` + +### 2. 浮动图标 (FloatingIcon) + +**功能特性**: +- 可拖拽定位 +- 状态指示(通过不同图标) +- 点击展开聊天界面 +- 动画效果支持 + +**交互流程**: +1. 长按拖拽调整位置 +2. 点击展开部分屏幕聊天界面 +3. 根据MessageService状态切换图标 + +### 3. 聊天界面架构 + +**部分屏模式** (PartScreen): +- 简化的聊天界面 +- 支持语音输入和文本显示 +- 可展开到全屏模式 + +**全屏模式** (FullScreen): +- 完整的聊天功能 +- 历史消息展示 +- 更多操作按钮 + +## 🔌 原生平台集成 + +### Android ASR集成 + +**Method Channel通信**: +```dart +static const MethodChannel _asrChannel = + MethodChannel('com.example.ai_chat_assistant/ali_sdk'); +``` + +**ASR事件处理**: +```dart +_asrChannel.setMethodCallHandler((call) async { + switch (call.method) { + case "onAsrResult": + // 实时更新识别结果 + replaceMessage(id: _latestUserMessageId!, text: call.arguments); + break; + case "onAsrStop": + // 识别结束,触发后续处理 + if (_asrCompleter != null && !_asrCompleter!.isCompleted) { + _asrCompleter!.complete(messages.last.text); + } + break; + } +}); +``` + +## 🛠️ 工具类分析 + +### CommonUtil +**主要功能**: +- 中文检测: `containChinese(String text)` +- Markdown清理: `cleanText(String text, bool forTts)` +- 公共颜色常量定义 + +**Markdown清理逻辑**: +```dart +static String cleanText(String text, bool forTts) { + // 1. 清理粗体/斜体标记 + // 2. 处理代码块 + // 3. 清理表格格式 + // 4. 处理链接和图片 + // 5. 清理列表和引用 + // 6. 规范化空白字符 +} +``` + +## 🔄 数据流分析 + +### 完整对话流程 + +```mermaid +graph TD + A[用户点击语音按钮] --> B[startVoiceInput] + B --> C[检查麦克风权限] + C --> D[开始录音/ASR] + D --> E[实时显示识别结果] + E --> F[用户松开按钮] + F --> G[stopAndProcessVoiceInput] + G --> H[调用reply处理文本] + H --> I[文本分类] + I --> J{分类结果} + J -->|车控命令| K[handleVehicleControl] + J -->|普通问答| L[answerQuestion] + J -->|错误问题| M[answerWrongQuestion] + K --> N[解析车控命令] + N --> O[执行TTS播报] + O --> P[执行车控命令] + P --> Q[生成执行反馈] + Q --> R[更新UI显示] +``` + +### 状态管理流程 + +**MessageService状态变化**: +``` +idle -> recording -> recognizing -> replying -> idle +``` + +**消息状态变化**: +``` +listening -> normal -> thinking -> executing -> success/failure +``` + +## 🔧 配置与扩展 + +### 1. 插件初始化 + +```dart +// 在主应用中初始化插件 +ChatAssistantApp.initialize( + commandCallback: (VehicleCommandType type, Map? params) async { + // 实现具体的车控逻辑 + return (true, {'message': '命令执行成功'}); + }, +); +``` + +### 2. 服务端接口配置 + +**当前硬编码的服务端地址**: +- 文本分类: `http://143.64.185.20:18606/classify` +- 车控解析: `http://143.64.185.20:18606/control` +- 控制反馈: `http://143.64.185.20:18606/control_resp` + +**建议改进**: 将服务端地址配置化,支持动态配置 + +### 3. 资源文件引用 + +**图片资源**: +- 使用`package: 'ai_chat_assistant'`前缀 +- 路径: `assets/images/` + +**字体资源**: +- VWHead_Bold.otf +- VWHead_Regular.otf + +## 🐛 已知问题与注意事项 + +### 1. 资源路径问题 +当前资源文件引用可能存在路径问题,需要确保在example项目中正确配置package前缀。 + +### 2. 硬编码服务地址 +服务端API地址硬编码,不利于部署和环境切换。 + +### 3. 错误处理 +部分网络请求缺少完善的错误处理和重试机制。 + +### 4. 内存管理 +长时间运行可能存在内存泄漏风险,需要关注Completer和监听器的清理。 + +## 🚀 扩展建议 + +### 1. 配置化改进 +- 服务端地址配置化 +- 支持多语言配置 +- 主题自定义配置 + +### 2. 功能增强 +- 添加离线语音识别支持 +- 实现消息历史持久化 +- 添加更多车控命令类型 + +### 3. 性能优化 +- 实现网络请求缓存 +- 优化UI渲染性能 +- 添加资源预加载 + +### 4. 测试完善 +- 添加单元测试 +- 集成测试覆盖 +- 性能测试工具 + +## 📝 开发流程建议 + +### 1. 添加新功能 +1. 在相应的枚举中定义新类型 +2. 在models中添加数据模型 +3. 在services中实现业务逻辑 +4. 在widgets中创建UI组件 +5. 更新主流程集成新功能 + +### 2. 调试技巧 +- 使用`debugPrint`添加关键节点日志 +- 通过Provider DevTools监控状态变化 +- 使用Flutter Inspector检查UI结构 + +### 3. 集成测试 +- 在example项目中测试完整流程 +- 验证车控命令回调机制 +- 测试不同场景下的错误处理 + +--- + +这份文档涵盖了项目的核心架构、关键代码逻辑、数据流分析和扩展建议,可以帮助您快速理解项目结构并进行后续开发。如有具体问题,可以针对特定模块进行深入分析。 diff --git a/android/build.gradle b/android/build.gradle index 8f52ca7..dd5e36a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,7 +5,6 @@ buildscript { ext.kotlin_version = '2.1.20' repositories { maven { url file("mavenLocal") } - google() mavenCentral() } @@ -19,7 +18,6 @@ buildscript { allprojects { repositories { maven { url file("mavenLocal") } - google() mavenCentral() } @@ -50,7 +48,7 @@ android { } defaultConfig { - minSdkVersion 23 + minSdkVersion 21 ndk { abiFilters "armeabi-v7a", "arm64-v8a" } @@ -77,9 +75,9 @@ android { } repositories { -// flatDir { -// dirs("libs") -// } + flatDir { + dirs("libs") + } } dependencies { @@ -87,8 +85,6 @@ android { implementation 'com.alibaba:fastjson:1.2.83' // compileOnly files("$flutterRoot/bin/cache/artifacts/engine/android-arm/flutter.jar") // implementation(files("libs/fastjson-1.1.46.android.jar")) -// implementation(files("libs/nui-release-1.0.0.aar")) + // implementation(files("libs/nui-release-1.0.0.aar")) } } - - diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html index d3f963a..30af9b7 100644 --- a/android/build/reports/problems/problems-report.html +++ b/android/build/reports/problems/problems-report.html @@ -650,12 +650,12 @@ code + .copy-button { diff --git a/android/settings.gradle b/android/settings.gradle index 9ca8501..99b20e8 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1 +1 @@ - rootProject.name = 'ai_assistant_plugin' \ No newline at end of file +rootProject.name = 'ai_assistant_plugin' \ No newline at end of file diff --git a/assets/images/ai1.png b/assets/images/ai1.png new file mode 100644 index 0000000..142e85c Binary files /dev/null and b/assets/images/ai1.png differ diff --git a/assets/images/ai2.png b/assets/images/ai2.png new file mode 100644 index 0000000..5ef2db4 Binary files /dev/null and b/assets/images/ai2.png differ diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts index cc7d3a4..498f87f 100644 --- a/example/android/app/build.gradle.kts +++ b/example/android/app/build.gradle.kts @@ -24,7 +24,7 @@ android { applicationId = "com.example.example" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion + minSdk = 23 targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName @@ -44,7 +44,7 @@ android { includeInApk = includeInApk includeInBundle = includeInBundle } - ndkVersion = ndkVersion + ndkVersion = "27.0.12077973" } flutter { diff --git a/example/pubspec.lock b/example/pubspec.lock index 3e86f95..6f3836a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -20,10 +20,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.flutter-io.cn" source: hosted - version: "2.13.0" + version: "2.11.0" audioplayers: dependency: transitive description: @@ -91,34 +91,34 @@ packages: dependency: transitive description: name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.2" + version: "2.1.1" characters: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.flutter-io.cn" source: hosted - version: "1.4.0" + version: "1.3.0" clock: dependency: transitive description: name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.2" + version: "1.1.1" collection: dependency: transitive description: name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.flutter-io.cn" source: hosted - version: "1.19.1" + version: "1.19.0" crypto: dependency: transitive description: @@ -139,18 +139,18 @@ packages: dependency: transitive description: name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.3" + version: "1.3.1" ffi: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.4" + version: "2.1.3" file: dependency: transitive description: @@ -234,26 +234,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.flutter-io.cn" source: hosted - version: "11.0.1" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.10" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.2" + version: "3.0.1" lints: dependency: transitive description: @@ -274,10 +274,10 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.flutter-io.cn" source: hosted - version: "0.12.17" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: @@ -290,10 +290,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.flutter-io.cn" source: hosted - version: "1.16.0" + version: "1.15.0" nested: dependency: transitive description: @@ -306,10 +306,10 @@ packages: dependency: transitive description: name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.flutter-io.cn" source: hosted - version: "1.9.1" + version: "1.9.0" path_provider: dependency: transitive description: @@ -322,18 +322,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.18" + version: "2.2.17" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.2" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -503,58 +503,58 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.flutter-io.cn" source: hosted - version: "1.10.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.flutter-io.cn" source: hosted - version: "1.12.1" + version: "1.12.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.4" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.flutter-io.cn" source: hosted - version: "1.4.1" + version: "1.3.0" synchronized: dependency: transitive description: name: synchronized - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" url: "https://pub.flutter-io.cn" source: hosted - version: "3.4.0" + version: "3.3.0+3" term_glyph: dependency: transitive description: name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.2" + version: "1.2.1" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.flutter-io.cn" source: hosted - version: "0.7.6" + version: "0.7.3" typed_data: dependency: transitive description: @@ -575,18 +575,18 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.0" + version: "2.1.4" vm_service: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.flutter-io.cn" source: hosted - version: "15.0.2" + version: "14.3.0" web: dependency: transitive description: @@ -604,5 +604,5 @@ packages: source: hosted version: "1.1.0" sdks: - dart: ">=3.9.0 <4.0.0" - flutter: ">=3.29.0" + dart: ">=3.6.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index ff8a43b..e659557 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,4 +1,4 @@ -name: example +name: ai_chat_example description: "AI Chat Assistant Example App" # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ^3.9.0 + sdk: ^3.5.0 # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions diff --git a/lib/main.dart b/lib/main.dart deleted file mode 100644 index 10b0e39..0000000 --- a/lib/main.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'app.dart'; -import 'package:provider/provider.dart'; -import 'services/message_service.dart'; - -void main() async { - WidgetsFlutterBinding.ensureInitialized(); - if (!await Permission.microphone.isGranted) { - await Permission.microphone.request(); - } - runApp( - ChangeNotifierProvider( - create: (_) => MessageService(), - child: const ChatAssistantApp(), - ), - ); -} diff --git a/lib/screens/part_screen.dart b/lib/screens/part_screen.dart index de4051b..cb44627 100644 --- a/lib/screens/part_screen.dart +++ b/lib/screens/part_screen.dart @@ -1,4 +1,3 @@ -import 'package:ai_chat_assistant/utils/assets_util.dart'; import 'package:flutter/material.dart'; import 'dart:ui'; import '../widgets/chat_box.dart'; @@ -127,18 +126,14 @@ class _PartScreenState extends State { children: [ Padding( padding: const EdgeInsets.only( - top: 50, - left: 6, - right: 6, - bottom: 30), + top: 50, left: 6, right: 6, bottom: 30), child: LayoutBuilder( - builder: (context, boxConstraints) { - return ChatBox( - scrollController: _scrollController, - messages: messageService.messages, - ); - } - ), + builder: (context, boxConstraints) { + return ChatBox( + scrollController: _scrollController, + messages: messageService.messages, + ); + }), ), Positioned( top: 6, diff --git a/lib/services/chat_sse_service.dart b/lib/services/chat_sse_service.dart index 493779f..c95680b 100644 --- a/lib/services/chat_sse_service.dart +++ b/lib/services/chat_sse_service.dart @@ -59,7 +59,8 @@ class ChatSseService { 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; @@ -76,14 +77,8 @@ class ChatSseService { } // 只在达到完整句子时调用 TtsUtil.send for (final s in sentences) { - String ttsStr=CommonUtil.cleanText(s, true)+"\n"; - - ttsStr = ttsStr.replaceAllMapped( - RegExp(r'(? ' ${m.group(1)} ', - ); - - print("发送数据到TTS: $ttsStr"); + String ttsStr=CommonUtil.cleanText(s, true); + // print("发送数据到TTS: $ttsStr"); TtsUtil.send(ttsStr); } // 缓存剩余不完整部分 diff --git a/lib/services/message_service.dart b/lib/services/message_service.dart index a31058f..da89c53 100644 --- a/lib/services/message_service.dart +++ b/lib/services/message_service.dart @@ -21,7 +21,7 @@ import 'command_service.dart'; import 'package:fluttertoast/fluttertoast.dart'; class MessageService extends ChangeNotifier { - static const MethodChannel _asrChannel = MethodChannel('com.example.ai_chat_assistant/asr'); + static const MethodChannel _asrChannel = MethodChannel('com.example.ai_chat_assistant/ali_sdk'); static final MessageService _instance = MessageService._internal(); @@ -61,7 +61,7 @@ class MessageService extends ChangeNotifier { // final AudioRecorderService _audioService = AudioRecorderService(); // final VoiceRecognitionService _recognitionService = VoiceRecognitionService(); final TextClassificationService _classificationService = - TextClassificationService(); + TextClassificationService(); final VehicleCommandService _vehicleCommandService = VehicleCommandService(); final List _messages = []; @@ -106,7 +106,6 @@ class MessageService extends ChangeNotifier { abortReply(); _latestUserMessageId = null; _latestAssistantMessageId = null; - _isReplyAborted = false; changeState(MessageServiceState.recording); _latestUserMessageId = addMessage("", true, MessageStatus.listening); _asrChannel.invokeMethod("startAsr"); @@ -138,6 +137,7 @@ class MessageService extends ChangeNotifier { return; } try { + _isReplyAborted = false; changeState(MessageServiceState.recognizing); _asrChannel.invokeMethod("stopAsr"); _asrCompleter = Completer(); @@ -227,7 +227,7 @@ class MessageService extends ChangeNotifier { return; } final vehicleCommandResponse = - await _vehicleCommandService.getCommandFromText(text); + await _vehicleCommandService.getCommandFromText(text); if (vehicleCommandResponse == null) { if (_isReplyAborted) { return; @@ -269,12 +269,12 @@ class MessageService extends ChangeNotifier { bool isSuccess; if (containOpenAC && command.type == VehicleCommandType.changeACTemp) { isSuccess = await Future.delayed(const Duration(milliseconds: 2000), - () => processCommand(command, isChinese)); + () => processCommand(command, isChinese)); } else { isSuccess = await processCommand(command, isChinese); } if (isSuccess) { - successCommandList.add(command.type.name); + successCommandList.add(isChinese ? command.type.chinese : command.type.english); } } replaceMessage( @@ -286,7 +286,7 @@ class MessageService extends ChangeNotifier { return; } String controlResponse = - await _vehicleCommandService.getControlResponse(successCommandList); + await _vehicleCommandService.getControlResponse(successCommandList); if (_isReplyAborted || controlResponse.isEmpty) { return; } diff --git a/lib/utils/tts_util.dart b/lib/utils/tts_util.dart index 165577a..86c6f62 100644 --- a/lib/utils/tts_util.dart +++ b/lib/utils/tts_util.dart @@ -2,7 +2,7 @@ import 'package:flutter/services.dart'; class TtsUtil { static const MethodChannel _channel = - MethodChannel('com.example.ai_chat_assistant/tts'); + MethodChannel('com.example.ai_chat_assistant/ali_sdk'); static Future execute(String method, [Map? arguments]) { diff --git a/lib/widgets/chat_bubble.dart b/lib/widgets/chat_bubble.dart index 95addd9..f7e027f 100644 --- a/lib/widgets/chat_bubble.dart +++ b/lib/widgets/chat_bubble.dart @@ -123,7 +123,12 @@ class _ChatBubbleState extends State { break; case MessageStatus.completed: case MessageStatus.success: - icon = Image.asset('assets/images/checked.png', width: 20, height: 20, package: 'ai_chat_assistant',); + icon = Image.asset( + 'assets/images/checked.png', + width: 20, + height: 20, + package: 'ai_chat_assistant', + ); color = Colors.white; break; case MessageStatus.failure: @@ -301,8 +306,10 @@ class _ChatBubbleState extends State { }, child: Padding( padding: const EdgeInsets.only(left: 12), - child: Image.asset('assets/images/copy.png',package: 'ai_chat_assistant', - width: 22, height: 22), + child: Image.asset('assets/images/copy.png', + package: 'ai_chat_assistant', + width: 22, + height: 22), ), ), InkWell( @@ -315,10 +322,14 @@ class _ChatBubbleState extends State { child: Padding( padding: const EdgeInsets.only(left: 12), child: _liked - ? Image.asset('assets/images/liked2.png',package: 'ai_chat_assistant', - width: 22, height: 22) - : Image.asset('assets/images/liked1.png',package: 'ai_chat_assistant', - width: 22, height: 22), + ? Image.asset('assets/images/liked2.png', + package: 'ai_chat_assistant', + width: 22, + height: 22) + : Image.asset('assets/images/liked1.png', + package: 'ai_chat_assistant', + width: 22, + height: 22), ), ), InkWell( @@ -331,10 +342,14 @@ class _ChatBubbleState extends State { child: Padding( padding: const EdgeInsets.only(left: 12), child: _disliked - ? Image.asset('assets/images/disliked2.png',package: 'ai_chat_assistant', - width: 22, height: 22) - : Image.asset('assets/images/disliked1.png',package: 'ai_chat_assistant', - width: 22, height: 22)), + ? Image.asset('assets/images/disliked2.png', + package: 'ai_chat_assistant', + width: 22, + height: 22) + : Image.asset('assets/images/disliked1.png', + package: 'ai_chat_assistant', + width: 22, + height: 22)), ), ], ), diff --git a/lib/widgets/chat_header.dart b/lib/widgets/chat_header.dart index a354244..d9ad52f 100644 --- a/lib/widgets/chat_header.dart +++ b/lib/widgets/chat_header.dart @@ -44,7 +44,7 @@ class ChatHeader extends StatelessWidget { ? '您的专属看、选、买、用车助手' : 'Your exclusive assistant', style: TextStyle( - fontSize: 12, + fontSize: 11, color: Colors.white70, ), ), diff --git a/packages/basic_intl/pubspec.lock b/packages/basic_intl/pubspec.lock index 6b97eb2..312a984 100644 --- a/packages/basic_intl/pubspec.lock +++ b/packages/basic_intl/pubspec.lock @@ -5,50 +5,50 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.flutter-io.cn" source: hosted - version: "2.13.0" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.2" + version: "2.1.1" characters: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.flutter-io.cn" source: hosted - version: "1.4.0" + version: "1.3.0" clock: dependency: transitive description: name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.2" + version: "1.1.1" collection: dependency: transitive description: name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.flutter-io.cn" source: hosted - version: "1.19.1" + version: "1.19.0" fake_async: dependency: transitive description: name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.3" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -63,34 +63,34 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.flutter-io.cn" source: hosted - version: "11.0.1" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.10" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.2" + version: "3.0.1" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.flutter-io.cn" source: hosted - version: "0.12.17" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: @@ -103,18 +103,18 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.flutter-io.cn" source: hosted - version: "1.16.0" + version: "1.15.0" path: dependency: transitive description: name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.flutter-io.cn" source: hosted - version: "1.9.1" + version: "1.9.0" sky_engine: dependency: transitive description: flutter @@ -124,66 +124,66 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.flutter-io.cn" source: hosted - version: "1.10.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.flutter-io.cn" source: hosted - version: "1.12.1" + version: "1.12.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.4" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.flutter-io.cn" source: hosted - version: "1.4.1" + version: "1.3.0" term_glyph: dependency: transitive description: name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.2" + version: "1.2.1" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.flutter-io.cn" source: hosted - version: "0.7.6" + version: "0.7.3" vector_math: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.0" + version: "2.1.4" vm_service: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.flutter-io.cn" source: hosted - version: "15.0.2" + version: "14.3.0" sdks: - dart: ">=3.8.0-0 <4.0.0" + dart: ">=3.5.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/packages/basic_intl/pubspec.yaml b/packages/basic_intl/pubspec.yaml index 520a608..8c9d942 100644 --- a/packages/basic_intl/pubspec.yaml +++ b/packages/basic_intl/pubspec.yaml @@ -3,7 +3,7 @@ description: Basic internationalization utilities for ai_chat_assistant version: 0.2.0 environment: - sdk: ^3.6.2 + sdk: ^3.5.0 dependencies: flutter: diff --git a/pubspec.lock b/pubspec.lock index 3de2e65..e5009f2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -84,18 +84,18 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.flutter-io.cn" source: hosted - version: "1.4.0" + version: "1.3.0" collection: dependency: transitive description: name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.flutter-io.cn" source: hosted - version: "1.19.1" + version: "1.19.0" crypto: dependency: transitive description: @@ -108,10 +108,10 @@ packages: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.4" + version: "2.1.3" file: dependency: transitive description: @@ -214,10 +214,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.flutter-io.cn" source: hosted - version: "1.16.0" + version: "1.15.0" nested: dependency: transitive description: @@ -246,18 +246,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.18" + version: "2.2.17" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.2" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -443,10 +443,10 @@ packages: dependency: transitive description: name: synchronized - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" url: "https://pub.flutter-io.cn" source: hosted - version: "3.4.0" + version: "3.3.0+3" term_glyph: dependency: transitive description: @@ -475,10 +475,10 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.0" + version: "2.1.4" web: dependency: transitive description: @@ -496,5 +496,5 @@ packages: source: hosted version: "1.1.0" sdks: - dart: ">=3.8.0 <4.0.0" - flutter: ">=3.29.0" + dart: ">=3.6.2 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index ca1a9a4..602e418 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "ai_chat_assistant" publish_to: 'none' version: 1.0.0+1 environment: - sdk: ^3.6.2 + sdk: ^3.5.0 dependencies: flutter: sdk: flutter