mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
Add TTS toggle button to voice controls
Voice toolbar now shows three controls when voice is enabled: - Mic toggle (voice on/off) - TTS toggle (speaker icon, sends /voice tts) - Push to Talk (waveform, sends Ctrl+B) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,9 @@ final class ChatViewModel {
|
||||
var sessionPreviews: [String: String] = [:]
|
||||
var terminalView: LocalProcessTerminalView?
|
||||
var hasActiveProcess = false
|
||||
var voiceEnabled = false
|
||||
var ttsEnabled = false
|
||||
var isRecording = false
|
||||
private var coordinator: Coordinator?
|
||||
|
||||
var hermesBinaryExists: Bool {
|
||||
@@ -17,14 +20,23 @@ final class ChatViewModel {
|
||||
}
|
||||
|
||||
func startNewSession() {
|
||||
voiceEnabled = false
|
||||
ttsEnabled = false
|
||||
isRecording = false
|
||||
launchTerminal(arguments: ["chat"])
|
||||
}
|
||||
|
||||
func resumeSession(_ sessionId: String) {
|
||||
voiceEnabled = false
|
||||
ttsEnabled = false
|
||||
isRecording = false
|
||||
launchTerminal(arguments: ["chat", "--resume", sessionId])
|
||||
}
|
||||
|
||||
func continueLastSession() {
|
||||
voiceEnabled = false
|
||||
ttsEnabled = false
|
||||
isRecording = false
|
||||
launchTerminal(arguments: ["chat", "--continue"])
|
||||
}
|
||||
|
||||
@@ -42,6 +54,37 @@ final class ChatViewModel {
|
||||
return session.id
|
||||
}
|
||||
|
||||
func toggleVoice() {
|
||||
guard let tv = terminalView else { return }
|
||||
if voiceEnabled {
|
||||
sendToTerminal(tv, text: "/voice off\r")
|
||||
voiceEnabled = false
|
||||
isRecording = false
|
||||
} else {
|
||||
sendToTerminal(tv, text: "/voice on\r")
|
||||
voiceEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
func toggleTTS() {
|
||||
guard let tv = terminalView, voiceEnabled else { return }
|
||||
sendToTerminal(tv, text: "/voice tts\r")
|
||||
ttsEnabled.toggle()
|
||||
}
|
||||
|
||||
func pushToTalk() {
|
||||
guard let tv = terminalView, voiceEnabled else { return }
|
||||
// Ctrl+B = ASCII 0x02
|
||||
let ctrlB: [UInt8] = [0x02]
|
||||
tv.send(source: tv, data: ctrlB[0..<1])
|
||||
isRecording.toggle()
|
||||
}
|
||||
|
||||
private func sendToTerminal(_ tv: LocalProcessTerminalView, text: String) {
|
||||
let bytes = Array(text.utf8)
|
||||
tv.send(source: tv, data: bytes[0..<bytes.count])
|
||||
}
|
||||
|
||||
private func launchTerminal(arguments: [String]) {
|
||||
if let existing = terminalView {
|
||||
existing.terminate()
|
||||
@@ -55,6 +98,8 @@ final class ChatViewModel {
|
||||
|
||||
let coord = Coordinator(onTerminated: { [weak self] in
|
||||
self?.hasActiveProcess = false
|
||||
self?.voiceEnabled = false
|
||||
self?.isRecording = false
|
||||
})
|
||||
terminal.processDelegate = coord
|
||||
self.coordinator = coord
|
||||
|
||||
@@ -105,12 +105,26 @@ struct ChatView: View {
|
||||
.help("Toggle voice mode (/voice)")
|
||||
|
||||
if viewModel.voiceEnabled {
|
||||
Button {
|
||||
viewModel.toggleTTS()
|
||||
} label: {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: viewModel.ttsEnabled ? "speaker.wave.2.fill" : "speaker.slash")
|
||||
.foregroundStyle(viewModel.ttsEnabled ? .green : .secondary)
|
||||
Text(viewModel.ttsEnabled ? "TTS On" : "TTS Off")
|
||||
.font(.caption)
|
||||
.foregroundStyle(viewModel.ttsEnabled ? .primary : .secondary)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.help("Toggle text-to-speech (/voice tts)")
|
||||
|
||||
Button {
|
||||
viewModel.pushToTalk()
|
||||
} label: {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: viewModel.isRecording ? "waveform.circle.fill" : "waveform.circle")
|
||||
.foregroundStyle(viewModel.isRecording ? .red : .accentColor)
|
||||
.foregroundStyle(viewModel.isRecording ? .red : Color.accentColor)
|
||||
.symbolEffect(.pulse, isActive: viewModel.isRecording)
|
||||
Text(viewModel.isRecording ? "Recording..." : "Push to Talk")
|
||||
.font(.caption)
|
||||
|
||||
Reference in New Issue
Block a user