import SwiftUI struct SettingsView: View { @ObservedObject var settings: SettingsStore var session: VoiceSession? @State private var urlText: String = "" @State private var showVoicePicker = false @State private var showModelPicker = false init(settings: SettingsStore, session: VoiceSession? = nil) { self.settings = settings self.session = session } var body: some View { Form { Section("Server") { TextField("https://192.168.86.2:9178", text: $urlText) .keyboardType(.URL) .autocorrectionDisabled() .onAppear { urlText = settings.serverURL } .onChange(of: urlText) { _, newValue in settings.serverURL = newValue } } Section("Voice") { Button { showVoicePicker = true } label: { LabeledContent("Voice", value: Voices.display(for: settings.voiceID) ?? settings.voiceID) } } Section("Model") { if let session, !session.models.isEmpty { Button { showModelPicker = true } label: { LabeledContent("Model", value: session.selectedModel ?? settings.modelID ?? "—") } } else { Text(session != nil ? "Loading available models…" : "Start a conversation to load the model list.") .font(.footnote) .foregroundStyle(.secondary) } } Section("Interaction") { Toggle("Text mode", isOn: $settings.textMode) } Section { NavigationLink("Connection test") { ConnectionTestView(settings: settings) } } } .navigationTitle("Settings") .sheet(isPresented: $showVoicePicker) { VoicePickerView(settings: settings, session: session) } .sheet(isPresented: $showModelPicker) { ModelPickerView(settings: settings, session: session) } } } struct VoicePickerView: View { @ObservedObject var settings: SettingsStore var session: VoiceSession? @Environment(\.dismiss) private var dismiss var body: some View { NavigationStack { List { ForEach(Voices.all, id: \.id) { voice in Button { settings.voiceID = voice.id Task { await session?.selectVoice(voice.id) } dismiss() } label: { HStack { Text(voice.displayName) if settings.voiceID == voice.id { Spacer() Image(systemName: "checkmark") .foregroundStyle(.blue) } } } } } .navigationTitle("Voice") .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } } } } } struct ModelPickerView: View { @ObservedObject var settings: SettingsStore var session: VoiceSession? @Environment(\.dismiss) private var dismiss private var activeModelID: String? { guard let session else { return settings.modelID } if !session.models.isEmpty, let selected = session.selectedModel ?? settings.modelID, session.models.contains(where: { $0.id == selected }) { return selected } return session.models.first?.id } var body: some View { NavigationStack { List { ForEach(session?.models ?? [], id: \.id) { model in Button { settings.modelID = model.id Task { await session?.selectModel(model.id) } dismiss() } label: { HStack { Text(model.name ?? model.id) if activeModelID == model.id { Spacer() Image(systemName: "checkmark") .foregroundStyle(.blue) } } } } } .navigationTitle("Model") .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } } } } } #Preview { NavigationStack { SettingsView(settings: SettingsStore()) } }