Skip to content

Commit 5ceafb1

Browse files
joewongjcclaude
andcommitted
bump to v1.6.1: 流式识别韧性 + 代理绕过 + 词库优化
- 流式识别韧性大幅增强:按停止立即响应、不再重复粘贴、超时自动恢复 - 连接中途断开时自动用完整录音重新识别(batch fallback) - 中断/失败的识别也保存到历史记录 - 新增「绕过系统代理」选项 - Deepgram 热词限制 30 个并在设置页提示 - 词库管理界面优化:替换映射按组显示、支持排序 - ASR 设置自动填充默认值 - 自动更新修复:不再对已签名 DMG 重复签名 - AssemblyAI 多语言模型支持 - 6 个 ASR 客户端发送计数修正 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 49e57d8 commit 5ceafb1

27 files changed

Lines changed: 595 additions & 176 deletions

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## v1.6.1 — 流式识别韧性 + 代理绕过 + 词库优化 (2026-03-31)
4+
5+
- 流式识别韧性大幅增强:按停止立即响应、不再重复粘贴、超时自动恢复
6+
- 连接中途断开时自动用完整录音重新识别(batch fallback)
7+
- 中断/失败的识别也保存到历史记录
8+
- 新增「绕过系统代理」选项(关闭/仅 ASR/全部)
9+
- Deepgram 热词受 URL 长度限制,自动截取前 30 个并在设置页提示
10+
- 词库管理界面优化:替换映射按组显示、热词和替换映射支持排序
11+
- ASR 设置:新 provider 自动填充默认值、凭证校验优化
12+
- 自动更新修复:不再对已签名 DMG 重复签名(修复 Gatekeeper「已损坏」错误)
13+
- AssemblyAI 多语言模型支持
14+
- 6 个 ASR 客户端发送计数修正,避免误判连接状态
15+
316
## v1.6.0 — 应用内更新 + Apple Speech + Bug 修复 (2026-03-30)
417

