feat(mac): restructure config settings grid

main
Peter Steinberger 2025-12-13 15:55:31 +00:00
parent 9ad341d668
commit b3b4013637
1 changed files with 185 additions and 152 deletions

View File

@ -3,6 +3,7 @@ import SwiftUI
@MainActor
struct ConfigSettings: View {
private let isPreview = ProcessInfo.processInfo.isPreview
private let labelColumnWidth: CGFloat = 120
@State private var configModel: String = ""
@State private var customModel: String = ""
@State private var configSaving = false
@ -25,6 +26,7 @@ struct ConfigSettings: View {
@State private var browserAttachOnly: Bool = false
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 14) {
Text("Clawdis CLI config")
.font(.title3.weight(.semibold))
@ -32,12 +34,14 @@ struct ConfigSettings: View {
.font(.callout)
.foregroundStyle(.secondary)
LabeledContent("Model") {
GroupBox("Agent") {
Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 14, verticalSpacing: 10) {
GridRow {
self.gridLabel("Model")
VStack(alignment: .leading, spacing: 6) {
Picker("Model", selection: self.$configModel) {
ForEach(self.models) { choice in
Text(
"\(choice.name)\(choice.provider.uppercased())")
Text("\(choice.name)\(choice.provider.uppercased())")
.tag(choice.id)
}
Text("Manual entry…").tag("__custom__")
@ -52,7 +56,7 @@ struct ConfigSettings: View {
if self.configModel == "__custom__" {
TextField("Enter model ID", text: self.$customModel)
.textFieldStyle(.roundedBorder)
.frame(width: 320)
.frame(width: 360)
.onChange(of: self.customModel) { _, newValue in
self.configModel = newValue
self.autosaveConfig()
@ -72,8 +76,14 @@ struct ConfigSettings: View {
}
}
}
}
.frame(maxWidth: 520, alignment: .leading)
}
LabeledContent("Heartbeat") {
GroupBox("Heartbeat") {
Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 14, verticalSpacing: 10) {
GridRow {
self.gridLabel("Schedule")
VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 12) {
Stepper(
@ -83,38 +93,45 @@ struct ConfigSettings: View {
in: 0...720)
{
Text("Every \(self.heartbeatMinutes ?? 10) min")
.frame(width: 150, alignment: .leading)
}
.help("Set to 0 to disable automatic heartbeats")
TextField("HEARTBEAT", text: self.$heartbeatBody)
.textFieldStyle(.roundedBorder)
.frame(width: 240)
.frame(width: 200)
.onChange(of: self.heartbeatBody) { _, _ in
self.autosaveConfig()
}
.help("Message body sent on each heartbeat")
}
Text("Heartbeats keep Pi sessions warm; 0 minutes disables them.")
.font(.footnote)
.foregroundStyle(.secondary)
}
}
}
.frame(maxWidth: 520, alignment: .leading)
}
Divider().padding(.vertical, 4)
LabeledContent("Web chat") {
VStack(alignment: .leading, spacing: 6) {
Toggle("Enable embedded web chat (loopback only)", isOn: self.$webChatEnabled)
GroupBox("Web Chat") {
Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 14, verticalSpacing: 10) {
GridRow {
self.gridLabel("Enabled")
Toggle("", isOn: self.$webChatEnabled)
.labelsHidden()
.toggleStyle(.switch)
.frame(width: 320, alignment: .leading)
HStack(spacing: 8) {
Text("Port")
}
GridRow {
self.gridLabel("Port")
TextField("18788", value: self.$webChatPort, formatter: NumberFormatter())
.textFieldStyle(.roundedBorder)
.frame(width: 100)
.disabled(!self.webChatEnabled)
}
GridRow {
Color.clear
.frame(width: self.labelColumnWidth, height: 1)
Text(
"""
Mac app connects to the gateways loopback web chat on this port.
@ -122,30 +139,32 @@ struct ConfigSettings: View {
""")
.font(.footnote)
.foregroundStyle(.secondary)
.frame(maxWidth: 480, alignment: .leading)
.frame(maxWidth: 360, alignment: .leading)
}
}
.frame(maxWidth: 520, alignment: .leading)
}
Divider().padding(.vertical, 4)
LabeledContent("Browser (clawd)") {
VStack(alignment: .leading, spacing: 8) {
Toggle("Enable clawd browser control", isOn: self.$browserEnabled)
GroupBox("Browser (clawd)") {
Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 14, verticalSpacing: 10) {
GridRow {
self.gridLabel("Enabled")
Toggle("", isOn: self.$browserEnabled)
.labelsHidden()
.toggleStyle(.switch)
.frame(width: 360, alignment: .leading)
.onChange(of: self.browserEnabled) { _, _ in self.autosaveConfig() }
HStack(spacing: 8) {
Text("Control URL")
}
GridRow {
self.gridLabel("Control URL")
TextField("http://127.0.0.1:18791", text: self.$browserControlUrl)
.textFieldStyle(.roundedBorder)
.frame(width: 320)
.frame(width: 360)
.disabled(!self.browserEnabled)
.onChange(of: self.browserControlUrl) { _, _ in self.autosaveConfig() }
}
GridRow {
self.gridLabel("Accent")
HStack(spacing: 8) {
Text("Accent")
TextField("#FF4500", text: self.$browserColorHex)
.textFieldStyle(.roundedBorder)
.frame(width: 120)
@ -159,27 +178,35 @@ struct ConfigSettings: View {
.font(.footnote)
.foregroundStyle(.secondary)
}
Toggle("Attach only (never launch)", isOn: self.$browserAttachOnly)
}
GridRow {
self.gridLabel("Attach only")
Toggle("", isOn: self.$browserAttachOnly)
.labelsHidden()
.toggleStyle(.switch)
.frame(width: 360, alignment: .leading)
.disabled(!self.browserEnabled)
.onChange(of: self.browserAttachOnly) { _, _ in self.autosaveConfig() }
.help("When enabled, the browser server will only connect if the clawd browser is already running.")
}
GridRow {
Color.clear
.frame(width: self.labelColumnWidth, height: 1)
Text(
"Clawd uses a separate Chrome profile and ports (default 18791/18792) so it wont interfere with your daily browser."
)
.font(.footnote)
.foregroundStyle(.secondary)
.frame(maxWidth: 480, alignment: .leading)
.frame(maxWidth: 360, alignment: .leading)
}
}
.frame(maxWidth: 520, alignment: .leading)
}
Spacer()
Spacer(minLength: 0)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 12)
.padding(12)
}
.onChange(of: self.modelCatalogPath) { _, _ in
Task { await self.loadModels() }
}
@ -196,6 +223,12 @@ struct ConfigSettings: View {
}
}
private func gridLabel(_ text: String) -> some View {
Text(text)
.foregroundStyle(.secondary)
.frame(width: self.labelColumnWidth, alignment: .leading)
}
private func loadConfig() {
let parsed = self.loadConfigDict()
let inbound = parsed["inbound"] as? [String: Any]