feat(mac): restructure config settings grid
parent
9ad341d668
commit
b3b4013637
|
|
@ -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 gateway’s 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 won’t 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]
|
||||
|
|
|
|||
Loading…
Reference in New Issue