518
- 应用内更新:设置页 About 标签直接下载新版本并自动安装重启,Local 版更新时自动保留本地 ASR 模型

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ Prompt 模板支持三种变量,让语音输入从"听写"升级为"语音命
9595
9696
| 版本 | 说明 | 大小 |
9797
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------ |
98-
| **[Type4Me-v1.6.0-local.dmg](https://github.com/joewongjc/type4me/releases/download/v1.6.0/Type4Me-v1.6.0-local.dmg)** | 内嵌 SenseVoice(实时流式识别展示) + Qwen3-ASR(完整语音校准),支持单选1个模型使用或一起用。开箱即用。 | ~1.2GB |
99-
| **[Type4Me-v1.6.0-cloud.dmg](https://github.com/joewongjc/type4me/releases/download/v1.6.0/Type4Me-v1.6.0-cloud.dmg)** | 仅云端识别,需配置 API Key | ~23MB |
98+
| **[Type4Me-v1.6.1-local.dmg](https://github.com/joewongjc/type4me/releases/download/v1.6.1/Type4Me-v1.6.1-local.dmg)** | 内嵌 SenseVoice(实时流式识别展示) + Qwen3-ASR(完整语音校准),支持单选1个模型使用或一起用。开箱即用。 | ~1.2GB |
99+
| **[Type4Me-v1.6.1-cloud.dmg](https://github.com/joewongjc/type4me/releases/download/v1.6.1/Type4Me-v1.6.1-cloud.dmg)** | 仅云端识别,需配置 API Key | ~23MB |
100100

101101
系统要求:macOS 14+ (Sonoma)
102102

103-
> **关于更新:** v1.6.0 起支持应用内更新(设置 → 关于 → 下载更新),Local 版更新时仅需下载 ~24MB 的 Cloud 包,本地模型自动保留。从旧版本升级到 v1.6.0 的 Local 用户,请首次下载 Local DMG,之后即可使用应用内更新。
103+
> **关于更新:** v1.6.1 起支持应用内更新(设置 → 关于 → 下载更新),Local 版更新时仅需下载 ~24MB 的 Cloud 包,本地模型自动保留。从旧版本升级到 v1.6.1 的 Local 用户,请首次下载 Local DMG,之后即可使用应用内更新。
104104
105105
> **首次打开提示安全警告?** 这是 macOS 对所有非 App Store 应用的正常行为,不影响使用。
106106
>

Type4Me/ASR/AssemblyAIASRClient.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ actor AssemblyAIASRClient: SpeechRecognizer {
8686
let gate = AssemblyAIConnectionGate()
8787
let closeTracker = AssemblyAICloseTracker()
8888
let delegate = AssemblyAIWebSocketDelegate(connectionGate: gate, closeTracker: closeTracker)
89-
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
89+
let session = URLSession(configuration: options.urlSessionConfiguration, delegate: delegate, delegateQueue: nil)
9090
let task = session.webSocketTask(with: request)
9191
task.resume()
9292

@@ -104,12 +104,17 @@ actor AssemblyAIASRClient: SpeechRecognizer {
104104

105105
try await gate.waitUntilReady(timeout: .seconds(5))
106106
logger.info("AssemblyAI WebSocket connected: \(url.absoluteString, privacy: .private(mask: .hash))")
107+
108+
// Send keyterms via UpdateConfiguration after connection to avoid URL length limits
109+
if let keytermsMsg = AssemblyAIProtocol.updateConfigurationMessage(hotwords: options.hotwords) {
110+
try? await task.send(.string(keytermsMsg))
111+
}
107112
}
108113

109114
func sendAudio(_ data: Data) async throws {
110115
guard let task = webSocketTask else { return }
111-
audioPacketCount += 1
112116
try await task.send(.data(data))
117+
audioPacketCount += 1
113118
}
114119

115120
func endAudio() async throws {

Type4Me/ASR/BaiduASRClient.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ actor BaiduASRClient: SpeechRecognizer {
6363

6464
let gate = BaiduConnectionGate()
6565
let delegate = BaiduWebSocketDelegate(connectionGate: gate)
66-
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
66+
let session = URLSession(configuration: options.urlSessionConfiguration, delegate: delegate, delegateQueue: nil)
6767
let requestID = UUID().uuidString.lowercased()
6868
let task = session.webSocketTask(with: BaiduProtocol.buildWebSocketURL(requestID: requestID))
6969
task.resume()
@@ -85,8 +85,8 @@ actor BaiduASRClient: SpeechRecognizer {
8585

8686
func sendAudio(_ data: Data) async throws {
8787
guard let task = webSocketTask else { return }
88-
audioPacketCount += 1
8988
try await task.send(.data(data))
89+
audioPacketCount += 1
9090
}
9191

9292
func endAudio() async throws {

Type4Me/ASR/BailianASRClient.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ actor BailianASRClient: SpeechRecognizer {
6868
let taskID = UUID().uuidString.lowercased()
6969
let gate = BailianTaskStartGate()
7070
let delegate = BailianWebSocketDelegate(taskStartGate: gate)
71-
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
71+
let session = URLSession(configuration: options.urlSessionConfiguration, delegate: delegate, delegateQueue: nil)
7272
let task = session.webSocketTask(with: request)
7373
task.resume()
7474

@@ -96,8 +96,8 @@ actor BailianASRClient: SpeechRecognizer {
9696

9797
func sendAudio(_ data: Data) async throws {
9898
guard let task = webSocketTask else { return }
99-
audioPacketCount += 1
10099
try await task.send(.data(data))
100+
audioPacketCount += 1
101101
}
102102

103103
func endAudio() async throws {

Type4Me/ASR/DeepgramASRClient.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ actor DeepgramASRClient: SpeechRecognizer {
7070

7171
let connectionGate = DeepgramConnectionGate()
7272
let delegate = DeepgramWebSocketDelegate(connectionGate: connectionGate)
73-
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
73+
let session = URLSession(configuration: options.urlSessionConfiguration, delegate: delegate, delegateQueue: nil)
7474
let task = session.webSocketTask(with: request)
7575
task.resume()
7676

@@ -90,14 +90,14 @@ actor DeepgramASRClient: SpeechRecognizer {
9090

9191
func sendAudio(_ data: Data) async throws {
9292
guard let task = webSocketTask else { return }
93-
audioPacketCount += 1
9493
try await task.send(.data(data))
94+
audioPacketCount += 1
9595
}
9696

9797
func endAudio() async throws {
9898
guard let task = webSocketTask else { return }
99-
didRequestClose = true
10099
try await task.send(.string(DeepgramProtocol.closeStreamMessage()))
100+
didRequestClose = true
101101
}
102102

103103
func disconnect() {

Type4Me/ASR/Providers/AssemblyAIASRConfig.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ struct AssemblyAIASRConfig: ASRProviderConfig, Sendable {
1515
CredentialField(
1616
key: "apiKey",
1717
label: "API Key",
18-
placeholder: "aa_...",
18+
placeholder: L("粘贴 API Key", "Paste your API Key"),
1919
isSecure: true,
2020
isOptional: false,
2121
defaultValue: ""
@@ -26,7 +26,8 @@ struct AssemblyAIASRConfig: ASRProviderConfig, Sendable {
2626
placeholder: defaultModel,
2727
isSecure: false,
2828
isOptional: false,
29-
defaultValue: defaultModel
29+
defaultValue: defaultModel,
30+
options: supportedModels.map { FieldOption(value: $0, label: $0) }
3031
),
3132
]}
3233

Type4Me/ASR/Providers/DeepgramASRConfig.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,29 @@ struct DeepgramASRConfig: ASRProviderConfig, Sendable {
44

55
static let provider = ASRProvider.deepgram
66
static let displayName = "Deepgram"
7-
static let defaultModel = "nova-2"
7+
static let defaultModel = "nova-3"
88
static let defaultLanguage = "zh"
99

10+
static let supportedModels = [
11+
"nova-3",
12+
"nova-2",
13+
"nova-2-general",
14+
"nova-2-meeting",
15+
"nova-2-phonecall",
16+
]
17+
18+
static let supportedLanguages = [
19+
"zh", "zh-CN", "zh-TW", "zh-HK",
20+
"en", "en-US", "en-GB",
21+
"ja", "ko", "multi",
22+
]
23+
1024
static var credentialFields: [CredentialField] {[
11-
CredentialField(key: "apiKey", label: "API Key", placeholder: "dg_...", isSecure: true, isOptional: false, defaultValue: ""),
12-
CredentialField(key: "model", label: "Model", placeholder: defaultModel, isSecure: false, isOptional: false, defaultValue: defaultModel),
13-
CredentialField(key: "language", label: "Language", placeholder: defaultLanguage, isSecure: false, isOptional: false, defaultValue: defaultLanguage),
25+
CredentialField(key: "apiKey", label: "API Key", placeholder: L("粘贴 API Key", "Paste your API Key"), isSecure: true, isOptional: false, defaultValue: ""),
26+
CredentialField(key: "model", label: "Model", placeholder: defaultModel, isSecure: false, isOptional: false, defaultValue: defaultModel,
27+
options: supportedModels.map { FieldOption(value: $0, label: $0) }),
28+
CredentialField(key: "language", label: "Language", placeholder: defaultLanguage, isSecure: false, isOptional: false, defaultValue: defaultLanguage,
29+
options: supportedLanguages.map { FieldOption(value: $0, label: $0) }),
1430
]}
1531

1632
let apiKey: String

Type4Me/ASR/Providers/OpenAIASRConfig.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ struct OpenAIASRConfig: ASRProviderConfig, Sendable {
1818
FieldOption(value: "whisper-1", label: "Whisper ($0.36/hr)"),
1919
]
2020
),
21-
CredentialField(key: "baseURL", label: "Base URL", placeholder: "https://api.openai.com/v1", isSecure: false, isOptional: true, defaultValue: "https://api.openai.com/v1"),
2221
]
2322

23+
private static let defaultBaseURL = "https://api.openai.com/v1"
24+
2425
let apiKey: String
2526
let model: String
2627
let baseURL: String
@@ -33,7 +34,7 @@ struct OpenAIASRConfig: ASRProviderConfig, Sendable {
3334
: Self.defaultModel
3435
self.baseURL = credentials["baseURL"]?.isEmpty == false
3536
? credentials["baseURL"]!
36-
: "https://api.openai.com/v1"
37+
: Self.defaultBaseURL
3738
}
3839

3940
func toCredentials() -> [String: String] {

Type4Me/ASR/Providers/SonioxASRConfig.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ struct SonioxASRConfig: ASRProviderConfig, Sendable {
1414
CredentialField(
1515
key: "apiKey",
1616
label: "API Key",
17-
placeholder: "",
17+
placeholder: L("粘贴 API Key", "Paste your API Key"),
1818
isSecure: true,
1919
isOptional: false,
2020
defaultValue: ""

0 commit comments

Comments
 (0)