Files
clawdbot/apps/macos/Sources/Clawdbot/VoiceWakeOverlay.swift
2026-01-04 14:38:51 +00:00

61 lines
1.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
let logger = Logger(subsystem: "com.clawdbot", category: "voicewake.overlay")
let enableUI: Bool
/// Keep the voice wake overlay above any other Clawdbot windows, but below the systems pop-up menus.
/// (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 }
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
}
}