Удалить нестабильный системный ввод
This commit is contained in:
@@ -7,28 +7,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
private struct PendingChunk {
|
||||
let url: URL
|
||||
let continuesPhrase: Bool
|
||||
let isFinalSession: Bool
|
||||
}
|
||||
|
||||
private let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
private let recorder = AudioRecorder()
|
||||
private let hotKeys = HotKeyManager()
|
||||
private let composition = NativeCompositionBridge()
|
||||
private let settings = AppSettings.shared
|
||||
private var activeMode: TranscriptionMode?
|
||||
private var startingSession = false
|
||||
private var pendingChunks: [PendingChunk] = []
|
||||
private var processingChunk = false
|
||||
private var stopRequested = false
|
||||
private var stitchNextChunk = false
|
||||
private var nativeSession = false
|
||||
private var nativeDraft = ""
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
composition.onAvailabilityChange = { [weak self] in
|
||||
self?.rebuildMenu()
|
||||
}
|
||||
composition.start()
|
||||
configureStatusItem()
|
||||
requestPermissions()
|
||||
hotKeys.onLocal = { [weak self] in Task { @MainActor in await self?.toggle(.local) } }
|
||||
@@ -73,15 +64,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
menu.addItem(.separator())
|
||||
menu.addItem(item("Настройки…", #selector(openSettings)))
|
||||
menu.addItem(item("Проверить локальный движок", #selector(checkLocalEngine)))
|
||||
let nativeState: String
|
||||
if composition.isAvailable {
|
||||
nativeState = "Нативный ввод — активен"
|
||||
} else if composition.isInstalled {
|
||||
nativeState = "Нативный ввод — готов"
|
||||
} else {
|
||||
nativeState = "Установить нативный ввод…"
|
||||
}
|
||||
menu.addItem(item(nativeState, #selector(openInputSources)))
|
||||
menu.addItem(.separator())
|
||||
menu.addItem(item("Выход", #selector(quit), key: "q"))
|
||||
statusItem.menu = menu
|
||||
@@ -109,15 +91,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
await stopAndTranscribe()
|
||||
return
|
||||
}
|
||||
guard !startingSession else { return }
|
||||
startingSession = true
|
||||
defer { startingSession = false }
|
||||
var nativeSelected = composition.activateForDictation()
|
||||
if !nativeSelected {
|
||||
try? await Task.sleep(for: .milliseconds(250))
|
||||
nativeSelected = composition.activateForDictation()
|
||||
}
|
||||
try? await Task.sleep(for: .milliseconds(180))
|
||||
do {
|
||||
activeMode = mode
|
||||
lastMode = mode
|
||||
@@ -125,15 +98,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
processingChunk = false
|
||||
stopRequested = false
|
||||
stitchNextChunk = false
|
||||
nativeDraft = ""
|
||||
nativeSession = nativeSelected && composition.isAvailable
|
||||
if nativeSession {
|
||||
composition.cancel(restoreInputSource: false)
|
||||
} else {
|
||||
composition.restoreInputSource()
|
||||
}
|
||||
try recorder.start(
|
||||
chunkTiming: nativeSession ? .nativeDraft : .committed,
|
||||
onChunk: { [weak self] chunk in
|
||||
Task { @MainActor in self?.enqueue(chunk) }
|
||||
},
|
||||
@@ -160,39 +125,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
DictationSounds.playStop()
|
||||
setStatus("обрабатываю…")
|
||||
do {
|
||||
let result = try recorder.stop()
|
||||
if nativeSession, let fullSession = result.fullSessionURL {
|
||||
if let finalChunk = result.finalChunkURL {
|
||||
try? FileManager.default.removeItem(at: finalChunk)
|
||||
}
|
||||
enqueue(
|
||||
RecordedChunk(url: fullSession, continuesPhrase: false),
|
||||
isFinalSession: true
|
||||
)
|
||||
} else {
|
||||
if let fullSession = result.fullSessionURL {
|
||||
try? FileManager.default.removeItem(at: fullSession)
|
||||
}
|
||||
if let finalChunk = result.finalChunkURL {
|
||||
enqueue(RecordedChunk(url: finalChunk, continuesPhrase: false))
|
||||
}
|
||||
}
|
||||
if pendingChunks.isEmpty, !processingChunk {
|
||||
if nativeSession { composition.cancel() }
|
||||
completeLiveSession()
|
||||
if let final = try recorder.stop() {
|
||||
enqueue(RecordedChunk(url: final, continuesPhrase: false))
|
||||
}
|
||||
if pendingChunks.isEmpty, !processingChunk { completeLiveSession() }
|
||||
} catch {
|
||||
composition.cancel()
|
||||
setStatus("ошибка")
|
||||
showError("Диктовка не удалась", error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func enqueue(_ chunk: RecordedChunk, isFinalSession: Bool = false) {
|
||||
private func enqueue(_ chunk: RecordedChunk) {
|
||||
pendingChunks.append(PendingChunk(
|
||||
url: chunk.url,
|
||||
continuesPhrase: chunk.continuesPhrase,
|
||||
isFinalSession: isFinalSession
|
||||
continuesPhrase: chunk.continuesPhrase
|
||||
))
|
||||
processNextChunk()
|
||||
}
|
||||
@@ -211,7 +157,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
Task {
|
||||
do {
|
||||
let wav = try AudioConverter.toWhisperWAV(recording)
|
||||
if chunk.isFinalSession { saveDiagnosticRecording(wav) }
|
||||
saveDiagnosticRecording(wav)
|
||||
defer {
|
||||
try? FileManager.default.removeItem(at: recording)
|
||||
try? FileManager.default.removeItem(at: wav)
|
||||
@@ -222,43 +168,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
case .cloud: raw = try await OpenAITranscriber.transcribe(wav: wav, settings: settings)
|
||||
}
|
||||
let cleaned = TextCleaner.clean(raw, removeFillers: settings.removeFillers, glossary: settings.glossary)
|
||||
if chunk.isFinalSession {
|
||||
if cleaned.isEmpty {
|
||||
composition.cancel()
|
||||
} else if !composition.commit(cleaned + " ") {
|
||||
try TextInserter.insert(cleaned + " ")
|
||||
}
|
||||
nativeDraft = ""
|
||||
stitchNextChunk = false
|
||||
} else if !cleaned.isEmpty {
|
||||
if !cleaned.isEmpty {
|
||||
let joined = TranscriptJoiner.prepare(
|
||||
cleaned,
|
||||
continuesPrevious: stitchNextChunk,
|
||||
continuesNext: chunk.continuesPhrase,
|
||||
glossary: settings.glossary
|
||||
)
|
||||
if !joined.isEmpty {
|
||||
if nativeSession {
|
||||
nativeDraft += (nativeDraft.isEmpty ? "" : " ") + joined
|
||||
_ = composition.updateDraft(nativeDraft)
|
||||
} else {
|
||||
try TextInserter.insert(joined + " ")
|
||||
}
|
||||
}
|
||||
if !joined.isEmpty { try TextInserter.insert(joined + " ") }
|
||||
stitchNextChunk = chunk.continuesPhrase
|
||||
}
|
||||
} catch {
|
||||
if chunk.isFinalSession, !nativeDraft.isEmpty {
|
||||
if !composition.commit(nativeDraft + " ") {
|
||||
try? TextInserter.insert(nativeDraft + " ")
|
||||
}
|
||||
}
|
||||
showError(
|
||||
chunk.isFinalSession
|
||||
? "Финальная диктовка не распознана"
|
||||
: "Фрагмент диктовки не распознан",
|
||||
error.localizedDescription
|
||||
)
|
||||
showError("Фрагмент диктовки не распознан", error.localizedDescription)
|
||||
}
|
||||
processingChunk = false
|
||||
if pendingChunks.isEmpty {
|
||||
@@ -287,18 +208,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
stopRequested = false
|
||||
lastMode = nil
|
||||
stitchNextChunk = false
|
||||
nativeSession = false
|
||||
nativeDraft = ""
|
||||
setStatus("готово")
|
||||
}
|
||||
|
||||
@objc private func startLocal() { Task { await toggle(.local) } }
|
||||
@objc private func startCloud() { Task { await toggle(.cloud) } }
|
||||
@objc private func openSettings() { SettingsWindowController.shared.show() }
|
||||
@objc private func openInputSources() {
|
||||
let url = URL(string: "x-apple.systempreferences:com.apple.Keyboard-Settings.extension")!
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
@objc private func checkLocalEngine() {
|
||||
do {
|
||||
try LocalWhisper.validate(settings: settings)
|
||||
@@ -307,10 +222,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
showError("Локальный движок не готов", error.localizedDescription)
|
||||
}
|
||||
}
|
||||
@objc private func quit() {
|
||||
composition.cancel()
|
||||
NSApp.terminate(nil)
|
||||
}
|
||||
@objc private func quit() { NSApp.terminate(nil) }
|
||||
|
||||
@objc private func handleWake() {
|
||||
do {
|
||||
|
||||
Reference in New Issue
Block a user