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(