feat: add config editor for clawdis model and session store

This commit is contained in:
Peter Steinberger
2025-12-06 04:27:50 +01:00
parent 3c13a265bc
commit 15cdeeddaf

View File

@@ -1032,10 +1032,16 @@ struct SessionsSettings: View {
@State private var errorMessage: String?
@State private var loading = false
@State private var hasLoaded = false
@State private var configModel: String = ""
@State private var configStorePath: String = SessionLoader.defaultStorePath
@State private var configContextTokens: String = ""
@State private var configStatus: String?
@State private var configSaving = false
var body: some View {
VStack(alignment: .leading, spacing: 14) {
self.header
self.configEditor
self.storeMetadata
Divider().padding(.vertical, 4)
self.content
@@ -1046,6 +1052,7 @@ struct SessionsSettings: View {
.task {
guard !self.hasLoaded else { return }
self.hasLoaded = true
self.loadConfig()
await self.refresh()
}
}
@@ -1061,6 +1068,57 @@ struct SessionsSettings: View {
}
}
private var configEditor: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Clawdis CLI config")
.font(.callout.weight(.semibold))
Text("Writes to ~/.clawdis/clawdis.json (inbound.reply.agent/session).")
.font(.footnote)
.foregroundStyle(.secondary)
LabeledContent("Model") {
TextField("e.g. claude-3.5-sonnet", text: self.$configModel)
.textFieldStyle(.roundedBorder)
.frame(width: 260)
}
LabeledContent("Session store") {
TextField("Path", text: self.$configStorePath)
.textFieldStyle(.roundedBorder)
.frame(width: 320)
}
LabeledContent("Context tokens") {
TextField("Optional", text: self.$configContextTokens)
.textFieldStyle(.roundedBorder)
.frame(width: 160)
}
HStack(spacing: 12) {
Button {
Task { await self.saveConfig() }
} label: {
Label(self.configSaving ? "Saving…" : "Save config", systemImage: "square.and.arrow.down")
.labelStyle(.titleAndIcon)
}
.disabled(self.configSaving)
Button {
self.loadConfig()
} label: {
Label("Revert", systemImage: "arrow.uturn.backward")
}
.disabled(self.configSaving)
if let configStatus {
Text(configStatus)
.font(.footnote)
.foregroundStyle(.secondary)
}
}
}
}
private var storeMetadata: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(alignment: .top, spacing: 10) {
@@ -1199,6 +1257,80 @@ struct SessionsSettings: View {
NSWorkspace.shared.open(url.deletingLastPathComponent())
}
}
private func configURL() -> URL {
FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".clawdis")
.appendingPathComponent("clawdis.json")
}
private func loadConfig() {
let url = self.configURL()
guard let data = try? Data(contentsOf: url) else {
self.configModel = ""
self.configStorePath = SessionLoader.defaultStorePath
self.configContextTokens = ""
self.configStatus = "Using defaults (no config file yet)"
return
}
guard
let parsed = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let inbound = parsed["inbound"] as? [String: Any],
let reply = inbound["reply"] as? [String: Any]
else {
self.configStatus = "Invalid config file; using defaults"
return
}
let session = reply["session"] as? [String: Any]
let agent = reply["agent"] as? [String: Any]
self.configStorePath = (session?["store"] as? String) ?? SessionLoader.defaultStorePath
self.configModel = (agent?["model"] as? String) ?? ""
if let ctx = (agent?["contextTokens"] as? NSNumber)?.intValue {
self.configContextTokens = "\(ctx)"
} else {
self.configContextTokens = ""
}
self.configStatus = "Loaded from config"
}
private func saveConfig() async {
guard !self.configSaving else { return }
self.configSaving = true
defer { self.configSaving = false }
let ctxTokens: Int? = Int(self.configContextTokens.trimmingCharacters(in: .whitespacesAndNewlines))
var session: [String: Any] = [:]
var agent: [String: Any] = [:]
let trimmedStore = self.configStorePath.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedStore.isEmpty { session["store"] = trimmedStore }
let trimmedModel = self.configModel.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedModel.isEmpty { agent["model"] = trimmedModel }
if let ctxTokens { agent["contextTokens"] = ctxTokens }
let reply: [String: Any] = [
"session": session,
"agent": agent,
]
let inbound: [String: Any] = ["reply": reply]
let root: [String: Any] = ["inbound": inbound]
do {
let data = try JSONSerialization.data(withJSONObject: root, options: [.prettyPrinted, .sortedKeys])
let url = self.configURL()
try FileManager.default.createDirectory(
at: url.deletingLastPathComponent(),
withIntermediateDirectories: true)
try data.write(to: url, options: [.atomic])
self.configStatus = "Saved to \(url.path)"
// refresh session view with new defaults
await self.refresh()
} catch {
self.configStatus = "Save failed: \(error.localizedDescription)"
}
}
}
private struct SessionRowView: View {