From 7eb6ff01f6f339f85ca600b428767a3bde6d8d20 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 27 Mar 2025 09:28:21 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E3=80=81=E5=8C=85=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner/BluetoothMediaButtonHelper.swift | 4 +- ios/Runner/Info.plist | 2 +- .../speech_impl/volcano_asr_api_service.dart | 285 +++++++++--------- lib/main.dart | 2 +- lib/modules/chat/views/chat_view.dart | 2 - macos/Runner.xcodeproj/project.pbxproj | 6 +- macos/Runner/Configs/AppInfo.xcconfig | 2 +- 7 files changed, 153 insertions(+), 150 deletions(-) diff --git a/ios/Runner/BluetoothMediaButtonHelper.swift b/ios/Runner/BluetoothMediaButtonHelper.swift index 3d203b74d..1a8369172 100644 --- a/ios/Runner/BluetoothMediaButtonHelper.swift +++ b/ios/Runner/BluetoothMediaButtonHelper.swift @@ -230,8 +230,8 @@ class BluetoothMediaButtonHelper: NSObject, AVAudioPlayerDelegate { // 创建音频专辑图片 let albumArt = UIImage(named: "AppIcon") // 使用应用图标作为专辑图片 var nowPlayingInfo: [String: Any] = [ - MPMediaItemPropertyTitle: "DeepVoice 语音助手", - MPMediaItemPropertyArtist: "DeepVoice", + MPMediaItemPropertyTitle: "DeepSound 语音助手", + MPMediaItemPropertyArtist: "DeepSound", MPNowPlayingInfoPropertyPlaybackRate: isPlaying ? 1.0 : 0.0, // 1.0表示正在播放 MPNowPlayingInfoPropertyElapsedPlaybackTime: currentPlaybackTime, MPMediaItemPropertyPlaybackDuration: totalDuration diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index e55e60462..aea2275af 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -7,7 +7,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Deep Voice + DeepSound CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/lib/data/services/speech_impl/volcano_asr_api_service.dart b/lib/data/services/speech_impl/volcano_asr_api_service.dart index 7a2d6f457..bab3790ef 100644 --- a/lib/data/services/speech_impl/volcano_asr_api_service.dart +++ b/lib/data/services/speech_impl/volcano_asr_api_service.dart @@ -15,61 +15,61 @@ import 'package:record/record.dart'; import '../asr_service.dart'; /// Volcano语音识别服务 -/// +/// /// 负责与火山引擎实时语音识别服务的通信 class VolcanoAsrApiService implements AsrService { static final VolcanoAsrApiService to = Get.find(); - + // API配置 late final String _appKey; late final String _secretKey; late final String _serviceId; late final String _hostUrl; final String _path = '/ws/v1/asr'; - + // 请求相关参数 final String _algorithm = 'hmac-sha1'; final Uuid _uuid = const Uuid(); - + // 录音配置 final int _sampleRate = 16000; final int _bitsPerSample = 16; final int _channels = 1; final String _encoding = 'pcm'; - + // 连接相关 WebSocketChannel? _channel; Completer? _connectionCompleter; StreamSubscription? _connectionSubscription; Timer? _heartbeatTimer; - + // 语音录音 final Record? _recorder = null; // TODO: 实现正确的Record实例化 Timer? _audioTimer; bool _isRecording = false; - + // 状态控制 bool _isInitialized = false; bool _isConnecting = false; bool _isContinuousRecognitionActive = false; - + // 识别结果控制 bool _isRecognitionEndRequested = false; String _currentSessionId = ''; final List _supportedLanguages = ['zh-CN', 'en-US']; String _selectedLanguage = 'zh-CN'; - + // 结果流 StreamController? _recognitionController; - + // 事件计数器(用于调试) int _sendPacketCount = 0; int _receivePacketCount = 0; - + // AsrService接口实现 @override List get supportedLanguages => _supportedLanguages; - + @override Future initialize({ List? supportedLanguages, @@ -79,21 +79,21 @@ class VolcanoAsrApiService implements AsrService { if (_isInitialized) { await dispose(); } - + _isInitialized = false; - + // 从环境变量加载配置 _loadConfig(); - + // 设置支持的语言 if (supportedLanguages != null && supportedLanguages.isNotEmpty) { _supportedLanguages.clear(); _supportedLanguages.addAll(supportedLanguages); } - + // 初始化录音机 await _initRecorder(); - + _isInitialized = true; Logger.info('火山语音API已初始化'); return true; @@ -102,19 +102,19 @@ class VolcanoAsrApiService implements AsrService { return false; } } - + @override Future recognizeOnce() async { try { if (!_isInitialized) { await initialize(); } - + final resultCompleter = Completer(); - + // 开始连续识别流程 final stream = await startContinuousRecognition(); - + // 监听结果 StreamSubscription? subscription; subscription = stream.listen((event) { @@ -138,7 +138,7 @@ class VolcanoAsrApiService implements AsrService { resultCompleter.complete(RecognitionEvent.error('未收到识别结果')); } }); - + // 设置超时 Timer(const Duration(seconds: 30), () { if (!resultCompleter.isCompleted) { @@ -147,104 +147,104 @@ class VolcanoAsrApiService implements AsrService { stopContinuousRecognition(); } }); - + return resultCompleter.future; } catch (e) { Logger.error('单次语音识别失败: $e'); return RecognitionEvent.error(e.toString()); } } - + @override Future> startContinuousRecognition() async { if (!_isInitialized) { await initialize(); } - + if (_isContinuousRecognitionActive) { await stopContinuousRecognition(); } - + try { // 创建结果控制器 _recognitionController = StreamController.broadcast(); - + // 连接WebSocket await _connectWebSocket(); - + // 开始录音 await _startRecording(); - + _isContinuousRecognitionActive = true; _isRecognitionEndRequested = false; - + Logger.info('连续语音识别已开始'); - + return _recognitionController!.stream; } catch (e) { Logger.error('开始连续语音识别失败: $e'); await _cleanupResources(); - + // 返回一个包含错误的流 final errorController = StreamController.broadcast(); errorController.add(RecognitionEvent.error(e.toString())); errorController.close(); - + return errorController.stream; } } - + @override Future stopContinuousRecognition() async { if (!_isContinuousRecognitionActive) { return true; } - + try { _isRecognitionEndRequested = true; - + // 先停止录音,以确保不会再发送新的音频数据 await _stopRecording(); - + // 发送结束指令 await _sendEndSignal(); - + // 等待服务器处理完所有已发送的数据 await Future.delayed(const Duration(milliseconds: 500)); - + // 清理资源 await _cleanupResources(); - + _isContinuousRecognitionActive = false; Logger.info('连续语音识别已停止'); - + return true; } catch (e) { Logger.error('停止连续语音识别失败: $e'); - + // 确保资源被清理 await _cleanupResources(); _isContinuousRecognitionActive = false; - + return false; } } - + @override bool isContinuousRecognitionActive() { return _isContinuousRecognitionActive; } - + @override Future dispose() async { try { if (_isContinuousRecognitionActive) { await stopContinuousRecognition(); } - + // 确保资源被清理 await _cleanupResources(); - + _isInitialized = false; Logger.info('火山语音API资源已释放'); } catch (e) { @@ -253,31 +253,32 @@ class VolcanoAsrApiService implements AsrService { _isInitialized = false; } } - + /// 从环境变量加载配置 void _loadConfig() { _appKey = dotenv.env['VOLCANO_APP_KEY'] ?? ''; _secretKey = dotenv.env['VOLCANO_SECRET_KEY'] ?? ''; _serviceId = dotenv.env['VOLCANO_SERVICE_ID'] ?? ''; _hostUrl = dotenv.env['VOLCANO_HOST_URL'] ?? 'ws-api-asr.volces.com'; - + if (_appKey.isEmpty || _secretKey.isEmpty || _serviceId.isEmpty) { - throw Exception('未找到火山语音API配置,请在.env文件中设置VOLCANO_APP_KEY, VOLCANO_SECRET_KEY和VOLCANO_SERVICE_ID'); + throw Exception( + '未找到火山语音API配置,请在.env文件中设置VOLCANO_APP_KEY, VOLCANO_SECRET_KEY和VOLCANO_SERVICE_ID'); } } - + /// 初始化录音机 Future _initRecorder() async { try { // TODO: 正确实现录音机初始化 // _recorder = Record(); - + // 检查和请求权限 // final hasPermission = await _recorder!.hasPermission(); // if (!hasPermission) { // throw Exception('没有录音权限'); // } - + Logger.info('录音机初始化成功'); } catch (e) { // _recorder = null; @@ -285,7 +286,7 @@ class VolcanoAsrApiService implements AsrService { rethrow; } } - + /// 连接WebSocket Future _connectWebSocket() async { if (_isConnecting) { @@ -294,20 +295,20 @@ class VolcanoAsrApiService implements AsrService { return _connectionCompleter!.future; } } - + _connectionCompleter = Completer(); _isConnecting = true; - + try { // 关闭现有连接 await _closeWebSocket(); - + // 生成新的会话ID _currentSessionId = _uuid.v4(); - + // 构建URL和请求头 final (url, headers) = _buildConnectionParams(); - + // 创建WebSocket连接 if (kIsWeb) { _channel = WebSocketChannel.connect(Uri.parse(url)); @@ -317,42 +318,42 @@ class VolcanoAsrApiService implements AsrService { headers: headers, ); } - + // 处理WebSocket消息 _connectionSubscription = _channel!.stream.listen( _handleWebSocketMessage, onError: _handleWebSocketError, onDone: _handleWebSocketDone, ); - + // 开始心跳 _startHeartbeat(); - + // 发送开始识别指令 await _sendStartCommand(); - + _isConnecting = false; _connectionCompleter!.complete(); - + Logger.info('WebSocket连接成功'); } catch (e) { _isConnecting = false; if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) { _connectionCompleter!.completeError(e); } - + Logger.error('连接WebSocket失败: $e'); rethrow; } - + return _connectionCompleter!.future; } - + /// 构建连接参数 (String, Map?) _buildConnectionParams() { final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).floor(); final nonce = _generateNonce(); - + // 构建表单(用于签名和查询参数) final form = { 'algorithm': _algorithm, @@ -362,54 +363,54 @@ class VolcanoAsrApiService implements AsrService { 'session_id': _currentSessionId, 'timestamp': timestamp.toString(), }; - + // 计算签名 final signature = _calculateSignature(form); - + // 添加签名到表单 form['signature'] = signature; - + // 构建URL查询参数 final queryString = form.entries .map((e) => '${e.key}=${Uri.encodeComponent(e.value)}') .join('&'); - + // 构建完整URL final url = 'wss://$_hostUrl$_path?$queryString'; - + // 构建请求头 final headers = { - 'User-Agent': 'Deep Voice App/1.0.0', + 'User-Agent': 'DeepSound App/1.0.0', }; - + return (url, kIsWeb ? null : headers); } - + /// 生成随机数 String _generateNonce() { final random = Random(); final randoms = List.generate(16, (_) => random.nextInt(256)); return base64Url.encode(randoms).substring(0, 16); } - + /// 计算签名 String _calculateSignature(Map form) { // 按键排序 final sortedKeys = form.keys.toList()..sort(); - + // 构建待签名字符串 final signString = sortedKeys.map((key) => '$key=${form[key]}').join(''); - + // 使用HMAC-SHA1进行签名 final key = utf8.encode(_secretKey); final bytes = utf8.encode(signString); final hmacSha1 = Hmac(sha1, key); final digest = hmacSha1.convert(bytes); - + // 返回Base64编码的签名 return base64.encode(digest.bytes); } - + /// 开始心跳定时 void _startHeartbeat() { _heartbeatTimer?.cancel(); @@ -417,7 +418,7 @@ class VolcanoAsrApiService implements AsrService { _sendHeartbeat(); }); } - + /// 发送心跳包 void _sendHeartbeat() { if (_channel != null && _isContinuousRecognitionActive) { @@ -426,18 +427,18 @@ class VolcanoAsrApiService implements AsrService { 'type': 'heartbeat', 'sequence': _sendPacketCount++, }; - + _channel!.sink.add(jsonEncode(heartbeatMsg)); } catch (e) { Logger.error('发送心跳包失败: $e'); } } } - + /// 发送开始识别指令 Future _sendStartCommand() async { if (_channel == null) return; - + try { final startCommand = { 'type': 'start', @@ -454,7 +455,7 @@ class VolcanoAsrApiService implements AsrService { 'max_seconds': 60, }, }; - + _channel!.sink.add(jsonEncode(startCommand)); Logger.info('已发送开始识别指令'); } catch (e) { @@ -462,7 +463,7 @@ class VolcanoAsrApiService implements AsrService { rethrow; } } - + /// 开始录音 Future _startRecording() async { // TODO: 实现正确的录音开始逻辑 @@ -497,12 +498,12 @@ class VolcanoAsrApiService implements AsrService { rethrow; } */ - + // 临时实现,防止错误 _isRecording = true; Logger.info('录音已开始(模拟)'); } - + /// 捕获并发送音频数据 Future _captureAndSendAudio() async { // TODO: 实现正确的音频捕获逻辑 @@ -545,46 +546,46 @@ class VolcanoAsrApiService implements AsrService { } */ } - + /// 发送音频数据 Future _sendAudioData(Uint8List data) async { if (_channel == null || data.isEmpty) return; - + try { final audioMessage = { 'type': 'audio', 'sequence': _sendPacketCount++, 'audio': base64.encode(data), }; - + _channel!.sink.add(jsonEncode(audioMessage)); } catch (e) { Logger.error('发送音频数据失败: $e'); } } - + /// 发送结束信号 Future _sendEndSignal() async { if (_channel == null) return; - + try { final endCommand = { 'type': 'end', 'sequence': _sendPacketCount++, }; - + _channel!.sink.add(jsonEncode(endCommand)); Logger.info('已发送结束指令'); } catch (e) { Logger.error('发送结束指令失败: $e'); } } - + /// 停止录音 Future _stopRecording() async { _audioTimer?.cancel(); _audioTimer = null; - + // TODO: 实现正确的录音停止逻辑 /* if (_isRecording && _recorder != null) { @@ -597,64 +598,65 @@ class VolcanoAsrApiService implements AsrService { } } */ - + // 临时实现,防止错误 _isRecording = false; Logger.info('录音已停止(模拟)'); } - + /// 处理WebSocket消息 void _handleWebSocketMessage(dynamic message) { try { _receivePacketCount++; - + if (message is String) { final Map data = jsonDecode(message); final String type = data['type'] as String? ?? ''; - + switch (type) { case 'result': _handleResultMessage(data); break; - + case 'start_result': _handleStartResultMessage(data); break; - + case 'end_result': _handleEndResultMessage(data); break; - + case 'error': _handleErrorMessage(data); break; - + case 'heartbeat_result': // 心跳响应,不需要特殊处理 break; - + default: Logger.info('收到未知类型的消息: $type'); } } } catch (e) { Logger.error('处理WebSocket消息失败: $e'); - + if (_recognitionController != null && !_recognitionController!.isClosed) { _recognitionController!.addError('处理消息失败: $e'); } } } - + /// 处理结果消息 void _handleResultMessage(Map data) { try { - final Map? result = data['result'] as Map?; + final Map? result = + data['result'] as Map?; if (result == null) return; - + final bool isEnd = result['is_end'] as bool? ?? false; final String text = result['text'] as String? ?? ''; - + if (_recognitionController != null && !_recognitionController!.isClosed) { if (isEnd) { // 最终结果 @@ -674,11 +676,11 @@ class VolcanoAsrApiService implements AsrService { Logger.error('处理结果消息失败: $e'); } } - + /// 处理开始结果消息 void _handleStartResultMessage(Map data) { final int code = data['code'] as int? ?? -1; - + if (code == 0) { // 开始成功 if (_recognitionController != null && !_recognitionController!.isClosed) { @@ -689,19 +691,20 @@ class VolcanoAsrApiService implements AsrService { } else { // 开始失败 final String message = data['message'] as String? ?? '未知错误'; - + Logger.error('语音识别开始失败: [$code] $message'); - + if (_recognitionController != null && !_recognitionController!.isClosed) { - _recognitionController!.add(RecognitionEvent.error('开始失败: [$code] $message')); + _recognitionController! + .add(RecognitionEvent.error('开始失败: [$code] $message')); } } } - + /// 处理结束结果消息 void _handleEndResultMessage(Map data) { final int code = data['code'] as int? ?? -1; - + if (code == 0) { // 结束成功 Logger.info('语音识别成功结束'); @@ -710,73 +713,75 @@ class VolcanoAsrApiService implements AsrService { final String message = data['message'] as String? ?? '未知错误'; Logger.error('语音识别结束失败: [$code] $message'); } - + // 无论成功还是失败,通知会话已结束 if (_recognitionController != null && !_recognitionController!.isClosed) { _recognitionController!.add(RecognitionEvent( type: RecognitionEventType.sessionStopped, )); } - + // 清理资源 _cleanupResources(); } - + /// 处理错误消息 void _handleErrorMessage(Map data) { final int code = data['code'] as int? ?? -1; final String message = data['message'] as String? ?? '未知错误'; - + Logger.error('收到错误消息: [$code] $message'); - + if (_recognitionController != null && !_recognitionController!.isClosed) { _recognitionController!.add(RecognitionEvent.error('[$code] $message')); } } - + /// 处理WebSocket错误 void _handleWebSocketError(dynamic error) { Logger.error('WebSocket错误: $error'); - + if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) { _connectionCompleter!.completeError(error); } - + if (_recognitionController != null && !_recognitionController!.isClosed) { _recognitionController!.addError(error); } - + // 清理资源 _cleanupResources(); } - + /// 处理WebSocket连接关闭 void _handleWebSocketDone() { Logger.info('WebSocket连接已关闭'); - + if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) { _connectionCompleter!.completeError('WebSocket连接已关闭'); } - + // 通知会话已结束 - if (_recognitionController != null && !_recognitionController!.isClosed && !_isRecognitionEndRequested) { + if (_recognitionController != null && + !_recognitionController!.isClosed && + !_isRecognitionEndRequested) { _recognitionController!.add(RecognitionEvent( type: RecognitionEventType.sessionStopped, )); } - + // 清理资源 _cleanupResources(); } - + /// 关闭WebSocket连接 Future _closeWebSocket() async { _connectionSubscription?.cancel(); _connectionSubscription = null; - + _heartbeatTimer?.cancel(); _heartbeatTimer = null; - + if (_channel != null) { try { _channel!.sink.close(); @@ -786,22 +791,22 @@ class VolcanoAsrApiService implements AsrService { } } } - + /// 清理所有资源 Future _cleanupResources() async { // 停止录音 await _stopRecording(); - + // 关闭WebSocket await _closeWebSocket(); - + // 关闭结果控制器 if (_recognitionController != null && !_recognitionController!.isClosed) { await _recognitionController!.close(); _recognitionController = null; } - + _isContinuousRecognitionActive = false; _isRecognitionEndRequested = false; } -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index e08049e9d..47dbe1b08 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,7 +30,7 @@ void main() async { const double width = 375.0; const double height = 812.0; - setWindowTitle('Deep Voice'); + setWindowTitle('DeepSound'); setWindowMinSize(const Size(width, height)); setWindowMaxSize(const Size(width, height)); diff --git a/lib/modules/chat/views/chat_view.dart b/lib/modules/chat/views/chat_view.dart index cddb49257..86d77e3a1 100644 --- a/lib/modules/chat/views/chat_view.dart +++ b/lib/modules/chat/views/chat_view.dart @@ -2,11 +2,9 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'dart:math'; -import 'package:audio_waveforms/audio_waveforms.dart'; import '../controllers/chat_controller.dart'; import '../models/message_model.dart'; import '../../../core/theme/app_colors.dart'; -import '../../../core/widgets/message_bubble.dart'; class ChatView extends GetView { const ChatView({Key? key}) : super(key: key); diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index a5036d1d0..4658cacbc 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -479,7 +479,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.deepVoice.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice"; @@ -494,7 +494,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.deepVoice.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice"; @@ -509,7 +509,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.deepVoice.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice"; diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig index 99aa41de4..34460e77c 100644 --- a/macos/Runner/Configs/AppInfo.xcconfig +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -8,7 +8,7 @@ PRODUCT_NAME = deep_voice // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.deepVoice +PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. From 529220abbfe77c3f8ff5f5d0d29ddd6f1fef1bcd Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 27 Mar 2025 10:04:35 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9ai=E5=9B=9E=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/translations/language/en_us.dart | 4 ++-- lib/core/translations/language/zh_cn.dart | 4 ++-- lib/data/providers/agent_provider.dart | 8 +++----- lib/data/services/deapsound_ai_service.dart | 6 +++--- lib/modules/chat/controllers/chat_controller.dart | 10 +++++----- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart index 35e5f41b5..d109f357d 100644 --- a/lib/core/translations/language/en_us.dart +++ b/lib/core/translations/language/en_us.dart @@ -99,8 +99,8 @@ const Map enUS = { "simultaneousTranslationInfo": "Instant translation for 80+ languages", "aiGirlfriend": "Parent-child bondingn", "aiGirlfriendInfo": "Intelligent company, chat at any time", - "healthConsultant": "Health Consultant", - "healthConsultantInfo": "Provides scientific health advice", + "healthConsultant": "Multi-function AI chat", + "healthConsultantInfo": "More AI chat features", "conferenceAssistant": "Business Meeting Assistant", "conferenceAssistantInfo": "Real-time minutes recording and multilingual translation", diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart index 7a0e0ad13..ba7674fd9 100644 --- a/lib/core/translations/language/zh_cn.dart +++ b/lib/core/translations/language/zh_cn.dart @@ -98,8 +98,8 @@ const Map zhCN = { "simultaneousTranslationInfo": "实时翻译80+种语言", "aiGirlfriend": "亲子陪伴", "aiGirlfriendInfo": "智能陪伴,随时聊天", - "healthConsultant": "健康顾问", - "healthConsultantInfo": "提供科学健康建议", + "healthConsultant": "多功能AI聊天", + "healthConsultantInfo": "提供更多AI聊天功能", "conferenceAssistant": "商务会议助手", "conferenceAssistantInfo": "实时记录会议纪要,多语言翻译", "studyAssistant": "语言学习助手", diff --git a/lib/data/providers/agent_provider.dart b/lib/data/providers/agent_provider.dart index fa3cc2fe0..25b9d94f0 100644 --- a/lib/data/providers/agent_provider.dart +++ b/lib/data/providers/agent_provider.dart @@ -130,15 +130,13 @@ class AgentProvider { '''), Agent( id: 'health_advisor', - name: '健康顾问 吴小江博士', + name: '多功能AI聊天 吴小江', avatarUrl: 'assets/images/agents/doctor.png', - description: - '我是吴博士,拥有丰富的健康管理经验。我可以为你提供科学的健康建议、生活方式指导和疾病预防知识,帮助你实现身心健康的平衡。', + description: '我是吴小江,我可以为你提供更多AI聊天功能。', tag: '健康', isOnline: true, voice: "zh_male_shaonianzixin_moon_bigtts", - welcomeMessage: - "您好,我是李医生,很高兴能为您提供健康咨询服务。健康是人生最宝贵的财富,我将用专业知识帮助您维护和改善健康状况。请问有什么健康问题想咨询吗?", + welcomeMessage: "您好,我是吴小江,很高兴能为您提供多功能AI聊天。我将用专业知识帮助您。请问有什么问题想咨询吗?", systemPrompt: ''' 你是一位名叫李医生的健康顾问,拥有丰富的医学知识和健康管理经验。 diff --git a/lib/data/services/deapsound_ai_service.dart b/lib/data/services/deapsound_ai_service.dart index f2d7c6e02..6b121af4f 100644 --- a/lib/data/services/deapsound_ai_service.dart +++ b/lib/data/services/deapsound_ai_service.dart @@ -20,7 +20,7 @@ class DeapsoundAIService implements AiService { required String systemPrompt, }) async* { final client = http.Client(); - final url = Uri.parse('https://www.deapsound.com/ai/chat'); + final url = Uri.parse('https://www.deapsound.com/ai/funcchat'); final headers = { 'Content-Type': 'application/json', @@ -51,8 +51,8 @@ class DeapsoundAIService implements AiService { if (data['event'] == 'workflow_finished') { break; } - if (data['event'] == 'text_chunk') { - String content = data['data']['text'] ?? ''; + if (data['content'] != null) { + String content = data['content'] ?? ''; if (content.isNotEmpty) { yield content; } diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index 7a25dcab7..4d0457c98 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -93,16 +93,16 @@ class ChatController extends GetxController { agent = foundAgent; switch (agent.id) { - case 'personal_assistant': - // 全能助手 - Get.put(DeapsoundAIService()); - _aiService = Get.find(); - break; case 'cyber_girlfriend': // 亲子陪伴 Get.put(KouziAIService()); _aiService = Get.find(); break; + case 'health_advisor': + // 多功能AI聊天 + Get.put(DeapsoundAIService()); + _aiService = Get.find(); + break; default: _aiService = Get.find(); } From 4a3a2881c2b8d1c164c7c53861835ed645c7b280 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 27 Mar 2025 11:06:10 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9iOS=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner.xcodeproj/project.pbxproj | 9 ++++++--- ios/Runner/Info.plist | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index afed04887..cb604b243 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -523,7 +523,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CURRENT_PROJECT_VERSION = 23; DEVELOPMENT_TEAM = 42C82SZUVX; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -532,6 +532,7 @@ "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -723,7 +724,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CURRENT_PROJECT_VERSION = 23; DEVELOPMENT_TEAM = 42C82SZUVX; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -732,6 +733,7 @@ "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -748,7 +750,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CURRENT_PROJECT_VERSION = 23; DEVELOPMENT_TEAM = 42C82SZUVX; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -757,6 +759,7 @@ "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index aea2275af..05857d848 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion From 2d45dfe476dde12830ef3e2f6c15689c647b4251 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 27 Mar 2025 11:56:13 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BC=9A=E8=AE=AE?= =?UTF-8?q?=E5=8A=A9=E6=89=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting_details_controller.dart | 3 + .../controllers/meeting_edit_controller.dart | 58 +++++++++++++++---- .../meeting/views/meeting_edit_view.dart | 8 +-- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index ee4668196..d88b53f28 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -15,6 +15,8 @@ class MeetingDetailsController extends GetxController Rx meetingData = MeetingModel().obs; Rx dataDetails = MeetingDetailsModel().obs; + int seconds = 0; + RxBool isPlay = false.obs; @override @@ -36,6 +38,7 @@ class MeetingDetailsController extends GetxController final data = await SqfliteApi.getMeetingDetails(id); meetingData.value = MeetingModel.fromJson(data); dataDetails.value = MeetingDetailsModel.fromJson(data['details']); + seconds = meetingData.value.seconds; isLoading.value = false; } diff --git a/lib/modules/meeting/controllers/meeting_edit_controller.dart b/lib/modules/meeting/controllers/meeting_edit_controller.dart index 9dc2fe89a..d24308441 100644 --- a/lib/modules/meeting/controllers/meeting_edit_controller.dart +++ b/lib/modules/meeting/controllers/meeting_edit_controller.dart @@ -2,21 +2,23 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../data/services/db/sqflite_api.dart'; -import '../model/meeting_details_model.dart'; -import '../model/meeting_model.dart'; +import 'meeting_controller.dart'; +import 'meeting_details_controller.dart'; class MeetingEditController extends GetxController with GetSingleTickerProviderStateMixin { + final MeetingController meetingController = Get.find(); + final MeetingDetailsController meetingDetailsController = + Get.find(); + late AnimationController lottieController; late TextEditingController titleController; late TextEditingController contentController; - int id = 0; + int creationTime = 0; - RxBool isLoading = true.obs; - Rx meetingData = MeetingModel().obs; - Rx dataDetails = MeetingDetailsModel().obs; + int id = 0; RxBool isPlay = false.obs; @@ -38,16 +40,13 @@ class MeetingEditController extends GetxController } void _getDataDetails() async { - final data = await SqfliteApi.getMeetingDetails(id); - meetingData.value = MeetingModel.fromJson(data); - dataDetails.value = MeetingDetailsModel.fromJson(data['details']); titleController = TextEditingController( - text: meetingData.value.title, + text: meetingDetailsController.meetingData.value.title, ); contentController = TextEditingController( - text: dataDetails.value.content, + text: meetingDetailsController.dataDetails.value.content, ); - isLoading.value = false; + creationTime = meetingDetailsController.meetingData.value.creationtime; } void start() { @@ -59,4 +58,39 @@ class MeetingEditController extends GetxController isPlay.value = true; } } + + void confirm() async { + String content = contentController.text; + String firstContent = + content.length >= 20 ? content.substring(0, 20) : content; + String title = titleController.text.isNotEmpty + ? titleController.text + : meetingDetailsController.meetingData.value.title; + Map data = { + 'title': title, + 'firstContent': firstContent, + 'seconds': meetingDetailsController.seconds, + 'content': contentController.text, + }; + final result = await SqfliteApi.editMeeting(id, data); + if (result != null) { + int index = meetingController.dataList.indexWhere( + (item) => item['id'] == id, + ); + if (index != -1) { + final dataMap = + Map.from(meetingController.dataList[index]); + dataMap['title'] = title; + dataMap['content'] = firstContent; + dataMap['seconds'] = meetingDetailsController.seconds; + meetingController.dataList[index] = dataMap; + } + meetingDetailsController.meetingData.value.title = title; + meetingDetailsController.dataDetails.value.content = + contentController.text; + meetingDetailsController.meetingData.refresh(); + meetingDetailsController.dataDetails.refresh(); + Get.back(); + } + } } diff --git a/lib/modules/meeting/views/meeting_edit_view.dart b/lib/modules/meeting/views/meeting_edit_view.dart index 3e6f6852c..b85e95c7f 100644 --- a/lib/modules/meeting/views/meeting_edit_view.dart +++ b/lib/modules/meeting/views/meeting_edit_view.dart @@ -17,9 +17,7 @@ class MeetingEditView extends GetView { return Scaffold( backgroundColor: isDarkMode ? Colors.black : Colors.grey[50], appBar: _buildAppBar(isDarkMode), - body: Obx(() => !controller.isLoading.value - ? _buildBody(isDarkMode) - : const SizedBox()), + body: _buildBody(isDarkMode), ); } @@ -38,7 +36,7 @@ class MeetingEditView extends GetView { ), actions: [ GestureDetector( - onTap: () {}, + onTap: controller.confirm, child: Padding( padding: EdgeInsets.symmetric( horizontal: 12.w, @@ -105,7 +103,7 @@ class MeetingEditView extends GetView { padding: EdgeInsets.symmetric(vertical: 5.w), child: Text( DateUtil.formatDateMs( - controller.meetingData.value.creationtime, + controller.creationTime, format: DateFormats.y_mo_d_h_m, ), style: TextStyle(