Browse Source

client/services:测试服 AI 只允许科大讯飞,缺微软配置时提示而不崩溃。构建号 1.0.3+48

同声翻译在密钥未下发时不再反复挂微软听写,避免安卓栈溢出。文本和同声在有 SparkChain 密钥时优先走端上讯飞。

Co-authored-by: Cursor <cursoragent@cursor.com>
along-test
Rodger-Wang 8 hours ago
parent
commit
2007b59825
  1. 2
      apps/client/lib/core/translations/language/en_us.dart
  2. 1
      apps/client/lib/core/translations/language/zh_cn.dart
  3. 4
      apps/client/lib/core/utils/app_lifecycle_manager.dart
  4. 6
      apps/client/lib/core/utils/audio_foreground_service.dart
  5. 18
      apps/client/lib/data/services/ai_vendor_lock.dart
  6. 5
      apps/client/lib/data/services/alibaba_image_translation_service.dart
  7. 3
      apps/client/lib/data/services/asr_service.dart
  8. 5
      apps/client/lib/data/services/bailian_multimodal_service.dart
  9. 5
      apps/client/lib/data/services/bailian_native_dialog.dart
  10. 7
      apps/client/lib/data/services/deapsound_ai_service.dart
  11. 32
      apps/client/lib/data/services/device_ai_session_service.dart
  12. 5
      apps/client/lib/data/services/microsoft_text_translation.dart
  13. 41
      apps/client/lib/data/services/server_translation_service.dart
  14. 174
      apps/client/lib/data/services/sparkchain_session.dart
  15. 39
      apps/client/lib/data/services/speech_impl/azure_asr_service.dart
  16. 7
      apps/client/lib/data/services/speech_impl/azure_ast_service.dart
  17. 5
      apps/client/lib/data/services/speech_impl/azure_tts_service.dart
  18. 3
      apps/client/lib/data/utils/background_keepalive.dart
  19. 26
      apps/client/lib/data/utils/emai_audio_session.dart
  20. 17
      apps/client/lib/modules/mobile_elf/controllers/mobile_elf_controller.dart
  21. 180
      apps/client/lib/modules/translation/controllers/translation_controller.dart
  22. 4
      apps/client/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  23. 24
      apps/client/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  24. 93
      apps/client/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/DeviceDialogFocus.kt
  25. 90
      apps/client/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/AudioRecordingForegroundService.kt
  26. 32
      apps/client/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift
  27. 4
      apps/client/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/BesCallPcmBridge.swift
  28. 36
      apps/client/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/IntegratedSpeechTranslationService.swift
  29. 19
      apps/client/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift
  30. 31
      apps/client/local_plugins/sparkchain/android/build.gradle.kts
  31. 1
      apps/client/local_plugins/sparkchain/android/consumer-rules.pro
  32. BIN
      apps/client/local_plugins/sparkchain/android/libs/Codec.aar
  33. BIN
      apps/client/local_plugins/sparkchain/android/libs/Codec.jar
  34. BIN
      apps/client/local_plugins/sparkchain/android/libs/SparkChain.aar
  35. BIN
      apps/client/local_plugins/sparkchain/android/libs/SparkChain.jar
  36. 3
      apps/client/local_plugins/sparkchain/android/src/main/AndroidManifest.xml
  37. 17
      apps/client/local_plugins/sparkchain/android/src/main/assets/supplierconfig.json
  38. 334
      apps/client/local_plugins/sparkchain/android/src/main/kotlin/com/yunqiinnovation/sparkchain/SparkChainPlugin.kt
  39. 27
      apps/client/local_plugins/sparkchain/ios/sparkchain/Package.swift
  40. 21
      apps/client/local_plugins/sparkchain/ios/sparkchain/Sources/glue/SparkChainGlue.m
  41. 9
      apps/client/local_plugins/sparkchain/ios/sparkchain/Sources/glue/include/SparkChainGlue.h
  42. 270
      apps/client/local_plugins/sparkchain/ios/sparkchain/Sources/sparkchain/SparkChainPlugin.swift
  43. 27
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/Info.plist
  44. 64
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/AIPPT.h
  45. 83
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASR.h
  46. 23
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASRCallbacks.h
  47. 25
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASRError.h
  48. 22
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASRHotwordsResult.h
  49. 31
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASRResult.h
  50. 81
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/AST.h
  51. 30
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASTError.h
  52. 80
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASTResult.h
  53. 29
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/AudioAttributes.h
  54. 20
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/Embedding.h
  55. 34
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/EmbeddingOutput.h
  56. 57
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/Feature.h
  57. 17
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/FeatureError.h
  58. 16
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/FeatureResult.h
  59. 112
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/IST.h
  60. 22
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ISTError.h
  61. 34
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ISTResult.h
  62. 53
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ITS.h
  63. 22
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ITSError.h
  64. 34
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ITSResult.h
  65. 40
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ImageTrans.h
  66. 20
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ImageTransError.h
  67. 32
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ImageTransResult.h
  68. 168
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLM.h
  69. 20
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLMBaseOutput.h
  70. 46
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLMConfig.h
  71. 21
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLMError.h
  72. 21
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLMEvent.h
  73. 65
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLMResult.h
  74. 22
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLMTools.h
  75. 28
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/Memory.h
  76. 37
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/OCR.h
  77. 22
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/OCRError.h
  78. 27
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/OCRExtension.h
  79. 52
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/OCRResult.h
  80. 28
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/OnlineTTS.h
  81. 22
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/PPTError.h
  82. 52
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/PPTResult.h
  83. 25
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/PersonateTTS.h
  84. 176
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/RAASR.h
  85. 35
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/RAASRError.h
  86. 59
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/RAASRResult.h
  87. 149
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/RTASR.h
  88. 25
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/RTASRError.h
  89. 38
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/RTASRResult.h
  90. 20
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/SCEnums.h
  91. 20
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/Segment.h
  92. 67
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/SparkChain.h
  93. 33
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/SparkChainConfig.h
  94. 24
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/SparkChainEntrance.h
  95. 90
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/TTS.h
  96. 22
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/TTSError.h
  97. 32
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/TTSResult.h
  98. 36
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/TableOcr.h
  99. 20
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/TableOcrError.h
  100. 24
      apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/TableOcrExtension.h

2
apps/client/lib/core/translations/language/en_us.dart

