New chats can default to autoship via a Settings toggle in New chat defaults. Enabling it implies Auto mode, mirroring the per-session control. Persisted under nucleic.defaultAutoShip and loaded into the store on launch. Co-Authored-By: Claude Opus 4.8 <[email protected]>
117 lines
6.1 KiB
Swift
117 lines
6.1 KiB
Swift
import SwiftUI
|
|
import NucleicCore
|
|
|
|
/// App settings: defaults applied to new chats, plus the Apple Intelligence model
|
|
/// used for summaries and auto-generated names.
|
|
struct SettingsView: View {
|
|
@Environment(AppStore.self) private var store
|
|
|
|
@AppStorage(IntelligenceChoice.storageKey) private var choiceRaw = IntelligenceChoice.onDevice.rawValue
|
|
@AppStorage(ModelCatalog.defaultModelKey) private var defaultModel = ModelCatalog.fallbackModel
|
|
@AppStorage(ModelCatalog.defaultEffortKey) private var defaultEffort = ModelCatalog.fallbackEffort
|
|
@AppStorage(ModelCatalog.defaultAutoKey) private var defaultAuto = false
|
|
@AppStorage(ModelCatalog.defaultAutoShipKey) private var defaultAutoShip = false
|
|
@AppStorage(AppTextSize.storageKey) private var textSizeRaw = AppTextSize.medium.rawValue
|
|
@AppStorage(ColorVisionMode.storageKey) private var colorVisionRaw = ColorVisionMode.standard.rawValue
|
|
@AppStorage(AppAppearance.storageKey) private var appearanceRaw = AppAppearance.dark.rawValue
|
|
@AppStorage(TranscriptDisplay.showDebugKey) private var showDebugLines = false
|
|
@AppStorage(StreakBadge.showKey) private var showStreak = true
|
|
@AppStorage(TriageLabelStyle.encouragingKey) private var encouragingTriageLabels = true
|
|
|
|
private var choice: IntelligenceChoice { IntelligenceChoice(rawValue: choiceRaw) ?? .onDevice }
|
|
private var textSize: AppTextSize { AppTextSize(rawValue: textSizeRaw) ?? .medium }
|
|
private var appearance: AppAppearance { AppAppearance(rawValue: appearanceRaw) ?? .dark }
|
|
|
|
var body: some View {
|
|
Form {
|
|
Section("Appearance") {
|
|
Picker("Theme", selection: $appearanceRaw) {
|
|
ForEach(AppAppearance.allCases) { Text($0.label).tag($0.rawValue) }
|
|
}
|
|
.pickerStyle(.segmented)
|
|
Picker("Text size", selection: $textSizeRaw) {
|
|
ForEach(AppTextSize.allCases) { Text($0.label).tag($0.rawValue) }
|
|
}
|
|
Picker("Color vision", selection: $colorVisionRaw) {
|
|
ForEach(ColorVisionMode.allCases) { Text($0.label).tag($0.rawValue) }
|
|
}
|
|
Text("Color vision remaps status dots, the activity grid, and accents to stay distinguishable.")
|
|
.font(.caption).foregroundStyle(.secondary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
Toggle("Show streak counter", isOn: $showStreak)
|
|
Text("Shows a day-streak badge next to the home greeting, counting consecutive days with activity.")
|
|
.font(.caption).foregroundStyle(.secondary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
Toggle("Encouraging to-do labels", isOn: $encouragingTriageLabels)
|
|
Text(encouragingTriageLabels
|
|
? "Triage badges read as opportunities: Max Impact, High Impact, Worthwhile, Someday."
|
|
: "Triage badges use the classic severity words: Critical, High, Medium, Low.")
|
|
.font(.caption).foregroundStyle(.secondary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
|
|
Section("Transcript") {
|
|
Toggle("Show advanced detail", isOn: $showDebugLines)
|
|
Text("Adds debug-level lines like rate limits, token usage, and turn markers to the chat. Thinking and tool calls always show.")
|
|
.font(.caption).foregroundStyle(.secondary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
|
|
Section("New chat defaults") {
|
|
Picker("Default model", selection: $defaultModel) {
|
|
ForEach(ModelCatalog.models, id: \.self) { Text(ModelCatalog.displayName($0)).tag($0) }
|
|
}
|
|
Picker("Default effort", selection: $defaultEffort) {
|
|
ForEach(ModelCatalog.efforts, id: \.self) { Text($0).tag($0) }
|
|
}
|
|
Toggle("Start new chats in Auto mode", isOn: $defaultAuto)
|
|
Text("Auto mode lets Claude auto-approve safe actions; destructive ones still ask.")
|
|
.font(.caption).foregroundStyle(.secondary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
Toggle("Autoship new chats", isOn: $defaultAutoShip)
|
|
Text("Autoship merges a finished chat's branch back into the project automatically. It implies Auto mode.")
|
|
.font(.caption).foregroundStyle(.secondary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
|
|
Section("Apple Intelligence") {
|
|
Picker("Model", selection: $choiceRaw) {
|
|
ForEach(IntelligenceChoice.allCases) { Text($0.label).tag($0.rawValue) }
|
|
}
|
|
.pickerStyle(.radioGroup)
|
|
Text(choice.detail)
|
|
.font(.caption).foregroundStyle(.secondary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
|
|
Section("Status") {
|
|
LabeledContent("On-device model", value: IntelligenceAvailability.status(for: .onDevice))
|
|
LabeledContent("Private Cloud Compute", value: IntelligenceAvailability.status(for: .privateCloudCompute))
|
|
}
|
|
.font(.callout)
|
|
|
|
RemoteAccessSection()
|
|
}
|
|
.formStyle(.grouped)
|
|
.frame(width: 480)
|
|
.padding()
|
|
.preferredColorScheme(appearance.colorScheme)
|
|
.dynamicTypeSize(textSize.dynamicTypeSize)
|
|
.onAppear { pushDefaults() }
|
|
.onChange(of: defaultModel) { _, _ in pushDefaults() }
|
|
.onChange(of: defaultEffort) { _, _ in pushDefaults() }
|
|
.onChange(of: defaultAuto) { _, _ in pushDefaults() }
|
|
.onChange(of: defaultAutoShip) { _, _ in
|
|
if defaultAutoShip { defaultAuto = true } // shipping implies autonomous
|
|
pushDefaults()
|
|
}
|
|
}
|
|
|
|
private func pushDefaults() {
|
|
store.defaultModel = defaultModel
|
|
store.defaultEffort = defaultEffort
|
|
store.defaultAuto = defaultAuto
|
|
store.defaultAutoShip = defaultAutoShip
|
|
}
|
|
}
|