chore: archive mac mini automation baseline
This commit is contained in:
+135
@@ -0,0 +1,135 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@StateObject private var streamManager: AudioStreamManager
|
||||
@StateObject private var captureManager = AudioCaptureManager()
|
||||
|
||||
@State private var isPressing = false
|
||||
|
||||
init() {
|
||||
let manager = AudioStreamManager(endpoint: URL(string: "ws://192.168.68.126:8642/stream")!)
|
||||
_streamManager = StateObject(wrappedValue: manager)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 14) {
|
||||
Text("Hermes")
|
||||
.font(.headline)
|
||||
|
||||
Text(statusText)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(statusColor)
|
||||
.lineLimit(2)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(minHeight: 28)
|
||||
|
||||
Circle()
|
||||
.fill(buttonFill)
|
||||
.overlay {
|
||||
Image(systemName: isPressing ? "waveform" : "mic.fill")
|
||||
.font(.system(size: 36, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
.overlay {
|
||||
Circle()
|
||||
.stroke(.white.opacity(isPressing ? 0.9 : 0.25), lineWidth: 3)
|
||||
}
|
||||
.frame(width: 112, height: 112)
|
||||
.scaleEffect(isPressing ? 0.94 : 1.0)
|
||||
.animation(.spring(response: 0.2, dampingFraction: 0.75), value: isPressing)
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 0)
|
||||
.onChanged { _ in
|
||||
guard !isPressing else { return }
|
||||
isPressing = true
|
||||
startPTT()
|
||||
}
|
||||
.onEnded { _ in
|
||||
isPressing = false
|
||||
stopPTT()
|
||||
}
|
||||
)
|
||||
|
||||
Text(isPressing ? "Release to send" : "Hold to talk")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding()
|
||||
.onAppear {
|
||||
streamManager.connect()
|
||||
captureManager.onPCMChunk = { [streamManager] chunk in
|
||||
streamManager.sendPCMChunk(chunk)
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
captureManager.stopRecording()
|
||||
streamManager.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private var statusText: String {
|
||||
switch captureManager.state {
|
||||
case .recording:
|
||||
return "Listening"
|
||||
case .starting:
|
||||
return "Starting mic"
|
||||
case .failed(let message):
|
||||
return message
|
||||
case .idle:
|
||||
break
|
||||
}
|
||||
|
||||
switch streamManager.playbackState {
|
||||
case .playing:
|
||||
return "Playing response"
|
||||
case .failed(let message):
|
||||
return message
|
||||
case .idle:
|
||||
break
|
||||
}
|
||||
|
||||
switch streamManager.connectionState {
|
||||
case .connected:
|
||||
return "Ready"
|
||||
case .connecting:
|
||||
return "Connecting"
|
||||
case .disconnected:
|
||||
return "Disconnected"
|
||||
case .failed(let message):
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
private var statusColor: Color {
|
||||
if case .failed = captureManager.state { return .red }
|
||||
if case .failed = streamManager.connectionState { return .red }
|
||||
if case .failed = streamManager.playbackState { return .red }
|
||||
if captureManager.state == .recording { return .green }
|
||||
if streamManager.playbackState == .playing { return .blue }
|
||||
return .secondary
|
||||
}
|
||||
|
||||
private var buttonFill: Color {
|
||||
if captureManager.state == .recording { return .red }
|
||||
if streamManager.playbackState == .playing { return .blue }
|
||||
return .accentColor
|
||||
}
|
||||
|
||||
private func startPTT() {
|
||||
streamManager.connect()
|
||||
streamManager.sendControlEvent("start")
|
||||
|
||||
Task {
|
||||
await captureManager.startRecording()
|
||||
}
|
||||
}
|
||||
|
||||
private func stopPTT() {
|
||||
captureManager.stopRecording()
|
||||
streamManager.sendControlEvent("stop")
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContentView()
|
||||
}
|
||||
Reference in New Issue
Block a user