@ -74,6 +74,8 @@ const Map<String, String> enUS = {
"unknownError": "Unknown error",
"asrConnectionFailed":
"Speech recognition service connection failed. Please check network and try again.",
"azureSpeechMissing":
"Microsoft speech settings were not received. Speech recognition is unavailable.",
"justNow": "Just now",
"minutesAgo": "@minutes minutes ago",
"yesterday": "Yesterday",

1
apps/client/lib/core/translations/language/zh_cn.dart

@ -73,6 +73,7 @@ const Map<String, String> zhCN = {
"responseTimeout": "响应超时",
"unknownError": "未知错误",
"asrConnectionFailed": "语音识别服务连接失败,请检查网络或稍后重试",
"azureSpeechMissing": "未获取到微软语音配置,语音识别暂不可用",
"justNow": "刚刚",
"minutesAgo": "@minutes分钟前",
"yesterday": "昨天",

4
apps/client/lib/core/utils/app_lifecycle_manager.dart

@ -8,6 +8,7 @@ import '../../modules/meeting/controllers/meeting_record_controller.dart';
import '../../routes/app_routes.dart';
import 'logger.dart';
import '../../data/models/user_Info.dart';
import '../../data/services/ai_vendor_lock.dart';
import '../../data/services/memory_reminder_scheduler.dart';
import '../../data/services/memory_service.dart';
import '../../modules/main_tab/views/widgets/memory_daily_sheet.dart';
@ -79,7 +80,8 @@ class AppLifecycleManager extends GetxService with WidgetsBindingObserver {
_isMeetingRoute(currentRoute) ||
_isTranslationRoute(currentRoute);
final bool canWakeUp = !shouldDisableWakeup;
// 测试服禁止唤醒移动精灵,避免按键把会话打到灵犀。
final bool canWakeUp = !aiOnlyIflytek && !shouldDisableWakeup;
if (_lastCanWakeUp == canWakeUp) return;
_lastCanWakeUp = canWakeUp;

6
apps/client/lib/core/utils/audio_foreground_service.dart

@ -35,15 +35,21 @@ class AudioForegroundService {
///
/// [pinSpeaker] 为真时静音钉在手机扬声器,避免把耳机 A2DP 抢走。
/// 同声翻译的译文本身就走这条媒体通道,钉住会让译文改从扬声器出来,这时要传 false。
/// 车载香薰的回复也走 A2DP,同样不能钉。
///
/// [holdFocus] 为真时,服务先进入前台,再决定要不要暂停外面的音乐,最后才开静音。
/// 这一次调用会等到判断做完才返回。后台直接抢焦点会被系统拒绝。
static Future<void> start({
bool keepalive = false,
bool pinSpeaker = true,
bool holdFocus = false,
}) async {
if (!Platform.isAndroid) return;
try {
await _channel.invokeMethod('startAudioForegroundService', {
'keepalive': keepalive,
'pinSpeaker': pinSpeaker,
'holdFocus': holdFocus,
});
} catch (e) {
// 起不来不该阻断录音本身:前台是为了「切后台还能录」,

18
apps/client/lib/data/services/ai_vendor_lock.dart

@ -0,0 +1,18 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'network/dio_manager.dart';
/// 连着测试服时,AI 只走科大讯飞。微软、阿里、字节、谷歌、灵犀都不要发请求。
///
/// 正式域名不进这把锁。密钥还在本地缓存里也不许拿去调别家。
bool get aiOnlyIflytek {
final hosts = <String>[
DioManager().baseUrl,
dotenv.env['SERVER_URL'] ?? '',
dotenv.env['HW_SERVER_URL'] ?? '',
];
for (final host in hosts) {
if (host.contains('ym-dev.ymaikj.com')) return true;
}
return false;
}

5
apps/client/lib/data/services/alibaba_image_translation_service.dart

@ -6,6 +6,7 @@ import 'package:http/http.dart' as http;
import '../../core/utils/logger.dart';
import '../../data/models/appconfig.dart';
import 'ai_vendor_lock.dart';
import '../../modules/picture_translation/models/picture_translation_models.dart';
import 'language_manager.dart';
@ -72,6 +73,10 @@ class AlibabaImageTranslationService extends GetxService {
required Uint8List imageBytes,
required String targetLanguageCode,
}) async {
if (aiOnlyIflytek) {
Logger.w(_tag, '测试服只允许科大讯飞,不调用通义图片理解');
return null;
}
if (!_isInitialized && !await initialize()) return null;
try {

3
apps/client/lib/data/services/asr_service.dart

@ -14,6 +14,9 @@ abstract class AsrService {
/// 支持的语言
List<String> get supportedLanguages;
/// 微软订阅密钥和区域是否已经下发。没有时调用方提示即可,不要再调原生。
bool get configured;
/// 初始化语音识别服务
Future<bool> initialize({
List<String>? supportedLanguages,

5
apps/client/lib/data/services/bailian_multimodal_service.dart

@ -7,6 +7,7 @@ import 'package:get/get.dart';
import 'package:web_socket_channel/io.dart';
import '../../core/utils/logger.dart';
import 'ai_vendor_lock.dart';
import '../models/appconfig.dart';
import '../models/assistant_directive.dart';
import 'device_location_service.dart';
@ -254,6 +255,10 @@ class BailianMultimodalService extends GetxService {
String source = 'emai',
String? mcpToken,
}) async {
if (aiOnlyIflytek) {
Logger.w(_tag, '测试服只允许科大讯飞,不连接百炼');
return false;
}
if (!isConfigured) {
Logger.e(_tag, '配置不全,无法启动会话:$configHint');
_events.add(BailianEvent(BailianEventType.error,

5
apps/client/lib/data/services/bailian_native_dialog.dart

@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
import '../../core/utils/logger.dart';
import '../models/assistant_directive.dart';
import 'ai_vendor_lock.dart';
import 'bailian_multimodal_service.dart';
/// 耳机唤醒那条 EMAI 链路的**原生**百炼客户端句柄。
@ -102,6 +103,10 @@ class BailianNativeDialog {
String source = 'earphone',
String? mcpToken,
}) async {
if (aiOnlyIflytek) {
Logger.w(_tag, '测试服只允许科大讯飞,不连接百炼');
return false;
}
if (!isConfigured) {
Logger.e(_tag, '配置不全,无法启动会话:$configHint');
_eventCtl.add(BailianEvent(BailianEventType.error,

7
apps/client/lib/data/services/deapsound_ai_service.dart

@ -5,6 +5,7 @@ import 'package:http/http.dart' as http;
import '../models/appconfig.dart';
import 'ai_service.dart';
import 'ai_vendor_lock.dart';
/// 语音纪要「Ask AI」用的文本大模型(OpenAI 兼容接口,客户端直连)。
///
@ -49,6 +50,9 @@ class DeapsoundAIService implements AiService {
String? systemPrompt,
String? userProperties,
}) async {
if (aiOnlyIflytek) {
throw StateError('测试服只允许科大讯飞');
}
final url = chatUrl;
final headers = {
@ -85,6 +89,9 @@ class DeapsoundAIService implements AiService {
String? systemPrompt,
String? userProperties,
}) async* {
if (aiOnlyIflytek) {
throw StateError('测试服只允许科大讯飞');
}
final client = http.Client();
final url = chatUrl;

32
apps/client/lib/data/services/device_ai_session_service.dart

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io' show Platform;
import 'dart:math';
import 'dart:typed_data';
@ -464,14 +465,24 @@ class DeviceAiSessionService extends GetxService {
// iOS:会话期间保持一个静音的后台音频播放,否则锁屏几秒后进程被挂起,
// BLE 上行与 WebSocket 一起停摆(见 BackgroundKeepalive)。Android 空操作。
await BackgroundKeepalive.acquire(_tag);
// 先看外面有没有音乐在播,再开静音保活。顺序反了的话,自己的音轨会让
// isMusicActive 变成 true,结束时交还焦点就把没在播的音乐拉起来。
await EmaiAudioSession.holdForDeviceDialog();
// 华为把退到后台的进程网络掐掉,日志是 Software caused connection abort。
// 用户那句已经写进记录,回复还在这条 WebSocket 上,连接一断就没有回答。
// 前台服务把进程钉住。香薰和耳机都要,不能只给走手机播放器的香薰。
await AudioForegroundService.start(keepalive: true);
_holdsForeground = true;
// 安卓:先成为前台服务再抢焦点,最后才开静音。顺序反了的话,
// 切到音乐 App 之后再唤醒,焦点申请会被拒绝,音乐不暂停,香薰麦一直听到音乐,
// 机体就停在聆听。香薰回复走 A2DP,静音不能钉在手机扬声器上,否则音乐会停、
// 结束却不在香薰上续上,第二次唤醒音量也不降。
// iOS 没有这道前台服务,仍在静音循环起来之后再独占会话。
if (Platform.isAndroid) {
await AudioForegroundService.start(
keepalive: true,
pinSpeaker: !_isEaiCar,
holdFocus: true,
);
_holdsForeground = true;
await EmaiAudioSession.holdForDeviceDialog(focusAlreadyTaken: true);
} else {
await EmaiAudioSession.holdForDeviceDialog();
await AudioForegroundService.start(keepalive: true);
_holdsForeground = true;
}
Logger.w(_tag, '设备对话已开前台服务');
// ★ 先试原生直连:上行 PCM 与合成音频都不经过 Dart。
@ -1140,8 +1151,9 @@ class DeviceAiSessionService extends GetxService {
}
if (wasActive && notifyDevice && s != null) await _sendExit(s);
_session = null;
// 先停静音保活,再交还会话。保活还在播时 deactivate 会被系统拒绝,
// 外部音乐收不到继续播放的通知。
// 先停 iOS 静音循环,再交还会话。循环还在播时 deactivate 会被系统拒绝,
// 外部音乐收不到继续播放的通知。安卓的静音轨在交还焦点时先停,播放键
// 才能送到音乐 App;前台服务留到键发出去再停。
await BackgroundKeepalive.release(_tag);
await EmaiAudioSession.releaseDeviceDialog();
if (_holdsForeground) {

5
apps/client/lib/data/services/microsoft_text_translation.dart

@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
import '../../core/utils/logger.dart';
import '../models/appconfig.dart';
import 'ai_vendor_lock.dart';
/// 微软文本翻译。同声 / 面对面 / 音视频里扩展语种走这条,不经服务端阿里。
///
@ -27,6 +28,10 @@ class MicrosoftTextTranslation {
}) async {
final src = text.trim();
if (src.isEmpty) return null;
if (aiOnlyIflytek) {
Logger.w('MsTrans', '测试服只允许科大讯飞,不调用微软翻译');
return null;
}
final key =
AppConfig.cred('mt_azure', 'subscription_key', 'AZURE_TRANSLATION_KEY');
if (key == null || key.isEmpty) {

41
apps/client/lib/data/services/server_translation_service.dart

@ -4,6 +4,8 @@ import 'package:get/get.dart';
import '../../core/utils/logger.dart';
import 'microsoft_text_translation.dart';
import 'network/api.dart';
import 'sparkchain_session.dart';
import 'ai_vendor_lock.dart';
/// 文本机器翻译:由**服务端**代调后台「第三方服务配置」里启用的 MT 服务。
///
@ -60,10 +62,47 @@ class ServerTranslationService extends GetxService {
required String targetLanguageCode,
CancelToken? cancelToken,
Duration? timeout = realtimeTimeout,
bool tryOnDevice = true,
}) async {
final src = text.trim();
if (src.isEmpty) return null;
if (microsoftMtRequired(sourceLanguageCode, targetLanguageCode)) {
if (!tryOnDevice) {
return _translateRemote(
src: src,
sourceLanguageCode: sourceLanguageCode,
targetLanguageCode: targetLanguageCode,
cancelToken: cancelToken,
timeout: timeout,
);
}
final onDevice = await SparkChainSession.translate(
from: sourceLanguageCode,
to: targetLanguageCode,
text: src,
);
if (onDevice != null) {
lastProvider.value = 'sparkchain';
lastSvcId.value = 'sparkchain';
return onDevice;
}
return _translateRemote(
src: src,
sourceLanguageCode: sourceLanguageCode,
targetLanguageCode: targetLanguageCode,
cancelToken: cancelToken,
timeout: timeout,
);
}
Future<String?> _translateRemote({
required String src,
required String sourceLanguageCode,
required String targetLanguageCode,
CancelToken? cancelToken,
Duration? timeout,
}) async {
if (src.isEmpty) return null;
if (!aiOnlyIflytek && microsoftMtRequired(sourceLanguageCode, targetLanguageCode)) {
final from = microsoftMtCode(sourceLanguageCode);
final to = microsoftMtCode(targetLanguageCode);
Logger.i(_tag, '阿里翻译覆盖不了,改走微软: $from→$to');

174
apps/client/lib/data/services/sparkchain_session.dart

@ -0,0 +1,174 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:sparkchain/sparkchain.dart';
import '../../core/utils/logger.dart';
import '../models/appconfig.dart';
/// 端上讯飞翻译。没配密钥就返回 null,调用方继续走原来的服务端翻译。
class SparkChainSession {
SparkChainSession._();
static const _tag = 'SparkChain';
static bool _ready = false;
static bool _eventsAttached = false;
static bool listening = false;
static int _seq = 0;
static final Map<String, Completer<Map<String, dynamic>>> _waiters = {};
static final Map<String, void Function(Uint8List pcm, bool isFinal)> _ttsHandlers = {};
static final Map<String, Completer<void>> _ttsWaiters = {};
/// 听写结果。只在 [startListen] 成功之后、[stopListen] 之前有意义。
static void Function(Map<String, dynamic> event)? asrListener;
static Future<bool> ensure() async {
if (_ready) return true;
final appId = AppConfig.cred('sparkchain', 'app_id', 'SPARKCHAIN_APP_ID');
final apiKey = AppConfig.cred('sparkchain', 'api_key', 'SPARKCHAIN_API_KEY');
final apiSecret =
AppConfig.cred('sparkchain', 'api_secret', 'SPARKCHAIN_API_SECRET');
if (appId == null || apiKey == null || apiSecret == null) {
return false;
}
if (!_eventsAttached) {
_eventsAttached = true;
SparkChain.events.listen(_onEvent);
}
final apiType =
int.tryParse(AppConfig.cred('sparkchain', 'api_type', 'SPARKCHAIN_API_TYPE') ?? '') ??
1;
final code = await SparkChain.init(
appId: appId,
apiKey: apiKey,
apiSecret: apiSecret,
apiType: apiType,
);
Logger.i(_tag, 'init code=$code apiType=$apiType');
_ready = code == 0;
return _ready;
}
static void _onEvent(Map<String, dynamic> event) {
final id = event['id']?.toString() ?? '';
final kind = event['kind'];
if (kind == 'its' || (kind == 'error' && event['where'] == 'its')) {
final waiter = _waiters[id];
if (waiter != null && !waiter.isCompleted) waiter.complete(event);
}
if (kind == 'asr' || (kind == 'error' && event['where'] == 'asr')) {
asrListener?.call(event);
}
if (kind == 'tts' || (kind == 'error' && event['where'] == 'tts')) {
_onTts(id, event);
}
}
static void _onTts(String id, Map<String, dynamic> event) {
if (event['kind'] == 'error') {
_ttsHandlers.remove(id);
final waiter = _ttsWaiters.remove(id);
if (waiter != null && !waiter.isCompleted) {
waiter.completeError(StateError('${event['code']}'));
}
return;
}
final raw = event['pcm'];
final pcm = raw is Uint8List ? raw : Uint8List(0);
_ttsHandlers[id]?.call(pcm, event['final'] == true);
if (event['final'] == true) {
_ttsHandlers.remove(id);
final waiter = _ttsWaiters.remove(id);
if (waiter != null && !waiter.isCompleted) waiter.complete();
}
}
/// 只听 [lang] 这一种。ga 走实时转写,其余走听写。SDK 不自己开麦。
static Future<bool> startListen(String lang) async {
if (!await ensure()) return false;
final code = await SparkChain.startListen(id: 'listen', lang: sparkChainShortCode(lang));
listening = code == 0;
if (!listening) {
Logger.w(_tag, 'startListen 失败 code=$code');
}
return listening;
}
static void write(Uint8List pcm) {
if (!listening || pcm.isEmpty) return;
unawaited(SparkChain.write(pcm));
}
static Future<void> stopListen() async {
if (!listening) return;
listening = false;
asrListener = null;
try {
await SparkChain.stopListen();
} catch (e) {
Logger.w(_tag, 'stopListen: $e');
}
}
/// 合成一段。PCM 按到达顺序交给 [onPcm],整段结束或失败才返回。
static Future<void> speak({
required String lang,
required String text,
required void Function(Uint8List pcm, bool isFinal) onPcm,
}) async {
if (!await ensure()) {
throw StateError('sparkchain not ready');
}
final id = 'tts-${++_seq}';
final waiter = Completer<void>();
_ttsWaiters[id] = waiter;
_ttsHandlers[id] = onPcm;
final code = await SparkChain.speak(
id: id,
lang: sparkChainShortCode(lang),
text: text,
);
if (code != 0) {
_ttsHandlers.remove(id);
_ttsWaiters.remove(id);
throw StateError('speak $code');
}
await waiter.future.timeout(const Duration(seconds: 20));
}
/// 短码 zh / en / ga。失败返回 null,不抛。
static Future<String?> translate({
required String from,
required String to,
required String text,
}) async {
if (!await ensure()) return null;
final id = '${++_seq}';
final waiter = Completer<Map<String, dynamic>>();
_waiters[id] = waiter;
try {
final code = await SparkChain.translate(
id: id,
from: sparkChainShortCode(from),
to: sparkChainShortCode(to),
text: text,
);
if (code != 0) {
Logger.w(_tag, 'translate 启动失败 code=$code');
return null;
}
final event = await waiter.future.timeout(const Duration(seconds: 12));
if (event['kind'] != 'its') {
Logger.w(_tag, 'translate 失败 code=${event['code']} sid=${event['sid']}');
return null;
}
final out = (event['text'] as String?)?.trim() ?? '';
return out.isEmpty ? null : out;
} on TimeoutException {
Logger.w(_tag, 'translate 超时');
return null;
} finally {
_waiters.remove(id);
}
}
}

39
apps/client/lib/data/services/speech_impl/azure_asr_service.dart

@ -4,6 +4,7 @@ import 'dart:typed_data';
import '../../../data/models/appconfig.dart';
import 'package:flutter/services.dart';
import '../../../core/utils/logger.dart';
import '../ai_vendor_lock.dart';
import 'package:get/get.dart';
import '../asr_service.dart';
@ -73,12 +74,14 @@ class AzureAsrService extends GetxService implements AsrService {
_xunfeiAccessKeySecret =
AppConfig.cred('stt_iflytek', 'access_key_secret', 'XUNFEI_ASR_ACCESS_KEY_SECRET') ?? '';
if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) {
throw Exception(
'未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION');
}
// 密钥没下发时留空。构造函数里抛异常会让翻译页直接起不来。
if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) return;
}
@override
bool get configured =>
_subscriptionKey.isNotEmpty && _serviceRegion.isNotEmpty;
/// 设置事件通道
void _setupEventChannel() {
_eventSubscription?.cancel();
@ -108,6 +111,15 @@ class AzureAsrService extends GetxService implements AsrService {
bool useExternalAudio = false,
bool useEchoCancellation = false,
}) async {
if (!configured) {
Logger.w('AzureASR', '未获取到微软语音配置');
_isInitialized = false;
return false;
}
if (aiOnlyIflytek) {
Logger.w('AzureASR', '测试服只允许科大讯飞,不初始化微软听写');
return false;
}
try {
final List<String> languages =
supportedLanguages ?? _defaultSupportedLanguages;
@ -141,7 +153,7 @@ class AzureAsrService extends GetxService implements AsrService {
} catch (e) {
Logger.error('Azure 语音识别服务初始化失败: ${e.toString()}');
_isInitialized = false;
rethrow;
return false;
}
}
@ -211,7 +223,11 @@ class AzureAsrService extends GetxService implements AsrService {
_lastStartAt = now;
if (!_isInitialized) {
await initialize();
final ok = await initialize();
if (!ok || !_isInitialized) {
Logger.w('AzureASR', '听写未就绪,不启动微软连续识别');
return false;
}
}
if (_isContinuousRecognitionActive) {
@ -235,14 +251,15 @@ class AzureAsrService extends GetxService implements AsrService {
});
if (!result) {
throw Exception('启动连续识别失败');
Logger.w('AzureASR', '启动连续识别失败');
return false;
}
_isContinuousRecognitionActive = true;
return result;
} catch (e) {
Logger.error('开始连续语音识别失败: ${e.toString()}');
rethrow;
return false;
}
}
@ -278,7 +295,11 @@ class AzureAsrService extends GetxService implements AsrService {
@override
Future<Stream<RecognitionEvent>> recognizeCallback() async {
if (!_isInitialized) {
await initialize();
final ok = await initialize();
if (!ok || !_isInitialized) {
Logger.w('AzureASR', '听写未就绪,不挂微软回调');
return const Stream.empty();
}
}
try {
_eventStreamController = StreamController<RecognitionEvent>.broadcast();

7
apps/client/lib/data/services/speech_impl/azure_ast_service.dart

@ -2,6 +2,7 @@ import 'dart:async';
import '../../../data/models/appconfig.dart';
import 'package:flutter/services.dart';
import '../../../core/utils/logger.dart';
import '../ai_vendor_lock.dart';
import 'package:get/get.dart';
import '../ast_service.dart';
@ -293,6 +294,8 @@ class AzureAstService extends GetxService implements AstService {
? 'wss://dashscope.aliyuncs.com/api-ws/v1/realtime'
: aliUrl;
if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) {
// 测试服停用微软后,这里再抛会让翻译页在画出第一帧之前失败,发布包就是一整块灰。
if (aiOnlyIflytek) return;
throw Exception(
'未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION');
}
@ -489,6 +492,10 @@ class AzureAstService extends GetxService implements AstService {
List<String>? supportedLanguages,
String provider = 'azure',
}) async {
if (aiOnlyIflytek) {
Logger.w('AzureAST', '测试服只允许科大讯飞,不初始化通话翻译');
return false;
}
_lastProvider = provider;
if (supportedLanguages != null) _lastLanguages = List<String>.from(supportedLanguages);
try {

5
apps/client/lib/data/services/speech_impl/azure_tts_service.dart

@ -3,6 +3,7 @@ import '../../../data/models/appconfig.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import '../../../core/utils/logger.dart';
import '../ai_vendor_lock.dart';
import '../tts_service.dart';
/// 微软 Text-to-Speech 服务
@ -131,6 +132,10 @@ class AzureTtsService extends GetxService implements TtsService {
Future<bool> initialize({
List<String>? supportedLanguages,
}) async {
if (aiOnlyIflytek) {
Logger.w('AzureTTS', '测试服只允许科大讯飞,不初始化微软合成');
return false;
}
// 检查是否需要重新初始化
if (_isInitialized) {
await dispose();

3
apps/client/lib/data/utils/background_keepalive.dart

@ -63,7 +63,8 @@ class BackgroundKeepalive {
Logger.i(_tag, '当前音频类别 $cur 已支持后台音频,不改类别');
}
await session.setActive(true);
final p = AudioPlayer();
// 播放器自己再 setActive 会盖掉后面独占会话的时机,结束时音乐就续不上。
final p = AudioPlayer(handleAudioSessionActivation: false);
await p.setAsset(_asset);
await p.setLoopMode(LoopMode.one);
await p.setVolume(0);

26
apps/client/lib/data/utils/emai_audio_session.dart

@ -63,10 +63,18 @@ class EmaiAudioSession {
/// 设备按键唤醒的对话(EaiCar 与耳机 EMAI)一开始就占住手机上的媒体播放。
///
/// 已经占住时直接返回,同一次唤醒不重复激活会话。失败只记日志,对话照常进行。
static Future<void> holdForDeviceDialog() async {
///
/// [focusAlreadyTaken]:Android 上焦点是前台服务进前台之后抢的,这里只登记
/// 「这场对话占着」,避免后面的播放器再去申请更弱的焦点把音乐放出来。
static Future<void> holdForDeviceDialog({bool focusAlreadyTaken = false}) async {
if (!_apply || _deviceDialogHeld) return;
_deviceDialogHeld = true;
try {
if (focusAlreadyTaken) {
_configured = EmaiAudioProfile.playbackOnly;
Logger.w(_tag, '媒体播放已由前台服务占住');
return;
}
if (Platform.isAndroid) {
// 不走 audio_session.setActive:它在已有焦点对象时直接返回 true,
// 并且申请的是 GAIN_TRANSIENT,华为上音乐可以继续播。
@ -80,14 +88,20 @@ class EmaiAudioSession {
Logger.w(_tag, '系统没在播音乐,不独占会话 before=$before');
return;
}
await _setNowPlaying(true);
final session = await AudioSession.instance;
await session.configure(_playbackOnlyConfig());
_configured = EmaiAudioProfile.playbackOnly;
final ok = await session.setActive(true);
var ok = await session.setActive(true);
var after = await _externalPlaying();
if (after == true) {
Logger.w(_tag, '独占后外部还在播,重新激活一次');
await session.setActive(false);
ok = await session.setActive(true);
after = await _externalPlaying();
}
_iosInterruptedOthers = ok == true;
await _setNowPlaying(true);
await BackgroundKeepalive.keepPlaying();
final after = await _externalPlaying();
Logger.w(_tag, '占住媒体播放 ok=$ok 外部播放 $before → $after');
} catch (e) {
Logger.w(_tag, '占住媒体播放失败(对话继续): $e');
@ -116,6 +130,9 @@ class EmaiAudioSession {
_iosInterruptedOthers = false;
final before = await _externalPlaying();
final session = await AudioSession.instance;
// 先摘掉自己的 Now Playing。还挂着的话,系统会把「继续播放」发给我们,
// 音乐 App 就一直停着,直到用户切进去。
await _setNowPlaying(false);
final ok = await session.setActive(
false,
avAudioSessionSetActiveOptions:
@ -125,7 +142,6 @@ class EmaiAudioSession {
// 这里不再 setActive,否则外部音乐刚被通知继续又会被打断。
await session.configure(_mixPlaybackConfig());
_configured = null;
await _setNowPlaying(false);
final after = await _externalPlaying();
Logger.i(_tag, '交还媒体播放 ok=$ok 外部播放 $before → $after');
} catch (e) {

17
apps/client/lib/modules/mobile_elf/controllers/mobile_elf_controller.dart

@ -7,6 +7,7 @@ import 'package:get_storage/get_storage.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../../data/models/appconfig.dart';
import '../../../data/services/ai_vendor_lock.dart';
import '../../../core/utils/logger.dart';
import '../../../core/utils/permission_util.dart';
@ -80,8 +81,10 @@ class MobileElfController extends GetxController {
ever(ttsEnabled, (bool enabled) {
AgentService.setTtsMuted(!enabled);
});
// 主动触发认证检查(页面打开时,确保 credential_failed 事件不被错过)
AgentService.fetchCredentials();
// 测试服不走移动精灵(灵犀)。凭据拉取会打到中国移动。
if (!aiOnlyIflytek) {
AgentService.fetchCredentials();
}
}
Future<void> _loadChatHistory() async {
@ -252,6 +255,11 @@ class MobileElfController extends GetxController {
// ═══════════════════════════════════════════════════════════════════════════
Future<void> startCall() async {
if (aiOnlyIflytek) {
status.value = MobileElfStatus.error;
errorMsg.value = '测试服只允许科大讯飞';
return;
}
if (inCall.value || _callStarting) return;
_callStarting = true;
_pttAborted = false;
@ -320,6 +328,11 @@ class MobileElfController extends GetxController {
}
Future<void> startPtt() async {
if (aiOnlyIflytek) {
status.value = MobileElfStatus.error;
errorMsg.value = '测试服只允许科大讯飞';
return;
}
if (pttRecording.value || _pttStarting) return;
_pttStarting = true;
_pttAborted = false;

180
apps/client/lib/modules/translation/controllers/translation_controller.dart

@ -27,6 +27,10 @@ import '../../../core/utils/toast_throttle.dart';
import '../../../core/services/floating_window_manager.dart';
import '../../../data/services/asr_service.dart';
import '../../../data/services/sparkchain_session.dart';
import '../../../data/services/ai_vendor_lock.dart';
import '../../../data/utils/mic_pcm_streamer.dart';
import '../../../data/utils/pcm_stream_player.dart';
import '../../../data/services/usage_stats_service.dart';
import 'package:device_plugin_interface/device_plugin_interface.dart';
import '../../../devices/device_hub.dart';
@ -231,6 +235,11 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
DeviceSession? _simHeadsetSession;
DeviceAudioSource? _simHeadsetMic;
StreamSubscription<Uint8List>? _simHeadsetMicSub;
/// 本轮同声翻译的识别、翻译、合成走讯飞。密钥没配好时为 false,沿用原来的识别。
bool _sparkListen = false;
MicPcmStreamer? _sparkMic;
StreamSubscription<Uint8List>? _sparkMicSub;
PcmStreamPlayer? _sparkPlayer;
DeviceFileRecorder? _simHeadsetRec;
DeviceAudioSource? _callLocalSrc;
DeviceAudioSource? _callPeerSrc;
@ -1600,7 +1609,14 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
Logger.w(
'Translation',
'开始语音识别,模式:同声传译,收音=${_simHeadsetSession != null ? "耳机麦" : "手机麦克风"}');
await _startAsrService('phone_call');
final spark = await _startSparkListen();
if (!spark) {
if (aiOnlyIflytek) {
Logger.w('Translation', '测试服只允许科大讯飞,同声翻译不改走原识别');
} else {
await _startAsrService('phone_call');
}
}
await _attachSimultaneousHeadsetMic();
} else if (currentMode.value == 'faceToFace') {
final bes = _besFaceToFaceSession();
@ -1921,7 +1937,9 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
// `Pged-Freezer: Freeze process` 冻结进程 → 两条 WebSocket 当场
// `Software caused connection abort` → Dart 当成 AST 故障 →
// _terminateCallOnError 把整个通话翻译关掉。用户看到的是「切个后台功能就没了」。
await AudioForegroundService.start(keepalive: true);
// 静音不能钉在手机扬声器上。华为上 setPreferredDevice(扬声器) 会把整路
// 输出从耳机拽走,通话和对译文都改从手机出来。跟同声翻译一样,跟着当前输出。
await AudioForegroundService.start(keepalive: true, pinSpeaker: false);
_callSession = await _awaitCallAudioSession();
_callModeUsingBes = _callSession != null;
_callAudioTornDown = false;
@ -2245,7 +2263,11 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
logged = true;
Logger.w('Translation', '同声翻译耳机麦首帧 ${pcm.length} 字节');
}
unawaited(_asrService.pushAudioData(pcm));
if (_sparkListen) {
SparkChainSession.write(pcm);
} else {
unawaited(_asrService.pushAudioData(pcm));
}
});
}
@ -2269,6 +2291,99 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
}
}
/// 同声翻译改走讯飞听写。密钥没有配好、或手机麦采集失败时返回 false,
/// 调用方继续用原来的识别,不影响没配讯飞的包。
Future<bool> _startSparkListen() async {
final directed = _directedShortCodes();
_sparkListen = true;
SparkChainSession.asrListener = _onSparkAsr;
final ok = await SparkChainSession.startListen(directed.source);
if (!ok) {
_sparkListen = false;
SparkChainSession.asrListener = null;
Logger.w('Translation', '同声翻译识别:SparkChain 未就绪,沿用原识别');
return false;
}
Logger.w('Translation', '同声翻译识别:SparkChain lang=${directed.source}');
if (_simHeadsetSession != null) return true;
final mic = MicPcmStreamer();
final started = await mic.start();
if (!started) {
Logger.w('Translation', '同声翻译手机麦采集失败,改回原识别');
await _stopSparkInput();
return false;
}
_sparkMic = mic;
_sparkMicSub = mic.packets.listen(SparkChainSession.write);
return true;
}
void _onSparkAsr(Map<String, dynamic> event) {
if (!_sparkListen) return;
if (event['kind'] == 'error') {
Logger.w('Translation',
'SparkChain 听写失败 code=${event['code']} sid=${event['sid']}');
_handleRecognitionEvent(RecognitionEvent(
type: RecognitionEventType.error,
error: '${event['code']}',
provider: 'sparkchain',
));
return;
}
final text = (event['text'] as String?) ?? '';
_handleRecognitionEvent(RecognitionEvent(
type: event['final'] == true
? RecognitionEventType.finalResult
: RecognitionEventType.intermediateResult,
text: text,
detectedLanguage: sourceLanguageCode.value,
provider: 'sparkchain',
));
}
/// 先停推流和听写,再由调用方关耳机麦、释放保活。
Future<void> _stopSparkInput() async {
await _sparkMicSub?.cancel();
_sparkMicSub = null;
final mic = _sparkMic;
_sparkMic = null;
if (mic != null) {
try {
await mic.stop();
} catch (e) {
Logger.w('Translation', '关闭讯飞手机麦失败: $e');
}
}
_sparkListen = false;
SparkChainSession.asrListener = null;
final player = _sparkPlayer;
_sparkPlayer = null;
try {
await player?.dispose();
} catch (e) {
Logger.w('Translation', '停止讯飞播报失败: $e');
}
await SparkChainSession.stopListen();
}
/// 译文 PCM 交给现有播放器。连着耳机时只放音,避免把声音钉在手机扬声器。
Future<void> _playSparkTts(String lang, String text) async {
final player = _sparkPlayer ??= PcmStreamPlayer(
sampleRate: 16000,
playbackOnly: _simHeadsetSession != null,
);
await player.begin();
try {
await SparkChainSession.speak(
lang: lang,
text: text,
onPcm: (pcm, _) => player.feed(pcm),
);
} finally {
await player.end();
}
}
/// 启动语音识别
///
/// 这一段是「首次进同传拉不起识别」的另一半嫌疑:原生
@ -2288,19 +2403,34 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
}
Future<void> _startAsrServiceInner(String mode) async {
final bool started;
if (mode == 'push_to_talk') {
await _asrService.startContinuousRecognition(
started = await _asrService.startContinuousRecognition(
_audioSourceType,
isRemoveFirstPunctuation: false,
mode: mode,
);
} else {
await _asrService.startContinuousRecognition(
started = await _asrService.startContinuousRecognition(
_audioSourceType,
isRemoveFirstPunctuation: true,
mode: mode,
);
}
if (!started) _showAzureSpeechMissing();
}
/// 微软配置没下发时给一条提示。页面还能留着,不能因为这个把进程打崩。
void _showAzureSpeechMissing() {
if (_asrService.configured) return;
const key = 'azureSpeechMissing';
final translated = key.tr;
ToastThrottle.show(
'asr.azure_missing',
title: 'error'.tr,
message: translated == key ? '未获取到微软语音配置,语音识别暂不可用' : translated,
window: ToastThrottle.gateWindow,
);
}
/// 完成识别启动
@ -2647,6 +2777,7 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
}
try {
await _stopSparkInput();
await stopRecording();
await _asrService.stopContinuousRecognition();
if (currentMode.value == "call") {
@ -2933,7 +3064,19 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
Future<String?> _performTranslation(String sourceText, String sourceCode,
String targetCode, CancelToken? cancelToken) async {
final directed = _directedShortCodes();
if (LanguageManager.microsoftTextPipeline(
if (_sparkListen) {
final onDevice = await SparkChainSession.translate(
from: directed.source,
to: directed.target,
text: sourceText,
);
if (onDevice != null) return onDevice;
if (!aiOnlyIflytek) {
Logger.w('Translation', '同声 SparkChain 翻译没有结果,改走原路径');
}
}
if (!aiOnlyIflytek &&
LanguageManager.microsoftTextPipeline(
currentMode.value, directed.source, directed.target)) {
final from = _languageManager.microsoftTranslateCode(directed.source);
final to = _languageManager.microsoftTranslateCode(directed.target);
@ -2950,6 +3093,7 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
sourceLanguageCode: sourceCode,
targetLanguageCode: targetCode,
cancelToken: cancelToken,
tryOnDevice: !_sparkListen,
);
}
@ -3123,8 +3267,12 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
Logger.info("voiceName=$voiceName,text=$text");
final startTime = DateTime.now();
await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(currsessionid, text);
if (_sparkListen) {
await _playSparkTts(directed.target, text);
} else {
await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(currsessionid, text);
}
final endTime = DateTime.now();
// 记录TTS API调用统计
@ -3715,12 +3863,16 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
} else {
Logger.info('重新初始化ASR服务,支持语言: $asrSupportedLanguages');
await _asrService.initialize(supportedLanguages: asrSupportedLanguages);
var recognitionStream = await _asrService.recognizeCallback();
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
final asrReady =
await _asrService.initialize(supportedLanguages: asrSupportedLanguages);
if (!asrReady) {
_showAzureSpeechMissing();
} else {
var recognitionStream = await _asrService.recognizeCallback();
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
}
}
Logger.info('ASR服务重新初始化成功');

4
apps/client/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -317,7 +317,9 @@ class AzureAsrHelper(private val context: Context) {
fun setupEventListeners(callback: ContinuousRecognizeCallback): Boolean {
Log.d(tag, "设置ssssss监听器:${speechConfig ?: "null"} ")
if (speechConfig == null) {
callback.onError(currsessionid, 1002, "语音服务未初始化")
// 未初始化时不能走 onError:插件的 onError 会立刻再调 recognizeCallback,
// 而这次 setup 还在栈上,等于自己套自己,一点同声翻译就栈溢出崩溃。
Log.e(tag, "语音服务未初始化,跳过挂监听")
return false
}

24
apps/client/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -759,9 +759,13 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin, ActivityAware,
}
//设置识别器
private var recognizeCallbackBusy = false
private fun recognizeCallback(): Boolean {
if (recognizeCallbackBusy) return false
recognizeCallbackBusy = true
try {
azureAsrHelper.setupEventListeners(object :
return azureAsrHelper.setupEventListeners(object :
AzureAsrHelper.ContinuousRecognizeCallback {
override fun onResult(sessionid: String, text: String, detectedLanguage: String) {
@ -831,9 +835,10 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin, ActivityAware,
recognizeCallback()
}
})
return true
} catch (e: Exception) {
return false
} finally {
recognizeCallbackBusy = false
}
}
@ -1412,8 +1417,19 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin, ActivityAware,
try {
val keepalive = call.argument<Boolean>("keepalive") ?: false
val pinSpeaker = call.argument<Boolean>("pinSpeaker") ?: true
AudioRecordingForegroundService.startService(context, keepalive, pinSpeaker)
result.success(true)
val holdFocus = call.argument<Boolean>("holdFocus") ?: false
if (holdFocus) {
// 结果要等服务进了前台、并且已经决定过音乐之后再回。
// 这里立刻 success 的话,后面的静音轨会赶在焦点判断之前响起来。
AudioRecordingForegroundService.startServiceHoldingFocus(
context,
pinSpeaker,
result,
)
} else {
AudioRecordingForegroundService.startService(context, keepalive, pinSpeaker)
result.success(true)
}
} catch (e: Exception) {
result.error("START_FGS_ERROR", e.message, null)
}

93
apps/client/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/DeviceDialogFocus.kt

@ -8,10 +8,12 @@ import android.media.AudioManager
import android.media.AudioPlaybackConfiguration
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.Process
import android.os.SystemClock
import android.util.Log
import android.view.KeyEvent
import com.yunqiinnovation.azure_speech.tools.AudioRecordingForegroundService
import io.flutter.plugin.common.MethodChannel
/**
@ -29,6 +31,9 @@ import io.flutter.plugin.common.MethodChannel
* 所以整段对话只持有一次 MEDIA 独占焦点。进来时**别的应用**正在播,才暂停,
* 结束时再补一次播放键。系统本来没在播,就不抢焦点、也不发播放键——
* 交还焦点或 MEDIA_PLAY 都会把华为音乐从暂停状态拉起来。
*
* 播放键必须在静音保活停掉之后发。音轨还在写的时候,键会落到我们自己身上,
* 音乐 App 收不到,用户只能切进那个 App 才会再响。
*/
object DeviceDialogFocus {
private const val TAG = "EmaiFocus"
@ -50,12 +55,20 @@ object DeviceDialogFocus {
}
fun hold(context: Context, handler: Handler, result: MethodChannel.Result) {
result.success(holdNow(context, handler))
}
/**
* 必须在本进程的静音音轨起来之前调用,并且调用时进程已经是前台服务。
* 后台直接申请焦点,Android 12 会拒绝,音乐继续响,香薰麦把音乐当成用户在说,
* 设备就一直停在聆听。
*/
fun holdNow(context: Context, handler: Handler = Handler(Looper.getMainLooper())): Map<String, Any?> {
val am = try {
manager(context)
} catch (e: Throwable) {
Log.e(TAG, "拿不到 AudioManager: ${e.message}")
result.success(mapOf("error" to (e.message ?: "no AudioManager")))
return
return mapOf("error" to (e.message ?: "no AudioManager"))
}
val before = music(am)
val external = externalStarted(am)
@ -72,21 +85,35 @@ object DeviceDialogFocus {
"系统没在播音乐,不抢焦点、不发媒体键 isMusicActive=$before " +
"importance=$importance ${Build.BRAND}/${Build.MODEL}"
)
result.success(
mapOf(
"gain" to "NONE",
"usage" to "MEDIA",
"result" to "SKIPPED",
"mediaPause" to false,
"externalPlaying" to false,
"musicBefore" to before,
"importance" to importance,
)
return mapOf(
"gain" to "NONE",
"usage" to "MEDIA",
"result" to "SKIPPED",
"mediaPause" to false,
"externalPlaying" to false,
"musicBefore" to before,
"importance" to importance,
)
return
}
abandon(am, "重抢前")
val outcome = request(am, AudioAttributes.USAGE_MEDIA, "MEDIA")
val granted = outcome.code == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
if (!granted) {
Log.w(
TAG,
"申请 EXCLUSIVE/MEDIA 未拿到,不发暂停键 结果=${outcome.name} " +
"importance=$importance ${Build.BRAND}/${Build.MODEL}"
)
return mapOf(
"gain" to "EXCLUSIVE",
"usage" to "MEDIA",
"result" to outcome.name,
"mediaPause" to false,
"externalPlaying" to true,
"musicBefore" to before,
"importance" to importance,
)
}
mediaKey(am, KeyEvent.KEYCODE_MEDIA_PAUSE)
pausedExternal = true
val after = music(am)
@ -96,24 +123,22 @@ object DeviceDialogFocus {
"mode=$mode importance=$importance 音乐 $before → $after " +
"sdk=${Build.VERSION.SDK_INT} ${Build.BRAND}/${Build.MODEL}"
)
result.success(
mapOf(
"gain" to "EXCLUSIVE",
"usage" to "MEDIA",
"result" to outcome.name,
"mediaPause" to pausedExternal,
"musicBefore" to before,
"musicAfter" to after,
"mode" to mode,
"importance" to importance,
"brand" to Build.BRAND,
"model" to Build.MODEL,
"sdk" to Build.VERSION.SDK_INT,
)
)
handler.postDelayed({
Log.i(TAG, "300ms 复查 音乐=${music(am)} 已发暂停键=$pausedExternal")
}, 300)
return mapOf(
"gain" to "EXCLUSIVE",
"usage" to "MEDIA",
"result" to outcome.name,
"mediaPause" to pausedExternal,
"musicBefore" to before,
"musicAfter" to after,
"mode" to mode,
"importance" to importance,
"brand" to Build.BRAND,
"model" to Build.MODEL,
"sdk" to Build.VERSION.SDK_INT,
)
}
fun release(context: Context): Map<String, Any?> {
@ -130,12 +155,22 @@ object DeviceDialogFocus {
"musicAfter" to before,
)
}
// 先停自己的静音轨,前台服务先留着。键要在我们不再占着媒体输出时发,
// 才会进到音乐 App;服务还在前台,后台发键会被华为丢掉。
AudioRecordingForegroundService.stopSilenceKeepalive()
SystemClock.sleep(120)
abandon(am, "交还")
pausedExternal = false
val afterAbandon = music(am)
mediaKey(am, KeyEvent.KEYCODE_MEDIA_PLAY)
var after = music(am)
if (!after) {
SystemClock.sleep(280)
mediaKey(am, KeyEvent.KEYCODE_MEDIA_PLAY)
after = music(am)
Log.i(TAG, "补发播放键 音乐=$after")
}
val sentPlay = true
val after = music(am)
Log.i(
TAG,
"交还 had=$had 播放键=$sentPlay 音乐 $before → $afterAbandon → $after"

90
apps/client/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/AudioRecordingForegroundService.kt

@ -14,11 +14,17 @@ import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioTrack
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.os.PowerManager
import androidx.core.app.NotificationCompat
import android.util.Log
import com.yunqiinnovation.azure_speech.DeviceDialogFocus
import io.flutter.plugin.common.MethodChannel
import org.json.JSONObject
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
import kotlin.math.max
/**
@ -44,6 +50,7 @@ class AudioRecordingForegroundService : Service() {
private const val KEEPALIVE_CHANNEL_ID = "session_keepalive"
private const val EXTRA_KEEPALIVE = "keepalive"
private const val EXTRA_PIN_SPEAKER = "pinSpeaker"
private const val EXTRA_HOLD_FOCUS = "holdFocus"
private const val ACTION_STOP = "com.yunqiinnovation.azure_speech.tools.STOP_SERVICE"
private const val NATIVE_LOCALIZATION_PREF_NAME = "native_localization"
private const val NATIVE_LOCALIZATION_PREF_KEY_STRINGS_JSON = "strings_json"
@ -63,11 +70,67 @@ class AudioRecordingForegroundService : Service() {
}
Log.i("EmaiFocus", "前台服务启动请求 keepalive=$keepalive")
}
@Volatile
private var running: AudioRecordingForegroundService? = null
private val pendingHold = AtomicReference<MethodChannel.Result?>(null)
private val holdGen = AtomicInteger(0)
/**
* 停止前台服务
* @param context 上下文
* 先进入前台(含媒体播放类型),再决定要不要暂停外面的音乐,最后才开静音轨。
* 结果在这一串做完之后才回调。后台直接抢焦点会被 Android 12 拒绝。
*/
fun startServiceHoldingFocus(
context: Context,
pinSpeaker: Boolean,
result: MethodChannel.Result,
) {
val gen = holdGen.incrementAndGet()
val intent = Intent(context, AudioRecordingForegroundService::class.java)
intent.putExtra(EXTRA_KEEPALIVE, true)
intent.putExtra(EXTRA_PIN_SPEAKER, pinSpeaker)
intent.putExtra(EXTRA_HOLD_FOCUS, true)
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
} catch (e: Exception) {
Log.e("EmaiFocus", "前台服务启动失败: ${e.message}")
result.error("START_FGS_ERROR", e.message, null)
return
}
val previous = pendingHold.getAndSet(result)
previous?.success(false)
Log.i("EmaiFocus", "前台服务启动请求 keepalive=true holdFocus pinSpeaker=$pinSpeaker")
Handler(Looper.getMainLooper()).postDelayed({
if (holdGen.get() != gen) return@postDelayed
val stuck = pendingHold.getAndSet(null)
if (stuck != null) {
Log.e("EmaiFocus", "前台服务未在时限内起来,焦点未抢")
stuck.success(false)
}
}, 2000)
}
fun stopSilenceKeepalive() {
val svc = running
if (svc == null) {
Log.i("EmaiFocus", "交还前没有静音保活可停")
return
}
svc.stopDialogKeepalive()
Log.i("EmaiFocus", "交还前已停静音保活")
}
private fun completeHold(ok: Boolean) {
holdGen.incrementAndGet()
val waiting = pendingHold.getAndSet(null)
waiting?.success(ok)
}
fun stopService(context: Context) {
val intent = Intent(context, AudioRecordingForegroundService::class.java)
// ⚠️ 用 stopService 而不是「startService + ACTION_STOP」。
@ -86,6 +149,7 @@ class AudioRecordingForegroundService : Service() {
override fun onCreate() {
super.onCreate()
running = this
Log.d(TAG, "AudioRecordingForegroundService onCreate")
createNotificationChannel()
}
@ -123,18 +187,33 @@ class AudioRecordingForegroundService : Service() {
Log.d(TAG, "AudioRecordingForegroundService onStartCommand action=${intent?.action}")
if (intent?.getBooleanExtra(EXTRA_KEEPALIVE, false) == true) {
keepaliveLatched = true
val wantPin = intent?.getBooleanExtra(EXTRA_PIN_SPEAKER, true) ?: true
// 上一场钉在扬声器时,音轨还在的话新的 pin 不会生效,通话翻译就会继续
// 把耳机路由拽走。钉法变了就先拆掉,下面按新的钉法重开。
if (silenceTrack != null && pinSilenceToSpeaker != wantPin) {
stopDialogKeepalive()
}
if (silenceTrack == null) {
pinSilenceToSpeaker =
intent?.getBooleanExtra(EXTRA_PIN_SPEAKER, true) ?: true
pinSilenceToSpeaker = wantPin
}
}
val keepalive = keepaliveLatched
val holdFocus = intent?.getBooleanExtra(EXTRA_HOLD_FOCUS, false) == true
// 创建前台通知
val notification = createNotification(keepalive)
// 必须立即调用 startForeground 以满足 Android 8.0+ 的要求
startForegroundCompat(notification, keepalive)
if (holdFocus) {
// 上一场若没停干净,自己的音轨会让 isMusicActive 变成 true,
// 这场就会误判「外面在播」或者反过来把外面的音乐漏掉。
if (silenceTrack != null) stopDialogKeepalive()
pinSilenceToSpeaker = intent?.getBooleanExtra(EXTRA_PIN_SPEAKER, true) ?: true
val snap = DeviceDialogFocus.holdNow(this)
Log.i("EmaiFocus", "设备对话焦点 $snap")
}
if (keepalive) startDialogKeepalive()
if (holdFocus) completeHold(true)
// 检查是否是停止指令
if (intent?.action == ACTION_STOP) {
@ -153,6 +232,7 @@ class AudioRecordingForegroundService : Service() {
}
override fun onDestroy() {
if (running === this) running = null
keepaliveLatched = false
pinSilenceToSpeaker = true
stopDialogKeepalive()

32
apps/client/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift

@ -324,8 +324,10 @@ import ble_service
// 录音相关
public var recordfile: RecordFile?
private var isRecord = false
// 创建事件回调
// 创建事件回调。A/B 都要留着:服务内部以前是 weak,只留 A 时
// B 路(远端)的识别回调会在初始化结束时被释放,表现为远端有声音但一个字都没有。
private var astEventCallback: AstEventCallback?
private var astEventCallbackB: AstEventCallback?
// 是否已添加AST事件监听器
private var isAstListenerAdded = false
@ -632,21 +634,14 @@ internal func handlePcmPlayerMethodCall(_ call: FlutterMethodCall, result: @esca
}
}
private static var deviceDialogNowPlayingReady = false
private static let deviceDialogNowPlayingTitle = "AI 助手"
/// 让系统把这次设备对话认成正在播放。不改会话类别。
/// 只清自己写上去的那条,避免抹掉别的模块的 Now Playing。
private func setDeviceDialogNowPlaying(_ active: Bool) {
if active {
if !Self.deviceDialogNowPlayingReady {
let commands = MPRemoteCommandCenter.shared()
commands.playCommand.isEnabled = true
commands.playCommand.addTarget { _ in .success }
commands.pauseCommand.isEnabled = true
commands.pauseCommand.addTarget { _ in .success }
Self.deviceDialogNowPlayingReady = true
}
// 不注册 play/pause。返回 .success 会让系统以为播放键已经有人处理,
// 对话结束时音乐 App 收不到继续播放,要用户切进那个 App 才会再响。
MPNowPlayingInfoCenter.default().nowPlayingInfo = [
MPMediaItemPropertyTitle: Self.deviceDialogNowPlayingTitle,
MPNowPlayingInfoPropertyPlaybackRate: 1.0,
@ -1370,7 +1365,11 @@ private func sendAudioDataEvent(_ event: [String: Any]) {
let args = call.arguments as? [String: Any]
let enabled = (args?["enabled"] as? Bool) ?? false
if enabled {
ensureCallPcmBridge().enable()
let b = ensureCallPcmBridge()
b.enable()
// 同页面第二场:helper 早就 initialize 过、不会再发 markReady。
// 按插件上的就绪状态补一次,否则上行全扣在缓冲里,界面没动静。
if isAstSessionReady { b.markReady() }
} else {
callPcmBridge?.disable()
}
@ -1399,6 +1398,8 @@ private func sendAudioDataEvent(_ event: [String: Any]) {
os_log("[AST-DISPOSE] Releasing all AST resources, provider=%{public}@", log: ctLog, type: .info, currentAstProvider)
azureAstHelperA.dispose()
azureAstHelperB.dispose()
astEventCallback = nil
astEventCallbackB = nil
doubaoAstHelperA.dispose()
doubaoAstHelperB.dispose()
bailianAstHelperA.dispose()
@ -1500,6 +1501,7 @@ private func sendAudioDataEvent(_ event: [String: Any]) {
let callbackA = AstEventCallback(plugin: self, serviceId: "A")
let callbackB = AstEventCallback(plugin: self, serviceId: "B")
astEventCallback = callbackA
astEventCallbackB = callbackB
azureAstHelperA.serviceId = "A"
azureAstHelperB.serviceId = "B"
@ -1798,6 +1800,8 @@ private class AstEventCallback: IntegratedSpeechTranslationService.ServiceEventC
"utteranceId": utteranceId,
"text": text
])
// 段尾空帧,和阿里回调、安卓 markEnd 同一份合约,下行才能把这一句收完。
plugin?.pushAstTtsFrame(leg: serviceId, pcm: Data(), isFinal: true)
}
public func onSynthesisFailed(utteranceId: String, text: String, error: String) {
@ -1854,9 +1858,11 @@ private class AstEventCallback: IntegratedSpeechTranslationService.ServiceEventC
public func onSynthesisAudioGenerated(utteranceId: String, text: String, audioData: Data) {
let pcmData = audioData
print("[\(serviceId)] 合成音频生成,PCM大小=\(pcmData.count),合成ID=\(utteranceId)")
os_log("[AST-TTS] 微软合成 leg=%{public}@ pcm=%d", log: OSLog(subsystem: "com.azure.speech", category: "CallTranslation"), type: .info, serviceId, pcmData.count)
if pcmData.count > 0 {
BleService.shared.writeExternalAudioData(data: pcmData)
// 爱尔兰语等扩展语种走微软三段拼装,合成音必须进耳机下行。
// 旧的 BleService 写入对恒玄是空操作,耳机里就没有译文。
plugin?.pushAstTtsFrame(leg: serviceId, pcm: pcmData, isFinal: false)
}
}
}

4
apps/client/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/BesCallPcmBridge.swift

@ -91,7 +91,9 @@ final class BesCallPcmBridge {
ingress.async {
NSLog("[BesCallPcmBridge] 已关闭 A=\(self.framesA) B=\(self.framesB) 丢帧=\(self.dropped) B门限前=\(self.gatedB) 下行=\(self.downlinkFrames) 峰值积压=\(self.maxPendingSeen)")
self.holdA.removeAll(); self.holdB.removeAll()
self.ready = false
// 不把 ready 打回 false。它表示 AST helper 起来了没,只由
// onSessionInit / onSessionDisposed 翻假。在这里清掉的话,同页面第二场
// 不会再 initialize,也就没人 markReady,之后每一场上行都扣在缓冲里。
}
}

36
apps/client/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/IntegratedSpeechTranslationService.swift

@ -69,6 +69,8 @@ import os.log
private var recognizingTranslationTask: Task<Void, Never>?
private var currentSynthesisId: String?
private var currentSynthesisText: String?
private var azureKey: String = ""
private var azureRegion: String = ""
/**
* 生成新的 utteranceId
@ -79,8 +81,9 @@ import os.log
return "utt-\(serviceId)-\(ts)-\(utteranceSeq)"
}
// 事件回调
private weak var eventCallback: ServiceEventCallback?
// 事件回调。必须强引用:调用方(插件)如果只把 B 路回调留在局部变量里,
// weak 会在 initialize 返回后立刻变 nil,远端识别结果一个都送不出去。
private var eventCallback: ServiceEventCallback?
// 语音映射缓存
private var voiceCache: [String: String] = [:]
@ -310,15 +313,19 @@ import os.log
do {
os_log("开始初始化Azure服务", log: log, type: .info)
azureKey = azureConfig.subscriptionKey
azureRegion = azureConfig.region
speechConfig = try SPXSpeechConfiguration(subscription: azureConfig.subscriptionKey, region: azureConfig.region)
guard let config = speechConfig else {
return false
}
// 识别配置上只钉这一路要听的语种。远端中→爱尔兰就是 ga-IE,
// 不能再混进中文,否则开头的串音会把整场锁成中文,爱尔兰语永远无字。
// 合成音色另建一份配置,避免和识别语种抢同一份 SpeechConfig。
config.speechRecognitionLanguage = serviceConfig.sourceLanguage
config.speechSynthesisVoiceName = getVoiceForLanguage(serviceConfig.targetLanguage)
config.setSpeechSynthesisOutputFormat(.raw16Khz16BitMonoPcm)
config.setPropertyTo("300", by: SPXPropertyId.speechSegmentationSilenceTimeoutMs)
// 自动语言检测
if serviceConfig.enableAutoLanguageDetection {
@ -379,6 +386,8 @@ import os.log
}
do {
speechConfig.speechRecognitionLanguage = serviceConfig.sourceLanguage
os_log("识别器语种: %@ service=%@", log: log, type: .info, serviceConfig.sourceLanguage, serviceId)
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig, audioConfiguration: audioConfig)
// 识别中事件
@ -449,7 +458,7 @@ import os.log
}
case .noMatch:
os_log("未识别到语音", log: self.log, type: .debug)
os_log("未识别到语音 lang=%@", log: self.log, type: .info, self.serviceConfig.sourceLanguage)
default:
os_log("识别结果: %@", log: self.log, type: .info, String(describing: result.reason))
@ -501,6 +510,7 @@ import os.log
} catch {
os_log("语音识别器设置失败: %@", log: log, type: .error, error.localizedDescription)
eventCallback?.onError(component: "Recognition", error: "识别器创建失败[\(serviceId)] \(serviceConfig.sourceLanguage): \(error.localizedDescription)")
}
}
@ -521,8 +531,16 @@ import os.log
// 不输出到扬声器,只生成音频数据
audioConfig = nil
}
synthesizer = try SPXSpeechSynthesizer(speechConfiguration: speechConfig, audioConfiguration: audioConfig)
let synthConfig: SPXSpeechConfiguration
if !azureKey.isEmpty && !azureRegion.isEmpty {
synthConfig = try SPXSpeechConfiguration(subscription: azureKey, region: azureRegion)
} else {
synthConfig = speechConfig
}
synthConfig.speechSynthesisVoiceName = getVoiceForLanguage(serviceConfig.targetLanguage)
synthConfig.setSpeechSynthesisOutputFormat(.raw16Khz16BitMonoPcm)
synthesizer = try SPXSpeechSynthesizer(speechConfiguration: synthConfig, audioConfiguration: audioConfig)
// 合成开始事件
synthesizer?.addSynthesisStartedEventHandler { [weak self] (synthesizer, event) in
@ -747,8 +765,7 @@ import os.log
do {
let srcLang = self.serviceConfig.translationSourceLanguage.isEmpty ? self.serviceConfig.sourceLanguage : self.serviceConfig.translationSourceLanguage
let tgtLang = self.serviceConfig.translationTargetLanguage.isEmpty ? self.serviceConfig.targetLanguage : self.serviceConfig.translationTargetLanguage
os_log("翻译语言对: %@ -> %@", log: log, type: .debug, srcLang, tgtLang)
os_log("翻译语言对: %@ -> %@", log: log, type: .info, srcLang, tgtLang)
let translationResult = try await withTimeout(self.serviceConfig.translationTimeout) {
os_log("开始翻译文本: %@", log: self.log, type: .debug, text)
return await self.translationService?.translateText(
@ -1179,6 +1196,7 @@ import os.log
recordFile?.closeFile(isSave: false)
recordFile = nil
eventCallback = nil
// 清理缓存
voiceCache.removeAll()

19
apps/client/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift

@ -137,7 +137,13 @@ public class SimpleAudioReceiver: NSObject {
_isWriting = true
print("liwei---------- startAudioRecord=mod:\(mod), audioSourceType:\(audioSourceType), isHeadphones:\(isHeadphones)")
// 外部 PCM(耳机通话的双路)不改音频会话。两路识别器都会走到这里,
// 第二路再 setCategory/setActive 会把刚起来的识别会话打断,
// 表现就是近端中文正常、远端爱尔兰语一句都没有。
if audioSourceType == .external {
runExternalCapture()
return
}
do {
try configureAudioSession()
@ -375,12 +381,17 @@ public class SimpleAudioReceiver: NSObject {
* @param data 音频数据字节数组
*/
public func saveAudioDataTo(data: Data) {
// 通过父类引用调用方法
if audioSourceType != .external {
return
}
// 放入队列,由写线程写入
self.writeQueue.put(data)
guard isContinuousRecognitionActive, let stream = pushAudioStream else {
return
}
do {
try stream.write(data)
} catch {
os_log("写入识别器失败: %@", log: log, type: .error, error.localizedDescription)
}
}
/**

31
apps/client/local_plugins/sparkchain/android/build.gradle.kts

@ -0,0 +1,31 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.yunqiinnovation.sparkchain"
compileSdk = 35
defaultConfig {
minSdk = 21
consumerProguardFiles("consumer-rules.pro")
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
sourceSets.getByName("main").jniLibs.srcDirs("src/main/jniLibs")
}
// 插件自己会打成 AAR,不能再 implementation 本地 AAR,否则 release 打包直接失败。
// SparkChain.aar / Codec.aar 已拆成 libs/*.jar、jniLibs 和 assets。
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
}

1
apps/client/local_plugins/sparkchain/android/consumer-rules.pro

@ -0,0 +1 @@
-keep class com.iflytek.sparkchain.** { *; }

BIN
apps/client/local_plugins/sparkchain/android/libs/Codec.aar

Binary file not shown.

BIN
apps/client/local_plugins/sparkchain/android/libs/Codec.jar

Binary file not shown.

BIN
apps/client/local_plugins/sparkchain/android/libs/SparkChain.aar

Binary file not shown.

BIN
apps/client/local_plugins/sparkchain/android/libs/SparkChain.jar

Binary file not shown.

3
apps/client/local_plugins/sparkchain/android/src/main/AndroidManifest.xml

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

17
apps/client/local_plugins/sparkchain/android/src/main/assets/supplierconfig.json

@ -0,0 +1,17 @@
{
"supplier":{
"vivo":{
"appid":""
},
"xiaomi":{
"appid":""
},
"huawei":{
"appid":"103256863"
},
"oppo":{
}
}
}

334
apps/client/local_plugins/sparkchain/android/src/main/kotlin/com/yunqiinnovation/sparkchain/SparkChainPlugin.kt

@ -0,0 +1,334 @@
package com.yunqiinnovation.sparkchain
import android.content.Context
import android.os.Handler
import android.os.Looper
import com.iflytek.sparkchain.core.SparkChain
import com.iflytek.sparkchain.core.SparkChainConfig
import com.iflytek.sparkchain.core.asr.ASR
import com.iflytek.sparkchain.core.asr.AsrCallbacks
import com.iflytek.sparkchain.core.asr.AudioAttributes
import com.iflytek.sparkchain.core.asr.RegionType
import com.iflytek.sparkchain.core.common.ApiType
import com.iflytek.sparkchain.core.its.ITS
import com.iflytek.sparkchain.core.its.ITSCallbacks
import com.iflytek.sparkchain.core.its.TransType
import com.iflytek.sparkchain.core.rtasr.RTASR
import com.iflytek.sparkchain.core.rtasr.RTASRCallbacks
import com.iflytek.sparkchain.core.tts.OnlineTTS
import com.iflytek.sparkchain.core.tts.TTS
import com.iflytek.sparkchain.core.tts.TTSCallbacks
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 java.util.concurrent.Executors
/// 讯飞海外 SDK。不自己开麦,PCM 由调用方 write 进来。
class SparkChainPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
private val main = Handler(Looper.getMainLooper())
// 听写的 write 不能排在翻译后面:arun 若卡住,耳机麦帧会在队列里过期。
private val audio = Executors.newSingleThreadExecutor()
private val cloud = Executors.newSingleThreadExecutor()
private var channel: MethodChannel? = null
private var events: EventChannel? = null
private var sink: EventChannel.EventSink? = null
private var appContext: Context? = null
private var ready = false
private var asr: ASR? = null
private var rtasr: RTASR? = null
private var its: ITS? = null
private var tts: OnlineTTS? = null
private val asrCallbacks = object : AsrCallbacks {
override fun onResult(result: ASR.ASRResult, usrTag: Any?) {
val text = result.bestMatchText ?: ""
emit(
mapOf(
"kind" to "asr",
"id" to (usrTag?.toString() ?: ""),
"text" to text,
"final" to (result.status == 2),
"sid" to (result.sid ?: ""),
),
)
}
override fun onError(error: ASR.ASRError, usrTag: Any?) {
emit(
mapOf(
"kind" to "error",
"where" to "asr",
"id" to (usrTag?.toString() ?: ""),
"code" to error.code,
"message" to (error.errMsg ?: ""),
"sid" to (error.sid ?: ""),
),
)
}
}
private val rtasrCallbacks = object : RTASRCallbacks {
override fun onResult(result: RTASR.RtAsrResult, usrTag: Any?) {
val status = result.status
emit(
mapOf(
"kind" to "asr",
"id" to (usrTag?.toString() ?: ""),
"text" to (result.data ?: ""),
"final" to (status == 2 || status == 3),
"sid" to (result.sid ?: ""),
),
)
}
override fun onError(error: RTASR.RtAsrError, usrTag: Any?) {
emit(
mapOf(
"kind" to "error",
"where" to "asr",
"id" to (usrTag?.toString() ?: ""),
"code" to error.code,
"message" to (error.errMsg ?: ""),
"sid" to (error.sid ?: ""),
),
)
}
}
private val itsCallbacks = object : ITSCallbacks {
override fun onResult(result: ITS.ITSResult, usrTag: Any?) {
val dst = result.transResult?.dst ?: ""
emit(
mapOf(
"kind" to "its",
"id" to (usrTag?.toString() ?: ""),
"text" to dst,
"sid" to (result.sid ?: ""),
),
)
}
override fun onError(error: ITS.ITSError, usrTag: Any?) {
emit(
mapOf(
"kind" to "error",
"where" to "its",
"id" to (usrTag?.toString() ?: ""),
"code" to error.code,
"message" to (error.errMsg ?: ""),
"sid" to (error.sid ?: ""),
),
)
}
}
private val ttsCallbacks = object : TTSCallbacks {
override fun onResult(result: TTS.TTSResult, usrTag: Any?) {
val audio = result.data
emit(
mapOf(
"kind" to "tts",
"id" to (usrTag?.toString() ?: ""),
"pcm" to (audio ?: ByteArray(0)),
"final" to (result.status == 2),
"sid" to (result.sid ?: ""),
),
)
}
override fun onError(error: TTS.TTSError, usrTag: Any?) {
emit(
mapOf(
"kind" to "error",
"where" to "tts",
"id" to (usrTag?.toString() ?: ""),
"code" to error.code,
"message" to (error.errMsg ?: ""),
"sid" to (error.sid ?: ""),
),
)
}
}
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
appContext = binding.applicationContext
channel = MethodChannel(binding.binaryMessenger, "sparkchain").also {
it.setMethodCallHandler(this)
}
events = EventChannel(binding.binaryMessenger, "sparkchain/events").also {
it.setStreamHandler(this)
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel?.setMethodCallHandler(null)
events?.setStreamHandler(null)
channel = null
events = null
sink = null
}
override fun onListen(arguments: Any?, eventSink: EventChannel.EventSink) {
sink = eventSink
}
override fun onCancel(arguments: Any?) {
sink = null
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"init" -> cloud.execute {
val code = doInit(call)
main.post { result.success(code) }
}
"translate" -> cloud.execute {
val code = doTranslate(call)
main.post { result.success(code) }
}
"speak" -> cloud.execute {
val code = doSpeak(call)
main.post { result.success(code) }
}
"startListen" -> audio.execute {
val code = doStartListen(call)
main.post { result.success(code) }
}
"write" -> audio.execute {
val pcm = call.argument<ByteArray>("pcm")
val code = when {
rtasr != null && pcm != null -> rtasr!!.write(pcm)
asr != null && pcm != null -> asr!!.write(pcm)
else -> -1
}
main.post { result.success(code) }
}
"stopListen" -> audio.execute {
rtasr?.stop(false)
asr?.stop(false)
rtasr = null
asr = null
main.post { result.success(0) }
}
"uninit" -> cloud.execute {
val code = if (ready) SparkChain.getInst().unInit() else 0
ready = false
main.post { result.success(code) }
}
else -> result.notImplemented()
}
}
private fun doInit(call: MethodCall): Int {
val ctx = appContext ?: return -1
val appId = call.argument<String>("appId") ?: return -2
val apiKey = call.argument<String>("apiKey") ?: return -2
val apiSecret = call.argument<String>("apiSecret") ?: return -2
if (ready) {
SparkChain.getInst().unInit()
ready = false
}
val apiType = when (call.argument<Int>("apiType") ?: 1) {
0 -> ApiType.TYPE_CH
2 -> ApiType.TYPE_EU
else -> ApiType.TYPE_SG
}
val work = java.io.File(ctx.filesDir, "sparkchain")
work.mkdirs()
val config = SparkChainConfig.builder()
.appID(appId)
.apiKey(apiKey)
.apiSecret(apiSecret)
.workDir(work.absolutePath)
.logLevel(3)
.apiType(apiType)
val code = SparkChain.getInst().init(ctx, config)
ready = code == 0
return code
}
/// GTRANS 的中文码是 zh-CN,爱尔兰语是 ga。听写中文码是 zh_cn,两套不要混。
private fun itsCode(short: String): String = when (short) {
"zh" -> "zh-CN"
"en" -> "en"
"ga" -> "ga"
else -> short
}
private fun asrCode(short: String): String = when (short) {
"zh" -> "zh_cn"
"en" -> "en_us"
else -> short
}
private fun doTranslate(call: MethodCall): Int {
if (!ready) return -1
val text = call.argument<String>("text") ?: return -2
val id = call.argument<String>("id") ?: ""
val from = itsCode(call.argument<String>("from") ?: "zh")
val to = itsCode(call.argument<String>("to") ?: "en")
val engine = ITS(TransType.GTRANS)
engine.registerCallbacks(itsCallbacks)
engine.fromlanguage(from)
engine.tolanguage(to)
its = engine
return engine.arun(text, id)
}
private fun doSpeak(call: MethodCall): Int {
if (!ready) return -1
val text = call.argument<String>("text") ?: return -2
val id = call.argument<String>("id") ?: ""
val lang = call.argument<String>("lang") ?: "zh"
val voice = call.argument<String>("voice")
val (region, vcn) = when (lang) {
"ga" -> RegionType.MSFT_TYPE to (voice ?: "ga-IE-OrlaNeural")
"en" -> null to (voice ?: "x_Catherine")
else -> null to (voice ?: "x_xiaoyan")
}
val engine = if (region != null) OnlineTTS(region, vcn) else OnlineTTS(vcn)
engine.aue("raw")
engine.registerCallbacks(ttsCallbacks)
tts = engine
return engine.aRun(text, id)
}
private fun doStartListen(call: MethodCall): Int {
if (!ready) return -1
rtasr?.stop(true)
asr?.stop(true)
rtasr = null
asr = null
val lang = call.argument<String>("lang") ?: "zh"
val id = call.argument<String>("id") ?: ""
if (lang == "ga") {
val engine = RTASR(null, RegionType.MSFT_TYPE)
engine.registerCallbacks(rtasrCallbacks)
engine.setParams("language_name", "ga-IE")
engine.OstEncoding("raw")
rtasr = engine
return engine.start(id)
}
val engine = ASR()
engine.registerCallbacks(asrCallbacks)
engine.language(asrCode(lang))
engine.domain("iat")
engine.accent("mandarin")
if (lang == "zh") engine.dwa("wpgs")
val attr = AudioAttributes()
attr.sampleRate = 16000
attr.encoding = "raw"
attr.channels = 1
attr.bitdepth = 16
asr = engine
// 带 AudioAttributes 的 start 只收 write 进来的 PCM,不让 SDK 自己开麦。
return engine.start(attr, id)
}
private fun emit(event: Map<String, Any>) {
val current = sink ?: return
main.post { current.success(event) }
}
}

27
apps/client/local_plugins/sparkchain/ios/sparkchain/Package.swift

@ -0,0 +1,27 @@
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "sparkchain",
platforms: [.iOS("13.0")],
products: [
.library(name: "sparkchain", targets: ["sparkchain"])
],
targets: [
.target(
name: "SparkChainGlue",
dependencies: ["SparkChain"],
path: "Sources/glue",
publicHeadersPath: "include"
),
.target(
name: "sparkchain",
dependencies: ["SparkChain", "SparkChainGlue"],
path: "Sources/sparkchain"
),
.binaryTarget(
name: "SparkChain",
path: "SparkChain.xcframework"
)
]
)

21
apps/client/local_plugins/sparkchain/ios/sparkchain/Sources/glue/SparkChainGlue.m

@ -0,0 +1,21 @@
#import "SparkChainGlue.h"
#import <SparkChain/SparkChain.h>
int SparkChainGlueInit(NSString *appId,
NSString *apiKey,
NSString *apiSecret,
NSString *workDir,
int apiType) {
SparkChainConfig *config = [[SparkChainConfig alloc] init];
config.appID(appId)
.apiKey(apiKey)
.apiSecret(apiSecret)
.workDir(workDir)
.logLevel(3)
.apiType(apiType);
return [[SparkChain getInst] init:config];
}
int SparkChainGlueUnInit(void) {
return [[SparkChain getInst] unInit];
}

9
apps/client/local_plugins/sparkchain/ios/sparkchain/Sources/glue/include/SparkChainGlue.h

@ -0,0 +1,9 @@
#import <Foundation/Foundation.h>
/// SDK 的 init: 和方法名冲突,放在 ObjC 里调用。返回 0 表示成功。
int SparkChainGlueInit(NSString *_Nonnull appId,
NSString *_Nonnull apiKey,
NSString *_Nonnull apiSecret,
NSString *_Nonnull workDir,
int apiType);
int SparkChainGlueUnInit(void);

270
apps/client/local_plugins/sparkchain/ios/sparkchain/Sources/sparkchain/SparkChainPlugin.swift

@ -0,0 +1,270 @@
import Flutter
import SparkChain
import SparkChainGlue
/// 回调对象必须由插件强引用。SDK 里的 delegate 是 weak,局部变量会在 start 返回后被释放。
@objc(SparkChainPlugin)
public final class SparkChainPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
ASRCallbacks, RTASRCallback, ITSCallbacks, TTSCallbacks {
private let channelName = "sparkchain"
private var events: FlutterEventSink?
private var ready = false
private var asr: ASR?
private var rtasr: RTASR?
private var its: ITS?
private var tts: OnlineTTS?
private let transOrder: [TransType] = [GOOGLETRANS, NIUTRANS, ITRANS_SG, ITRANS]
private var transIndex = 0
private var pendingIts: [String: String] = [:]
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = SparkChainPlugin()
let channel = FlutterMethodChannel(name: "sparkchain", binaryMessenger: registrar.messenger())
registrar.addMethodCallDelegate(instance, channel: channel)
let events = FlutterEventChannel(name: "sparkchain/events", binaryMessenger: registrar.messenger())
events.setStreamHandler(instance)
}
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.events = events
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
events = nil
return nil
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let args = call.arguments as? [String: Any] ?? [:]
switch call.method {
case "init":
result(doInit(args))
case "translate":
result(doTranslate(args))
case "speak":
result(doSpeak(args))
case "startListen":
result(doStartListen(args))
case "write":
let data = args["pcm"] as? FlutterStandardTypedData
if let rtasr, let data {
result(rtasr.write(data.data))
} else if let asr, let data {
result(asr.write(data.data))
} else {
result(-1)
}
case "stopListen":
rtasr?.stop(false)
asr?.stop(false)
rtasr = nil
asr = nil
result(0)
case "uninit":
let code = ready ? SparkChainGlueUnInit() : 0
ready = false
result(code)
default:
result(FlutterMethodNotImplemented)
}
}
private func doInit(_ args: [String: Any]) -> Int {
guard let appId = args["appId"] as? String,
let apiKey = args["apiKey"] as? String,
let apiSecret = args["apiSecret"] as? String else {
return -2
}
if ready {
SparkChainGlueUnInit()
ready = false
}
let apiType = args["apiType"] as? Int ?? 1
let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
.appendingPathComponent("sparkchain", isDirectory: true)
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
let code = SparkChainGlueInit(appId, apiKey, apiSecret, dir.path, Int32(apiType))
ready = code == 0
return Int(code)
}
/// 大语种表用 zh-CN / ga。ITRANS 那张小表的中文是 cn,重试到 ITRANS 时再换。
private func itsCode(_ short: String, smallTable: Bool) -> String {
switch short {
case "zh": return smallTable ? "cn" : "zh-CN"
case "en": return "en"
case "ga": return "ga"
default: return short
}
}
private func doTranslate(_ args: [String: Any]) -> Int {
guard ready, let text = args["text"] as? String else { return ready ? -2 : -1 }
let id = args["id"] as? String ?? ""
let from = args["from"] as? String ?? "zh"
let to = args["to"] as? String ?? "en"
pendingIts[id] = "\(from)|\(to)|\(text)"
return runIts(id: id, from: from, to: to, text: text)
}
private func runIts(id: String, from: String, to: String, text: String) -> Int {
let type = transOrder[transIndex]
let small = type == ITRANS || type == ITRANS_SG
let engine = ITS(formLanguage: itsCode(from, smallTable: small), toLanguage: itsCode(to, smallTable: small), type: type)
engine.callbacks = self
its = engine
return Int(engine.arun(text, userInfo: id))
}
private func doSpeak(_ args: [String: Any]) -> Int {
guard ready, let text = args["text"] as? String else { return ready ? -2 : -1 }
let id = args["id"] as? String ?? ""
let lang = args["lang"] as? String ?? "zh"
let voice = args["voice"] as? String
let engine: OnlineTTS
if lang == "ga" {
engine = OnlineTTS(region: RegionMSFT, vcn: voice ?? "ga-IE-OrlaNeural")
} else if lang == "en" {
engine = OnlineTTS(vcn: voice ?? "x_Catherine")
} else {
engine = OnlineTTS(vcn: voice ?? "x_xiaoyan")
}
engine.aue = "raw"
engine.cbs = self
tts = engine
return Int(engine.arun(text, userInfo: id))
}
private func doStartListen(_ args: [String: Any]) -> Int {
guard ready else { return -1 }
rtasr?.stop(true)
asr?.stop(true)
rtasr = nil
asr = nil
let lang = args["lang"] as? String ?? "zh"
let id = args["id"] as? String ?? ""
if lang == "ga" {
let engine = RTASR(region: RegionMSFT)
engine.delegate = self
engine.addStringParam("language_name", value: "ga-IE")
rtasr = engine
return Int(engine.start(withUserInfo: id))
}
let engine = ASR()
engine.cbs = self
let code = lang == "en" ? "en_us" : "zh_cn"
engine.language(code)
engine.domain("iat")
engine.accent("mandarin")
if lang == "zh" { engine.dwa("wpgs") }
let attr = AudioAttributes()
attr.sampleRate = 16000
attr.encoding = "raw"
attr.channels = 1
attr.bitdepth = 16
asr = engine
return Int(engine.start(attr, usrTag: id))
}
private func emit(_ event: [String: Any]) {
let sink = events
DispatchQueue.main.async { sink?(event) }
}
public func onResult(_ result: ASRResult, usrTag: Any) {
emit([
"kind": "asr",
"id": (usrTag as? String) ?? "",
"text": result.bestMatchText,
"final": result.status == 2,
"sid": result.sid,
])
}
public func onError(_ error: ASRError, usrTag: Any) {
emit([
"kind": "error",
"where": "asr",
"id": (usrTag as? String) ?? "",
"code": error.code,
"message": error.errMsg,
"sid": error.sid,
])
}
public func onResult(_ result: RTASRResult, useInfo userInfo: Any?) {
let status = result.status
emit([
"kind": "asr",
"id": (userInfo as? String) ?? "",
"text": result.data,
"final": status == 2 || status == 3,
"sid": result.sid,
])
}
public func onError(_ error: RTASRError, useInfo userInfo: Any?) {
emit([
"kind": "error",
"where": "asr",
"id": (userInfo as? String) ?? "",
"code": error.code,
"message": error.errMsg,
"sid": error.sid,
])
}
public func its(_ its: ITS, on result: ITSResult, userInfo: Any) {
emit([
"kind": "its",
"id": (userInfo as? String) ?? "",
"text": result.trans.dst,
"sid": result.sid,
])
}
public func its(_ its: ITS, onError error: ITSError, userInfo: Any) {
let id = (userInfo as? String) ?? ""
if transIndex + 1 < transOrder.count, let packed = pendingIts[id] {
transIndex += 1
let parts = packed.split(separator: "|", maxSplits: 2).map(String.init)
if parts.count == 3 {
_ = runIts(id: id, from: parts[0], to: parts[1], text: parts[2])
return
}
}
emit([
"kind": "error",
"where": "its",
"id": id,
"code": error.code,
"message": error.errMsg,
"sid": error.sid,
])
}
public func tts(_ tts: TTS, on result: TTSResult, usrContext: Any) {
emit([
"kind": "tts",
"id": (usrContext as? String) ?? "",
"pcm": FlutterStandardTypedData(bytes: result.data),
"final": result.status == 2,
"sid": result.sid,
])
}
public func tts(_ tts: TTS, onError error: TTSError, usrContext: Any) {
emit([
"kind": "error",
"where": "tts",
"id": (usrContext as? String) ?? "",
"code": error.code,
"message": error.errMsg,
"sid": error.sid,
])
}
}

27
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/Info.plist

@ -0,0 +1,27 @@
<?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>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>SparkChain.framework/SparkChain</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>SparkChain.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

64
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/AIPPT.h

@ -0,0 +1,64 @@
//
// AIPPT.h
// SparkChain_Mac
//
// Created by pcfang on 21.5.24.
//
#import <Foundation/Foundation.h>
@class PPTResult;
@class PPTError;
@class AIPPT;
NS_ASSUME_NONNULL_BEGIN
@protocol PPTCallback <NSObject>
- (void)ppt:(AIPPT *)ppt onResult:(PPTResult *)result userInfo:(id)userInfo;
- (void)ppt:(AIPPT *)ppt onError:(PPTError *)error userInfo:(id)userInfo;
@end
@interface AIPPT : NSObject
@property (nonatomic, weak) id<PPTCallback> callback;
- (instancetype)init;
- (void)createModel:(NSString *)createModel;
- (void)theme:(NSString *)theme;
- (void)businessId:(NSString *)businessId;
- (void)author:(NSString *)author;
- (void)isCardNote:(BOOL)isCardNote;
- (int)asyncPPTFromText:(NSString *)text;
- (int)asyncPPTFromText:(NSString *)text userInfo:(id _Nullable)userInfo;
- (int)asyncPPTOutlineCreat:(NSString *)text;
- (int)asyncPPTOutlineCreat:(NSString *)text userInfo:(id _Nullable)userInfo;
- (int)asyncPPTFromSid:(NSString *)sid outline:(NSString * _Nullable)outline;
- (int)asyncPPTFromSid:(NSString *)sid outline:(NSString * _Nullable)outline userInfo:(id _Nullable)userInfo;
- (int)asyncPPTFromOutline:(NSString *)outline query:(NSString *)query;
- (int)asyncPPTFromOutline:(NSString *)outline query:(NSString *)query userInfo:(id _Nullable)userInfo;
- (int)asyncPPTGetTopicList;
- (int)asyncPPTGetTopicListWithUserInfo:(id _Nullable)userInfo;
- (int)asyncPPTGetResult:(NSString *)sid;
- (int)asyncPPTGetResult:(NSString *)sid userInfo:(id _Nullable)userInfo;
@end
NS_ASSUME_NONNULL_END

83
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASR.h

@ -0,0 +1,83 @@
//
// ASR.h
// SparkChain_Mac
//
// Created by XX on 2024/5/10.
//
#ifndef ASR_h
#define ASR_h
#import <Foundation/Foundation.h>
#import <SparkChain/SCEnums.h>
#import "AudioAttributes.h"
#import "ASRCallbacks.h"
#import "ASRHotwordsResult.h"
NS_ASSUME_NONNULL_BEGIN
@interface ASR : NSObject
@property (nonatomic, weak) id<ASRCallbacks> cbs;
- (instancetype)init;
- (instancetype)initWithRegion:(RegionType)region;
- (instancetype)initWithLanguage:(NSString *)language domain:(NSString *)domain accent:(NSString *)accent;
- (instancetype)initWithLanguage:(NSString *)language domain:(NSString *)domain accent:(NSString *)accent region:(RegionType)region;
- (int)start:(AudioAttributes *)attributes;
- (int)start:(AudioAttributes *)attributes usrTag:(id)usrTag;
- (int)write:(NSData *)data;
- (int)stop:(BOOL)immediate;
- (void)language:(NSString *)language;
- (void)domain:(NSString *)domain;
- (void)accent:(NSString *)accent;
- (void)vadEos:(int)vadEos;
- (void)dwa:(NSString *)dwa;
- (void)pd:(NSString *)pd;
- (void)ptt:(BOOL)enable;
- (void)rlang:(NSString *)rlang;
- (void)vinfo:(BOOL)vinfo;
- (void)nunum:(BOOL)enable;
- (void)speexSize:(int)speexSize;
- (void)nbest:(int)nbest;
- (void)wbest:(int)wbest;
- (void)vgap:(int)vgap;
- (void)proc:(BOOL)proc;
- (void)smth:(BOOL)smth;
- (void)ln:(NSString *)ln;
- (void)opt:(int)opt;
- (void)dhw:(NSString *)dhw;
- (void)ltc:(int)ltc;
- (void)svad:(int)svad;
- (void)vadEnable:(BOOL)enable;
- (void)targetLanuage:(NSString *)target;
- (void)region:(RegionType)region;
- (void)addIntParam:(NSString *)key value:(int)value;
- (void)addBoolParam:(NSString *)key value:(BOOL)value;
- (void)addStringParam:(NSString *)key value:(NSString *)value;
- (ASRHotwordsResult *)uploadHotwordsWithResId:(NSString *)resId
hotwordsFile:(NSString *)hotwordsFile
namexp:(NSString *)namexp
wordType:(NSString *)wordType;
- (ASRHotwordsResult *)uploadHotwordsWithResId:(NSString *)resId
hotwords:(NSData *)hotwords
namexp:(NSString *)namexp
wordType:(NSString *)wordType;
- (ASRHotwordsResult *)queryHotwordsWithResId:(NSString *)resId;
@end
NS_ASSUME_NONNULL_END
#endif /* ASR_h */

23
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASRCallbacks.h

@ -0,0 +1,23 @@
//
// AsrCallbacks.h
// SparkChain_Mac
//
// Created by XX on 2024/5/11.
//
#import <Foundation/Foundation.h>
#import "ASRResult.h"
#import "ASRError.h"
NS_ASSUME_NONNULL_BEGIN
@protocol ASRCallbacks <NSObject>
- (void)onResult:(ASRResult *)result usrTag:(id)usrTag;
- (void)onError:(ASRError *)error usrTag:(id)usrTag;
@end
NS_ASSUME_NONNULL_END

25
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASRError.h

@ -0,0 +1,25 @@
//
// ASRError.h
// SparkChain_Mac
//
// Created by XX on 2024/5/11.
//
#ifndef ASRError_h
#define ASRError_h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ASRError : NSObject
@property (nonatomic, assign) int code;
@property (nonatomic, copy) NSString *errMsg;
@property (nonatomic, copy) NSString *sid;
@end
NS_ASSUME_NONNULL_END
#endif /* ASRError_h */

22
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASRHotwordsResult.h

@ -0,0 +1,22 @@
//
// ASRHotwordsResult.h
// SparkChain
//
#ifndef ASRHotwordsResult_h
#define ASRHotwordsResult_h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ASRHotwordsResult : NSObject
@property (nonatomic, assign) int code;
@property (nonatomic, copy) NSString *response;
@end
NS_ASSUME_NONNULL_END
#endif /* ASRHotwordsResult_h */

31
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASRResult.h

@ -0,0 +1,31 @@
//
// ASRResult.h
// SparkChain_Mac
//
// Created by XX on 2024/5/11.
//
#ifndef ASRResult_h
#define ASRResult_h
#import <Foundation/Foundation.h>
#import "Transcription.h"
#import "Vad.h"
NS_ASSUME_NONNULL_BEGIN
@interface ASRResult : NSObject
@property (nonatomic, strong) NSArray<Transcription *> *transcriptions;
@property (nonatomic, strong) NSArray<Vad *> *vads;
@property (nonatomic) int begin;
@property (nonatomic) int end;
@property (nonatomic, copy) NSString *sid;
@property (nonatomic, copy) NSString *bestMatchText;
@property (nonatomic) int status;
@end
NS_ASSUME_NONNULL_END
#endif /* ASRResult_h */

81
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/AST.h

@ -0,0 +1,81 @@
//
// AST.h
// SparkChain_iOS
//
// Created by pcfang on 2025/11/14.
//
#import <Foundation/Foundation.h>
#import <SparkChain/SCEnums.h>
@class AST;
@class ASTResult;
@class ASTError;
NS_ASSUME_NONNULL_BEGIN
// 回调协议
@protocol ASTDelegate <NSObject>
- (void)ast:(AST *)ast onResult:(ASTResult *)result userInfo:(_Nullable id)userInfo;
- (void)ast:(AST *)ast onError:(ASTError *)error userInfo:(_Nullable id)userInfo;
@end
@interface AST : NSObject
@property (nonatomic, weak, readonly) id<ASTDelegate> delegate;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithDelegate:(id<ASTDelegate>) delegate;
- (instancetype)initWithregion:(RegionType)region delegate:(id<ASTDelegate>)delegate;
- (int)start:(NSString *)lang audioEncoding:(NSString *)encoding sampleRate:(int)sampleRate userInfo:(_Nullable id)userInfo;
- (int)write:(NSData *)data;
- (int)stop:(BOOL)immediate;
#pragma mark - 热词管理
- (int)setHotwords:(NSString *)hotwords;
- (int)uploadHotwords:(NSString *)hotwordsFile userInfo:(_Nullable id)userInfo;
- (int)updateHotwords:(NSString *)hotwordsFile hotwordId:(int)hotwordId userInfo:(_Nullable id)userInfo;
- (int)queryHotWords:(int)hotwordId userInfo:(_Nullable id)userInfo;
#pragma mark - 翻译列表管理
- (int)setTransLists:(NSDictionary *)transLists;
- (int)uploadTransLists:(NSString *)from to:(NSString *)to transLists:(NSDictionary *)transLists userInfo:(_Nullable id)userInfo;
#pragma mark - 替换词管理
- (int)setReplaceWords:(NSDictionary *)replaceWords;
- (int)uploadReplaceList:(NSDictionary *)replaceList userInfo:(_Nullable id)userInfo;
- (int)deleteReplaceList:(NSString *)replaceListId userInfo:(_Nullable id)userInfo;
#pragma mark - 声纹管理
- (int)registerVoicePrint:(NSData *)audioData audioType:(NSString *)audioType userInfo:(_Nullable id)userInfo;
- (int)updateVoicePrint:(NSData *)audioData audioType:(NSString *)audioType featureId:(NSString *)featureId userInfo:(_Nullable id)userInfo;
- (int)deleteVoicePrint:(NSArray *)featureIds userInfo:(_Nullable id)userInfo;
#pragma mark - 参数设置
- (void)addIntParam:(NSString *)key value:(int)value;
- (void)addBoolParam:(NSString *)key value:(BOOL)value;
- (void)addStringParam:(NSString *)key value:(NSString *)value;
@end
NS_ASSUME_NONNULL_END

30
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASTError.h

@ -0,0 +1,30 @@
//
// ASTError.h
// SparkChain_iOS
//
// Created by pcfang on 2025/11/19.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ASTError : NSObject
@property (nonatomic, readonly) NSString *code;
@property (nonatomic, readonly) NSString *sid;
@property (nonatomic, readonly) NSString *fnType;
@property (nonatomic, readonly) NSString *detail;
@property (nonatomic, readonly) NSString *desc;
@property (nonatomic, readonly) NSString *normal;
@property (nonatomic, readonly) NSString *resType;
@end
NS_ASSUME_NONNULL_END

80
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ASTResult.h

@ -0,0 +1,80 @@
//
// ASTTransResult.h
// SparkChain_iOS
//
// Created by pcfang on 2025/11/14.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
// AST 声纹结果
@interface ASTVoicePrintResult : NSObject
@property (nonatomic, readonly) NSString *sid;
@property (nonatomic, readonly) NSString *featureId;
@property (nonatomic, readonly) NSInteger status;
@property (nonatomic, readonly) NSString *delFailIds;
@end
// AST TTS结果
@interface ASTTtsResult : NSObject
@property (nonatomic, readonly) NSInteger status;
@property (nonatomic, readonly) NSData * audio;
@property (nonatomic, readonly) NSString *engine;
@property (nonatomic, readonly) NSString *lang;
@property (nonatomic, readonly) NSString *rawResult;
@end
// AST 翻译结果
@interface ASTTransResult : NSObject
@property (nonatomic, readonly) NSString *src;
@property (nonatomic, readonly) NSString *dst;
@property (nonatomic, readonly) NSString *from;
@property (nonatomic, readonly) NSString *to;
@property (nonatomic, readonly) NSInteger type;
@property (nonatomic, readonly) BOOL end;
@property (nonatomic, readonly) NSString *isOver;
@property (nonatomic, readonly) NSString *isFinal;
@property (nonatomic, readonly) NSString *dstFixedPart;
@end
// AST热词结果
@interface ASTHotwordsResult : NSObject
@property (nonatomic, readonly) NSInteger hotWordId;
@property (nonatomic, readonly) NSString *hotWord;
@property (nonatomic, readonly) NSString *sid;
@property (nonatomic, readonly) NSString *engineUid;
@property (nonatomic, readonly) NSString *resId;
@property (nonatomic, readonly) NSString *lang;
@end
@interface ASTResult : NSObject
@property (nonatomic, readonly) NSString *contextId;
@property (nonatomic, readonly) NSString *sessionId;
@property (nonatomic, readonly) NSString *resType;
@property (nonatomic, readonly) NSInteger status;
@property (nonatomic, readonly) NSInteger isEnd;
@property (nonatomic, readonly) NSString *data;
@property (nonatomic, readonly) NSString *rawResult;
@property (nonatomic, readonly) ASTTransResult *transResult;
@property (nonatomic, readonly) ASTTtsResult *ttsResult;
@property (nonatomic, readonly) ASTHotwordsResult *hotwordsResult;
@property (nonatomic, readonly) ASTVoicePrintResult *voicePrintResult;
@property (nonatomic, readonly) NSString *transReplaceWordId;
@property (nonatomic, readonly) NSString *replaceListId;
@property (nonatomic, readonly) NSString *replaceListSid;
@end
NS_ASSUME_NONNULL_END

29
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/AudioAttributes.h

@ -0,0 +1,29 @@
//
// AudioAttributes.h
// SparkChain_Mac
//
// Created by XX on 2024/5/11.
//
#ifndef AudioAttributes_h
#define AudioAttributes_h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface AudioAttributes : NSObject
@property (nonatomic, assign) int sampleRate;
@property (nonatomic, copy) NSString *encoding;
@property (nonatomic, assign) int channels;
@property (nonatomic, assign) int bitdepth;
@property (nonatomic, assign) int frameSize;
- (instancetype)init;
@end
NS_ASSUME_NONNULL_END
#endif /* AudioAttributes_h */

20
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/Embedding.h

@ -0,0 +1,20 @@
//
// Embedding.h
// SparkChain
//
// Created by pcfang on 2024/11/28.
//
#import <Foundation/Foundation.h>
@class EmbeddingOutput;
NS_ASSUME_NONNULL_BEGIN
@interface Embedding : NSObject
+ (EmbeddingOutput *)embedding:(NSString *)input domain:(NSString *)domain;
+ (EmbeddingOutput *)embedding:(NSString *)input domain:(NSString *)domain uid:(NSString * __nullable)uid;
@end
NS_ASSUME_NONNULL_END

34
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/EmbeddingOutput.h

@ -0,0 +1,34 @@
//
// EmbeddingOutput.h
// SparkChain
//
// Created by pcfang on 2024/11/28.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface EmbeddingData : NSObject
@property (readonly) float * byte NS_RETURNS_INNER_POINTER;
@property (nonatomic, assign) NSUInteger length;
@end
@interface EmbeddingOutput : NSObject
@property (nonatomic,copy,readonly) NSString * sid;
@property (nonatomic,strong,readonly) EmbeddingData * data;
@property (nonatomic,copy,readonly) NSString * raw;
@property (nonatomic,copy,readonly) NSString * errMsg;
@property (nonatomic,assign,readonly) int errCode;
@end
NS_ASSUME_NONNULL_END

57
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/Feature.h

@ -0,0 +1,57 @@
//
// Feature.h
// SparkChain_iOS
//
#import <Foundation/Foundation.h>
@class FeatureResult;
@class FeatureError;
NS_ASSUME_NONNULL_BEGIN
@protocol FeatureCallback <NSObject>
- (void)onResult:(FeatureResult *)result userInfo:(id _Nullable)userInfo;
- (void)onError:(FeatureError *)error userInfo:(id _Nullable)userInfo;
@end
@interface Feature : NSObject
@property (nonatomic, weak) id<FeatureCallback> delegate;
- (instancetype)init;
/// 声纹注册
/// - Parameters:
/// - audioData: 音频数据
/// - audioType: 音频类型
/// - uid: 用户 id
/// - userInfo: 用户自定义标识,可为 nil;非 nil 时会在回调中回传
- (int)featureRegisterWithAudioData:(NSData *)audioData
audioType:(NSString *)audioType
uid:(NSString *)uid
userInfo:(id _Nullable)userInfo;
/// 声纹更新
/// - Parameters:
/// - audioData: 音频数据
/// - audioType: 音频类型
/// - featureId: 声纹 id
/// - userInfo: 用户自定义标识
- (int)featureUpdateWithAudioData:(NSData *)audioData
audioType:(NSString *)audioType
featureId:(NSString *)featureId
userInfo:(id _Nullable)userInfo;
/// 声纹删除(多个声纹 id 以逗号隔开)
/// - Parameters:
/// - featureIds: 声纹 id,多个以逗号分隔
/// - userInfo: 用户自定义标识
- (int)featureDeleteWithFeatureIds:(NSString *)featureIds
userInfo:(id _Nullable)userInfo;
@end
NS_ASSUME_NONNULL_END

17
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/FeatureError.h

@ -0,0 +1,17 @@
//
// FeatureError.h
// SparkChain_iOS
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FeatureError : NSObject
@property (nonatomic, copy, readonly) NSString *code;
@property (nonatomic, copy, readonly) NSString *message;
@end
NS_ASSUME_NONNULL_END

16
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/FeatureResult.h

@ -0,0 +1,16 @@
//
// FeatureResult.h
// SparkChain_iOS
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FeatureResult : NSObject
@property (nonatomic, copy, readonly) NSString *featureId;
@end
NS_ASSUME_NONNULL_END

112
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/IST.h

@ -0,0 +1,112 @@
//
// IST.h
// SparkChain_iOS
// 极速语音转写
// Created by pcfang on 2025/8/7.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class IST;
@class ISTResult;
@class ISTError;
@protocol ISTDelegate <NSObject>
@optional
- (void)ist:(IST *)ist onResult:(ISTResult *)result userInfo:(id)userInfo;
- (void)ist:(IST *)ist onProcess:(NSString *)process userInfo:(id)userInfo;
- (void)ist:(IST *)ist onError:(ISTError *)error userInfo:(id)userInfo;
@end
@interface IST : NSObject
@property (nonatomic, copy) NSString * requestId;
@property (nonatomic, copy) NSString * language;
@property (nonatomic, copy) NSString * domain;
@property (nonatomic, copy) NSString * accent;
@property (nonatomic, copy) NSString * callbackUrl;
@property (nonatomic, assign) int vsppOn;
@property (nonatomic, assign) int speakerNum;
@property (nonatomic, assign) int outputType;
@property (nonatomic, assign) int postprocON;
@property (nonatomic, copy) NSString * pd;
@property (nonatomic, assign) int duration;
@property (nonatomic, assign) int enableSubtitle;
@property (nonatomic, assign) BOOL smoothproc;
@property (nonatomic, assign) BOOL colloqproc;
@property (nonatomic, assign) BOOL languageType;
@property (nonatomic, assign) int vto;
@property (nonatomic, copy) NSString * dhw;
@property (nonatomic, weak) id<ISTDelegate> delegate;
- (void)addIntParam:(NSString *)key value:(int)value;
- (void)addBoolParam:(NSString *)key value:(BOOL)value;
- (void)addStringParam:(NSString *)key value:(NSString *)value;
- (instancetype)initDelegate:(id<ISTDelegate>) delegate;
/// 上传音频文件,返回0成功,其他失败
/// - Parameters:
/// - audioPath: 音频文件路径
/// - requestId: 请求ID
/// - userInfo: 用户自定义数据
- (int)upload:(NSString *) audioPath requestId:(NSString *)requestId userInfo:(__nullable id)userInfo;
/// 分片上传音频文件,0表示成功,其他表示失败
/// - Parameters:
/// - audioPath: 音频文件路径
/// - requestId: 请求ID
/// - partSize: 分片大小
/// - userInfo: 用户自定义数据
- (int)mpUpload:(NSString *)audioPath
requestId:(NSString *)requestId
partSize:(NSUInteger)partSize
userInfo:(__nullable id)userInfo;
/// 创建任务,0表示成功,其他表示失败
/// - Parameters:
/// - requestId: 请求ID
/// - path: 音频文件URL
/// - format: 音频的采样率支持16k,audio/L16;rate=16000
/// - encoding: 音频数据格式:wav、pcm(传参raw),mp3(传参lame)
/// - userInfo: 用户自定义数据
- (int)createTask:(NSString *)requestId
audioURLPath:(NSString *)path
format:(NSString *)format
encoding:(NSString *)encoding
userInfo:(__nullable id)userInfo;
/// 查询任务,0表示成功,其他表示失败
/// - Parameters:
/// - taskId: 任务ID
/// - userInfo: 用户自定义数据
- (int)queryTask:(NSString *)taskId userInfo:(__nullable id)userInfo;
/// 停止识别
- (int)stop;
@end
NS_ASSUME_NONNULL_END

22
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ISTError.h

@ -0,0 +1,22 @@
//
// ISTError.h
// SparkChain_iOS
//
// Created by pcfang on 2025/8/7.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ISTError : NSObject
@property(nonatomic, copy, readonly) NSString * sid;
@property(nonatomic, assign, readonly) int code;
@property(nonatomic, copy, readonly) NSString * errMsg;
@end
NS_ASSUME_NONNULL_END

34
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ISTResult.h

@ -0,0 +1,34 @@
//
// ISTResult.h
// SparkChain_iOS
//
// Created by pcfang on 2025/8/7.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ISTResult : NSObject
@property (nonatomic, assign, readonly) int code;
@property (nonatomic, copy, readonly) NSString * key;
@property (nonatomic, copy, readonly) NSString * sid;
@property (nonatomic, copy, readonly) NSString * url;
@property (nonatomic, copy, readonly) NSString * taskId;
@property (nonatomic, copy, readonly) NSString * taskStatus;
@property (nonatomic, copy, readonly) NSString * taskType;
@property (nonatomic, copy, readonly) NSString * result;
@property (nonatomic, copy, readonly) NSString * rawResult;
@end
NS_ASSUME_NONNULL_END

53
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ITS.h

@ -0,0 +1,53 @@
//
// ITS.h
// SparkChain_Mac
//
// Created by pcfang on 15.5.24.
//
#import <Foundation/Foundation.h>
@class ITS;
@class ITSResult;
@class ITSError;
NS_ASSUME_NONNULL_BEGIN
typedef enum {
ITRANS = 0, //讯飞翻译
NIUTRANS, //小牛翻译
ITRANS_SG, //讯飞翻译(新加坡)
GOOGLETRANS //谷歌翻译
} TransType;
@protocol ITSCallbacks <NSObject>
- (void)its:(ITS *)its onResult:(ITSResult *)result userInfo:(id)userInfo;
- (void)its:(ITS *)its onError:(ITSError *)error userInfo:(id)userInfo;
@end
@interface ITS : NSObject
- (instancetype) init;
- (instancetype) initWithType:(TransType)type;
- (instancetype) initWithFormLanguage:(NSString *)from toLanguage:(NSString *)to;
- (instancetype)initWithFormLanguage:(NSString *)from toLanguage:(NSString *)to type:(TransType)type;
@property (nonatomic, weak) id<ITSCallbacks> callbacks;
@property (nonatomic, copy) NSString * fromLanguage;
@property (nonatomic, copy) NSString * toLanguage;
@property (nonatomic, copy) NSString * resId;
- (int)arun:(NSString *)text;
- (int)arun:(NSString *)text userInfo:(_Nullable id)userInfo;
@end
NS_ASSUME_NONNULL_END

22
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ITSError.h

@ -0,0 +1,22 @@
//
// ITSError.h
// SparkChain_Mac
//
// Created by pcfang on 15.5.24.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ITSError : NSObject
@property(nonatomic, copy, readonly) NSString * sid;
@property(nonatomic, assign, readonly) int code;
@property(nonatomic, copy, readonly) NSString * errMsg;
@end
NS_ASSUME_NONNULL_END

34
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ITSResult.h

@ -0,0 +1,34 @@
//
// ITSResult.h
// SparkChain_Mac
//
// Created by pcfang on 15.5.24.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Trans : NSObject
@property (nonatomic, copy, readonly) NSString * src;
@property (nonatomic, copy, readonly) NSString * dst;
@end
@interface ITSResult : NSObject
@property (nonatomic, copy, readonly) NSString * sid;
@property (nonatomic, assign, readonly) int status;
@property (nonatomic, copy, readonly) NSString * from;
@property (nonatomic, copy, readonly) NSString * to;
@property (nonatomic, strong, readonly) Trans * trans;
@end
NS_ASSUME_NONNULL_END

40
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ImageTrans.h

@ -0,0 +1,40 @@
//
// ImageTrans.h
// IMAGE_TRANS
//
// Created by xyzhang on 2024/5/28.
//
#import <Foundation/Foundation.h>
@class ImageTrans;
@class ImageTransResult;
@class ImageTransError;
NS_ASSUME_NONNULL_BEGIN
@protocol ImageTransDelegate <NSObject>
- (void)imgTrans:(ImageTrans *)imgTrans onResult:(ImageTransResult *)result usrContext:(id)usrContext;
- (void)imgTrans:(ImageTrans *)imgTrans onError:(ImageTransError *)error usrContext:(id)usrContext;
@end
@interface ImageTrans : NSObject
@property (nonatomic, weak) id<ImageTransDelegate> delegate;
//@property (nonatomic, copy) NSString *imagePath;
@property (nonatomic, copy) NSString *ocrLanguage;
@property (nonatomic, copy) NSString *fromLanguage;
@property (nonatomic, copy) NSString *toLanguage;
@property (nonatomic, assign) int returnType; // ???
- (instancetype)init;
- (instancetype)initWithOcrLanguage:(NSString *)ocrLanguage fromLanguage:(NSString *)fromLanguage toLanguage:(NSString *)toLanguage returnType:(int)returnType;
- (int)arun:(NSData *)imageData encoding:(NSString *)encoding userInfo:(id)userInfo;
@end
NS_ASSUME_NONNULL_END

20
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ImageTransError.h

@ -0,0 +1,20 @@
//
// ImageTransError.h
// IMAGE_TRANS
//
// Created by xyzhang on 2024/5/28.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ImageTransError : NSObject
@property (nonatomic, copy, readonly) NSString *sid;
@property (nonatomic, copy, readonly) NSString *errMsg;
@property (nonatomic, assign, readonly) int code;
@end
NS_ASSUME_NONNULL_END

32
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/ImageTransResult.h

@ -0,0 +1,32 @@
//
// ImageTransResult.h
// IMAGE_TRANS
//
// Created by xyzhang on 2024/5/28.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface BlockText : NSObject
@property (nonatomic, copy) NSString *srcText;
@property (nonatomic, copy) NSString *dstText;
@end
@interface ImageTransResult : NSObject
@property (nonatomic, copy, readonly) NSString *sid;
//@property (nonatomic, copy, readonly) NSString *itsImage;
//@property (nonatomic, copy, readonly) NSString *itsOutput;
@property (nonatomic, copy, readonly) NSData *itsImageData; //图片翻译 二进制数据
@property (nonatomic, copy, readonly) NSString *itsOutputJsStr; // 整体
@property (nonatomic, assign, readonly) int status;
@property (nonatomic, copy, readonly) NSArray <BlockText *>*blockTexts;
@end
NS_ASSUME_NONNULL_END

168
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLM.h

@ -0,0 +1,168 @@
//
// LLM.h
// SparkChain
//
// Created by pcfang on 12.9.23.
//
#import <Foundation/Foundation.h>
@class LLM;
@class LLMEvent;
@class LLMError;
@class LLMResult;
@class LLMConfig;
@class LLMOutput;
@class LLMTools;
@class Memory;
NS_ASSUME_NONNULL_BEGIN
typedef enum{
TEXT_GENERATION = 0,
IMAGE_GENERATION,
IMAGE_UNDERSTANDING
} ModelType;
@protocol LLMCallback <NSObject>
- (void)llm:(LLM *)llm onResult:(LLMResult * _Nullable)result usrContext:(id _Nullable)usrContext;
- (void)llm:(LLM *)llm onEvent:(LLMEvent * _Nullable)event usrContext:(id _Nullable)usrContext;
- (void)llm:(LLM *)llm onError:(LLMError * _Nullable)error usrContext:(id _Nullable)usrContext;
@end
@interface LLM : NSObject
@property(nonatomic, weak) id<LLMCallback> callback;
@property(nonatomic, strong, readonly) LLMConfig * config;
#pragma mark - init
- (instancetype)init;
- (instancetype)initWithConfig:(LLMConfig * _Nullable)config;
- (instancetype)initWithCallback:(id<LLMCallback> _Nullable)callback;
- (instancetype)initWithConfig:(LLMConfig * _Nullable)config callback:(id<LLMCallback> _Nullable)callback;
- (instancetype)initWithModelType:(ModelType)modelType config:(LLMConfig *)config;
- (instancetype)initWithModelType:(ModelType)modelType config:(LLMConfig * _Nullable)config memory:(Memory * _Nullable)memory;
#pragma mark - 同步接口
/// 同步获取LLM结果
/// - Parameter query: 用户问题
- (LLMOutput *)run:(NSString *)query;
- (LLMOutput *)run:(NSString *)query image:(NSData *)image;
/// 同步获取LLM结果
/// - Parameters:
/// - query: 用户问题
/// - ttl: 超时时间
- (LLMOutput *)run:(NSString *)query ttl:(int)ttl;
/// 同步获取LLM结果
/// - Parameters:
/// - query: 用户问题
/// - tools: 工具(如function call)
- (LLMOutput *)run:(NSString *)query tools:(LLMTools *)tools;
/// 同步获取LLM结果
/// - Parameters:
/// - query: 用户问题
/// - tools: 工具(如function call)
/// - ttl: 超时时间
- (LLMOutput *)run:(NSString *)query tools:(LLMTools *)tools ttl:(int)ttl;
/// 同步获取图片理解结果
/// - Parameters:
/// - query: 用户问题
/// - image: 图片
- (LLMOutput *)run:(NSString *)query image:(NSData *)image;
/// 使用raw json同步调用大模型
/// - Parameters:
/// - json: 原始JSON
/// - ttl: 超时时间
- (LLMOutput *)runWithJSON:(NSString *)json ttl:(int)ttl;
#pragma mark - 异步接口
/// 异步获取LLM结果
/// - Parameter query: 用户问题
- (int)arun:(NSString *)query;
/// 异步获取LLM结果
/// - Parameters:
/// - query: 用户问题
/// - usrContext: 用户自定义数据
- (int)arun:(NSString *)query usrContext:(id _Nullable)usrContext;
- (int)arun:(NSString *)query image:(NSData *)image usrContext:(id _Nullable)usrContext;
/// 异步获取LLM结果
/// - Parameters:
/// - query: 用户问题
/// - tools: 工具(如function call)
- (int)arun:(NSString *)query tools:(LLMTools *)tools;
/// 异步获取LLM结果
/// - Parameters:
/// - query: 用户问题
/// - tools: 工具(如function call)
/// - usrContext: 用户自定义数据
- (int)arun:(NSString *)query tools:(LLMTools *)tools usrContext:(id _Nullable)usrContext;
/// 异步获取图片理解结果
/// - Parameters:
/// - query: 用户问题
/// - image: 图片
- (int)arun:(NSString *)query image:(NSData *)image;
/// 异步获取图片理解结果
/// - Parameters:
/// - query: 用户问题
/// - image: 图片
/// - usrContext: 用户自定义数据
- (int)arun:(NSString *)query image:(NSData *)image usrContext:(id _Nullable)usrContext;
/// 使用raw json异步调用大模型
/// - Parameters:
/// - json: 原始JSON
/// - usrContext: 用户自定义数据
- (int)arunWithJSON:(NSString *)json usrContext:(id _Nullable)usrContext;
#pragma mark - 其他
/// 添加system角色(如:你是一个智能旅游助手,推荐旅游地点)
/// - Parameter prompt: system内容
- (int)addSystemPrompt:(NSString *)prompt;
/// 清除上下文
- (void)clearHistory;
/// 停止输出
- (int)stop;
@end
@interface LLMFactory : NSObject
+ (LLM *)textGeneration:(LLMConfig * _Nullable)config;
+ (LLM *)textGeneration:(LLMConfig * _Nullable)config memory:(Memory * _Nullable)memory;
+ (LLM *)imageGeneration:(LLMConfig * _Nullable)config;
+ (LLM *)imageGeneration:(LLMConfig * _Nullable)config width:(int)width height:(int)height;
+ (LLM *)imageUnderstanding:(LLMConfig * _Nullable)config;
+ (LLM *)imageUnderstanding:(LLMConfig * _Nullable)config memory:(Memory * _Nullable)memory;
@end
NS_ASSUME_NONNULL_END

20
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLMBaseOutput.h

@ -0,0 +1,20 @@
//
// LLMBaseOutput.h
// SparkChain
//
// Created by pcfang on 15.9.23.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LLMBaseOutput : NSObject
@property (nonatomic, copy, readonly) NSString * sid;
- (instancetype)initWithSid:(NSString *)sid;
@end
NS_ASSUME_NONNULL_END

46
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLMConfig.h

@ -0,0 +1,46 @@
//
// LLMConfig.h
// SparkChain
//
// Created by pcfang on 12.9.23.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#define chainProperty(returnType,name) property(nonatomic, readonly, copy) returnType (^name)
@interface LLMConfig : NSObject
@chainProperty(LLMConfig *,uid)(NSString * uid);
@chainProperty(LLMConfig *,domain)(NSString * domain);
@chainProperty(LLMConfig *,auditing)(NSString * auditing);
@chainProperty(LLMConfig *,chatID)(NSString * chatID);
@chainProperty(LLMConfig *,temperature)(float temperature);
@chainProperty(LLMConfig *,topK)(int topK);
@chainProperty(LLMConfig *,maxToken)(int maxToken);
@chainProperty(LLMConfig *,url)(NSString * url);
@chainProperty(LLMConfig *,showRefLabel)(BOOL isShow);
@chainProperty(LLMConfig *,tools)(NSString * toolList);
@chainProperty(LLMConfig *,addString)(NSString * key, NSString * param);
@chainProperty(LLMConfig *,addDouble)(NSString * key,double param);
@chainProperty(LLMConfig *,addInt)(NSString * key,int param);
@chainProperty(LLMConfig *,addBool)(NSString * key,BOOL param);
@end
NS_ASSUME_NONNULL_END

21
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLMError.h

@ -0,0 +1,21 @@
//
// LLMError.h
// SparkChain
//
// Created by pcfang on 12.9.23.
//
#import <Foundation/Foundation.h>
#import <SparkChain/LLMBaseOutput.h>
NS_ASSUME_NONNULL_BEGIN
@interface LLMError : LLMBaseOutput
@property (nonatomic, assign, readonly) int errCode;
@property (nonatomic, copy, readonly) NSString * errMsg;
@end
NS_ASSUME_NONNULL_END

21
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLMEvent.h

@ -0,0 +1,21 @@
//
// LLMEvent.h
// SparkChain
//
// Created by pcfang on 12.9.23.
//
#import <Foundation/Foundation.h>
#import <SparkChain/LLMBaseOutput.h>
NS_ASSUME_NONNULL_BEGIN
@interface LLMEvent : LLMBaseOutput
@property (nonatomic, assign, readonly) int eventID;
@property (nonatomic, copy, readonly) NSString * eventMsg;
@end
NS_ASSUME_NONNULL_END

65
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLMResult.h

@ -0,0 +1,65 @@
//
// LLMResult.h
// SparkChain
//
// Created by pcfang on 12.9.23.
//
#import <Foundation/Foundation.h>
#import <SparkChain/LLMBaseOutput.h>
NS_ASSUME_NONNULL_BEGIN
@interface LLMResult : LLMBaseOutput
@property (nonatomic, copy, readonly) NSString * _Nullable raw;
@property (nonatomic, copy, readonly) NSString * _Nullable role;
@property (nonatomic, copy, readonly) NSString * _Nullable content;
@property (nonatomic, copy, readonly) NSString * _Nullable reasoningContent;
@property (nonatomic, copy, readonly) NSData * _Nullable image;
@property (nonatomic, assign, readonly) int status;
@property (nonatomic, assign, readonly) int completionTokens;
@property (nonatomic, assign, readonly) int promptTokens;
@property (nonatomic, assign, readonly) int totalTokens;
@property (nonatomic, copy, readonly) NSString * functionCall;
@end
@interface LLMOutput : LLMBaseOutput
@property (nonatomic, copy, readonly) NSString * _Nullable raw;
@property (nonatomic, copy, readonly) NSString * _Nullable role;
@property (nonatomic, copy, readonly) NSString * _Nullable content;
@property (nonatomic, copy, readonly) NSString * _Nullable reasoningContent;
@property (nonatomic, copy, readonly) NSData * _Nullable image;
@property (nonatomic, assign, readonly) int status;
@property (nonatomic, assign, readonly) int completionTokens;
@property (nonatomic, assign, readonly) int promptTokens;
@property (nonatomic, assign, readonly) int totalTokens;
@property (nonatomic, assign, readonly) int errCode;
@property (nonatomic, copy, readonly) NSString * _Nullable errMsg;
@property (nonatomic, copy, readonly) NSString * functionCall;
@end
NS_ASSUME_NONNULL_END

22
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/LLMTools.h

@ -0,0 +1,22 @@
//
// LLMTools.h
// SparkChain
//
// Created by pcfang on 2024/11/21.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LLMTools : NSObject
@property(nonatomic, copy) NSString * toolType;
@property(nonatomic, copy) NSString * toolDescription;
- (instancetype)initWithType:(NSString *)type description:(NSString *)description;
@end
NS_ASSUME_NONNULL_END

28
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/Memory.h

@ -0,0 +1,28 @@
//
// Memory.h
// SparkChain
//
// Created by pcfang on 2024/11/22.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Memory : NSObject
- (instancetype) init NS_UNAVAILABLE;
/// 按照轮数进行大模型交互上下文记忆,默认5轮,超出轮数自动移除最早的会话
+ (instancetype)windowMemory;
/// 按照轮数进行大模型交互上下文记忆,用户自定义轮数,超出轮数自动移除最早的会话
+ (instancetype)windowMemory:(int)maxCache;
/// 按照token进行大模型交互上下文记忆,默认4096
+ (instancetype)tokenMemory;
/// 按照token进行大模型交互上下文记忆,用户自定义token
+ (instancetype)tokenMemory:(int)maxToken;
@end
NS_ASSUME_NONNULL_END

37
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/OCR.h

@ -0,0 +1,37 @@
//
// OCR.h
// SparkChain_Mac
//
// Created by pcfang on 16.5.24.
//
#import <Foundation/Foundation.h>
@class OCR;
@class OCRResult;
@class OCRError;
NS_ASSUME_NONNULL_BEGIN
@protocol OCRCallbacks <NSObject>
- (void)ocr:(OCR *)ocr onResult:(OCRResult *)result userInfo:(id)userInfo;
- (void)ocr:(OCR *)ocr onError:(OCRError *)error userInfo:(id)userInfo;
@end
@interface OCR : NSObject
@property (nonatomic, weak) id <OCRCallbacks> callbacks;
- (int)arunWithImagePath:(NSString *)imagePath;
- (int)arunWithImagePath:(NSString *)imagePath userInfo:(id)userInfo;
- (int)arunWithImageData:(NSData *)imageData;
- (int)arunWithImageData:(NSData *)imageData userInfo:(id)userInfo;
@end
NS_ASSUME_NONNULL_END

22
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/OCRError.h

@ -0,0 +1,22 @@
//
// OCRError.h
// SparkChain_Mac
//
// Created by pcfang on 17.5.24.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface OCRError : NSObject
@property(nonatomic, copy, readonly) NSString * sid;
@property(nonatomic, assign, readonly) int code;
@property(nonatomic, copy, readonly) NSString * errMsg;
@end
NS_ASSUME_NONNULL_END

27
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/OCRExtension.h

@ -0,0 +1,27 @@
//
// OCRExtension.h
// SparkChain_Mac
//
// Created by pcfang on 17.5.24.
//
#import "OCRResult.h"
#import "OCRError.h"
#include <sparkchain_api/sc_ocr.h>
@interface OCRLine ()
- (instancetype)initWithLine:(SparkChain::Line *)line;
@end
@interface OCRResult ()
- (instancetype)initWithResult:(SparkChain::OCRResult *)result;
@end
@interface OCRError ()
- (instancetype)initWithError:(SparkChain::OCRError *)error;
@end

52
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/OCRResult.h

@ -0,0 +1,52 @@
//
// OCRResult.h
// SparkChain_Mac
//
// Created by pcfang on 17.5.24.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface OCRLine : NSObject
/// 文本块被旋转的角度
@property (nonatomic, assign, readonly) int angle;
///字符中心坐标,Int类型
@property (nonatomic, copy, readonly) NSArray <NSArray <NSNumber *> *> * charCenters;
/// 字符块坐标(每个字符左上角起,顺时针一周四角坐标形成的集合), int类型
@property (nonatomic, copy, readonly) NSArray <NSArray <NSNumber *> *> * charPolygons;
/// 字符置信度,值域范围:0-1,double类型
@property (nonatomic, copy, readonly) NSArray <NSNumber *> * charScore;
/// 文本块坐标(文本阅读方向的左上角起,顺时针一周四角坐标形成的集合),int类型
@property (nonatomic, copy, readonly) NSArray <NSNumber *> * position;
/// 文本块所属属性表下标,从0开始
@property (nonatomic, assign, readonly) int property;
/// 文本块的整体置信度
@property (nonatomic, assign, readonly) double score;
/// 文本块内容
@property (nonatomic, copy, readonly) NSString * text;
@end
@interface OCRResult : NSObject
/// 请求的sid
@property (nonatomic, copy, readonly) NSString * sid;
/// 返回结果的状态 (0:首结果,1:中间结果,2:尾结果, 3:一次性结果)
@property (nonatomic, assign, readonly) int status;
/// 图片旋转角度
@property (nonatomic, assign, readonly) int imageAngle;
/// 文本块识别信息集合
@property (nonatomic, copy, readonly) NSArray <OCRLine *> * lines;
/// 文本块属性表,可选值:text(文本)、stamp(印章)、formula(公示);
/// 与lines里的每个元素的property搭配使用,
/// property:0文本、1印章、2公式
@property (nonatomic, copy, readonly) NSArray <NSString *> * properties;
/// 旋转后图片宽度
@property (nonatomic, assign, readonly) int rotatedImageWidth;
/// 旋转后图片高度
@property (nonatomic, assign, readonly) int rotatedImageHeight;
/// 全文识别的所有内容(同行内容整合为一行输出)
@property (nonatomic, copy, readonly) NSString * wholeText;
@end
NS_ASSUME_NONNULL_END

28
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/OnlineTTS.h

@ -0,0 +1,28 @@
//
// OnlineTTS.h
// SparkChain_Mac
//
// Created by XX on 2024/5/10.
//
#import <Foundation/Foundation.h>
#import <SparkChain/SCEnums.h>
#import "TTS.h"
NS_ASSUME_NONNULL_BEGIN
@interface OnlineTTS : TTS
@property (nonatomic, copy) NSString *aue;
@property (nonatomic, assign) int sfl;
@property (nonatomic, copy) NSString *auf;
@property (nonatomic, copy) NSString *tte;
- (instancetype)initWithVcn:(NSString *)vcn;
/// 指定接入区域与发音人(与底层 `OnlineTTS(REGION_TYPE, string)` 一致)。
- (instancetype)initWithRegion:(RegionType)region vcn:(NSString *)vcn;
@end
NS_ASSUME_NONNULL_END

22
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/PPTError.h

@ -0,0 +1,22 @@
//
// PPTError.h
// SparkChain
//
// Created by pcfang on 2025/5/14.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface PPTError : NSObject
@property (nonatomic, assign) int code;
@property (nonatomic, copy) NSString *errMsg;
@property (nonatomic, copy) NSString *sid;
@end
NS_ASSUME_NONNULL_END

52
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/PPTResult.h

@ -0,0 +1,52 @@
//
// PPTInfo.h
// SparkChain_Mac
//
// Created by pcfang on 22.5.24.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface PPTTheme : NSObject
@property (nonatomic, copy, readonly) NSString * key;
@property (nonatomic, copy, readonly) NSString * name;
@property (nonatomic, copy, readonly) NSString * thumbnail;
@end
@interface PPTInfo : NSObject
@property (nonatomic, assign, readonly) int code;
@property (nonatomic, copy, readonly) NSString * coverImgSrc;
@property (nonatomic, copy, readonly) NSString * title;
@property (nonatomic, copy, readonly) NSString * subtitle;
@property (nonatomic, copy, readonly) NSString * pptUrl;
@property (nonatomic, copy, readonly) NSString * pptId;
@property (nonatomic, copy, readonly) NSString * outline;
@property (nonatomic, strong, readonly) NSArray <PPTTheme *> * themes;
@end
@interface PPTResult : NSObject
@property (nonatomic, copy, readonly) NSString * sid;
@property (nonatomic, strong, readonly) PPTInfo * info;
@property (nonatomic, assign, readonly) int progress;
@end
NS_ASSUME_NONNULL_END

25
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/PersonateTTS.h

@ -0,0 +1,25 @@
//
// PersonateTTS.h
// SparkChain
//
// Created by pcfang on 23.2.24.
//
#import <Foundation/Foundation.h>
#import "TTS.h"
NS_ASSUME_NONNULL_BEGIN
@interface PersonateTTS : TTS
@property(nonatomic, assign) int topK;
@property(nonatomic, assign) int maxTokens;
@property(nonatomic, copy) NSString * oralLevel;
@property(nonatomic, assign) bool sparkAssist;
- (instancetype)initWithVcn:(NSString *)vcn;
@end
NS_ASSUME_NONNULL_END

176
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/RAASR.h

@ -0,0 +1,176 @@
//
// RAASR.h
// SparkChain_iOS
//
// Created by pcfang on 2024/8/8.
//
#import <Foundation/Foundation.h>
#import <SparkChain/SCEnums.h>
@class RAASRResult;
@class RAASRError;
NS_ASSUME_NONNULL_BEGIN
@protocol RAASRCallback <NSObject>
- (void)onResult:(RAASRResult *)result userInfo:(id _Nullable)userInfo;
- (void)onError:(RAASRError *)error userInfo:(id _Nullable)userInfo;
- (void)onProcess:(double)dltotal dlnow:(double)dlnow utotal:(double)utotal ulnow:(double)ulnow userInfo:(id _Nullable)userInfo;
@end
@interface RAASR : NSObject
//common
@property (nonatomic, copy) NSString * pd;
@property (nonatomic, copy) NSString * audioURL;
@property (nonatomic, assign) int languageType;
@property (nonatomic, assign) int engSegMax;
@property (nonatomic, assign) int engSegMin;
@property (nonatomic, assign) float engSegWeight;
@property (nonatomic, assign) bool engSmoothproc;
@property (nonatomic, assign) bool engColloqproc;
@property (nonatomic, assign) int engVadMdn;
@property (nonatomic, assign) int engVadMargin;
@property (nonatomic, assign) int engRlang;
//国内
@property (nonatomic, copy) NSString * language;
@property (nonatomic, copy) NSString * resultType;
@property (nonatomic, copy) NSString * hotWord;
@property (nonatomic, assign) int candidate;
@property (nonatomic, assign) int roleType;
@property (nonatomic, assign) int roleNum;
@property (nonatomic, copy) NSString * audioMode;
@property (nonatomic, assign) int standardWav;
@property (nonatomic, assign) int trackMode;
@property (nonatomic, copy) NSString * transLanguage;
@property (nonatomic, assign) int transMode;
//海外
// common params
@property (nonatomic, copy) NSString * ostUid;
@property (nonatomic, copy) NSString * ostDeviceId;
//@property (nonatomic, copy) NSString * ostRequestId;
@property (nonatomic, copy) NSString * ostDeviceImei;
@property (nonatomic, copy) NSString * ostDeviceImsi;
@property (nonatomic, copy) NSString * ostDeviceMac;
@property (nonatomic, copy) NSString * ostDeviceOther;
@property (nonatomic, copy) NSString * ostNetType;
@property (nonatomic, copy) NSString * ostNetIsp;
@property (nonatomic, copy) NSString * ostAppVer;
// business params
@property (nonatomic, copy) NSString * ostTaskType;
@property (nonatomic, copy) NSString * ostFormat;
@property (nonatomic, copy) NSString * ostEncoding;
@property (nonatomic, assign) int ostAudioSize;
@property (nonatomic, copy) NSString * ostAudioSrc;
@property (nonatomic, copy) NSString * ostCallbackKey;
@property (nonatomic, copy) NSString * ostCallbackSecret;
@property (nonatomic, copy) NSArray<NSString *> * ostFeatureList;
@property (nonatomic, assign) int ostProcinfoOn;
@property (nonatomic, assign) int ostVsppOn;
@property (nonatomic, assign) int ostSpeakerNum;
@property (nonatomic, assign) int ostOutputType;
@property (nonatomic, assign) int ostPostprocOn;
@property (nonatomic, assign) int ostResegment;
@property (nonatomic, copy) NSString * ostResId;
@property (nonatomic, assign) int ostDuration;
@property (nonatomic, copy) NSString * ostResUrl;
@property (nonatomic, assign) int ostEnableSubtitle;
@property (nonatomic, assign) int ostVto;
@property (nonatomic, copy) NSString * ostDhw;
@property (nonatomic, assign) int ostNbest;
@property (nonatomic, assign) int ostCombineMax;
@property (nonatomic, assign) int ostNunum;
- (void)addIntParam:(NSString *)key value:(int)value;
- (void)addBoolParam:(NSString *)key value:(BOOL)value;
- (void)addStringParam:(NSString *)key value:(NSString *)value;
@property (nonatomic, weak) id<RAASRCallback> delegate;
- (instancetype)init;
- (instancetype)initWithRegion:(RegionType)regoin;
- (instancetype)initWithSecretKey:(NSString *)apiKey;
- (instancetype)initWithSecretKey:(NSString *)apiKey regoin:(RegionType)regoin;
- (void)setOstPersonalizationKey:(NSString *)key value:(NSString *)value;
/// 上传音频文件
/// - Parameters:
/// - fileName: 音频文件路径(仅国内支持),或者音频链接,国内需设置audioMode为urlLink
/// - requestId: 仅海外支持,表示请求id,必传;国内可传空值
/// - userInfo: 用户自定义标识
- (int)uploadAsync:(NSString *)fileName requestId:(NSString *)requestId userInfo:(id _Nullable)userInfo;
/// 上传音频文件
/// - Parameter fileName: 音频文件路径(仅国内支持),或者音频链接,国内需设置audioMode为urlLink
- (int) uploadAsync:(NSString *)fileName;
/// 查询订单状态
/// - Parameters:
/// - orderId: 订单id
/// - userInfo: 用户自定义标识
- (int)queryResult:(NSString *)orderId userInfo:(id _Nullable)userInfo;
/// 查询订单状态
/// - Parameter orderId: 订单id
- (int)queryResult:(NSString *)orderId;
//仅国内
- (int)stop;
#pragma mark - overseas
//海外
/// 取消任务,仅海外支持
/// - Parameters:
/// - orderId: 订单id
/// - userInfo: 用户自定义标识
- (int)ostRetryTaskWithOrderId:( NSString * _Nonnull )orderId userInfo:(id _Nullable)userInfo;
/// 删除任务,仅海外支持
/// - Parameters:
/// - orderId: 订单id
/// - requestId: 客户端用于标记任务的唯一id,最大长度64字符,由客户端保证唯一性,服务回调结果时会会包含此参数
/// - userInfo: 用户自定义标识
- (int)ostCancelTaskWithOrderId:( NSString * _Nonnull )orderId requestId:( NSString * _Nonnull )requestId userInfo:(id _Nullable)userInfo;
/// 删除任务,仅海外支持
/// - Parameters:
/// - orderId: 订单id
/// - requestId: 客户端用于标记任务的唯一id,最大长度64字符,由客户端保证唯一性,服务回调结果时会会包含此参数
/// - userInfo: 用户自定义标识
- (int)ostDeleteTaskWithOrderId:( NSString * _Nonnull )orderId requestId:( NSString * _Nonnull )requestId userInfo:(id _Nullable)userInfo;
@end
NS_ASSUME_NONNULL_END

35
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/RAASRError.h

@ -0,0 +1,35 @@
//
// RAASRError.h
// SparkChain_iOS
//
// Created by pcfang on 2024/8/9.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RAASRError : NSObject
//common
@property (nonatomic, assign) int code;
@property (nonatomic, copy) NSString *errMsg;
//国内
@property (nonatomic, copy) NSString *orderId;
@property (nonatomic, assign) int failType;
//海外
//海外
@property (nonatomic, copy, readonly) NSString * ostTaskStatus;
@property (nonatomic, copy, readonly) NSString * ostSid;
@property (nonatomic, copy, readonly) NSString * ostTaskId;
@property (nonatomic, copy, readonly) NSString * ostTaskType;
@end
NS_ASSUME_NONNULL_END

59
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/RAASRResult.h

@ -0,0 +1,59 @@
//
// RAASRResult.h
// SparkChain_iOS
//
// Created by pcfang on 2024/8/9.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RAASRTransResult : NSObject
@property (nonatomic, copy, readonly) NSString * segId;
@property (nonatomic, copy, readonly) NSString * dst;
@property (nonatomic, assign, readonly) int bg;
@property (nonatomic, assign, readonly) int ed;
@property (nonatomic, copy, readonly) NSArray <NSString *> * tags;
@property (nonatomic, copy, readonly) NSArray <NSString *> * roles;
@end
@interface RAASRResult : NSObject
//common
@property (nonatomic, assign, readonly) int code;
@property (nonatomic, copy, readonly) NSString * orderResult;
//国内
@property (nonatomic, assign, readonly) int status;
@property (nonatomic, copy, readonly) NSString * orderId;
@property (nonatomic, assign, readonly) long originalDuration;
@property (nonatomic, assign, readonly) long realDuration;
@property (nonatomic, assign, readonly) long expireTime;
@property (nonatomic, assign, readonly) int taskEstimateTime;
@property (nonatomic, copy, readonly) NSArray <RAASRTransResult *> * transResult;
//海外
@property (nonatomic, copy, readonly) NSString * ostTaskStatus;
@property (nonatomic, copy, readonly) NSString * ostSid;
@property (nonatomic, copy, readonly) NSString * ostTaskId;
@property (nonatomic, copy, readonly) NSString * ostTaskType;
@end
NS_ASSUME_NONNULL_END

149
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/RTASR.h

@ -0,0 +1,149 @@
//
// RTASR.h
// SparkChain_iOS
// 实时语音转写
// Created by pcfang on 2024/8/8.
//
#import <Foundation/Foundation.h>
#import <SparkChain/SCEnums.h>
@class RTASRResult;
@class RTASRError;
NS_ASSUME_NONNULL_BEGIN
@protocol RTASRCallback <NSObject>
- (void)onResult:(RTASRResult *) result useInfo:(id _Nullable)userInfo;
- (void)onError:(RTASRError *) error useInfo:(id _Nullable)userInfo;
@end
@interface RTASR : NSObject
//common
@property (nonatomic, copy) NSString * lang;
@property (nonatomic, copy) NSString * punc;
@property (nonatomic, copy) NSString * pd;
@property (nonatomic, assign) int vadMdn;
//国内
@property (nonatomic, copy) NSString * transType;
@property (nonatomic, assign) int transStrategy;
@property (nonatomic, copy) NSString * targetLanguage;
@property (nonatomic, assign) int roleType;
@property (nonatomic, assign) int engLangType;
#pragma mark - region overseas params
@property (nonatomic, copy) NSString * ostContextId;
@property (nonatomic, copy) NSString * ostUid;
@property (nonatomic, copy) NSString * ostDeviceId;
@property (nonatomic, copy) NSString * ostDeviceImei;
@property (nonatomic, copy) NSString * ostDeviceMac;
@property (nonatomic, copy) NSString * ostDeviceImsi;
@property (nonatomic, copy) NSString * ostDeviceOther;
@property (nonatomic, copy) NSString * ostNetType;
@property (nonatomic, copy) NSString * ostNetIsp;
@property (nonatomic, copy) NSString * ostAppVer;
@property (nonatomic, copy) NSString * ostDid;
//business params
@property (nonatomic, copy) NSString * ostFormat;
@property (nonatomic, copy) NSString * ostEncoding;
@property (nonatomic, copy) NSString * ostDomain;
@property (nonatomic, copy) NSString * ostAccent;
@property (nonatomic, copy) NSString * ostRequestId;
@property (nonatomic, copy) NSString * ostDwa;
@property (nonatomic, assign) int ostEos;
@property (nonatomic, copy) NSString * ostRf;
@property (nonatomic, copy) NSString * ostResId;
@property (nonatomic, assign) int ostVto;
@property (nonatomic, assign) int ostNunum;
@property (nonatomic, assign) int ostPptaw;
@property (nonatomic, assign) int ostDyhotws;
@property (nonatomic, assign) int ostSegMax;
@property (nonatomic, assign) int ostSegMin;
@property (nonatomic, assign) float ostSegWeight;
@property (nonatomic, assign) int ostSpeexSize;
@property (nonatomic, assign) int ostSpkdia;
@property (nonatomic, assign) int ostPgsnum;
@property (nonatomic, assign) int ostLanguageType;
@property (nonatomic, copy) NSString * ostDhw;
@property (nonatomic, assign) int ostDhwMod;
@property (nonatomic, copy) NSArray<NSString *> * ostFeatureList;
@property (nonatomic, assign) int ostRsgid;
@property (nonatomic, assign) int ostRlang;
@property (nonatomic, assign) int ostPgsFlashFreq;
@property (nonatomic, copy) NSString * ostFrameId;
- (void)addIntParam:(NSString *)key value:(int)value;
- (void)addBoolParam:(NSString *)key value:(BOOL)value;
- (void)addStringParam:(NSString *)key value:(NSString *)value;
@property (nonatomic, weak) id<RTASRCallback> delegate;
- (instancetype)init;
- (instancetype)initWithRegion:(RegionType)region;
- (instancetype)initWithSecretKey:(NSString *)secretKey;
- (instancetype)initWithSecretKey:(NSString *)secretKey region:(RegionType)region;
- (void)setOstPersonalization:(NSString *)key value:(NSString *)value;
- (int)start;
- (int)startWithUserInfo:(id _Nullable)userInfo;
- (int)write:(NSData *)audioData;
- (int)stop;
- (int)stop:(BOOL)immediate;
@end
NS_ASSUME_NONNULL_END

25
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/RTASRError.h

@ -0,0 +1,25 @@
//
// RTASRError.h
// SparkChain_iOS
//
// Created by pcfang on 2024/8/9.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RTASRError : NSObject
@property (nonatomic, assign) int code;
@property (nonatomic, copy) NSString *errMsg;
@property (nonatomic, copy) NSString *sid;
//海外
@property (nonatomic, copy) NSString *ostContextId;
@end
NS_ASSUME_NONNULL_END

38
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/RTASRResult.h

@ -0,0 +1,38 @@
//
// RTASRResult.h
// SparkChain_iOS
//
// Created by pcfang on 2024/8/9.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RTASRTransResult : NSObject
@property(nonatomic, assign, readonly) int status;
@property(nonatomic, copy, readonly) NSString * src;
@property(nonatomic, copy, readonly) NSString * dst;
@end
@interface RTASRResult : NSObject
@property(nonatomic, assign, readonly) int status;
@property(nonatomic, copy, readonly) NSString * sid;
@property(nonatomic, copy, readonly) NSString * data;
@property(nonatomic, copy, readonly) NSString * raw;
@property(nonatomic, copy, readonly) RTASRTransResult * transResult;
@property(nonatomic, copy, readonly) NSString * ostContextId;
@end
NS_ASSUME_NONNULL_END

20
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/SCEnums.h

@ -0,0 +1,20 @@
//
// SCEnums.h
// SparkChain_iOS
//
// Created by pcfang on 2025/5/8.
//
#ifndef SCEnums_h
#define SCEnums_h
typedef enum : NSUInteger {
RegionDefault,
RegionSG,
RegionEU,
RegionCNLLM,
RegionMSFT
} RegionType;
#endif /* SCEnums_h */

20
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/Segment.h

@ -0,0 +1,20 @@
//
// Segment.h
// SparkChain_Mac
//
// Created by XX on 2024/5/11.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Segment : NSObject
@property (nonatomic) int score;
@property (nonatomic, copy) NSString *text;
@property (nonatomic, copy) NSString *lg;
@end
NS_ASSUME_NONNULL_END

67
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/SparkChain.h

@ -0,0 +1,67 @@
//
// SparkChain.h
// SparkChain
//
// Created by pcfang on 12.9.23.
//
#import <Foundation/Foundation.h>
//! Project version number for SparkChain.
FOUNDATION_EXPORT double SparkChainVersionNumber;
//! Project version string for SparkChain.
FOUNDATION_EXPORT const unsigned char SparkChainVersionString[];
#import <SparkChain/SparkChainConfig.h>
#import <SparkChain/SparkChainEntrance.h>
#import <SparkChain/LLM.h>
#import <SparkChain/LLMEvent.h>
#import <SparkChain/LLMError.h>
#import <SparkChain/LLMResult.h>
#import <SparkChain/LLMConfig.h>
#import <SparkChain/Memory.h>
#import <SparkChain/LLMTools.h>
#import <SparkChain/TTS.h>
#import <SparkChain/PersonateTTS.h>
//合成
#import <SparkChain/OnlineTTS.h>
#import <SparkChain/TTSResult.h>
#import <SparkChain/TTSError.h>
// 实时转写
#import <SparkChain/RTASR.h>
#import <SparkChain/RTASRResult.h>
#import <SparkChain/RTASRError.h>
// 录音文件转写
#import <SparkChain/RAASR.h>
#import <SparkChain/RAASRResult.h>
#import <SparkChain/RAASRError.h>
// Feature / 虚拟人 VC
#import <SparkChain/Feature.h>
#import <SparkChain/FeatureResult.h>
#import <SparkChain/FeatureError.h>
#import <SparkChain/VC.h>
#import <SparkChain/VCResult.h>
#import <SparkChain/VCError.h>
#import <SparkChain/VCTextSeg.h>
// 翻译
#import <SparkChain/ITS.h>
#import <SparkChain/ITSResult.h>
#import <SparkChain/ITSError.h>
//识别
#import <SparkChain/ASR.h>
#import <SparkChain/ASRResult.h>
#import <SparkChain/ASRError.h>
#import <SparkChain/ASRHotwordsResult.h>

33
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/SparkChainConfig.h

@ -0,0 +1,33 @@
//
// SparkChainConfig.h
// SparkChain
//
// Created by pcfang on 12.9.23.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#define chainProperty(returnType,name) property(nonatomic, readonly, copy) returnType (^name)
@interface SparkChainConfig : NSObject
@chainProperty(SparkChainConfig *,appID)(NSString * appID);
@chainProperty(SparkChainConfig *,apiKey)(NSString * apiKey);
@chainProperty(SparkChainConfig *,apiSecret)(NSString * apiSecret);
@chainProperty(SparkChainConfig *,workDir)(NSString * workDir);
@chainProperty(SparkChainConfig *,uid)(NSString * uid);
@chainProperty(SparkChainConfig *,logLevel)(int logLevel);
@chainProperty(SparkChainConfig *,logPath)(NSString * logPath);
@chainProperty(SparkChainConfig *,apiType)(int apiType);
@end
NS_ASSUME_NONNULL_END

24
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/SparkChainEntrance.h

@ -0,0 +1,24 @@
//
// SparkChain.h
// SparkChain
//
// Created by pcfang on 12.9.23.
//
#import <Foundation/Foundation.h>
@class SparkChainConfig;
NS_ASSUME_NONNULL_BEGIN
@interface SparkChain : NSObject
+ (SparkChain *)getInst;
- (int)init:(SparkChainConfig *)config;
- (int)unInit;
- (int)setConfig:(NSString *)key value:(void *)value;
@end
NS_ASSUME_NONNULL_END

90
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/TTS.h

@ -0,0 +1,90 @@
//
// TTS.h
// SparkChain
//
// Created by pcfang on 22.2.24.
//
#import <Foundation/Foundation.h>
@class TTS;
@class TTSResult;
@class TTSError;
NS_ASSUME_NONNULL_BEGIN
typedef enum : NSUInteger {
Begin,
Continue,
End,
} Status;
@protocol TTSCallbacks <NSObject>
- (void)tts:(TTS *)tts onResult:(TTSResult *)result usrContext:(id)usrContext;
- (void)tts:(TTS *)tts onError:(TTSError *)error usrContext:(id)usrContext;
@end
@interface TTS : NSObject
@property(nonatomic, weak) id<TTSCallbacks> cbs;
@property(nonatomic, copy) NSString * vcn;
@property(nonatomic, assign) int speed;
@property(nonatomic, assign) int pitch;
@property(nonatomic, assign) int volume;
@property(nonatomic, assign) int reg;
@property(nonatomic, assign) int rdn;
// 以下海外不支持
@property(nonatomic, assign) int sampleRate;
@property(nonatomic, copy) NSString * encoding;
@property(nonatomic, assign) int bitDepth;
@property(nonatomic, assign) int channels;
@property(nonatomic, assign) int frameSize;
@property(nonatomic, assign) int bgs;
@property(nonatomic, assign) BOOL rhy;
@property(nonatomic, assign) int scn;
@property(nonatomic, assign) BOOL version;
@property(nonatomic, assign) int l5SilLen;
@property(nonatomic, assign) int paragraphSilLen;
- (instancetype)init;
- (instancetype)initWithVcn:(NSString *)vcn;
- (int)arun:(NSString *)text;
- (int)arun:(NSString *)text userInfo:(id)userInfo;
- (int)arun:(NSString *)text status:(Status)status;
- (int)arun:(NSString *)text status:(Status)status userInfo:(id)userInfo;
- (void)stop;
- (void)addIntParam:(NSString *)key value:(int)value;
- (void)addBoolParam:(NSString *)key value:(BOOL)value;
- (void)addStringParam:(NSString *)key value:(NSString *)value;
@end
NS_ASSUME_NONNULL_END

22
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/TTSError.h

@ -0,0 +1,22 @@
//
// TTSError.h
// SparkChain
//
// Created by pcfang on 22.2.24.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TTSError : NSObject
@property(nonatomic, copy, readonly) NSString * sid;
@property(nonatomic, assign, readonly) int code;
@property(nonatomic, copy, readonly) NSString * errMsg;
@end
NS_ASSUME_NONNULL_END

32
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/TTSResult.h

@ -0,0 +1,32 @@
//
// TTSResult.h
// SparkChain
//
// Created by pcfang on 22.2.24.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TTSResult : NSObject
@property(nonatomic, copy, readonly) NSString * sid;
@property(nonatomic, assign, readonly) int seq;
@property(nonatomic, copy, readonly) NSData * data;
@property(nonatomic, copy, readonly) NSString * ced;
@property(nonatomic, copy, readonly) NSString * pybuf;
@property(nonatomic, copy, readonly) NSString * spell;
@property(nonatomic, copy, readonly) NSString * version;
@property(nonatomic, assign, readonly) int status;
@end
NS_ASSUME_NONNULL_END

36
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/TableOcr.h

@ -0,0 +1,36 @@
//
// OCTableOcr.h
// TABLE_OCR
//
// Created by xyzhang on 2024/6/4.
//
#import <Foundation/Foundation.h>
@class TableOcr;
@class TableOcrResult;
@class TableOcrError;
NS_ASSUME_NONNULL_BEGIN
@protocol TableOcrDelegate <NSObject>
- (void)tableOcr:(TableOcr *)tableOcr onResult:(TableOcrResult *)result usrContext:(id)usrContext;
- (void)tableOcr:(TableOcr *)tableOcr onError:(TableOcrError *)error usrContext:(id)usrContext;
@end
@interface TableOcr : NSObject
@property (nonatomic, weak) id<TableOcrDelegate> delegate;
@property (nonatomic, copy) NSString *resultEncoding;
@property (nonatomic, copy) NSString *resultCompress;
@property (nonatomic, copy) NSString *resultFormat;
- (instancetype)init;
- (int)arun:(NSData *)imageData encoding:(NSString *)encoding userInfo:(id)userInfo;
@end
NS_ASSUME_NONNULL_END

20
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/TableOcrError.h

@ -0,0 +1,20 @@
//
// OCTableOcrError.h
// TABLE_OCR
//
// Created by xyzhang on 2024/6/4.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TableOcrError : NSObject
@property (nonatomic, copy, readonly) NSString *sid;
@property (nonatomic, copy, readonly) NSString *errMsg;
@property (nonatomic, assign, readonly) int code;
@end
NS_ASSUME_NONNULL_END

24
apps/client/local_plugins/sparkchain/ios/sparkchain/SparkChain.xcframework/ios-arm64/SparkChain.framework/Headers/TableOcrExtension.h

@ -0,0 +1,24 @@
//
// OCTableOcrExtension.h
// TABLE_OCR
//
// Created by xyzhang on 2024/6/4.
//
#import "TableOcr.h"
#import "TableOcrResult.h"
#import "TableOcrError.h"
#include "sc_tableOCR.h"
@interface TableOcr ()
- (instancetype)initWithTableOcr:(SparkChain::TableOCR *)tableOcr;
- (SparkChain::TableOCR *)tableOcrRef;
@end
@interface TableOcrResult ()
- (instancetype)initWithResult:(SparkChain::TableOCRResult *)result;
@end
@interface TableOcrError ()
- (instancetype)initWithError:(SparkChain::TableOCRError *)error;
@end

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

Loading…
Cancel
Save