feat(macos): local ASR model manager, menu-bar polish, and release 0.5.2
Adds a bundled local ASR model catalog for the macOS app with one-click Sherpa Qwen3 / SenseVoice downloads (pause/resume, inline actions) and a shared model storage directory used by MLX Qwen3. Fixes the light-mode sidebar material and makes the menu-bar icon follow the system appearance with a refreshed status mark. Renames the built product to OSGKeyboard.app. Bumps version to 0.5.2 (build 19).
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
// LocalASRTranscriptCorrector.swift
|
||||
// OSGKeyboard · Shared
|
||||
//
|
||||
// Deterministic alias → canonical term replacement between raw ASR output
|
||||
// and the LLM polish step. Only applies whole-phrase matches.
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum LocalASRTranscriptCorrector {
|
||||
|
||||
/// Applies high-confidence alias replacements (longest match first).
|
||||
public static func apply(
|
||||
_ text: String,
|
||||
pairs: [LocalASRCorrectionPair]
|
||||
) -> String {
|
||||
guard !text.isEmpty, !pairs.isEmpty else { return text }
|
||||
|
||||
let sorted = pairs.sorted { lhs, rhs in
|
||||
if lhs.alias.count != rhs.alias.count {
|
||||
return lhs.alias.count > rhs.alias.count
|
||||
}
|
||||
return lhs.alias.localizedCaseInsensitiveCompare(rhs.alias) == .orderedAscending
|
||||
}
|
||||
|
||||
var result = text
|
||||
for pair in sorted {
|
||||
result = replaceWholeMatches(
|
||||
in: result,
|
||||
alias: pair.alias,
|
||||
term: pair.term
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private static func replaceWholeMatches(
|
||||
in text: String,
|
||||
alias: String,
|
||||
term: String
|
||||
) -> String {
|
||||
guard !alias.isEmpty, alias != term else { return text }
|
||||
|
||||
if alias.unicodeScalars.allSatisfy({ $0.isASCII }) {
|
||||
return replaceASCIIWord(in: text, alias: alias, term: term)
|
||||
}
|
||||
return text.replacingOccurrences(of: alias, with: term)
|
||||
}
|
||||
|
||||
private static func replaceASCIIWord(
|
||||
in text: String,
|
||||
alias: String,
|
||||
term: String
|
||||
) -> String {
|
||||
let escaped = NSRegularExpression.escapedPattern(for: alias)
|
||||
let pattern = "(?i)(?<![A-Za-z0-9_])\(escaped)(?![A-Za-z0-9_])"
|
||||
guard let regex = try? NSRegularExpression(pattern: pattern) else {
|
||||
return text
|
||||
}
|
||||
let range = NSRange(text.startIndex..<text.endIndex, in: text)
|
||||
return regex.stringByReplacingMatches(
|
||||
in: text,
|
||||
range: range,
|
||||
withTemplate: term
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user