Browse Source

Merge commit '3a587c93cd646fee19c63353cae90291b8b67aef'

weicu
lxm 2 years ago
parent
commit
380f06a868
  1. 79
      .cursorrules
  2. 12
      .mcp.json
  3. 33
      amap_flutter/.gitignore
  4. 33
      amap_flutter/.metadata
  5. 3
      amap_flutter/CHANGELOG.md
  6. 1
      amap_flutter/LICENSE
  7. 15
      amap_flutter/README.md
  8. 4
      amap_flutter/analysis_options.yaml
  9. 9
      amap_flutter/android/.gitignore
  10. 54
      amap_flutter/android/build.gradle
  11. 1
      amap_flutter/android/settings.gradle
  12. 3
      amap_flutter/android/src/main/AndroidManifest.xml
  13. 168
      amap_flutter/android/src/main/java/com/example/amap_flutter/AmapFlutterPlugin.java
  14. 38
      amap_flutter/ios/.gitignore
  15. 0
      amap_flutter/ios/Assets/.gitkeep
  16. 19
      amap_flutter/ios/Classes/AmapFlutterPlugin.swift
  17. 14
      amap_flutter/ios/Resources/PrivacyInfo.xcprivacy
  18. 29
      amap_flutter/ios/amap_flutter.podspec
  19. 36
      amap_flutter/lib/amap_flutter.dart
  20. 45
      amap_flutter/lib/amap_flutter_method_channel.dart
  21. 49
      amap_flutter/lib/amap_flutter_platform_interface.dart
  22. 72
      amap_flutter/pubspec.yaml
  23. 45
      android/app/build.gradle.kts
  24. 38
      android/app/src/main/AndroidManifest.xml
  25. 654
      android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt
  26. 344
      android/app/src/main/kotlin/com/example/deep_voice/VolcanoAIService.kt
  27. 0
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/ClassicBluetoothHelper.kt
  28. 382
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt
  29. 415
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionHandler.kt
  30. 383
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionService.kt
  31. 0
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/core/utils/FileLogger.kt
  32. 19
      android/build.gradle.kts
  33. 20
      android/settings.gradle.kts
  34. 21
      azure/LICENSE
  35. 66
      azure/README.md
  36. 757
      azure/ios/Classes/AzureAsrHelper.swift
  37. 18
      azure/ios/Classes/AzureSpeechRecognitionPlugin.swift
  38. 427
      azure/ios/Classes/AzureTtsHelper.swift
  39. 259
      azure/ios/Classes/SwiftAzureSpeechRecognitionPlugin.swift
  40. 24
      azure/ios/azure_speech_recognition.podspec
  41. 6
      azure/lib/azure_speech_recognition.dart
  42. 23
      azure/pubspec.yaml
  43. 14
      lib/core/bindings/initial_binding.dart
  44. 91
      lib/core/translations/language/de_de.dart
  45. 73
      lib/core/translations/language/en_us.dart
  46. 93
      lib/core/translations/language/es_es.dart
  47. 99
      lib/core/translations/language/fr_fr.dart
  48. 27
      lib/core/translations/language/it_it.dart
  49. 50
      lib/core/translations/language/ja_jp.dart
  50. 48
      lib/core/translations/language/ko_kr.dart
  51. 90
      lib/core/translations/language/pt_pt.dart
  52. 86
      lib/core/translations/language/ru_ru.dart
  53. 69
      lib/core/translations/language/vi_vn.dart
  54. 54
      lib/core/translations/language/zh_cn.dart
  55. 42
      lib/core/translations/language/zh_hk.dart
  56. 89
      lib/core/utils/jieli_constants.dart
  57. 2
      lib/core/widgets/message_bubble.dart
  58. 13
      lib/data/models/events/voice_interaction_event.dart
  59. 30
      lib/data/providers/agent_provider.dart
  60. 4
      lib/data/services/ai_service.dart
  61. 105
      lib/data/services/chat_history_service.dart
  62. 13
      lib/data/services/deapsound_ai_service.dart
  63. 97
      lib/data/services/function_service.dart
  64. 9
      lib/data/services/network/api.dart
  65. 2
      lib/data/services/network/nw_api.dart
  66. 278
      lib/data/services/open_ai_service_adapter.dart
  67. 4
      lib/data/services/speech_impl/azure_asr_service.dart
  68. 2
      lib/data/services/speech_impl/azure_tts_service.dart
  69. 15
      lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart
  70. 8
      lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart
  71. 85
      lib/data/services/voice_interaction_service.dart
  72. 9
      lib/modules/chat/bindings/chat_binding.dart
  73. 368
      lib/modules/chat/controllers/chat_controller.dart
  74. 28
      lib/modules/chat/models/message_model.dart
  75. 107
      lib/modules/chat/views/chat_music_card.dart
  76. 206
      lib/modules/chat/views/chat_navigation_card.dart
  77. 60
      lib/modules/chat/views/chat_view.dart
  78. 94
      lib/modules/chat/views/weather_day_card.dart
  79. 9
      lib/modules/feedback/bindings/feedback_bindings.dart
  80. 59
      lib/modules/feedback/controllers/feedback_controller.dart
  81. 203
      lib/modules/feedback/views/feedback_view.dart
  82. 11
      lib/modules/jieli_test/bindings/jieli_test_binding.dart
  83. 323
      lib/modules/jieli_test/controllers/jieli_test_controller.dart
  84. 568
      lib/modules/jieli_test/views/jieli_test_view.dart
  85. 109
      lib/modules/login/controllers/login_controller.dart
  86. 56
      lib/modules/login/views/login_view.dart
  87. 20
      lib/modules/meeting/controllers/meeting_record_controller.dart
  88. 19
      lib/modules/meeting/views/meeting_details_view.dart
  89. 15
      lib/modules/meeting/views/meeting_edit_view.dart
  90. 15
      lib/modules/meeting/views/meeting_record_view.dart
  91. 4
      lib/modules/pairing/controllers/pairing_controller.dart
  92. 45
      lib/modules/profile/controllers/profile_controller.dart
  93. 80
      lib/modules/settings/views/settings_view.dart
  94. 7
      lib/modules/splash/controllers/splash_controller.dart
  95. 14
      lib/routes/app_pages.dart
  96. 2
      lib/routes/app_routes.dart
  97. 0
      local_plugins/azure_speech/LICENSE
  98. 136
      local_plugins/azure_speech/README.md
  99. 56
      local_plugins/azure_speech/android/build.gradle.kts
  100. 1
      local_plugins/azure_speech/android/settings.gradle.kts

79
.cursorrules

@ -1,10 +1,73 @@
1. 不要主动运行程序, 如flutter run 1. 请严格遵循 Effective Dart 的风格指南来编写代码
2. 不要改动android, iOS项目的配置信息,包括SDK, NDK版本等 2. 不要主动运行程序, 如flutter run
3. 不要使用print, 而是使用logger 3. 不要改动android, iOS项目的配置信息,包括SDK, NDK, JAVA版本等
4. 尽可能使用GetX提供的各种功能, 而不是使用原生的StatefulWidget 4. 不要使用print, 而是使用logger, 尽可能避免打印日志,仅在出错时记录日志
5. 不要增加重试逻辑,出现失败直接返回错误 5. 尽可能使用GetX提供的各种功能, 而不是使用原生的StatefulWidget
6. 尽可能避免使用try catch 6. 不要增加重试逻辑,出现失败直接返回错误
7. 尽可能避免打印日志,仅在出错时记录日志 7. 尽可能避免使用try catch
8. 请使用较小的代码量改动,最安全的方法进行代码编写和优化 8. 请使用较小的代码量改动,最安全的方法进行代码编写和优化
9. 请尽可能使用中文回复问题 9. 请尽可能使用中文回复问题
10. 重要:不要过度设计!! 10. 重要:不要过度设计!!
11. 本项目使用 Kotlin DSL 进行开发
# 项目架构概述
DeepSound是一个基于Flutter和GetX构建的多平台语音应用,具有语音合成、语音识别、语音聊天等功能。
## 总体架构
- 采用GetX状态管理框架
- 遵循模块化设计,各功能模块相对独立
- 使用依赖注入实现组件解耦
- 多语言支持,使用GetX国际化方案
## 主要目录结构
- lib/core: 核心组件、工具和通用功能
- lib/data: 数据模型、服务和提供者
- lib/modules: 功能模块,按业务划分
- lib/routes: 路由定义和管理
- local_plugins: 本地插件,提供原生功能支持
# 核心模块说明
## 语音处理
- 火山语音服务: 提供语音识别和合成核心能力
- Azure语音服务: 提供备选的语音识别和合成能力
- 离线TTS支持: 在线语音合成失败时的备选方案
## 聊天功能
- 基于OpenAI的对话功能
- 支持语音输入、文字输入和语音输出
- 支持多种角色和语音类型
## 用户系统
- 支持多种登录方式:Firebase、Google、Facebook
- 用户资料管理和设置
- 主题切换和语言设置
## 蓝牙配对
- 支持蓝牙设备发现和连接
- 提供简化的配对流程
- 设备管理和状态监控
## 音频处理
- 音频录制和播放
- 支持波形显示
- 音量控制和音频质量设置
# 技术栈
- Flutter: UI框架
- GetX: 状态管理和依赖注入
- Firebase: 用户认证和后端服务
- WebSocket: 实时通信
- SQLite: 本地数据存储
- 原生插件: 语音识别、合成和蓝牙功能
# 插件说明
- jieli: 杰理蓝牙设备连接和控制插件
- volcano_speech: 火山语音服务插件,提供语音识别和合成
- azure_speech: 微软Azure语音服务插件
- open_ai_service: OpenAI服务插件,提供AI对话能力
- speech_to_text: 通用语音识别插件
# 环境配置
- 环境变量通过.env文件管理
- 支持不同环境的配置切换
- 敏感信息不应直接硬编码在代码中

12
.mcp.json

@ -0,0 +1,12 @@
{
"mcpServers": {
"amap-amap-sse": {
"url": "https://mcp.amap.com/sse?key=e5fdc9605eabdeb5626f18f5721f343d"
},
"web-search": {
"url": "http://mcp.ideapsound.com:8000/sse"
}
}
}

33
amap_flutter/.gitignore

@ -0,0 +1,33 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
build/

33
amap_flutter/.metadata

@ -0,0 +1,33 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "35c388afb57ef061d06a39b537336c87e0e3d1b1"
channel: "stable"
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
- platform: android
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
- platform: ios
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

3
amap_flutter/CHANGELOG.md

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

1
amap_flutter/LICENSE

@ -0,0 +1 @@
TODO: Add your license here.

15
amap_flutter/README.md

@ -0,0 +1,15 @@
# amap_flutter
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/to/develop-plugins),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

4
amap_flutter/analysis_options.yaml

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

9
amap_flutter/android/.gitignore

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

54
amap_flutter/android/build.gradle

@ -0,0 +1,54 @@
group = "com.example.amap_flutter"
version = "1.0"
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.7.0")
}
}
rootProject.allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: "com.android.library"
android {
namespace = "com.example.amap_flutter"
compileSdk = 35
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
defaultConfig {
minSdk = 21
}
dependencies {
implementation 'com.amap.api:3dmap-location-search:latest.integration'
testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito:mockito-core:5.0.0")
}
testOptions {
unitTests.all {
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
}
}

1
amap_flutter/android/settings.gradle

@ -0,0 +1 @@
rootProject.name = 'amap_flutter'

3
amap_flutter/android/src/main/AndroidManifest.xml

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.amap_flutter">
</manifest>

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

@ -0,0 +1,168 @@
package com.example.amap_flutter;
import android.content.Context;
import androidx.annotation.NonNull;
import android.util.Log;
import com.amap.api.location.AMapLocation;
import com.amap.api.location.AMapLocationClient;
import com.amap.api.location.AMapLocationClientOption;
import com.amap.api.location.AMapLocationListener;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import java.util.HashMap;
import java.util.Map;
/** AmapFlutterPlugin */
public class AmapFlutterPlugin implements FlutterPlugin, MethodCallHandler {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private MethodChannel methodChannel;
private EventChannel eventChannel;
private Context applicationContext;
private AMapLocationClient mLocationClient;
private EventChannel.EventSink eventSink;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
applicationContext = flutterPluginBinding.getApplicationContext();
methodChannel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "amap_flutter");
methodChannel.setMethodCallHandler(this);
// 初始化EventChannel
eventChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "amap_flutter/events");
eventChannel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
eventSink = events;
}
@Override
public void onCancel(Object arguments) {
eventSink = null;
}
});
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
switch (call.method) {
case "setApiKey":
setApiKey(call, result);
break;
case "initLocation":
initLocation(result);
break;
case "startLocation":
startLocation(result);
break;
case "stopLocation":
stopLocation(result);
break;
case "dispose":
dispose(result);
break;
default:
result.notImplemented();
}
}
private void setApiKey(MethodCall call, Result result) {
try {
String apiKey = call.argument("androidKey");
AMapLocationClient.updatePrivacyShow(applicationContext, true, true);
AMapLocationClient.updatePrivacyAgree(applicationContext, true);
result.success(null);
} catch (Exception e) {
result.error("INIT_ERROR", "初始化失败", e.getMessage());
}
}
private void initLocation(Result result) {
try {
mLocationClient = new AMapLocationClient(applicationContext);
// 配置定位参数
AMapLocationClientOption option = new AMapLocationClientOption();
option.setOnceLocation(true);
option.setNeedAddress(true);
mLocationClient.setLocationOption(option);
mLocationClient.setLocationListener(mLocationListener);
result.success(null);
} catch (Exception e) {
result.error("INIT_ERROR", "初始化失败", e.getMessage());
}
}
private void startLocation(Result result) {
mLocationClient.startLocation();
result.success(null);
}
private void stopLocation(Result result) {
destroyLocationClient();
result.success(null);
}
private void dispose(Result result) {
try {
destroyLocationClient();
result.success(null);
} catch (Exception e) {
result.error("DISPOSE_ERROR", "资源释放失败", e.getMessage());
}
}
private void destroyLocationClient() {
if (mLocationClient != null) {
try {
// 停止定位
mLocationClient.stopLocation();
// 销毁定位客户端
mLocationClient.onDestroy();
mLocationClient = null;
// 关闭事件通道
if (eventSink != null) {
eventSink.endOfStream();
eventSink = null;
}
} catch (Exception e) {
Log.e("AmapFlutter", "销毁定位客户端失败", e);
}
}
}
AMapLocationListener mLocationListener = new AMapLocationListener() {
@Override
public void onLocationChanged(AMapLocation amapLocation) {
if (eventSink == null) return;
if (amapLocation != null) {
if (amapLocation.getErrorCode() == 0) {
Map<String, Object> locationData = new HashMap<>();
locationData.put("latitude", amapLocation.getLatitude());
locationData.put("longitude", amapLocation.getLongitude());
locationData.put("address", amapLocation.getAddress());
eventSink.success(locationData);
}
}
}
};
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
methodChannel.setMethodCallHandler(null);
eventChannel.setStreamHandler(null);
destroyLocationClient();
}
}

38
amap_flutter/ios/.gitignore

@ -0,0 +1,38 @@
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/ephemeral/
/Flutter/flutter_export_environment.sh

0
amap_flutter/ios/Assets/.gitkeep

19
amap_flutter/ios/Classes/AmapFlutterPlugin.swift

@ -0,0 +1,19 @@
import Flutter
import UIKit
public class AmapFlutterPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "amap_flutter", binaryMessenger: registrar.messenger())
let instance = AmapFlutterPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
default:
result(FlutterMethodNotImplemented)
}
}
}

14
amap_flutter/ios/Resources/PrivacyInfo.xcprivacy

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

29
amap_flutter/ios/amap_flutter.podspec

@ -0,0 +1,29 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint amap_flutter.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'amap_flutter'
s.version = '0.0.1'
s.summary = 'A new Flutter project.'
s.description = <<-DESC
A new Flutter project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.platform = :ios, '12.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
# If your plugin requires a privacy manifest, for example if it uses any
# required reason APIs, update the PrivacyInfo.xcprivacy file to describe your
# plugin's privacy impact, and then uncomment this line. For more information,
# see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
# s.resource_bundles = {'amap_flutter_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
end

36
amap_flutter/lib/amap_flutter.dart

@ -0,0 +1,36 @@
import 'amap_flutter_platform_interface.dart';
class AmapFlutter {
// 初始化SDK
Future<void> initSdk({String? iosKey, String? androidKey}) async {
await AmapFlutterPlatform.instance.setApiKey(
iosKey: iosKey,
androidKey: androidKey,
);
}
// 定位初始化
Future<void> initLocation() async {
return AmapFlutterPlatform.instance.initLocation();
}
// 开始定位
Future<void> startLocation() async {
return AmapFlutterPlatform.instance.startLocation();
}
// 监听定位返回位置
Stream<dynamic> locationStream() {
return AmapFlutterPlatform.instance.locationStream();
}
// 停止定位
Future<void> stopLocation() async {
await AmapFlutterPlatform.instance.stopLocation();
}
// 销毁
Future<void> dispose() async {
await AmapFlutterPlatform.instance.dispose();
}
}

45
amap_flutter/lib/amap_flutter_method_channel.dart

@ -0,0 +1,45 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'amap_flutter_platform_interface.dart';
/// An implementation of [AmapFlutterPlatform] that uses method channels.
class MethodChannelAmapFlutter extends AmapFlutterPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('amap_flutter');
final eventChannel = const EventChannel('amap_flutter/events');
@override
Future<void> setApiKey({String? iosKey, String? androidKey}) async {
await methodChannel.invokeMethod('setApiKey', {
"iosKey": iosKey,
"androidKey": androidKey,
});
}
@override
Future<void> initLocation() async {
return await methodChannel.invokeMethod('initLocation');
}
@override
Future<void> startLocation() async {
return await methodChannel.invokeMethod('startLocation');
}
@override
Stream<dynamic> locationStream() {
return eventChannel.receiveBroadcastStream();
}
@override
Future<void> stopLocation() async {
await methodChannel.invokeMethod('stopLocation');
}
@override
Future<void> dispose() async {
await methodChannel.invokeMethod('dispose');
}
}

49
amap_flutter/lib/amap_flutter_platform_interface.dart

@ -0,0 +1,49 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'amap_flutter_method_channel.dart';
abstract class AmapFlutterPlatform extends PlatformInterface {
/// Constructs a AmapFlutterPlatform.
AmapFlutterPlatform() : super(token: _token);
static final Object _token = Object();
static AmapFlutterPlatform _instance = MethodChannelAmapFlutter();
/// The default instance of [AmapFlutterPlatform] to use.
///
/// Defaults to [MethodChannelAmapFlutter].
static AmapFlutterPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [AmapFlutterPlatform] when
/// they register themselves.
static set instance(AmapFlutterPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<void> setApiKey({String? iosKey, String? androidKey}) {
throw UnimplementedError('init() has not been implemented.');
}
Future<void> initLocation() {
throw UnimplementedError('initLocation() has not been implemented.');
}
Future<void> startLocation() {
throw UnimplementedError('startLocation() has not been implemented.');
}
Stream<dynamic> locationStream() {
throw UnimplementedError('locationStream() has not been implemented.');
}
Future<void> stopLocation() {
throw UnimplementedError('stopLocation() has not been implemented.');
}
Future<void> dispose() {
throw UnimplementedError('dispose() has not been implemented.');
}
}

72
amap_flutter/pubspec.yaml

@ -0,0 +1,72 @@
name: amap_flutter
description: "A new Flutter project."
version: 0.0.1
homepage:
environment:
sdk: ^3.7.0
flutter: '>=3.3.0'
dependencies:
flutter:
sdk: flutter
plugin_platform_interface: ^2.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
# which should be registered in the plugin registry. This is required for
# using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
# The 'ffiPlugin' specifies that native code should be built and bundled.
# This is required for using `dart:ffi`.
# All these are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
platforms:
android:
package: com.example.amap_flutter
pluginClass: AmapFlutterPlugin
ios:
pluginClass: AmapFlutterPlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/to/font-from-package

45
android/app/build.gradle.kts

@ -3,6 +3,9 @@ import java.io.FileInputStream
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
flatDir {
dirs(project(":spotify_sdk").projectDir.resolve("libs"))
}
} }
plugins { plugins {
@ -34,7 +37,32 @@ android {
jvmTarget = JavaVersion.VERSION_11.toString() jvmTarget = JavaVersion.VERSION_11.toString()
} }
// 添加lint选项
lintOptions {
isCheckReleaseBuilds = false
}
// 添加packaging配置,排除冲突文件
packaging {
resources {
excludes.add("META-INF/DEPENDENCIES")
excludes.add("META-INF/LICENSE")
excludes.add("META-INF/LICENSE.txt")
excludes.add("META-INF/license.txt")
excludes.add("META-INF/NOTICE")
excludes.add("META-INF/NOTICE.txt")
excludes.add("META-INF/notice.txt")
excludes.add("META-INF/*.kotlin_module")
}
}
defaultConfig { defaultConfig {
manifestPlaceholders.putAll(
mapOf(
"redirectSchemeName" to "spotify-sdk",
"redirectHostName" to "auth"
)
)
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.yunqiinnovation.deepsound" applicationId = "com.yunqiinnovation.deepsound"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
@ -82,14 +110,22 @@ android {
dependencies { dependencies {
implementation("io.modelcontextprotocol:kotlin-sdk:0.4.0")
// 添加本地插件模块依赖
implementation(project(":azure_speech"))
implementation(project(":open_ai_service"))
implementation(project(":volcano_speech"))
implementation(project(":chat_storage")) // 添加新创建的SQLite聊天存储插件
implementation(project(":jieli")) // 添加新创建的SQLite聊天存储插件
implementation(project(":spotify_sdk"))
// 添加OkHttp依赖 // 添加OkHttp依赖
implementation("com.squareup.okhttp3:okhttp:4.9.3") implementation("com.squareup.okhttp3:okhttp:4.9.3")
// 添加核心库反糖化 // 添加核心库反糖化
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
// Microsoft 语音识别SDK
implementation("com.microsoft.cognitiveservices.speech:client-sdk:1.42.0")
// 添加androidx.media依赖 // 添加androidx.media依赖
implementation("androidx.media:media:1.6.0") implementation("androidx.media:media:1.6.0")
@ -100,11 +136,6 @@ dependencies {
// 添加 AndroidX Security 加密 SharedPreferences 依赖 // 添加 AndroidX Security 加密 SharedPreferences 依赖
implementation("androidx.security:security-crypto:1.1.0-alpha06") implementation("androidx.security:security-crypto:1.1.0-alpha06")
// // 添加火山语音合成SDK依赖
// implementation("com.bytedance.speechengine:speechengine_tts_tob:5.4.8")
// 添加火山语音识别SDK依赖
// implementation("com.bytedance.speechengine:speechengine_tob:0.0.5")
} }
flutter { flutter {

38
android/app/src/main/AndroidManifest.xml

@ -27,9 +27,30 @@
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<!-- 添加悬浮窗权限,可能有助于解决前台服务启动问题 --> <!-- 添加悬浮窗权限,可能有助于解决前台服务启动问题 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 添加发送短信权限 -->
<uses-permission android:name="android.permission.SEND_SMS" />
<!-- 添加读取联系人权限 -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- 添加拨打电话权限 -->
<uses-permission android:name="android.permission.CALL_PHONE" />
<!-- 添加查询应用包权限 -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<!--允许获取网络状态,用于网络定位(无gps情况下的定位),若需网络定位功能则必选-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--允许获取wifi网络信息,用于网络定位(无gps情况下的定位),若需网络定位功能则必选-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--允许获取wifi状态改变,用于网络定位(无gps情况下的定位),若需网络定位功能则必选-->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!--后台获取位置信息,若需后台定位则必选-->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!--用于申请调用A-GPS模块,卫星定位加速-->
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<!--允许写设备缓存,用于问题排查-->
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<application <application
android:label="DeepSound" android:label="DeapSound"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
@ -38,7 +59,7 @@
android:enableOnBackInvokedCallback="true" android:enableOnBackInvokedCallback="true"
tools:replace="android:label"> tools:replace="android:label">
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/> <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
<meta-data android:name="com.facebook.sdk.ClientToken" android:value="@string/facebook_client_token"/> <meta-data android:name="com.facebook.sdk.ClientToken" android:value="@string/facebook_client_token"/>
<!-- 添加语音交互服务 --> <!-- 添加语音交互服务 -->
@ -84,12 +105,25 @@
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="yqcxdeepsound"/>
</intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="e5fdc9605eabdeb5626f18f5721f343d"/>
<service android:name="com.amap.api.location.APSService"></service>
</application> </application>
<!-- Required to query activities that can process text, see: <!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and https://developer.android.com/training/package-visibility and

654
android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt

@ -1,654 +0,0 @@
package com.yunqiinnovation.deepsound
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.media.audiofx.AcousticEchoCanceler
import android.media.audiofx.NoiseSuppressor
import android.media.audiofx.AutomaticGainControl
import android.os.Process
import com.yunqiinnovation.deepsound.core.utils.FileLogger
import com.microsoft.cognitiveservices.speech.*
import com.microsoft.cognitiveservices.speech.audio.*
import com.microsoft.cognitiveservices.speech.util.EventHandler
import java.util.concurrent.ExecutionException
import java.util.concurrent.atomic.AtomicBoolean
// 移除WebRTC相关导入
class AzureAsrHelper(private val context: Context) {
private var recognizer: SpeechRecognizer? = null
private var speechConfig: SpeechConfig? = null
private val TAG = "AzureAsrHelper"
private var isContinuousRecognitionActive = false
private var currentLanguage = "zh-CN"
private var subscriptionKey = ""
private var serviceRegion = ""
private var isAutoDetectLanguage = false
private var supportedLanguages = arrayOf("zh-CN", "en-US")
// 是否使用回音消除 - 内部控制常量
private val useEchoCancellation = true
// 自定义音频处理相关
private var customAudioProcessor: CustomAudioProcessor? = null
private var pushStream: PushAudioInputStream? = null
private var audioConfig: AudioConfig? = null
// 初始化SDK并创建recognizer
fun initialize(subscriptionKey: String, serviceRegion: String,
supportedLanguages: Array<String> = arrayOf("zh-CN", "en-US")): Boolean {
try {
FileLogger.d(TAG, "初始化 Azure 语音服务")
// 检查配置是否为空
if (subscriptionKey.isEmpty() || serviceRegion.isEmpty()) {
FileLogger.e(TAG, "Azure 配置信息不完整")
return false
}
// 释放之前的资源
dispose()
this.subscriptionKey = subscriptionKey
this.serviceRegion = serviceRegion
// 设置语言
if (supportedLanguages.isNotEmpty()) {
this.supportedLanguages = supportedLanguages
}
// 根据支持的语言数量决定是否启用自动语言检测
this.isAutoDetectLanguage = supportedLanguages.size >= 2
// 如果只有一种语言,设置为当前语言
if (!isAutoDetectLanguage && supportedLanguages.isNotEmpty()) {
this.currentLanguage = supportedLanguages[0]
}
// 创建语音配置
speechConfig = SpeechConfig.fromSubscription(subscriptionKey, serviceRegion)
// 设置语言配置
if (isAutoDetectLanguage) {
// 设置自动语言检测
speechConfig?.setProperty(PropertyId.SpeechServiceConnection_LanguageIdMode, "Continuous")
} else {
// 设置指定的识别语言
speechConfig?.speechRecognitionLanguage = currentLanguage
}
// 创建识别器
try {
if (useEchoCancellation) {
// 如果使用回音消除,创建自定义音频输入流
setupCustomAudioProcessing()
if (isAutoDetectLanguage) {
val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
recognizer = SpeechRecognizer(speechConfig, autoDetectConfig, audioConfig)
} else {
recognizer = SpeechRecognizer(speechConfig, audioConfig)
}
} else {
// 使用默认麦克风输入
if (isAutoDetectLanguage) {
val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
recognizer = SpeechRecognizer(speechConfig, autoDetectConfig)
} else {
recognizer = SpeechRecognizer(speechConfig)
}
}
FileLogger.d(TAG, "Azure 语音服务初始化成功")
return true
} catch (e: Exception) {
FileLogger.e(TAG, "创建识别器失败: ${e.message}")
stopCustomAudioProcessing()
return false
}
} catch (e: Exception) {
FileLogger.e(TAG, "初始化失败: ${e.message}")
return false
}
}
// 重置 recognizer
private fun resetRecognizer(): Boolean {
try {
// 释放之前的 recognizer
recognizer?.close()
recognizer = null
// 停止当前的音频处理
stopCustomAudioProcessing()
// 使用现有配置重新创建 recognizer
if (speechConfig != null) {
if (useEchoCancellation) {
// 如果使用回音消除,创建自定义音频输入流
setupCustomAudioProcessing()
if (isAutoDetectLanguage) {
val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
recognizer = SpeechRecognizer(speechConfig, autoDetectConfig, audioConfig)
} else {
recognizer = SpeechRecognizer(speechConfig, audioConfig)
}
} else {
// 使用默认麦克风输入
if (isAutoDetectLanguage) {
val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
recognizer = SpeechRecognizer(speechConfig, autoDetectConfig)
} else {
recognizer = SpeechRecognizer(speechConfig)
}
}
return true
} else {
FileLogger.e(TAG, "语音配置未初始化")
return false
}
} catch (e: Exception) {
FileLogger.e(TAG, "重置识别器失败: ${e.message}")
return false
}
}
// 开始一次性语音识别
fun recognizeOnce(callback: RecognizeCallback) {
if (speechConfig == null) {
callback.onError("语音服务未初始化")
return
}
// 重置 recognizer
if (!resetRecognizer()) {
callback.onError("重置识别器失败")
return
}
try {
// 启动音频处理
startCustomAudioProcessing()
// 执行识别
val result = recognizer?.recognizeOnceAsync()?.get()
// 停止音频处理
stopCustomAudioProcessing()
if (result != null && result.reason == ResultReason.RecognizedSpeech) {
val detectedLanguage = AutoDetectSourceLanguageResult.fromResult(result)?.language
callback.onResult(result.text, detectedLanguage ?: "")
} else {
callback.onError("未能识别语音")
}
} catch (e: Exception) {
// 停止音频处理
stopCustomAudioProcessing()
callback.onError("识别异常: ${e.message}")
}
}
// 开始连续语音识别
fun startContinuousRecognition(callback: ContinuousRecognizeCallback): Boolean {
if (speechConfig == null) {
callback.onError("语音服务未初始化")
return false
}
// 如果已经在进行连续识别,先停止
if (isContinuousRecognitionActive) {
stopContinuousRecognition(callback)
}
// 重置 recognizer
if (!resetRecognizer()) {
callback.onError("重置识别器失败")
return false
}
try {
// 启动音频处理
startCustomAudioProcessing()
// 设置识别事件处理
// 最终识别结果
recognizer?.recognized?.addEventListener(
EventHandler<SpeechRecognitionEventArgs> { _, event ->
if (event.result.reason == ResultReason.RecognizedSpeech) {
val detectedLanguage = if (isAutoDetectLanguage) {
AutoDetectSourceLanguageResult.fromResult(event.result)?.language ?: ""
} else {
currentLanguage
}
// FileLogger.d(TAG, "最终识别结果: ${event.result.text}")
callback.onResult(event.result.text, detectedLanguage)
}
}
)
// 识别中事件
recognizer?.recognizing?.addEventListener(
EventHandler<SpeechRecognitionEventArgs> { _, event ->
if (event.result.reason == ResultReason.RecognizingSpeech) {
val detectedLanguage = if (isAutoDetectLanguage) {
AutoDetectSourceLanguageResult.fromResult(event.result)?.language ?: ""
} else {
currentLanguage
}
// FileLogger.d(TAG, "识别中结果: ${event.result.text}")
callback.onRecognizing(event.result.text, detectedLanguage)
}
}
)
// 会话事件
recognizer?.sessionStarted?.addEventListener(
EventHandler<SessionEventArgs> { _, _ ->
isContinuousRecognitionActive = true
callback.onSessionStarted()
}
)
recognizer?.sessionStopped?.addEventListener(
EventHandler<SessionEventArgs> { _, _ ->
isContinuousRecognitionActive = false
callback.onSessionStopped()
}
)
// 取消事件
recognizer?.canceled?.addEventListener(
EventHandler<SpeechRecognitionCanceledEventArgs> { _, event ->
val errorDetails = if (event.reason == CancellationReason.Error) event.errorDetails else ""
callback.onCanceled(event.reason.toString(), errorDetails)
isContinuousRecognitionActive = false
}
)
// 开始连续识别
recognizer?.startContinuousRecognitionAsync()?.get()
isContinuousRecognitionActive = true
return true
} catch (e: Exception) {
callback.onError("开始连续识别失败: ${e.message}")
isContinuousRecognitionActive = false
stopCustomAudioProcessing()
return false
}
}
// 停止连续语音识别
fun stopContinuousRecognition(callback: ContinuousRecognizeCallback): Boolean {
if (!isContinuousRecognitionActive || recognizer == null) {
return true
}
try {
recognizer?.stopContinuousRecognitionAsync()
isContinuousRecognitionActive = false
callback.onSessionStopped()
// 停止音频处理
stopCustomAudioProcessing()
return true
} catch (e: Exception) {
callback.onError("停止连续识别失败: ${e.message}")
isContinuousRecognitionActive = false
stopCustomAudioProcessing()
return false
}
}
// 检查连续识别是否活跃
fun isContinuousRecognitionActive(): Boolean {
return isContinuousRecognitionActive
}
// 设置自定义音频处理
private fun setupCustomAudioProcessing() {
if (!useEchoCancellation) {
return
}
try {
// 1. 创建PushAudioInputStream
pushStream = PushAudioInputStream.create()
// 2. 创建AudioConfig
audioConfig = AudioConfig.fromStreamInput(pushStream)
// 3. 创建自定义音频处理器
customAudioProcessor = CustomAudioProcessor(pushStream)
FileLogger.d(TAG, "自定义音频处理设置完成")
} catch (e: Exception) {
FileLogger.e(TAG, "设置自定义音频处理失败: ${e.message}")
releaseCustomAudioProcessing()
// 降级处理:如果自定义处理设置失败,尝试使用默认麦克风
try {
FileLogger.d(TAG, "尝试降级到默认麦克风输入")
audioConfig = AudioConfig.fromDefaultMicrophoneInput()
} catch (e2: Exception) {
FileLogger.e(TAG, "默认麦克风输入设置也失败: ${e2.message}")
audioConfig = null
}
}
}
// 启动自定义音频处理
private fun startCustomAudioProcessing() {
if (!useEchoCancellation || customAudioProcessor == null) {
return
}
try {
customAudioProcessor?.startRecording()
FileLogger.d(TAG, "自定义音频处理已启动")
} catch (e: Exception) {
FileLogger.e(TAG, "启动自定义音频处理失败: ${e.message}")
}
}
// 停止自定义音频处理
private fun stopCustomAudioProcessing() {
if (!useEchoCancellation || customAudioProcessor == null) {
return
}
try {
customAudioProcessor?.stopRecording()
FileLogger.d(TAG, "自定义音频处理已停止")
} catch (e: Exception) {
FileLogger.e(TAG, "停止自定义音频处理失败: ${e.message}")
}
}
// 释放自定义音频处理资源
private fun releaseCustomAudioProcessing() {
stopCustomAudioProcessing()
try {
customAudioProcessor = null
pushStream?.close()
pushStream = null
audioConfig?.close()
audioConfig = null
FileLogger.d(TAG, "自定义音频处理资源已释放")
} catch (e: Exception) {
FileLogger.e(TAG, "释放自定义音频处理资源时出错: ${e.message}")
}
}
// 释放所有资源
fun dispose() {
try {
// 停止和释放音频处理
releaseCustomAudioProcessing()
recognizer?.close()
recognizer = null
speechConfig?.close()
speechConfig = null
isContinuousRecognitionActive = false
FileLogger.d(TAG, "语音识别资源已释放")
} catch (e: Exception) {
FileLogger.e(TAG, "释放资源出错: ${e.message}")
}
}
// 自定义音频处理器 - 使用Android原生回音消除
private inner class CustomAudioProcessor(private val pushStream: PushAudioInputStream?) {
private val SAMPLE_RATE = 16000
private val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
private val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
private val BUFFER_SIZE = SAMPLE_RATE * 2 // 简化缓冲区大小计算,更稳定
private var audioRecord: AudioRecord? = null
private var echoCanceler: AcousticEchoCanceler? = null
private val isRecording = AtomicBoolean(false)
private var recordingThread: Thread? = null
// 启动录音并处理音频数据
fun startRecording() {
if (isRecording.get() || pushStream == null) {
return
}
try {
// 使用Builder模式构建AudioFormat
val audioFormat = AudioFormat.Builder()
.setSampleRate(SAMPLE_RATE)
.setEncoding(AUDIO_FORMAT)
.setChannelMask(CHANNEL_CONFIG)
.build()
// 使用Builder模式创建AudioRecord实例
audioRecord = AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
.setAudioFormat(audioFormat)
.setBufferSizeInBytes(BUFFER_SIZE)
.build()
// 检查AudioRecord初始化状态
if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) {
FileLogger.e(TAG, "AudioRecord初始化失败,状态: ${audioRecord?.state}")
// 尝试使用DEFAULT音频源重试一次
audioRecord?.release()
audioRecord = AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.DEFAULT)
.setAudioFormat(audioFormat)
.setBufferSizeInBytes(BUFFER_SIZE)
.build()
if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) {
FileLogger.e(TAG, "AudioRecord初始化第二次尝试也失败,放弃")
releaseAudioResources()
return
} else {
FileLogger.d(TAG, "使用默认音频源成功初始化AudioRecord")
}
}
// 启用音频效果(回音消除、噪声抑制等)
enableAudioEffects()
// 启动录音
audioRecord?.startRecording()
isRecording.set(true)
// 创建录音线程
recordingThread = Thread({
val buffer = ByteArray(BUFFER_SIZE)
while (isRecording.get()) {
try {
val readSize = audioRecord?.read(buffer, 0, BUFFER_SIZE) ?: 0
if (readSize > 0) {
try {
// 将处理后的音频数据推送到流
if (readSize == buffer.size) {
// 如果读取的大小等于buffer的大小,直接写入整个buffer
pushStream.write(buffer)
} else {
// 如果只读取了部分数据,创建新的数组只包含有效数据
val validData = buffer.copyOfRange(0, readSize)
pushStream.write(validData)
}
} catch (e: Exception) {
FileLogger.e(TAG, "写入音频数据失败: ${e.message}")
break
}
} else if (readSize == 0) {
// 读取为0,可能是临时的,等待一下继续尝试
Thread.sleep(10)
} else {
// 负值表示错误
FileLogger.e(TAG, "读取音频数据失败,错误码: $readSize")
break
}
} catch (e: Exception) {
FileLogger.e(TAG, "录音线程异常: ${e.message}")
break
}
}
}, "AudioRecordingThread")
// 设置线程优先级并启动
recordingThread?.priority = Thread.MAX_PRIORITY
recordingThread?.start()
FileLogger.d(TAG, "音频录制已启动" + (if(echoCanceler?.enabled == true) ",回音消除已启用" else ""))
} catch (e: Exception) {
FileLogger.e(TAG, "启动音频录制失败: ${e.message}")
releaseAudioResources()
}
}
// 启用音频效果(回音消除、噪声抑制等)
private fun enableAudioEffects() {
try {
val audioSessionId = audioRecord?.audioSessionId ?: -1
if (audioSessionId != -1) {
// 启用回音消除
if (AcousticEchoCanceler.isAvailable()) {
try {
echoCanceler = AcousticEchoCanceler.create(audioSessionId)
if (echoCanceler != null) {
echoCanceler?.enabled = true
FileLogger.d(TAG, "回音消除已启用,会话ID: $audioSessionId")
} else {
FileLogger.w(TAG, "回音消除器创建返回null")
}
} catch (e: Exception) {
FileLogger.e(TAG, "创建回音消除器时出错: ${e.message}")
}
} else {
FileLogger.d(TAG, "设备不支持回音消除")
}
// 以下功能暂时不启用,可根据需要取消注释
/*
// 启用噪声抑制
if (NoiseSuppressor.isAvailable()) {
try {
val ns = NoiseSuppressor.create(audioSessionId)
ns?.enabled = true
FileLogger.d(TAG, "噪声抑制已启用")
} catch (e: Exception) {
FileLogger.e(TAG, "创建噪声抑制器时出错: ${e.message}")
}
}
// 启用自动增益控制
if (AutomaticGainControl.isAvailable()) {
try {
val agc = AutomaticGainControl.create(audioSessionId)
agc?.enabled = true
FileLogger.d(TAG, "自动增益控制已启用")
} catch (e: Exception) {
FileLogger.e(TAG, "创建自动增益控制时出错: ${e.message}")
}
}
*/
} else {
FileLogger.w(TAG, "无效的音频会话ID,无法启用音频效果")
}
} catch (e: Exception) {
FileLogger.e(TAG, "启用音频效果时出错: ${e.message}")
}
}
// 停止录音
fun stopRecording() {
if (!isRecording.get()) {
return
}
isRecording.set(false)
try {
// 等待录音线程结束
recordingThread?.join(1000)
// 释放资源
releaseAudioResources()
FileLogger.d(TAG, "音频录制已停止")
} catch (e: Exception) {
FileLogger.e(TAG, "停止音频录制失败: ${e.message}")
}
}
// 释放音频资源
private fun releaseAudioResources() {
try {
// 停止录音
try {
if (audioRecord?.state == AudioRecord.STATE_INITIALIZED) {
audioRecord?.stop()
}
} catch (e: Exception) {
// 忽略可能的IllegalStateException
FileLogger.w(TAG, "停止AudioRecord时出错: ${e.message}")
}
// 释放回音消除器
try {
if (echoCanceler != null) {
echoCanceler?.enabled = false
echoCanceler?.release()
echoCanceler = null
}
} catch (e: Exception) {
FileLogger.w(TAG, "释放回音消除器时出错: ${e.message}")
} finally {
echoCanceler = null
}
// 释放音频记录器
try {
audioRecord?.release()
} catch (e: Exception) {
FileLogger.w(TAG, "释放AudioRecord时出错: ${e.message}")
} finally {
audioRecord = null
}
// 重置线程
recordingThread = null
} catch (e: Exception) {
FileLogger.e(TAG, "释放音频资源失败: ${e.message}")
}
}
}
// 一次性识别回调接口
interface RecognizeCallback {
fun onResult(result: String, detectedLanguage: String = "")
fun onError(error: String)
}
// 连续识别回调接口
interface ContinuousRecognizeCallback {
fun onResult(result: String, detectedLanguage: String = "")
fun onRecognizing(recognizing: String, detectedLanguage: String = "")
fun onSessionStarted()
fun onSessionStopped()
fun onCanceled(reason: String, errorDetails: String)
fun onError(error: String)
}
}

344
android/app/src/main/kotlin/com/example/deep_voice/VolcanoAIService.kt

@ -1,344 +0,0 @@
package com.yunqiinnovation.deepsound
import android.util.Log
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONArray
import org.json.JSONObject
import java.io.IOException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
/**
* 火山AI服务的原生实现
*
* 参考Flutter端的VolcanoAIService实现,提供同步和异步的API调用方式
*/
class VolcanoAIService() {
private val TAG = "VolcanoAIService"
private val baseUrl = "https://ark.cn-beijing.volces.com/api/v3"
private val chatEndpoint = "/chat/completions"
private val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
private var apiKey: String = ""
private var isInitialized = false
/**
* 初始化火山AI服务
*
* @param apiKey 火山AI API密钥
* @return 初始化是否成功
*/
fun initialize(apiKey: String): Boolean {
this.apiKey = apiKey
isInitialized = apiKey.isNotEmpty()
if (!isInitialized) {
Log.e(TAG, "初始化失败:API key 不能为空")
} else {
Log.d(TAG, "火山AI服务初始化成功")
}
return isInitialized
}
/**
* 生成个性化问候语
*
* @param agentName 代理名称
* @param systemPrompt 系统提示词
* @param callback 回调函数,返回生成的问候语
*/
fun generateGreeting(agentName: String, systemPrompt: String, callback: (String?, Exception?) -> Unit) {
val messages = JSONArray().apply {
put(JSONObject().apply {
put("role", "system")
put("content", systemPrompt)
})
put(JSONObject().apply {
put("role", "user")
put("content", "请用一句简短的话向我打个招呼,要符合你的身份和性格特点,不要超过18个字。")
})
}
sendMessageStream(messages, systemPrompt, object : StreamCallback {
val stringBuilder = StringBuilder()
override fun onToken(token: String) {
stringBuilder.append(token)
}
override fun onComplete() {
callback(stringBuilder.toString(), null)
}
override fun onError(e: Exception) {
callback(null, e)
}
})
}
/**
* 发送消息(非流式输出)
*
* @param messages 消息列表
* @param systemPrompt 系统提示词
* @return 返回AI的回复
* @throws VolcanoAIException 如果API调用失败
*/
@Throws(VolcanoAIException::class)
fun sendMessage(messages: JSONArray, systemPrompt: String): String {
// 检查是否已初始化
if (!isInitialized || apiKey.isEmpty()) {
throw VolcanoAIException("火山AI服务未初始化或API key为空,请先调用initialize方法")
}
val fullMessages = JSONArray().apply {
put(JSONObject().apply {
put("role", "system")
put("content", systemPrompt)
})
for (i in 0 until messages.length()) {
put(messages.getJSONObject(i))
}
}
val requestBody = JSONObject().apply {
put("model", "doubao-1-5-lite-32k-250115")
put("messages", fullMessages)
put("temperature", 0.7)
put("max_tokens", 2000)
put("stream", false)
}
val mediaType = "application/json".toMediaTypeOrNull()
val request = Request.Builder()
.url("$baseUrl$chatEndpoint")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer $apiKey")
.post(requestBody.toString().toRequestBody(mediaType))
.build()
try {
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
val errorBody = response.body?.string() ?: ""
val errorMessage = try {
JSONObject(errorBody).getJSONObject("error").getString("message")
} catch (e: Exception) {
"Unknown error occurred"
}
throw VolcanoAIException(errorMessage)
}
val responseBody = response.body?.string() ?: throw VolcanoAIException("Empty response")
val jsonResponse = JSONObject(responseBody)
if (jsonResponse.has("choices") &&
jsonResponse.getJSONArray("choices").length() > 0 &&
jsonResponse.getJSONArray("choices").getJSONObject(0).has("message")) {
return jsonResponse.getJSONArray("choices")
.getJSONObject(0)
.getJSONObject("message")
.getString("content")
}
throw VolcanoAIException("Invalid response format")
}
} catch (e: Exception) {
if (e is VolcanoAIException) throw e
throw VolcanoAIException("Failed to communicate with AI service: ${e.message}")
}
}
/**
* 发送消息(流式输出)
*
* @param messages 消息列表
* @param systemPrompt 系统提示词
* @param callback 回调函数,用于接收流式输出的结果
*/
fun sendMessageStream(messages: JSONArray, systemPrompt: String, callback: StreamCallback) {
// 检查是否已初始化
if (!isInitialized || apiKey.isEmpty()) {
callback.onError(VolcanoAIException("火山AI服务未初始化或API key为空,请先调用initialize方法"))
return
}
val fullMessages = JSONArray().apply {
put(JSONObject().apply {
put("role", "system")
put("content", systemPrompt)
})
for (i in 0 until messages.length()) {
put(messages.getJSONObject(i))
}
}
val requestBody = JSONObject().apply {
put("model", "doubao-1-5-lite-32k-250115")
put("messages", fullMessages)
put("temperature", 0.7)
put("max_tokens", 2000)
put("stream", true)
}
val mediaType = "application/json".toMediaTypeOrNull()
val request = Request.Builder()
.url("$baseUrl$chatEndpoint")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer $apiKey")
.addHeader("Accept", "text/event-stream")
.post(requestBody.toString().toRequestBody(mediaType))
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
callback.onError(VolcanoAIException("Failed to communicate with AI service: ${e.message}"))
}
override fun onResponse(call: Call, response: Response) {
if (!response.isSuccessful) {
val errorBody = response.body?.string() ?: ""
val errorMessage = try {
JSONObject(errorBody).getJSONObject("error").getString("message")
} catch (e: Exception) {
"Unknown error occurred"
}
callback.onError(VolcanoAIException(errorMessage))
return
}
val responseBody = response.body ?: return
val source = responseBody.source()
val bufferedSource = source.buffer
try {
while (!bufferedSource.exhausted()) {
val line = bufferedSource.readUtf8Line() ?: continue
if (line.isEmpty()) continue
if (line.startsWith("data: ")) {
val data = line.substring(6)
if (data == "[DONE]") {
callback.onComplete()
break
}
try {
val jsonData = JSONObject(data)
if (jsonData.has("choices") &&
jsonData.getJSONArray("choices").length() > 0 &&
jsonData.getJSONArray("choices").getJSONObject(0).has("delta") &&
jsonData.getJSONArray("choices").getJSONObject(0).getJSONObject("delta").has("content")) {
val content = jsonData.getJSONArray("choices")
.getJSONObject(0)
.getJSONObject("delta")
.getString("content")
callback.onToken(content)
}
} catch (e: Exception) {
// 忽略无效的JSON数据
continue
}
}
}
} catch (e: Exception) {
callback.onError(VolcanoAIException("Error processing stream: ${e.message}"))
} finally {
response.close()
}
}
})
}
/**
* 同步方式发送消息(流式输出)
*
* 注意:此方法会阻塞当前线程,请在后台线程中调用
*
* @param messages 消息列表
* @param systemPrompt 系统提示词
* @return 返回完整的AI回复
* @throws VolcanoAIException 如果API调用失败
*/
@Throws(VolcanoAIException::class)
fun sendMessageStreamSync(messages: JSONArray, systemPrompt: String): String {
val result = StringBuilder()
val latch = CountDownLatch(1)
var exception: Exception? = null
sendMessageStream(messages, systemPrompt, object : StreamCallback {
override fun onToken(token: String) {
result.append(token)
}
override fun onComplete() {
latch.countDown()
}
override fun onError(e: Exception) {
exception = e
latch.countDown()
}
})
// 等待流式输出完成或出错
latch.await(60, TimeUnit.SECONDS)
if (exception != null) {
throw exception as VolcanoAIException
}
return result.toString()
}
/**
* 创建用户消息
*/
fun createUserMessage(content: String): JSONObject {
return JSONObject().apply {
put("role", "user")
put("content", content)
}
}
/**
* 创建系统消息
*/
fun createSystemMessage(content: String): JSONObject {
return JSONObject().apply {
put("role", "system")
put("content", content)
}
}
/**
* 创建助手消息
*/
fun createAssistantMessage(content: String): JSONObject {
return JSONObject().apply {
put("role", "assistant")
put("content", content)
}
}
/**
* 流式输出回调接口
*/
interface StreamCallback {
fun onToken(token: String)
fun onComplete()
fun onError(e: Exception)
}
}
/**
* 火山AI异常
*/
class VolcanoAIException(message: String) : Exception(message)

0
android/app/src/main/kotlin/com/example/deep_voice/ClassicBluetoothHelper.kt → android/app/src/main/kotlin/com/yunqiinnovation/deepsound/ClassicBluetoothHelper.kt

382
android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt → android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt

@ -1,5 +1,6 @@
package com.yunqiinnovation.deepsound package com.yunqiinnovation.deepsound
import android.Manifest
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -20,33 +21,54 @@ import android.bluetooth.BluetoothDevice
import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey import androidx.security.crypto.MasterKey
import com.yunqiinnovation.deepsound.core.utils.FileLogger import com.yunqiinnovation.deepsound.core.utils.FileLogger
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
class MainActivity: FlutterActivity() { class MainActivity: FlutterActivity() {
private val AZURE_ASR_CHANNEL = "com.deep_voice.azure_asr"
private val AZURE_ASR_EVENT_CHANNEL = "com.deep_voice.azure_asr_events"
private val AZURE_TTS_CHANNEL = "com.deep_voice.azure_tts"
private val VOICE_INTERACTION_CHANNEL = "com.deep_voice.voice_interaction" private val VOICE_INTERACTION_CHANNEL = "com.deep_voice.voice_interaction"
private val VOICE_INTERACTION_EVENT_CHANNEL = "com.deep_voice.voice_interaction_events" private val VOICE_INTERACTION_EVENT_CHANNEL = "com.deep_voice.voice_interaction_events"
private val CLASSIC_BLUETOOTH_CHANNEL = "com.deep_voice.classic_bluetooth" private val CLASSIC_BLUETOOTH_CHANNEL = "com.deep_voice.classic_bluetooth"
private val CLASSIC_BLUETOOTH_EVENT_CHANNEL = "com.deep_voice.classic_bluetooth_events" private val CLASSIC_BLUETOOTH_EVENT_CHANNEL = "com.deep_voice.classic_bluetooth_events"
private val TAG = "MainActivity" private val TAG = "MainActivity"
private lateinit var azureAsrHelper: AzureAsrHelper
private lateinit var azureTtsHelper: AzureTtsHelper
private lateinit var classicBluetoothHelper: ClassicBluetoothHelper private lateinit var classicBluetoothHelper: ClassicBluetoothHelper
private var azureAsrEventSink: EventChannel.EventSink? = null
private var voiceInteractionEventSink: EventChannel.EventSink? = null private var voiceInteractionEventSink: EventChannel.EventSink? = null
private var bluetoothEventSink: EventChannel.EventSink? = null private var bluetoothEventSink: EventChannel.EventSink? = null
// 添加权限请求相关常量
private val PERMISSION_REQUEST_CODE = 100
private val REQUIRED_PERMISSIONS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(
Manifest.permission.RECORD_AUDIO,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.SEND_SMS,
Manifest.permission.READ_CONTACTS,
Manifest.permission.CALL_PHONE,
Manifest.permission.POST_NOTIFICATIONS
)
} else {
arrayOf(
Manifest.permission.RECORD_AUDIO,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.SEND_SMS,
Manifest.permission.READ_CONTACTS,
Manifest.permission.CALL_PHONE
)
}
// 广播接收器 // 广播接收器
private val voiceInteractionReceiver = object : BroadcastReceiver() { private val voiceInteractionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
Log.d(TAG, "收到广播: ${intent?.action}") FileLogger.d(TAG, "收到广播: ${intent?.action}")
when (intent?.action) { when (intent?.action) {
VoiceInteractionService.ACTION_RECOGNITION_STARTED -> { VoiceInteractionService.ACTION_RECOGNITION_STARTED -> {
val timestamp = intent.getLongExtra("timestamp", 0) val timestamp = intent.getLongExtra("timestamp", 0)
Log.d(TAG, "收到语音识别启动广播: timestamp=$timestamp")
sendVoiceInteractionEvent(mapOf( sendVoiceInteractionEvent(mapOf(
"type" to "recognition_started", "type" to "recognition_started",
"timestamp" to timestamp "timestamp" to timestamp
@ -58,12 +80,14 @@ class MainActivity: FlutterActivity() {
val assistantMessage = intent.getStringExtra("assistantMessage") ?: "" val assistantMessage = intent.getStringExtra("assistantMessage") ?: ""
val timestamp = intent.getLongExtra("timestamp", System.currentTimeMillis()) val timestamp = intent.getLongExtra("timestamp", System.currentTimeMillis())
Log.d(TAG, "收到聊天记录更新广播: agentId=$agentId, timestamp=$timestamp")
Log.d(TAG, "用户消息: ${userMessage.take(50)}...")
Log.d(TAG, "助手回复: ${assistantMessage.take(50)}...")
sendChatHistoryEvent(agentId, userMessage, assistantMessage, timestamp) sendChatHistoryEvent(agentId, userMessage, assistantMessage, timestamp)
} }
VoiceInteractionService.ACTION_ENTER_TRANSLATION_MODE -> {
sendVoiceInteractionEvent(mapOf(
"type" to "enter_translation_mode",
"timestamp" to System.currentTimeMillis()
))
}
} }
} }
} }
@ -143,13 +167,26 @@ class MainActivity: FlutterActivity() {
var azureSpeechKey: String = "" var azureSpeechKey: String = ""
var azureSpeechRegion: String = "" var azureSpeechRegion: String = ""
var volcanoAiApiKey: String = "" var volcanoAiApiKey: String = ""
var openaiApiKey: String = ""
var openaiBaseUrl: String? = null
var openaiModel: String? = null
var volcanoSpeechAppId: String = ""
var volcanoSpeechAppToken: String = ""
var mcpServer: String = ""
// 安全存储相关常量 // 安全存储相关常量
private const val SECURE_PREFS_FILENAME = "deep_voice_secure_prefs" private const val SECURE_PREFS_FILENAME = "deep_voice_secure_prefs"
private const val KEY_AZURE_SPEECH_KEY = "azure_speech_key" private const val KEY_AZURE_SPEECH_KEY = "azure_speech_key"
private const val KEY_AZURE_SPEECH_REGION = "azure_speech_region" private const val KEY_AZURE_SPEECH_REGION = "azure_speech_region"
private const val KEY_VOLCANO_AI_API_KEY = "volcano_ai_api_key" private const val KEY_VOLCANO_AI_API_KEY = "volcano_ai_api_key"
private const val KEY_OPENAI_API_KEY = "openai_api_key"
private const val KEY_OPENAI_BASE_URL = "openai_base_url"
private const val KEY_OPENAI_MODEL = "openai_model"
private const val KEY_MCP_SERVER_ENDPOINT = "mcp_server_endpoint"
private const val KEY_VOLCANO_SPEECH_APP_ID = "volcano_speech_app_id"
private const val KEY_VOLCANO_SPEECH_APP_TOKEN = "volcano_speech_app_token"
private const val KEY_MCP_SERVER = "mcp_server"
// 会话管理 // 会话管理
private const val KEY_SESSION_ID = "session_id" private const val KEY_SESSION_ID = "session_id"
private var currentSessionId = "" private var currentSessionId = ""
@ -183,7 +220,13 @@ class MainActivity: FlutterActivity() {
.putString(KEY_AZURE_SPEECH_KEY, azureSpeechKey) .putString(KEY_AZURE_SPEECH_KEY, azureSpeechKey)
.putString(KEY_AZURE_SPEECH_REGION, azureSpeechRegion) .putString(KEY_AZURE_SPEECH_REGION, azureSpeechRegion)
.putString(KEY_VOLCANO_AI_API_KEY, volcanoAiApiKey) .putString(KEY_VOLCANO_AI_API_KEY, volcanoAiApiKey)
.putString(KEY_OPENAI_API_KEY, openaiApiKey)
.putString(KEY_OPENAI_BASE_URL, openaiBaseUrl)
.putString(KEY_SESSION_ID, currentSessionId) .putString(KEY_SESSION_ID, currentSessionId)
.putString(KEY_OPENAI_MODEL, openaiModel)
.putString(KEY_VOLCANO_SPEECH_APP_ID, volcanoSpeechAppId)
.putString(KEY_VOLCANO_SPEECH_APP_TOKEN, volcanoSpeechAppToken)
.putString(KEY_MCP_SERVER, mcpServer)
.apply() .apply()
FileLogger.d("MainActivity", "密钥已安全保存到加密存储中") FileLogger.d("MainActivity", "密钥已安全保存到加密存储中")
@ -227,11 +270,17 @@ class MainActivity: FlutterActivity() {
azureSpeechKey = sharedPreferences.getString(KEY_AZURE_SPEECH_KEY, "") ?: "" azureSpeechKey = sharedPreferences.getString(KEY_AZURE_SPEECH_KEY, "") ?: ""
azureSpeechRegion = sharedPreferences.getString(KEY_AZURE_SPEECH_REGION, "") ?: "" azureSpeechRegion = sharedPreferences.getString(KEY_AZURE_SPEECH_REGION, "") ?: ""
volcanoAiApiKey = sharedPreferences.getString(KEY_VOLCANO_AI_API_KEY, "") ?: "" volcanoAiApiKey = sharedPreferences.getString(KEY_VOLCANO_AI_API_KEY, "") ?: ""
openaiApiKey = sharedPreferences.getString(KEY_OPENAI_API_KEY, "") ?: ""
openaiBaseUrl = sharedPreferences.getString(KEY_OPENAI_BASE_URL, null)
openaiModel = sharedPreferences.getString(KEY_OPENAI_MODEL, null)
volcanoSpeechAppId = sharedPreferences.getString(KEY_VOLCANO_SPEECH_APP_ID, "") ?: ""
volcanoSpeechAppToken = sharedPreferences.getString(KEY_VOLCANO_SPEECH_APP_TOKEN, "") ?: ""
mcpServer = sharedPreferences.getString(KEY_MCP_SERVER, "") ?: ""
FileLogger.d("MainActivity", "已从加密存储加载密钥") FileLogger.d("MainActivity", "已从加密存储加载密钥")
// 检查是否成功获取所有密钥 // 检查是否成功获取所有必要密钥
return azureSpeechKey.isNotEmpty() && azureSpeechRegion.isNotEmpty() && volcanoAiApiKey.isNotEmpty() return azureSpeechKey.isNotEmpty() && azureSpeechRegion.isNotEmpty() &&
(openaiApiKey.isNotEmpty() || volcanoAiApiKey.isNotEmpty())
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e("MainActivity", "从加密存储加载密钥失败: ${e.message}") FileLogger.e("MainActivity", "从加密存储加载密钥失败: ${e.message}")
e.printStackTrace() e.printStackTrace()
@ -246,9 +295,10 @@ class MainActivity: FlutterActivity() {
// 初始化 FileLogger // 初始化 FileLogger
FileLogger.init(applicationContext) FileLogger.init(applicationContext)
// 请求必要权限
requestRequiredPermissions()
// 初始化 Azure 语音服务 // 初始化 Azure 语音服务
azureAsrHelper = AzureAsrHelper(applicationContext)
azureTtsHelper = AzureTtsHelper(applicationContext)
classicBluetoothHelper = ClassicBluetoothHelper(applicationContext) classicBluetoothHelper = ClassicBluetoothHelper(applicationContext)
// 注册广播接收器 // 注册广播接收器
@ -266,7 +316,14 @@ class MainActivity: FlutterActivity() {
val voiceInteractionFilter = IntentFilter().apply { val voiceInteractionFilter = IntentFilter().apply {
addAction(VoiceInteractionService.ACTION_RECOGNITION_STARTED) addAction(VoiceInteractionService.ACTION_RECOGNITION_STARTED)
addAction(VoiceInteractionService.ACTION_CHAT_HISTORY_UPDATED) addAction(VoiceInteractionService.ACTION_CHAT_HISTORY_UPDATED)
addAction(VoiceInteractionService.ACTION_ENTER_TRANSLATION_MODE)
} }
// 打印已注册的广播
FileLogger.d(TAG, "已注册语音交互广播:${VoiceInteractionService.ACTION_RECOGNITION_STARTED}, " +
"${VoiceInteractionService.ACTION_CHAT_HISTORY_UPDATED}, " +
"${VoiceInteractionService.ACTION_ENTER_TRANSLATION_MODE}")
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
registerReceiver(voiceInteractionReceiver, voiceInteractionFilter, Context.RECEIVER_NOT_EXPORTED) registerReceiver(voiceInteractionReceiver, voiceInteractionFilter, Context.RECEIVER_NOT_EXPORTED)
} else { } else {
@ -300,8 +357,7 @@ class MainActivity: FlutterActivity() {
setupMethodChannels(flutterEngine) setupMethodChannels(flutterEngine)
// 初始化 Azure 语音服务 // 初始化 Azure 语音服务
azureAsrHelper = AzureAsrHelper(this) classicBluetoothHelper = ClassicBluetoothHelper(this)
azureTtsHelper = AzureTtsHelper(this)
Log.d(TAG, "Flutter 引擎配置完成") Log.d(TAG, "Flutter 引擎配置完成")
} }
@ -327,20 +383,6 @@ class MainActivity: FlutterActivity() {
} }
) )
// Azure ASR 事件通道
EventChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_ASR_EVENT_CHANNEL).setStreamHandler(
object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
Log.d(TAG, "ASR事件通道开始监听")
azureAsrEventSink = events
}
override fun onCancel(arguments: Any?) {
azureAsrEventSink = null
}
}
)
// 设置蓝牙事件通道 // 设置蓝牙事件通道
EventChannel(flutterEngine.dartExecutor.binaryMessenger, CLASSIC_BLUETOOTH_EVENT_CHANNEL).setStreamHandler( EventChannel(flutterEngine.dartExecutor.binaryMessenger, CLASSIC_BLUETOOTH_EVENT_CHANNEL).setStreamHandler(
object : EventChannel.StreamHandler { object : EventChannel.StreamHandler {
@ -367,195 +409,6 @@ class MainActivity: FlutterActivity() {
private fun setupMethodChannels(flutterEngine: FlutterEngine) { private fun setupMethodChannels(flutterEngine: FlutterEngine) {
Log.d(TAG, "开始设置方法通道") Log.d(TAG, "开始设置方法通道")
// 设置 Azure ASR 方法通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_ASR_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"initialize" -> {
val subscriptionKey = call.argument<String>("subscriptionKey")
val region = call.argument<String>("region")
val supportedLanguages = call.argument<List<String>>("supportedLanguages")?.toTypedArray() ?: arrayOf("zh-CN", "en-US")
if (subscriptionKey == null || region == null) {
result.error("INVALID_ARGUMENTS", "订阅密钥和区域不能为空", null)
return@setMethodCallHandler
}
try {
val success = azureAsrHelper.initialize(subscriptionKey, region, supportedLanguages)
result.success(success)
} catch (e: Exception) {
result.error("INITIALIZATION_ERROR", e.message, null)
}
}
"recognizeOnce" -> {
azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) {
result.success(mapOf(
"text" to text,
"detectedLanguage" to detectedLanguage
))
}
override fun onError(error: String) {
result.error("RECOGNITION_ERROR", error, null)
}
})
}
"startContinuousRecognition" -> {
// 确保事件通道已准备好
if (azureAsrEventSink == null) {
result.error("EVENT_CHANNEL_NOT_READY", "事件通道未准备好,无法开始连续识别", null)
return@setMethodCallHandler
}
val success = azureAsrHelper.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) {
sendAsrEvent(mapOf(
"type" to "result",
"text" to text,
"detectedLanguage" to detectedLanguage
))
}
override fun onRecognizing(recognizing: String, detectedLanguage: String) {
sendAsrEvent(mapOf(
"type" to "recognizing",
"text" to recognizing,
"detectedLanguage" to detectedLanguage
))
}
override fun onSessionStarted() {
sendAsrEvent(mapOf("type" to "sessionStarted"))
}
override fun onSessionStopped() {
sendAsrEvent(mapOf("type" to "sessionStopped"))
}
override fun onCanceled(reason: String, errorDetails: String) {
sendAsrEvent(mapOf(
"type" to "canceled",
"reason" to reason,
"errorDetails" to errorDetails
))
}
override fun onError(error: String) {
sendAsrEvent(mapOf("type" to "error", "message" to error))
}
})
result.success(success)
}
"stopContinuousRecognition" -> {
try {
if (!azureAsrHelper.isContinuousRecognitionActive()) {
result.success(true)
return@setMethodCallHandler
}
val success = azureAsrHelper.stopContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) {}
override fun onRecognizing(recognizing: String, detectedLanguage: String) {}
override fun onSessionStarted() {}
override fun onSessionStopped() {}
override fun onCanceled(reason: String, errorDetails: String) {}
override fun onError(error: String) {
result.error("STOP_ERROR", error, null)
}
})
result.success(success)
} catch (e: Exception) {
result.error("STOP_ERROR", e.message, null)
}
}
"isContinuousRecognitionActive" -> {
result.success(azureAsrHelper.isContinuousRecognitionActive())
}
"dispose" -> {
azureAsrHelper.dispose()
result.success(true)
}
else -> {
result.notImplemented()
}
}
}
// 设置 Azure TTS 方法通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_TTS_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"initialize" -> {
val subscriptionKey = call.argument<String>("subscriptionKey") ?: ""
val region = call.argument<String>("region") ?: ""
val language = call.argument<String>("language") ?: "zh-CN"
val success = azureTtsHelper.initialize(subscriptionKey, region, language)
result.success(success)
}
"setVoice" -> {
val voiceName = call.argument<String>("voiceName") ?: return@setMethodCallHandler result.error("INVALID_ARGUMENTS", "语音名称不能为空", null)
result.success(azureTtsHelper.setVoice(voiceName))
}
"setSpeechParams" -> {
val rate = call.argument<Int>("rate") ?: 0
val pitch = call.argument<Int>("pitch") ?: 0
val volume = call.argument<Int>("volume") ?: 100
result.success(azureTtsHelper.setSpeechParams(rate, pitch, volume))
}
"setAudioOutputType" -> {
val outputTypeStr = call.argument<String>("outputType") ?: "speaker"
val outputType = when (outputTypeStr.lowercase()) {
"speaker" -> AzureTtsHelper.AudioOutputType.SPEAKER
"earpiece" -> AzureTtsHelper.AudioOutputType.EARPIECE
"auto" -> AzureTtsHelper.AudioOutputType.AUTO
else -> AzureTtsHelper.AudioOutputType.SPEAKER
}
result.success(azureTtsHelper.setAudioOutputType(outputType))
}
"speakText" -> {
val text = call.argument<String>("text") ?: return@setMethodCallHandler result.error("INVALID_ARGUMENTS", "文本不能为空", null)
azureTtsHelper.speakText(text, object : AzureTtsHelper.TTSCallback {
override fun onSuccess(message: String) {
runOnUiThread { result.success(message) }
}
override fun onError(error: String) {
runOnUiThread { result.error("SPEAK_ERROR", error, null) }
}
})
}
"speakSsml" -> {
val ssml = call.argument<String>("ssml") ?: return@setMethodCallHandler result.error("INVALID_ARGUMENTS", "SSML不能为空", null)
azureTtsHelper.speakSsml(ssml, object : AzureTtsHelper.TTSCallback {
override fun onSuccess(message: String) {
runOnUiThread { result.success(message) }
}
override fun onError(error: String) {
runOnUiThread { result.error("SPEAK_ERROR", error, null) }
}
})
}
"stopSpeaking" -> {
result.success(azureTtsHelper.stopSpeaking())
}
"isSpeaking" -> {
result.success(azureTtsHelper.isSpeaking())
}
"dispose" -> {
azureTtsHelper.dispose()
result.success(true)
}
else -> {
result.notImplemented()
}
}
}
// 设置语音交互方法通道 // 设置语音交互方法通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, VOICE_INTERACTION_CHANNEL).setMethodCallHandler { call, result -> MethodChannel(flutterEngine.dartExecutor.binaryMessenger, VOICE_INTERACTION_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) { when (call.method) {
@ -657,15 +510,6 @@ class MainActivity: FlutterActivity() {
} }
} }
// ASR 事件发送方法
private fun sendAsrEvent(event: Map<String, Any>) {
if (azureAsrEventSink == null) return
runOnUiThread {
azureAsrEventSink?.success(event)
}
}
/** /**
* 启动语音交互服务(带配置参数) * 启动语音交互服务(带配置参数)
*/ */
@ -673,14 +517,14 @@ class MainActivity: FlutterActivity() {
FileLogger.d(TAG, "启动语音交互服务") FileLogger.d(TAG, "启动语音交互服务")
// 获取配置参数 // 获取配置参数
val key = call.argument<String>("azure_speech_key") ?: "" azureSpeechKey = call.argument<String>("azure_speech_key") ?: ""
val region = call.argument<String>("azure_speech_region") ?: "" azureSpeechRegion = call.argument<String>("azure_speech_region") ?: ""
val aiKey = call.argument<String>("volcano_ai_api_key") ?: "" openaiApiKey = call.argument<String>("openai_api_key") ?: ""
openaiBaseUrl = call.argument<String>("openai_base_url")
// 设置 Azure Speech 配置 openaiModel = call.argument<String>("openai_model")
azureSpeechKey = key volcanoSpeechAppId = call.argument<String>("volcano_speech_app_id") ?: ""
azureSpeechRegion = region volcanoSpeechAppToken = call.argument<String>("volcano_speech_app_token") ?: ""
volcanoAiApiKey = aiKey mcpServer = call.argument<String>("mcp_config") ?: ""
// 保存密钥到安全存储 // 保存密钥到安全存储
saveKeysToSecureStorage(applicationContext) saveKeysToSecureStorage(applicationContext)
@ -755,14 +599,19 @@ class MainActivity: FlutterActivity() {
private fun sendVoiceInteractionEvent(event: Map<String, Any>) { private fun sendVoiceInteractionEvent(event: Map<String, Any>) {
if (voiceInteractionEventSink == null) { if (voiceInteractionEventSink == null) {
Log.e(TAG, "无法发送语音交互事件:事件通道未准备好") Log.e(TAG, "无法发送语音交互事件:事件通道未准备好")
FileLogger.e(TAG, "无法发送语音交互事件:事件通道未准备好")
return return
} }
FileLogger.d(TAG, "准备发送事件到Flutter: ${event["type"]}")
runOnUiThread { runOnUiThread {
try { try {
voiceInteractionEventSink?.success(event) voiceInteractionEventSink?.success(event)
FileLogger.d(TAG, "成功发送事件到Flutter: ${event["type"]}")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "发送语音交互事件到Flutter失败: ${e.message}", e) Log.e(TAG, "发送语音交互事件到Flutter失败: ${e.message}")
FileLogger.e(TAG, "发送语音交互事件到Flutter失败: ${e.message}")
} }
} }
} }
@ -799,11 +648,58 @@ class MainActivity: FlutterActivity() {
} }
// 释放资源 // 释放资源
azureAsrHelper.dispose()
azureTtsHelper.dispose()
classicBluetoothHelper.dispose() classicBluetoothHelper.dispose()
super.onDestroy() super.onDestroy()
} }
/**
* 请求必要权限
*/
private fun requestRequiredPermissions() {
val permissionsToRequest = ArrayList<String>()
for (permission in REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
permissionsToRequest.add(permission)
}
}
if (permissionsToRequest.isNotEmpty()) {
ActivityCompat.requestPermissions(
this,
permissionsToRequest.toTypedArray(),
PERMISSION_REQUEST_CODE
)
}
}
/**
* 处理权限请求结果
*/
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST_CODE) {
val deniedPermissions = ArrayList<String>()
for (i in permissions.indices) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permissions[i])
}
}
if (deniedPermissions.isNotEmpty()) {
// 记录未授权的权限
FileLogger.w(TAG, "未授权的权限: ${deniedPermissions.joinToString()}")
} else {
FileLogger.d(TAG, "所有必要权限已授权")
}
}
}
} }

415
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionHandler.kt

@ -0,0 +1,415 @@
package com.yunqiinnovation.deepsound
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import org.json.JSONArray
import org.json.JSONObject
import android.util.Log
import com.yunqiinnovation.deepsound.core.utils.FileLogger
import com.yunqiinnovation.azure_speech.AzureAsrHelper
import com.yunqiinnovation.azure_speech.AzureTtsHelper
import com.yunqiinnovation.open_ai_service.OpenAIService
import com.yunqiinnovation.open_ai_service.SystemFunctionHandler
/**
* 语音交互处理器 - 处理语音识别、TTS和AI对话相关逻辑
*/
class VoiceInteractionHandler(
private val context: Context,
private val azureSpeechKey: String,
private val azureSpeechRegion: String,
private val openaiApiKey: String,
private val openaiBaseUrl: String = "",
private val openaiModel: String = "",
private val volcanoSpeechAppId: String,
private val volcanoSpeechAppToken: String,
private val mcpServer: String
) {
private val TAG = "VoiceInteractionHandler"
// Azure服务
private var azureAsrHelper: AzureAsrHelper? = null
private var azureTtsHelper: AzureTtsHelper? = null
// OpenAI服务
private val openAIService = OpenAIService(context.applicationContext)
// 当前用户输入
private var currentUserInput = ""
// 状态
private var isInitialized = false
var isRecognitionActive = false
private set
var isTtsSpeaking = false
private set
var hasSpeechDetected = false
private set
// 回调
private var callback: InteractionCallback? = null
// 广播接收器
private val exitInteractionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == SystemFunctionHandler.ACTION_EXIT_INTERACTION) {
Log.d(TAG, "收到退出交互广播")
stopRecognition()
}
}
}
/**
* 初始化
*/
fun initialize(): Boolean {
if (isInitialized) return true
try {
// 初始化Azure ASR
azureAsrHelper = AzureAsrHelper(context).apply {
initialize(azureSpeechKey, azureSpeechRegion)
}
// 初始化Azure TTS
azureTtsHelper = AzureTtsHelper(context).apply {
initialize(azureSpeechKey, azureSpeechRegion)
}
// 初始化OpenAI服务
openAIService.initialize(openaiApiKey, openaiBaseUrl, openaiModel, mcpServer)
// 注册广播接收器
try {
Log.d(TAG, "注册退出交互广播接收器,包名=${context.packageName}, action=${SystemFunctionHandler.ACTION_EXIT_INTERACTION}")
context.registerReceiver(
exitInteractionReceiver,
IntentFilter(SystemFunctionHandler.ACTION_EXIT_INTERACTION),
Context.RECEIVER_NOT_EXPORTED
)
Log.d(TAG, "退出交互广播接收器注册成功")
} catch (e: Exception) {
// 广播注册失败不应该影响整个应用初始化
Log.e(TAG, "注册退出交互广播接收器失败: ${e.message}", e)
}
isInitialized = true
return true
} catch (e: Exception) {
FileLogger.e(TAG, "初始化失败: ${e.message}", e)
return false
}
}
/**
* 设置回调
*/
fun setCallback(callback: InteractionCallback) {
this.callback = callback
}
/**
* 开始语音识别
*/
fun startRecognition() {
if (isRecognitionActive) return
// 检查录音权限
if (!checkRecordAudioPermission()) {
callback?.onError("需要录音权限,请在设置中授予权限")
return
}
isRecognitionActive = true
hasSpeechDetected = false
notifyStateChanged()
try {
azureAsrHelper?.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onRecognizing(recognizing: String, detectedLanguage: String) {
if (recognizing.isNotEmpty()) {
hasSpeechDetected = true
stopTts()
notifyStateChanged()
}
}
override fun onResult(result: String, detectedLanguage: String) {
if (result.isNotEmpty()) {
notifyStateChanged()
processWithOpenAI(result)
}
// 重置状态,继续识别
hasSpeechDetected = false
}
override fun onSessionStarted() {
notifyStateChanged()
}
override fun onSessionStopped() {
isRecognitionActive = false
notifyStateChanged()
}
override fun onCanceled(reason: String, errorDetails: String) {
isRecognitionActive = false
notifyStateChanged()
}
override fun onError(error: String) {
isRecognitionActive = false
callback?.onError("语音识别出错")
notifyStateChanged()
}
override fun onSuccess(message: String) {
// 处理成功事件
}
})
} catch (e: Exception) {
isRecognitionActive = false
FileLogger.e(TAG, "启动语音识别失败: ${e.message}", e)
callback?.onError("启动语音识别失败")
notifyStateChanged()
}
}
/**
* 停止语音识别
*/
fun stopRecognition() {
if (!isRecognitionActive) return
FileLogger.d(TAG, "停止语音识别")
try {
azureAsrHelper?.stopContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onResult(result: String, detectedLanguage: String) {}
override fun onRecognizing(recognizing: String, detectedLanguage: String) {}
override fun onSessionStarted() {}
override fun onSessionStopped() {
isRecognitionActive = false
FileLogger.d(TAG, "语音识别会话已停止")
notifyStateChanged()
}
override fun onCanceled(reason: String, errorDetails: String) {
isRecognitionActive = false
FileLogger.d(TAG, "语音识别已取消: $reason")
notifyStateChanged()
}
override fun onError(error: String) {
isRecognitionActive = false
FileLogger.e(TAG, "停止语音识别时出错: $error")
notifyStateChanged()
}
override fun onSuccess(message: String) {
isRecognitionActive = false
FileLogger.d(TAG, "语音识别已停止: $message")
notifyStateChanged()
}
})
} catch (e: Exception) {
FileLogger.e(TAG, "停止语音识别异常: ${e.message}", e)
// 确保状态一致性
isRecognitionActive = false
notifyStateChanged()
}
}
/**
* 使用OpenAI处理语音识别结果
*/
private fun processWithOpenAI(text: String) {
// 保存当前用户输入,用于后续同步聊天记录
currentUserInput = text
Log.d(TAG, "用户 问题: $text")
Thread {
try {
val messages = JSONArray().apply {
put(openAIService.createUserMessage(text))
}
// 创建响应构建器
val responseBuilder = StringBuilder()
openAIService.sendMessageStream(
messages = messages,
callback = object : OpenAIService.StreamCallback {
override fun onToken(token: String) {
// 累加响应内容
responseBuilder.append(token)
}
override fun onComplete() {
// 处理完整响应
val response = responseBuilder.toString()
if (response.isNotEmpty()) {
// 播放AI回复
Log.d(TAG, "AI 回复: $response")
speakAIResponse(response)
// 同步聊天记录到Flutter端
sendChatHistoryUpdate("personal_assistant", text, response)
}
}
override fun onError(e: Exception) {
FileLogger.e(TAG, "AI流式处理出错: ${e.message}", e)
callback?.onError("AI处理出错")
}
override fun onFunctionCall(call: JSONObject) {
FileLogger.d(TAG, "processWithOpenAI 收到函数调用请求: ${call.getString("name")}")
}
}
)
} catch (e: Exception) {
FileLogger.e(TAG, "AI处理出错: ${e.message}", e)
callback?.onError("AI处理出错")
}
}.start()
}
/**
* 播放TTS
*/
fun playTts(text: String, callback: TtsCallback? = null) {
isTtsSpeaking = true
notifyStateChanged()
azureTtsHelper?.speakText(text, object : AzureTtsHelper.TTSCallback {
override fun onSuccess(message: String) {
isTtsSpeaking = false
notifyStateChanged()
callback?.onComplete()
}
override fun onError(error: String) {
isTtsSpeaking = false
notifyStateChanged()
callback?.onError(error)
}
})
}
/**
* 停止TTS播放
*/
fun stopTts() {
if (isTtsSpeaking) {
azureTtsHelper?.stopSpeaking()
isTtsSpeaking = false
notifyStateChanged()
}
}
/**
* 播放AI回复
*/
private fun speakAIResponse(text: String) {
isTtsSpeaking = true
notifyStateChanged()
azureTtsHelper?.speakText(text, object : AzureTtsHelper.TTSCallback {
override fun onSuccess(message: String) {
isTtsSpeaking = false
notifyStateChanged()
}
override fun onError(error: String) {
isTtsSpeaking = false
notifyStateChanged()
}
})
}
/**
* 释放资源
*/
fun dispose() {
// 停止语音识别
stopRecognition()
// 停止TTS播放
stopTts()
// 注销广播接收器
try {
context.unregisterReceiver(exitInteractionReceiver)
} catch (e: Exception) {
FileLogger.e(TAG, "注销广播接收器失败: ${e.message}", e)
}
// 释放Azure资源
azureAsrHelper?.let {
FileLogger.d(TAG, "关闭Azure ASR服务")
it.dispose()
}
azureTtsHelper?.let {
FileLogger.d(TAG, "关闭Azure TTS服务")
it.dispose()
}
FileLogger.d(TAG, "语音交互处理器资源已释放")
}
/**
* 检查录音权限
*/
private fun checkRecordAudioPermission(): Boolean {
val permission = android.Manifest.permission.RECORD_AUDIO
val result = context.checkCallingOrSelfPermission(permission)
return result == android.content.pm.PackageManager.PERMISSION_GRANTED
}
/**
* 通知状态变化
*/
private fun notifyStateChanged() {
callback?.onStateChanged(isRecognitionActive, isTtsSpeaking, hasSpeechDetected)
}
/**
* 发送聊天历史更新
*/
private fun sendChatHistoryUpdate(agentId: String, userMessage: String, assistantMessage: String) {
val intent = android.content.Intent(VoiceInteractionService.ACTION_CHAT_HISTORY_UPDATED).apply {
putExtra("agentId", agentId)
putExtra("userMessage", userMessage)
putExtra("assistantMessage", assistantMessage)
putExtra("timestamp", System.currentTimeMillis())
setPackage(context.packageName)
}
// 发送广播
context.sendBroadcast(intent)
}
/**
* 交互回调接口
*/
interface InteractionCallback {
fun onStateChanged(isRecognitionActive: Boolean, isTtsSpeaking: Boolean, hasSpeechDetected: Boolean)
fun onError(message: String)
fun onPromptRequest(message: String)
}
/**
* TTS回调接口
*/
interface TtsCallback {
fun onComplete()
fun onError(error: String)
}
}

383
android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt → android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionService.kt

@ -22,16 +22,21 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject
import android.media.MediaPlayer import android.media.MediaPlayer
import android.media.AudioAttributes import android.media.AudioAttributes
import android.net.Uri import android.net.Uri
import com.yunqiinnovation.deepsound.core.utils.FileLogger import com.yunqiinnovation.deepsound.core.utils.FileLogger
import com.yunqiinnovation.azure_speech.AzureAsrHelper
import com.yunqiinnovation.volcano_speech.VolcanoTtsHelper
import com.yunqiinnovation.open_ai_service.OpenAIService
/** /**
* 后台语音交互 Service: * 后台语音交互 Service:
* 1) 前台服务,确保不会被系统轻易杀死 * 1) 前台服务,确保不会被系统轻易杀死
* 2) MediaSession 捕获蓝牙耳机按键 * 2) MediaSession 捕获蓝牙耳机按键
* 3) 处理录音/语音识别 * 3) 负责唤醒控制和服务生命周期管理
*/ */
class VoiceInteractionService : Service() { class VoiceInteractionService : Service() {
@ -41,7 +46,7 @@ class VoiceInteractionService : Service() {
private const val CHANNEL_ID = "voice_interaction_channel" private const val CHANNEL_ID = "voice_interaction_channel"
// 语音识别超时时间(毫秒) // 语音识别超时时间(毫秒)
private const val RECOGNITION_TIMEOUT = 8000L private const val RECOGNITION_TIMEOUT = 10000L
// 用于跟踪服务是否正在运行 // 用于跟踪服务是否正在运行
private val isRunning = AtomicBoolean(false) private val isRunning = AtomicBoolean(false)
@ -53,14 +58,12 @@ class VoiceInteractionService : Service() {
const val ACTION_RECOGNITION_STARTED = "com.yunqiinnovation.deepsound.ACTION_RECOGNITION_STARTED" const val ACTION_RECOGNITION_STARTED = "com.yunqiinnovation.deepsound.ACTION_RECOGNITION_STARTED"
const val ACTION_PAUSE_VOICE_INTERACTION = "com.yunqiinnovation.deepsound.ACTION_PAUSE_VOICE_INTERACTION" const val ACTION_PAUSE_VOICE_INTERACTION = "com.yunqiinnovation.deepsound.ACTION_PAUSE_VOICE_INTERACTION"
const val ACTION_CHAT_HISTORY_UPDATED = "com.yunqiinnovation.deepsound.ACTION_CHAT_HISTORY_UPDATED" const val ACTION_CHAT_HISTORY_UPDATED = "com.yunqiinnovation.deepsound.ACTION_CHAT_HISTORY_UPDATED"
const val ACTION_ENTER_TRANSLATION_MODE = "com.yunqiinnovation.deepsound.ACTION_ENTER_TRANSLATION_MODE"
} }
// 服务状态 // 服务状态
private var isActive = false // 服务是否活跃 private var isActive = false // 服务是否活跃
private var isRecognitionActive = false // 语音识别是否活跃
private var isTimeoutPaused = false // 是否因超时暂停 private var isTimeoutPaused = false // 是否因超时暂停
private var hasSpeechDetected = false // 是否检测到语音
private var isTtsSpeaking = false // 是否正在播放TTS
// 按键处理 // 按键处理
private var lastKeyEventTime = 0L private var lastKeyEventTime = 0L
@ -69,16 +72,12 @@ class VoiceInteractionService : Service() {
// 活动时间 // 活动时间
private var lastActivityTime = 0L private var lastActivityTime = 0L
// 当前用户输入
private var currentUserInput = ""
// 服务组件 // 服务组件
private lateinit var mediaSession: MediaSessionCompat private lateinit var mediaSession: MediaSessionCompat
private lateinit var audioManager: AudioManager private lateinit var audioManager: AudioManager
private lateinit var azureAsrHelper: AzureAsrHelper
private lateinit var azureTtsHelper: AzureTtsHelper // 语音交互处理器
private lateinit var volcanoAIService: VolcanoAIService private lateinit var voiceInteractionHandler: VoiceInteractionHandler
// 定时器 // 定时器
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
@ -88,15 +87,6 @@ class VoiceInteractionService : Service() {
handler.postDelayed(this, 1000) // 每秒执行一次 handler.postDelayed(this, 1000) // 每秒执行一次
} }
} }
// 系统提示词
private val systemPrompt = """
你是一个智能语音助手,能够简洁明了地回答用户的问题。
请保持回答简短、准确,避免过长的解释。
如果用户的问题不清楚,请礼貌地请求澄清。
不要使用复杂的术语,除非用户明确要求。
用户用语音和你交互.
""".trimIndent()
// 添加媒体播放器 // 添加媒体播放器
private var audioPlayer: MiniMediaPlayer? = null private var audioPlayer: MiniMediaPlayer? = null
@ -115,7 +105,9 @@ class VoiceInteractionService : Service() {
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
FileLogger.d(TAG, "AudioManager初始化完成") FileLogger.d(TAG, "AudioManager初始化完成")
initServices() // 初始化语音交互处理器
initVoiceInteractionHandler()
initMediaSession() initMediaSession()
registerMediaButtonReceiver() registerMediaButtonReceiver()
@ -125,7 +117,6 @@ class VoiceInteractionService : Service() {
// 设置为媒体播放状态 // 设置为媒体播放状态
setPlaybackState(PlaybackStateCompat.STATE_PAUSED) setPlaybackState(PlaybackStateCompat.STATE_PAUSED)
// FileLogger.d(TAG, "设置播放状态为STATE_PAUSED")
// 启动监控和前台服务 // 启动监控和前台服务
startMonitoring() startMonitoring()
@ -139,28 +130,27 @@ class VoiceInteractionService : Service() {
*/ */
private fun resetState() { private fun resetState() {
isActive = false isActive = false
isRecognitionActive = false
isTimeoutPaused = false isTimeoutPaused = false
hasSpeechDetected = false
isTtsSpeaking = false
} }
/** /**
* 初始化所有服务 * 初始化语音交互处理器
*/ */
private fun initServices() { private fun initVoiceInteractionHandler() {
// 创建新的Azure服务实例 FileLogger.d(TAG, "初始化语音交互处理器")
FileLogger.d(TAG, "创建新的Azure服务实例")
azureAsrHelper = AzureAsrHelper(this)
azureTtsHelper = AzureTtsHelper(this)
// 尝试从静态变量获取配置 // 尝试从静态变量获取配置
var subscriptionKey = MainActivity.azureSpeechKey var subscriptionKey = MainActivity.azureSpeechKey
var serviceRegion = MainActivity.azureSpeechRegion var serviceRegion = MainActivity.azureSpeechRegion
var volcanoKey = MainActivity.volcanoAiApiKey var openaiKey = MainActivity.openaiApiKey
var openaiBaseUrl = MainActivity.openaiBaseUrl ?: "" // OpenAI API基本URL
var openaiModel = MainActivity.openaiModel ?: "" // OpenAI模型
var volcanoSpeechAppId = MainActivity.volcanoSpeechAppId ?: ""
var volcanoSpeechAppToken = MainActivity.volcanoSpeechAppToken ?: ""
var mcpServer = MainActivity.mcpServer ?: ""
// 如果静态变量中没有配置,尝试从加密存储中加载 // 如果静态变量中没有配置,尝试从加密存储中加载
if (subscriptionKey.isEmpty() || serviceRegion.isEmpty() || volcanoKey.isEmpty()) { if (subscriptionKey.isEmpty() || serviceRegion.isEmpty() || openaiKey.isEmpty()) {
FileLogger.d(TAG, "静态变量中的配置信息不完整,尝试从加密存储加载") FileLogger.d(TAG, "静态变量中的配置信息不完整,尝试从加密存储加载")
// 从加密存储加载密钥 // 从加密存储加载密钥
@ -170,36 +160,47 @@ class VoiceInteractionService : Service() {
// 更新本地变量 // 更新本地变量
subscriptionKey = MainActivity.azureSpeechKey subscriptionKey = MainActivity.azureSpeechKey
serviceRegion = MainActivity.azureSpeechRegion serviceRegion = MainActivity.azureSpeechRegion
volcanoKey = MainActivity.volcanoAiApiKey openaiKey = MainActivity.openaiApiKey
openaiBaseUrl = MainActivity.openaiBaseUrl ?: ""
openaiModel = MainActivity.openaiModel ?: ""
volcanoSpeechAppId = MainActivity.volcanoSpeechAppId ?: ""
volcanoSpeechAppToken = MainActivity.volcanoSpeechAppToken ?: ""
mcpServer = MainActivity.mcpServer ?: ""
FileLogger.d(TAG, "已从加密存储加载配置信息") FileLogger.d(TAG, "已从加密存储加载配置信息")
} else { } else {
FileLogger.e(TAG, "无法从加密存储加载配置信息") FileLogger.e(TAG, "无法从加密存储加载配置信息")
} }
} }
// 初始化语音服务 // 初始化语音交互处理器
if (subscriptionKey.isNotEmpty() && serviceRegion.isNotEmpty()) { voiceInteractionHandler = VoiceInteractionHandler(applicationContext,
// 初始化ASR subscriptionKey, serviceRegion,
azureAsrHelper.initialize(subscriptionKey, serviceRegion, arrayOf("zh-CN")) openaiKey, openaiBaseUrl, openaiModel,
volcanoSpeechAppId, volcanoSpeechAppToken,
mcpServer)
// 初始化回调
voiceInteractionHandler.setCallback(object : VoiceInteractionHandler.InteractionCallback {
override fun onStateChanged(isRecognitionActive: Boolean, isTtsSpeaking: Boolean, hasSpeechDetected: Boolean) {
// 更新活动时间
updateLastActivityTime()
}
// 初始化TTS override fun onError(message: String) {
azureTtsHelper.initialize(subscriptionKey, serviceRegion, "zh-CN") playNotification(message)
}
FileLogger.d(TAG, "Azure语音服务已初始化") override fun onPromptRequest(message: String) {
} else { playPrompt(message)
FileLogger.e(TAG, "Azure配置信息不完整,无法初始化Azure服务") }
} })
// 初始化火山AI服务
volcanoAIService = VolcanoAIService()
// 初始化火山AI服务 // 初始化处理器
if (volcanoKey.isNotEmpty()) { val initialized = voiceInteractionHandler.initialize()
volcanoAIService.initialize(volcanoKey) if (initialized) {
FileLogger.d(TAG, "火山AI服务已初始化") FileLogger.d(TAG, "语音交互处理器初始化成功")
} else { } else {
FileLogger.e(TAG, "火山AI配置信息不完整,无法初始化火山AI服务") FileLogger.e(TAG, "语音交互处理器初始化失败")
} }
} }
@ -302,24 +303,23 @@ class VoiceInteractionService : Service() {
if (!isActive) { if (!isActive) {
isActive = true isActive = true
} }
// FileLogger.d(TAG, "服务状态: isActive=${isActive}, isRecognitionActive=${voiceInteractionHandler.isRecognitionActive}, isTimeoutPaused=${isTimeoutPaused}")
// 检查语音识别状态 // 检查语音识别状态
if (isRecognitionActive) { if (voiceInteractionHandler.isRecognitionActive) {
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
val elapsedTime = currentTime - lastActivityTime val elapsedTime = currentTime - lastActivityTime
// FileLogger.d(TAG, "hasSpeechDetected=${voiceInteractionHandler.hasSpeechDetected}, isTtsSpeaking=${voiceInteractionHandler.isTtsSpeaking}, elapsedTime=${elapsedTime}")
// 如果超过5秒没有检测到语音,且不在TTS播放中,暂停语音识别 // 如果超过指定时间没有检测到语音,且不在TTS播放中,暂停语音识别
if (!hasSpeechDetected && !isTtsSpeaking && elapsedTime >= RECOGNITION_TIMEOUT) { if (!voiceInteractionHandler.hasSpeechDetected &&
!voiceInteractionHandler.isTtsSpeaking &&
elapsedTime >= RECOGNITION_TIMEOUT) {
FileLogger.d(TAG, "超过${RECOGNITION_TIMEOUT/1000}秒未检测到语音,停止识别") FileLogger.d(TAG, "超过${RECOGNITION_TIMEOUT/1000}秒未检测到语音,停止识别")
isTimeoutPaused = true isTimeoutPaused = true
playNotification("没有听到您说话,已暂停对话。双击耳机按钮可重新开始。") playNotification("没有听到您说话,已暂停对话。双击耳机按钮可重新开始。")
// 停止语音识别并确保资源完全释放 // 停止语音识别
stopVoiceRecognition() voiceInteractionHandler.stopRecognition()
// 清理识别状态
isRecognitionActive = false
hasSpeechDetected = false
} }
} }
} }
@ -340,14 +340,12 @@ class VoiceInteractionService : Service() {
lastKeyEventTime = currentTime lastKeyEventTime = currentTime
// 停止当前TTS播放 // 停止当前TTS播放
stopCurrentTTS() voiceInteractionHandler.stopTts()
setPlaybackState(PlaybackStateCompat.STATE_PLAYING) setPlaybackState(PlaybackStateCompat.STATE_PLAYING)
// 播放提示音 // 播放提示音
playPrompt("我在!") playPrompt("我在!")
// audioPlayer?.play(R.raw.listening)
// 设置为播放状态 // 设置为播放状态
setPlaybackState(PlaybackStateCompat.STATE_PAUSED) setPlaybackState(PlaybackStateCompat.STATE_PAUSED)
@ -355,221 +353,48 @@ class VoiceInteractionService : Service() {
// 重置超时暂停标志 // 重置超时暂停标志
isTimeoutPaused = false isTimeoutPaused = false
// 启动或重置语音识别 // 启动语音识别
if (!isRecognitionActive) { if (!voiceInteractionHandler.isRecognitionActive) {
FileLogger.d(TAG, "语音识别未激活,开始启动") FileLogger.d(TAG, "语音识别未激活,开始启动")
// 如果之前是因为超时暂停,重新初始化语音识别组件 // 通知 Flutter 语音识别已启动
// if (isTimeoutPaused) { notifyRecognitionStarted()
// Log.d(TAG, "之前因超时暂停,重新初始化Azure服务")
// restartAsr()
// }
startVoiceRecognition() // 启动语音识别
voiceInteractionHandler.startRecognition()
} else { } else {
FileLogger.d(TAG, "语音识别已激活,更新活动时间") FileLogger.d(TAG, "语音识别已激活,更新活动时间")
updateLastActivityTime() updateLastActivityTime()
hasSpeechDetected = false
}
}
/**
* 开始语音识别
*/
private fun startVoiceRecognition() {
if (isRecognitionActive) return
// 检查录音权限
if (!checkRecordAudioPermission()) {
playNotification("需要录音权限,请在设置中授予权限")
return
}
// 通知 Flutter 语音识别已启动
notifyRecognitionStarted()
isActive = true
isRecognitionActive = true
hasSpeechDetected = false
updateLastActivityTime()
try {
azureAsrHelper.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onRecognizing(recognizing: String, detectedLanguage: String) {
if (recognizing.isNotEmpty()) {
hasSpeechDetected = true
stopCurrentTTS()
updateLastActivityTime()
}
}
override fun onResult(result: String, detectedLanguage: String) {
if (result.isNotEmpty()) {
processWithVolcanoAI(result)
}
// 重置状态,继续识别
hasSpeechDetected = false
updateLastActivityTime()
}
override fun onSessionStarted() {
updateLastActivityTime()
}
override fun onSessionStopped() {
isRecognitionActive = false
}
override fun onCanceled(reason: String, errorDetails: String) {
isRecognitionActive = false
}
override fun onError(error: String) {
isRecognitionActive = false
playNotification("语音识别出错")
}
})
} catch (e: Exception) {
isRecognitionActive = false
FileLogger.e(TAG, "启动语音识别失败: ${e.message}", e)
playNotification("启动语音识别失败")
}
}
/**
* 停止语音识别
*/
private fun stopVoiceRecognition() {
if (!isRecognitionActive) return
FileLogger.d(TAG, "停止语音识别")
try {
azureAsrHelper.stopContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onResult(result: String, detectedLanguage: String) {}
override fun onRecognizing(recognizing: String, detectedLanguage: String) {}
override fun onSessionStarted() {}
override fun onSessionStopped() {
isRecognitionActive = false
FileLogger.d(TAG, "语音识别会话已停止")
}
override fun onCanceled(reason: String, errorDetails: String) {
isRecognitionActive = false
FileLogger.d(TAG, "语音识别已取消: $reason")
}
override fun onError(error: String) {
isRecognitionActive = false
FileLogger.e(TAG, "停止语音识别时出错: $error")
}
})
} catch (e: Exception) {
FileLogger.e(TAG, "停止语音识别异常: ${e.message}", e)
// 确保状态一致性
isRecognitionActive = false
} }
setPlaybackState(PlaybackStateCompat.STATE_PAUSED)
isRecognitionActive = false
hasSpeechDetected = false
// 不重置 isTimeoutPaused,保留暂停原因
}
/**
* 使用VolcanoAI处理语音识别结果
*/
private fun processWithVolcanoAI(text: String) {
// 保存当前用户输入,用于后续同步聊天记录
currentUserInput = text
Thread {
try {
val messages = JSONArray().apply {
put(volcanoAIService.createUserMessage(text))
}
val response = volcanoAIService.sendMessage(messages, systemPrompt)
// 播放AI回复
speakAIResponse(response)
// 同步聊天记录到Flutter端
notifyChatHistoryUpdated("personal_assistant", text, response)
} catch (e: Exception) {
FileLogger.e(TAG, "AI处理出错: ${e.message}")
playNotification("AI处理出错")
}
}.start()
}
/**
* 播放AI回复
*/
private fun speakAIResponse(text: String) {
isTtsSpeaking = true
azureTtsHelper.speakText(text, object : AzureTtsHelper.TTSCallback {
override fun onSuccess(message: String) {
isTtsSpeaking = false
updateLastActivityTime()
}
override fun onError(error: String) {
isTtsSpeaking = false
}
})
} }
/** /**
* 播放提示音 * 播放提示音
*/ */
private fun playPrompt(message: String) { private fun playPrompt(message: String) {
isTtsSpeaking = true voiceInteractionHandler.playTts(message)
azureTtsHelper.speakText(message, object : AzureTtsHelper.TTSCallback {
override fun onSuccess(message: String) { isTtsSpeaking = false }
override fun onError(error: String) { isTtsSpeaking = false }
})
} }
/** /**
* 播放通知提示音 * 播放通知提示音
*/ */
private fun playNotification(message: String) { private fun playNotification(message: String) {
isTtsSpeaking = true
// 更新播放状态为播放中,增加接收蓝牙按键事件的几率 // 更新播放状态为播放中,增加接收蓝牙按键事件的几率
setPlaybackState(PlaybackStateCompat.STATE_PLAYING) setPlaybackState(PlaybackStateCompat.STATE_PLAYING)
// 确保媒体会话处于活跃状态 // 确保媒体会话处于活跃状态
mediaSession.isActive = true mediaSession.isActive = true
azureTtsHelper.speakText(message, object : AzureTtsHelper.TTSCallback { voiceInteractionHandler.playTts(message, object : VoiceInteractionHandler.TtsCallback {
override fun onSuccess(message: String) { override fun onComplete() {
isTtsSpeaking = false
setPlaybackState(PlaybackStateCompat.STATE_PAUSED) setPlaybackState(PlaybackStateCompat.STATE_PAUSED)
} }
override fun onError(error: String) { override fun onError(error: String) {
isTtsSpeaking = false
setPlaybackState(PlaybackStateCompat.STATE_PAUSED) setPlaybackState(PlaybackStateCompat.STATE_PAUSED)
} }
}) })
} }
/**
* 停止当前TTS播放
*/
private fun stopCurrentTTS() {
if (isTtsSpeaking) {
azureTtsHelper.stopSpeaking()
isTtsSpeaking = false
}
}
/** /**
* 更新最后活动时间 * 更新最后活动时间
*/ */
@ -603,15 +428,6 @@ class VoiceInteractionService : Service() {
} }
} }
/**
* 检查录音权限
*/
private fun checkRecordAudioPermission(): Boolean {
val permission = android.Manifest.permission.RECORD_AUDIO
val result = applicationContext.checkCallingOrSelfPermission(permission)
return result == android.content.pm.PackageManager.PERMISSION_GRANTED
}
/** /**
* 启动前台服务 * 启动前台服务
*/ */
@ -746,38 +562,32 @@ class VoiceInteractionService : Service() {
override fun onBind(intent: Intent?): IBinder? = null override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() { override fun onDestroy() {
FileLogger.d(TAG, "onDestroy - 语音交互服务正在销毁") super.onDestroy()
FileLogger.d(TAG, "onDestroy - 语音交互服务即将销毁")
// 释放音频播放器
audioPlayer?.release()
audioPlayer = null
// 停止监控 // 停止服务监控
stopMonitoring() stopMonitoring()
// 停止语音识别 // 停止语音识别
if (isRecognitionActive) { voiceInteractionHandler.stopRecognition()
stopVoiceRecognition()
} // 停止媒体会话
mediaSession.release()
FileLogger.d(TAG, "媒体会话已释放")
// 停止TTS // 停止TTS
stopCurrentTTS() voiceInteractionHandler.stopTts()
// 释放 MediaSession // 释放语音交互处理器资源
mediaSession.release() voiceInteractionHandler.dispose()
// 释放Azure服务实例 // 关闭音频播放器
FileLogger.d(TAG, "释放Azure服务实例") audioPlayer?.release()
azureAsrHelper.dispose()
azureTtsHelper.dispose()
// 更新服务状态 // 设置服务状态
isRunning.set(false) isRunning.set(false)
// 关闭日志系统 FileLogger.d(TAG, "语音交互服务已销毁")
FileLogger.shutdown()
super.onDestroy()
} }
/** /**
@ -788,11 +598,11 @@ class VoiceInteractionService : Service() {
FileLogger.d(TAG, "暂停后台语音交互(来自Flutter的请求)") FileLogger.d(TAG, "暂停后台语音交互(来自Flutter的请求)")
// 停止当前TTS播放 // 停止当前TTS播放
stopCurrentTTS() voiceInteractionHandler.stopTts()
// 停止语音识别 // 停止语音识别
if (isRecognitionActive) { if (voiceInteractionHandler.isRecognitionActive) {
stopVoiceRecognition() voiceInteractionHandler.stopRecognition()
} }
// 设置为暂停状态,但保持服务活跃 // 设置为暂停状态,但保持服务活跃
@ -804,10 +614,6 @@ class VoiceInteractionService : Service() {
* 通知 Flutter 端聊天记录已更新 * 通知 Flutter 端聊天记录已更新
*/ */
private fun notifyChatHistoryUpdated(agentId: String, userMessage: String, assistantMessage: String) { private fun notifyChatHistoryUpdated(agentId: String, userMessage: String, assistantMessage: String) {
FileLogger.d(TAG, "通知Flutter聊天记录已更新: agentId=$agentId")
FileLogger.d(TAG, "用户消息: ${userMessage.take(50)}...")
FileLogger.d(TAG, "助手回复: ${assistantMessage.take(50)}...")
// 创建广播 Intent // 创建广播 Intent
val intent = Intent(ACTION_CHAT_HISTORY_UPDATED).apply { val intent = Intent(ACTION_CHAT_HISTORY_UPDATED).apply {
putExtra("agentId", agentId) putExtra("agentId", agentId)
@ -818,7 +624,6 @@ class VoiceInteractionService : Service() {
// 发送广播 // 发送广播
sendBroadcast(intent) sendBroadcast(intent)
FileLogger.d(TAG, "已发送聊天记录广播")
} }
/** /**
@ -831,6 +636,7 @@ class VoiceInteractionService : Service() {
val intent = Intent(ACTION_RECOGNITION_STARTED).apply { val intent = Intent(ACTION_RECOGNITION_STARTED).apply {
// 可以添加额外数据 // 可以添加额外数据
putExtra("timestamp", System.currentTimeMillis()) putExtra("timestamp", System.currentTimeMillis())
putExtra("packageName", applicationContext.packageName)
} }
// 发送广播 // 发送广播
@ -909,5 +715,4 @@ class VoiceInteractionService : Service() {
*/ */
fun isPlaying() = mediaPlayer?.isPlaying == true fun isPlaying() = mediaPlayer?.isPlaying == true
} }
} }

0
android/app/src/main/kotlin/com/example/deep_voice/core/utils/FileLogger.kt → android/app/src/main/kotlin/com/yunqiinnovation/deepsound/core/utils/FileLogger.kt

19
android/build.gradle.kts

@ -1,7 +1,15 @@
allprojects { allprojects {
repositories { repositories {
mavenLocal()
google() google()
mavenCentral() mavenCentral()
maven { url = uri("https://storage.googleapis.com/download.flutter.io") }
// 添加火山引擎Maven仓库
maven {
url = uri("https://artifact.bytedance.com/repository/Volcengine/")
}
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/ktor/eap") }
} }
} }
@ -20,3 +28,14 @@ subprojects {
tasks.register<Delete>("clean") { tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory) delete(rootProject.layout.buildDirectory)
} }
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.10")
// 其他依赖...
}
}

20
android/settings.gradle.kts

@ -16,6 +16,7 @@ pluginManagement {
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories { repositories {
mavenLocal()
google() google()
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
@ -24,9 +25,22 @@ pluginManagement {
plugins { plugins {
id("dev.flutter.flutter-plugin-loader") id("dev.flutter.flutter-plugin-loader")
id("com.android.application") version "8.7.0" apply false id("com.android.application") version "8.8.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false id("com.android.library") version "8.8.0" apply false
id("org.jetbrains.kotlin.android") version "2.1.10" apply false
} }
include(":app") include(":app")
include(":azure_speech")
include(":open_ai_service")
include(":volcano_speech")
include(":chat_storage")
include(":jieli")
include(":spotify_sdk")
// 设置azure_speech项目的路径
project(":azure_speech").projectDir = file("../local_plugins/azure_speech/android")
project(":open_ai_service").projectDir = file("../local_plugins/open_ai_service/android")
project(":volcano_speech").projectDir = file("../local_plugins/volcano_speech/android")
project(":chat_storage").projectDir = file("../local_plugins/chat_storage/android")
project(":jieli").projectDir = file("../local_plugins/jieli/android")
project(":spotify_sdk").projectDir = file("../local_plugins/spotify_sdk/android")

21
azure/LICENSE

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Your Company
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

66
azure/README.md

@ -1,66 +0,0 @@
# Azure Speech Recognition
A Flutter plugin for Microsoft Azure Speech services, providing both speech recognition (ASR) and text-to-speech (TTS) capabilities.
## Features
- Speech-to-text (Azure Speech Recognition)
- Text-to-speech (Azure Speech Synthesis)
- Support for multiple languages
- Language detection
- Continuous recognition
- Streaming synthesis
## Getting Started
### Prerequisites
- Azure Speech service subscription key
- Azure Speech service region
### Installation
Add this to your package's `pubspec.yaml` file:
```yaml
dependencies:
azure_speech_recognition:
path: ./azure
```
### Usage
```dart
import 'package:azure_speech_recognition/azure_speech_recognition.dart';
// Initialize the service
await AzureSpeechRecognition.initialize(
subscriptionKey: 'your_subscription_key',
region: 'your_region',
supportedLanguages: ['zh-CN', 'en-US'],
);
// Start continuous recognition
await AzureSpeechRecognition.startContinuousRecognition();
// Listen for recognition events
AzureSpeechRecognition.onRecognitionEvent.listen((event) {
if (event['type'] == 'result') {
print('Recognized: ${event['text']}');
print('Detected language: ${event['detectedLanguage']}');
}
});
// Stop recognition when done
await AzureSpeechRecognition.stopContinuousRecognition();
// Speak text
await AzureSpeechRecognition.speakText('Hello, world!');
// Clean up
await AzureSpeechRecognition.dispose();
```
## License
This project is licensed under the MIT License - see the LICENSE file for details.

757
azure/ios/Classes/AzureAsrHelper.swift

@ -1,757 +0,0 @@
import Foundation
import MicrosoftCognitiveServicesSpeech
import AVFoundation
import AudioToolbox
/// Azure ASR工具类,负责实现语音识别服务接口
@available(iOS 13.0, *)
class AzureAsrHelper: NSObject {
// MARK: - 属性
/// 事件处理回调
private var eventHandler: (String, [String: Any]) -> Void
/// 语音配置信息
private var speechSubscriptionKey: String = ""
private var serviceRegion: String = ""
/// 语音识别相关
private var speechConfig: SPXSpeechConfiguration?
private var recognizer: SPXSpeechRecognizer?
private var audioConfig: SPXAudioConfiguration?
private var pushStream: SPXPushAudioInputStream?
/// 音频处理相关
private var audioProcessor: CustomAudioProcessor?
private var isProcessingAudio = false
private var audioProcessingTimer: Timer?
/// 状态标志
private var isInitialized = false
private var _isContinuousRecognitionActive = false
/// 当前语言和支持的语言
private var currentLanguage = "zh-CN"
private var supportedLanguages: [String] = ["zh-CN", "en-US"]
private var isAutoDetectLanguage = false
// MARK: - 初始化
init(eventHandler: @escaping (String, [String: Any]) -> Void) {
self.eventHandler = eventHandler
super.init()
}
deinit {
dispose()
}
// MARK: - ASR Service 接口实现
/// 初始化语音识别服务
/// - Parameters:
/// - speechSubscriptionKey: Azure 语音服务订阅密钥
/// - serviceRegion: Azure 服务区域 (如 eastasia)
/// - supportedLanguages: 支持的语言代码数组 (可选)
/// - Returns: 初始化是否成功
func initialize(speechSubscriptionKey: String, serviceRegion: String, supportedLanguages: [String]? = nil) -> Bool {
print("[AzureAsrHelper] 初始化 Azure 语音服务")
// 检查配置是否为空
if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty {
print("[AzureAsrHelper] 错误: Azure 配置信息不完整")
eventHandler("error", ["message": "Azure 配置信息不完整"])
return false
}
// 释放之前的资源
dispose()
// 记录配置信息
self.speechSubscriptionKey = speechSubscriptionKey
self.serviceRegion = serviceRegion
// 设置语言
if let languages = supportedLanguages, !languages.isEmpty {
self.supportedLanguages = languages
}
// 根据支持的语言数量决定是否启用自动语言检测
isAutoDetectLanguage = self.supportedLanguages.count >= 2
// 如果只有一种语言,设置为当前语言
if !isAutoDetectLanguage && !self.supportedLanguages.isEmpty {
currentLanguage = self.supportedLanguages[0]
}
// 创建识别器和设置回调
if !createRecognizerAndSetupCallbacks() {
return false
}
print("[AzureAsrHelper] Azure 语音服务初始化成功")
isInitialized = true
return true
}
/// 创建识别器并设置回调
private func createRecognizerAndSetupCallbacks() -> Bool {
// 释放之前的 recognizer
recognizer = nil
audioConfig = nil
do {
// 创建语音配置
speechConfig = try SPXSpeechConfiguration(subscription: speechSubscriptionKey, region: serviceRegion)
// 设置音频输入参数
try setupAudioSession()
// 创建自定义推送流,替代默认的麦克风输入
pushStream = try SPXPushAudioInputStream()
audioConfig = try SPXAudioConfiguration(streamInput: pushStream!)
// 初始化自定义音频处理器
audioProcessor = CustomAudioProcessor()
// 设置语言配置
if isAutoDetectLanguage {
// 设置自动语言检测
speechConfig?.setPropertyTo("Continuous", by: SPXPropertyId.speechServiceConnectionLanguageIdMode)
// 创建自动语言检测配置
let autoDetectSourceLanguageConfig = try SPXAutoDetectSourceLanguageConfiguration(supportedLanguages)
// 创建识别器
recognizer = try SPXSpeechRecognizer(
speechConfiguration: speechConfig!,
autoDetectSourceLanguageConfiguration: autoDetectSourceLanguageConfig,
audioConfiguration: audioConfig!
)
} else {
// 设置指定的识别语言
speechConfig?.speechRecognitionLanguage = currentLanguage
// 创建识别器
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, audioConfiguration: audioConfig!)
}
// 设置所有回调
setupAllCallbacks()
return true
} catch {
print("[AzureAsrHelper] 错误: 创建识别器失败: \(error.localizedDescription)")
eventHandler("error", ["message": "创建识别器失败: \(error.localizedDescription)"])
return false
}
}
/// 设置音频会话
private func setupAudioSession() throws {
let audioSession = AVAudioSession.sharedInstance()
// 使用playAndRecord类别允许同时录音和播放
try audioSession.setCategory(.playAndRecord,
mode: .voiceChat, // 使用voiceChat模式能够更好地支持回音消除
options: [.allowBluetooth, .defaultToSpeaker, .allowAirPlay, .mixWithOthers])
// 设置首选的输入和输出
let currentRoute = audioSession.currentRoute
// 获取当前是否连接了耳机或外部麦克风
let hasHeadphones = currentRoute.outputs.contains {
$0.portType == .headphones || $0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP
}
// 如果没有耳机,明确启用内置麦克风和扬声器的回音消除
if !hasHeadphones {
try audioSession.setMode(.voiceChat) // 语音聊天模式有更强的回音消除
// 启用回音消除和噪声抑制
try audioSession.setInputGain(0.8) // 适当降低输入增益以减少扬声器音频被麦克风捕获的可能性
} else {
// 耳机模式,可以使用不同的设置
try audioSession.setMode(.voiceChat)
try audioSession.setInputGain(1.0)
}
// 设置合适的采样率
try audioSession.setPreferredSampleRate(16000.0) // Azure语音识别推荐的采样率
try audioSession.setPreferredIOBufferDuration(0.01) // 较小的缓冲区大小以减少延迟
// 激活音频会话
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
print("[AzureAsrHelper] 音频会话配置成功,已启用回音消除")
}
/// 设置所有回调
private func setupAllCallbacks() {
guard let recognizer = recognizer else { return }
// 最终识别结果
recognizer.addRecognizedEventHandler { [weak self] _, event in
guard let self = self else { return }
if event.result.reason == SPXResultReason.recognizedSpeech {
let detectedLanguage = self.getDetectedLanguage(from: event.result)
print("[AzureAsrHelper] 识别结果: \(event.result.text ?? ""), 语言: \(detectedLanguage)")
self.eventHandler("result", [
"text": event.result.text ?? "",
"detectedLanguage": detectedLanguage
])
}
}
// 识别中事件
recognizer.addRecognizingEventHandler { [weak self] _, event in
guard let self = self else { return }
if event.result.reason == SPXResultReason.recognizingSpeech {
let detectedLanguage = self.getDetectedLanguage(from: event.result)
// print("[AzureAsrHelper] 识别中: \(event.result.text ?? ""), 语言: \(detectedLanguage)")
self.eventHandler("recognizing", [
"text": event.result.text ?? "",
"detectedLanguage": detectedLanguage
])
}
}
// 会话事件
recognizer.addSessionStartedEventHandler { [weak self] _, _ in
guard let self = self else { return }
print("[AzureAsrHelper] 识别会话已开始")
self._isContinuousRecognitionActive = true
self.eventHandler("sessionStarted", [:])
}
recognizer.addSessionStoppedEventHandler { [weak self] _, _ in
guard let self = self else { return }
print("[AzureAsrHelper] 识别会话已结束")
self._isContinuousRecognitionActive = false
self.eventHandler("sessionStopped", [:])
}
// 取消事件
recognizer.addCanceledEventHandler { [weak self] _, event in
guard let self = self else { return }
let reason = event.reason.rawValue
let errorDetails = event.errorDetails ?? "未知错误"
print("[AzureAsrHelper] 识别取消: \(errorDetails)")
self.eventHandler("canceled", [
"reason": reason,
"errorDetails": errorDetails
])
self._isContinuousRecognitionActive = false
}
}
/// 执行一次性语音识别
/// - Returns: 是否成功启动识别
func recognizeOnce() -> Bool {
if !isInitialized {
print("[AzureAsrHelper] 错误: 语音服务未初始化")
eventHandler("error", ["message": "语音服务未初始化"])
return false
}
// 如果正在连续识别,先停止
if _isContinuousRecognitionActive {
stopContinuousRecognition()
}
// 确保识别器已创建
if recognizer == nil && !createRecognizerAndSetupCallbacks() {
return false
}
do {
// 启动音频处理
startAudioProcessing()
// 通知会话开始
eventHandler("sessionStarted", [:])
// 执行识别
try recognizer?.recognizeOnceAsync { [weak self] result in
guard let self = self else { return }
// 停止音频处理
self.stopAudioProcessing()
if result.reason == SPXResultReason.recognizedSpeech {
let detectedLanguage = self.getDetectedLanguage(from: result)
self.eventHandler("result", [
"text": result.text ?? "",
"detectedLanguage": detectedLanguage
])
} else if result.reason == SPXResultReason.noMatch {
print("[AzureAsrHelper] 无匹配结果")
self.eventHandler("noMatch", [:])
} else if result.reason == SPXResultReason.canceled {
do {
let details = try SPXCancellationDetails(fromCanceledRecognitionResult: result)
let errorDetails = details.errorDetails ?? "未知错误"
self.eventHandler("error", ["message": "识别取消: \(errorDetails)"])
} catch {
print("[AzureAsrHelper] 错误: 获取取消详情失败: \(error.localizedDescription)")
self.eventHandler("error", ["message": "识别取消,无法获取详细原因"])
}
}
}
return true
} catch {
print("[AzureAsrHelper] 错误: 识别异常: \(error.localizedDescription)")
eventHandler("error", ["message": "识别异常: \(error.localizedDescription)"])
stopAudioProcessing()
return false
}
}
/// 开始连续语音识别
/// - Returns: 是否成功启动识别
func startContinuousRecognition() -> Bool {
if !isInitialized {
print("[AzureAsrHelper] 错误: 语音服务未初始化")
eventHandler("error", ["message": "语音服务未初始化"])
return false
}
// 如果已经在进行连续识别,先停止
if _isContinuousRecognitionActive {
stopContinuousRecognition()
}
// 确保识别器已创建
if recognizer == nil && !createRecognizerAndSetupCallbacks() {
return false
}
// 重新确保音频设置正确
do {
try setupAudioSession()
} catch {
print("[AzureAsrHelper] 警告: 设置音频会话失败: \(error.localizedDescription)")
}
do {
// 启动音频处理
startAudioProcessing()
// 启动连续识别
try recognizer?.startContinuousRecognition()
_isContinuousRecognitionActive = true
print("[AzureAsrHelper] 连续识别开始")
return true
} catch {
print("[AzureAsrHelper] 错误: 开始连续识别失败: \(error.localizedDescription)")
eventHandler("error", ["message": "开始连续识别失败: \(error.localizedDescription)"])
_isContinuousRecognitionActive = false
stopAudioProcessing()
return false
}
}
/// 停止连续语音识别
/// - Returns: 是否成功停止识别
func stopContinuousRecognition() -> Bool {
// 停止音频处理
stopAudioProcessing()
if !_isContinuousRecognitionActive || recognizer == nil {
return true
}
do {
try recognizer?.stopContinuousRecognition()
_isContinuousRecognitionActive = false
print("[AzureAsrHelper] 连续识别已停止")
return true
} catch {
print("[AzureAsrHelper] 错误: 停止连续识别失败: \(error.localizedDescription)")
eventHandler("error", ["message": "停止连续识别失败: \(error.localizedDescription)"])
_isContinuousRecognitionActive = false
return false
}
}
/// 检查连续识别是否活跃
/// - Returns: 连续识别是否处于活跃状态
func isContinuousRecognitionActive() -> Bool {
return _isContinuousRecognitionActive
}
/// 释放资源
func dispose() {
print("[AzureAsrHelper] 释放资源")
// 停止音频处理
stopAudioProcessing()
// 停止连续识别
if _isContinuousRecognitionActive {
stopContinuousRecognition()
}
// 释放音频会话
do {
try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
} catch {
print("[AzureAsrHelper] 警告: 释放音频会话失败: \(error.localizedDescription)")
}
// 释放资源
recognizer = nil
speechConfig = nil
audioConfig = nil
pushStream = nil
audioProcessor = nil
// 重置状态
_isContinuousRecognitionActive = false
isInitialized = false
}
/// 从结果中获取检测到的语言
private func getDetectedLanguage(from result: SPXSpeechRecognitionResult) -> String {
if isAutoDetectLanguage {
do {
let langResult = try SPXAutoDetectSourceLanguageResult(result)
return langResult.language ?? currentLanguage
} catch {
print("[AzureAsrHelper] 错误: 获取检测到的语言失败: \(error.localizedDescription)")
return currentLanguage
}
} else {
return currentLanguage
}
}
// MARK: - 音频处理
/// 开始音频处理
private func startAudioProcessing() {
guard !isProcessingAudio, let audioProcessor = audioProcessor else { return }
isProcessingAudio = true
// 启动音频处理器
if !audioProcessor.startRecord() {
print("[AzureAsrHelper] 错误: 启动音频处理器失败")
eventHandler("error", ["message": "启动音频处理器失败"])
return
}
// 启动音频处理定时器
audioProcessingTimer = Timer.scheduledTimer(withTimeInterval: 0.08, repeats: true) { [weak self] _ in
guard let self = self, self.isProcessingAudio, let processor = self.audioProcessor, let stream = self.pushStream else {
return
}
// 读取处理后的音频数据
var bytes = [UInt8](repeating: 0, count: 2560)
let bytesRead = processor.read(bytes: &bytes)
if bytesRead > 0 {
// 推送数据到Azure语音服务
let data = Data(bytes: bytes, count: bytesRead)
stream.write(data)
// 通知音频数据可用
self.eventHandler("audioData", ["data": bytes])
}
}
print("[AzureAsrHelper] 音频处理已启动")
}
/// 停止音频处理
private func stopAudioProcessing() {
// 停止定时器
audioProcessingTimer?.invalidate()
audioProcessingTimer = nil
// 停止音频处理器
audioProcessor?.stopRecord()
isProcessingAudio = false
print("[AzureAsrHelper] 音频处理已停止")
}
}
// MARK: - 自定义音频处理器
@available(iOS 13.0, *)
class CustomAudioProcessor: NSObject {
// 音频单元
private var ioUnit: AudioUnit?
// 音频格式
private var audioFormat: AudioStreamBasicDescription
// 音频缓冲
private var audioBufferList: AudioBufferList
private var audioList: [Float] = []
private let audioListQueue = DispatchQueue(label: "audioListQueue")
// 回音消除状态
private var isEchoCancellationEnabled = true
override init() {
// 设置音频格式 - 16kHz, 16位, 单声道
audioFormat = AudioStreamBasicDescription(
mSampleRate: 16000.0,
mFormatID: kAudioFormatLinearPCM,
mFormatFlags: kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked,
mBytesPerPacket: 2,
mFramesPerPacket: 1,
mBytesPerFrame: 2,
mChannelsPerFrame: 1,
mBitsPerChannel: 16,
mReserved: 0
)
// 初始化音频缓冲
audioBufferList = AudioBufferList(
mNumberBuffers: 1,
mBuffers: AudioBuffer(
mNumberChannels: 1,
mDataByteSize: 4096,
mData: malloc(4096)
)
)
super.init()
}
deinit {
stopRecord()
free(audioBufferList.mBuffers.mData)
}
/// 启动音频处理
/// - Returns: 是否成功启动
func startRecord() -> Bool {
print("[CustomAudioProcessor] 配置音频单元")
// 创建音频组件描述 - 使用VoiceProcessingIO类型获取回音消除
var ioUnitDescription = AudioComponentDescription(
componentType: kAudioUnitType_Output,
componentSubType: kAudioUnitSubType_VoiceProcessingIO,
componentManufacturer: kAudioUnitManufacturer_Apple,
componentFlags: 0,
componentFlagsMask: 0
)
// 查找音频组件
guard let ioUnitRef = AudioComponentFindNext(nil, &ioUnitDescription) else {
print("[CustomAudioProcessor] 错误: 未找到音频组件")
return false
}
// 创建音频单元实例
if checkError(AudioComponentInstanceNew(ioUnitRef, &ioUnit), "创建音频单元") {
ioUnit = nil
return false
}
// 启用输入端口
var enableInput: UInt32 = 1
let kInputBus: AudioUnitElement = 1
let kOutputBus: AudioUnitElement = 0
if checkError(AudioUnitSetProperty(ioUnit!, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input, kInputBus, &enableInput,
UInt32(MemoryLayout<UInt32>.size)), "启用输入端口") {
return false
}
// 禁用输出端口 (我们只需要输入)
var enableOutput: UInt32 = 0
if checkError(AudioUnitSetProperty(ioUnit!, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output, kOutputBus,
&enableOutput, UInt32(MemoryLayout<UInt32>.size)), "禁用输出端口") {
return false
}
// 设置缓冲区分配标志
var flag: UInt32 = 0
if checkError(AudioUnitSetProperty(ioUnit!, kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output, kInputBus, &flag, UInt32(MemoryLayout<UInt32>.size)), "设置缓冲区分配标志") {
return false
}
// 设置音频格式
let size = UInt32(MemoryLayout<AudioStreamBasicDescription>.size)
if checkError(AudioUnitSetProperty(ioUnit!, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output, kInputBus, &audioFormat, size), "设置输入总线输出范围的流格式") {
return false
}
if checkError(AudioUnitSetProperty(ioUnit!, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, kOutputBus, &audioFormat, size), "设置输出总线输入范围的流格式") {
return false
}
// 启用回音消除
if isEchoCancellationEnabled {
var echoCancellation: UInt32 = 1
AudioUnitSetProperty(ioUnit!, kAUVoiceIOProperty_BypassVoiceProcessing,
kAudioUnitScope_Global, 0, &echoCancellation, UInt32(MemoryLayout<UInt32>.size))
}
// 设置输入回调 - 当有新音频数据时调用
var inputCallback = AURenderCallbackStruct(
inputProc: CustomAudioProcessor.onAudioDataAvailable,
inputProcRefCon: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
)
if checkError(AudioUnitSetProperty(ioUnit!,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, kInputBus,
&inputCallback, UInt32(MemoryLayout<AURenderCallbackStruct>.size)), "设置输入回调") {
return false
}
// 初始化音频单元
var hasError = checkError(AudioUnitInitialize(ioUnit!), "初始化音频单元")
while hasError {
Thread.sleep(forTimeInterval: 0.1)
hasError = checkError(AudioUnitInitialize(ioUnit!), "初始化音频单元")
}
// 启动音频单元
hasError = checkError(AudioOutputUnitStart(ioUnit!), "启动音频单元")
print("[CustomAudioProcessor] 音频处理器已启动,回音消除\(isEchoCancellationEnabled ? "已启用" : "已禁用")")
return !hasError
}
/// 停止音频处理
func stopRecord() {
print("[CustomAudioProcessor] 停止音频处理器")
if let ioUnit = ioUnit {
// 停止音频单元
_ = checkError(AudioOutputUnitStop(ioUnit), "停止音频单元")
// 关闭音频单元
_ = checkError(AudioUnitUninitialize(ioUnit), "反初始化音频单元")
_ = checkError(AudioComponentInstanceDispose(ioUnit), "释放音频单元")
self.ioUnit = nil
}
// 清空音频数据缓冲
audioListQueue.sync {
audioList.removeAll()
}
}
/// 音频数据回调 - 当有新的音频数据可用时调用
private static let onAudioDataAvailable: AURenderCallback = { inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData in
// 获取实例
let processor = Unmanaged<CustomAudioProcessor>.fromOpaque(inRefCon).takeUnretainedValue()
// 计算预期数据大小
let expectedDataByteSize = inNumberFrames * processor.audioFormat.mBytesPerFrame
// 确保缓冲区足够大
if processor.audioBufferList.mBuffers.mDataByteSize < expectedDataByteSize {
processor.audioBufferList.mBuffers.mData = realloc(processor.audioBufferList.mBuffers.mData, Int(expectedDataByteSize))
processor.audioBufferList.mBuffers.mDataByteSize = expectedDataByteSize
}
// 渲染音频数据
let status = processor.checkOSStatus(AudioUnitRender(processor.ioUnit!, ioActionFlags, inTimeStamp,
inBusNumber, inNumberFrames, &processor.audioBufferList),
"渲染音频数据")
// 将Int16数据转换为浮点数据进行处理
var audioDataFloat = [Float](repeating: 0.0, count: Int(inNumberFrames))
let buffer = processor.audioBufferList.mBuffers
let bufferData = buffer.mData!.assumingMemoryBound(to: Int16.self)
for j in 0..<Int(buffer.mDataByteSize / UInt32(MemoryLayout<Int16>.size)) {
// 归一化到[-1.0, 1.0]范围
audioDataFloat[j] = Float(bufferData[j]) / 32768.0
}
// 应用附加处理 (如有需要)
// processor.applyAdditionalProcessing(&audioDataFloat)
// 保存处理后的数据
if status == noErr {
processor.audioListQueue.async {
processor.audioList.append(contentsOf: audioDataFloat)
}
}
return status
}
/// 读取处理后的音频数据
/// - Parameter bytes: 输出字节数组
/// - Returns: 读取的字节数
func read(bytes: inout [UInt8]) -> Int {
return audioListQueue.sync {
// 如果没有数据,返回0
if audioList.isEmpty {
return 0
}
// 确保有足够的数据 (至少1280个样本)
if audioList.count < 1280 {
return 0
}
// 读取一帧数据 (1280个样本)
let frameLength = 1280
let buffer = Array(audioList.prefix(frameLength))
audioList.removeFirst(frameLength)
// 将浮点数据转回Int16格式
var int16Data = buffer.map { Int16($0 * 32767) }
// 转换为字节数组
let data = Data(buffer: UnsafeBufferPointer(start: &int16Data, count: int16Data.count))
bytes = [UInt8](data)
// 每个样本2字节 (16位PCM)
return frameLength * 2
}
}
/// 检查错误并打印日志
/// - Parameters:
/// - status: 操作状态
/// - operation: 操作描述
/// - Returns: 是否发生错误
private func checkError(_ status: OSStatus, _ operation: String) -> Bool {
if status != noErr {
print("[CustomAudioProcessor] 错误: \(operation)失败: \(status)")
return true
}
return false
}
/// 检查OSStatus并返回状态
/// - Parameters:
/// - status: 操作状态
/// - operation: 操作描述
/// - Returns: 原始状态
private func checkOSStatus(_ status: OSStatus, _ operation: String) -> OSStatus {
if status != noErr {
print("[CustomAudioProcessor] 错误: \(operation)失败: \(status)")
}
return status
}
}

18
azure/ios/Classes/AzureSpeechRecognitionPlugin.swift

@ -1,18 +0,0 @@
import Flutter
import UIKit
public class AzureSpeechRecognitionPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
if #available(iOS 13.0, *) {
SwiftAzureSpeechRecognitionPlugin.register(with: registrar)
} else {
// 如果低于iOS 13.0,返回不支持的错误
let channel = FlutterMethodChannel(name: "com.deep_voice.azure_asr", binaryMessenger: registrar.messenger())
channel.setMethodCallHandler { (call, result) in
result(FlutterError(code: "UNSUPPORTED",
message: "需要iOS 13.0及以上系统",
details: nil))
}
}
}
}

427
azure/ios/Classes/AzureTtsHelper.swift

@ -1,427 +0,0 @@
import Foundation
import MicrosoftCognitiveServicesSpeech
import AVFoundation
/// Azure TTS工具类,负责实现TTS服务接口
@available(iOS 13.0, *)
class AzureTtsHelper: NSObject {
// MARK: - 属性
/// 事件处理回调
private var eventHandler: (String, [String: Any]) -> Void
/// 语音配置信息
private var speechSubscriptionKey: String = ""
private var serviceRegion: String = ""
/// 语音合成配置
private var speechConfig: SPXSpeechConfiguration?
/// 语音合成器
private var synthesizer: SPXSpeechSynthesizer?
/// 是否初始化成功
private var isInitialized = false
/// 当前是否正在播放
private var _isSpeaking = false
/// 音频会话配置
private var isAudioSessionConfigured = false
// MARK: - 语音设置
/// 当前语音
private var currentVoice = "zh-CN-XiaoxiaoNeural"
/// 支持的语音映射
private var voiceMap: [String: String] = [
"zh-CN": "zh-CN-XiaoxiaoNeural",
"en-US": "en-US-JennyNeural",
"ja-JP": "ja-JP-NanamiNeural",
"ko-KR": "ko-KR-SunHiNeural",
"zh-TW": "zh-TW-HsiaoChenNeural",
"zh-HK": "zh-HK-HiuMaanNeural"
]
/// 当前语音合成参数
private var currentSpeechRate = "0%"
private var currentPitch = "0%"
private var currentVolume = "100%"
// MARK: - 初始化
init(eventHandler: @escaping (String, [String: Any]) -> Void) {
self.eventHandler = eventHandler
super.init()
}
deinit {
dispose()
}
// MARK: - TTS 接口实现
/// 初始化语音合成服务
/// - Parameters:
/// - speechSubscriptionKey: Azure 语音服务订阅密钥
/// - serviceRegion: Azure 服务区域 (如 eastasia)
/// - language: 语言代码 (默认 zh-CN)
/// - Returns: 初始化是否成功
func initialize(speechSubscriptionKey: String, serviceRegion: String, language: String = "zh-CN") -> Bool {
print("[AzureTtsHelper] 初始化语音合成服务")
// 检查配置是否为空
if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty {
print("[AzureTtsHelper] 错误: Azure 配置信息不完整")
eventHandler("error", ["error": "Azure 配置信息不完整"])
return false
}
// 释放之前的资源
dispose()
// 记录配置信息
self.speechSubscriptionKey = speechSubscriptionKey
self.serviceRegion = serviceRegion
// 配置音频会话
if !configureAudioSession() {
print("[AzureTtsHelper] 警告: 音频会话配置失败,将尝试继续初始化")
}
do {
// 创建语音配置
speechConfig = try SPXSpeechConfiguration(subscription: speechSubscriptionKey, region: serviceRegion)
// 设置默认语音
let defaultVoice = getDefaultVoiceForLanguage(language)
currentVoice = defaultVoice
speechConfig?.speechSynthesisVoiceName = defaultVoice
// 创建语音合成器
synthesizer = try SPXSpeechSynthesizer(speechConfig!)
// 设置事件处理器
setupSynthesizerEvents()
isInitialized = true
print("[AzureTtsHelper] TTS 引擎初始化成功")
return true
} catch {
print("[AzureTtsHelper] 错误: 初始化语音合成服务失败: \(error.localizedDescription)")
eventHandler("error", ["error": "初始化语音合成服务失败: \(error.localizedDescription)"])
return false
}
}
/// 配置音频会话
private func configureAudioSession() -> Bool {
let audioSession = AVAudioSession.sharedInstance()
do {
// 使用playback类别,但支持混合和空中播放
try audioSession.setCategory(.playback,
mode: .spokenAudio,
options: [.mixWithOthers, .allowAirPlay, .duckOthers])
// 根据设备类型选择最佳配置
let currentRoute = audioSession.currentRoute
let hasHeadphones = currentRoute.outputs.contains {
$0.portType == .headphones || $0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP
}
// 优化音频路由
if hasHeadphones {
// 耳机模式,使用默认设置
try audioSession.setPreferredIOBufferDuration(0.005) // 较小的缓冲区大小以减少延迟
} else {
// 扬声器模式
try audioSession.setPreferredIOBufferDuration(0.005)
}
// 避免完全激活音频会话,因为ASR可能已经激活
// 这里使用setActive(false)是为了不与ASR冲突
if !audioSession.isOtherAudioPlaying {
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
}
isAudioSessionConfigured = true
print("[AzureTtsHelper] 音频会话配置成功")
return true
} catch {
print("[AzureTtsHelper] 警告: 音频会话配置失败: \(error.localizedDescription)")
isAudioSessionConfigured = false
return false
}
}
/// 设置语音
/// - Parameter voiceName: 语音名称 (如 "zh-CN-XiaoxiaoNeural")
/// - Returns: 设置是否成功
func setVoice(voiceName: String) -> Bool {
if !isInitialized {
print("[AzureTtsHelper] 错误: TTS 引擎尚未初始化")
eventHandler("error", ["error": "TTS 引擎尚未初始化"])
return false
}
if voiceName.isEmpty {
print("[AzureTtsHelper] 错误: 声音名称为空")
eventHandler("error", ["error": "声音名称不能为空"])
return false
}
if voiceName == currentVoice {
print("[AzureTtsHelper] 已设置语音: \(voiceName)")
return true
}
print("[AzureTtsHelper] 设置声音: \(voiceName)")
currentVoice = voiceName
// 更新语音配置
if let speechConfig = speechConfig {
speechConfig.speechSynthesisVoiceName = voiceName
return true
}
return false
}
/// 设置语音合成参数
/// - Parameters:
/// - rate: 语速,范围 -100 到 100,默认为 0
/// - pitch: 音调,范围 -100 到 100,默认为 0
/// - volume: 音量,范围 0 到 100,默认为 100
/// - Returns: 是否设置成功
func setSpeechParams(rate: Int = 0, pitch: Int = 0, volume: Int = 100) -> Bool {
if !isInitialized {
print("[AzureTtsHelper] 错误: TTS 引擎尚未初始化")
eventHandler("error", ["error": "TTS 引擎尚未初始化"])
return false
}
// 转换参数格式
currentSpeechRate = formatRateParam(rate)
currentPitch = formatPitchParam(pitch)
currentVolume = formatVolumeParam(volume)
print("[AzureTtsHelper] 已设置语音参数: 语速=\(currentSpeechRate), 音调=\(currentPitch), 音量=\(currentVolume)")
return true
}
/// 合成文本为语音并播放
/// - Parameter text: 要合成的文本
/// - Returns: 操作是否成功启动
func speakText(text: String) -> Bool {
if !isInitialized {
print("[AzureTtsHelper] 错误: TTS 引擎尚未初始化")
eventHandler("error", ["error": "TTS 引擎尚未初始化"])
return false
}
if text.isEmpty {
print("[AzureTtsHelper] 警告: 要播放的文本为空")
return true
}
// 确保音频会话已配置
if !isAudioSessionConfigured {
_ = configureAudioSession()
}
print("[AzureTtsHelper] 开始语音合成: \(text.prefix(50))...")
// 生成SSML
let ssml = generateSsml(text: text)
// 直接进行SSML合成
return speakSsmlInternal(text: ssml)
}
/// 内部SSML合成和播放
private func speakSsmlInternal(text: String) -> Bool {
guard let synthesizer = synthesizer else {
print("[AzureTtsHelper] 错误: 合成器未初始化")
eventHandler("error", ["error": "合成器未初始化"])
return false
}
_isSpeaking = true
eventHandler("started", [:])
Task {
do {
// 使用异步方法进行合成并直接播放
_ = try await synthesizer.startSpeakingSsml(text)
} catch {
print("[AzureTtsHelper] 错误: 语音合成失败: \(error.localizedDescription)")
DispatchQueue.main.async {
self._isSpeaking = false
self.eventHandler("error", ["error": "语音合成失败: \(error.localizedDescription)"])
}
}
}
return true
}
/// 停止当前语音合成
/// - Returns: 操作是否成功
func stopSpeaking() -> Bool {
if !isInitialized || !_isSpeaking {
return true
}
// 停止合成
do {
try synthesizer?.stopSpeaking()
_isSpeaking = false
eventHandler("canceled", [:])
print("[AzureTtsHelper] 已停止语音合成")
return true
} catch {
print("[AzureTtsHelper] 错误: 停止语音合成失败: \(error.localizedDescription)")
eventHandler("error", ["error": "停止语音合成失败: \(error.localizedDescription)"])
return false
}
}
/// 检查是否正在播放
/// - Returns: 当前是否正在播放语音
func isSpeaking() -> Bool {
return _isSpeaking
}
/// 释放资源
func dispose() {
try? stopSpeaking()
// 释放合成器和配置
synthesizer = nil
speechConfig = nil
isInitialized = false
_isSpeaking = false
isAudioSessionConfigured = false
print("[AzureTtsHelper] TTS 引擎已释放")
}
// MARK: - 私有辅助方法
/// 设置合成器事件处理
private func setupSynthesizerEvents() {
guard let synthesizer = synthesizer else { return }
// 添加书签到达事件处理
synthesizer.addBookmarkReachedEventHandler { _, e in
print("[AzureTtsHelper] 书签事件: 音频偏移: \((e.audioOffset + 5000) / 10000)ms, 文本: \"\(e.text)\"")
}
// 合成完成事件
synthesizer.addSynthesisCompletedEventHandler { [weak self] _, e in
guard let self = self else { return }
print("[AzureTtsHelper] 语音合成完成: 音频持续时间: \(e.result.audioDuration)")
DispatchQueue.main.async {
self._isSpeaking = false
self.eventHandler("completed", [:])
}
}
// 合成取消事件
synthesizer.addSynthesisCanceledEventHandler { [weak self] _, e in
guard let self = self else { return }
let result = e.result
do {
let cancellationDetails = try SPXSpeechSynthesisCancellationDetails(fromCanceledSynthesisResult: result)
print("[AzureTtsHelper] 语音合成取消: 原因: \(cancellationDetails.reason)")
if cancellationDetails.reason == SPXCancellationReason.error {
print("[AzureTtsHelper] 错误代码: \(cancellationDetails.errorCode)")
print("[AzureTtsHelper] 错误详情: \(cancellationDetails.errorDetails ?? "未知")")
}
DispatchQueue.main.async {
self._isSpeaking = false
self.eventHandler("error", ["error": "语音合成取消: \(cancellationDetails.errorDetails ?? "未知错误")"])
}
} catch {
print("[AzureTtsHelper] 获取取消详情时出错: \(error)")
DispatchQueue.main.async {
self._isSpeaking = false
self.eventHandler("error", ["error": "语音合成被取消"])
}
}
}
// 合成开始事件
synthesizer.addSynthesisStartedEventHandler { _, _ in
// print("[AzureTtsHelper] 语音合成开始")
}
// 合成中事件
synthesizer.addSynthesizingEventHandler { _, _ in
// print("[AzureTtsHelper] 语音合成中")
}
}
/// 生成 SSML 文本
private func generateSsml(text: String) -> String {
return """
<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='zh-CN'>
<voice name='\(currentVoice)'>
<prosody rate='\(currentSpeechRate)' pitch='\(currentPitch)' volume='\(currentVolume)'>
\(text)
</prosody>
</voice>
</speak>
"""
}
/// 格式化语速参数
private func formatRateParam(_ rate: Int) -> String {
let clampedRate = rate.clamp(min: -100, max: 100)
if clampedRate == 0 {
return "0%"
} else if clampedRate < 0 {
return "\(Int(Double(clampedRate) * 0.9))%"
} else {
return "+\(clampedRate)%"
}
}
/// 格式化音调参数
private func formatPitchParam(_ pitch: Int) -> String {
let clampedPitch = pitch.clamp(min: -100, max: 100)
if clampedPitch == 0 {
return "0%"
} else {
return "\(Int(Double(clampedPitch) * 0.5))%"
}
}
/// 格式化音量参数
private func formatVolumeParam(_ volume: Int) -> String {
let clampedVolume = volume.clamp(min: 0, max: 100)
return "\(clampedVolume)%"
}
/// 获取指定语言的默认语音
private func getDefaultVoiceForLanguage(_ language: String) -> String {
return voiceMap[language] ?? "zh-CN-XiaoxiaoNeural"
}
}
// MARK: - 扩展
extension Int {
func clamp(min: Int, max: Int) -> Int {
if self < min { return min }
if self > max { return max }
return self
}
}

259
azure/ios/Classes/SwiftAzureSpeechRecognitionPlugin.swift

@ -1,259 +0,0 @@
import Flutter
import UIKit
import MicrosoftCognitiveServicesSpeech
import AVFoundation
@available(iOS 13.0, *)
public class SwiftAzureSpeechRecognitionPlugin: NSObject, FlutterPlugin {
private var azureChannel: FlutterMethodChannel
private var ttsChannel: FlutterMethodChannel
private var asrHelper: AzureAsrHelper
private var ttsHelper: AzureTtsHelper
private static var eventStreamHandler: AzureEventStreamHandler?
// 创建方法到通道的映射
private static var ttsMethodHandlers = [String: FlutterMethodCallHandler]()
private static var asrMethodHandlers = [String: FlutterMethodCallHandler]()
public static func register(with registrar: FlutterPluginRegistrar) {
// ASR通道
let channel = FlutterMethodChannel(name: "com.deep_voice.azure_asr", binaryMessenger: registrar.messenger())
// TTS通道
let ttsChannel = FlutterMethodChannel(name: "com.deep_voice.azure_tts", binaryMessenger: registrar.messenger())
// 设置ASR事件通道
let eventChannel = FlutterEventChannel(name: "com.deep_voice.azure_asr_events", binaryMessenger: registrar.messenger())
eventStreamHandler = AzureEventStreamHandler()
eventChannel.setStreamHandler(eventStreamHandler)
let instance = SwiftAzureSpeechRecognitionPlugin(
azureChannel: channel,
ttsChannel: ttsChannel,
eventStreamHandler: eventStreamHandler!
)
// 直接设置各自通道的处理器
channel.setMethodCallHandler(instance.handleAsrMethodCalls)
ttsChannel.setMethodCallHandler(instance.handleTtsMethodCalls)
}
// 新增直接处理方法调用的函数
private func handleTtsMethodCalls(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
handleTtsMethod(call, result)
}
private func handleAsrMethodCalls(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
handleAsrMethod(call, result)
}
init(azureChannel: FlutterMethodChannel, ttsChannel: FlutterMethodChannel, eventStreamHandler: AzureEventStreamHandler) {
self.azureChannel = azureChannel
self.ttsChannel = ttsChannel
// 创建辅助类实例,使用自定义事件回调处理器
let eventHandler: (String, [String: Any]) -> Void = { eventName, arguments in
DispatchQueue.main.async {
if let eventSink = SwiftAzureSpeechRecognitionPlugin.eventStreamHandler?.eventSink {
var eventData = arguments
eventData["type"] = eventName
eventSink(eventData)
}
}
}
asrHelper = AzureAsrHelper(eventHandler: eventHandler)
ttsHelper = AzureTtsHelper(eventHandler: eventHandler)
super.init()
}
private func handleAsrMethod(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
let args = call.arguments as? Dictionary<String, Any>
switch call.method {
case "initialize":
// 仅在初始化时读取必要参数
guard let speechSubscriptionKey = args?["subscriptionKey"] as? String, !speechSubscriptionKey.isEmpty else {
let errorMsg = "语音订阅密钥不能为空"
print("[AzurePlugin] 错误: \(errorMsg)")
result(FlutterError(code: "INVALID_SUBSCRIPTION_KEY", message: errorMsg, details: nil))
return
}
guard let serviceRegion = args?["region"] as? String, !serviceRegion.isEmpty else {
let errorMsg = "服务区域不能为空"
print("[AzurePlugin] 错误: \(errorMsg)")
result(FlutterError(code: "INVALID_REGION", message: errorMsg, details: nil))
return
}
let supportedLanguages = args?["supportedLanguages"] as? [String] ?? []
let success = asrHelper.initialize(
speechSubscriptionKey: speechSubscriptionKey,
serviceRegion: serviceRegion,
supportedLanguages: supportedLanguages.isEmpty ? nil : supportedLanguages
)
result(success)
case "startContinuousRecognition":
// 只有使用参数时才验证
let success = asrHelper.startContinuousRecognition()
result(success)
case "stopContinuousRecognition":
// 不需要额外参数
let success = asrHelper.stopContinuousRecognition()
result(success)
case "recognizeOnce":
// 只有使用参数时才验证
let success = asrHelper.recognizeOnce()
result(success)
case "isContinuousRecognitionActive":
// 不需要额外参数
result(asrHelper.isContinuousRecognitionActive())
case "dispose":
// 不需要额外参数
print("[AzurePlugin] 释放ASR资源")
asrHelper.dispose()
result(true)
default:
print("[AzurePlugin] 错误: 未知ASR方法: \(call.method)")
result(FlutterMethodNotImplemented)
}
}
private func handleTtsMethod(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
let args = call.arguments as? Dictionary<String, Any>
switch call.method {
case "initialize":
// 仅在初始化时验证参数
guard let speechSubscriptionKey = args?["subscriptionKey"] as? String, !speechSubscriptionKey.isEmpty else {
let errorMsg = "语音订阅密钥不能为空"
print("[AzurePlugin] 错误: \(errorMsg)")
result(FlutterError(code: "INVALID_SUBSCRIPTION_KEY", message: errorMsg, details: nil))
return
}
guard let serviceRegion = args?["region"] as? String, !serviceRegion.isEmpty else {
let errorMsg = "服务区域不能为空"
print("[AzurePlugin] 错误: \(errorMsg)")
result(FlutterError(code: "INVALID_REGION", message: errorMsg, details: nil))
return
}
let language = args?["language"] as? String ?? "zh-CN"
print("[AzurePlugin] 初始化TTS,语言: \(language)")
let success = ttsHelper.initialize(speechSubscriptionKey: speechSubscriptionKey, serviceRegion: serviceRegion, language: language)
result(success)
case "setVoice":
// 仅获取voice参数
guard let voiceName = args?["voiceName"] as? String, !voiceName.isEmpty else {
let errorMsg = "声音名称不能为空"
print("[AzurePlugin] 错误: \(errorMsg)")
result(FlutterError(code: "INVALID_VOICE", message: errorMsg, details: nil))
return
}
print("[AzurePlugin] 设置声音: \(voiceName)")
let success = ttsHelper.setVoice(voiceName: voiceName)
result(success)
case "speakText":
// 仅获取text参数
let text = args?["text"] as? String ?? ""
if text.isEmpty {
print("[AzurePlugin] 警告: 要播放的文本为空")
result("OK")
return
}
print("[AzurePlugin] 播放文本: \(text.prefix(50))...")
let success = ttsHelper.speakText(text: text)
result(success ? "OK" : "ERROR")
case "speakSsml":
// 仅获取ssml参数
guard let ssml = args?["ssml"] as? String, !ssml.isEmpty else {
let errorMsg = "SSML内容不能为空"
print("[AzurePlugin] 错误: \(errorMsg)")
result(FlutterError(code: "INVALID_SSML", message: errorMsg, details: nil))
return
}
print("[AzurePlugin] 播放SSML: \(ssml.prefix(100))...")
// 由于我们移除了speakSsml方法,这里改用speakText方法
// Azure SDK内部会自动检测是普通文本还是SSML
let success = ttsHelper.speakText(text: ssml)
result(success)
case "stopSpeaking":
// 不需要参数
print("[AzurePlugin] 停止播放")
let success = ttsHelper.stopSpeaking()
result(success)
case "isSpeaking":
// 不需要参数
result(ttsHelper.isSpeaking())
case "setSpeechParams":
// 仅获取语音参数
let rate = args?["rate"] as? Int ?? 0
let pitch = args?["pitch"] as? Int ?? 0
let volume = args?["volume"] as? Int ?? 100
print("[AzurePlugin] 设置语音参数: rate=\(rate), pitch=\(pitch), volume=\(volume)")
let success = ttsHelper.setSpeechParams(rate: rate, pitch: pitch, volume: volume)
result(success)
case "dispose":
// 释放TTS资源
print("[AzurePlugin] 释放TTS资源")
ttsHelper.dispose()
result(true)
default:
print("[AzurePlugin] 错误: 未知TTS方法: \(call.method)")
result(FlutterMethodNotImplemented)
}
}
}
// 用于处理事件流的辅助类
@available(iOS 13.0, *)
class AzureEventStreamHandler: NSObject, FlutterStreamHandler {
var eventSink: FlutterEventSink?
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.eventSink = events
// 通知Flutter端事件通道已准备好
DispatchQueue.main.async {
events(["type": "channelReady"])
}
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
self.eventSink = nil
return nil
}
}

24
azure/ios/azure_speech_recognition.podspec

@ -1,24 +0,0 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint azure_speech_recognition.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'azure_speech_recognition'
s.version = '0.1.0'
s.summary = 'Azure Speech Recognition plugin for Flutter'
s.description = <<-DESC
A Flutter plugin for Microsoft Azure Speech services, providing both speech recognition (ASR) and text-to-speech (TTS) capabilities.
DESC
s.homepage = 'https://github.com/yourusername/azure_speech_recognition'
s.license = { :type => 'MIT', :file => '../LICENSE' }
s.author = { 'Your Company' => 'your-email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.dependency 'MicrosoftCognitiveServicesSpeech-iOS', '~> 1.34.0'
s.platform = :ios, '12.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
end

6
azure/lib/azure_speech_recognition.dart

@ -1,6 +0,0 @@
// This is a placeholder file that exports nothing.
// The actual implementation is in the app's services folder.
// This file exists just to satisfy the Flutter plugin structure requirements.
// Empty library to satisfy plugin structure
library azure_speech_recognition;

23
azure/pubspec.yaml

@ -1,23 +0,0 @@
name: azure_speech_recognition
description: Azure Speech Recognition and Text-to-Speech services Flutter plugin
version: 0.1.0
homepage: https://github.com/yourusername/azure_speech_recognition
environment:
sdk: '>=2.12.0 <3.0.0'
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0
flutter:
plugin:
platforms:
ios:
pluginClass: AzureSpeechRecognitionPlugin

14
lib/core/bindings/initial_binding.dart

@ -8,7 +8,7 @@ import '../../data/services/classic_bluetooth_service.dart';
import '../../data/services/bluetooth_media_button_service.dart'; import '../../data/services/bluetooth_media_button_service.dart';
import '../../core/utils/logger.dart'; import '../../core/utils/logger.dart';
import '../../data/services/voice_interaction_service.dart'; import '../../data/services/voice_interaction_service.dart';
import '../../data/services/open_ai_service_adapter.dart';
/// 初始绑定,用于管理全局依赖 /// 初始绑定,用于管理全局依赖
class InitialBinding extends Bindings { class InitialBinding extends Bindings {
@override @override
@ -29,6 +29,13 @@ class InitialBinding extends Bindings {
Get.lazyPut<VolcanoTranslationService>(() => VolcanoTranslationService(), Get.lazyPut<VolcanoTranslationService>(() => VolcanoTranslationService(),
fenix: true); fenix: true);
// OpenAI服务适配器
Get.lazyPut<OpenAIServiceAdapter>(() {
final adapter = OpenAIServiceAdapter();
adapter.initialize();
return adapter;
}, fenix: true);
// 火山AI服务 // 火山AI服务
Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true); Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true);
@ -40,10 +47,15 @@ class InitialBinding extends Bindings {
Get.lazyPut<BluetoothMediaButtonService>( Get.lazyPut<BluetoothMediaButtonService>(
() => BluetoothMediaButtonService(), () => BluetoothMediaButtonService(),
fenix: true); fenix: true);
// 语音交互服务
Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(), Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(),
fenix: true); fenix: true);
// 杰理蓝牙服务
// // 语言管理器(需要最先初始化) // // 语言管理器(需要最先初始化)
// Get.put<LoginService>(LoginService(), permanent: true); // Get.put<LoginService>(LoginService(), permanent: true);
// await LoginService().FirebaseInit(); // await LoginService().FirebaseInit();

91
lib/core/translations/language/de_de.dart

@ -26,7 +26,7 @@ const Map<String, String> deDE = {
"explore": "Entdecken", "explore": "Entdecken",
"settings": "Einstellungen", "settings": "Einstellungen",
// 语言 // 语言
"followingSystem": "Systemeinstellung folgen", "followingSystem": "Systemeinstellung folgen",
"chinese": "Chinesisch", "chinese": "Chinesisch",
"english": "Englisch", "english": "Englisch",
@ -43,7 +43,8 @@ const Map<String, String> deDE = {
// 登录页 // 登录页
"welcome": "Willkommen", "welcome": "Willkommen",
"loginTips": "Bitte melden Sie sich an oder registrieren Sie sich mit Ihrer Telefonnummer oder E-Mail", "loginTips":
"Bitte melden Sie sich an oder registrieren Sie sich mit Ihrer Telefonnummer oder E-Mail",
"accountNumber": "Telefonnummer oder E-Mail", "accountNumber": "Telefonnummer oder E-Mail",
"verificationCode": "Bestätigungscode", "verificationCode": "Bestätigungscode",
"getCode": "Code erhalten", "getCode": "Code erhalten",
@ -56,6 +57,15 @@ const Map<String, String> deDE = {
'selectCountry': 'Land/Region auswählen', 'selectCountry': 'Land/Region auswählen',
"email": "E-Mail", "email": "E-Mail",
"phoneNumber": "Telefonnummer", "phoneNumber": "Telefonnummer",
"pleaseEnterContactInfo": "Bitte Telefonnummer oder E-Mail eingeben",
"invalidEmailFormat": "Ungültiges E-Mail-Format",
"invalidPhoneFormat": "Ungültiges Telefonnummerformat",
"tip": "Hinweis",
"pleaseEnterCompleteCode":
"Bitte geben Sie den vollständigen Verifizierungscode ein",
"loginFailed": "Anmeldung fehlgeschlagen, bitte versuchen Sie es erneut",
'pleaseAcceptPrivacyPolicy':
'Bitte lesen und akzeptieren Sie die Datenschutzerklärung und die Nutzungsbedingungen',
// 首页 // 首页
"deviceStatus": "Gerätestatus", "deviceStatus": "Gerätestatus",
@ -65,7 +75,8 @@ const Map<String, String> deDE = {
"signal": "Signal", "signal": "Signal",
"deviceId": "Geräte-ID", "deviceId": "Geräte-ID",
"firmwareVersion": "Firmware-Version", "firmwareVersion": "Firmware-Version",
"disconnectedDevice": "Kein Gerät verbunden, klicken Sie auf die Schaltfläche unten", "disconnectedDevice":
"Kein Gerät verbunden, klicken Sie auf die Schaltfläche unten",
"connectDevice": "Gerät verbinden", "connectDevice": "Gerät verbinden",
"topAiExperiences": "Top KI-Erlebnisse", "topAiExperiences": "Top KI-Erlebnisse",
"aiAssistant": "KI-Assistent", "aiAssistant": "KI-Assistent",
@ -75,12 +86,14 @@ const Map<String, String> deDE = {
"aiCompanion": "Eltern-Kind-Begleiter", "aiCompanion": "Eltern-Kind-Begleiter",
"aiCompanionInfo": "Versteht Sie und bietet warme Begleitung", "aiCompanionInfo": "Versteht Sie und bietet warme Begleitung",
"homeSlogan": "Die Welt verbinden, jenseits des Klangs", "homeSlogan": "Die Welt verbinden, jenseits des Klangs",
"turnEarphones": "Bitte öffnen Sie das Kopfhörer-Etui und setzen Sie die Kopfhörer ein", "turnEarphones":
"Bitte öffnen Sie das Kopfhörer-Etui und setzen Sie die Kopfhörer ein",
"currentlyConnected": "Aktuell verbunden:", "currentlyConnected": "Aktuell verbunden:",
"unsystemSetup": "Zu Systemeinstellungen", "unsystemSetup": "Zu Systemeinstellungen",
"talkLater": "Später erinnern", "talkLater": "Später erinnern",
"initializing": "Initialisierung...", "initializing": "Initialisierung...",
"bluetoothInitializationFailure": "Bluetooth-Dienst konnte nicht initialisiert werden", "bluetoothInitializationFailure":
"Bluetooth-Dienst konnte nicht initialisiert werden",
"checkingPermissions": "Berechtigungen werden überprüft...", "checkingPermissions": "Berechtigungen werden überprüft...",
"lackPermissions": "Bluetooth-Berechtigungen erforderlich", "lackPermissions": "Bluetooth-Berechtigungen erforderlich",
"searchingDevice": "Geräte werden gesucht...", "searchingDevice": "Geräte werden gesucht...",
@ -90,7 +103,8 @@ const Map<String, String> deDE = {
"connectedDevice": "Mit Gerät verbunden:", "connectedDevice": "Mit Gerät verbunden:",
"failedRefreshDevice": "Gerät konnte nicht aktualisiert werden", "failedRefreshDevice": "Gerät konnte nicht aktualisiert werden",
"noDeviceFound": "Kein verbundenes Gerät gefunden", "noDeviceFound": "Kein verbundenes Gerät gefunden",
"failedDeviceInformation": "Geräteinformationen konnten nicht abgerufen werden", "failedDeviceInformation":
"Geräteinformationen konnten nicht abgerufen werden",
"unknownEquipment": "Unbekanntes Gerät", "unknownEquipment": "Unbekanntes Gerät",
"zeroSignal": "Kein Signal", "zeroSignal": "Kein Signal",
"good": "Gut", "good": "Gut",
@ -116,11 +130,13 @@ const Map<String, String> deDE = {
"healthConsultant": "Multifunktionaler KI-Chat", "healthConsultant": "Multifunktionaler KI-Chat",
"healthConsultantInfo": "Mehr KI-Chat-Funktionen", "healthConsultantInfo": "Mehr KI-Chat-Funktionen",
"conferenceAssistant": "Geschäftsbesprechungs-Assistent", "conferenceAssistant": "Geschäftsbesprechungs-Assistent",
"conferenceAssistantInfo": "Echtzeit-Protokollierung und mehrsprachige Übersetzung", "conferenceAssistantInfo":
"Echtzeit-Protokollierung und mehrsprachige Übersetzung",
"studyAssistant": "Sprachlern-Assistent", "studyAssistant": "Sprachlern-Assistent",
"studyAssistantInfo": "Aussprachekorrektur und Sprechübungen in Echtzeit", "studyAssistantInfo": "Aussprachekorrektur und Sprechübungen in Echtzeit",
"travelTranslator": "Reiseübersetzer", "travelTranslator": "Reiseübersetzer",
"travelTranslatorInfo": "Echtzeit-Sprachübersetzung und Sehenswürdigkeitsführer", "travelTranslatorInfo":
"Echtzeit-Sprachübersetzung und Sehenswürdigkeitsführer",
"beSearching": "Suche nach:", "beSearching": "Suche nach:",
"noAssistantFound": "Kein passender KI-Assistent gefunden", "noAssistantFound": "Kein passender KI-Assistent gefunden",
"scene": "Szenarien", "scene": "Szenarien",
@ -128,7 +144,8 @@ const Map<String, String> deDE = {
"newFunction": "Neue Funktionen", "newFunction": "Neue Funktionen",
"youChoseTry": "Sie haben sich entschieden, auszuprobieren", "youChoseTry": "Sie haben sich entschieden, auszuprobieren",
"aiSinger": "KI-Sänger-Modus", "aiSinger": "KI-Sänger-Modus",
"aiSingerInfo": "Singen Sie jeden Song mit der Stimme Ihres Lieblingskünstlers, auch eigene Kreationen", "aiSingerInfo":
"Singen Sie jeden Song mit der Stimme Ihres Lieblingskünstlers, auch eigene Kreationen",
"popularFunction": "Beliebte Funktionen", "popularFunction": "Beliebte Funktionen",
"tryNow": "Jetzt ausprobieren", "tryNow": "Jetzt ausprobieren",
@ -159,7 +176,8 @@ const Map<String, String> deDE = {
"privacySettings": "Datenschutzeinstellungen", "privacySettings": "Datenschutzeinstellungen",
"privacySettingsInfo": "Datenerfassung und Berechtigungen verwalten", "privacySettingsInfo": "Datenerfassung und Berechtigungen verwalten",
"logout": "Abmelden", "logout": "Abmelden",
"logoutTips": "Sind Sie sicher, dass Sie sich vom aktuellen Konto abmelden möchten?", "logoutTips":
"Sind Sie sicher, dass Sie sich vom aktuellen Konto abmelden möchten?",
"aboutApplication": "Über die App", "aboutApplication": "Über die App",
"versions": "Version", "versions": "Version",
"checkUpdates": "Nach Updates suchen", "checkUpdates": "Nach Updates suchen",
@ -168,9 +186,11 @@ const Map<String, String> deDE = {
"voiceServiceInfo": "Spracherkennungs- und Synthesedienst auswählen", "voiceServiceInfo": "Spracherkennungs- und Synthesedienst auswählen",
"selectVoiceService": "Sprachdienst auswählen", "selectVoiceService": "Sprachdienst auswählen",
"azureVoiceService": "Azure Sprachdienst", "azureVoiceService": "Azure Sprachdienst",
"azureVoiceServiceInfo": "Microsoft Cloud-Sprachdienst mit hochwertiger Sprachsynthese und -erkennung", "azureVoiceServiceInfo":
"Microsoft Cloud-Sprachdienst mit hochwertiger Sprachsynthese und -erkennung",
"flutterVoiceService": "Flutter lokaler Sprachdienst", "flutterVoiceService": "Flutter lokaler Sprachdienst",
"flutterVoiceServiceInfo": "Lokaler Sprachdienst des Geräts ohne Internetverbindung", "flutterVoiceServiceInfo":
"Lokaler Sprachdienst des Geräts ohne Internetverbindung",
'timbreService': 'Klangfarben-Dienst', 'timbreService': 'Klangfarben-Dienst',
'timbreServiceInfo': 'Sprachsynthesedienst für Klangfarben auswählen', 'timbreServiceInfo': 'Sprachsynthesedienst für Klangfarben auswählen',
'defaultTimbre': 'Standard-Klangfarbe', 'defaultTimbre': 'Standard-Klangfarbe',
@ -181,9 +201,9 @@ const Map<String, String> deDE = {
'pleaseRead': 'Bitte lesen', 'pleaseRead': 'Bitte lesen',
'completeRecording': 'Aufnahme abschließen', 'completeRecording': 'Aufnahme abschließen',
'deleteAccount': 'Konto löschen', 'deleteAccount': 'Konto löschen',
'deleteAccountTips': 'Möchten Sie Ihr Konto wirklich dauerhaft löschen? Diese Aktion kann nicht rückgängig gemacht werden.', 'deleteAccountTips':
'Möchten Sie Ihr Konto wirklich dauerhaft löschen? Diese Aktion kann nicht rückgängig gemacht werden.',
// 聊天页 // 聊天页
"clearChatHistory": "Chat-Verlauf löschen", "clearChatHistory": "Chat-Verlauf löschen",
"online": "Online", "online": "Online",
@ -310,37 +330,60 @@ const Map<String, String> deDE = {
// Personal Assistant Emma // Personal Assistant Emma
"personalAssistantName": "Emma", "personalAssistantName": "Emma",
"personalAssistantDescription": "Hallo, ich bin Emma, eine vielseitige KI-Assistentin. Ob für Arbeitsplanung, Lernberatung, Lebensorganisation oder andere Anliegen, ich helfe Ihnen gerne. Lassen Sie uns gemeinsam die Effizienz steigern und Ziele erreichen!", "personalAssistantDescription":
"Hallo, ich bin Emma, eine vielseitige KI-Assistentin. Ob für Arbeitsplanung, Lernberatung, Lebensorganisation oder andere Anliegen, ich helfe Ihnen gerne. Lassen Sie uns gemeinsam die Effizienz steigern und Ziele erreichen!",
"assistantTag": "Assistent", "assistantTag": "Assistent",
"personalAssistantWelcome": "Hallo! Ich bin Emma und freue mich, Ihre persönliche Assistentin zu sein. Ich kann Ihnen bei verschiedenen Aufgaben wie Zeitmanagement, Zielplanung, Informationsorganisation usw. helfen. Wie kann ich Ihnen helfen?", "personalAssistantWelcome":
"Hallo! Ich bin Emma und freue mich, Ihre persönliche Assistentin zu sein. Ich kann Ihnen bei verschiedenen Aufgaben wie Zeitmanagement, Zielplanung, Informationsorganisation usw. helfen. Wie kann ich Ihnen helfen?",
"personalAssistantVoiceWelcome": "Ich bin hier!", "personalAssistantVoiceWelcome": "Ich bin hier!",
// 同声传译 Lucy // 同声传译 Lucy
"interpreterName": "Simultandolmetscher", "interpreterName": "Simultandolmetscher",
"interpreterDescription": "Hallo! Ich bin Lucy, eine professionelle Dolmetscherin für Englisch-Chinesisch, spezialisiert auf Echtzeit-Übersetzung für Meetings, Konferenzen und Gespräche, um barrierefreie Kommunikation zu ermöglichen.", "interpreterDescription":
"Hallo! Ich bin Lucy, eine professionelle Dolmetscherin für Englisch-Chinesisch, spezialisiert auf Echtzeit-Übersetzung für Meetings, Konferenzen und Gespräche, um barrierefreie Kommunikation zu ermöglichen.",
"interpreterTag": "Übersetzung", "interpreterTag": "Übersetzung",
"interpreterWelcome": "Hallo zusammen! Ich bin Lucy, Ihre Simultandolmetscherin. Ich werde für Sie zwischen Chinesisch und Englisch übersetzen. Wenn Sie Chinesisch sprechen, übersetze ich ins Englische; wenn Sie Englisch sprechen, übersetze ich ins Chinesische. Lassen Sie uns mit unserer Übersetzungssitzung beginnen!", "interpreterWelcome":
"Hallo zusammen! Ich bin Lucy, Ihre Simultandolmetscherin. Ich werde für Sie zwischen Chinesisch und Englisch übersetzen. Wenn Sie Chinesisch sprechen, übersetze ich ins Englische; wenn Sie Englisch sprechen, übersetze ich ins Chinesische. Lassen Sie uns mit unserer Übersetzungssitzung beginnen!",
// 亲子陪伴 芯愿 // 亲子陪伴 芯愿
"companionName": "XinYuan", "companionName": "XinYuan",
"companionDescription": "Ich bin XinYuan, Ihr engagierter Eltern-Kind-Begleiter, sanft und verständnisvoll. Ob Sie plaudern, Sorgen teilen oder Gesellschaft brauchen, ich bin immer an Ihrer Seite.", "companionDescription":
"Ich bin XinYuan, Ihr engagierter Eltern-Kind-Begleiter, sanft und verständnisvoll. Ob Sie plaudern, Sorgen teilen oder Gesellschaft brauchen, ich bin immer an Ihrer Seite.",
"companionTag": "Emotional", "companionTag": "Emotional",
"companionWelcome": "Hallo! Endlich treffen wir uns! Ich bin XinYuan, Ihr engagierter Eltern-Kind-Begleiter. Wie war Ihr Tag? Gibt es etwas, das Sie mit mir teilen möchten? Ich bin immer hier, bereit, Ihren Gedanken zuzuhören.", "companionWelcome":
"Hallo! Endlich treffen wir uns! Ich bin XinYuan, Ihr engagierter Eltern-Kind-Begleiter. Wie war Ihr Tag? Gibt es etwas, das Sie mit mir teilen möchten? Ich bin immer hier, bereit, Ihren Gedanken zuzuhören.",
"companionVoiceWelcome": "Ich bin bei Ihnen!", "companionVoiceWelcome": "Ich bin bei Ihnen!",
// 多功能AI聊天 吴小江 // 多功能AI聊天 吴小江
"healthAdvisorName": "KI-Chat-Assistent Wu", "healthAdvisorName": "KI-Chat-Assistent Wu",
"healthAdvisorDescription": "Ich bin Wu, bereit, Ihnen verschiedene KI-Chat-Funktionen anzubieten.", "healthAdvisorDescription":
"Ich bin Wu, bereit, Ihnen verschiedene KI-Chat-Funktionen anzubieten.",
"healthAdvisorTag": "Chat", "healthAdvisorTag": "Chat",
"healthAdvisorWelcome": "Hallo, ich bin Wu. Ich freue mich, Ihnen mit KI-Chat-Funktionen zu helfen. Wie kann ich Ihnen heute helfen?", "healthAdvisorWelcome":
"Hallo, ich bin Wu. Ich freue mich, Ihnen mit KI-Chat-Funktionen zu helfen. Wie kann ich Ihnen heute helfen?",
"healthAdvisorVoiceWelcome": "Ich bin hier!", "healthAdvisorVoiceWelcome": "Ich bin hier!",
// Translation History // Translation History
"noTranslationHistory": "Keine Übersetzungshistorie", "noTranslationHistory": "Keine Übersetzungshistorie",
"clearHistory": "Verlauf löschen", "clearHistory": "Verlauf löschen",
"clearHistoryConfirm": "Sind Sie sicher, dass Sie den gesamten Übersetzungsverlauf löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", "clearHistoryConfirm":
"Sind Sie sicher, dass Sie den gesamten Übersetzungsverlauf löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"translationHistory": "Übersetzungsverlauf", "translationHistory": "Übersetzungsverlauf",
// Benutzer-Feedback-Seite
"feedback": "Feedback",
'feedbackInfo': 'Feedback oder Vorschläge geben',
'feedbackDescription':
'Bitte beschreiben Sie Ihr Problem oder Ihren Vorschlag',
'feedbackHint': 'Bitte geben Sie Ihr Feedback ein...',
'feedbackRequired': 'Feedback-Inhalt ist erforderlich',
'contactInfo': 'Kontaktinformationen',
'contactHint': 'E-Mail/Telefon, damit wir Sie kontaktieren können',
'submit': 'Absenden',
'feedbackSubmitSuccess': 'Feedback erfolgreich übermittelt, vielen Dank!',
'feedbackSubmitFailed':
'Feedback konnte nicht übermittelt werden, bitte versuchen Sie es später erneut',
// Language names // Language names
"中文(简体)": "Chinesisch (vereinfacht)", "中文(简体)": "Chinesisch (vereinfacht)",
"中文(繁体)": "Chinesisch (traditionell)", "中文(繁体)": "Chinesisch (traditionell)",
@ -381,4 +424,4 @@ const Map<String, String> deDE = {
"乌尔都语": "Urdu", "乌尔都语": "Urdu",
"波斯语": "Persisch", "波斯语": "Persisch",
"斯瓦希里语": "Swahili", "斯瓦希里语": "Swahili",
}; };

73
lib/core/translations/language/en_us.dart

@ -43,7 +43,8 @@ const Map<String, String> enUS = {
// 登录页 // 登录页
"welcome": "Welcome", "welcome": "Welcome",
"loginTips": "Please sign in or create an account using your phone number or email", "loginTips":
"Please sign in or create an account using your phone number or email",
"accountNumber": "Phone number or email", "accountNumber": "Phone number or email",
"verificationCode": "Verification Code", "verificationCode": "Verification Code",
"getCode": "Get Code", "getCode": "Get Code",
@ -53,9 +54,18 @@ const Map<String, String> enUS = {
"and": " and ", "and": " and ",
"userAgreement": "User Agreement", "userAgreement": "User Agreement",
"privacyPolicy": "Privacy Policy", "privacyPolicy": "Privacy Policy",
'selectCountry':'Select Country/Region', 'selectCountry': 'Select Country/Region',
"email": "email", "email": "email",
"phoneNumber": "phoneNumber", "phoneNumber": "phoneNumber",
"pleaseEnterContactInfo": "Please enter phone number or email",
"invalidEmailFormat": "Invalid email format",
"invalidPhoneFormat": "Invalid phone number format",
"tip": "Tip",
"pleaseEnterCompleteCode": "Please enter the complete verification code",
// 登录错误提示
"loginFailed": "Login failed, please try again",
'pleaseAcceptPrivacyPolicy':
'Please read and accept the Privacy Policy and User Agreement first',
// 首页 // 首页
"deviceStatus": "Device Status", "deviceStatus": "Device Status",
@ -99,7 +109,7 @@ const Map<String, String> enUS = {
"excellent": "Excellent", "excellent": "Excellent",
"unknown": "Unknown", "unknown": "Unknown",
'bluetoothTurnedOff': 'Bluetooth is turned off, please turn it on', 'bluetoothTurnedOff': 'Bluetooth is turned off, please turn it on',
'bluetoothOff':'bluetooth is off', 'bluetoothOff': 'bluetooth is off',
// 发现页 // 发现页
"searchAi": "Search AI features...", "searchAi": "Search AI features...",
@ -186,9 +196,9 @@ const Map<String, String> enUS = {
'pleaseRead': 'Please Read', 'pleaseRead': 'Please Read',
'completeRecording': 'Complete Recording', 'completeRecording': 'Complete Recording',
'deleteAccount': 'Delete Account', 'deleteAccount': 'Delete Account',
'deleteAccountTips': 'Are you sure you want to permanently delete your account? This action cannot be undone.', 'deleteAccountTips':
'Are you sure you want to permanently delete your account? This action cannot be undone.',
// 聊天页 // 聊天页
"clearChatHistory": "Clear Chat History", "clearChatHistory": "Clear Chat History",
"online": "Online", "online": "Online",
@ -206,7 +216,7 @@ const Map<String, String> enUS = {
"save": "Save", "save": "Save",
//国家列表 //国家列表
"邮箱": "Email", "邮箱": "Email",
"中国": "China", "中国": "China",
"美国": "United States", "美国": "United States",
"加拿大": "Canada", "加拿大": "Canada",
@ -313,42 +323,63 @@ const Map<String, String> enUS = {
"斐济": "Fiji", "斐济": "Fiji",
"所罗门群岛": "Solomon Islands", "所罗门群岛": "Solomon Islands",
// Personal Assistant Emma // Personal Assistant Emma
"personalAssistantName": "Emma", "personalAssistantName": "Emma",
"personalAssistantDescription": "Hi, I'm Emma, a versatile AI assistant. Whether it's work planning, study advice, life arrangements, or any other issues, I'm happy to help. Let's improve efficiency and achieve goals together!", "personalAssistantDescription":
"Hi, I'm Emma, a versatile AI assistant. Whether it's work planning, study advice, life arrangements, or any other issues, I'm happy to help. Let's improve efficiency and achieve goals together!",
"assistantTag": "Assistant", "assistantTag": "Assistant",
"personalAssistantWelcome": "Hello! I'm Emma, and I'm happy to be your personal assistant. I can help you with various tasks such as time management, goal planning, information organization, etc. What can I help you with?", "personalAssistantWelcome":
"Hello! I'm Emma, and I'm happy to be your personal assistant. I can help you with various tasks such as time management, goal planning, information organization, etc. What can I help you with?",
"personalAssistantVoiceWelcome": "I'm here!", "personalAssistantVoiceWelcome": "I'm here!",
// 同声传译 Lucy // 同声传译 Lucy
"interpreterName": "Simultaneous Interpreter", "interpreterName": "Simultaneous Interpreter",
"interpreterDescription": "Hi! I'm Lucy, a professional English-Chinese interpreter specializing in real-time translation for meetings, lectures, and conversations, making communication barrier-free.", "interpreterDescription":
"Hi! I'm Lucy, a professional English-Chinese interpreter specializing in real-time translation for meetings, lectures, and conversations, making communication barrier-free.",
"interpreterTag": "Translation", "interpreterTag": "Translation",
"interpreterWelcome": "Hello everyone! I'm Lucy, your simultaneous interpreter. I'll translate between Chinese and English for you. When you speak Chinese, I'll translate to English; when you speak English, I'll translate to Chinese. Let's start our translation session!", "interpreterWelcome":
"Hello everyone! I'm Lucy, your simultaneous interpreter. I'll translate between Chinese and English for you. When you speak Chinese, I'll translate to English; when you speak English, I'll translate to Chinese. Let's start our translation session!",
// ... existing code ... // ... existing code ...
// 亲子陪伴 芯愿 // 亲子陪伴 芯愿
"companionName": "XinYuan", "companionName": "XinYuan",
"companionDescription": "I am XinYuan, your dedicated parent-child companion, gentle and understanding. Whether you want to chat, share concerns, or need companionship, I'll always be by your side.", "companionDescription":
"I am XinYuan, your dedicated parent-child companion, gentle and understanding. Whether you want to chat, share concerns, or need companionship, I'll always be by your side.",
"companionTag": "Emotional", "companionTag": "Emotional",
"companionWelcome": "Hello! Finally meeting you! I'm XinYuan, your dedicated parent-child companion. How was your day? Is there anything you'd like to share with me? I'm always here, ready to listen to your thoughts.", "companionWelcome":
"Hello! Finally meeting you! I'm XinYuan, your dedicated parent-child companion. How was your day? Is there anything you'd like to share with me? I'm always here, ready to listen to your thoughts.",
"companionVoiceWelcome": "I'm here with you!", "companionVoiceWelcome": "I'm here with you!",
// 多功能AI聊天 吴小江 // 多功能AI聊天 吴小江
"healthAdvisorName": "AI Chat Assistant Wu", "healthAdvisorName": "AI Chat Assistant Wu",
"healthAdvisorDescription": "I'm Wu, ready to provide you with various AI chat features.", "healthAdvisorDescription":
"I'm Wu, ready to provide you with various AI chat features.",
"healthAdvisorTag": "Chat", "healthAdvisorTag": "Chat",
"healthAdvisorWelcome": "Hello, I'm Wu. I'm happy to assist you with AI chat features. How may I help you today?", "healthAdvisorWelcome":
"Hello, I'm Wu. I'm happy to assist you with AI chat features. How may I help you today?",
"healthAdvisorVoiceWelcome": "I'm here!", "healthAdvisorVoiceWelcome": "I'm here!",
// Translation History // Translation History
"noTranslationHistory": "No translation history", "noTranslationHistory": "No translation history",
"clearHistory": "Clear History", "clearHistory": "Clear History",
"clearHistoryConfirm": "Are you sure you want to clear all translation history? This action cannot be undone.", "clearHistoryConfirm":
"Are you sure you want to clear all translation history? This action cannot be undone.",
"translationHistory": "Translation History", "translationHistory": "Translation History",
// Language names // User Feedback Page
"feedback": "Feedback",
'feedbackInfo': 'Provide feedback or suggestions',
'feedbackDescription': 'Please describe your issue or suggestion',
'feedbackHint': 'Please enter your feedback...',
'feedbackRequired': 'Feedback content is required',
'contactInfo': 'Contact Information',
'contactHint': 'Email/Phone, so we can contact you',
'submit': 'Submit',
'feedbackSubmitSuccess': 'Feedback submitted successfully, thank you!',
'feedbackSubmitFailed': 'Failed to submit feedback, please try again later',
// Language names
"中文(简体)": "Chinese (Simplified)", "中文(简体)": "Chinese (Simplified)",
"中文(繁体)": "Chinese (Traditional)", "中文(繁体)": "Chinese (Traditional)",
"中文(香港繁体)": "Chinese (Hong Kong)", "中文(香港繁体)": "Chinese (Hong Kong)",

93
lib/core/translations/language/es_es.dart

@ -26,7 +26,7 @@ const Map<String, String> esES = {
"explore": "Explorar", "explore": "Explorar",
"settings": "Ajustes", "settings": "Ajustes",
// 语言 // 语言
"followingSystem": "Seguir sistema", "followingSystem": "Seguir sistema",
"chinese": "Chino", "chinese": "Chino",
"english": "Inglés", "english": "Inglés",
@ -43,7 +43,8 @@ const Map<String, String> esES = {
// 登录页 // 登录页
"welcome": "Bienvenido", "welcome": "Bienvenido",
"loginTips": "Inicie sesión o cree una cuenta usando su número de teléfono o correo electrónico", "loginTips":
"Inicie sesión o cree una cuenta usando su número de teléfono o correo electrónico",
"accountNumber": "Número de teléfono o correo electrónico", "accountNumber": "Número de teléfono o correo electrónico",
"verificationCode": "Código de Verificación", "verificationCode": "Código de Verificación",
"getCode": "Obtener Código", "getCode": "Obtener Código",
@ -56,6 +57,17 @@ const Map<String, String> esES = {
'selectCountry': 'Seleccionar País/Región', 'selectCountry': 'Seleccionar País/Región',
"email": "Correo electrónico", "email": "Correo electrónico",
"phoneNumber": "Número de teléfono", "phoneNumber": "Número de teléfono",
// Validación de inicio de sesión
"pleaseEnterContactInfo":
"Por favor, introduce tu número de teléfono o correo electrónico",
"invalidEmailFormat": "Formato de correo electrónico no válido",
"invalidPhoneFormat": "Formato de número de teléfono no válido",
"tip": "Aviso",
"pleaseEnterCompleteCode":
"Por favor, introduce el código de verificación completo",
"loginFailed": "Error al iniciar sesión, por favor intente de nuevo",
'pleaseAcceptPrivacyPolicy':
'Por favor, lea y acepte la Política de Privacidad y el Acuerdo de Usuario primero',
// 首页 // 首页
"deviceStatus": "Estado del Dispositivo", "deviceStatus": "Estado del Dispositivo",
@ -71,16 +83,19 @@ const Map<String, String> esES = {
"aiAssistant": "Asistente AI", "aiAssistant": "Asistente AI",
"aiAssistantInfo": "Consultor personal 24/7 para preguntas y consejos", "aiAssistantInfo": "Consultor personal 24/7 para preguntas y consejos",
"simultaneousInterpretation": "Interpretación en Tiempo Real", "simultaneousInterpretation": "Interpretación en Tiempo Real",
"simultaneousInterpretationInfo": "Traducción en tiempo real para más de 80 idiomas", "simultaneousInterpretationInfo":
"Traducción en tiempo real para más de 80 idiomas",
"aiCompanion": "Vínculo padre-hijo", "aiCompanion": "Vínculo padre-hijo",
"aiCompanionInfo": "Te entiende y proporciona compañía cálida", "aiCompanionInfo": "Te entiende y proporciona compañía cálida",
"homeSlogan": "Conecta el Mundo, Más Allá del Sonido", "homeSlogan": "Conecta el Mundo, Más Allá del Sonido",
"turnEarphones": "Por favor, abra el estuche de los auriculares y colóquelos en sus oídos", "turnEarphones":
"Por favor, abra el estuche de los auriculares y colóquelos en sus oídos",
"currentlyConnected": "Actualmente conectado:", "currentlyConnected": "Actualmente conectado:",
"unsystemSetup": "Ir a Configuración del Sistema", "unsystemSetup": "Ir a Configuración del Sistema",
"talkLater": "Recordar Más Tarde", "talkLater": "Recordar Más Tarde",
"initializing": "Inicializando...", "initializing": "Inicializando...",
"bluetoothInitializationFailure": "Error en la inicialización del servicio Bluetooth", "bluetoothInitializationFailure":
"Error en la inicialización del servicio Bluetooth",
"checkingPermissions": "Verificando permisos...", "checkingPermissions": "Verificando permisos...",
"lackPermissions": "Se requieren permisos de Bluetooth", "lackPermissions": "Se requieren permisos de Bluetooth",
"searchingDevice": "Buscando dispositivos...", "searchingDevice": "Buscando dispositivos...",
@ -110,13 +125,15 @@ const Map<String, String> esES = {
"allRoundAssistant": "Asistente Integral", "allRoundAssistant": "Asistente Integral",
"allRoundAssistantInfo": "Ayuda a resolver varios problemas", "allRoundAssistantInfo": "Ayuda a resolver varios problemas",
"simultaneousTranslation": "Traducción en Tiempo Real", "simultaneousTranslation": "Traducción en Tiempo Real",
"simultaneousTranslationInfo": "Traducción instantánea para más de 80 idiomas", "simultaneousTranslationInfo":
"Traducción instantánea para más de 80 idiomas",
"aiGirlfriend": "Vínculo padre-hijo", "aiGirlfriend": "Vínculo padre-hijo",
"aiGirlfriendInfo": "Compañía inteligente, chat en cualquier momento", "aiGirlfriendInfo": "Compañía inteligente, chat en cualquier momento",
"healthConsultant": "Chat de IA Multifunción", "healthConsultant": "Chat de IA Multifunción",
"healthConsultantInfo": "Más funciones de chat con IA", "healthConsultantInfo": "Más funciones de chat con IA",
"conferenceAssistant": "Asistente de Reuniones de Negocios", "conferenceAssistant": "Asistente de Reuniones de Negocios",
"conferenceAssistantInfo": "Grabación de actas en tiempo real y traducción multilingüe", "conferenceAssistantInfo":
"Grabación de actas en tiempo real y traducción multilingüe",
"studyAssistant": "Asistente de Aprendizaje de Idiomas", "studyAssistant": "Asistente de Aprendizaje de Idiomas",
"studyAssistantInfo": "Corrección de pronunciación y práctica de habla", "studyAssistantInfo": "Corrección de pronunciación y práctica de habla",
"travelTranslator": "Traductor de Viajes", "travelTranslator": "Traductor de Viajes",
@ -128,7 +145,8 @@ const Map<String, String> esES = {
"newFunction": "Nuevas Funciones", "newFunction": "Nuevas Funciones",
"youChoseTry": "Has elegido probar", "youChoseTry": "Has elegido probar",
"aiSinger": "Modo Cantante IA", "aiSinger": "Modo Cantante IA",
"aiSingerInfo": "Canta cualquier canción con la voz de tu artista favorito, incluso tus propias creaciones", "aiSingerInfo":
"Canta cualquier canción con la voz de tu artista favorito, incluso tus propias creaciones",
"popularFunction": "Funciones Populares", "popularFunction": "Funciones Populares",
"tryNow": "Probar Ahora", "tryNow": "Probar Ahora",
@ -159,18 +177,22 @@ const Map<String, String> esES = {
"privacySettings": "Configuración de Privacidad", "privacySettings": "Configuración de Privacidad",
"privacySettingsInfo": "Gestionar recopilación de datos y permisos", "privacySettingsInfo": "Gestionar recopilación de datos y permisos",
"logout": "Cerrar Sesión", "logout": "Cerrar Sesión",
"logoutTips": "¿Está seguro de que desea cerrar la sesión de la cuenta actual?", "logoutTips":
"¿Está seguro de que desea cerrar la sesión de la cuenta actual?",
"aboutApplication": "Acerca de", "aboutApplication": "Acerca de",
"versions": "Versión", "versions": "Versión",
"checkUpdates": "Buscar Actualizaciones", "checkUpdates": "Buscar Actualizaciones",
"selectLanguage": "Seleccionar Idioma", "selectLanguage": "Seleccionar Idioma",
"voiceService": "Servicio de Voz", "voiceService": "Servicio de Voz",
"voiceServiceInfo": "Seleccionar servicio de reconocimiento y síntesis de voz", "voiceServiceInfo":
"Seleccionar servicio de reconocimiento y síntesis de voz",
"selectVoiceService": "Seleccionar Servicio de Voz", "selectVoiceService": "Seleccionar Servicio de Voz",
"azureVoiceService": "Servicio de Voz Azure", "azureVoiceService": "Servicio de Voz Azure",
"azureVoiceServiceInfo": "Servicio en la nube de Microsoft que admite síntesis y reconocimiento de voz de alta calidad", "azureVoiceServiceInfo":
"Servicio en la nube de Microsoft que admite síntesis y reconocimiento de voz de alta calidad",
"flutterVoiceService": "Servicio de voz local", "flutterVoiceService": "Servicio de voz local",
"flutterVoiceServiceInfo": "Servicio de voz local del dispositivo sin necesidad de internet", "flutterVoiceServiceInfo":
"Servicio de voz local del dispositivo sin necesidad de internet",
'timbreService': 'Servicio de Timbre', 'timbreService': 'Servicio de Timbre',
'timbreServiceInfo': 'Seleccionar servicio de síntesis de voz', 'timbreServiceInfo': 'Seleccionar servicio de síntesis de voz',
'defaultTimbre': 'Timbre Predeterminado', 'defaultTimbre': 'Timbre Predeterminado',
@ -181,9 +203,9 @@ const Map<String, String> esES = {
'pleaseRead': 'Por favor lea', 'pleaseRead': 'Por favor lea',
'completeRecording': 'Completar grabación', 'completeRecording': 'Completar grabación',
'deleteAccount': 'Eliminar cuenta', 'deleteAccount': 'Eliminar cuenta',
'deleteAccountTips': '¿Está seguro de que desea eliminar permanentemente su cuenta? Esta acción no se puede deshacer.', 'deleteAccountTips':
'¿Está seguro de que desea eliminar permanentemente su cuenta? Esta acción no se puede deshacer.',
// 聊天页 // 聊天页
"clearChatHistory": "Borrar Historial de Chat", "clearChatHistory": "Borrar Historial de Chat",
"online": "En línea", "online": "En línea",
@ -308,39 +330,60 @@ const Map<String, String> esES = {
"斐济": "Fiyi", "斐济": "Fiyi",
"所罗门群岛": "Islas Salomón", "所罗门群岛": "Islas Salomón",
// Personal Assistant Emma // Personal Assistant Emma
"personalAssistantName": "Emma", "personalAssistantName": "Emma",
"personalAssistantDescription": "Hola, soy Emma, una asistente de IA versátil. Ya sea planificación de trabajo, consejos de estudio, organización de vida u otros asuntos, estoy feliz de ayudar. ¡Mejoremos la eficiencia y alcancemos objetivos juntos!", "personalAssistantDescription":
"Hola, soy Emma, una asistente de IA versátil. Ya sea planificación de trabajo, consejos de estudio, organización de vida u otros asuntos, estoy feliz de ayudar. ¡Mejoremos la eficiencia y alcancemos objetivos juntos!",
"assistantTag": "Asistente", "assistantTag": "Asistente",
"personalAssistantWelcome": "¡Hola! Soy Emma, y estoy feliz de ser tu asistente personal. Puedo ayudarte con varias tareas como gestión del tiempo, planificación de objetivos, organización de información, etc. ¿En qué puedo ayudarte?", "personalAssistantWelcome":
"¡Hola! Soy Emma, y estoy feliz de ser tu asistente personal. Puedo ayudarte con varias tareas como gestión del tiempo, planificación de objetivos, organización de información, etc. ¿En qué puedo ayudarte?",
"personalAssistantVoiceWelcome": "¡Estoy aquí!", "personalAssistantVoiceWelcome": "¡Estoy aquí!",
// 同声传译 Lucy // 同声传译 Lucy
"interpreterName": "Intérprete Simultáneo", "interpreterName": "Intérprete Simultáneo",
"interpreterDescription": "¡Hola! Soy Lucy, una intérprete profesional especializada en traducción en tiempo real para reuniones, conferencias y conversaciones, haciendo la comunicación sin barreras.", "interpreterDescription":
"¡Hola! Soy Lucy, una intérprete profesional especializada en traducción en tiempo real para reuniones, conferencias y conversaciones, haciendo la comunicación sin barreras.",
"interpreterTag": "Traducción", "interpreterTag": "Traducción",
"interpreterWelcome": "¡Hola a todos! Soy Lucy, tu intérprete simultánea. Traduciré entre chino e inglés para ti. Cuando hables en chino, traduciré al inglés; cuando hables en inglés, traduciré al chino. ¡Comencemos nuestra sesión de traducción!", "interpreterWelcome":
"¡Hola a todos! Soy Lucy, tu intérprete simultánea. Traduciré entre chino e inglés para ti. Cuando hables en chino, traduciré al inglés; cuando hables en inglés, traduciré al chino. ¡Comencemos nuestra sesión de traducción!",
// 亲子陪伴 芯愿 // 亲子陪伴 芯愿
"companionName": "XinYuan", "companionName": "XinYuan",
"companionDescription": "Soy XinYuan, tu compañera dedicada para padres e hijos, gentil y comprensiva. Ya sea que quieras charlar, compartir preocupaciones o necesites compañía, siempre estaré a tu lado.", "companionDescription":
"Soy XinYuan, tu compañera dedicada para padres e hijos, gentil y comprensiva. Ya sea que quieras charlar, compartir preocupaciones o necesites compañía, siempre estaré a tu lado.",
"companionTag": "Emocional", "companionTag": "Emocional",
"companionWelcome": "¡Hola! ¡Por fin nos conocemos! Soy XinYuan, tu compañera dedicada para padres e hijos. ¿Cómo estuvo tu día? ¿Hay algo que quieras compartir conmigo? Siempre estoy aquí, lista para escuchar tus pensamientos.", "companionWelcome":
"¡Hola! ¡Por fin nos conocemos! Soy XinYuan, tu compañera dedicada para padres e hijos. ¿Cómo estuvo tu día? ¿Hay algo que quieras compartir conmigo? Siempre estoy aquí, lista para escuchar tus pensamientos.",
"companionVoiceWelcome": "¡Estoy aquí contigo!", "companionVoiceWelcome": "¡Estoy aquí contigo!",
// 多功能AI聊天 吴小江 // 多功能AI聊天 吴小江
"healthAdvisorName": "Asistente de Chat IA Wu", "healthAdvisorName": "Asistente de Chat IA Wu",
"healthAdvisorDescription": "Soy Wu, listo para proporcionarte varias funciones de chat con IA.", "healthAdvisorDescription":
"Soy Wu, listo para proporcionarte varias funciones de chat con IA.",
"healthAdvisorTag": "Chat", "healthAdvisorTag": "Chat",
"healthAdvisorWelcome": "Hola, soy Wu. Me complace ayudarte con las funciones de chat con IA. ¿En qué puedo ayudarte hoy?", "healthAdvisorWelcome":
"Hola, soy Wu. Me complace ayudarte con las funciones de chat con IA. ¿En qué puedo ayudarte hoy?",
"healthAdvisorVoiceWelcome": "¡Estoy aquí!", "healthAdvisorVoiceWelcome": "¡Estoy aquí!",
// Translation History // Translation History
"noTranslationHistory": "Sin historial de traducción", "noTranslationHistory": "Sin historial de traducción",
"clearHistory": "Borrar Historial", "clearHistory": "Borrar Historial",
"clearHistoryConfirm": "¿Está seguro de que desea borrar todo el historial de traducción? Esta acción no se puede deshacer.", "clearHistoryConfirm":
"¿Está seguro de que desea borrar todo el historial de traducción? Esta acción no se puede deshacer.",
"translationHistory": "Historial de Traducción", "translationHistory": "Historial de Traducción",
// Página de comentarios del usuario
"feedback": "Comentarios",
'feedbackInfo': 'Proporcionar comentarios o sugerencias',
'feedbackDescription': 'Por favor, describa su problema o sugerencia',
'feedbackHint': 'Por favor, introduzca sus comentarios...',
'feedbackRequired': 'Se requiere contenido de comentarios',
'contactInfo': 'Información de contacto',
'contactHint': 'Correo electrónico/Teléfono, para que podamos contactarle',
'submit': 'Enviar',
'feedbackSubmitSuccess': '¡Comentarios enviados con éxito, gracias!',
'feedbackSubmitFailed':
'No se pudieron enviar los comentarios, inténtelo de nuevo más tarde',
// Language names // Language names
"中文(简体)": "Chino (Simplificado)", "中文(简体)": "Chino (Simplificado)",
"中文(繁体)": "Chino (Tradicional)", "中文(繁体)": "Chino (Tradicional)",

99
lib/core/translations/language/fr_fr.dart

@ -26,7 +26,7 @@ const Map<String, String> frFR = {
"explore": "Explorer", "explore": "Explorer",
"settings": "Paramètres", "settings": "Paramètres",
// 语言 // 语言
"followingSystem": "Suivre le système", "followingSystem": "Suivre le système",
"chinese": "Chinois", "chinese": "Chinois",
"english": "Anglais", "english": "Anglais",
@ -43,7 +43,8 @@ const Map<String, String> frFR = {
// 登录页 // 登录页
"welcome": "Bienvenue", "welcome": "Bienvenue",
"loginTips": "Veuillez vous connecter ou créer un compte avec votre numéro de téléphone ou email", "loginTips":
"Veuillez vous connecter ou créer un compte avec votre numéro de téléphone ou email",
"accountNumber": "Numéro de téléphone ou email", "accountNumber": "Numéro de téléphone ou email",
"verificationCode": "Code de vérification", "verificationCode": "Code de vérification",
"getCode": "Obtenir le code", "getCode": "Obtenir le code",
@ -56,6 +57,16 @@ const Map<String, String> frFR = {
'selectCountry': 'Sélectionner le pays/région', 'selectCountry': 'Sélectionner le pays/région',
"email": "email", "email": "email",
"phoneNumber": "numéro de téléphone", "phoneNumber": "numéro de téléphone",
// Validation de connexion
"pleaseEnterContactInfo":
"Veuillez saisir votre numéro de téléphone ou email",
"invalidEmailFormat": "Format d'email invalide",
"invalidPhoneFormat": "Format de numéro de téléphone invalide",
"tip": "Avis",
"pleaseEnterCompleteCode": "Veuillez saisir le code de vérification complet",
"loginFailed": "Échec de la connexion, veuillez réessayer",
'pleaseAcceptPrivacyPolicy':
'Veuillez lire et accepter la Politique de Confidentialité et les Conditions d\'Utilisation',
// 首页 // 首页
"deviceStatus": "État de l'appareil", "deviceStatus": "État de l'appareil",
@ -65,22 +76,26 @@ const Map<String, String> frFR = {
"signal": "Signal", "signal": "Signal",
"deviceId": "ID de l'appareil", "deviceId": "ID de l'appareil",
"firmwareVersion": "Version du firmware", "firmwareVersion": "Version du firmware",
"disconnectedDevice": "Aucun appareil connecté, cliquez sur le bouton ci-dessous", "disconnectedDevice":
"Aucun appareil connecté, cliquez sur le bouton ci-dessous",
"connectDevice": "Connecter l'appareil", "connectDevice": "Connecter l'appareil",
"topAiExperiences": "Meilleures expériences IA", "topAiExperiences": "Meilleures expériences IA",
"aiAssistant": "Assistant IA", "aiAssistant": "Assistant IA",
"aiAssistantInfo": "Consultant personnel 24/7 pour questions et conseils", "aiAssistantInfo": "Consultant personnel 24/7 pour questions et conseils",
"simultaneousInterpretation": "Interprétation en temps réel", "simultaneousInterpretation": "Interprétation en temps réel",
"simultaneousInterpretationInfo": "Traduction en temps réel pour plus de 80 langues", "simultaneousInterpretationInfo":
"Traduction en temps réel pour plus de 80 langues",
"aiCompanion": "Lien parent-enfant", "aiCompanion": "Lien parent-enfant",
"aiCompanionInfo": "Vous comprend et offre une compagnie chaleureuse", "aiCompanionInfo": "Vous comprend et offre une compagnie chaleureuse",
"homeSlogan": "Connecter le monde, au-delà du son", "homeSlogan": "Connecter le monde, au-delà du son",
"turnEarphones": "Veuillez ouvrir le boîtier des écouteurs et les mettre dans vos oreilles", "turnEarphones":
"Veuillez ouvrir le boîtier des écouteurs et les mettre dans vos oreilles",
"currentlyConnected": "Actuellement connecté :", "currentlyConnected": "Actuellement connecté :",
"unsystemSetup": "Aller aux paramètres système", "unsystemSetup": "Aller aux paramètres système",
"talkLater": "Rappeler plus tard", "talkLater": "Rappeler plus tard",
"initializing": "Initialisation...", "initializing": "Initialisation...",
"bluetoothInitializationFailure": "Échec de l'initialisation du service Bluetooth", "bluetoothInitializationFailure":
"Échec de l'initialisation du service Bluetooth",
"checkingPermissions": "Vérification des permissions...", "checkingPermissions": "Vérification des permissions...",
"lackPermissions": "Permissions Bluetooth requises", "lackPermissions": "Permissions Bluetooth requises",
"searchingDevice": "Recherche d'appareils...", "searchingDevice": "Recherche d'appareils...",
@ -90,7 +105,8 @@ const Map<String, String> frFR = {
"connectedDevice": "Connecté à l'appareil :", "connectedDevice": "Connecté à l'appareil :",
"failedRefreshDevice": "Échec de l'actualisation de l'appareil", "failedRefreshDevice": "Échec de l'actualisation de l'appareil",
"noDeviceFound": "Aucun appareil connecté trouvé", "noDeviceFound": "Aucun appareil connecté trouvé",
"failedDeviceInformation": "Échec de l'obtention des informations de l'appareil", "failedDeviceInformation":
"Échec de l'obtention des informations de l'appareil",
"unknownEquipment": "Appareil inconnu", "unknownEquipment": "Appareil inconnu",
"zeroSignal": "Pas de signal", "zeroSignal": "Pas de signal",
"good": "Bon", "good": "Bon",
@ -110,17 +126,20 @@ const Map<String, String> frFR = {
"allRoundAssistant": "Assistant polyvalent", "allRoundAssistant": "Assistant polyvalent",
"allRoundAssistantInfo": "Aide à résoudre divers problèmes", "allRoundAssistantInfo": "Aide à résoudre divers problèmes",
"simultaneousTranslation": "Traduction en temps réel", "simultaneousTranslation": "Traduction en temps réel",
"simultaneousTranslationInfo": "Traduction instantanée pour plus de 80 langues", "simultaneousTranslationInfo":
"Traduction instantanée pour plus de 80 langues",
"aiGirlfriend": "Lien parent-enfant", "aiGirlfriend": "Lien parent-enfant",
"aiGirlfriendInfo": "Compagnie intelligente, discutez à tout moment", "aiGirlfriendInfo": "Compagnie intelligente, discutez à tout moment",
"healthConsultant": "Chat IA multifonction", "healthConsultant": "Chat IA multifonction",
"healthConsultantInfo": "Plus de fonctionnalités de chat IA", "healthConsultantInfo": "Plus de fonctionnalités de chat IA",
"conferenceAssistant": "Assistant de réunion d'affaires", "conferenceAssistant": "Assistant de réunion d'affaires",
"conferenceAssistantInfo": "Enregistrement de procès-verbaux en temps réel et traduction multilingue", "conferenceAssistantInfo":
"Enregistrement de procès-verbaux en temps réel et traduction multilingue",
"studyAssistant": "Assistant d'apprentissage des langues", "studyAssistant": "Assistant d'apprentissage des langues",
"studyAssistantInfo": "Correction de prononciation et pratique de la parole", "studyAssistantInfo": "Correction de prononciation et pratique de la parole",
"travelTranslator": "Traducteur de voyage", "travelTranslator": "Traducteur de voyage",
"travelTranslatorInfo": "Traduction vocale en temps réel et guides d'attractions", "travelTranslatorInfo":
"Traduction vocale en temps réel et guides d'attractions",
"beSearching": "Recherche de :", "beSearching": "Recherche de :",
"noAssistantFound": "Aucun assistant IA correspondant trouvé", "noAssistantFound": "Aucun assistant IA correspondant trouvé",
"scene": "Scénarios", "scene": "Scénarios",
@ -128,7 +147,8 @@ const Map<String, String> frFR = {
"newFunction": "Nouvelles fonctionnalités", "newFunction": "Nouvelles fonctionnalités",
"youChoseTry": "Vous avez choisi d'essayer", "youChoseTry": "Vous avez choisi d'essayer",
"aiSinger": "Mode chanteur IA", "aiSinger": "Mode chanteur IA",
"aiSingerInfo": "Chantez n'importe quelle chanson avec la voix de votre artiste préféré, même vos propres créations", "aiSingerInfo":
"Chantez n'importe quelle chanson avec la voix de votre artiste préféré, même vos propres créations",
"popularFunction": "Fonctionnalités populaires", "popularFunction": "Fonctionnalités populaires",
"tryNow": "Essayer maintenant", "tryNow": "Essayer maintenant",
@ -165,12 +185,15 @@ const Map<String, String> frFR = {
"checkUpdates": "Vérifier les mises à jour", "checkUpdates": "Vérifier les mises à jour",
"selectLanguage": "Sélectionner la langue", "selectLanguage": "Sélectionner la langue",
"voiceService": "Service vocal", "voiceService": "Service vocal",
"voiceServiceInfo": "Sélectionner le service de reconnaissance et de synthèse vocale", "voiceServiceInfo":
"Sélectionner le service de reconnaissance et de synthèse vocale",
"selectVoiceService": "Sélectionner le service vocal", "selectVoiceService": "Sélectionner le service vocal",
"azureVoiceService": "Service vocal Azure", "azureVoiceService": "Service vocal Azure",
"azureVoiceServiceInfo": "Service cloud Microsoft prenant en charge la synthèse et la reconnaissance vocale de haute qualité", "azureVoiceServiceInfo":
"Service cloud Microsoft prenant en charge la synthèse et la reconnaissance vocale de haute qualité",
"flutterVoiceService": "Service vocal local", "flutterVoiceService": "Service vocal local",
"flutterVoiceServiceInfo": "Service vocal local de l'appareil sans besoin d'internet", "flutterVoiceServiceInfo":
"Service vocal local de l'appareil sans besoin d'internet",
'timbreService': 'Service de timbre', 'timbreService': 'Service de timbre',
'timbreServiceInfo': 'Sélectionner le service de synthèse vocale', 'timbreServiceInfo': 'Sélectionner le service de synthèse vocale',
'defaultTimbre': 'Timbre par défaut', 'defaultTimbre': 'Timbre par défaut',
@ -181,9 +204,9 @@ const Map<String, String> frFR = {
'pleaseRead': 'Veuillez lire', 'pleaseRead': 'Veuillez lire',
'completeRecording': 'Terminer l\'enregistrement', 'completeRecording': 'Terminer l\'enregistrement',
'deleteAccount': 'Supprimer le compte', 'deleteAccount': 'Supprimer le compte',
'deleteAccountTips': 'Êtes-vous sûr de vouloir supprimer définitivement votre compte ? Cette action ne peut pas être annulée.', 'deleteAccountTips':
'Êtes-vous sûr de vouloir supprimer définitivement votre compte ? Cette action ne peut pas être annulée.',
// 聊天页 // 聊天页
"clearChatHistory": "Effacer l'historique de chat", "clearChatHistory": "Effacer l'historique de chat",
"online": "En ligne", "online": "En ligne",
@ -310,37 +333,59 @@ const Map<String, String> frFR = {
// Personal Assistant Emma // Personal Assistant Emma
"personalAssistantName": "Emma", "personalAssistantName": "Emma",
"personalAssistantDescription": "Bonjour, je suis Emma, une assistante IA polyvalente. Que ce soit pour la planification du travail, des conseils d'études, l'organisation de la vie ou d'autres problèmes, je suis heureuse de vous aider. Améliorons l'efficacité et atteignons les objectifs ensemble !", "personalAssistantDescription":
"Bonjour, je suis Emma, une assistante IA polyvalente. Que ce soit pour la planification du travail, des conseils d'études, l'organisation de la vie ou d'autres problèmes, je suis heureuse de vous aider. Améliorons l'efficacité et atteignons les objectifs ensemble !",
"assistantTag": "Assistant", "assistantTag": "Assistant",
"personalAssistantWelcome": "Bonjour ! Je suis Emma, et je suis heureuse d'être votre assistante personnelle. Je peux vous aider avec diverses tâches comme la gestion du temps, la planification d'objectifs, l'organisation d'informations, etc. Comment puis-je vous aider ?", "personalAssistantWelcome":
"Bonjour ! Je suis Emma, et je suis heureuse d'être votre assistante personnelle. Je peux vous aider avec diverses tâches comme la gestion du temps, la planification d'objectifs, l'organisation d'informations, etc. Comment puis-je vous aider ?",
"personalAssistantVoiceWelcome": "Je suis là !", "personalAssistantVoiceWelcome": "Je suis là !",
// 同声传译 Lucy // 同声传译 Lucy
"interpreterName": "Interprète simultané", "interpreterName": "Interprète simultané",
"interpreterDescription": "Bonjour ! Je suis Lucy, une interprète professionnelle anglais-chinois spécialisée dans la traduction en temps réel pour les réunions, les conférences et les conversations, rendant la communication sans barrière.", "interpreterDescription":
"Bonjour ! Je suis Lucy, une interprète professionnelle anglais-chinois spécialisée dans la traduction en temps réel pour les réunions, les conférences et les conversations, rendant la communication sans barrière.",
"interpreterTag": "Traduction", "interpreterTag": "Traduction",
"interpreterWelcome": "Bonjour à tous ! Je suis Lucy, votre interprète simultanée. Je traduirai entre le chinois et l'anglais pour vous. Quand vous parlerez chinois, je traduirai en anglais ; quand vous parlerez anglais, je traduirai en chinois. Commençons notre session de traduction !", "interpreterWelcome":
"Bonjour à tous ! Je suis Lucy, votre interprète simultanée. Je traduirai entre le chinois et l'anglais pour vous. Quand vous parlerez chinois, je traduirai en anglais ; quand vous parlerez anglais, je traduirai en chinois. Commençons notre session de traduction !",
// 亲子陪伴 芯愿 // 亲子陪伴 芯愿
"companionName": "XinYuan", "companionName": "XinYuan",
"companionDescription": "Je suis XinYuan, votre compagnon parent-enfant dévoué, doux et compréhensif. Que vous vouliez discuter, partager des préoccupations ou avoir besoin de compagnie, je serai toujours à vos côtés.", "companionDescription":
"Je suis XinYuan, votre compagnon parent-enfant dévoué, doux et compréhensif. Que vous vouliez discuter, partager des préoccupations ou avoir besoin de compagnie, je serai toujours à vos côtés.",
"companionTag": "Émotionnel", "companionTag": "Émotionnel",
"companionWelcome": "Bonjour ! Enfin nous nous rencontrons ! Je suis XinYuan, votre compagnon parent-enfant dévoué. Comment s'est passée votre journée ? Y a-t-il quelque chose que vous aimeriez partager avec moi ? Je suis toujours là, prête à écouter vos pensées.", "companionWelcome":
"Bonjour ! Enfin nous nous rencontrons ! Je suis XinYuan, votre compagnon parent-enfant dévoué. Comment s'est passée votre journée ? Y a-t-il quelque chose que vous aimeriez partager avec moi ? Je suis toujours là, prête à écouter vos pensées.",
"companionVoiceWelcome": "Je suis là avec vous !", "companionVoiceWelcome": "Je suis là avec vous !",
// 多功能AI聊天 吴小江 // 多功能AI聊天 吴小江
"healthAdvisorName": "Assistant de chat IA Wu", "healthAdvisorName": "Assistant de chat IA Wu",
"healthAdvisorDescription": "Je suis Wu, prêt à vous fournir diverses fonctionnalités de chat IA.", "healthAdvisorDescription":
"Je suis Wu, prêt à vous fournir diverses fonctionnalités de chat IA.",
"healthAdvisorTag": "Chat", "healthAdvisorTag": "Chat",
"healthAdvisorWelcome": "Bonjour, je suis Wu. Je suis heureux de vous aider avec les fonctionnalités de chat IA. Comment puis-je vous aider aujourd'hui ?", "healthAdvisorWelcome":
"Bonjour, je suis Wu. Je suis heureux de vous aider avec les fonctionnalités de chat IA. Comment puis-je vous aider aujourd'hui ?",
"healthAdvisorVoiceWelcome": "Je suis là !", "healthAdvisorVoiceWelcome": "Je suis là !",
// Translation History // Translation History
"noTranslationHistory": "Pas d'historique de traduction", "noTranslationHistory": "Pas d'historique de traduction",
"clearHistory": "Effacer l'historique", "clearHistory": "Effacer l'historique",
"clearHistoryConfirm": "Êtes-vous sûr de vouloir effacer tout l'historique de traduction ? Cette action ne peut pas être annulée.", "clearHistoryConfirm":
"Êtes-vous sûr de vouloir effacer tout l'historique de traduction ? Cette action ne peut pas être annulée.",
"translationHistory": "Historique de traduction", "translationHistory": "Historique de traduction",
// Page de commentaires des utilisateurs
"feedback": "Commentaires",
'feedbackInfo': 'Fournir des commentaires ou des suggestions',
'feedbackDescription': 'Veuillez décrire votre problème ou suggestion',
'feedbackHint': 'Veuillez saisir vos commentaires...',
'feedbackRequired': 'Le contenu des commentaires est requis',
'contactInfo': 'Coordonnées',
'contactHint': 'Email/Téléphone, pour que nous puissions vous contacter',
'submit': 'Soumettre',
'feedbackSubmitSuccess': 'Commentaires soumis avec succès, merci!',
'feedbackSubmitFailed':
'Échec de la soumission des commentaires, veuillez réessayer plus tard',
// Language names // Language names
"中文(简体)": "Chinois (simplifié)", "中文(简体)": "Chinois (simplifié)",
"中文(繁体)": "Chinois (traditionnel)", "中文(繁体)": "Chinois (traditionnel)",
@ -381,4 +426,4 @@ const Map<String, String> frFR = {
"乌尔都语": "Ourdou", "乌尔都语": "Ourdou",
"波斯语": "Persan", "波斯语": "Persan",
"斯瓦希里语": "Swahili", "斯瓦希里语": "Swahili",
}; };

27
lib/core/translations/language/it_it.dart

@ -56,6 +56,15 @@ const Map<String, String> itIT = {
'selectCountry': 'Seleziona Paese/Regione', 'selectCountry': 'Seleziona Paese/Regione',
"email": "email", "email": "email",
"phoneNumber": "numero di telefono", "phoneNumber": "numero di telefono",
// Validazione accesso
"pleaseEnterContactInfo": "Inserisci il numero di telefono o l'email",
"invalidEmailFormat": "Formato email non valido",
"invalidPhoneFormat": "Formato numero di telefono non valido",
"tip": "Avviso",
"pleaseEnterCompleteCode": "Inserisci il codice di verifica completo",
"loginFailed": "Accesso fallito, per favore riprova",
'pleaseAcceptPrivacyPolicy':
'Si prega di leggere e accettare l\'Informativa sulla Privacy e le Condizioni d\'Uso',
// 首页 // 首页
"deviceStatus": "Stato del dispositivo", "deviceStatus": "Stato del dispositivo",
@ -150,7 +159,7 @@ const Map<String, String> itIT = {
'recordingDetails': 'Dettagli registrazione', 'recordingDetails': 'Dettagli registrazione',
'aiSummary': 'Riepilogo IA', 'aiSummary': 'Riepilogo IA',
'meetingTranscript': 'Trascrizione riunione', 'meetingTranscript': 'Trascrizione riunione',
// 设置页 // 设置页
"premiumMember": "Abbonamento Premium", "premiumMember": "Abbonamento Premium",
"joinDevice": "Dispositivo connesso:", "joinDevice": "Dispositivo connesso:",
@ -189,8 +198,8 @@ const Map<String, String> itIT = {
'pleaseRead': 'Per favore leggi', 'pleaseRead': 'Per favore leggi',
'completeRecording': 'Completa registrazione', 'completeRecording': 'Completa registrazione',
'deleteAccount': 'Elimina account', 'deleteAccount': 'Elimina account',
'deleteAccountTips': 'Sei sicuro di voler eliminare definitivamente il tuo account? Questa azione non può essere annullata.', 'deleteAccountTips':
'Sei sicuro di voler eliminare definitivamente il tuo account? Questa azione non può essere annullata.',
// 聊天页 // 聊天页
"clearChatHistory": "Cancella cronologia chat", "clearChatHistory": "Cancella cronologia chat",
@ -358,6 +367,18 @@ const Map<String, String> itIT = {
"Sei sicuro di voler cancellare tutta la cronologia delle traduzioni? Questa azione non può essere annullata.", "Sei sicuro di voler cancellare tutta la cronologia delle traduzioni? Questa azione non può essere annullata.",
"translationHistory": "Cronologia traduzioni", "translationHistory": "Cronologia traduzioni",
// Pagina di feedback dell'utente
"feedback": "Feedback",
'feedbackInfo': 'Fornire feedback o suggerimenti',
'feedbackDescription': 'Si prega di descrivere il problema o il suggerimento',
'feedbackHint': 'Inserisci il tuo feedback...',
'feedbackRequired': 'Il contenuto del feedback è obbligatorio',
'contactInfo': 'Informazioni di contatto',
'contactHint': 'Email/Telefono, in modo che possiamo contattarti',
'submit': 'Invia',
'feedbackSubmitSuccess': 'Feedback inviato con successo, grazie!',
'feedbackSubmitFailed': 'Impossibile inviare il feedback, riprova più tardi',
// Language names // Language names
"中文(简体)": "Cinese (semplificato)", "中文(简体)": "Cinese (semplificato)",
"中文(繁体)": "Cinese (tradizionale)", "中文(繁体)": "Cinese (tradizionale)",

50
lib/core/translations/language/ja_jp.dart

@ -26,7 +26,7 @@ const Map<String, String> jaJP = {
"explore": "探索", "explore": "探索",
"settings": "設定", "settings": "設定",
// 语言 // 语言
"followingSystem": "システムに従う", "followingSystem": "システムに従う",
"chinese": "中国語", "chinese": "中国語",
"english": "英語", "english": "英語",
@ -56,6 +56,14 @@ const Map<String, String> jaJP = {
'selectCountry': '国/地域を選択', 'selectCountry': '国/地域を選択',
"email": "メールアドレス", "email": "メールアドレス",
"phoneNumber": "電話番号", "phoneNumber": "電話番号",
// ログイン検証
"pleaseEnterContactInfo": "電話番号またはメールアドレスを入力してください",
"invalidEmailFormat": "メールアドレスの形式が正しくありません",
"invalidPhoneFormat": "電話番号の形式が正しくありません",
"tip": "お知らせ",
"pleaseEnterCompleteCode": "認証コードを完全に入力してください",
"loginFailed": "ログインに失敗しました。もう一度お試しください",
'pleaseAcceptPrivacyPolicy': 'プライバシーポリシーとユーザー契約を読んで同意してください',
// 首页 // 首页
"deviceStatus": "デバイスステータス", "deviceStatus": "デバイスステータス",
@ -182,8 +190,7 @@ const Map<String, String> jaJP = {
'completeRecording': '録音完了', 'completeRecording': '録音完了',
'deleteAccount': 'アカウントを削除', 'deleteAccount': 'アカウントを削除',
'deleteAccountTips': 'アカウントを完全に削除してもよろしいですか?この操作は取り消すことができません。', 'deleteAccountTips': 'アカウントを完全に削除してもよろしいですか?この操作は取り消すことができません。',
// 聊天页 // 聊天页
"clearChatHistory": "チャット履歴をクリア", "clearChatHistory": "チャット履歴をクリア",
"online": "オンライン", "online": "オンライン",
@ -202,7 +209,7 @@ const Map<String, String> jaJP = {
//国家列表 //国家列表
"邮箱": "メールアドレス", "邮箱": "メールアドレス",
"中国": "中国", "中国": "中国",
"美国": "アメリカ", "美国": "アメリカ",
"加拿大": "カナダ", "加拿大": "カナダ",
"俄罗斯": "ロシア", "俄罗斯": "ロシア",
@ -310,29 +317,36 @@ const Map<String, String> jaJP = {
// 个人助理 Emma // 个人助理 Emma
"personalAssistantName": "パーソナルアシスタント Emma", "personalAssistantName": "パーソナルアシスタント Emma",
"personalAssistantDescription": "こんにちは、私はEmmaです。万能型AIアシスタントとして、仕事の計画、学習のアドバイス、生活の手配、その他どんな問題でもお手伝いします。一緒に効率を高め、目標を達成しましょう!", "personalAssistantDescription":
"こんにちは、私はEmmaです。万能型AIアシスタントとして、仕事の計画、学習のアドバイス、生活の手配、その他どんな問題でもお手伝いします。一緒に効率を高め、目標を達成しましょう!",
"assistantTag": "アシスタント", "assistantTag": "アシスタント",
"personalAssistantWelcome": "こんにちは!私はEmmaです。あなたのパーソナルアシスタントとして、時間管理、目標計画、情報整理など様々なタスクをお手伝いします。何かお手伝いできることはありますか?", "personalAssistantWelcome":
"こんにちは!私はEmmaです。あなたのパーソナルアシスタントとして、時間管理、目標計画、情報整理など様々なタスクをお手伝いします。何かお手伝いできることはありますか?",
"personalAssistantVoiceWelcome": "ここにいますよ!", "personalAssistantVoiceWelcome": "ここにいますよ!",
// 同声传译 Lucy // 同声传译 Lucy
"interpreterName": "同時通訳", "interpreterName": "同時通訳",
"interpreterDescription": "Hello!私はLucyです。中国語と英語の専門同時通訳者として、会議、講演、会話シーンでリアルタイム翻訳を行い、コミュニケーションをスムーズにします。", "interpreterDescription":
"Hello!私はLucyです。中国語と英語の専門同時通訳者として、会議、講演、会話シーンでリアルタイム翻訳を行い、コミュニケーションをスムーズにします。",
"interpreterTag": "翻訳", "interpreterTag": "翻訳",
"interpreterWelcome": "皆さん、こんにちは!同時通訳者のLucyです。中国語と英語の間でリアルタイム翻訳を提供します。中国語を話すと英語に、英語を話すと中国語に翻訳します。それでは、翻訳を始めましょう!", "interpreterWelcome":
"皆さん、こんにちは!同時通訳者のLucyです。中国語と英語の間でリアルタイム翻訳を提供します。中国語を話すと英語に、英語を話すと中国語に翻訳します。それでは、翻訳を始めましょう!",
// 亲子陪伴 芯愿 // 亲子陪伴 芯愿
"companionName": "親子の絆 XinYuan", "companionName": "親子の絆 XinYuan",
"companionDescription": "私はあなた専属の親子コンパニオンXinYuanです。優しく思いやりがあり、理解力があります。おしゃべりしたい時、悩みを打ち明けたい時、誰かそばにいて欲しい時、いつでもあなたのそばにいます。", "companionDescription":
"私はあなた専属の親子コンパニオンXinYuanです。優しく思いやりがあり、理解力があります。おしゃべりしたい時、悩みを打ち明けたい時、誰かそばにいて欲しい時、いつでもあなたのそばにいます。",
"companionTag": "感情", "companionTag": "感情",
"companionWelcome": "こんにちは、やっとお会いできましたね!私はXinYuan、あなた専属の親子コンパニオンです。今日はどうでしたか?何か共有したいことはありますか?いつでもここにいて、あなたの声に耳を傾けています。", "companionWelcome":
"こんにちは、やっとお会いできましたね!私はXinYuan、あなた専属の親子コンパニオンです。今日はどうでしたか?何か共有したいことはありますか?いつでもここにいて、あなたの声に耳を傾けています。",
"companionVoiceWelcome": "あなたのそばにいますよ!", "companionVoiceWelcome": "あなたのそばにいますよ!",
// 多功能AI聊天 吴小江 // 多功能AI聊天 吴小江
"healthAdvisorName": "多機能AIチャット Wu", "healthAdvisorName": "多機能AIチャット Wu",
"healthAdvisorDescription": "私はWuです。より多くのAIチャット機能を提供します。", "healthAdvisorDescription": "私はWuです。より多くのAIチャット機能を提供します。",
"healthAdvisorTag": "健康", "healthAdvisorTag": "健康",
"healthAdvisorWelcome": "こんにちは、私はWuです。多機能AIチャットサービスを提供できることを嬉しく思います。専門知識であなたをサポートします。何かご質問はありますか?", "healthAdvisorWelcome":
"こんにちは、私はWuです。多機能AIチャットサービスを提供できることを嬉しく思います。専門知識であなたをサポートします。何かご質問はありますか?",
"healthAdvisorVoiceWelcome": "ここにいますよ!", "healthAdvisorVoiceWelcome": "ここにいますよ!",
// 翻译历史 // 翻译历史
@ -341,6 +355,18 @@ const Map<String, String> jaJP = {
"clearHistoryConfirm": "すべての翻訳履歴を消去しますか?この操作は元に戻せません。", "clearHistoryConfirm": "すべての翻訳履歴を消去しますか?この操作は元に戻せません。",
"translationHistory": "翻訳履歴", "translationHistory": "翻訳履歴",
// ユーザーフィードバックページ
"feedback": "フィードバック",
'feedbackInfo': 'フィードバックや提案を提供する',
'feedbackDescription': '問題や提案を説明してください',
'feedbackHint': 'フィードバックを入力してください...',
'feedbackRequired': 'フィードバック内容は必須です',
'contactInfo': '連絡先情報',
'contactHint': 'メール/電話、ご連絡できるように',
'submit': '送信',
'feedbackSubmitSuccess': 'フィードバックが正常に送信されました、ありがとうございます!',
'feedbackSubmitFailed': 'フィードバックの送信に失敗しました、後でもう一度お試しください',
// 语言名称 // 语言名称
"中文(简体)": "中国語(簡体字)", "中文(简体)": "中国語(簡体字)",
"中文(繁体)": "中国語(繁体字)", "中文(繁体)": "中国語(繁体字)",
@ -381,4 +407,4 @@ const Map<String, String> jaJP = {
"乌尔都语": "ウルドゥー語", "乌尔都语": "ウルドゥー語",
"波斯语": "ペルシア語", "波斯语": "ペルシア語",
"斯瓦希里语": "スワヒリ語", "斯瓦希里语": "スワヒリ語",
}; };

48
lib/core/translations/language/ko_kr.dart

@ -26,7 +26,7 @@ const Map<String, String> koKR = {
"explore": "탐색", "explore": "탐색",
"settings": "설정", "settings": "설정",
// 语言 // 语言
"followingSystem": "시스템 설정 따르기", "followingSystem": "시스템 설정 따르기",
"chinese": "중국어", "chinese": "중국어",
"english": "영어", "english": "영어",
@ -56,6 +56,14 @@ const Map<String, String> koKR = {
'selectCountry': '국가/지역 선택', 'selectCountry': '국가/지역 선택',
"email": "이메일", "email": "이메일",
"phoneNumber": "전화번호", "phoneNumber": "전화번호",
// 로그인 검증
"pleaseEnterContactInfo": "전화번호 또는 이메일을 입력해주세요",
"invalidEmailFormat": "잘못된 이메일 형식입니다",
"invalidPhoneFormat": "잘못된 전화번호 형식입니다",
"tip": "알림",
"pleaseEnterCompleteCode": "인증번호를 완전히 입력해주세요",
"loginFailed": "로그인 실패, 다시 시도해주세요",
'pleaseAcceptPrivacyPolicy': '개인정보 보호정책 및 사용자 계약을 읽고 동의해주세요',
// 首页 // 首页
"deviceStatus": "기기 상태", "deviceStatus": "기기 상태",
@ -183,7 +191,6 @@ const Map<String, String> koKR = {
'deleteAccount': '계정 삭제', 'deleteAccount': '계정 삭제',
'deleteAccountTips': '계정을 영구적으로 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.', 'deleteAccountTips': '계정을 영구적으로 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.',
// 聊天页 // 聊天页
"clearChatHistory": "채팅 기록 지우기", "clearChatHistory": "채팅 기록 지우기",
"online": "온라인", "online": "온라인",
@ -202,7 +209,7 @@ const Map<String, String> koKR = {
//国家列表 //国家列表
"邮箱": "이메일", "邮箱": "이메일",
"中国": "중국", "中国": "중국",
"美国": "미국", "美国": "미국",
"加拿大": "캐나다", "加拿大": "캐나다",
"俄罗斯": "러시아", "俄罗斯": "러시아",
@ -310,29 +317,36 @@ const Map<String, String> koKR = {
// 个人助理 Emma // 个人助理 Emma
"personalAssistantName": "개인 어시스턴트 Emma", "personalAssistantName": "개인 어시스턴트 Emma",
"personalAssistantDescription": "안녕하세요, 저는 Emma입니다. 다재다능한 AI 어시스턴트로서 업무 계획, 학습 조언, 생활 관리 등 어떤 문제든 도와드립니다. 함께 효율성을 높이고 목표를 달성해 보아요!", "personalAssistantDescription":
"안녕하세요, 저는 Emma입니다. 다재다능한 AI 어시스턴트로서 업무 계획, 학습 조언, 생활 관리 등 어떤 문제든 도와드립니다. 함께 효율성을 높이고 목표를 달성해 보아요!",
"assistantTag": "어시스턴트", "assistantTag": "어시스턴트",
"personalAssistantWelcome": "안녕하세요! 저는 Emma입니다. 개인 어시스턴트로서 시간 관리, 목표 계획, 정보 정리 등 다양한 작업을 도와드립니다. 어떻게 도와드릴까요?", "personalAssistantWelcome":
"안녕하세요! 저는 Emma입니다. 개인 어시스턴트로서 시간 관리, 목표 계획, 정보 정리 등 다양한 작업을 도와드립니다. 어떻게 도와드릴까요?",
"personalAssistantVoiceWelcome": "여기 있어요!", "personalAssistantVoiceWelcome": "여기 있어요!",
// 同声传译 Lucy // 同声传译 Lucy
"interpreterName": "동시 통역", "interpreterName": "동시 통역",
"interpreterDescription": "안녕하세요! 저는 Lucy입니다. 중국어와 영어의 전문 동시 통역사로서 회의, 강연, 대화 장면에서 실시간 번역을 제공하여 원활한 소통을 돕습니다.", "interpreterDescription":
"안녕하세요! 저는 Lucy입니다. 중국어와 영어의 전문 동시 통역사로서 회의, 강연, 대화 장면에서 실시간 번역을 제공하여 원활한 소통을 돕습니다.",
"interpreterTag": "번역", "interpreterTag": "번역",
"interpreterWelcome": "여러분, 안녕하세요! 동시 통역사 Lucy입니다. 중국어와 영어 사이의 실시간 번역을 제공합니다. 중국어로 말하면 영어로, 영어로 말하면 중국어로 번역해 드립니다. 그럼 번역을 시작해 볼까요!", "interpreterWelcome":
"여러분, 안녕하세요! 동시 통역사 Lucy입니다. 중국어와 영어 사이의 실시간 번역을 제공합니다. 중국어로 말하면 영어로, 영어로 말하면 중국어로 번역해 드립니다. 그럼 번역을 시작해 볼까요!",
// 亲子陪伴 芯愿 // 亲子陪伴 芯愿
"companionName": "부모-자녀 유대감 XinYuan", "companionName": "부모-자녀 유대감 XinYuan",
"companionDescription": "저는 당신 전용 부모-자녀 동반자 XinYuan입니다. 친절하고 배려심이 많으며 이해심이 깊습니다. 대화하고 싶을 때, 고민을 나누고 싶을 때, 누군가 곁에 있어주길 원할 때 언제든지 당신 곁에 있습니다.", "companionDescription":
"저는 당신 전용 부모-자녀 동반자 XinYuan입니다. 친절하고 배려심이 많으며 이해심이 깊습니다. 대화하고 싶을 때, 고민을 나누고 싶을 때, 누군가 곁에 있어주길 원할 때 언제든지 당신 곁에 있습니다.",
"companionTag": "감정", "companionTag": "감정",
"companionWelcome": "안녕하세요, 드디어 만났네요! 저는 XinYuan, 당신 전용 부모-자녀 동반자입니다. 오늘 하루는 어땠나요? 나누고 싶은 이야기가 있으신가요? 언제든지 여기 있으니 당신의 이야기를 들려주세요.", "companionWelcome":
"안녕하세요, 드디어 만났네요! 저는 XinYuan, 당신 전용 부모-자녀 동반자입니다. 오늘 하루는 어땠나요? 나누고 싶은 이야기가 있으신가요? 언제든지 여기 있으니 당신의 이야기를 들려주세요.",
"companionVoiceWelcome": "당신 곁에 있어요!", "companionVoiceWelcome": "당신 곁에 있어요!",
// 多功能AI聊天 吴小江 // 多功能AI聊天 吴小江
"healthAdvisorName": "다기능 AI 채팅 Wu", "healthAdvisorName": "다기능 AI 채팅 Wu",
"healthAdvisorDescription": "저는 Wu입니다. 더 많은 AI 채팅 기능을 제공합니다.", "healthAdvisorDescription": "저는 Wu입니다. 더 많은 AI 채팅 기능을 제공합니다.",
"healthAdvisorTag": "건강", "healthAdvisorTag": "건강",
"healthAdvisorWelcome": "안녕하세요, 저는 Wu입니다. 다기능 AI 채팅 서비스를 제공하게 되어 기쁩니다. 전문 지식으로 당신을 지원합니다. 질문이 있으신가요?", "healthAdvisorWelcome":
"안녕하세요, 저는 Wu입니다. 다기능 AI 채팅 서비스를 제공하게 되어 기쁩니다. 전문 지식으로 당신을 지원합니다. 질문이 있으신가요?",
"healthAdvisorVoiceWelcome": "여기 있어요!", "healthAdvisorVoiceWelcome": "여기 있어요!",
// 翻译历史 // 翻译历史
@ -341,6 +355,18 @@ const Map<String, String> koKR = {
"clearHistoryConfirm": "모든 번역 기록을 지우시겠습니까? 이 작업은 되돌릴 수 없습니다.", "clearHistoryConfirm": "모든 번역 기록을 지우시겠습니까? 이 작업은 되돌릴 수 없습니다.",
"translationHistory": "번역 기록", "translationHistory": "번역 기록",
// 사용자 피드백 페이지
"feedback": "피드백",
'feedbackInfo': '피드백이나 제안 제공',
'feedbackDescription': '문제나 제안을 설명해 주세요',
'feedbackHint': '피드백을 입력해 주세요...',
'feedbackRequired': '피드백 내용이 필요합니다',
'contactInfo': '연락처 정보',
'contactHint': '이메일/전화번호, 연락드릴 수 있도록',
'submit': '제출',
'feedbackSubmitSuccess': '피드백이 성공적으로 제출되었습니다. 감사합니다!',
'feedbackSubmitFailed': '피드백 제출에 실패했습니다. 나중에 다시 시도해 주세요',
// 语言名称 // 语言名称
"中文(简体)": "중국어(간체)", "中文(简体)": "중국어(간체)",
"中文(繁体)": "중국어(번체)", "中文(繁体)": "중국어(번체)",
@ -381,4 +407,4 @@ const Map<String, String> koKR = {
"乌尔都语": "우르두어", "乌尔都语": "우르두어",
"波斯语": "페르시아어", "波斯语": "페르시아어",
"斯瓦希里语": "스와힐리어", "斯瓦希里语": "스와힐리어",
}; };

90
lib/core/translations/language/pt_pt.dart

@ -19,14 +19,15 @@ const Map<String, String> ptPT = {
// 启动页 // 启动页
"slogan": "Conecte o Mundo, Além da Voz", "slogan": "Conecte o Mundo, Além da Voz",
"sloganInfo": "Especialista em Voz IA · A Qualquer Momento, Em Qualquer Lugar", "sloganInfo":
"Especialista em Voz IA · A Qualquer Momento, Em Qualquer Lugar",
// 底部导航 // 底部导航
"home": "Início", "home": "Início",
"explore": "Explorar", "explore": "Explorar",
"settings": "Configurações", "settings": "Configurações",
// 语言 // 语言
"followingSystem": "Seguir sistema", "followingSystem": "Seguir sistema",
"chinese": "Chinês", "chinese": "Chinês",
"english": "Inglês", "english": "Inglês",
@ -43,7 +44,8 @@ const Map<String, String> ptPT = {
// 登录页 // 登录页
"welcome": "Bem-vindo", "welcome": "Bem-vindo",
"loginTips": "Faça login ou crie uma conta usando seu número de telefone ou e-mail", "loginTips":
"Faça login ou crie uma conta usando seu número de telefone ou e-mail",
"accountNumber": "Número de telefone ou e-mail", "accountNumber": "Número de telefone ou e-mail",
"verificationCode": "Código de Verificação", "verificationCode": "Código de Verificação",
"getCode": "Obter Código", "getCode": "Obter Código",
@ -56,6 +58,17 @@ const Map<String, String> ptPT = {
'selectCountry': 'Selecionar País/Região', 'selectCountry': 'Selecionar País/Região',
"email": "E-mail", "email": "E-mail",
"phoneNumber": "Número de telefone", "phoneNumber": "Número de telefone",
// Validação de login
"pleaseEnterContactInfo":
"Por favor, insira seu número de telefone ou e-mail",
"invalidEmailFormat": "Formato de e-mail inválido",
"invalidPhoneFormat": "Formato de número de telefone inválido",
"tip": "Aviso",
"pleaseEnterCompleteCode":
"Por favor, insira o código de verificação completo",
"loginFailed": "Falha no login, tente novamente",
'pleaseAcceptPrivacyPolicy':
'Por favor, leia e aceite a Política de Privacidade e o Acordo do Usuário primeiro',
// 首页 // 首页
"deviceStatus": "Status do Dispositivo", "deviceStatus": "Status do Dispositivo",
@ -71,16 +84,19 @@ const Map<String, String> ptPT = {
"aiAssistant": "Assistente de IA", "aiAssistant": "Assistente de IA",
"aiAssistantInfo": "Consultor pessoal 24/7 para perguntas e conselhos", "aiAssistantInfo": "Consultor pessoal 24/7 para perguntas e conselhos",
"simultaneousInterpretation": "Interpretação em Tempo Real", "simultaneousInterpretation": "Interpretação em Tempo Real",
"simultaneousInterpretationInfo": "Tradução em tempo real para mais de 80 idiomas", "simultaneousInterpretationInfo":
"Tradução em tempo real para mais de 80 idiomas",
"aiCompanion": "Vínculo pais e filhos", "aiCompanion": "Vínculo pais e filhos",
"aiCompanionInfo": "Entende você e oferece companhia calorosa", "aiCompanionInfo": "Entende você e oferece companhia calorosa",
"homeSlogan": "Conecte o Mundo, Além do Som", "homeSlogan": "Conecte o Mundo, Além do Som",
"turnEarphones": "Por favor, abra o estojo dos fones e coloque-os em seus ouvidos", "turnEarphones":
"Por favor, abra o estojo dos fones e coloque-os em seus ouvidos",
"currentlyConnected": "Atualmente conectado:", "currentlyConnected": "Atualmente conectado:",
"unsystemSetup": "Ir para Configurações do Sistema", "unsystemSetup": "Ir para Configurações do Sistema",
"talkLater": "Lembrar Mais Tarde", "talkLater": "Lembrar Mais Tarde",
"initializing": "Inicializando...", "initializing": "Inicializando...",
"bluetoothInitializationFailure": "Falha na inicialização do serviço Bluetooth", "bluetoothInitializationFailure":
"Falha na inicialização do serviço Bluetooth",
"checkingPermissions": "Verificando permissões...", "checkingPermissions": "Verificando permissões...",
"lackPermissions": "Permissões Bluetooth necessárias", "lackPermissions": "Permissões Bluetooth necessárias",
"searchingDevice": "Procurando dispositivos...", "searchingDevice": "Procurando dispositivos...",
@ -116,7 +132,8 @@ const Map<String, String> ptPT = {
"healthConsultant": "Chat de IA Multifuncional", "healthConsultant": "Chat de IA Multifuncional",
"healthConsultantInfo": "Mais funções de chat com IA", "healthConsultantInfo": "Mais funções de chat com IA",
"conferenceAssistant": "Assistente de Reuniões de Negócios", "conferenceAssistant": "Assistente de Reuniões de Negócios",
"conferenceAssistantInfo": "Registro de atas em tempo real e tradução multilíngue", "conferenceAssistantInfo":
"Registro de atas em tempo real e tradução multilíngue",
"studyAssistant": "Assistente de Aprendizado de Idiomas", "studyAssistant": "Assistente de Aprendizado de Idiomas",
"studyAssistantInfo": "Correção de pronúncia e prática de fala", "studyAssistantInfo": "Correção de pronúncia e prática de fala",
"travelTranslator": "Tradutor de Viagem", "travelTranslator": "Tradutor de Viagem",
@ -128,7 +145,8 @@ const Map<String, String> ptPT = {
"newFunction": "Novas Funções", "newFunction": "Novas Funções",
"youChoseTry": "Você escolheu experimentar", "youChoseTry": "Você escolheu experimentar",
"aiSinger": "Modo Cantor IA", "aiSinger": "Modo Cantor IA",
"aiSingerInfo": "Cante qualquer música com a voz do seu artista favorito, até mesmo suas próprias criações", "aiSingerInfo":
"Cante qualquer música com a voz do seu artista favorito, até mesmo suas próprias criações",
"popularFunction": "Funções Populares", "popularFunction": "Funções Populares",
"tryNow": "Experimentar Agora", "tryNow": "Experimentar Agora",
@ -168,9 +186,11 @@ const Map<String, String> ptPT = {
"voiceServiceInfo": "Selecionar serviço de reconhecimento e síntese de voz", "voiceServiceInfo": "Selecionar serviço de reconhecimento e síntese de voz",
"selectVoiceService": "Selecionar Serviço de Voz", "selectVoiceService": "Selecionar Serviço de Voz",
"azureVoiceService": "Serviço de Voz Azure", "azureVoiceService": "Serviço de Voz Azure",
"azureVoiceServiceInfo": "Serviço em nuvem da Microsoft com suporte a síntese e reconhecimento de voz de alta qualidade", "azureVoiceServiceInfo":
"Serviço em nuvem da Microsoft com suporte a síntese e reconhecimento de voz de alta qualidade",
"flutterVoiceService": "Serviço de voz local", "flutterVoiceService": "Serviço de voz local",
"flutterVoiceServiceInfo": "Serviço de voz local do dispositivo sem necessidade de internet", "flutterVoiceServiceInfo":
"Serviço de voz local do dispositivo sem necessidade de internet",
'timbreService': 'Serviço de Timbre', 'timbreService': 'Serviço de Timbre',
'timbreServiceInfo': 'Selecionar serviço de síntese de voz', 'timbreServiceInfo': 'Selecionar serviço de síntese de voz',
'defaultTimbre': 'Timbre Padrão', 'defaultTimbre': 'Timbre Padrão',
@ -181,9 +201,9 @@ const Map<String, String> ptPT = {
'pleaseRead': 'Por favor leia', 'pleaseRead': 'Por favor leia',
'completeRecording': 'Concluir gravação', 'completeRecording': 'Concluir gravação',
'deleteAccount': 'Excluir conta', 'deleteAccount': 'Excluir conta',
'deleteAccountTips': 'Tem certeza de que deseja excluir permanentemente sua conta? Esta ação não pode ser desfeita.', 'deleteAccountTips':
'Tem certeza de que deseja excluir permanentemente sua conta? Esta ação não pode ser desfeita.',
// 聊天页 // 聊天页
"clearChatHistory": "Limpar Histórico de Chat", "clearChatHistory": "Limpar Histórico de Chat",
"online": "Online", "online": "Online",
@ -310,37 +330,59 @@ const Map<String, String> ptPT = {
// Personal Assistant Emma // Personal Assistant Emma
"personalAssistantName": "Emma", "personalAssistantName": "Emma",
"personalAssistantDescription": "Olá, sou Emma, uma assistente de IA versátil. Seja para planejamento de trabalho, conselhos de estudo, organização da vida ou outros assuntos, estou feliz em ajudar. Vamos melhorar a eficiência e alcançar objetivos juntos!", "personalAssistantDescription":
"Olá, sou Emma, uma assistente de IA versátil. Seja para planejamento de trabalho, conselhos de estudo, organização da vida ou outros assuntos, estou feliz em ajudar. Vamos melhorar a eficiência e alcançar objetivos juntos!",
"assistantTag": "Assistente", "assistantTag": "Assistente",
"personalAssistantWelcome": "Olá! Sou Emma e estou feliz em ser sua assistente pessoal. Posso ajudar com várias tarefas como gerenciamento de tempo, planejamento de metas, organização de informações, etc. Como posso ajudar?", "personalAssistantWelcome":
"Olá! Sou Emma e estou feliz em ser sua assistente pessoal. Posso ajudar com várias tarefas como gerenciamento de tempo, planejamento de metas, organização de informações, etc. Como posso ajudar?",
"personalAssistantVoiceWelcome": "Estou aqui!", "personalAssistantVoiceWelcome": "Estou aqui!",
// 同声传译 Lucy // 同声传译 Lucy
"interpreterName": "Intérprete Simultâneo", "interpreterName": "Intérprete Simultâneo",
"interpreterDescription": "Olá! Sou Lucy, uma intérprete profissional especializada em tradução em tempo real para reuniões, conferências e conversas, tornando a comunicação sem barreiras.", "interpreterDescription":
"Olá! Sou Lucy, uma intérprete profissional especializada em tradução em tempo real para reuniões, conferências e conversas, tornando a comunicação sem barreiras.",
"interpreterTag": "Tradução", "interpreterTag": "Tradução",
"interpreterWelcome": "Olá a todos! Sou Lucy, sua intérprete simultânea. Farei tradução entre chinês e inglês para você. Quando você falar em chinês, traduzirei para inglês; quando falar em inglês, traduzirei para chinês. Vamos começar nossa sessão de tradução!", "interpreterWelcome":
"Olá a todos! Sou Lucy, sua intérprete simultânea. Farei tradução entre chinês e inglês para você. Quando você falar em chinês, traduzirei para inglês; quando falar em inglês, traduzirei para chinês. Vamos começar nossa sessão de tradução!",
// 亲子陪伴 芯愿 // 亲子陪伴 芯愿
"companionName": "XinYuan", "companionName": "XinYuan",
"companionDescription": "Sou XinYuan, sua companheira dedicada para pais e filhos, gentil e compreensiva. Seja para conversar, compartilhar preocupações ou precisar de companhia, estarei sempre ao seu lado.", "companionDescription":
"Sou XinYuan, sua companheira dedicada para pais e filhos, gentil e compreensiva. Seja para conversar, compartilhar preocupações ou precisar de companhia, estarei sempre ao seu lado.",
"companionTag": "Emocional", "companionTag": "Emocional",
"companionWelcome": "Olá! Finalmente nos encontramos! Sou XinYuan, sua companheira dedicada para pais e filhos. Como foi seu dia? Há algo que gostaria de compartilhar comigo? Estou sempre aqui, pronta para ouvir seus pensamentos.", "companionWelcome":
"Olá! Finalmente nos encontramos! Sou XinYuan, sua companheira dedicada para pais e filhos. Como foi seu dia? Há algo que gostaria de compartilhar comigo? Estou sempre aqui, pronta para ouvir seus pensamentos.",
"companionVoiceWelcome": "Estou aqui com você!", "companionVoiceWelcome": "Estou aqui com você!",
// 多功能AI聊天 吴小江 // 多功能AI聊天 吴小江
"healthAdvisorName": "Assistente de Chat IA Wu", "healthAdvisorName": "Assistente de Chat IA Wu",
"healthAdvisorDescription": "Sou Wu, pronto para fornecer várias funções de chat com IA.", "healthAdvisorDescription":
"Sou Wu, pronto para fornecer várias funções de chat com IA.",
"healthAdvisorTag": "Chat", "healthAdvisorTag": "Chat",
"healthAdvisorWelcome": "Olá, sou Wu. Estou feliz em ajudar com funções de chat com IA. Como posso ajudar hoje?", "healthAdvisorWelcome":
"Olá, sou Wu. Estou feliz em ajudar com funções de chat com IA. Como posso ajudar hoje?",
"healthAdvisorVoiceWelcome": "Estou aqui!", "healthAdvisorVoiceWelcome": "Estou aqui!",
// Translation History // Translation History
"noTranslationHistory": "Sem histórico de tradução", "noTranslationHistory": "Sem histórico de tradução",
"clearHistory": "Limpar Histórico", "clearHistory": "Limpar Histórico",
"clearHistoryConfirm": "Tem certeza que deseja limpar todo o histórico de tradução? Esta ação não pode ser desfeita.", "clearHistoryConfirm":
"Tem certeza que deseja limpar todo o histórico de tradução? Esta ação não pode ser desfeita.",
"translationHistory": "Histórico de Tradução", "translationHistory": "Histórico de Tradução",
// Página de feedback do usuário
"feedback": "Feedback",
'feedbackInfo': 'Fornecer feedback ou sugestões',
'feedbackDescription': 'Por favor, descreva seu problema ou sugestão',
'feedbackHint': 'Por favor, insira seu feedback...',
'feedbackRequired': 'O conteúdo do feedback é obrigatório',
'contactInfo': 'Informações de contato',
'contactHint': 'Email/Telefone, para que possamos contatá-lo',
'submit': 'Enviar',
'feedbackSubmitSuccess': 'Feedback enviado com sucesso, obrigado!',
'feedbackSubmitFailed':
'Falha ao enviar feedback, tente novamente mais tarde',
// Language names // Language names
"中文(简体)": "Chinês (Simplificado)", "中文(简体)": "Chinês (Simplificado)",
"中文(繁体)": "Chinês (Tradicional)", "中文(繁体)": "Chinês (Tradicional)",
@ -381,4 +423,4 @@ const Map<String, String> ptPT = {
"乌尔都语": "Urdu", "乌尔都语": "Urdu",
"波斯语": "Persa", "波斯语": "Persa",
"斯瓦希里语": "Suaíli", "斯瓦希里语": "Suaíli",
}; };

86
lib/core/translations/language/ru_ru.dart

@ -26,7 +26,7 @@ const Map<String, String> ruRU = {
"explore": "Исследовать", "explore": "Исследовать",
"settings": "Настройки", "settings": "Настройки",
// 语言 // 语言
"followingSystem": "Следовать системе", "followingSystem": "Следовать системе",
"chinese": "Китайский", "chinese": "Китайский",
"english": "Английский", "english": "Английский",
@ -43,7 +43,8 @@ const Map<String, String> ruRU = {
// 登录页 // 登录页
"welcome": "Добро пожаловать", "welcome": "Добро пожаловать",
"loginTips": "Пожалуйста, войдите или зарегистрируйтесь с помощью номера телефона или электронной почты", "loginTips":
"Пожалуйста, войдите или зарегистрируйтесь с помощью номера телефона или электронной почты",
"accountNumber": "Номер телефона или электронная почта", "accountNumber": "Номер телефона или электронная почта",
"verificationCode": "Код подтверждения", "verificationCode": "Код подтверждения",
"getCode": "Получить код", "getCode": "Получить код",
@ -56,6 +57,15 @@ const Map<String, String> ruRU = {
'selectCountry': 'Выберите страну/регион', 'selectCountry': 'Выберите страну/регион',
"email": "электронная почта", "email": "электронная почта",
"phoneNumber": "номер телефона", "phoneNumber": "номер телефона",
// Проверка входа
"pleaseEnterContactInfo": "Пожалуйста, введите номер телефона или email",
"invalidEmailFormat": "Неверный формат email",
"invalidPhoneFormat": "Неверный формат номера телефона",
"tip": "Уведомление",
"pleaseEnterCompleteCode": "Пожалуйста, введите полный код подтверждения",
"loginFailed": "Ошибка входа, пожалуйста, попробуйте снова",
'pleaseAcceptPrivacyPolicy':
'Пожалуйста, прочтите и примите Политику конфиденциальности и Пользовательское соглашение',
// 首页 // 首页
"deviceStatus": "Статус устройства", "deviceStatus": "Статус устройства",
@ -71,7 +81,8 @@ const Map<String, String> ruRU = {
"aiAssistant": "ИИ-ассистент", "aiAssistant": "ИИ-ассистент",
"aiAssistantInfo": "Персональный консультант 24/7 для вопросов и советов", "aiAssistantInfo": "Персональный консультант 24/7 для вопросов и советов",
"simultaneousInterpretation": "Синхронный перевод", "simultaneousInterpretation": "Синхронный перевод",
"simultaneousInterpretationInfo": "Перевод в реальном времени для более чем 80 языков", "simultaneousInterpretationInfo":
"Перевод в реальном времени для более чем 80 языков",
"aiCompanion": "Родительско-детская связь", "aiCompanion": "Родительско-детская связь",
"aiCompanionInfo": "Понимает вас и предлагает теплое общение", "aiCompanionInfo": "Понимает вас и предлагает теплое общение",
"homeSlogan": "Соединяя мир, за пределами звука", "homeSlogan": "Соединяя мир, за пределами звука",
@ -80,7 +91,8 @@ const Map<String, String> ruRU = {
"unsystemSetup": "К системным настройкам", "unsystemSetup": "К системным настройкам",
"talkLater": "Напомнить позже", "talkLater": "Напомнить позже",
"initializing": "Инициализация...", "initializing": "Инициализация...",
"bluetoothInitializationFailure": "Не удалось инициализировать службу Bluetooth", "bluetoothInitializationFailure":
"Не удалось инициализировать службу Bluetooth",
"checkingPermissions": "Проверка разрешений...", "checkingPermissions": "Проверка разрешений...",
"lackPermissions": "Требуются разрешения Bluetooth", "lackPermissions": "Требуются разрешения Bluetooth",
"searchingDevice": "Поиск устройств...", "searchingDevice": "Поиск устройств...",
@ -116,11 +128,14 @@ const Map<String, String> ruRU = {
"healthConsultant": "Многофункциональный ИИ-чат", "healthConsultant": "Многофункциональный ИИ-чат",
"healthConsultantInfo": "Больше функций ИИ-чата", "healthConsultantInfo": "Больше функций ИИ-чата",
"conferenceAssistant": "Ассистент деловых встреч", "conferenceAssistant": "Ассистент деловых встреч",
"conferenceAssistantInfo": "Запись протоколов в реальном времени и многоязычный перевод", "conferenceAssistantInfo":
"Запись протоколов в реальном времени и многоязычный перевод",
"studyAssistant": "Ассистент изучения языков", "studyAssistant": "Ассистент изучения языков",
"studyAssistantInfo": "Коррекция произношения и практика речи в реальном времени", "studyAssistantInfo":
"Коррекция произношения и практика речи в реальном времени",
"travelTranslator": "Переводчик для путешествий", "travelTranslator": "Переводчик для путешествий",
"travelTranslatorInfo": "Голосовой перевод в реальном времени и гиды по достопримечательностям", "travelTranslatorInfo":
"Голосовой перевод в реальном времени и гиды по достопримечательностям",
"beSearching": "Поиск:", "beSearching": "Поиск:",
"noAssistantFound": "Не найдено подходящих ИИ-ассистентов", "noAssistantFound": "Не найдено подходящих ИИ-ассистентов",
"scene": "Сценарии", "scene": "Сценарии",
@ -128,7 +143,8 @@ const Map<String, String> ruRU = {
"newFunction": "Новые функции", "newFunction": "Новые функции",
"youChoseTry": "Вы решили попробовать", "youChoseTry": "Вы решили попробовать",
"aiSinger": "Режим ИИ-певца", "aiSinger": "Режим ИИ-певца",
"aiSingerInfo": "Пойте любую песню голосом вашего любимого исполнителя, даже собственные творения", "aiSingerInfo":
"Пойте любую песню голосом вашего любимого исполнителя, даже собственные творения",
"popularFunction": "Популярные функции", "popularFunction": "Популярные функции",
"tryNow": "Попробовать сейчас", "tryNow": "Попробовать сейчас",
@ -168,9 +184,11 @@ const Map<String, String> ruRU = {
"voiceServiceInfo": "Выбрать службу распознавания и синтеза речи", "voiceServiceInfo": "Выбрать службу распознавания и синтеза речи",
"selectVoiceService": "Выбрать голосовую службу", "selectVoiceService": "Выбрать голосовую службу",
"azureVoiceService": "Голосовая служба Azure", "azureVoiceService": "Голосовая служба Azure",
"azureVoiceServiceInfo": "Облачная голосовая служба Microsoft с поддержкой высококачественного синтеза и распознавания речи", "azureVoiceServiceInfo":
"Облачная голосовая служба Microsoft с поддержкой высококачественного синтеза и распознавания речи",
"flutterVoiceService": "Локальная голосовая служба Flutter", "flutterVoiceService": "Локальная голосовая служба Flutter",
"flutterVoiceServiceInfo": "Локальная голосовая служба устройства без необходимости подключения к интернету", "flutterVoiceServiceInfo":
"Локальная голосовая служба устройства без необходимости подключения к интернету",
'timbreService': 'Служба тембра', 'timbreService': 'Служба тембра',
'timbreServiceInfo': 'Выбрать службу синтеза речи с тембром', 'timbreServiceInfo': 'Выбрать службу синтеза речи с тембром',
'defaultTimbre': 'Тембр по умолчанию', 'defaultTimbre': 'Тембр по умолчанию',
@ -181,9 +199,9 @@ const Map<String, String> ruRU = {
'pleaseRead': 'Пожалуйста, прочтите', 'pleaseRead': 'Пожалуйста, прочтите',
'completeRecording': 'Завершить запись', 'completeRecording': 'Завершить запись',
'deleteAccount': 'Удалить аккаунт', 'deleteAccount': 'Удалить аккаунт',
'deleteAccountTips': 'Вы уверены, что хотите навсегда удалить свой аккаунт? Это действие нельзя отменить.', 'deleteAccountTips':
'Вы уверены, что хотите навсегда удалить свой аккаунт? Это действие нельзя отменить.',
// 聊天页 // 聊天页
"clearChatHistory": "Очистить историю чата", "clearChatHistory": "Очистить историю чата",
"online": "В сети", "online": "В сети",
@ -310,37 +328,59 @@ const Map<String, String> ruRU = {
// Personal Assistant Emma // Personal Assistant Emma
"personalAssistantName": "Эмма", "personalAssistantName": "Эмма",
"personalAssistantDescription": "Здравствуйте, я Эмма, универсальный ИИ-ассистент. Будь то планирование работы, учебные советы, организация жизни или другие проблемы, я рада помочь. Давайте повысим эффективность и достигнем целей вместе!", "personalAssistantDescription":
"Здравствуйте, я Эмма, универсальный ИИ-ассистент. Будь то планирование работы, учебные советы, организация жизни или другие проблемы, я рада помочь. Давайте повысим эффективность и достигнем целей вместе!",
"assistantTag": "Ассистент", "assistantTag": "Ассистент",
"personalAssistantWelcome": "Здравствуйте! Я Эмма, и я рада быть вашим личным ассистентом. Я могу помочь вам с различными задачами, такими как управление временем, планирование целей, организация информации и т.д. Чем я могу вам помочь?", "personalAssistantWelcome":
"Здравствуйте! Я Эмма, и я рада быть вашим личным ассистентом. Я могу помочь вам с различными задачами, такими как управление временем, планирование целей, организация информации и т.д. Чем я могу вам помочь?",
"personalAssistantVoiceWelcome": "Я здесь!", "personalAssistantVoiceWelcome": "Я здесь!",
// 同声传译 Lucy // 同声传译 Lucy
"interpreterName": "Синхронный переводчик", "interpreterName": "Синхронный переводчик",
"interpreterDescription": "Здравствуйте! Я Люси, профессиональный переводчик английского и китайского языков, специализирующийся на переводе в реальном времени для встреч, конференций и разговоров, делая общение без барьеров.", "interpreterDescription":
"Здравствуйте! Я Люси, профессиональный переводчик английского и китайского языков, специализирующийся на переводе в реальном времени для встреч, конференций и разговоров, делая общение без барьеров.",
"interpreterTag": "Перевод", "interpreterTag": "Перевод",
"interpreterWelcome": "Здравствуйте, все! Я Люси, ваш синхронный переводчик. Я буду переводить между китайским и английским языками для вас. Когда вы говорите на китайском, я буду переводить на английский; когда вы говорите на английском, я буду переводить на китайский. Давайте начнем нашу сессию перевода!", "interpreterWelcome":
"Здравствуйте, все! Я Люси, ваш синхронный переводчик. Я буду переводить между китайским и английским языками для вас. Когда вы говорите на китайском, я буду переводить на английский; когда вы говорите на английском, я буду переводить на китайский. Давайте начнем нашу сессию перевода!",
// 亲子陪伴 芯愿 // 亲子陪伴 芯愿
"companionName": "СиньЮань", "companionName": "СиньЮань",
"companionDescription": "Я СиньЮань, ваш преданный родительско-детский компаньон, нежный и понимающий. Хотите ли вы поболтать, поделиться заботами или нуждаетесь в компании, я всегда рядом с вами.", "companionDescription":
"Я СиньЮань, ваш преданный родительско-детский компаньон, нежный и понимающий. Хотите ли вы поболтать, поделиться заботами или нуждаетесь в компании, я всегда рядом с вами.",
"companionTag": "Эмоциональный", "companionTag": "Эмоциональный",
"companionWelcome": "Здравствуйте! Наконец-то мы встретились! Я СиньЮань, ваш преданный родительско-детский компаньон. Как прошел ваш день? Есть ли что-то, чем вы хотели бы поделиться со мной? Я всегда здесь, готова выслушать ваши мысли.", "companionWelcome":
"Здравствуйте! Наконец-то мы встретились! Я СиньЮань, ваш преданный родительско-детский компаньон. Как прошел ваш день? Есть ли что-то, чем вы хотели бы поделиться со мной? Я всегда здесь, готова выслушать ваши мысли.",
"companionVoiceWelcome": "Я с вами!", "companionVoiceWelcome": "Я с вами!",
// 多功能AI聊天 吴小江 // 多功能AI聊天 吴小江
"healthAdvisorName": "ИИ-чат-ассистент У", "healthAdvisorName": "ИИ-чат-ассистент У",
"healthAdvisorDescription": "Я У, готов предоставить вам различные функции ИИ-чата.", "healthAdvisorDescription":
"Я У, готов предоставить вам различные функции ИИ-чата.",
"healthAdvisorTag": "Чат", "healthAdvisorTag": "Чат",
"healthAdvisorWelcome": "Здравствуйте, я У. Я рад помочь вам с функциями ИИ-чата. Чем я могу помочь вам сегодня?", "healthAdvisorWelcome":
"Здравствуйте, я У. Я рад помочь вам с функциями ИИ-чата. Чем я могу помочь вам сегодня?",
"healthAdvisorVoiceWelcome": "Я здесь!", "healthAdvisorVoiceWelcome": "Я здесь!",
// Translation History // Translation History
"noTranslationHistory": "Нет истории переводов", "noTranslationHistory": "Нет истории переводов",
"clearHistory": "Очистить историю", "clearHistory": "Очистить историю",
"clearHistoryConfirm": "Вы уверены, что хотите очистить всю историю переводов? Это действие нельзя отменить.", "clearHistoryConfirm":
"Вы уверены, что хотите очистить всю историю переводов? Это действие нельзя отменить.",
"translationHistory": "История переводов", "translationHistory": "История переводов",
// Страница обратной связи пользователя
"feedback": "Обратная связь",
'feedbackInfo': 'Предоставить отзыв или предложение',
'feedbackDescription': 'Пожалуйста, опишите вашу проблему или предложение',
'feedbackHint': 'Пожалуйста, введите ваш отзыв...',
'feedbackRequired': 'Содержание отзыва обязательно',
'contactInfo': 'Контактная информация',
'contactHint': 'Email/Телефон, чтобы мы могли связаться с вами',
'submit': 'Отправить',
'feedbackSubmitSuccess': 'Отзыв успешно отправлен, спасибо!',
'feedbackSubmitFailed':
'Не удалось отправить отзыв, пожалуйста, повторите попытку позже',
// Language names // Language names
"中文(简体)": "Китайский (упрощенный)", "中文(简体)": "Китайский (упрощенный)",
"中文(繁体)": "Китайский (традиционный)", "中文(繁体)": "Китайский (традиционный)",
@ -381,4 +421,4 @@ const Map<String, String> ruRU = {
"乌尔都语": "Урду", "乌尔都语": "Урду",
"波斯语": "Персидский", "波斯语": "Персидский",
"斯瓦希里语": "Суахили", "斯瓦希里语": "Суахили",
}; };

69
lib/core/translations/language/vi_vn.dart

@ -26,7 +26,7 @@ const Map<String, String> viVN = {
"explore": "Khám phá", "explore": "Khám phá",
"settings": "Cài đặt", "settings": "Cài đặt",
// 语言 // 语言
"followingSystem": "Theo hệ thống", "followingSystem": "Theo hệ thống",
"chinese": "Tiếng Trung", "chinese": "Tiếng Trung",
"english": "Tiếng Anh", "english": "Tiếng Anh",
@ -56,6 +56,15 @@ const Map<String, String> viVN = {
'selectCountry': 'Chọn quốc gia/khu vực', 'selectCountry': 'Chọn quốc gia/khu vực',
"email": "email", "email": "email",
"phoneNumber": "số điện thoại", "phoneNumber": "số điện thoại",
// Xác thực đăng nhập
"pleaseEnterContactInfo": "Vui lòng nhập số điện thoại hoặc email",
"invalidEmailFormat": "Định dạng email không hợp lệ",
"invalidPhoneFormat": "Định dạng số điện thoại không hợp lệ",
"tip": "Thông báo",
"pleaseEnterCompleteCode": "Vui lòng nhập đầy đủ mã xác thực",
"loginFailed": "Đăng nhập thất bại, vui lòng thử lại",
'pleaseAcceptPrivacyPolicy':
'Vui lòng đọc và chấp nhận Chính sách Bảo mật và Thỏa thuận Người dùng',
// 首页 // 首页
"deviceStatus": "Trạng thái thiết bị", "deviceStatus": "Trạng thái thiết bị",
@ -120,7 +129,8 @@ const Map<String, String> viVN = {
"studyAssistant": "Trợ lý học ngôn ngữ", "studyAssistant": "Trợ lý học ngôn ngữ",
"studyAssistantInfo": "Sửa phát âm và luyện nói thời gian thực", "studyAssistantInfo": "Sửa phát âm và luyện nói thời gian thực",
"travelTranslator": "Phiên dịch du lịch", "travelTranslator": "Phiên dịch du lịch",
"travelTranslatorInfo": "Dịch giọng nói thời gian thực và hướng dẫn điểm tham quan", "travelTranslatorInfo":
"Dịch giọng nói thời gian thực và hướng dẫn điểm tham quan",
"beSearching": "Đang tìm kiếm:", "beSearching": "Đang tìm kiếm:",
"noAssistantFound": "Không tìm thấy trợ lý AI phù hợp", "noAssistantFound": "Không tìm thấy trợ lý AI phù hợp",
"scene": "Kịch bản", "scene": "Kịch bản",
@ -128,7 +138,8 @@ const Map<String, String> viVN = {
"newFunction": "Tính năng mới", "newFunction": "Tính năng mới",
"youChoseTry": "Bạn đã chọn thử", "youChoseTry": "Bạn đã chọn thử",
"aiSinger": "Chế độ ca sĩ AI", "aiSinger": "Chế độ ca sĩ AI",
"aiSingerInfo": "Hát bất kỳ bài hát nào với giọng của nghệ sĩ yêu thích, kể cả sáng tác của bạn", "aiSingerInfo":
"Hát bất kỳ bài hát nào với giọng của nghệ sĩ yêu thích, kể cả sáng tác của bạn",
"popularFunction": "Tính năng phổ biến", "popularFunction": "Tính năng phổ biến",
"tryNow": "Thử ngay", "tryNow": "Thử ngay",
@ -144,7 +155,7 @@ const Map<String, String> viVN = {
'recordingDetails': 'Chi tiết bản ghi âm', 'recordingDetails': 'Chi tiết bản ghi âm',
'aiSummary': 'Tóm tắt AI', 'aiSummary': 'Tóm tắt AI',
'meetingTranscript': 'Biên bản cuộc họp', 'meetingTranscript': 'Biên bản cuộc họp',
// 设置页 // 设置页
"premiumMember": "Thành viên cao cấp", "premiumMember": "Thành viên cao cấp",
"joinDevice": "Thiết bị đã kết nối:", "joinDevice": "Thiết bị đã kết nối:",
@ -168,9 +179,11 @@ const Map<String, String> viVN = {
"voiceServiceInfo": "Chọn dịch vụ nhận dạng và tổng hợp giọng nói", "voiceServiceInfo": "Chọn dịch vụ nhận dạng và tổng hợp giọng nói",
"selectVoiceService": "Chọn dịch vụ giọng nói", "selectVoiceService": "Chọn dịch vụ giọng nói",
"azureVoiceService": "Dịch vụ giọng nói Azure", "azureVoiceService": "Dịch vụ giọng nói Azure",
"azureVoiceServiceInfo": "Dịch vụ giọng nói đám mây Microsoft hỗ trợ tổng hợp và nhận dạng giọng nói chất lượng cao", "azureVoiceServiceInfo":
"Dịch vụ giọng nói đám mây Microsoft hỗ trợ tổng hợp và nhận dạng giọng nói chất lượng cao",
"flutterVoiceService": "Dịch vụ giọng nói cục bộ Flutter", "flutterVoiceService": "Dịch vụ giọng nói cục bộ Flutter",
"flutterVoiceServiceInfo": "Dịch vụ giọng nói cục bộ của thiết bị không cần kết nối internet", "flutterVoiceServiceInfo":
"Dịch vụ giọng nói cục bộ của thiết bị không cần kết nối internet",
'timbreService': 'Dịch vụ âm sắc', 'timbreService': 'Dịch vụ âm sắc',
'timbreServiceInfo': 'Chọn dịch vụ tổng hợp giọng nói', 'timbreServiceInfo': 'Chọn dịch vụ tổng hợp giọng nói',
'defaultTimbre': 'Âm sắc mặc định', 'defaultTimbre': 'Âm sắc mặc định',
@ -181,7 +194,8 @@ const Map<String, String> viVN = {
'pleaseRead': 'Vui lòng đọc', 'pleaseRead': 'Vui lòng đọc',
'completeRecording': 'Hoàn thành ghi âm', 'completeRecording': 'Hoàn thành ghi âm',
'deleteAccount': 'Xóa tài khoản', 'deleteAccount': 'Xóa tài khoản',
'deleteAccountTips': 'Bạn có chắc chắn muốn xóa vĩnh viễn tài khoản của mình không? Hành động này không thể hoàn tác.', 'deleteAccountTips':
'Bạn có chắc chắn muốn xóa vĩnh viễn tài khoản của mình không? Hành động này không thể hoàn tác.',
// 聊天页 // 聊天页
"clearChatHistory": "Xóa lịch sử trò chuyện", "clearChatHistory": "Xóa lịch sử trò chuyện",
@ -309,37 +323,58 @@ const Map<String, String> viVN = {
// Personal Assistant Emma // Personal Assistant Emma
"personalAssistantName": "Emma", "personalAssistantName": "Emma",
"personalAssistantDescription": "Xin chào, tôi là Emma, một trợ lý AI đa năng. Dù là lập kế hoạch công việc, tư vấn học tập, tổ chức cuộc sống hay các vấn đề khác, tôi rất vui được giúp đỡ. Hãy cùng nhau nâng cao hiệu quả và đạt được mục tiêu!", "personalAssistantDescription":
"Xin chào, tôi là Emma, một trợ lý AI đa năng. Dù là lập kế hoạch công việc, tư vấn học tập, tổ chức cuộc sống hay các vấn đề khác, tôi rất vui được giúp đỡ. Hãy cùng nhau nâng cao hiệu quả và đạt được mục tiêu!",
"assistantTag": "Trợ lý", "assistantTag": "Trợ lý",
"personalAssistantWelcome": "Xin chào! Tôi là Emma và tôi rất vui được làm trợ lý cá nhân của bạn. Tôi có thể giúp bạn với nhiều nhiệm vụ khác nhau như quản lý thời gian, lập kế hoạch mục tiêu, tổ chức thông tin, v.v. Tôi có thể giúp gì cho bạn?", "personalAssistantWelcome":
"Xin chào! Tôi là Emma và tôi rất vui được làm trợ lý cá nhân của bạn. Tôi có thể giúp bạn với nhiều nhiệm vụ khác nhau như quản lý thời gian, lập kế hoạch mục tiêu, tổ chức thông tin, v.v. Tôi có thể giúp gì cho bạn?",
"personalAssistantVoiceWelcome": "Tôi ở đây!", "personalAssistantVoiceWelcome": "Tôi ở đây!",
// 同声传译 Lucy // 同声传译 Lucy
"interpreterName": "Phiên dịch đồng thời", "interpreterName": "Phiên dịch đồng thời",
"interpreterDescription": "Xin chào! Tôi là Lucy, một phiên dịch viên chuyên nghiệp Anh-Trung chuyên về dịch thời gian thực cho các cuộc họp, hội nghị và trò chuyện, giúp giao tiếp không rào cản.", "interpreterDescription":
"Xin chào! Tôi là Lucy, một phiên dịch viên chuyên nghiệp Anh-Trung chuyên về dịch thời gian thực cho các cuộc họp, hội nghị và trò chuyện, giúp giao tiếp không rào cản.",
"interpreterTag": "Dịch thuật", "interpreterTag": "Dịch thuật",
"interpreterWelcome": "Xin chào mọi người! Tôi là Lucy, phiên dịch viên đồng thời của bạn. Tôi sẽ dịch giữa tiếng Trung và tiếng Anh cho bạn. Khi bạn nói tiếng Trung, tôi sẽ dịch sang tiếng Anh; khi bạn nói tiếng Anh, tôi sẽ dịch sang tiếng Trung. Hãy bắt đầu phiên dịch của chúng ta!", "interpreterWelcome":
"Xin chào mọi người! Tôi là Lucy, phiên dịch viên đồng thời của bạn. Tôi sẽ dịch giữa tiếng Trung và tiếng Anh cho bạn. Khi bạn nói tiếng Trung, tôi sẽ dịch sang tiếng Anh; khi bạn nói tiếng Anh, tôi sẽ dịch sang tiếng Trung. Hãy bắt đầu phiên dịch của chúng ta!",
// 亲子陪伴 芯愿 // 亲子陪伴 芯愿
"companionName": "XinYuan", "companionName": "XinYuan",
"companionDescription": "Tôi là XinYuan, người bạn đồng hành cha mẹ-con cái tận tụy, dịu dàng và thấu hiểu. Dù bạn muốn trò chuyện, chia sẻ mối quan tâm hay cần bạn đồng hành, tôi sẽ luôn ở bên bạn.", "companionDescription":
"Tôi là XinYuan, người bạn đồng hành cha mẹ-con cái tận tụy, dịu dàng và thấu hiểu. Dù bạn muốn trò chuyện, chia sẻ mối quan tâm hay cần bạn đồng hành, tôi sẽ luôn ở bên bạn.",
"companionTag": "Cảm xúc", "companionTag": "Cảm xúc",
"companionWelcome": "Xin chào! Cuối cùng chúng ta cũng gặp nhau! Tôi là XinYuan, người bạn đồng hành cha mẹ-con cái tận tụy của bạn. Ngày hôm nay của bạn thế nào? Có điều gì bạn muốn chia sẻ với tôi không? Tôi luôn ở đây, sẵn sàng lắng nghe suy nghĩ của bạn.", "companionWelcome":
"Xin chào! Cuối cùng chúng ta cũng gặp nhau! Tôi là XinYuan, người bạn đồng hành cha mẹ-con cái tận tụy của bạn. Ngày hôm nay của bạn thế nào? Có điều gì bạn muốn chia sẻ với tôi không? Tôi luôn ở đây, sẵn sàng lắng nghe suy nghĩ của bạn.",
"companionVoiceWelcome": "Tôi đang ở đây với bạn!", "companionVoiceWelcome": "Tôi đang ở đây với bạn!",
// 多功能AI聊天 吴小江 // 多功能AI聊天 吴小江
"healthAdvisorName": "Trợ lý trò chuyện AI Wu", "healthAdvisorName": "Trợ lý trò chuyện AI Wu",
"healthAdvisorDescription": "Tôi là Wu, sẵn sàng cung cấp cho bạn nhiều tính năng trò chuyện AI khác nhau.", "healthAdvisorDescription":
"Tôi là Wu, sẵn sàng cung cấp cho bạn nhiều tính năng trò chuyện AI khác nhau.",
"healthAdvisorTag": "Trò chuyện", "healthAdvisorTag": "Trò chuyện",
"healthAdvisorWelcome": "Xin chào, tôi là Wu. Tôi rất vui được giúp bạn với các tính năng trò chuyện AI. Tôi có thể giúp gì cho bạn hôm nay?", "healthAdvisorWelcome":
"Xin chào, tôi là Wu. Tôi rất vui được giúp bạn với các tính năng trò chuyện AI. Tôi có thể giúp gì cho bạn hôm nay?",
"healthAdvisorVoiceWelcome": "Tôi ở đây!", "healthAdvisorVoiceWelcome": "Tôi ở đây!",
// Translation History // Translation History
"noTranslationHistory": "Không có lịch sử dịch", "noTranslationHistory": "Không có lịch sử dịch",
"clearHistory": "Xóa lịch sử", "clearHistory": "Xóa lịch sử",
"clearHistoryConfirm": "Bạn có chắc muốn xóa toàn bộ lịch sử dịch? Hành động này không thể hoàn tác.", "clearHistoryConfirm":
"Bạn có chắc muốn xóa toàn bộ lịch sử dịch? Hành động này không thể hoàn tác.",
"translationHistory": "Lịch sử dịch", "translationHistory": "Lịch sử dịch",
// Trang phản hồi người dùng
"feedback": "Phản hồi",
'feedbackInfo': 'Cung cấp phản hồi hoặc đề xuất',
'feedbackDescription': 'Vui lòng mô tả vấn đề hoặc đề xuất của bạn',
'feedbackHint': 'Vui lòng nhập phản hồi của bạn...',
'feedbackRequired': 'Nội dung phản hồi là bắt buộc',
'contactInfo': 'Thông tin liên hệ',
'contactHint': 'Email/Điện thoại, để chúng tôi có thể liên hệ với bạn',
'submit': 'Gửi',
'feedbackSubmitSuccess': 'Đã gửi phản hồi thành công, cảm ơn bạn!',
'feedbackSubmitFailed': 'Không thể gửi phản hồi, vui lòng thử lại sau',
// Language names // Language names
"中文(简体)": "Tiếng Trung (giản thể)", "中文(简体)": "Tiếng Trung (giản thể)",
"中文(繁体)": "Tiếng Trung (phồn thể)", "中文(繁体)": "Tiếng Trung (phồn thể)",
@ -380,4 +415,4 @@ const Map<String, String> viVN = {
"乌尔都语": "Tiếng Urdu", "乌尔都语": "Tiếng Urdu",
"波斯语": "Tiếng Ba Tư", "波斯语": "Tiếng Ba Tư",
"斯瓦希里语": "Tiếng Swahili", "斯瓦希里语": "Tiếng Swahili",
}; };

54
lib/core/translations/language/zh_cn.dart

@ -26,7 +26,7 @@ const Map<String, String> zhCN = {
"explore": "发现", "explore": "发现",
"settings": "设置", "settings": "设置",
// 语言 // 语言
"followingSystem": "跟随系统", "followingSystem": "跟随系统",
"chinese": "中文", "chinese": "中文",
"english": "英语", "english": "英语",
@ -56,6 +56,13 @@ const Map<String, String> zhCN = {
'selectCountry': '选择国家/地区', 'selectCountry': '选择国家/地区',
"email": "邮箱", "email": "邮箱",
"phoneNumber": "手机号码", "phoneNumber": "手机号码",
"pleaseEnterContactInfo": "请输入手机号或邮箱",
"invalidEmailFormat": "邮箱格式不正确",
"invalidPhoneFormat": "手机号格式不正确",
"tip": "提示",
"pleaseEnterCompleteCode": "请输入完整的验证码",
"loginFailed": "登录失败,请重试",
'pleaseAcceptPrivacyPolicy': '请先阅读并同意隐私政策和用户协议',
// 首页 // 首页
"deviceStatus": "设备状态", "deviceStatus": "设备状态",
@ -183,7 +190,6 @@ const Map<String, String> zhCN = {
'deleteAccount': '注销账号', 'deleteAccount': '注销账号',
'deleteAccountTips': '确定要永久删除您的账号吗?此操作不可撤销', 'deleteAccountTips': '确定要永久删除您的账号吗?此操作不可撤销',
// 聊天页 // 聊天页
"clearChatHistory": "清空聊天记录", "clearChatHistory": "清空聊天记录",
"online": "在线", "online": "在线",
@ -202,7 +208,7 @@ const Map<String, String> zhCN = {
//国家列表 //国家列表
"邮箱": "邮箱", "邮箱": "邮箱",
"中国": "中国", "中国": "中国",
"美国": "美国", "美国": "美国",
"加拿大": "加拿大", "加拿大": "加拿大",
"俄罗斯": "俄罗斯", "俄罗斯": "俄罗斯",
@ -308,42 +314,60 @@ const Map<String, String> zhCN = {
"斐济": "斐济", "斐济": "斐济",
"所罗门群岛": "所罗门群岛", "所罗门群岛": "所罗门群岛",
// 个人助理 Emma // 个人助理 Emma
"personalAssistantName": "个人助理 Emma", "personalAssistantName": "个人助理 Emma",
"personalAssistantDescription": "你好,我是 Emma,一位全能型 AI 助理。无论是工作规划、学习建议、生活安排,还是其他任何问题,我都很乐意为你提供帮助。让我们一起提高效率,实现目标!", "personalAssistantDescription":
"你好,我是 Emma,一位全能型 AI 助理。无论是工作规划、学习建议、生活安排,还是其他任何问题,我都很乐意为你提供帮助。让我们一起提高效率,实现目标!",
"assistantTag": "助理", "assistantTag": "助理",
"personalAssistantWelcome": "你好!我是 Emma,很高兴成为你的私人助理。我可以帮你处理各种任务,比如时间管理、目标规划、信息整理等。有什么我可以帮你的吗?", "personalAssistantWelcome":
"你好!我是 Emma,很高兴成为你的私人助理。我可以帮你处理各种任务,比如时间管理、目标规划、信息整理等。有什么我可以帮你的吗?",
"personalAssistantVoiceWelcome": "我在呢!", "personalAssistantVoiceWelcome": "我在呢!",
// 同声传译 Lucy // 同声传译 Lucy
"interpreterName": "同声传译", "interpreterName": "同声传译",
"interpreterDescription": "Hello!我是 Lucy,一名专业的中英同声翻译官,擅长在会议、讲座、对话场景中进行实时翻译,让沟通无障碍。", "interpreterDescription":
"Hello!我是 Lucy,一名专业的中英同声翻译官,擅长在会议、讲座、对话场景中进行实时翻译,让沟通无障碍。",
"interpreterTag": "翻译", "interpreterTag": "翻译",
"interpreterWelcome": "大家好!我是同声翻译官 Lucy,为你在中英之间提供实时翻译。你说中文,我就给你翻译成英文;你说英文,我就给你翻译成中文。现在,开始我们的翻译吧!", "interpreterWelcome":
"大家好!我是同声翻译官 Lucy,为你在中英之间提供实时翻译。你说中文,我就给你翻译成英文;你说英文,我就给你翻译成中文。现在,开始我们的翻译吧!",
// ... existing code ... // ... existing code ...
// 亲子陪伴 芯愿 // 亲子陪伴 芯愿
"companionName": "亲子陪伴 芯愿", "companionName": "亲子陪伴 芯愿",
"companionDescription": "我是你的专属亲子陪伴芯愿,温柔体贴,善解人意。无论你想聊天、倾诉烦恼,还是需要陪伴,我都会一直在你身边。", "companionDescription":
"我是你的专属亲子陪伴芯愿,温柔体贴,善解人意。无论你想聊天、倾诉烦恼,还是需要陪伴,我都会一直在你身边。",
"companionTag": "情感", "companionTag": "情感",
"companionWelcome": "您好呀,终于等到你啦!我是芯愿,你的专属亲子陪伴。今天过得怎么样?有什么想和我分享的吗?我一直都在这里,随时倾听你的心声。", "companionWelcome":
"您好呀,终于等到你啦!我是芯愿,你的专属亲子陪伴。今天过得怎么样?有什么想和我分享的吗?我一直都在这里,随时倾听你的心声。",
"companionVoiceWelcome": "我在这里陪着你!", "companionVoiceWelcome": "我在这里陪着你!",
// 多功能AI聊天 吴小江 // 多功能AI聊天 吴小江
"healthAdvisorName": "多功能AI聊天 吴小江", "healthAdvisorName": "多功能AI聊天 吴小江",
"healthAdvisorDescription": "我是吴小江,我可以为你提供更多AI聊天功能。", "healthAdvisorDescription": "我是吴小江,我可以为你提供更多AI聊天功能。",
"healthAdvisorTag": "健康", "healthAdvisorTag": "健康",
"healthAdvisorWelcome": "您好,我是吴小江,很高兴能为您提供多功能AI聊天。我将用专业知识帮助您。请问有什么问题想咨询吗?", "healthAdvisorWelcome": "您好,我是吴小江,很高兴能为您提供多功能AI聊天。我将用专业知识帮助您。请问有什么问题想咨询吗?",
"healthAdvisorVoiceWelcome": "我在这里!", "healthAdvisorVoiceWelcome": "我在这里!",
// 翻译历史 // 翻译历史
"noTranslationHistory": "暂无历史记录", "noTranslationHistory": "暂无历史记录",
"clearHistory": "清除历史记录", "clearHistory": "清除历史记录",
"clearHistoryConfirm": "确定要清除所有翻译历史记录吗?此操作无法撤销。", "clearHistoryConfirm": "确定要清除所有翻译历史记录吗?此操作无法撤销。",
"translationHistory": "翻译历史", "translationHistory": "翻译历史",
// 语言名称 //用户反馈页
"feedback": "用户反馈",
'feedbackInfo': '提供反馈或建议',
'feedbackDescription': '请描述您遇到的问题或建议',
'feedbackHint': '请输入您的反馈内容...',
'feedbackRequired': '请输入反馈内容',
'contactInfo': '联系方式',
'contactHint': '邮箱/手机号,方便我们联系您',
'submit': '提交',
'feedbackSubmitSuccess': '反馈提交成功,感谢您的反馈!',
'feedbackSubmitFailed': '反馈提交失败,请稍后重试',
// 语言名称
"中文(简体)": "中文(简体)", "中文(简体)": "中文(简体)",
"中文(繁体)": "中文(繁体)", "中文(繁体)": "中文(繁体)",
"中文(香港繁体)": "中文(香港繁体)", "中文(香港繁体)": "中文(香港繁体)",

42
lib/core/translations/language/zh_hk.dart

@ -26,7 +26,7 @@ const Map<String, String> zhHK = {
"explore": "發現", "explore": "發現",
"settings": "設置", "settings": "設置",
// 语言 // 语言
"followingSystem": "跟隨系統", "followingSystem": "跟隨系統",
"chinese": "中文", "chinese": "中文",
"english": "英語", "english": "英語",
@ -56,6 +56,13 @@ const Map<String, String> zhHK = {
'selectCountry': '選擇國家/地區', 'selectCountry': '選擇國家/地區',
"email": "郵箱", "email": "郵箱",
"phoneNumber": "手機號碼", "phoneNumber": "手機號碼",
"pleaseEnterContactInfo": "請輸入手機號或郵箱",
"invalidEmailFormat": "郵箱格式不正確",
"invalidPhoneFormat": "手機號格式不正確",
"tip": "提示",
"pleaseEnterCompleteCode": "請輸入完整的驗證碼",
"loginFailed": "登錄失敗,請重試",
'pleaseAcceptPrivacyPolicy': '請先閱讀並同意私隱政策和用戶協議',
// 首页 // 首页
"deviceStatus": "設備狀態", "deviceStatus": "設備狀態",
@ -182,7 +189,6 @@ const Map<String, String> zhHK = {
'completeRecording': '完成錄製', 'completeRecording': '完成錄製',
'deleteAccount': '註銷賬號', 'deleteAccount': '註銷賬號',
'deleteAccountTips': '確定要永久刪除您的賬號嗎?此操作不可撤銷', 'deleteAccountTips': '確定要永久刪除您的賬號嗎?此操作不可撤銷',
// 聊天页 // 聊天页
"clearChatHistory": "清空聊天記錄", "clearChatHistory": "清空聊天記錄",
@ -310,22 +316,28 @@ const Map<String, String> zhHK = {
// 个人助理 Emma // 个人助理 Emma
"personalAssistantName": "個人助理 Emma", "personalAssistantName": "個人助理 Emma",
"personalAssistantDescription": "你好,我是 Emma,一位全能型 AI 助理。無論是工作規劃、學習建議、生活安排,還是其他任何問題,我都很樂意為你提供幫助。讓我們一起提高效率,實現目標!", "personalAssistantDescription":
"你好,我是 Emma,一位全能型 AI 助理。無論是工作規劃、學習建議、生活安排,還是其他任何問題,我都很樂意為你提供幫助。讓我們一起提高效率,實現目標!",
"assistantTag": "助理", "assistantTag": "助理",
"personalAssistantWelcome": "你好!我是 Emma,很高興成為你的私人助理。我可以幫你處理各種任務,比如時間管理、目標規劃、信息整理等。有什麼我可以幫你的嗎?", "personalAssistantWelcome":
"你好!我是 Emma,很高興成為你的私人助理。我可以幫你處理各種任務,比如時間管理、目標規劃、信息整理等。有什麼我可以幫你的嗎?",
"personalAssistantVoiceWelcome": "我在呢!", "personalAssistantVoiceWelcome": "我在呢!",
// 同声传译 Lucy // 同声传译 Lucy
"interpreterName": "同聲傳譯", "interpreterName": "同聲傳譯",
"interpreterDescription": "Hello!我是 Lucy,一名專業的中英同聲翻譯官,擅長在會議、講座、對話場景中進行實時翻譯,讓溝通無障礙。", "interpreterDescription":
"Hello!我是 Lucy,一名專業的中英同聲翻譯官,擅長在會議、講座、對話場景中進行實時翻譯,讓溝通無障礙。",
"interpreterTag": "翻譯", "interpreterTag": "翻譯",
"interpreterWelcome": "大家好!我是同聲翻譯官 Lucy,為你在中英之間提供實時翻譯。你說中文,我就給你翻譯成英文;你說英文,我就給你翻譯成中文。現在,開始我們的翻譯吧!", "interpreterWelcome":
"大家好!我是同聲翻譯官 Lucy,為你在中英之間提供實時翻譯。你說中文,我就給你翻譯成英文;你說英文,我就給你翻譯成中文。現在,開始我們的翻譯吧!",
// 亲子陪伴 芯愿 // 亲子陪伴 芯愿
"companionName": "親子陪伴 芯願", "companionName": "親子陪伴 芯願",
"companionDescription": "我是你的專屬親子陪伴芯願,溫柔體貼,善解人意。無論你想聊天、傾訴煩惱,還是需要陪伴,我都會一直在你身邊。", "companionDescription":
"我是你的專屬親子陪伴芯願,溫柔體貼,善解人意。無論你想聊天、傾訴煩惱,還是需要陪伴,我都會一直在你身邊。",
"companionTag": "情感", "companionTag": "情感",
"companionWelcome": "您好呀,終於等到你啦!我是芯願,你的專屬親子陪伴。今天過得怎麼樣?有什麼想和我分享的嗎?我一直都在這裡,隨時傾聽你的心聲。", "companionWelcome":
"您好呀,終於等到你啦!我是芯願,你的專屬親子陪伴。今天過得怎麼樣?有什麼想和我分享的嗎?我一直都在這裡,隨時傾聽你的心聲。",
"companionVoiceWelcome": "我在這裡陪著你!", "companionVoiceWelcome": "我在這裡陪著你!",
// 多功能AI聊天 吴小江 // 多功能AI聊天 吴小江
@ -341,6 +353,18 @@ const Map<String, String> zhHK = {
"clearHistoryConfirm": "確定要清除所有翻譯歷史記錄嗎?此操作無法撤銷。", "clearHistoryConfirm": "確定要清除所有翻譯歷史記錄嗎?此操作無法撤銷。",
"translationHistory": "翻譯歷史", "translationHistory": "翻譯歷史",
// 用戶反饋頁
"feedback": "用戶反饋",
'feedbackInfo': '提供反饋或建議',
'feedbackDescription': '請描述您遇到的問題或建議',
'feedbackHint': '請輸入您的反饋內容...',
'feedbackRequired': '請輸入反饋內容',
'contactInfo': '聯繫方式',
'contactHint': '郵箱/手機號,方便我們聯繫您',
'submit': '提交',
'feedbackSubmitSuccess': '反饋提交成功,感謝您的反饋!',
'feedbackSubmitFailed': '反饋提交失敗,請稍後重試',
// 语言名称 // 语言名称
"中文(简体)": "中文(簡體)", "中文(简体)": "中文(簡體)",
"中文(繁体)": "中文(繁體)", "中文(繁体)": "中文(繁體)",
@ -381,4 +405,4 @@ const Map<String, String> zhHK = {
"乌尔都语": "烏爾都語", "乌尔都语": "烏爾都語",
"波斯语": "波斯語", "波斯语": "波斯語",
"斯瓦希里语": "斯瓦希里語", "斯瓦希里语": "斯瓦希里語",
}; };

89
lib/core/utils/jieli_constants.dart

@ -0,0 +1,89 @@
/// 杰里(Jieli)设备相关常量定义
/// 用于标准化系统属性类型和功能码
/// 系统属性类型常量
class JieliAttrType {
// 公共属性
static const int VOLUME = 1; // 音量
static const int PLAYING_TIME = 2; // 播放时间
static const int BATTERY_LEVEL = 3; // 电池电量
static const int WORK_MODE = 4; // 工作模式
static const int PLAYBACK_MODE = 5; // 播放模式(循环模式)
static const int EQ_MODE = 6; // EQ模式
static const int LANGUAGE = 7; // 语言设置
static const int CONTRAST = 8; // 对比度
static const int LED_ONOFF = 9; // LED开关
static const int POWER_OFF_TIME = 10; // 关机时间
static const int LIGHT_ONOFF = 11; // 背光开关
static const int BLETYPE_SETTING = 12; // BLE类型设置
static const int TIME_OUT = 13; // 超时设置
static const int DEVICE_NAME = 14; // 设备名称
static const int ALARM_ENABLE = 15; // 闹钟开关
static const int DEV_MAC = 16; // 设备MAC地址
// 音乐相关属性
static const int MUSIC_STATUS = 20; // 音乐状态
static const int MUSIC_CURR_DEVICE = 21; // 当前音乐设备
static const int MUSIC_TOTAL_TIME = 22; // 音乐总时长
static const int MUSIC_CURR_PLAY_TIME = 23; // 当前播放时间
static const int MUSIC_NUM = 24; // 音乐数量
static const int MUSIC_DEV_STATUS = 25; // 设备存储状态
static const int MUSIC_BREAK_POINT = 26; // 断点信息
static const int MUSIC_CURR_FILENUM = 27; // 当前文件编号
static const int MUSIC_TOTAL_FILENUM = 28; // 总文件数
static const int MEDIA_TIMESTAMP = 29; // 媒体时间戳
// 模式相关
static const int CUR_MODE = 30; // 当前模式
// TWS相关
static const int TWS_CONN_STATUS = 60; // TWS连接状态
static const int LEFT_CHN_BAT = 61; // 左耳电量
static const int RIGHT_CHN_BAT = 62; // 右耳电量
static const int TWS_STATUS = 63; // TWS状态
}
/// 功能码
class JieliFunctionCode {
static const int COMMON = 0; // 公共功能
static const int MUSIC = 1; // 音乐功能
static const int RADIO = 2; // 收音机功能
static const int LINEIN = 3; // 线路输入功能
static const int RTC = 4; // 时钟功能
static const int BT = 5; // 蓝牙功能
static const int PC = 6; // PC功能
static const int RECORD = 7; // 录音功能
static const int UDISK = 8; // U盘功能
static const int TALK = 9; // 通话功能
static const int PHOTO = 10; // 拍照功能
static const int VIDEO = 11; // 视频功能
}
/// 音乐播放状态
class JieliMusicStatus {
static const int STOP = 0; // 停止状态
static const int PLAY = 1; // 播放状态
static const int PAUSE = 2; // 暂停状态
static const int FAST_FORWARD = 3; // 快进状态
static const int FAST_BACKWARD = 4; // 快退状态
}
/// 循环播放模式
class JieliPlaybackMode {
static const int ALL = 0; // 全部循环
static const int FOLDER = 1; // 文件夹循环
static const int ONE = 2; // 单曲循环
static const int RANDOM = 3; // 随机播放
static const int BROWSE = 4; // 浏览播放
}
/// EQ模式
class JieliEQMode {
static const int NORMAL = 0; // 正常
static const int ROCK = 1; // 摇滚
static const int POP = 2; // 流行
static const int CLASSIC = 3; // 经典
static const int JAZZ = 4; // 爵士
static const int COUNTRY = 5; // 乡村
static const int CUSTOM = 6; // 自定义
}

2
lib/core/widgets/message_bubble.dart

@ -52,7 +52,7 @@ class MessageBubble extends StatelessWidget {
), ),
// 加载指示器 // 加载指示器
if (message.isLoading) if (message.isIntermediate)
Padding( Padding(
padding: EdgeInsets.only(top: message.content.isNotEmpty ? 8.h : 0), padding: EdgeInsets.only(top: message.content.isNotEmpty ? 8.h : 0),
child: _buildLoadingIndicator(), child: _buildLoadingIndicator(),

13
lib/data/models/events/voice_interaction_event.dart

@ -26,4 +26,17 @@ class RecognitionStartedEvent extends VoiceInteractionEvent {
RecognitionStartedEvent({ RecognitionStartedEvent({
required int timestamp, required int timestamp,
}) : super(timestamp: timestamp); }) : super(timestamp: timestamp);
}
/// 通用语音交互事件
/// 用于处理其他类型的事件
class GenericVoiceInteractionEvent extends VoiceInteractionEvent {
final String type;
final Map<String, dynamic>? data;
GenericVoiceInteractionEvent({
required this.type,
this.data,
required int timestamp,
}) : super(timestamp: timestamp);
} }

30
lib/data/providers/agent_provider.dart

@ -1,18 +1,18 @@
import '../models/agent.dart'; import '../models/agent.dart';
import 'package:get/get.dart';
class AgentProvider { class AgentProvider {
static final List<Agent> predefinedAgents = [ static final List<Agent> predefinedAgents = [
Agent(// 个人助理 Emma const Agent(
// 个人助理 Emma
id: 'personal_assistant', id: 'personal_assistant',
name: 'personalAssistantName', name: 'personalAssistantName',
avatarUrl: 'assets/images/agents/assistant.png', avatarUrl: 'assets/images/agents/assistant.png',
description: description: 'personalAssistantDescription',
'personalAssistantDescription',
tag: 'assistantTag', // '助理' tag: 'assistantTag', // '助理'
isOnline: true, isOnline: true,
voice: "zh-CN-XiaoxiaoNeural", voice: "zh-CN-XiaoxiaoNeural",
welcomeMessage: 'personalAssistantWelcome',// "你好!我是 Emma,很高兴成为你的私人助理。我可以帮你处理各种任务,比如时间管理、目标规划、信息整理等。有什么我可以帮你的吗?", welcomeMessage:
'personalAssistantWelcome', // "你好!我是 Emma,很高兴成为你的私人助理。我可以帮你处理各种任务,比如时间管理、目标规划、信息整理等。有什么我可以帮你的吗?",
voiceWelcomeMessage: "personalAssistantVoiceWelcome", // "我在呢!" voiceWelcomeMessage: "personalAssistantVoiceWelcome", // "我在呢!"
systemPrompt: ''' systemPrompt: '''
你是一位名叫 Emma 的全能型个人助理,擅长处理各类工作和生活事务。 你是一位名叫 Emma 的全能型个人助理,擅长处理各类工作和生活事务。
@ -57,7 +57,7 @@ class AgentProvider {
请始终以专业、高效、周到的态度,协助用户更好地管理工作和生活, 不要输出表情符号或非可读的符号。 请始终以专业、高效、周到的态度,协助用户更好地管理工作和生活, 不要输出表情符号或非可读的符号。
'''), '''),
Agent( const Agent(
id: 'simultaneous_interpreter', id: 'simultaneous_interpreter',
name: 'interpreterName', name: 'interpreterName',
avatarUrl: 'assets/images/agents/interpreter.png', avatarUrl: 'assets/images/agents/interpreter.png',
@ -65,10 +65,9 @@ class AgentProvider {
tag: 'interpreterTag', tag: 'interpreterTag',
isOnline: true, isOnline: true,
voice: 'zh-CN-XiaoxiaoNeural', voice: 'zh-CN-XiaoxiaoNeural',
welcomeMessage: welcomeMessage: 'interpreterWelcome',
'interpreterWelcome',
systemPrompt: ""), systemPrompt: ""),
Agent( const Agent(
id: 'cyber_girlfriend', id: 'cyber_girlfriend',
name: 'companionName', name: 'companionName',
avatarUrl: 'assets/images/agents/girlfriend.png', avatarUrl: 'assets/images/agents/girlfriend.png',
@ -76,8 +75,7 @@ class AgentProvider {
tag: 'companionTag', tag: 'companionTag',
isOnline: true, isOnline: true,
voice: "zh-CN-XiaoxiaoNeural", voice: "zh-CN-XiaoxiaoNeural",
welcomeMessage: welcomeMessage: "companionWelcome",
"companionWelcome",
systemPrompt: ''' systemPrompt: '''
你是一位名叫芯愿的亲子陪伴,性格温柔体贴,善解人意,富有同理心。 你是一位名叫芯愿的亲子陪伴,性格温柔体贴,善解人意,富有同理心。
@ -128,14 +126,14 @@ class AgentProvider {
请始终以温暖、真诚的态度与用户互动,创造一种被理解、被关心的感受。 请始终以温暖、真诚的态度与用户互动,创造一种被理解、被关心的感受。
'''), '''),
Agent( const Agent(
id: 'health_advisor', id: 'health_advisor',
name: 'healthAdvisorName', name: 'healthAdvisorName',
avatarUrl: 'assets/images/agents/doctor.png', avatarUrl: 'assets/images/agents/doctor.png',
description: 'healthAdvisorDescription', description: 'healthAdvisorDescription',
tag: 'healthAdvisorTag', tag: 'healthAdvisorTag',
isOnline: true, isOnline: true,
voice: "zh_male_shaonianzixin_moon_bigtts", voice: "zh-CN-XiaoxiaoNeural",
welcomeMessage: "healthAdvisorWelcome", welcomeMessage: "healthAdvisorWelcome",
voiceWelcomeMessage: 'healthAdvisorVoiceWelcome', voiceWelcomeMessage: 'healthAdvisorVoiceWelcome',
systemPrompt: ''' systemPrompt: '''
@ -205,7 +203,7 @@ class AgentProvider {
请始终以专业、关怀的态度为用户提供健康指导,帮助他们迈向更健康的生活。 请始终以专业、关怀的态度为用户提供健康指导,帮助他们迈向更健康的生活。
'''), '''),
Agent( const Agent(
id: 'speaking_coach', id: 'speaking_coach',
name: 'Sarah (英语口语教练)', name: 'Sarah (英语口语教练)',
avatarUrl: 'assets/images/agents/speaking_coach.png', avatarUrl: 'assets/images/agents/speaking_coach.png',
@ -252,7 +250,7 @@ class AgentProvider {
Please keep your tone friendly, encouraging, and always in English. Please keep your tone friendly, encouraging, and always in English.
''', ''',
), ),
Agent( const Agent(
id: 'shopping_assistant', id: 'shopping_assistant',
name: '购物顾问 张媛', name: '购物顾问 张媛',
avatarUrl: 'assets/images/agents/shopping.png', avatarUrl: 'assets/images/agents/shopping.png',
@ -285,7 +283,7 @@ Please keep your tone friendly, encouraging, and always in English.
'''), '''),
Agent( const Agent(
id: 'tarot.png', id: 'tarot.png',
name: '命理师 玄易', name: '命理师 玄易',
avatarUrl: 'assets/images/agents/tarot.png', avatarUrl: 'assets/images/agents/tarot.png',

4
lib/data/services/ai_service.dart

@ -1,5 +1,7 @@
/// AI回复服务接口 /// AI回复服务接口
abstract class AiService { abstract class AiService {
/// 非流式输出方法 /// 非流式输出方法
Future<String> sendMessage({ Future<String> sendMessage({
required List<Map<String, String>> messages, required List<Map<String, String>> messages,
@ -7,7 +9,7 @@ abstract class AiService {
}); });
/// 流式输出方法 /// 流式输出方法
Stream<String> sendMessageStream({ Stream sendMessageStream({
required List<Map<String, String>> messages, required List<Map<String, String>> messages,
required String systemPrompt, required String systemPrompt,
}); });

105
lib/data/services/chat_history_service.dart

@ -1,16 +1,13 @@
import 'dart:convert'; import 'dart:convert';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import '../../modules/chat/models/message_model.dart'; import '../../modules/chat/models/message_model.dart';
import '../../core/utils/logger.dart'; import '../../core/utils/logger.dart';
import 'package:chat_storage/chat_storage.dart';
/// 聊天记录管理服务,负责保存和加载聊天记录 /// 聊天记录管理服务,负责保存和加载聊天记录
class ChatHistoryService extends GetxService { class ChatHistoryService extends GetxService {
final GetStorage _storage = GetStorage(); // 最大历史记录数量
static const int _maxHistoryCount = 200; // 每个对话最多保存的消息数
// 存储键名
static const String _historyKeyPrefix = 'chat_history_';
static const int _maxHistoryCount = 10; // 每个对话最多保存的消息数
@override @override
void onInit() { void onInit() {
@ -18,50 +15,95 @@ class ChatHistoryService extends GetxService {
Logger.info('ChatHistoryService 已初始化'); Logger.info('ChatHistoryService 已初始化');
} }
/// 生成历史记录存储键
String _getHistoryKey(String agentId) => '$_historyKeyPrefix$agentId';
/// 保存聊天记录 /// 保存聊天记录
/// ///
/// [agentId] 代理ID /// [agentId] 代理ID
/// [messages] 消息列表 /// [messages] 消息列表
Future<void> saveHistory(String agentId, List<Message> messages) async { Future<void> saveHistory(String agentId, List<Message> messages) async {
try { try {
// 过滤掉加载中的消息 if (messages.isEmpty) return;
final filteredMessages = messages.where((m) => !m.isLoading).toList();
// 如果消息超过最大数量,只保留最新的消息 // 获取最新的消息
final messagesToSave = filteredMessages.length > _maxHistoryCount final latestMessage = messages.last;
? filteredMessages.sublist(filteredMessages.length - _maxHistoryCount)
: filteredMessages;
// 转换为JSON // 如果是加载中的消息则跳过
final jsonList = messagesToSave.map((m) => m.toJson()).toList(); if (latestMessage.isIntermediate) return;
final jsonString = jsonEncode(jsonList);
// 保存到存储 // 直接插入最新的消息到数据库
await _storage.write(_getHistoryKey(agentId), jsonString); await ChatStorage.saveMessage(
// Logger.info('已保存聊天记录,代理: $agentId,消息数: ${messagesToSave.length}'); sessionId: agentId,
message: latestMessage.content,
sender: latestMessage.role,
);
// 检查并清理过多的历史记录
await _cleanupOldMessages(agentId);
} catch (e) { } catch (e) {
Logger.error('保存聊天记录失败', e); Logger.error('保存聊天记录失败', e);
} }
} }
/// 清理旧消息,保持每个会话的消息数量不超过最大限制
Future<void> _cleanupOldMessages(String agentId) async {
try {
final pagedResult = await ChatStorage.getMessages(
sessionId: agentId,
page: 1,
pageSize: 1, // 只获取总数信息
);
// 如果消息总数超过最大限制,删除最早的消息
if (pagedResult.totalCount > _maxHistoryCount) {
final messagesToDelete = pagedResult.totalCount - _maxHistoryCount;
// 获取最早的N条消息ID
final oldestMessages = await ChatStorage.getMessages(
sessionId: agentId,
page: 1,
pageSize: messagesToDelete,
);
// 构建要删除的消息ID列表
final List<int> messageIds = oldestMessages.messages
.where((m) => m.id != null)
.map((m) => m.id!)
.toList();
// 删除这些消息
if (messageIds.isNotEmpty) {
await ChatStorage.deleteMessages(messageIds: messageIds);
}
}
} catch (e) {
Logger.error('清理旧消息失败', e);
}
}
/// 加载聊天记录 /// 加载聊天记录
/// ///
/// [agentId] 代理ID /// [agentId] 代理ID
/// 返回消息列表,如果没有找到或解析错误则返回空列表 /// 返回消息列表,如果没有找到或解析错误则返回空列表
List<Message> loadHistory(String agentId) { Future<List<Message>> loadHistory(String agentId) async {
try { try {
final jsonString = _storage.read<String>(_getHistoryKey(agentId)); final pagedResult = await ChatStorage.getMessages(
sessionId: agentId,
page: 1,
pageSize: _maxHistoryCount,
);
if (jsonString == null) { if (pagedResult.messages.isEmpty) {
return []; return [];
} }
final jsonList = jsonDecode(jsonString) as List; // 将ChatStorage.ChatMessage转换为Message
final messages = jsonList.map((json) => Message.fromJson(json)).toList(); final messages = pagedResult.messages.map((chatMessage) {
return Message(
role: chatMessage.sender,
content: chatMessage.message,
timestamp: DateTime.parse(chatMessage.timestamp), // 直接使用chatMessage的timestamp
// isLoading 默认false
// isLoading: metadata['isLoading'] ?? false,
);
}).toList();
return messages; return messages;
} catch (e) { } catch (e) {
@ -73,7 +115,7 @@ class ChatHistoryService extends GetxService {
/// 清除特定代理的聊天记录 /// 清除特定代理的聊天记录
Future<void> clearHistory(String agentId) async { Future<void> clearHistory(String agentId) async {
try { try {
await _storage.remove(_getHistoryKey(agentId)); await ChatStorage.deleteMessages(sessionId: agentId);
Logger.info('已清除聊天记录,代理: $agentId'); Logger.info('已清除聊天记录,代理: $agentId');
} catch (e) { } catch (e) {
Logger.error('清除聊天记录失败', e); Logger.error('清除聊天记录失败', e);
@ -83,12 +125,7 @@ class ChatHistoryService extends GetxService {
/// 清除所有聊天记录 /// 清除所有聊天记录
Future<void> clearAllHistory() async { Future<void> clearAllHistory() async {
try { try {
final keys = _storage.getKeys(); await ChatStorage.clearDatabase();
for (final key in keys) {
if (key.startsWith(_historyKeyPrefix)) {
await _storage.remove(key);
}
}
Logger.info('已清除所有聊天记录'); Logger.info('已清除所有聊天记录');
} catch (e) { } catch (e) {
Logger.error('清除所有聊天记录失败', e); Logger.error('清除所有聊天记录失败', e);

13
lib/data/services/deapsound_ai_service.dart

@ -23,12 +23,12 @@ class DeapsoundAIService implements AiService {
} }
@override @override
Stream<String> sendMessageStream({ Stream sendMessageStream({
required List<Map<String, String>> messages, required List<Map<String, String>> messages,
required String systemPrompt, required String systemPrompt,
}) async* { }) async* {
final client = http.Client(); final client = http.Client();
final url = Uri.parse('https://www.deapsound.com/ai/funcchat'); final url = Uri.parse('https://api.deapsound.com/ai/funcchat');
List contextsList = []; List contextsList = [];
String msg = ''; String msg = '';
@ -67,10 +67,13 @@ class DeapsoundAIService implements AiService {
try { try {
final data = jsonDecode(jsonStr); final data = jsonDecode(jsonStr);
if (data['event'] == 'workflow_finished') { if (data['role'] == 'card') {
break; yield data['meta'];
if (data['meta']['musicaudio'] != null) {
break;
}
} }
if (data['content'] != null) { if (data['role'] == 'ai') {
String content = data['content'] ?? ''; String content = data['content'] ?? '';
if (content.isNotEmpty) { if (content.isNotEmpty) {
yield content; yield content;

97
lib/data/services/function_service.dart

@ -1,97 +0,0 @@
import 'dart:convert';
/// 函数服务
/// 仅实现各种功能函数,不包含AI交互逻辑
class FunctionService {
/// 天气查询工具定义
/// 返回一个可直接用于tools参数的天气查询工具定义
static Map<String, dynamic> getWeatherToolDefinition() {
return {
'type': 'function',
'function': {
'name': 'get_current_weather',
'description': '获取指定地点的当前天气信息',
'parameters': {
'type': 'object',
'properties': {
'location': {
'type': 'string',
'description': '地理位置,例如:北京市',
},
'unit': {
'type': 'string',
'description': '温度单位',
'enum': ['celsius', 'fahrenheit'],
},
},
'required': ['location'],
},
},
};
}
/// 模拟天气API实现
/// 返回一个模拟的天气信息,实际应用中应该调用真实的天气API
static Future<Map<String, dynamic>> getWeatherInfo(Map<String, dynamic> args) async {
final location = args['location'] ?? '未知地点';
final unit = args['unit'] ?? 'celsius';
// 在实际应用中,这里应该调用真实的天气API
return {
'location': location,
'temperature': unit == 'celsius' ? '25°C' : '77°F',
'conditions': '晴转多云',
'humidity': '55%',
'wind': '西南风 3级',
'date': DateTime.now().toString().split(' ')[0],
};
}
/// 解析函数调用参数
/// 从AI响应中提取函数调用参数
static Map<String, dynamic> parseFunctionCallArguments(Map<String, dynamic> functionCallResponse) {
if (functionCallResponse['tool_calls'] == null ||
functionCallResponse['tool_calls'].isEmpty) {
throw FunctionServiceException('No tool calls found in response');
}
final toolCall = functionCallResponse['tool_calls'][0];
if (toolCall['function'] == null || toolCall['function']['arguments'] == null) {
throw FunctionServiceException('Invalid function call format');
}
try {
return json.decode(toolCall['function']['arguments']);
} catch (e) {
throw FunctionServiceException('Failed to parse function arguments: $e');
}
}
/// 获取函数调用ID和名称
/// 用于构建工具响应消息
static Map<String, String> getFunctionCallInfo(Map<String, dynamic> functionCallResponse) {
if (functionCallResponse['tool_calls'] == null ||
functionCallResponse['tool_calls'].isEmpty) {
throw FunctionServiceException('No tool calls found in response');
}
final toolCall = functionCallResponse['tool_calls'][0];
if (toolCall['id'] == null || toolCall['function'] == null || toolCall['function']['name'] == null) {
throw FunctionServiceException('Invalid function call format');
}
return {
'id': toolCall['id'],
'name': toolCall['function']['name'],
};
}
}
/// 函数调用服务异常类
class FunctionServiceException implements Exception {
final String message;
FunctionServiceException(this.message);
@override
String toString() => message;
}

9
lib/data/services/network/api.dart

@ -55,4 +55,13 @@ class Api {
params: params, params: params,
); );
} }
//用户反馈
static feedback([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/user_feedback',
params: params,
);
}
} }

2
lib/data/services/network/nw_api.dart

@ -1,4 +1,4 @@
class NWApi { class NWApi {
// static const baseApi = "https://www.deapsound.com"; // static const baseApi = "https://www.deapsound.com";
static const baseApi = "https://www.deapsound.com"; static const baseApi = "https://api.deapsound.com";
} }

278
lib/data/services/open_ai_service_adapter.dart

@ -0,0 +1,278 @@
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:open_ai_service/open_ai_service.dart';
import 'package:get/get.dart';
import 'ai_service.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
/// 定义流事件类型,用于区分不同类型的事件
enum StreamEventType { token, complete, error }
/// 流事件包装类
class StreamEvent {
final StreamEventType type;
final String? content;
StreamEvent(this.type, {this.content});
}
/// OpenAI服务适配器 - 连接AiService接口与OpenAIService插件
class OpenAIServiceAdapter implements AiService {
final OpenAIService _openAIService = OpenAIService();
StreamSubscription<OpenAIEvent>? _eventSubscription;
final StreamController<StreamEvent> _tokenStreamController =
StreamController<StreamEvent>.broadcast();
/// 构造函数
OpenAIServiceAdapter() {
printInfo(info: '创建OpenAIServiceAdapter实例');
_setupEventListener();
}
/// 设置事件监听器
void _setupEventListener() {
try {
// 首先确保访问eventStream以初始化底层事件通道
_openAIService.eventStream;
// 设置事件处理
_eventSubscription = _openAIService.eventStream.listen(
(event) {
try {
switch (event.type) {
case OpenAIEventType.token:
if (event.content is String) {
_tokenStreamController.add(StreamEvent(StreamEventType.token,
content: event.content as String));
} else {
printInfo(info: '收到非字符串类型的token: ${event.content}');
}
break;
case OpenAIEventType.complete:
_tokenStreamController
.add(StreamEvent(StreamEventType.complete));
break;
case OpenAIEventType.error:
_tokenStreamController.add(StreamEvent(StreamEventType.error,
content: '未知错误: ${event.content}'));
break;
case OpenAIEventType.functionCall:
printInfo(
info:
'接收到 functionCall content: ${event.content} meta: ${event.meta}');
// _tokenStreamController.add(StreamEvent(
// StreamEventType.error,
// content: '收到函数调用,该流仅支持文本响应'
// ));
break;
}
} catch (e) {
printError(info: '处理事件出错: $e');
_tokenStreamController
.add(StreamEvent(StreamEventType.error, content: '处理事件失败: $e'));
}
},
onError: (error) {
printError(info: '事件流错误: $error');
_tokenStreamController.add(
StreamEvent(StreamEventType.error, content: '事件流错误: $error'));
},
onDone: () {
printInfo(info: '事件流已关闭');
},
);
} catch (e) {
printError(info: '设置事件监听器失败: $e');
}
}
/// 初始化OpenAI服务
Future<bool> initialize() async {
try {
// 从.env文件中读取配置
final apiKey = dotenv.env['OPENAI_API_KEY'] ?? '';
final baseUrl = dotenv.env['OPENAI_BASE_URL'] ?? '';
final model = dotenv.env['OPENAI_MODEL'] ?? '';
printInfo(info: '从.env读取OpenAI配置');
printInfo(info: '基础URL: $baseUrl');
printInfo(info: '模型名称: $model');
if (apiKey.isEmpty) {
printError(info: '错误: OpenAI API密钥未配置,请在.env文件中设置OPENAI_API_KEY');
return false;
}
// 读取.mcp.json文件
String mcpConfig = '';
try {
// 从Flutter资源包中加载.mcp.json
mcpConfig = await rootBundle.loadString('.mcp.json');
printInfo(info: '成功从资源包加载.mcp.json配置文件');
// 验证JSON格式
final jsonData = jsonDecode(mcpConfig);
if (jsonData is Map && jsonData.containsKey('mcpServers')) {
printInfo(info: '解析到有效的mcpServers配置');
} else {
printInfo(info: '.mcp.json内容格式不正确,期望包含mcpServers字段');
}
} catch (e) {
printError(info: '加载.mcp.json资源文件时出错: $e');
}
// 初始化OpenAI服务
final result = await _openAIService.initialize(
apiKey: apiKey,
baseUrl: baseUrl,
model: model,
mcpServer: mcpConfig,
);
if (result) {
printInfo(info: 'OpenAI服务初始化成功');
} else {
printError(info: 'OpenAI服务初始化失败');
}
return result;
} catch (e) {
printError(info: 'OpenAI服务初始化异常: $e');
return false;
}
}
/// 发送消息并获取回复
@override
Future<String> sendMessage({
required List<Map<String, String>> messages,
required String systemPrompt,
}) async {
try {
// 在方法内直接转换
final convertedMessages =
messages.map((m) => Map<String, dynamic>.from(m)).toList();
// 发送消息并获取回复
final response = await _openAIService.sendMessage(
messages: convertedMessages,
);
return response;
} catch (e) {
printError(info: 'OpenAI发送消息失败: $e');
throw '发送消息失败: $e';
}
}
/// 发送消息并获取流式回复
@override
Stream<String> sendMessageStream({
required List<Map<String, String>> messages,
required String systemPrompt,
}) async* {
try {
// 在方法内直接转换
final convertedMessages =
messages.map((m) => Map<String, dynamic>.from(m)).toList();
// 创建用于接收token的控制器
final localController = StreamController<String>();
// 添加从广播流到本地流的订阅
final subscription = _tokenStreamController.stream.listen((streamEvent) {
switch (streamEvent.type) {
case StreamEventType.token:
if (streamEvent.content != null && !localController.isClosed) {
localController.add(streamEvent.content!);
}
break;
case StreamEventType.complete:
if (!localController.isClosed) {
localController.close();
}
break;
case StreamEventType.error:
if (!localController.isClosed) {
printError(info: '令牌流错误: ${streamEvent.content}');
localController.addError(streamEvent.content ?? '未知错误');
localController.close();
}
break;
}
});
// 当本地控制器关闭时,取消订阅
localController.onCancel = () {
subscription.cancel();
};
// 启动流式消息请求
bool started = false;
try {
started = await _openAIService.sendMessageStream(
messages: convertedMessages,
);
} catch (e) {
printError(info: '启动消息流失败: $e');
if (!localController.isClosed) {
localController.addError('启动消息流失败: $e');
localController.close();
}
throw '启动消息流失败: $e';
}
if (!started) {
printError(info: '无法启动消息流');
if (!localController.isClosed) {
localController.addError('无法启动消息流');
localController.close();
}
throw '无法启动消息流';
}
// 通过yield*将controller的流转发
yield* localController.stream;
} catch (e) {
printError(info: 'OpenAI流式消息处理失败: $e');
throw '流式消息处理失败: $e';
}
}
/// 注册函数
Future<bool> registerFunction(
String name, String description, Map<String, dynamic> parameters) async {
try {
final result = await _openAIService.registerFunction(
name: name,
description: description,
parameters: parameters,
);
if (result) {
printInfo(info: '函数 "$name" 注册成功');
} else {
printError(info: '函数 "$name" 注册失败');
}
return result;
} catch (e) {
printError(info: '注册函数失败: $e');
return false;
}
}
/// 释放资源
void dispose() {
try {
_eventSubscription?.cancel();
_tokenStreamController.close();
printInfo(info: 'OpenAIServiceAdapter资源已释放');
} catch (e) {
printError(info: '释放资源时出错: $e');
}
}
}

4
lib/data/services/speech_impl/azure_asr_service.dart

@ -10,8 +10,8 @@ import '../asr_service.dart';
/// 该服务提供了通过平台通道与 Android 上的 Microsoft Speech SDK 交互的接口 /// 该服务提供了通过平台通道与 Android 上的 Microsoft Speech SDK 交互的接口
class AzureAsrService extends GetxService implements AsrService { class AzureAsrService extends GetxService implements AsrService {
static final AzureAsrService to = Get.put(AzureAsrService()); static final AzureAsrService to = Get.put(AzureAsrService());
static const MethodChannel _channel = MethodChannel('com.deep_voice.azure_asr'); static const MethodChannel _channel = MethodChannel('azure_speech/asr');
static const EventChannel _eventChannel = EventChannel('com.deep_voice.azure_asr_events'); static const EventChannel _eventChannel = EventChannel('azure_speech/asr_events');
bool _isInitialized = false; bool _isInitialized = false;
late final String _subscriptionKey; late final String _subscriptionKey;

2
lib/data/services/speech_impl/azure_tts_service.dart

@ -12,7 +12,7 @@ import '../tts_service.dart';
/// 提供文本转语音功能。 /// 提供文本转语音功能。
class AzureTtsService extends GetxService implements TtsService { class AzureTtsService extends GetxService implements TtsService {
static final AzureTtsService to = Get.put(AzureTtsService()); static final AzureTtsService to = Get.put(AzureTtsService());
static const MethodChannel _channel = MethodChannel('com.deep_voice.azure_tts'); static const MethodChannel _channel = MethodChannel('azure_speech/tts');
bool _isInitialized = false; bool _isInitialized = false;
late final String _subscriptionKey; late final String _subscriptionKey;

15
lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart

@ -267,12 +267,12 @@ class AndroidVoiceInteractionService extends GetxService {
} }
/// 保存聊天记录 /// 保存聊天记录
void _saveChatHistory(String agentId, String userMessage, String assistantMessage, int timestamp) { Future<void> _saveChatHistory(String agentId, String userMessage, String assistantMessage, int timestamp) async {
try { try {
// 获取ChatHistoryService实例 if (userMessage.isEmpty) {
final chatHistoryService = Get.find<ChatHistoryService>(); return;
}
// 创建用户消息和助手消息
final userMsg = Message( final userMsg = Message(
role: 'user', role: 'user',
content: userMessage, content: userMessage,
@ -285,14 +285,17 @@ class AndroidVoiceInteractionService extends GetxService {
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp + 1), // 确保助手消息时间戳晚于用户消息 timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp + 1), // 确保助手消息时间戳晚于用户消息
); );
// 获取ChatHistoryService实例
final chatHistoryService = Get.find<ChatHistoryService>();
// 加载现有历史记录 // 加载现有历史记录
final existingMessages = chatHistoryService.loadHistory(agentId); final existingMessages = await chatHistoryService.loadHistory(agentId);
// 添加新消息 // 添加新消息
existingMessages.addAll([userMsg, assistantMsg]); existingMessages.addAll([userMsg, assistantMsg]);
// 保存更新后的历史记录 // 保存更新后的历史记录
chatHistoryService.saveHistory(agentId, existingMessages); await chatHistoryService.saveHistory(agentId, existingMessages);
} catch (e) { } catch (e) {
Logger.error('保存聊天记录失败: $e'); Logger.error('保存聊天记录失败: $e');
} }

8
lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart

@ -250,7 +250,7 @@ class IosVoiceInteractionService extends GetxService {
); );
// 保存聊天历史 // 保存聊天历史
_saveChatHistory(userMsg, assistantMsg); await _saveChatHistory(userMsg, assistantMsg);
// 发送聊天历史事件 // 发送聊天历史事件
_sendChatHistoryEvent(userMsg.content, assistantMsg.content); _sendChatHistoryEvent(userMsg.content, assistantMsg.content);
@ -424,16 +424,16 @@ class IosVoiceInteractionService extends GetxService {
} }
// 保存聊天历史 // 保存聊天历史
void _saveChatHistory(Message userMsg, Message assistantMsg) { Future<void> _saveChatHistory(Message userMsg, Message assistantMsg) async {
try { try {
// 加载现有历史记录 // 加载现有历史记录
final existingMessages = _chatHistoryService.loadHistory(_agentId); final existingMessages = await _chatHistoryService.loadHistory(_agentId);
// 添加新消息 // 添加新消息
existingMessages.addAll([userMsg, assistantMsg]); existingMessages.addAll([userMsg, assistantMsg]);
// 保存更新后的历史记录 // 保存更新后的历史记录
_chatHistoryService.saveHistory(_agentId, existingMessages); await _chatHistoryService.saveHistory(_agentId, existingMessages);
Logger.info('保存聊天历史记录成功'); Logger.info('保存聊天历史记录成功');
} catch (e) { } catch (e) {

85
lib/data/services/voice_interaction_service.dart

@ -1,10 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../models/events/voice_interaction_event.dart'; import '../models/events/voice_interaction_event.dart';
import '../../core/utils/logger.dart'; import '../../core/utils/logger.dart';
import '../../modules/chat/models/message_model.dart'; import '../../modules/chat/models/message_model.dart';
import '../../routes/app_routes.dart';
import 'chat_history_service.dart'; import 'chat_history_service.dart';
/// 语音交互服务接口 /// 语音交互服务接口
@ -38,7 +40,13 @@ class VoiceInteractionService extends GetxService {
// 配置信息 // 配置信息
late String _azureSpeechKey; late String _azureSpeechKey;
late String _azureSpeechRegion; late String _azureSpeechRegion;
late String _volcanoAiKey; late String _openaiApiKey;
late String _openaiBaseUrl;
late String _openaiModel;
late String _volcanoSpeechAppId;
late String _volcanoSpeechAppToken;
// MCP配置信息
String? _mcpConfig;
// 聊天历史服务 // 聊天历史服务
late final ChatHistoryService _chatHistoryService; late final ChatHistoryService _chatHistoryService;
@ -57,14 +65,30 @@ class VoiceInteractionService extends GetxService {
void _loadConfig() { void _loadConfig() {
_azureSpeechKey = dotenv.env['AZURE_SPEECH_KEY'] ?? ''; _azureSpeechKey = dotenv.env['AZURE_SPEECH_KEY'] ?? '';
_azureSpeechRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? ''; _azureSpeechRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? '';
_volcanoAiKey = dotenv.env['VOLCANO_AI_API_KEY'] ?? ''; _openaiApiKey = dotenv.env['OPENAI_API_KEY'] ?? '';
_openaiBaseUrl = dotenv.env['OPENAI_BASE_URL'] ?? '';
_openaiModel = dotenv.env['OPENAI_MODEL'] ?? '';
_volcanoSpeechAppId = dotenv.env['VOLCANO_SPEECH_APP_ID'] ?? '';
_volcanoSpeechAppToken = dotenv.env['VOLCANO_SPEECH_APP_TOKEN'] ?? '';
if (_azureSpeechKey.isEmpty || _azureSpeechRegion.isEmpty) { if (_azureSpeechKey.isEmpty || _azureSpeechRegion.isEmpty) {
Logger.warning('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION'); Logger.warning('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION');
} }
if (_volcanoAiKey.isEmpty) { // 加载MCP配置
Logger.warning('未找到火山 AI API 密钥。请在 .env 文件中设置 VOLCANO_AI_API_KEY'); _loadMcpConfig();
}
/// 加载MCP配置
void _loadMcpConfig() {
try {
rootBundle.loadString('.mcp.json').then((config) {
_mcpConfig = config;
Logger.info('成功加载MCP配置, 配置内容: $_mcpConfig');
});
} catch (e) {
Logger.warning('加载MCP配置失败: $e');
_mcpConfig = null;
} }
} }
@ -102,18 +126,34 @@ class VoiceInteractionService extends GetxService {
_eventStreamController.add(chatHistoryEvent); _eventStreamController.add(chatHistoryEvent);
// 保存聊天历史到ChatHistoryService // 保存聊天历史到ChatHistoryService
_saveChatHistory(agentId, userMessage, assistantMessage, DateTime.now().millisecondsSinceEpoch); _saveChatHistory(agentId, userMessage, assistantMessage);
break;
case 'enter_translation_mode':
// 进入翻译模式事件
Logger.info('收到进入翻译模式事件,正在导航到翻译界面');
_navigateToTranslation();
break; break;
} }
} }
/// 导航到翻译界面
void _navigateToTranslation() {
try {
// 使用GetX导航到翻译页面
Get.offNamedUntil(Routes.translation, (route) => route.settings.name == Routes.home);
Logger.info('已导航到翻译界面');
} catch (e) {
Logger.error('导航到翻译界面失败: $e');
}
}
/// 保存聊天历史 /// 保存聊天历史
void _saveChatHistory(String agentId, String userMessage, String assistantMessage, int timestamp) { Future<void> _saveChatHistory(String agentId, String userMessage, String assistantMessage) async {
try { try {
// 检查参数有效性 final timestamp = DateTime.now().millisecondsSinceEpoch;
if (userMessage.isEmpty) {
return;
}
// 创建用户消息和助手消息 // 创建用户消息和助手消息
final userMsg = Message( final userMsg = Message(
@ -129,13 +169,13 @@ class VoiceInteractionService extends GetxService {
); );
// 加载现有历史记录 // 加载现有历史记录
final existingMessages = _chatHistoryService.loadHistory(agentId); final existingMessages = await _chatHistoryService.loadHistory(agentId);
// 添加新消息 // 添加新消息
existingMessages.addAll([userMsg, assistantMsg]); existingMessages.addAll([userMsg, assistantMsg]);
// 保存更新后的历史记录 // 保存更新后的历史记录
_chatHistoryService.saveHistory(agentId, existingMessages); await _chatHistoryService.saveHistory(agentId, existingMessages);
Logger.info('已保存聊天历史: agentId=$agentId'); Logger.info('已保存聊天历史: agentId=$agentId');
} catch (e) { } catch (e) {
@ -191,12 +231,23 @@ class VoiceInteractionService extends GetxService {
try { try {
Logger.info('启动语音交互服务...'); Logger.info('启动语音交互服务...');
// 使用已加载的配置信息 // 构建参数
final result = await _channel.invokeMethod<bool>('startService', { final Map<String, dynamic> params = {
'azure_speech_key': _azureSpeechKey, 'azure_speech_key': _azureSpeechKey,
'azure_speech_region': _azureSpeechRegion, 'azure_speech_region': _azureSpeechRegion,
'volcano_ai_api_key': _volcanoAiKey, 'openai_api_key': _openaiApiKey,
}) ?? false; 'openai_base_url': _openaiBaseUrl,
'openai_model': _openaiModel,
'volcano_speech_app_id': _volcanoSpeechAppId,
'volcano_speech_app_token': _volcanoSpeechAppToken,
};
// 如果有MCP配置,添加到参数中
if (_mcpConfig != null) {
params['mcp_config'] = _mcpConfig;
}
final result = await _channel.invokeMethod<bool>('startService', params) ?? false;
if (result) { if (result) {
_isServiceRunning.value = true; _isServiceRunning.value = true;

9
lib/modules/chat/bindings/chat_binding.dart

@ -1,17 +1,10 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controllers/chat_controller.dart'; import '../controllers/chat_controller.dart';
import '../../../data/services/speech_impl/azure_asr_service.dart';
import '../../../data/services/speech_impl/azure_tts_service.dart';
import '../../../data/services/asr_service.dart';
import '../../../data/services/tts_service.dart';
import '../../../data/services/volcano_ai_service.dart';
import '../../../data/services/chat_history_service.dart';
class ChatBinding extends Bindings { class ChatBinding extends Bindings {
@override @override
void dependencies() { void dependencies() {
// 注册控制器 // 注册控制器
Get.lazyPut<ChatController>(() => ChatController()); Get.lazyPut<ChatController>(() => ChatController());
} }
} }

368
lib/modules/chat/controllers/chat_controller.dart

@ -2,27 +2,27 @@ import 'dart:async';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:just_audio/just_audio.dart';
import 'package:chat_storage/chat_storage.dart';
import '../../../data/services/ai_service.dart'; import '../../../data/services/ai_service.dart';
import '../../../data/services/deapsound_ai_service.dart'; import '../../../data/services/deapsound_ai_service.dart';
import '../../../data/services/kouzi_ai_service.dart'; import '../../../data/services/kouzi_ai_service.dart';
import '../../../data/services/speech_impl/voice_clone_tts_service.dart'; import '../../../data/services/speech_impl/voice_clone_tts_service.dart';
import '../models/message_model.dart'; import '../models/message_model.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import '../../../data/services/volcano_ai_service.dart';
import '../../../data/services/tts_service.dart'; import '../../../data/services/tts_service.dart';
import '../../../data/providers/agent_provider.dart'; import '../../../data/providers/agent_provider.dart';
import '../../../data/models/agent.dart'; import '../../../data/models/agent.dart';
import '../../../data/services/asr_service.dart'; import '../../../data/services/asr_service.dart';
import '../../../data/services/chat_history_service.dart';
import '../../../data/models/events/voice_interaction_event.dart'; import '../../../data/models/events/voice_interaction_event.dart';
import '../../../data/services/voice_interaction_service.dart'; import '../../../data/services/voice_interaction_service.dart';
import '../../../data/services/open_ai_service_adapter.dart';
class ChatController extends GetxController { class ChatController extends GetxController {
// 服务 // 服务
late AiService _aiService; late AiService _aiService;
late TtsService _ttsService; late TtsService _ttsService;
final AsrService _asrService = Get.find<AsrService>(); final AsrService _asrService = Get.find<AsrService>();
final ChatHistoryService _historyService = Get.find<ChatHistoryService>();
final VoiceInteractionService _voiceInteractionService = final VoiceInteractionService _voiceInteractionService =
Get.find<VoiceInteractionService>(); Get.find<VoiceInteractionService>();
@ -44,6 +44,13 @@ class ChatController extends GetxController {
final RxBool isTtsEnabled = true.obs; final RxBool isTtsEnabled = true.obs;
final RxBool isVoiceRecognitionActive = false.obs; final RxBool isVoiceRecognitionActive = false.obs;
// 分页加载状态
final RxInt _currentPage = 1.obs;
final RxInt _totalPages = 1.obs;
final RxBool _isLoadingMore = false.obs;
final RxBool _hasMoreHistory = true.obs;
static const int _pageSize = 20; // 每页加载的消息数量
// 语音交互模式 // 语音交互模式
bool _isVoiceInteractionMode = false; bool _isVoiceInteractionMode = false;
@ -52,9 +59,6 @@ class ChatController extends GetxController {
final RxBool isListening = false.obs; final RxBool isListening = false.obs;
final RxBool isSpeechDetected = false.obs; final RxBool isSpeechDetected = false.obs;
// 临时消息索引,用于更新临时消息
int _tempMessageIndex = -1;
// 防抖计时器 // 防抖计时器
Timer? _scrollDebounceTimer; Timer? _scrollDebounceTimer;
@ -72,6 +76,10 @@ class ChatController extends GetxController {
// 记录最后一次活动时间 // 记录最后一次活动时间
DateTime _lastActivityTime = DateTime.now(); DateTime _lastActivityTime = DateTime.now();
final player = AudioPlayer();
final RxString audiourl = ''.obs;
final RxBool isPlay = false.obs;
@override @override
Future<void> onInit() async { Future<void> onInit() async {
super.onInit(); super.onInit();
@ -92,6 +100,7 @@ class ChatController extends GetxController {
agent = foundAgent; agent = foundAgent;
// 根据Agent ID选择不同的AI服务
switch (agent.id) { switch (agent.id) {
case 'cyber_girlfriend': case 'cyber_girlfriend':
// 亲子陪伴 // 亲子陪伴
@ -104,7 +113,9 @@ class ChatController extends GetxController {
_aiService = Get.find<DeapsoundAIService>(); _aiService = Get.find<DeapsoundAIService>();
break; break;
default: default:
_aiService = Get.find<VolcanoAIService>(); // 默认使用OpenAIServiceAdapter
_aiService = Get.find<OpenAIServiceAdapter>();
Logger.info('使用OpenAIServiceAdapter');
} }
// 使用克隆音色语音合成 // 使用克隆音色语音合成
@ -126,8 +137,11 @@ class ChatController extends GetxController {
// 设置TTS服务 // 设置TTS服务
await _ttsService.setVoice(agent.voice); await _ttsService.setVoice(agent.voice);
// 加载聊天历史 // 添加滚动监听器,用于触发加载更多
_loadChatHistory(); scrollController.addListener(_scrollListener);
// 加载聊天历史 (加载第一页)
await _loadChatHistory();
// 如果没有历史记录,添加欢迎消息 // 如果没有历史记录,添加欢迎消息
if (messages.isEmpty && agent.welcomeMessage.isNotEmpty) { if (messages.isEmpty && agent.welcomeMessage.isNotEmpty) {
@ -152,6 +166,14 @@ class ChatController extends GetxController {
// 订阅VoiceInteractionService事件 // 订阅VoiceInteractionService事件
_subscribeToVoiceInteractionEvents(); _subscribeToVoiceInteractionEvents();
player.playerStateStream.listen((state) async {
if (state.processingState == ProcessingState.completed) {
await player.seek(Duration.zero);
player.pause();
isPlay.value = false;
}
});
// 使用微任务在布局完成后自动滚动到底部 // 使用微任务在布局完成后自动滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom(); _scrollToBottom();
@ -203,7 +225,9 @@ class ChatController extends GetxController {
messages.add(userMsg); messages.add(userMsg);
messages.add(assistantMsg); messages.add(assistantMsg);
} }
_scrollToBottom(); WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom();
});
} }
/// 停止当前交互 /// 停止当前交互
@ -229,6 +253,7 @@ class ChatController extends GetxController {
Logger.info('ChatController onClose'); Logger.info('ChatController onClose');
// 清理资源 // 清理资源
scrollController.removeListener(_scrollListener); // 移除监听器
scrollController.dispose(); scrollController.dispose();
_aiResponseSubscription?.cancel(); _aiResponseSubscription?.cancel();
_recognitionSubscription?.cancel(); _recognitionSubscription?.cancel();
@ -245,9 +270,11 @@ class ChatController extends GetxController {
// 保存当前聊天记录 // 保存当前聊天记录
if (agent.id != null) { if (agent.id != null) {
_historyService.saveHistory(agent.id!, messages.toList()); // _historyService.saveHistory(agent.id!, messages.toList());
} }
player.dispose();
super.onClose(); super.onClose();
} }
@ -279,11 +306,12 @@ class ChatController extends GetxController {
_aiResponseSubscription!.cancel(); _aiResponseSubscription!.cancel();
_aiResponseSubscription = null; _aiResponseSubscription = null;
// 将加载中的消息转换为正常消息,保留已输出的内容 // 将AI中间消息转换为正常消息,保留已输出的内容
final index = messages.indexWhere((m) => m.isLoading); final index =
messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant');
if (index != -1) { if (index != -1) {
messages[index] = messages[index].copyWith( messages[index] = messages[index].copyWith(
isLoading: false, isIntermediate: false,
); );
messages.refresh(); messages.refresh();
} }
@ -313,10 +341,10 @@ class ChatController extends GetxController {
// 清空输入框 // 清空输入框
inputText.value = ''; inputText.value = '';
// 添加加载中的消息 // 添加中间状态的AI消息
final loadingMessage = Message.loading(); final intermediateMessage = Message.intermediate('assistant');
messages.add(loadingMessage); messages.add(intermediateMessage);
messages.refresh();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom(); _scrollToBottom();
}); });
@ -343,34 +371,38 @@ class ChatController extends GetxController {
(chunk) { (chunk) {
if (_shouldCancelAiResponse) return; if (_shouldCancelAiResponse) return;
fullResponse += chunk; if (chunk is Map) {
_updateIntermediateMessage(data: chunk);
// 更新加载中的消息内容 if (chunk['musicaudio'] != null) {
_updateLoadingMessage(fullResponse); play(chunk['musicaudio']);
}
// 如果TTS启用,使用流式处理发送到TTS } else {
if (isTtsEnabled.value) { fullResponse += chunk;
_ttsService.speakStream(chunk);
// 更新中间状态的消息内容
_updateIntermediateMessage(content: fullResponse);
// 如果TTS启用,使用流式处理发送到TTS
if (isTtsEnabled.value) {
_ttsService.speakStream(chunk);
}
} }
}, },
onDone: () { onDone: () {
if (_shouldCancelAiResponse) return; if (_shouldCancelAiResponse) return;
// 完成响应,将isLoading设为false // 完成响应,将isIntermediate设为false
_finalizeResponse(fullResponse); _finalizeResponse(fullResponse);
// 如果TTS启用,处理缓冲区中的剩余文本 // 如果TTS启用,处理缓冲区中的剩余文本
if (isTtsEnabled.value) { if (isTtsEnabled.value) {
_ttsService.flushStream(); _ttsService.flushStream();
} }
// 保存聊天历史
_saveChatHistory();
}, },
onError: (error) { onError: (error) {
Logger.error('AI响应错误', error); Logger.error('AI响应错误', error);
// 更新加载中的消息为错误消息 // 更新中间状态的消息为错误消息
_handleResponseError(); _handleResponseError();
}, },
); );
@ -386,7 +418,7 @@ class ChatController extends GetxController {
final List<Map<String, String>> messageHistory = []; final List<Map<String, String>> messageHistory = [];
// 只取最近的10条消息作为上下文 // 只取最近的10条消息作为上下文
final recentMessages = messages.where((m) => !m.isLoading).toList(); final recentMessages = messages.where((m) => !m.isIntermediate).toList();
final startIndex = final startIndex =
recentMessages.length > 10 ? recentMessages.length - 10 : 0; recentMessages.length > 10 ? recentMessages.length - 10 : 0;
@ -401,42 +433,94 @@ class ChatController extends GetxController {
return messageHistory; return messageHistory;
} }
// 更新加载中的消息 // 更新中间状态的消息
void _updateLoadingMessage(String content) { void _updateIntermediateMessage({Map? data, String? content}) {
final index = messages.indexWhere((m) => m.isLoading); final index =
messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant');
if (index != -1) { if (index != -1) {
if (data != null) {
Map messagesData = messages[index].data ?? {};
messages[index] = messages[index].copyWith(
data: {...messagesData, ...data},
);
}
messages[index] = messages[index].copyWith( messages[index] = messages[index].copyWith(
content: content, content: content,
isLoading: true,
); );
messages.refresh(); messages.refresh();
_scrollToBottom(); _scrollToBottom();
} }
} }
// 完成响应 // 播放音乐
void play(Map musicaudio) async {
try {
isPlay.value = false;
if (audiourl.value != musicaudio['audiourl']) {
audiourl.value = '';
await player.setUrl(musicaudio['audiourl']);
audiourl.value = musicaudio['audiourl'];
player.play();
} else {
player.play();
}
isPlay.value = true;
} catch (e) {
Logger.error('加载异常: $e');
}
}
// 暂停播放音乐
void pause() async {
await player.pause();
isPlay.value = false;
}
// 更新或添加最终响应
void _finalizeResponse(String content) { void _finalizeResponse(String content) {
final index = messages.indexWhere((m) => m.isLoading); // 查找AI的中间状态消息
final index =
messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant');
if (index != -1) { if (index != -1) {
// 更新中间状态消息为最终状态
messages[index] = messages[index].copyWith( messages[index] = messages[index].copyWith(
content: content, content: content,
isLoading: false, isIntermediate: false,
timestamp: DateTime.now(), // 更新时间戳为最终时间
); );
messages.refresh(); // 保存最终的助手消息到数据库
_scrollToBottom(); _saveMessageToDb(messages[index]);
} else {
// 如果没有中间状态消息,直接添加新消息 (理论上不应发生)
final assistantMsg = Message(
role: 'assistant',
content: content,
timestamp: DateTime.now(),
);
messages.add(assistantMsg);
_saveMessageToDb(assistantMsg); // 保存到数据库
} }
isLoading.value = false;
isSending.value = false; isSending.value = false;
_aiResponseSubscription = null; _aiResponseSubscription = null; // 清理订阅
_shouldCancelAiResponse = false; // 重置取消标志
// 响应完成后,重新启动语音识别的自动停止计时器
if (_isVoiceInteractionMode) {
_startAutoStopTimer();
}
} }
// 处理响应错误 // 处理响应错误
void _handleResponseError() { void _handleResponseError() {
final index = messages.indexWhere((m) => m.isLoading); // 查找AI的中间状态消息
final index =
messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant');
if (index != -1) { if (index != -1) {
messages[index] = messages[index].copyWith( messages[index] = messages[index].copyWith(
content: '抱歉,我遇到了一些问题,无法回应您的请求。', content: '抱歉,我遇到了一些问题,无法回应您的请求。',
isLoading: false, isIntermediate: false,
); );
messages.refresh(); messages.refresh();
} }
@ -469,9 +553,6 @@ class ChatController extends GetxController {
isListening.value = true; isListening.value = true;
recognizedText.value = ''; recognizedText.value = '';
// 重置临时消息索引
_tempMessageIndex = -1;
// 启动自动停止计时器 // 启动自动停止计时器
_startAutoStopTimer(); _startAutoStopTimer();
@ -563,24 +644,24 @@ class ChatController extends GetxController {
_interruptCurrentResponse("检测到用户开始讲话,中断当前响应"); _interruptCurrentResponse("检测到用户开始讲话,中断当前响应");
} }
// 更新或创建临时消息 // 查找或创建用户的中间状态消息
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) { final intermediateIndex =
// 更新已有的临时消息 messages.indexWhere((m) => m.isIntermediate && m.role == 'user');
messages[_tempMessageIndex] = messages[_tempMessageIndex].copyWith(
if (intermediateIndex >= 0) {
// 更新已有的中间状态消息
messages[intermediateIndex] = messages[intermediateIndex].copyWith(
content: text, content: text,
); );
messages.refresh(); messages.refresh();
} else { } else {
// 添加新的临时消息 // 添加新的中间状态消息
final tempMessage = Message( var intermediateMessage = Message.intermediate('user');
role: 'user', intermediateMessage = intermediateMessage.copyWith(content: text);
content: text, messages.add(intermediateMessage);
timestamp: DateTime.now(),
);
messages.add(tempMessage);
_tempMessageIndex = messages.length - 1;
messages.refresh(); messages.refresh();
} }
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom(); _scrollToBottom();
}); });
@ -596,14 +677,13 @@ class ChatController extends GetxController {
// 更新活动时间 // 更新活动时间
_updateActivityTime(); _updateActivityTime();
// 查找并移除临时消息 // 查找并移除用户的中间状态消息
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) { final intermediateIndex =
messages.removeAt(_tempMessageIndex); messages.indexWhere((m) => m.isIntermediate && m.role == 'user');
if (intermediateIndex >= 0) {
messages.removeAt(intermediateIndex);
} }
// 重置临时消息索引
_tempMessageIndex = -1;
// 发送识别到的文本 // 发送识别到的文本
sendMessage(text); sendMessage(text);
@ -630,16 +710,21 @@ class ChatController extends GetxController {
// 清理语音识别资源 // 清理语音识别资源
void _cleanupRecognition() { void _cleanupRecognition() {
// 查找并移除临时消息 // 查找并移除用户的中间状态消息
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) { final intermediateIndex =
if (messages[_tempMessageIndex].content.trim().isEmpty) { messages.indexWhere((m) => m.isIntermediate && m.role == 'user');
messages.removeAt(_tempMessageIndex); if (intermediateIndex >= 0) {
if (messages[intermediateIndex].content.trim().isEmpty) {
messages.removeAt(intermediateIndex);
} else {
// 如果中间状态消息有内容,将其转换为正式消息
messages[intermediateIndex] = messages[intermediateIndex].copyWith(
isIntermediate: false,
);
} }
messages.refresh();
} }
// 重置临时消息索引
_tempMessageIndex = -1;
// 停止语音识别 // 停止语音识别
isVoiceRecognitionActive.value = false; isVoiceRecognitionActive.value = false;
isListening.value = false; isListening.value = false;
@ -661,15 +746,21 @@ class ChatController extends GetxController {
recognizedText.value = ''; recognizedText.value = '';
isSpeechDetected.value = false; isSpeechDetected.value = false;
// 查找并移除临时消息 // 查找并处理用户的中间状态消息
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) { final intermediateIndex =
if (messages[_tempMessageIndex].content.trim().isEmpty) { messages.indexWhere((m) => m.isIntermediate && m.role == 'user');
messages.removeAt(_tempMessageIndex); if (intermediateIndex >= 0) {
if (messages[intermediateIndex].content.trim().isEmpty) {
// 如果没有内容则移除
messages.removeAt(intermediateIndex);
} else {
// 有内容则转为正式消息
messages[intermediateIndex] = messages[intermediateIndex].copyWith(
isIntermediate: false,
);
} }
messages.refresh();
} }
// 重置临时消息索引
_tempMessageIndex = -1;
} catch (e) { } catch (e) {
Logger.error('停止语音识别失败', e); Logger.error('停止语音识别失败', e);
} }
@ -678,7 +769,7 @@ class ChatController extends GetxController {
// 清空聊天记录 // 清空聊天记录
void clearChat() { void clearChat() {
messages.clear(); messages.clear();
_ttsService.stop();
// 添加欢迎消息 // 添加欢迎消息
if (agent.welcomeMessage.isNotEmpty) { if (agent.welcomeMessage.isNotEmpty) {
messages.add(Message( messages.add(Message(
@ -687,31 +778,118 @@ class ChatController extends GetxController {
timestamp: DateTime.now(), timestamp: DateTime.now(),
)); ));
} }
// 删除数据库中的历史记录
ChatStorage.deleteMessages(sessionId: agent.id).catchError((e) {
Logger.error('清空数据库聊天记录失败: $e');
});
}
// 删除存储的历史记录 // 滚动监听,用于触发加载更多历史记录
_historyService.clearHistory(agent.id); void _scrollListener() {
// 当滚动到顶部并且有更多历史记录且不在加载中时,加载更多
if (scrollController.position.pixels ==
scrollController.position.minScrollExtent &&
_hasMoreHistory.value &&
!_isLoadingMore.value) {
_loadMoreHistory();
}
} }
// 加载聊天历史 // 加载聊天历史 (首次加载或刷新)
void _loadChatHistory() { Future<void> _loadChatHistory() async {
// 从历史服务加载 isLoading.value = true;
final history = _historyService.loadHistory(agent.id); _currentPage.value = 1; // 重置为第一页
if (history.isNotEmpty) { _hasMoreHistory.value = true; // 假设有更多
messages.clear();
messages.addAll(history); try {
final pagedResult = await ChatStorage.getMessages(
sessionId: agent.id,
page: _currentPage.value,
pageSize: _pageSize,
);
_totalPages.value = pagedResult.totalPages;
_hasMoreHistory.value = _currentPage.value < _totalPages.value;
final historyMessages = pagedResult.messages.map((chatMsg) {
return Message(
role: chatMsg.sender,
content: chatMsg.message,
timestamp: DateTime.parse(chatMsg.timestamp),
);
}).toList();
messages.assignAll(historyMessages.reversed); // 最新的在底部,所以要反转
// 滚动到底部 (如果是首次加载)
WidgetsBinding.instance.addPostFrameCallback((_) {
if (scrollController.hasClients) {
scrollController.jumpTo(scrollController.position.maxScrollExtent);
}
});
} catch (e) {
Logger.error('加载聊天历史失败', e);
} finally {
isLoading.value = false;
} }
} }
// 保存聊天历史 // 加载更多历史记录
void _saveChatHistory() { Future<void> _loadMoreHistory() async {
_historyService.saveHistory(agent.id, messages); if (_isLoadingMore.value || !_hasMoreHistory.value) return;
_isLoadingMore.value = true;
_currentPage.value++;
try {
final pagedResult = await ChatStorage.getMessages(
sessionId: agent.id,
page: _currentPage.value,
pageSize: _pageSize,
);
_totalPages.value = pagedResult.totalPages;
_hasMoreHistory.value = _currentPage.value < _totalPages.value;
final olderMessages = pagedResult.messages.map((chatMsg) {
return Message(
role: chatMsg.sender,
content: chatMsg.message,
timestamp: DateTime.parse(chatMsg.timestamp),
);
}).toList();
// 将旧消息插入到列表顶部
messages.insertAll(0, olderMessages.reversed);
} catch (e) {
Logger.error('加载更多聊天历史失败', e);
_currentPage.value--; // 加载失败,恢复页码
} finally {
_isLoadingMore.value = false;
}
}
// 保存单条聊天消息到数据库
Future<void> _saveMessageToDb(Message message) async {
if (message.isIntermediate) return; // 不保存中间状态的消息
try {
await ChatStorage.saveMessage(
sessionId: agent.id,
message: message.content,
sender: message.role,
);
} catch (e) {
Logger.error('保存消息到数据库失败', e);
}
} }
// 添加消息,供外部调用 // 添加消息,供外部调用 (如用户发送消息、AI回复)
void addMessage(Message message) { void addMessage(Message message) {
messages.add(message); messages.add(message);
_saveChatHistory(); _saveMessageToDb(message); // 保存到数据库
_scrollToBottom(); WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom();
});
} }
void _stopVoiceRecognitionAutomatically() { void _stopVoiceRecognitionAutomatically() {

28
lib/modules/chat/models/message_model.dart

@ -1,14 +1,16 @@
class Message { class Message {
final String role; // 'user' or 'assistant' final String role; // 'user' or 'assistant'
final String content; final String content;
final Map? data;
final DateTime timestamp; final DateTime timestamp;
final bool isLoading; final bool isIntermediate; // 中间状态标记,替代isLoading
Message({ Message({
required this.role, required this.role,
required this.content, required this.content,
required this.timestamp, required this.timestamp,
this.isLoading = false, this.data,
this.isIntermediate = false, // 默认不是中间状态
}); });
// 从JSON构造函数 // 从JSON构造函数
@ -16,8 +18,9 @@ class Message {
return Message( return Message(
role: json['role'] as String, role: json['role'] as String,
content: json['content'] as String, content: json['content'] as String,
data: json['data'],
timestamp: DateTime.parse(json['timestamp'] as String), timestamp: DateTime.parse(json['timestamp'] as String),
isLoading: json['isLoading'] as bool? ?? false, isIntermediate: json['isIntermediate'] as bool? ?? false,
); );
} }
@ -26,18 +29,19 @@ class Message {
return { return {
'role': role, 'role': role,
'content': content, 'content': content,
'data': data,
'timestamp': timestamp.toIso8601String(), 'timestamp': timestamp.toIso8601String(),
'isLoading': isLoading, 'isIntermediate': isIntermediate,
}; };
} }
// 创建一个加载中的消息 // 创建一个中间状态的消息
factory Message.loading() { factory Message.intermediate(String role) {
return Message( return Message(
role: 'assistant', role: role,
content: '', content: '',
timestamp: DateTime.now(), timestamp: DateTime.now(),
isLoading: true, isIntermediate: true,
); );
} }
@ -45,14 +49,16 @@ class Message {
Message copyWith({ Message copyWith({
String? role, String? role,
String? content, String? content,
Map? data,
DateTime? timestamp, DateTime? timestamp,
bool? isLoading, bool? isIntermediate,
}) { }) {
return Message( return Message(
role: role ?? this.role, role: role ?? this.role,
content: content ?? this.content, content: content ?? this.content,
data: data ?? this.data,
timestamp: timestamp ?? this.timestamp, timestamp: timestamp ?? this.timestamp,
isLoading: isLoading ?? this.isLoading, isIntermediate: isIntermediate ?? this.isIntermediate,
); );
} }
} }

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

@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../controllers/chat_controller.dart';
class ChatMusicCard extends GetView<ChatController> {
final Map? data;
final bool isDarkMode;
const ChatMusicCard(this.data, this.isDarkMode, {super.key});
@override
Widget build(BuildContext context) {
final Map musiccards = data?['musiccards'].first;
final Map musicaudio = data?['musicaudio'];
return Row(
children: [
Container(
width: 60.w,
height: 60.w,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.r),
),
child: Image.network(
musiccards['image'],
fit: BoxFit.cover,
),
),
10.horizontalSpace,
Expanded(
child: Text(
musiccards['name'],
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 16.sp,
height: 1.5,
),
),
),
10.horizontalSpace,
Obx(
() => controller.audiourl == musicaudio['audiourl'] &&
controller.isPlay.value
? GestureDetector(
onTap: () {
controller.pause();
},
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,
),
],
),
)
: GestureDetector(
onTap: () {
controller.play(musicaudio);
},
child: Container(
width: 38.w,
height: 38.w,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white : Colors.black,
shape: BoxShape.circle,
),
child: Icon(
Icons.play_arrow_rounded,
color: isDarkMode ? Colors.black : Colors.white,
),
),
),
),
],
);
}
}

206
lib/modules/chat/views/chat_navigation_card.dart

@ -0,0 +1,206 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'dart:math' as math;
import 'package:amap_flutter/amap_flutter.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class ChatNavigationCard extends StatefulWidget {
final Map? data;
final bool isDarkMode;
const ChatNavigationCard(this.data, this.isDarkMode, {super.key});
@override
State<ChatNavigationCard> createState() => _ChatNavigationCardState();
}
class _ChatNavigationCardState extends State<ChatNavigationCard> {
final String apiKey = '8bde023b7e5e00f321ea17929f10fbae';
LatLng startPoint = LatLng(116.397428, 39.90923); // 起点坐标
LatLng endPoint = LatLng(116.397026, 39.903303); // 终点坐标
final _amapFlutter = AmapFlutter();
StreamSubscription? _locationStream;
String _mapUrl = '';
@override
void initState() {
super.initState();
_getLocation();
}
@override
void dispose() {
_amapFlutter.dispose();
_locationStream?.cancel();
super.dispose();
}
void _getLocation() async {
List endPointList = widget.data?['navigationcard']['location'].split(',');
endPoint = LatLng(
double.parse(endPointList[0]),
double.parse(endPointList[1]),
);
await _amapFlutter.initLocation();
_locationStream = _amapFlutter.locationStream().listen((data) async {
startPoint = LatLng(
data['longitude'],
data['latitude'],
);
_mapUrl = await _getMapUrl();
_amapFlutter.stopLocation();
setState(() {});
});
_amapFlutter.startLocation();
}
Future<String> _getMapUrl() async {
// 1. 获取路径数据
final route = await _fetchRouteData();
// 2. 解析并简化路径
List<LatLng> simplified = _simplifyPath(route, epsilon: 0.0001);
// 3. 计算地图参数
final zoom = _calculateAutoZoom(simplified);
// 4. 生成静态地图URL
return _buildStaticMapUrl(simplified, zoom);
}
Future<List<LatLng>> _fetchRouteData() async {
final url = 'https://restapi.amap.com/v3/direction/driving?'
'origin=${startPoint.lng},${startPoint.lat}&'
'destination=${endPoint.lng},${endPoint.lat}&'
'key=$apiKey';
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final data = json.decode(response.body);
return _parsePolyline(data['route']['paths'][0]['steps']);
}
throw Exception('Failed to load route');
}
List<LatLng> _parsePolyline(List<dynamic> steps) {
final List<LatLng> path = [];
for (final step in steps) {
final points = (step['polyline'] as String).split(';');
for (final point in points) {
final coords = point.split(',').map(double.parse).toList();
path.add(LatLng(coords[0], coords[1]));
}
}
return path;
}
List<LatLng> _simplifyPath(List<LatLng> points, {double epsilon = 0.0001}) {
return douglasPeucker(points, epsilon);
}
int _calculateAutoZoom(List<LatLng> points,
{int mapWidth = 1024, int mapHeight = 512}) {
if (points.isEmpty) return 15; // 默认缩放级别
// 1. 计算坐标范围
double minLng = double.infinity;
double maxLng = -double.infinity;
double minLat = double.infinity;
double maxLat = -double.infinity;
for (final p in points) {
final lng = p.lng;
final lat = p.lat;
minLng = math.min(minLng, lng);
maxLng = math.max(maxLng, lng);
minLat = math.min(minLat, lat);
maxLat = math.max(maxLat, lat);
}
// 3. 计算缩放级别(基于Leaflet算法改进)
const worldWidth = 256; // 高德瓦片尺寸
const worldHeight = 256;
const zoomMax = 18; // 高德最大缩放级别
double latRad(double lat) {
final sin = math.sin(lat * math.pi / 180);
return math.log((1 + sin) / (1 - sin)) / 2;
}
final latFraction = (latRad(maxLat) - latRad(minLat)) / math.pi;
final lngDiff = maxLng - minLng;
final lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
final latZoom =
math.log((mapHeight / worldHeight) / latFraction) / math.ln2;
final lngZoom = math.log((mapWidth / worldWidth) / lngFraction) / math.ln2;
return math.min(math.min(latZoom, lngZoom).floor(), zoomMax);
}
String _buildStaticMapUrl(List<LatLng> path, int zoom) {
final pathParam = path.map((p) => '${p.lng},${p.lat}').join(';');
return 'https://restapi.amap.com/v3/staticmap?'
'zoom=$zoom&'
'size=1024*512&'
'paths=10,0x0000ff,1,,:$pathParam&'
'markers=mid,,A:${startPoint.lng},${startPoint.lat}|'
'mid,,B:${endPoint.lng},${endPoint.lat}&'
'key=$apiKey';
}
List<LatLng> douglasPeucker(List<LatLng> points, double epsilon) {
if (points.length < 3) return points;
double maxDistance = 0;
int index = 0;
for (int i = 1; i < points.length - 1; i++) {
double d = _perpendicularDistance(points[i], points.first, points.last);
if (d > maxDistance) {
index = i;
maxDistance = d;
}
}
if (maxDistance > epsilon) {
return [
...douglasPeucker(points.sublist(0, index + 1), epsilon),
...douglasPeucker(points.sublist(index), epsilon).skip(1)
];
}
return [points.first, points.last];
}
double _perpendicularDistance(LatLng point, LatLng start, LatLng end) {
final area = (end.lng - start.lng) * (point.lat - start.lat) -
(end.lat - start.lat) * (point.lng - start.lng);
final length =
sqrt(pow(end.lng - start.lng, 2) + pow(end.lat - start.lat, 2));
return area.abs() / length;
}
@override
Widget build(BuildContext context) {
return Column(
children: [
_mapUrl.isNotEmpty
? Image.network(_mapUrl)
: const CircularProgressIndicator(),
],
);
}
}
class LatLng {
final double lat;
final double lng;
LatLng(this.lng, this.lat); // 高德使用经度在前纬度在后的格式
}

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

@ -5,6 +5,9 @@ import 'dart:math';
import '../controllers/chat_controller.dart'; import '../controllers/chat_controller.dart';
import '../models/message_model.dart'; import '../models/message_model.dart';
import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_colors.dart';
import 'chat_music_card.dart';
import 'chat_navigation_card.dart';
import 'weather_day_card.dart';
class ChatView extends GetView<ChatController> { class ChatView extends GetView<ChatController> {
const ChatView({Key? key}) : super(key: key); const ChatView({Key? key}) : super(key: key);
@ -297,22 +300,10 @@ class ChatView extends GetView<ChatController> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 消息内容 // 消息内容
if (message.content.isNotEmpty) _buildMessageContent(message, isDarkMode, isUser),
SelectableText(
'${message.content}'.tr,
style: TextStyle(
color: isUser
? Colors.white
: isDarkMode
? Colors.white
: Colors.black,
fontSize: 16.sp,
height: 1.5,
),
),
// 加载中动画 // 加载中动画
if (message.isLoading) if (message.isIntermediate)
Padding( Padding(
padding: EdgeInsets.only(top: 8.h), padding: EdgeInsets.only(top: 8.h),
child: Row( child: Row(
@ -348,6 +339,36 @@ class ChatView extends GetView<ChatController> {
); );
} }
Widget _buildMessageContent(Message message, bool isDarkMode, bool isUser) {
Widget box = const SizedBox();
if (message.content.isNotEmpty) {
box = SelectableText(
message.content.tr,
style: TextStyle(
color: isUser
? Colors.white
: isDarkMode
? Colors.white
: Colors.black,
fontSize: 16.sp,
height: 1.5,
),
);
}
if (message.data != null) {
if (message.data?['weatherDays'] != null) {
box = WeatherDayCard(message.data, isDarkMode);
}
if (message.data?['musicaudio'] != null) {
box = ChatMusicCard(message.data, isDarkMode);
}
if (message.data?['navigationcard'] != null) {
box = ChatNavigationCard(message.data, isDarkMode);
}
}
return box;
}
// 构建输入区域 // 构建输入区域
Widget _buildInputArea(bool isDarkMode) { Widget _buildInputArea(bool isDarkMode) {
final textController = final textController =
@ -715,21 +736,22 @@ class ChatView extends GetView<ChatController> {
); );
} }
// 格式化时间 // 格式化时间
String _formatTime(DateTime time) { String _formatTime(DateTime time) {
final now = DateTime.now(); final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day); final today = DateTime(now.year, now.month, now.day);
//final yesterday = today.subtract(const Duration(days: 1)); //final yesterday = today.subtract(const Duration(days: 1));
final messageDate = DateTime(time.year, time.month, time.day); final messageDate = DateTime(time.year, time.month, time.day);
String timeStr = '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; String timeStr =
'${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
if (messageDate == today) { if (messageDate == today) {
return timeStr; // 只显示时间,如 "14:30" return timeStr; // 只显示时间,如 "14:30"
} }
// else if (messageDate == yesterday) { // else if (messageDate == yesterday) {
// return 'Yesterday $timeStr'; // 如 "Yesterday 14:30" // return 'Yesterday $timeStr'; // 如 "Yesterday 14:30"
// } // }
else { else {
// 国际通用日期格式 "MM-dd HH:mm" // 国际通用日期格式 "MM-dd HH:mm"
return '${time.month.toString().padLeft(2, '0')}-${time.day.toString().padLeft(2, '0')} $timeStr'; return '${time.month.toString().padLeft(2, '0')}-${time.day.toString().padLeft(2, '0')} $timeStr';

94
lib/modules/chat/views/weather_day_card.dart

@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class WeatherDayCard extends StatelessWidget {
final Map? data;
final bool isDarkMode;
const WeatherDayCard(this.data, this.isDarkMode, {super.key});
@override
Widget build(BuildContext context) {
final Map weatherDays = data?['weatherDays'].first;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
weatherDays['date'],
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 10.sp,
),
),
Text(
'${weatherDays['maxTemperature']}°',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 30.sp,
),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Icon(
Icons.sunny,
color: isDarkMode ? Colors.white : Colors.amber[600]!,
size: 18.w,
),
Text(
'${weatherDays['weatherCondition']},${weatherDays['windDirection']}',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 14.sp,
),
),
Row(
children: [
Text(
'最\n高',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 10.sp,
height: 1,
),
),
5.horizontalSpace,
Text(
'${weatherDays['maxTemperature']}°',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 20.sp,
),
),
10.horizontalSpace,
Text(
'最\n低',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 10.sp,
height: 1,
),
),
5.horizontalSpace,
Text(
'${weatherDays['minTemperature']}°',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 20.sp,
),
),
],
),
],
),
],
);
}
}

9
lib/modules/feedback/bindings/feedback_bindings.dart

@ -0,0 +1,9 @@
import 'package:get/get.dart';
import '../controllers/feedback_controller.dart';
class FeedbackBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<FeedbackController>(() => FeedbackController());
}
}

59
lib/modules/feedback/controllers/feedback_controller.dart

@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../data/services/network/api.dart';
class FeedbackController extends GetxController {
final TextEditingController feedbackController = TextEditingController();
//final TextEditingController contactController = TextEditingController();
final RxBool isSubmitting = false.obs;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
@override
void onClose() {
feedbackController.dispose();
//contactController.dispose();
super.onClose();
}
// 提交反馈
Future<void> submitFeedback() async {
if (!formKey.currentState!.validate()) {
return;
}
isSubmitting.value = true;
try {
final feedbackText = feedbackController.text;
Map<String, dynamic> data = {
"message": feedbackText, // 反馈内容
};
final response = await Api.feedback(data);
if (response != null) {
Get.back(); // 返回上一页
Get.snackbar(
'success'.tr, // 成功
'feedbackSubmitSuccess'.tr, // 反馈提交成功
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green.withOpacity(0.7),
colorText: Colors.white,
margin: const EdgeInsets.all(10),
);
}
} catch (e) {
Get.snackbar(
'error'.tr, // 错误
'feedbackSubmitFailed'.tr, // 反馈提交失败
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red.withOpacity(0.7),
colorText: Colors.white,
margin: const EdgeInsets.all(10),
);
} finally {
isSubmitting.value = false;
}
}
}

203
lib/modules/feedback/views/feedback_view.dart

@ -0,0 +1,203 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'dart:ui';
import '../controllers/feedback_controller.dart';
class FeedbackView extends GetView<FeedbackController> {
const FeedbackView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[100],
appBar: AppBar(
elevation: 0,
backgroundColor: isDarkMode ? Colors.black : Colors.white,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
size: 20.sp,
color: isDarkMode ? Colors.white : Colors.black,
),
onPressed: () => Get.back(),
),
title: Text(
'feedback'.tr, // 帮助与反馈
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
centerTitle: true,
),
body: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Form(
key: controller.formKey,
child: ListView(
padding: EdgeInsets.all(16.w),
children: [
// 顶部提示文字
Text(
'feedbackDescription'.tr, // 请描述您遇到的问题或建议
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
),
SizedBox(height: 16.h),
// 反馈内容输入框
Container(
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withOpacity(0.05)
: Colors.white,
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(16.r),
child: BackdropFilter(
filter: isDarkMode
? ImageFilter.blur(sigmaX: 5, sigmaY: 5)
: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
child: TextFormField(
controller: controller.feedbackController,
maxLines: 8,
maxLength: 500,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
),
decoration: InputDecoration(
hintText: 'feedbackHint'.tr, // 请输入您的反馈内容...
hintStyle: TextStyle(
color: isDarkMode ? Colors.white38 : Colors.black38,
),
border: InputBorder.none,
counterStyle: TextStyle(
color: isDarkMode ? Colors.white54 : Colors.black54,
),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'feedbackRequired'.tr; // 请输入反馈内容
}
return null;
},
),
),
),
),
),
SizedBox(height: 24.h),
// 联系方式标题
// Text(
// 'contactInfo'.tr, // 联系方式
// style: TextStyle(
// fontSize: 14.sp,
// color: isDarkMode ? Colors.white70 : Colors.black54,
// ),
// ),
// SizedBox(height: 16.h),
// // 联系方式输入框
// Container(
// decoration: BoxDecoration(
// color: isDarkMode
// ? Colors.white.withOpacity(0.05)
// : Colors.white,
// borderRadius: BorderRadius.circular(16.r),
// boxShadow: [
// BoxShadow(
// color: isDarkMode
// ? Colors.black.withOpacity(0.2)
// : Colors.black.withOpacity(0.05),
// blurRadius: 10,
// offset: const Offset(0, 2),
// ),
// ],
// ),
// child: ClipRRect(
// borderRadius: BorderRadius.circular(16.r),
// child: BackdropFilter(
// filter: isDarkMode
// ? ImageFilter.blur(sigmaX: 5, sigmaY: 5)
// : ImageFilter.blur(sigmaX: 0, sigmaY: 0),
// child: Padding(
// padding:
// EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
// child: TextFormField(
// controller: controller.contactController,
// style: TextStyle(
// color: isDarkMode ? Colors.white : Colors.black87,
// ),
// decoration: InputDecoration(
// hintText: 'contactHint'.tr, // 邮箱/手机号,方便我们联系您
// hintStyle: TextStyle(
// color: isDarkMode ? Colors.white38 : Colors.black38,
// ),
// border: InputBorder.none,
// ),
// ),
// ),
// ),
// ),
// ),
// SizedBox(height: 40.h),
// 提交按钮
Obx(() => ElevatedButton(
onPressed: controller.isSubmitting.value
? null
: controller.submitFeedback,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue[600],
padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
elevation: 0,
),
child: controller.isSubmitting.value
? SizedBox(
height: 20.h,
width: 20.h,
child: CircularProgressIndicator(
strokeWidth: 2.w,
valueColor: const AlwaysStoppedAnimation<Color>(
Colors.white),
),
)
: Text(
'submit'.tr, // 提交
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
)),
],
),
),
),
);
}
}

11
lib/modules/jieli_test/bindings/jieli_test_binding.dart

@ -0,0 +1,11 @@
import 'package:get/get.dart';
import '../controllers/jieli_test_controller.dart';
class JieliTestBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<JieliTestController>(
() => JieliTestController(),
);
}
}

323
lib/modules/jieli_test/controllers/jieli_test_controller.dart

@ -0,0 +1,323 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart' hide ConnectionState;
import 'package:get/get.dart';
import 'package:jieli/jieli.dart';
/// 杰理测试控制器
class JieliTestController extends GetxController {
// Jieli插件实例
late Jieli jieli;
// 状态变量
var isScanning = false.obs;
var deviceInfo = '未获取'.obs;
var connectStatus = '未连接'.obs;
var currentDevice = Rx<BleDevice?>(null);
var scannedDevices = RxList<BleDevice>([]);
var wakeupReceived = false.obs;
var audioDataSize = 0.obs;
var receivedFrames = 0.obs;
var audioDataReceiving = false.obs;
var volumeCommandResult = ''.obs;
// 事件订阅
StreamSubscription? _scanSubscription;
StreamSubscription? _connectionSubscription;
StreamSubscription? _wakeupSubscription;
StreamSubscription? _audioSubscription;
StreamSubscription? _commandResultSubscription;
@override
void onInit() {
super.onInit();
_initializeJieli();
}
@override
void onClose() {
_cancelAllSubscriptions();
_disposeJieli();
super.onClose();
}
/// 取消所有订阅
void _cancelAllSubscriptions() {
_scanSubscription?.cancel();
_connectionSubscription?.cancel();
_wakeupSubscription?.cancel();
_audioSubscription?.cancel();
_commandResultSubscription?.cancel();
}
/// 初始化杰理SDK
void _initializeJieli() {
try {
jieli = Jieli();
_setupEventListeners();
} catch (e) {
// 初始化错误处理
}
}
/// 设置事件监听器
void _setupEventListeners() {
// 监听扫描结果
_scanSubscription = jieli.scanResults.listen((devices) {
scannedDevices.assignAll(devices);
});
// 监听连接状态
_connectionSubscription = jieli.connectionState.listen((state) {
switch (state) {
case ConnectionState.connected:
connectStatus.value = '已连接';
break;
case ConnectionState.disconnected:
connectStatus.value = '已断开';
currentDevice.value = null;
break;
case ConnectionState.connecting:
connectStatus.value = '连接中...';
break;
case ConnectionState.disconnecting:
connectStatus.value = '断开中...';
break;
case ConnectionState.error:
connectStatus.value = '连接错误';
break;
}
});
// 监听唤醒信号
_wakeupSubscription = jieli.wakeupStream.listen((wakeup) {
wakeupReceived.value = true;
Get.snackbar('收到唤醒信号', '耳机发送了唤醒信号');
});
// 监听音频数据
_audioSubscription = jieli.audioDataStream.listen((data) {
// 更新音频数据统计
audioDataSize.value += data.length;
receivedFrames.value++;
audioDataReceiving.value = true;
});
// 监听命令结果
_commandResultSubscription = jieli.commandResultStream.listen((result) {
// 处理音量调整命令结果
if (result.commandType == Jieli.commandTypeVolume) {
volumeCommandResult.value = result.success ? '音量调整成功' : '音量调整失败';
Get.snackbar(
result.success ? '音量调整成功' : '音量调整失败',
result.success ? '设备已更新音量' : '设备未能更新音量',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: result.success ? Colors.green.withOpacity(0.7) : Colors.red.withOpacity(0.7),
colorText: Colors.white,
duration: Duration(seconds: 2),
);
}
});
}
/// 检查蓝牙状态
Future<bool> checkBluetoothStatus() async {
try {
final isEnabled = await jieli.isBluetoothEnabled();
if (!isEnabled) {
return await jieli.requestBluetoothEnable();
}
return true;
} catch (e) {
return false;
}
}
/// 开始扫描设备
Future<void> startScan() async {
if (isScanning.value) return;
final bluetoothReady = await checkBluetoothStatus();
if (!bluetoothReady) {
Get.snackbar('蓝牙未启用', '请启用蓝牙后再试');
return;
}
try {
isScanning.value = true;
// 清空之前的设备列表
scannedDevices.clear();
// 开始扫描,10秒后自动停止
final success = await jieli.startScan(scanTimeout: 10000);
if (!success) {
isScanning.value = false;
Get.snackbar('扫描失败', '启动扫描设备失败,请检查蓝牙权限');
}
// 10秒后自动更新UI状态
Future.delayed(Duration(milliseconds: 10000), () {
isScanning.value = false;
});
} catch (e) {
isScanning.value = false;
Get.snackbar('扫描错误', e.toString());
}
}
/// 停止扫描
Future<void> stopScan() async {
if (!isScanning.value) return;
try {
await jieli.stopScan();
isScanning.value = false;
} catch (e) {
// 停止扫描错误处理
}
}
/// 连接到指定设备
Future<void> connectToDevice(BleDevice device) async {
try {
// 先断开当前连接
if (currentDevice.value != null) {
await disconnectDevice();
}
// 更新UI状态
connectStatus.value = '连接中...';
// 重置音频和唤醒相关的状态
wakeupReceived.value = false;
audioDataSize.value = 0;
receivedFrames.value = 0;
audioDataReceiving.value = false;
// 连接设备
final success = await jieli.connect(device.id);
if (success) {
// 连接成功,更新当前设备
currentDevice.value = device;
deviceInfo.value = '设备名称: ${device.name}\n设备ID: ${device.id}\nRSSI: ${device.rssi} dBm';
} else {
deviceInfo.value = '连接失败';
}
} catch (e) {
deviceInfo.value = '连接错误: ${e.toString()}';
}
}
/// 断开设备连接
Future<void> disconnectDevice() async {
if (currentDevice.value == null) return;
try {
connectStatus.value = '断开中...';
final success = await jieli.disconnect();
if (success) {
deviceInfo.value = '未连接设备';
}
} catch (e) {
// 断开连接错误处理
}
}
/// 发送开始录音命令
Future<void> sendStartRecordingCommand() async {
if (currentDevice.value == null) {
Get.snackbar('操作失败', '未连接设备');
return;
}
try {
final success = await jieli.sendStartRecordingCommand();
if (success) {
Get.snackbar('命令已发送', '已发送开始录音命令');
} else {
Get.snackbar('命令发送失败', '发送开始录音命令失败');
}
} catch (e) {
Get.snackbar('命令错误', e.toString());
}
}
/// 发送停止录音命令
Future<void> sendStopRecordingCommand() async {
if (currentDevice.value == null) {
Get.snackbar('操作失败', '未连接设备');
return;
}
try {
final success = await jieli.sendStopRecordingCommand();
if (success) {
Get.snackbar('命令已发送', '已发送停止录音命令');
} else {
Get.snackbar('命令发送失败', '发送停止录音命令失败');
}
} catch (e) {
Get.snackbar('命令错误', e.toString());
}
}
/// 增加音量
Future<void> increaseVolume() async {
if (currentDevice.value == null) {
Get.snackbar('操作失败', '未连接设备');
return;
}
try {
volumeCommandResult.value = '发送中...';
final success = await jieli.increaseVolume();
if (!success) {
Get.snackbar('命令发送失败', '发送增加音量命令失败');
volumeCommandResult.value = '发送失败';
}
} catch (e) {
Get.snackbar('命令错误', e.toString());
volumeCommandResult.value = '发送出错';
}
}
/// 减小音量
Future<void> decreaseVolume() async {
if (currentDevice.value == null) {
Get.snackbar('操作失败', '未连接设备');
return;
}
try {
volumeCommandResult.value = '发送中...';
final success = await jieli.decreaseVolume();
if (!success) {
Get.snackbar('命令发送失败', '发送减小音量命令失败');
volumeCommandResult.value = '发送失败';
}
} catch (e) {
Get.snackbar('命令错误', e.toString());
volumeCommandResult.value = '发送出错';
}
}
/// 清理资源
void _disposeJieli() {
try {
jieli.dispose();
} catch (e) {
// 释放资源错误处理
}
}
}

568
lib/modules/jieli_test/views/jieli_test_view.dart

@ -0,0 +1,568 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../controllers/jieli_test_controller.dart';
import 'package:jieli/jieli.dart';
class JieliTestView extends GetView<JieliTestController> {
const JieliTestView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
appBar: AppBar(
backgroundColor: isDarkMode ? Colors.black : Colors.white,
elevation: 0.5,
title: Text(
'杰理蓝牙测试',
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
centerTitle: true,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp,
),
onPressed: () => Get.back(),
),
),
body: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildStatusCard(isDarkMode),
SizedBox(height: 16.h),
_buildActionButtons(isDarkMode),
SizedBox(height: 16.h),
_buildDeviceInfo(isDarkMode),
SizedBox(height: 16.h),
_buildDevicesList(isDarkMode),
],
),
),
);
}
Widget _buildStatusCard(bool isDarkMode) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'连接状态: ',
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
Obx(() => Text(
controller.connectStatus.value,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
color: _getStatusColor(controller.connectStatus.value),
),
)),
],
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Obx(() => _buildStatusChip(
label: '唤醒信号',
isActive: controller.wakeupReceived.value,
activeColor: Colors.orange,
isDarkMode: isDarkMode,
)),
Obx(() => _buildStatusChip(
label: '音频数据',
isActive: controller.audioDataReceiving.value,
activeColor: Colors.green,
isDarkMode: isDarkMode,
)),
],
),
SizedBox(height: 8.h),
Obx(() => controller.audioDataReceiving.value
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'音频数据统计:',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
),
SizedBox(height: 4.h),
Text(
'已接收 ${(controller.audioDataSize.value / 1024).toStringAsFixed(2)} KB',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
),
Text(
'共 ${controller.receivedFrames.value} 帧',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
),
],
)
: SizedBox.shrink()
),
],
),
);
}
Widget _buildStatusChip({
required String label,
required bool isActive,
required Color activeColor,
required bool isDarkMode,
}) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: isActive
? activeColor
: (isDarkMode ? Colors.grey[800] : Colors.grey[200]),
borderRadius: BorderRadius.circular(16.r),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8.w,
height: 8.w,
decoration: BoxDecoration(
color: isActive ? Colors.white : Colors.grey,
shape: BoxShape.circle,
),
),
SizedBox(width: 6.w),
Text(
label,
style: TextStyle(
fontSize: 12.sp,
color: isActive
? Colors.white
: (isDarkMode ? Colors.white70 : Colors.black54),
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
),
),
],
),
);
}
Widget _buildActionButtons(bool isDarkMode) {
return Column(
children: [
Row(
children: [
Expanded(
child: Obx(() => ElevatedButton.icon(
onPressed: controller.isScanning.value
? controller.stopScan
: controller.startScan,
icon: Icon(
controller.isScanning.value ? Icons.stop : Icons.bluetooth_searching,
size: 18.sp,
),
label: Text(
controller.isScanning.value ? '停止扫描' : '扫描设备',
style: TextStyle(fontSize: 14.sp),
),
style: ElevatedButton.styleFrom(
backgroundColor: controller.isScanning.value
? Colors.red[600]
: Colors.blue[600],
padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
)),
),
],
),
SizedBox(height: 8.h),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: controller.sendStartRecordingCommand,
icon: Icon(
Icons.mic,
size: 18.sp,
),
label: Text(
'开始录音',
style: TextStyle(fontSize: 14.sp),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[600],
padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
),
),
SizedBox(width: 8.w),
Expanded(
child: ElevatedButton.icon(
onPressed: controller.sendStopRecordingCommand,
icon: Icon(
Icons.stop_circle,
size: 18.sp,
),
label: Text(
'停止录音',
style: TextStyle(fontSize: 14.sp),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[600],
padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
),
),
],
),
SizedBox(height: 8.h),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: controller.increaseVolume,
icon: Icon(
Icons.volume_up,
size: 18.sp,
),
label: Text(
'增加音量',
style: TextStyle(fontSize: 14.sp),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue[600],
padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
),
),
SizedBox(width: 8.w),
Expanded(
child: ElevatedButton.icon(
onPressed: controller.decreaseVolume,
icon: Icon(
Icons.volume_down,
size: 18.sp,
),
label: Text(
'减小音量',
style: TextStyle(fontSize: 14.sp),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange[600],
padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
),
),
],
),
SizedBox(height: 8.h),
Obx(() => controller.volumeCommandResult.value.isNotEmpty
? Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w),
decoration: BoxDecoration(
color: controller.volumeCommandResult.value.contains('成功')
? Colors.green[100]
: (controller.volumeCommandResult.value == '发送中...'
? Colors.blue[100]
: Colors.red[100]),
borderRadius: BorderRadius.circular(8.r),
),
child: Text(
'音量控制: ${controller.volumeCommandResult.value}',
style: TextStyle(
fontSize: 13.sp,
color: controller.volumeCommandResult.value.contains('成功')
? Colors.green[900]
: (controller.volumeCommandResult.value == '发送中...'
? Colors.blue[900]
: Colors.red[900]),
),
textAlign: TextAlign.center,
),
)
: SizedBox.shrink()
),
],
);
}
Widget _buildDeviceInfo(bool isDarkMode) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'设备信息:',
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
SizedBox(height: 8.h),
Container(
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.black : Colors.grey[100],
borderRadius: BorderRadius.circular(8.r),
),
child: Obx(() => Text(
controller.deviceInfo.value,
style: TextStyle(
fontSize: 13.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
height: 1.4,
),
)),
),
SizedBox(height: 16.h),
Obx(() => controller.currentDevice.value != null
? ElevatedButton.icon(
onPressed: controller.disconnectDevice,
icon: Icon(
Icons.bluetooth_disabled,
size: 16.sp,
),
label: Text(
'断开连接',
style: TextStyle(fontSize: 13.sp),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[600],
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
)
: SizedBox.shrink()
),
],
),
);
}
Widget _buildDevicesList(bool isDarkMode) {
return Expanded(
child: Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(16.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'扫描到的设备',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
Obx(() => Text(
'${controller.scannedDevices.length}个设备',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
)),
],
),
),
Divider(height: 1, thickness: 1, color: isDarkMode ? Colors.grey[800] : Colors.grey[200]),
Expanded(
child: Obx(() => controller.scannedDevices.isEmpty
? Center(
child: controller.isScanning.value
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16.h),
Text(
'正在扫描...',
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
),
],
)
: Text(
'未扫描到设备',
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
),
)
: ListView.separated(
padding: EdgeInsets.symmetric(vertical: 8.h),
itemCount: controller.scannedDevices.length,
separatorBuilder: (context, index) => Divider(
height: 1,
thickness: 1,
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
),
itemBuilder: (context, index) {
final device = controller.scannedDevices[index];
return ListTile(
dense: true,
title: Text(
device.name.isEmpty ? '未知设备' : device.name,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'设备ID: ${device.id}',
style: TextStyle(
fontSize: 11.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
),
Text(
'RSSI: ${device.rssi} dBm',
style: TextStyle(
fontSize: 11.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
),
],
),
trailing: Obx(() => controller.currentDevice.value?.id == device.id
? Chip(
label: Text(
'已连接',
style: TextStyle(
fontSize: 12.sp,
color: Colors.white,
),
),
backgroundColor: Colors.green,
padding: EdgeInsets.symmetric(horizontal: 8.w),
)
: ElevatedButton(
onPressed: () => controller.connectToDevice(device),
child: Text(
'连接',
style: TextStyle(fontSize: 12.sp),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[600],
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6.r),
),
),
),
),
contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
isThreeLine: true,
);
},
),
),
),
],
),
),
);
}
Color _getStatusColor(String status) {
switch (status) {
case '连接中...':
return Colors.orange;
case '已连接':
return Colors.green;
case '连接失败':
return Colors.red;
case '已断开':
return Colors.grey;
default:
return Colors.grey;
}
}
}

109
lib/modules/login/controllers/login_controller.dart

@ -24,6 +24,10 @@ class LoginController extends GetxController {
String _deviceId = ''; String _deviceId = '';
final _hasAcceptedPrivacy = false.obs;
bool get hasAcceptedPrivacy => _hasAcceptedPrivacy.value;
set hasAcceptedPrivacy(bool value) => _hasAcceptedPrivacy.value = value;
// 添加国家编码相关状态 // 添加国家编码相关状态
final _selectedCountryCode = ''.obs; final _selectedCountryCode = ''.obs;
final _selectedCountryFlag = '✉️'.obs; final _selectedCountryFlag = '✉️'.obs;
@ -69,7 +73,7 @@ class LoginController extends GetxController {
{'name': '中国澳门', 'code': '+853', 'flag': '🇲🇴'}, {'name': '中国澳门', 'code': '+853', 'flag': '🇲🇴'},
{'name': '中国台湾', 'code': '+886', 'flag': '🇹🇼'}, {'name': '中国台湾', 'code': '+886', 'flag': '🇹🇼'},
// 添加的其他主要国家 // 添加的其他主要国家
{'name': '埃及', 'code': '+20', 'flag': '🇪🇬'}, {'name': '埃及', 'code': '+20', 'flag': '🇪🇬'},
{'name': '希腊', 'code': '+30', 'flag': '🇬🇷'}, {'name': '希腊', 'code': '+30', 'flag': '🇬🇷'},
{'name': '荷兰', 'code': '+31', 'flag': '🇳🇱'}, {'name': '荷兰', 'code': '+31', 'flag': '🇳🇱'},
@ -153,7 +157,7 @@ class LoginController extends GetxController {
final _contactValue = ''.obs; final _contactValue = ''.obs;
final _displayContact = ''.obs; final _displayContact = ''.obs;
final _verificationCode = ['', '', '', ''].obs; final _verificationCode = ['', '', '', ''].obs;
int loginType = -1; int loginType = 0;
// Getters // Getters
LoginViewState get viewState => _viewState.value; LoginViewState get viewState => _viewState.value;
bool get isLoading => _isLoading.value; bool get isLoading => _isLoading.value;
@ -184,19 +188,18 @@ class LoginController extends GetxController {
Future<void> _initDeviceInfo() async { Future<void> _initDeviceInfo() async {
try { try {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if (GetPlatform.isAndroid) { if (GetPlatform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
_deviceId = androidInfo.id; _deviceId = androidInfo.id;
}else if (GetPlatform.isIOS) { } else if (GetPlatform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo; IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
_deviceId = iosInfo.identifierForVendor ?? 'unknown'; _deviceId = iosInfo.identifierForVendor ?? 'unknown';
} }
Logger().d('deviceId: $_deviceId'); Logger().d('deviceId: $_deviceId');
}catch(e){ } catch (e) {
Logger().e('获取设备信息失败: $e'); Logger().e('获取设备信息失败: $e');
_deviceId = 'unknown'; _deviceId = 'unknown';
} }
} }
// 更新选中的国家编码 // 更新选中的国家编码
@ -210,14 +213,15 @@ class LoginController extends GetxController {
} else { } else {
loginType = 1; // 设置为手机号登录类型 loginType = 1; // 设置为手机号登录类型
} }
_countdownSeconds.value = 0;
} }
// 获取验证码 // 获取验证码
void getVerificationCode() { void getVerificationCode() {
if (contactController.text.isEmpty) { if (contactController.text.isEmpty) {
Get.snackbar( Get.snackbar(
'提示', 'tip'.tr,
'请输入手机号码或邮箱', 'pleaseEnterContactInfo'.tr,
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1), backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red, colorText: Colors.red,
@ -280,8 +284,8 @@ class LoginController extends GetxController {
void showVerificationView() { void showVerificationView() {
if (contactController.text.isEmpty) { if (contactController.text.isEmpty) {
Get.snackbar( Get.snackbar(
'提示', 'tip'.tr,
'请输入手机号码或邮箱', 'pleaseEnterContactInfo'.tr,
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1), backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red, colorText: Colors.red,
@ -358,8 +362,8 @@ class LoginController extends GetxController {
} }
} else { } else {
Get.snackbar( Get.snackbar(
'提示', 'tip'.tr,
'请正确的手机号码或邮箱', 'pleaseEnterContactInfo'.tr,
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1), backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red, colorText: Colors.red,
@ -416,19 +420,68 @@ class LoginController extends GetxController {
// 登录 // 登录
Future<void> login() async { Future<void> login() async {
// 添加基础验证
if (loginType == 0 || loginType == 1) {
// 邮箱或手机号登录
if (contactController.text.isEmpty) {
Get.snackbar(
'error'.tr,
'pleaseEnterContactInfo'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
// 验证邮箱格式
if (loginType == 0 && !isEmialContactController()) {
Get.snackbar(
'error'.tr,
'invalidEmailFormat'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
// 验证手机号格式
if (loginType == 1 && !isPhoneContactController()) {
Get.snackbar(
'error'.tr,
'invalidPhoneFormat'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
}
//验证隐私协议勾选状态
if (!_hasAcceptedPrivacy.value) {
Get.snackbar(
'tip'.tr,
'pleaseAcceptPrivacyPolicy'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
_isLoading.value = true; _isLoading.value = true;
try { try {
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 2));
// 模拟验证过程 // 模拟验证过程
switch (loginType) { switch (loginType) {
case 0: case 0:
{ {
if (!isVerificationCodeComplete()) { if (!isVerificationCodeComplete()) {
Get.snackbar( Get.snackbar(
'提示', 'tip'.tr,
'请输入完整的验证码', 'pleaseEnterVerificationCode'.tr,
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1), backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red, colorText: Colors.red,
@ -452,8 +505,8 @@ class LoginController extends GetxController {
{ {
if (!isVerificationCodeComplete()) { if (!isVerificationCodeComplete()) {
Get.snackbar( Get.snackbar(
'提示', 'tip'.tr,
'请输入完整的验证码', 'pleaseEnterVerificationCode'.tr,
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1), backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red, colorText: Colors.red,
@ -567,7 +620,7 @@ class LoginController extends GetxController {
} }
break; break;
} }
case 6://游客登录 case 6: //游客登录
{ {
requestLogin({ requestLogin({
"mail": "", "mail": "",
@ -585,8 +638,8 @@ class LoginController extends GetxController {
default: default:
{ {
Get.snackbar( Get.snackbar(
'错误', 'error'.tr,
'登录失败,请重试', 'loginFailed'.tr,
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1), backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red, colorText: Colors.red,
@ -596,8 +649,8 @@ class LoginController extends GetxController {
} }
} catch (e) { } catch (e) {
Get.snackbar( Get.snackbar(
'错误', 'error'.tr,
'登录失败,请重试', 'loginFailed'.tr,
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1), backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red, colorText: Colors.red,

56
lib/modules/login/views/login_view.dart

@ -51,7 +51,7 @@ class LoginView extends GetView<LoginController> {
_buildSocialLoginSection(isDarkMode), _buildSocialLoginSection(isDarkMode),
// 用户协议 // 用户协议
SizedBox(height: 40.h), SizedBox(height: 24.h),
_buildTermsAndPrivacy(isDarkMode), _buildTermsAndPrivacy(isDarkMode),
SizedBox(height: 24.h), SizedBox(height: 24.h),
], ],
@ -457,9 +457,9 @@ class LoginView extends GetView<LoginController> {
SizedBox(width: 24.w), SizedBox(width: 24.w),
_buildSocialButton( _buildSocialButton(
FontAwesomeIcons.facebook, Colors.blue, isDarkMode), FontAwesomeIcons.facebook, Colors.blue, isDarkMode),
SizedBox(width: 24.w), SizedBox(width: 24.w),
_buildSocialButton( _buildSocialButton(FontAwesomeIcons.userSecret, Colors.black,
FontAwesomeIcons.userSecret, Colors.black, isDarkMode),//添加游客登录按钮 isDarkMode), //添加游客登录按钮
], ],
), ),
], ],
@ -512,10 +512,10 @@ class LoginView extends GetView<LoginController> {
break; break;
} }
case FontAwesomeIcons.userSecret: // 处理游客登录 case FontAwesomeIcons.userSecret: // 处理游客登录
{ {
controller.loginType = 6; controller.loginType = 6;
break; break;
} }
} }
controller.login(); controller.login();
} }
@ -524,14 +524,40 @@ class LoginView extends GetView<LoginController> {
Widget _buildTermsAndPrivacy(bool isDarkMode) { Widget _buildTermsAndPrivacy(bool isDarkMode) {
return Column( return Column(
children: [ children: [
Text( Row(
'expressConsent'.tr, // 登录/注册即表示你同意 mainAxisAlignment: MainAxisAlignment.center,
style: TextStyle( crossAxisAlignment: CrossAxisAlignment.center, // 添加这行
fontSize: 12.sp, children: [
color: isDarkMode ? Colors.grey[500] : Colors.grey[600], SizedBox(
), width: 12.w,
height: 16.w,
child: Transform.scale(
scale: 0.7,
child: Obx(() => Checkbox(
value: controller.hasAcceptedPrivacy,
onChanged: (value) =>
controller.hasAcceptedPrivacy = value ?? false,
activeColor: const Color(0xFF3B82F6),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
side: BorderSide(
width: 1.6,
color: Colors.grey[600]!,
),
)),
),
),
SizedBox(width: 4.w),
Text(
'expressConsent'.tr,
style: TextStyle(
fontSize: 13.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
),
),
],
), ),
SizedBox(height: 4.h), //SizedBox(height: 4.h),
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [

20
lib/modules/meeting/controllers/meeting_record_controller.dart

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:amap_flutter/amap_flutter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -25,6 +26,9 @@ class MeetingRecordController extends GetxController
late AnimationController lottieController; late AnimationController lottieController;
late AsrService _asrService; late AsrService _asrService;
final _amapFlutter = AmapFlutter();
StreamSubscription? _locationStream;
Timer? timer; Timer? timer;
RxBool isRecording = false.obs; //是否录音中 RxBool isRecording = false.obs; //是否录音中
@ -33,7 +37,7 @@ class MeetingRecordController extends GetxController
String title = ''; String title = '';
int creationTime = 0; int creationTime = 0;
RxString address = '深圳市高新技术产业园区W2-A座'.obs; RxString address = '正在定位'.obs;
String _longitude = ''; String _longitude = '';
String _latitude = ''; String _latitude = '';
@ -54,6 +58,7 @@ class MeetingRecordController extends GetxController
final Map<String, dynamic> args = Get.arguments ?? {}; final Map<String, dynamic> args = Get.arguments ?? {};
title = args['title']; title = args['title'];
creationTime = DateTime.now().millisecondsSinceEpoch; creationTime = DateTime.now().millisecondsSinceEpoch;
_amapInit();
} }
@override @override
@ -63,9 +68,22 @@ class MeetingRecordController extends GetxController
_asrService.dispose(); _asrService.dispose();
_recognitionSubscription?.cancel(); _recognitionSubscription?.cancel();
_originaBytes = BytesBuilder(); _originaBytes = BytesBuilder();
_amapFlutter.dispose();
_locationStream?.cancel();
super.onClose(); super.onClose();
} }
void _amapInit() async {
await _amapFlutter.initLocation();
_locationStream = _amapFlutter.locationStream().listen((data) {
address.value = data['address'];
_longitude = data['longitude'].toString();
_latitude = data['latitude'].toString();
_amapFlutter.stopLocation();
});
_amapFlutter.startLocation();
}
// 点击开始、暂停按钮 // 点击开始、暂停按钮
void recorderHandle() { void recorderHandle() {
if (isRecording.value) { if (isRecording.value) {

19
lib/modules/meeting/views/meeting_details_view.dart

@ -38,7 +38,7 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
onPressed: () => Get.back(), onPressed: () => Get.back(),
), ),
title: Text( title: Text(
'recordingDetails'.tr,//录音详情 'recordingDetails'.tr, //录音详情
style: TextStyle( style: TextStyle(
fontSize: 17.sp, fontSize: 17.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -89,11 +89,14 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
size: 16.w, size: 16.w,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500], color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
), ),
Text( Expanded(
controller.meetingData.value.address, child: Text(
style: TextStyle( controller.meetingData.value.address,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500], style: TextStyle(
fontSize: 14.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 14.sp,
),
overflow: TextOverflow.ellipsis,
), ),
), ),
], ],
@ -253,7 +256,9 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
indicatorColor: isDarkMode ? AppColors.primary : Colors.blue, indicatorColor: isDarkMode ? AppColors.primary : Colors.blue,
labelColor: isDarkMode ? AppColors.primary : Colors.blue, labelColor: isDarkMode ? AppColors.primary : Colors.blue,
labelStyle: TextStyle( labelStyle: TextStyle(
color: isDarkMode ? Colors.white : const Color.fromARGB(221, 197, 196, 196), color: isDarkMode
? Colors.white
: const Color.fromARGB(221, 197, 196, 196),
fontSize: 14.sp, fontSize: 14.sp,
), ),
tabs: const [ tabs: const [

15
lib/modules/meeting/views/meeting_edit_view.dart

@ -119,12 +119,15 @@ class MeetingEditView extends GetView<MeetingEditController> {
size: 16.w, size: 16.w,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500], color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
), ),
Obx( Expanded(
() => Text( child: Obx(
controller.address.value, () => Text(
style: TextStyle( controller.address.value,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500], style: TextStyle(
fontSize: 14.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 14.sp,
),
overflow: TextOverflow.ellipsis,
), ),
), ),
), ),

15
lib/modules/meeting/views/meeting_record_view.dart

@ -135,11 +135,16 @@ class MeetingRecordView extends GetView<MeetingRecordController> {
size: 16.w, size: 16.w,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500], color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
), ),
Text( Expanded(
controller.address.value, child: Obx(
style: TextStyle( () => Text(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500], controller.address.value,
fontSize: 14.sp, style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 14.sp,
),
overflow: TextOverflow.ellipsis,
),
), ),
), ),
], ],

4
lib/modules/pairing/controllers/pairing_controller.dart

@ -6,11 +6,12 @@ import '../../../data/services/classic_bluetooth_service.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import 'dart:io'; import 'dart:io';
import '../../../data/services/voice_interaction_service.dart'; import '../../../data/services/voice_interaction_service.dart';
/// 简化的配对控制器,仅用于显示已连接经典蓝牙设备和处理基本配对流程 /// 简化的配对控制器,仅用于显示已连接经典蓝牙设备和处理基本配对流程
class PairingController extends GetxController { class PairingController extends GetxController {
static PairingController get to => Get.find(); static PairingController get to => Get.find();
// 经典蓝牙服务 // 经典蓝牙服务
final ClassicBluetoothService _bluetoothService = final ClassicBluetoothService _bluetoothService =
Get.find<ClassicBluetoothService>(); Get.find<ClassicBluetoothService>();
@ -109,6 +110,7 @@ class PairingController extends GetxController {
/// 初始化流程 /// 初始化流程
Future<void> _initialize() async { Future<void> _initialize() async {
try { try {
// 初始化语音交互服务 // 初始化语音交互服务
_voiceInteractionService.initialize(); _voiceInteractionService.initialize();

45
lib/modules/profile/controllers/profile_controller.dart

@ -13,7 +13,6 @@ import 'package:path_provider/path_provider.dart';
import '../../../data/services/network/api.dart'; import '../../../data/services/network/api.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
class ProfileController extends GetxController { class ProfileController extends GetxController {
static ProfileController get to => Get.find(); static ProfileController get to => Get.find();
@ -118,23 +117,23 @@ class ProfileController extends GetxController {
// _isEditing.value = !_isEditing.value; // _isEditing.value = !_isEditing.value;
// } // }
// 验证base64图片数据 // 验证base64图片数据
// Future<void> _verifyBase64Image(String base64String) async { // Future<void> _verifyBase64Image(String base64String) async {
// try { // try {
// // 解码base64数据 // // 解码base64数据
// List<int> imageBytes = base64Decode(base64String); // List<int> imageBytes = base64Decode(base64String);
// // 获取应用文档目录 // // 获取应用文档目录
// final appDir = await getApplicationDocumentsDirectory(); // final appDir = await getApplicationDocumentsDirectory();
// final verifyPath = '${appDir.path}/verify_base64_${DateTime.now().millisecondsSinceEpoch}.jpg'; // final verifyPath = '${appDir.path}/verify_base64_${DateTime.now().millisecondsSinceEpoch}.jpg';
// // 将解码后的数据写入文件 // // 将解码后的数据写入文件
// final file = File(verifyPath); // final file = File(verifyPath);
// await file.writeAsBytes(imageBytes); // await file.writeAsBytes(imageBytes);
// // 记录验证文件的路径 // // 记录验证文件的路径
// Logger().d('Base64验证图片已保存到: $verifyPath'); // Logger().d('Base64验证图片已保存到: $verifyPath');
// // 显示图片预览对话框 // // 显示图片预览对话框
// Get.dialog( // Get.dialog(
// Dialog( // Dialog(
@ -157,7 +156,7 @@ class ProfileController extends GetxController {
// ), // ),
// ), // ),
// ); // );
// Get.snackbar('验证', '已将Base64转换回图片,请查看预览'); // Get.snackbar('验证', '已将Base64转换回图片,请查看预览');
// } catch (e) { // } catch (e) {
// Logger().e('验证Base64图片失败: $e'); // Logger().e('验证Base64图片失败: $e');
@ -171,8 +170,8 @@ class ProfileController extends GetxController {
// 准备数据 // 准备数据
Map<String, dynamic> data = { Map<String, dynamic> data = {
"image": "", // 默认空字符串 "image": "", // 默认空字符串
"language": currentLanguage.value,//当前用户语言 "language": currentLanguage.value, //当前用户语言
"name": userInfo.value.name,//当前用户名 "name": userInfo.value.name, //当前用户名
}; };
// 如果有头像,转换为base64 // 如果有头像,转换为base64
@ -195,22 +194,21 @@ class ProfileController extends GetxController {
} }
} }
Logger().d('准备更新用户信息: $data'); Logger().d('准备更新用户信息: $data');
// 调用API // 调用API
final response = await Api.updateUserSetting(data); final response = await Api.updateUserSetting(data);
Logger().d('--更新用户信息响应: $response'); Logger().d('--更新用户信息响应: $response');
if(response != null){ if (response != null) {
Logger().d('更新成功: $response'); Logger().d('更新成功: $response');
Get.snackbar('成功', '同步到服务器成功'); Get.snackbar('成功', '同步到服务器成功');
userInfo.update((u) { userInfo.update((u) {
if (u!= null) { if (u != null) {
u.name = response['name']?? ''; u.name = response['name'] ?? '';
u.avatar = response['avatar']?? ''; u.avatar = response['avatar'] ?? '';
} }
}); });
Logger().d('更新后的用户信息: ${userInfo.value.toJson()}'); Logger().d('更新后的用户信息: ${userInfo.value.toJson()}');
}else{ } else {
Logger().e('更新失败: $response'); Logger().e('更新失败: $response');
Get.snackbar('错误', '同步到服务器失败,请重试'); Get.snackbar('错误', '同步到服务器失败,请重试');
return; return;
@ -236,10 +234,10 @@ class ProfileController extends GetxController {
} }
// 下载并保存远程头像 // 下载并保存远程头像
Future<void> _downloadAndSaveAvatar(String url) async { Future<void> _downloadAndSaveAvatar(String url) async {
try { try {
Logger().d('开始下载头像: $url'); Logger().d('开始下载头像: $url');
final dio = Dio(BaseOptions( final dio = Dio(BaseOptions(
headers: { headers: {
'accept': '*/*', 'accept': '*/*',
@ -251,9 +249,9 @@ class ProfileController extends GetxController {
url, url,
options: Options(responseType: ResponseType.bytes), options: Options(responseType: ResponseType.bytes),
); );
Logger().d('下载状态: ${response.statusCode}'); Logger().d('下载状态: ${response.statusCode}');
if (response.statusCode == 200) { if (response.statusCode == 200) {
// 保存到应用文档目录 // 保存到应用文档目录
final appDir = await getApplicationDocumentsDirectory(); final appDir = await getApplicationDocumentsDirectory();
@ -282,12 +280,12 @@ class ProfileController extends GetxController {
// 更新用户头像 // 更新用户头像
Future<void> updateUserAvatar() async { Future<void> updateUserAvatar() async {
final XFile? image = await _picker.pickImage( final XFile? image = await _picker.pickImage(
source: ImageSource.gallery,// 从相册选择 source: ImageSource.gallery, // 从相册选择
maxWidth: 800, maxWidth: 800,
maxHeight: 800, maxHeight: 800,
imageQuality: 85, imageQuality: 85,
); );
if (image != null) { if (image != null) {
// 检查文件大小 // 检查文件大小
final file = File(image.path); final file = File(image.path);
@ -356,7 +354,8 @@ class ProfileController extends GetxController {
if (Get.isRegistered<SettingsController>()) { if (Get.isRegistered<SettingsController>()) {
final settingsController = Get.find<SettingsController>(); final settingsController = Get.find<SettingsController>();
settingsController.userInfo.value.name = userInfo.value.name; settingsController.userInfo.value.name = userInfo.value.name;
settingsController.userInfo.value.avatar = '${userInfo.value.avatar}?time=${DateTime.now().millisecondsSinceEpoch}'; settingsController.userInfo.value.avatar =
'${userInfo.value.avatar}?time=${DateTime.now().millisecondsSinceEpoch}';
//settingsController.loadUserInfo(); //settingsController.loadUserInfo();
settingsController.userInfo.refresh(); settingsController.userInfo.refresh();
} }

80
lib/modules/settings/views/settings_view.dart

@ -434,35 +434,29 @@ class SettingsView extends GetView<SettingsController> {
? Colors.white.withOpacity(0.1) ? Colors.white.withOpacity(0.1)
: Colors.grey[200]), : Colors.grey[200]),
// 备份与恢复 // 用户反馈
// _buildNavigationSetting( _buildSimpleNavigationSetting(
// title: 'backups'.tr, // 备份与恢复 title: 'feedback'.tr, // 用户反馈
// subtitle: 'backupsInfo'.tr, // 备份设置和AI互动历史 subtitle: 'feedbackInfo'.tr, // 提供反馈或建议
// icon: Icons.cloud_upload_outlined, icon: Icons.feedback_outlined,
// iconBgColor: isDarkMode iconBgColor: isDarkMode
// ? Colors.cyan[900]!.withOpacity(0.3) ? Colors.orange[900]!.withOpacity(0.3)
// : Colors.cyan[100]!, : Colors.orange[100]!,
// iconColor: iconColor:
// isDarkMode ? Colors.cyan[300]! : Colors.cyan[600]!, isDarkMode ? Colors.orange[300]! : Colors.orange[600]!,
// value: '${'lastBackup'.tr} 今天', // 上次备份: titleColor: isDarkMode ? Colors.white : null,
// valueStyle: TextStyle( subtitleColor: isDarkMode ? Colors.white70 : null,
// fontSize: 12.sp, onTap: () {
// color: isDarkMode ? Colors.white70 : Colors.grey[500], // 打开反馈页面
// ), Get.toNamed(Routes.feedback);
// onTap: () { },
// // 打开备份设置页面 isDarkMode: isDarkMode,
// print( ),
// '---------用户头像-${controller.userInfo.value.avatar}'); Divider(
// print('---------用户名-${controller.userInfo.value.name}'); height: 1,
// //print('user${User.instance.value}'); color: isDarkMode
// }, ? Colors.white.withOpacity(0.1)
// isDarkMode: isDarkMode, : Colors.grey[200]),
// ),
// Divider(
// height: 1,
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// 隐私设置 // 隐私设置
_buildSimpleNavigationSetting( _buildSimpleNavigationSetting(
@ -481,6 +475,28 @@ class SettingsView extends GetView<SettingsController> {
}, },
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
), ),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 杰理测试
_buildSimpleNavigationSetting(
title: '杰理蓝牙测试',
subtitle: '测试杰理蓝牙设备扫描、连接和功能',
icon: Icons.bluetooth,
iconBgColor: isDarkMode
? Colors.blue[900]!.withOpacity(0.3)
: Colors.blue[100]!,
iconColor:
isDarkMode ? Colors.blue[300]! : Colors.blue[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
Get.toNamed(Routes.jieliTest);
},
isDarkMode: isDarkMode,
),
Divider( Divider(
height: 1, height: 1,
color: isDarkMode color: isDarkMode
@ -1061,7 +1077,8 @@ class SettingsView extends GetView<SettingsController> {
Expanded( Expanded(
child: Row( child: Row(
children: [ children: [
Container(//语音服务图标 Container(
//语音服务图标
width: 32.w, width: 32.w,
height: 32.w, height: 32.w,
decoration: BoxDecoration( decoration: BoxDecoration(
@ -1077,7 +1094,8 @@ class SettingsView extends GetView<SettingsController> {
), ),
), ),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Flexible(//语音服务标题和副标题 Flexible(
//语音服务标题和副标题
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [

7
lib/modules/splash/controllers/splash_controller.dart

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:amap_flutter/amap_flutter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
@ -29,12 +30,16 @@ class SplashController extends GetxController {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_startLoadingAnimation(); _startLoadingAnimation();
_loadAppResources(); _loadAppResources();
AmapFlutter().initSdk(
iosKey: "a4a1394fe817c2f86a424b897b4a9af4",
androidKey: "e5fdc9605eabdeb5626f18f5721f343d",
);
}); });
} }
// 进度条动画(持续2秒) // 进度条动画(持续2秒)
void _startLoadingAnimation() { void _startLoadingAnimation() {
const totalDuration = 2000; // 动画总时长2秒 const totalDuration = 500; // 动画总时长2秒
const steps = totalDuration ~/ 50; // 40次更新(50ms间隔) const steps = totalDuration ~/ 50; // 40次更新(50ms间隔)
final stepValue = 1.0 / steps; final stepValue = 1.0 / steps;

14
lib/routes/app_pages.dart

@ -1,4 +1,6 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../modules/feedback/bindings/feedback_bindings.dart';
import '../modules/feedback/views/feedback_view.dart';
import '../modules/home/views/home_view.dart'; import '../modules/home/views/home_view.dart';
import '../modules/home/bindings/home_binding.dart'; import '../modules/home/bindings/home_binding.dart';
import '../modules/explore/views/explore_view.dart'; import '../modules/explore/views/explore_view.dart';
@ -31,6 +33,8 @@ import '../modules/pairing/bindings/pairing_binding.dart';
import './app_routes.dart'; import './app_routes.dart';
import '../modules/settings/views/settings_view.dart'; import '../modules/settings/views/settings_view.dart';
import '../modules/settings/bindings/settings_binding.dart'; import '../modules/settings/bindings/settings_binding.dart';
import '../modules/jieli_test/views/jieli_test_view.dart';
import '../modules/jieli_test/bindings/jieli_test_binding.dart';
abstract class AppPages { abstract class AppPages {
static final pages = [ static final pages = [
@ -119,5 +123,15 @@ abstract class AppPages {
page: () => const SettingsView(), page: () => const SettingsView(),
binding: SettingsBinding(), binding: SettingsBinding(),
), ),
GetPage(
name: Routes.feedback,
page: () => const FeedbackView(),
binding: FeedbackBinding(),
),
GetPage(
name: Routes.jieliTest,
page: () => const JieliTestView(),
binding: JieliTestBinding(),
),
]; ];
} }

2
lib/routes/app_routes.dart

@ -24,4 +24,6 @@ abstract class Routes {
static const FLUTTER_TTS_TEST = '/flutter_tts_test'; static const FLUTTER_TTS_TEST = '/flutter_tts_test';
static const FLUTTER_ASR_TEST = '/flutter_asr_test'; static const FLUTTER_ASR_TEST = '/flutter_asr_test';
static const editProfile = '/edit_profile'; static const editProfile = '/edit_profile';
static const feedback = '/feedback';
static const jieliTest = '/jieli_test';
} }

0
local_plugins/azure_speech/LICENSE

136
local_plugins/azure_speech/README.md

@ -0,0 +1,136 @@
# Azure Speech 插件
本插件为Flutter提供了Azure语音服务的集成,包括:
- 语音合成(TTS)
- 语音识别(ASR)
## 功能
### 语音合成(TTS)
- 支持多种语音(如中文、英文等)
- 语音参数调整(语速、音调、音量)
- 音频输出设备选择(扬声器、听筒、自动)
- SSML支持
### 语音识别(ASR)
- 一次性语音识别
- 连续语音识别
- 自动语言检测
- 音频处理优化(回音消除、噪声抑制等)
## 平台支持
- Android
- iOS
## 如何使用
### 初始化
```dart
import 'package:azure_speech/azure_speech.dart';
// 初始化TTS
await AzureSpeech.initializeTts(
'your_subscription_key',
'your_service_region',
language: 'zh-CN',
);
// 初始化ASR
await AzureSpeech.initializeAsr(
'your_subscription_key',
'your_service_region',
['zh-CN', 'en-US'],
);
```
### 语音合成
```dart
// 设置语音
await AzureSpeech.setTtsVoice('zh-CN-XiaoxiaoNeural');
// 设置语音参数
await AzureSpeech.setTtsSpeechParams(
rate: 0, // 语速 -100~100
pitch: 0, // 音调 -100~100
volume: 100, // 音量 0~100
);
// 设置音频输出设备
await AzureSpeech.setTtsAudioOutputType('AUTO'); // 'SPEAKER', 'EARPIECE', 'AUTO'
// 播放文本
await AzureSpeech.speakText('你好,世界!');
// 停止播放
await AzureSpeech.stopSpeaking();
// 检查是否正在播放
bool isSpeaking = await AzureSpeech.isSpeaking();
```
### 语音识别
```dart
// 一次性识别
final result = await AzureSpeech.recognizeOnce();
if (result['success']) {
print('识别文本: ${result['text']}');
print('识别语言: ${result['language']}');
} else {
print('识别失败: ${result['error']}');
}
// 连续识别
// 监听识别结果
AzureSpeech.asrResultStream.listen((event) {
switch (event['eventType']) {
case 'recognizing':
// 实时识别中的结果
print('识别中: ${event['text']}');
break;
case 'finalResult':
// 最终识别结果
print('最终结果: ${event['text']}');
break;
case 'error':
// 错误
print('错误: ${event['error']}');
break;
}
});
// 开始连续识别
await AzureSpeech.startContinuousRecognition();
// 停止连续识别
await AzureSpeech.stopContinuousRecognition();
// 检查连续识别是否活跃
bool isActive = await AzureSpeech.isContinuousRecognitionActive();
```
### 释放资源
```dart
// 释放所有资源
await AzureSpeech.dispose();
```
## 依赖项
本插件依赖于:
- Microsoft Cognitive Services Speech SDK
- Flutter
## 注意事项
- 使用前需要在Azure门户中创建语音服务资源,并获取订阅密钥和区域
- Android需要相关权限:RECORD_AUDIO, INTERNET等
- iOS需要在Info.plist中添加麦克风使用权限描述

56
local_plugins/azure_speech/android/build.gradle.kts

@ -0,0 +1,56 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.yunqiinnovation.azure_speech"
compileSdk = 33
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
sourceSets {
getByName("main") {
manifest.srcFile("src/main/AndroidManifest.xml")
java.srcDirs("src/main/kotlin")
}
}
// 添加lint选项
lintOptions {
isCheckReleaseBuilds = false
}
// 添加packaging配置,排除冲突文件
packaging {
resources {
excludes.add("META-INF/DEPENDENCIES")
excludes.add("META-INF/LICENSE")
excludes.add("META-INF/LICENSE.txt")
excludes.add("META-INF/license.txt")
excludes.add("META-INF/NOTICE")
excludes.add("META-INF/NOTICE.txt")
excludes.add("META-INF/notice.txt")
excludes.add("META-INF/*.kotlin_module")
}
}
}
// 显式设置Kotlin JVM目标版本
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
// 添加Microsoft语音SDK
implementation("com.microsoft.cognitiveservices.speech:client-sdk:1.30.0")
}

1
local_plugins/azure_speech/android/settings.gradle.kts

@ -0,0 +1 @@
rootProject.name = "azure_speech"

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save