Files
openclaw/apps/macos/Sources/OpenClaw/VoiceWakeOverlay.swift

63 lines
1.9 KiB
Swift
Raw Normal View History

2026-01-27 14:12:17 -06:00
import AppKit
import Observation
import SwiftUI
/// Lightweight, borderless panel that shows the current voice wake transcript near the menu bar.
@MainActor
@Observable
final class VoiceWakeOverlayController {
static let shared = VoiceWakeOverlayController()
2026-01-30 03:15:10 +01:00
let logger = Logger(subsystem: "ai.openclaw", category: "voicewake.overlay")
2026-01-27 14:12:17 -06:00
let enableUI: Bool
2026-01-30 03:15:10 +01:00
/// Keep the voice wake overlay above any other OpenClaw windows, but below the systems pop-up menus.
2026-01-27 14:12:17 -06:00
/// (Menu bar menus typically live at `.popUpMenu`.)
static let preferredWindowLevel = NSWindow.Level(rawValue: NSWindow.Level.popUpMenu.rawValue - 4)
enum Source: String { case wakeWord, pushToTalk }
var model = Model()
var isVisible: Bool {
self.model.isVisible
}
2026-01-27 14:12:17 -06:00
struct Model {
var text: String = ""
var isFinal: Bool = false
var isVisible: Bool = false
var forwardEnabled: Bool = false
var isSending: Bool = false
var attributed: NSAttributedString = .init(string: "")
var isOverflowing: Bool = false
var isEditing: Bool = false
var level: Double = 0 // normalized 0...1 speech level for UI
}
var window: NSPanel?
var hostingView: NSHostingView<VoiceWakeOverlayView>?
var autoSendTask: Task<Void, Never>?
var autoSendToken: UUID?
var activeToken: UUID?
var activeSource: Source?
var lastLevelUpdate: TimeInterval = 0
let width: CGFloat = 360
let padding: CGFloat = 10
let buttonWidth: CGFloat = 36
let spacing: CGFloat = 8
let verticalPadding: CGFloat = 8
let maxHeight: CGFloat = 400
let minHeight: CGFloat = 48
let closeOverflow: CGFloat = 10
let levelUpdateInterval: TimeInterval = 1.0 / 12.0
enum DismissReason { case explicit, empty }
enum SendOutcome { case sent, empty }
enum GuardOutcome { case accept, dropMismatch, dropNoActive }
init(enableUI: Bool = true) {
self.enableUI = enableUI
}
}