VoiceWake: centralize send chime and guard play
This commit is contained in:
@@ -349,19 +349,23 @@ enum CommandResolver {
|
||||
|
||||
// Run the real clawdis CLI on the remote host; do not fall back to clawdis-mac.
|
||||
let exportedPath = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/steipete/Library/pnpm:$PATH"
|
||||
let cdPrefix = settings.projectRoot.isEmpty ? "" : "cd \(self.shellQuote(settings.projectRoot)) && "
|
||||
let prjVar = settings.projectRoot.isEmpty ? "" : "PRJ=\(self.shellQuote(settings.projectRoot)); "
|
||||
let quotedArgs = ([subcommand] + extraArgs).map(self.shellQuote).joined(separator: " ")
|
||||
let userPRJ = settings.projectRoot
|
||||
let prjInit = userPRJ.isEmpty ? "" : "PRJ=\(self.shellQuote(userPRJ));"
|
||||
let scriptBody = """
|
||||
PATH=\(exportedPath);
|
||||
CLI="";
|
||||
\(prjVar)
|
||||
\(prjInit)
|
||||
DEFAULT_PRJ="$HOME/Projects/clawdis"
|
||||
if [ -z "${PRJ:-}" ] && [ -d "$DEFAULT_PRJ" ]; then PRJ="$DEFAULT_PRJ"; fi
|
||||
if [ -n "${PRJ:-}" ]; then
|
||||
cd "$PRJ" || { echo "Project root not found: $PRJ"; exit 127; }
|
||||
fi
|
||||
if command -v clawdis >/dev/null 2>&1; then
|
||||
\(cdPrefix)clawdis \(quotedArgs);
|
||||
clawdis \(quotedArgs);
|
||||
elif [ -n "${PRJ:-}" ] && [ -f "$PRJ/bin/clawdis.js" ] && command -v node >/dev/null 2>&1; then
|
||||
\(cdPrefix)node "$PRJ/bin/clawdis.js" \(quotedArgs);
|
||||
node "$PRJ/bin/clawdis.js" \(quotedArgs);
|
||||
elif command -v pnpm >/dev/null 2>&1; then
|
||||
\(cdPrefix)pnpm --silent clawdis \(quotedArgs);
|
||||
pnpm --silent clawdis \(quotedArgs);
|
||||
else
|
||||
echo "clawdis CLI missing on remote host"; exit 127;
|
||||
fi
|
||||
@@ -383,12 +387,16 @@ enum CommandResolver {
|
||||
args.append(userHost)
|
||||
|
||||
let exportedPath = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
|
||||
let cdPrefix = settings.projectRoot.isEmpty ? "" : "cd \(self.shellQuote(settings.projectRoot)) && "
|
||||
let userPRJ = settings.projectRoot
|
||||
let quotedArgs = ([subcommand] + extraArgs).map(self.shellQuote).joined(separator: " ")
|
||||
let scriptBody = """
|
||||
PATH=\(exportedPath);
|
||||
PRJ=\(userPRJ.isEmpty ? "" : self.shellQuote(userPRJ))
|
||||
DEFAULT_PRJ="$HOME/Projects/clawdis"
|
||||
if [ -z "${PRJ:-}" ] && [ -d "$DEFAULT_PRJ" ]; then PRJ="$DEFAULT_PRJ"; fi
|
||||
if [ -n "${PRJ:-}" ]; then cd "$PRJ" || { echo "Project root not found: $PRJ"; exit 127; }; fi
|
||||
if ! command -v clawdis-mac >/dev/null 2>&1; then echo "clawdis-mac missing on remote host"; exit 127; fi;
|
||||
\(cdPrefix)clawdis-mac \(quotedArgs)
|
||||
clawdis-mac \(quotedArgs)
|
||||
"""
|
||||
args.append(contentsOf: ["/bin/sh", "-c", scriptBody])
|
||||
return ["/usr/bin/ssh"] + args
|
||||
|
||||
@@ -79,6 +79,7 @@ actor VoicePushToTalk {
|
||||
private var volatile: String = ""
|
||||
private var activeConfig: Config?
|
||||
private var isCapturing = false
|
||||
private var triggerChimePlayed = false
|
||||
|
||||
private struct Config {
|
||||
let micID: String?
|
||||
@@ -99,7 +100,9 @@ actor VoicePushToTalk {
|
||||
let config = await MainActor.run { self.makeConfig() }
|
||||
self.activeConfig = config
|
||||
self.isCapturing = true
|
||||
self.triggerChimePlayed = false
|
||||
if config.triggerChime != .none {
|
||||
self.triggerChimePlayed = true
|
||||
await MainActor.run { VoiceWakeChimePlayer.play(config.triggerChime) }
|
||||
}
|
||||
await VoiceWakeRuntime.shared.pauseForPushToTalk()
|
||||
@@ -137,21 +140,21 @@ actor VoicePushToTalk {
|
||||
forward = await MainActor.run { AppStateStore.shared.voiceWakeForwardConfig }
|
||||
}
|
||||
|
||||
if !finalText.isEmpty, let chime = self.activeConfig?.sendChime, chime != .none {
|
||||
await MainActor.run { VoiceWakeChimePlayer.play(chime) }
|
||||
}
|
||||
let chime = finalText.isEmpty ? .none : (self.activeConfig?.sendChime ?? .none)
|
||||
|
||||
await MainActor.run {
|
||||
VoiceWakeOverlayController.shared.presentFinal(
|
||||
transcript: finalText,
|
||||
forwardConfig: forward,
|
||||
delay: finalText.isEmpty ? 0.0 : 0.8,
|
||||
sendChime: chime,
|
||||
attributed: attributed)
|
||||
}
|
||||
|
||||
self.committed = ""
|
||||
self.volatile = ""
|
||||
self.activeConfig = nil
|
||||
self.triggerChimePlayed = false
|
||||
|
||||
// Resume the wake-word runtime after push-to-talk finishes.
|
||||
_ = await MainActor.run {
|
||||
@@ -209,8 +212,8 @@ actor VoicePushToTalk {
|
||||
self.volatile = Self.delta(after: self.committed, current: transcript)
|
||||
}
|
||||
|
||||
let attributed = Self.makeAttributed(committed: self.committed, volatile: self.volatile, isFinal: isFinal)
|
||||
let snapshot = self.committed + self.volatile
|
||||
let attributed = Self.makeAttributed(committed: self.committed, volatile: self.volatile, isFinal: isFinal)
|
||||
await MainActor.run {
|
||||
VoiceWakeOverlayController.shared.showPartial(transcript: snapshot, attributed: attributed)
|
||||
}
|
||||
|
||||
@@ -42,10 +42,15 @@ struct VoiceWakeChimeCatalog {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
enum VoiceWakeChimePlayer {
|
||||
private static var lastSound: NSSound?
|
||||
|
||||
@MainActor
|
||||
static func play(_ chime: VoiceWakeChime) {
|
||||
guard let sound = self.sound(for: chime) else { return }
|
||||
self.lastSound = sound
|
||||
sound.stop()
|
||||
sound.play()
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ actor VoiceWakeRuntime {
|
||||
private var capturedTranscript: String = ""
|
||||
private var isCapturing: Bool = false
|
||||
private var heardBeyondTrigger: Bool = false
|
||||
private var triggerChimePlayed: Bool = false
|
||||
private var committedTranscript: String = ""
|
||||
private var volatileTranscript: String = ""
|
||||
private var cooldownUntil: Date?
|
||||
@@ -124,6 +125,7 @@ actor VoiceWakeRuntime {
|
||||
self.isCapturing = false
|
||||
self.capturedTranscript = ""
|
||||
self.captureStartedAt = nil
|
||||
self.triggerChimePlayed = false
|
||||
self.recognitionTask?.cancel()
|
||||
self.recognitionTask = nil
|
||||
self.recognitionRequest?.endAudio()
|
||||
@@ -203,9 +205,6 @@ actor VoiceWakeRuntime {
|
||||
|
||||
private func beginCapture(transcript: String, config: RuntimeConfig) async {
|
||||
self.isCapturing = true
|
||||
if config.triggerChime != .none {
|
||||
await MainActor.run { VoiceWakeChimePlayer.play(config.triggerChime) }
|
||||
}
|
||||
let trimmed = Self.trimmedAfterTrigger(transcript, triggers: config.triggers)
|
||||
self.capturedTranscript = trimmed
|
||||
self.committedTranscript = ""
|
||||
@@ -213,6 +212,12 @@ actor VoiceWakeRuntime {
|
||||
self.captureStartedAt = Date()
|
||||
self.cooldownUntil = nil
|
||||
self.heardBeyondTrigger = !trimmed.isEmpty
|
||||
self.triggerChimePlayed = false
|
||||
|
||||
if config.triggerChime != .none {
|
||||
self.triggerChimePlayed = true
|
||||
await MainActor.run { VoiceWakeChimePlayer.play(config.triggerChime) }
|
||||
}
|
||||
|
||||
let snapshot = self.committedTranscript + self.volatileTranscript
|
||||
let attributed = Self.makeAttributed(
|
||||
@@ -264,6 +269,7 @@ actor VoiceWakeRuntime {
|
||||
self.captureStartedAt = nil
|
||||
self.lastHeard = nil
|
||||
self.heardBeyondTrigger = false
|
||||
self.triggerChimePlayed = false
|
||||
|
||||
await MainActor.run { AppStateStore.shared.stopVoiceEars() }
|
||||
|
||||
@@ -275,14 +281,13 @@ actor VoiceWakeRuntime {
|
||||
committed: finalTranscript,
|
||||
volatile: "",
|
||||
isFinal: true)
|
||||
if !finalTranscript.isEmpty, config.sendChime != .none {
|
||||
await MainActor.run { VoiceWakeChimePlayer.play(config.sendChime) }
|
||||
}
|
||||
let sendChime = finalTranscript.isEmpty ? .none : config.sendChime
|
||||
await MainActor.run {
|
||||
VoiceWakeOverlayController.shared.presentFinal(
|
||||
transcript: finalTranscript,
|
||||
forwardConfig: forwardConfig,
|
||||
delay: delay,
|
||||
sendChime: sendChime,
|
||||
attributed: finalAttributed)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user