Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion clients/macos/vellum-assistant/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1360,7 +1360,8 @@ public final class AppDelegate: NSObject, NSApplicationDelegate {
}

let sensitivity = UserDefaults.standard.float(forKey: "wakeWordSensitivity")
let engine = PorcupineWakeWordEngine(sensitivity: sensitivity > 0 ? sensitivity : 0.5)
let keyword = UserDefaults.standard.string(forKey: "wakeWordKeyword") ?? "computer"
let engine = PorcupineWakeWordEngine(sensitivity: sensitivity > 0 ? sensitivity : 0.5, keyword: keyword)
Comment on lines +1363 to +1364
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rebuild wake-word engine when keyword changes

setupWakeWordCoordinator reads wakeWordKeyword once and bakes it into a single PorcupineWakeWordEngine instance, so changing the picker in settings does not update the active detector during the current app session. The UI now immediately shows the new keyword, but the engine continues listening for the old one because AlwaysOnAudioMonitor reuses the same engine object for start/stop cycles; users who switch keywords while running will get inconsistent behavior until a full app restart.

Useful? React with 👍 / 👎.

let audioMonitor = AlwaysOnAudioMonitor(engine: engine)

let coordinator = WakeWordCoordinator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ struct WakeWordSettingsView: View {
@AppStorage("wakeWordEnabled") private var wakeWordEnabled: Bool = false
@AppStorage("wakeWordSensitivity") private var wakeWordSensitivity: Double = 0.5
@AppStorage("wakeWordTimeoutSeconds") private var wakeWordTimeoutSeconds: Int = 30
@AppStorage("wakeWordKeyword") private var wakeWordKeyword: String = "computer"

@State private var picovoiceKeyText: String = ""

var body: some View {
VStack(alignment: .leading, spacing: VSpacing.xl) {
statusSection
enableSection
keywordSection
accessKeySection
sensitivitySection
timeoutSection
Expand All @@ -31,7 +33,7 @@ struct WakeWordSettingsView: View {
.font(.system(size: 14))
.foregroundColor(wakeWordEnabled ? VColor.success : VColor.textMuted)

Text(wakeWordEnabled ? "Listening for \"hey vellum\"" : "Wake word disabled")
Text(wakeWordEnabled ? "Listening for \"\(wakeWordKeyword)\"" : "Wake word disabled")
.font(VFont.body)
.foregroundColor(wakeWordEnabled ? VColor.textPrimary : VColor.textSecondary)

Expand All @@ -54,7 +56,7 @@ struct WakeWordSettingsView: View {
Text("Enable wake word listening")
.font(VFont.body)
.foregroundColor(VColor.textSecondary)
Text("Activate the assistant by saying \"hey vellum\" instead of using a keyboard shortcut.")
Text("Activate the assistant by saying the wake word instead of using a keyboard shortcut.")
.font(VFont.caption)
.foregroundColor(VColor.textMuted)
}
Expand All @@ -69,6 +71,45 @@ struct WakeWordSettingsView: View {
.vCard(background: VColor.surfaceSubtle)
}

// MARK: - Keyword

private var keywordSection: some View {
VStack(alignment: .leading, spacing: VSpacing.md) {
Text("Keyword")
.font(VFont.sectionTitle)
.foregroundColor(VColor.textPrimary)

HStack {
Text("Keyword")
.font(VFont.body)
.foregroundColor(VColor.textSecondary)
Spacer()
Picker("", selection: $wakeWordKeyword) {
Text("Computer").tag("computer")
Text("Jarvis").tag("jarvis")
Text("Alexa").tag("alexa")
Text("Hey Siri").tag("hey siri")
Text("Picovoice").tag("picovoice")
Text("Porcupine").tag("porcupine")
Text("Terminator").tag("terminator")
Text("Bumblebee").tag("bumblebee")
Text("Blueberry").tag("blueberry")
Text("Grapefruit").tag("grapefruit")
Text("Grasshopper").tag("grasshopper")
}
.pickerStyle(.menu)
.frame(width: 160)
.accessibilityLabel("Wake word keyword")
}

Text("The keyword that triggers voice activation. Requires restart of wake word listening to take effect.")
.font(VFont.caption)
.foregroundColor(VColor.textMuted)
}
.padding(VSpacing.lg)
.vCard(background: VColor.surfaceSubtle)
}

// MARK: - Access Key

private var accessKeySection: some View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ final class PorcupineWakeWordEngine: WakeWordEngine {
guard !isRunning else { return }

// 1. Access key
guard let accessKey = APIKeyManager.shared.getAPIKey(provider: "picovoice") else {
guard let accessKey = APIKeyManager.getKey(for: "picovoice") else {
log.warning("Picovoice access key not found in keychain — wake word detection disabled")
return
}
Expand Down