fix(voicewake): snap overlay to top-right
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import AppKit
|
||||
import OSLog
|
||||
import QuartzCore
|
||||
import SwiftUI
|
||||
|
||||
@@ -7,6 +8,8 @@ import SwiftUI
|
||||
final class VoiceWakeOverlayController: ObservableObject {
|
||||
static let shared = VoiceWakeOverlayController()
|
||||
|
||||
private let logger = Logger(subsystem: "com.steipete.clawdis", category: "voicewake.overlay")
|
||||
|
||||
@Published private(set) var model = Model()
|
||||
|
||||
struct Model {
|
||||
@@ -33,9 +36,10 @@ final class VoiceWakeOverlayController: ObservableObject {
|
||||
private let verticalPadding: CGFloat = 8
|
||||
private let maxHeight: CGFloat = 400
|
||||
private let minHeight: CGFloat = 48
|
||||
let closeOverflow: CGFloat = 18
|
||||
let closeOverflow: CGFloat = 10
|
||||
|
||||
func showPartial(transcript: String, attributed: NSAttributedString? = nil) {
|
||||
self.logger.debug("overlay showPartial len=\(transcript.count, privacy: .public) visible=\(self.model.isVisible, privacy: .public) isFinal=false")
|
||||
self.autoSendTask?.cancel()
|
||||
self.forwardConfig = nil
|
||||
self.model.text = transcript
|
||||
@@ -56,6 +60,7 @@ final class VoiceWakeOverlayController: ObservableObject {
|
||||
sendChime: VoiceWakeChime = .none,
|
||||
attributed: NSAttributedString? = nil)
|
||||
{
|
||||
self.logger.debug("overlay presentFinal len=\(transcript.count, privacy: .public) autoSendAfter=\(delay ?? -1, privacy: .public) forwardEnabled=\(forwardConfig.enabled, privacy: .public)")
|
||||
self.autoSendTask?.cancel()
|
||||
self.forwardConfig = forwardConfig
|
||||
self.model.text = transcript
|
||||
@@ -96,26 +101,31 @@ final class VoiceWakeOverlayController: ObservableObject {
|
||||
}
|
||||
|
||||
func sendNow(sendChime: VoiceWakeChime = .none) {
|
||||
self.logger.debug("overlay sendNow called isSending=\(self.model.isSending, privacy: .public) forwardEnabled=\(self.model.forwardEnabled, privacy: .public) textLen=\(self.model.text.count, privacy: .public)")
|
||||
self.autoSendTask?.cancel()
|
||||
self.autoSendTask = nil
|
||||
if self.model.isSending { return }
|
||||
self.model.isEditing = false
|
||||
guard let forwardConfig, forwardConfig.enabled else {
|
||||
self.logger.debug("overlay sendNow disabled -> dismiss")
|
||||
self.dismiss(reason: .explicit)
|
||||
return
|
||||
}
|
||||
let text = self.model.text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !text.isEmpty else {
|
||||
self.logger.debug("overlay sendNow empty -> dismiss")
|
||||
self.dismiss(reason: .empty)
|
||||
return
|
||||
}
|
||||
|
||||
if sendChime != .none {
|
||||
self.logger.debug("overlay sendNow playing sendChime=\(String(describing: sendChime), privacy: .public)")
|
||||
VoiceWakeChimePlayer.play(sendChime)
|
||||
}
|
||||
|
||||
self.model.isSending = true
|
||||
let payload = VoiceWakeForwarder.prefixedTranscript(text)
|
||||
self.logger.debug("overlay sendNow forwarding len=\(payload.count, privacy: .public) target=\(forwardConfig.target, privacy: .public)")
|
||||
Task.detached {
|
||||
await VoiceWakeForwarder.forward(transcript: payload, config: forwardConfig)
|
||||
}
|
||||
@@ -125,6 +135,7 @@ final class VoiceWakeOverlayController: ObservableObject {
|
||||
}
|
||||
|
||||
func dismiss(reason: DismissReason = .explicit, outcome: SendOutcome = .empty) {
|
||||
self.logger.debug("overlay dismiss reason=\(String(describing: reason), privacy: .public) outcome=\(String(describing: outcome), privacy: .public) visible=\(self.model.isVisible, privacy: .public) sending=\(self.model.isSending, privacy: .public)")
|
||||
self.autoSendTask?.cancel()
|
||||
self.model.isSending = false
|
||||
self.model.isEditing = false
|
||||
@@ -169,6 +180,7 @@ final class VoiceWakeOverlayController: ObservableObject {
|
||||
guard let window else { return }
|
||||
if !self.model.isVisible {
|
||||
self.model.isVisible = true
|
||||
self.logger.debug("overlay present windowShown textLen=\(self.model.text.count, privacy: .public)")
|
||||
// Keep the status item in “listening” mode until we explicitly dismiss the overlay.
|
||||
AppStateStore.shared.triggerVoiceEars(ttl: nil)
|
||||
let start = target.offsetBy(dx: 0, dy: -6)
|
||||
@@ -220,8 +232,8 @@ final class VoiceWakeOverlayController: ObservableObject {
|
||||
let size = NSSize(width: self.width + self.closeOverflow * 2, height: height + self.closeOverflow * 2)
|
||||
let visible = screen.visibleFrame
|
||||
let origin = CGPoint(
|
||||
x: visible.maxX - size.width - self.padding,
|
||||
y: visible.maxY - size.height - self.padding)
|
||||
x: visible.maxX - size.width,
|
||||
y: visible.maxY - size.height)
|
||||
return NSRect(origin: origin, size: size)
|
||||
}
|
||||
|
||||
@@ -281,6 +293,7 @@ final class VoiceWakeOverlayController: ObservableObject {
|
||||
}
|
||||
|
||||
private func scheduleAutoSend(after delay: TimeInterval, sendChime: VoiceWakeChime) {
|
||||
self.logger.debug("overlay scheduleAutoSend after=\(delay, privacy: .public) sendChime=\(String(describing: sendChime), privacy: .public)")
|
||||
self.autoSendTask?.cancel()
|
||||
self.autoSendTask = Task<Void, Never> { [weak self, sendChime] in
|
||||
let nanos = UInt64(max(0, delay) * 1_000_000_000)
|
||||
@@ -288,6 +301,7 @@ final class VoiceWakeOverlayController: ObservableObject {
|
||||
guard !Task.isCancelled else { return }
|
||||
await MainActor.run {
|
||||
guard let self else { return }
|
||||
self.logger.debug("overlay autoSend firing")
|
||||
self.sendNow(sendChime: sendChime)
|
||||
self.autoSendTask = nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user