Skip to content

Commit 2c0be26

Browse files
committed
Fix AI settings label wrap
1 parent dcfe71a commit 2c0be26

3 files changed

Lines changed: 165 additions & 115 deletions

File tree

Planet/Settings/PlanetSettingsAIView.swift

Lines changed: 159 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import FoundationModels
1212
#endif
1313

1414
struct PlanetSettingsAIView: View {
15+
private enum Layout {
16+
static let secondaryRowLeadingInset: CGFloat = 8
17+
}
18+
1519
@State private var aiAPIBase: String = UserDefaults.standard.string(forKey: .settingsAIAPIBase) ?? ""
1620
@State private var aiAPIToken: String = ""
1721
@State private var isShowingToken: Bool = false
@@ -84,140 +88,183 @@ struct PlanetSettingsAIView: View {
8488
var body: some View {
8589
Form {
8690
Section {
87-
TextField("API Base URL", text: $aiAPIBase)
88-
.textFieldStyle(.roundedBorder)
89-
.onChange(of: aiAPIBase) { newValue in
90-
UserDefaults.standard.set(newValue, forKey: .settingsAIAPIBase)
91-
scheduleCheck()
92-
}
93-
if hasInsecureHTTPError {
94-
Text(AIEndpointSecurityPolicy.insecureHTTPErrorDescription)
95-
.font(.footnote)
96-
.foregroundStyle(.red)
97-
.padding(.top, 1)
98-
.padding(.bottom, 8)
99-
}
91+
PlanetSettingsContainer {
92+
VStack(alignment: .leading, spacing: PlanetSettingsSharedLayout.descriptionSpacing) {
93+
PlanetSettingsRow("API Base URL") {
94+
TextField("", text: $aiAPIBase)
95+
.textFieldStyle(.roundedBorder)
96+
.accessibilityLabel("API Base URL")
97+
.onChange(of: aiAPIBase) { newValue in
98+
UserDefaults.standard.set(newValue, forKey: .settingsAIAPIBase)
99+
scheduleCheck()
100+
}
101+
}
100102

101-
if ollamaDetected {
102-
HStack(spacing: 8) {
103-
StatusIndicatorView(state: .success)
104-
if isUsingOllama {
105-
Text("Using Ollama")
106-
} else {
107-
Text("Ollama detected on localhost")
108-
Spacer()
109-
Button("Use Ollama") {
110-
aiAPIBase = "http://localhost:11434/v1"
103+
if hasInsecureHTTPError {
104+
PlanetSettingsControlRow(alignment: .top) {
105+
Text(AIEndpointSecurityPolicy.insecureHTTPErrorDescription)
106+
.font(.footnote)
107+
.foregroundStyle(.red)
108+
.fixedSize(horizontal: false, vertical: true)
111109
}
112110
}
113-
}
114-
.padding(.bottom, 4)
115-
}
116111

117-
if lmStudioDetected {
118-
HStack(spacing: 8) {
119-
StatusIndicatorView(state: .success)
120-
if isUsingLMStudio {
121-
Text("Using LM Studio")
122-
} else {
123-
Text("LM Studio detected on localhost")
124-
Spacer()
125-
Button("Use LM Studio") {
126-
aiAPIBase = "http://localhost:1234/v1"
112+
if ollamaDetected {
113+
PlanetSettingsControlRow {
114+
HStack(spacing: 8) {
115+
StatusIndicatorView(state: .success)
116+
if isUsingOllama {
117+
Text("Using Ollama")
118+
.fixedSize(horizontal: false, vertical: true)
119+
} else {
120+
Text("Ollama detected on localhost")
121+
.fixedSize(horizontal: false, vertical: true)
122+
Spacer()
123+
Button("Use Ollama") {
124+
aiAPIBase = "http://localhost:11434/v1"
125+
}
126+
}
127+
}
128+
.frame(maxWidth: .infinity, alignment: .leading)
129+
.padding(.leading, Layout.secondaryRowLeadingInset)
127130
}
128131
}
129-
}
130-
.padding(.bottom, 4)
131-
}
132-
}
133132

134-
Section {
135-
ZStack {
136-
TextField("API Token", text: $aiAPIToken)
137-
.opacity(isShowingToken ? 1.0 : 0.0)
138-
SecureField("API Token", text: $aiAPIToken)
139-
.opacity(!isShowingToken ? 1.0 : 0.0)
140-
HStack {
141-
Spacer()
142-
Button {
143-
isShowingToken.toggle()
144-
} label: {
145-
Image(systemName: !isShowingToken ? "eye.slash" : "eye")
146-
.resizable()
147-
.aspectRatio(contentMode: .fit)
148-
.frame(width: 14, height: 14, alignment: .center)
133+
if lmStudioDetected {
134+
PlanetSettingsControlRow {
135+
HStack(spacing: 8) {
136+
StatusIndicatorView(state: .success)
137+
if isUsingLMStudio {
138+
Text("Using LM Studio")
139+
.fixedSize(horizontal: false, vertical: true)
140+
} else {
141+
Text("LM Studio detected on localhost")
142+
.fixedSize(horizontal: false, vertical: true)
143+
Spacer()
144+
Button("Use LM Studio") {
145+
aiAPIBase = "http://localhost:1234/v1"
146+
}
147+
}
148+
}
149+
.frame(maxWidth: .infinity, alignment: .leading)
150+
.padding(.leading, Layout.secondaryRowLeadingInset)
151+
}
149152
}
150-
.buttonStyle(.plain)
151153
}
152-
.padding(.horizontal, 8)
153-
}
154-
.textFieldStyle(.roundedBorder)
155-
.onChange(of: aiAPIToken) { newValue in
156-
Task { @MainActor in
157-
do {
158-
if newValue.isEmpty {
159-
try KeychainHelper.shared.delete(forKey: .settingsAIAPIToken)
160-
} else {
161-
try KeychainHelper.shared.saveValue(newValue, forKey: .settingsAIAPIToken)
154+
155+
PlanetSettingsRow("API Token") {
156+
ZStack {
157+
TextField("", text: $aiAPIToken)
158+
.opacity(isShowingToken ? 1.0 : 0.0)
159+
.accessibilityHidden(!isShowingToken)
160+
SecureField("", text: $aiAPIToken)
161+
.opacity(!isShowingToken ? 1.0 : 0.0)
162+
.accessibilityHidden(isShowingToken)
163+
HStack {
164+
Spacer()
165+
Button {
166+
isShowingToken.toggle()
167+
} label: {
168+
Label(
169+
isShowingToken ? "Hide API Token" : "Show API Token",
170+
systemImage: !isShowingToken ? "eye.slash" : "eye"
171+
)
172+
.labelStyle(.iconOnly)
173+
.font(.system(size: 14))
174+
.frame(width: 14, height: 14, alignment: .center)
175+
}
176+
.buttonStyle(.plain)
177+
}
178+
.padding(.horizontal, 8)
179+
}
180+
.textFieldStyle(.roundedBorder)
181+
.accessibilityLabel("API Token")
182+
.onChange(of: aiAPIToken) { newValue in
183+
Task { @MainActor in
184+
do {
185+
if newValue.isEmpty {
186+
try KeychainHelper.shared.delete(forKey: .settingsAIAPIToken)
187+
} else {
188+
try KeychainHelper.shared.saveValue(newValue, forKey: .settingsAIAPIToken)
189+
}
190+
} catch {
191+
debugPrint("failed to save AI API token: \(error)")
192+
}
162193
}
163-
} catch {
164-
debugPrint("failed to save AI API token: \(error)")
194+
scheduleCheck()
165195
}
166196
}
167-
scheduleCheck()
168-
}
169-
}
170197

171-
Section {
172-
TextField("Preferred Model", text: $aiPreferredModel)
173-
.textFieldStyle(.roundedBorder)
174-
.focused($isModelFieldFocused)
175-
.onChange(of: aiPreferredModel) { newValue in
176-
UserDefaults.standard.set(newValue, forKey: .settingsAIPreferredModel)
177-
schedulePreferredModelCheck()
178-
}
198+
VStack(alignment: .leading, spacing: PlanetSettingsSharedLayout.descriptionSpacing) {
199+
PlanetSettingsRow("Preferred Model") {
200+
TextField("", text: $aiPreferredModel)
201+
.textFieldStyle(.roundedBorder)
202+
.accessibilityLabel("Preferred Model")
203+
.focused($isModelFieldFocused)
204+
.onChange(of: aiPreferredModel) { newValue in
205+
UserDefaults.standard.set(newValue, forKey: .settingsAIPreferredModel)
206+
schedulePreferredModelCheck()
207+
}
208+
}
179209

180-
if showSuggestions {
181-
ScrollView {
182-
VStack(alignment: .leading, spacing: 0) {
183-
ForEach(filteredModelIDs, id: \.self) { modelID in
184-
Text(modelID)
185-
.font(.system(.body, design: .monospaced))
186-
.padding(.horizontal, 4)
187-
.padding(.vertical, 5)
188-
.frame(maxWidth: .infinity, alignment: .leading)
189-
.contentShape(Rectangle())
190-
.onTapGesture {
191-
aiPreferredModel = modelID
192-
isModelFieldFocused = false
210+
if showSuggestions {
211+
PlanetSettingsControlRow(alignment: .top) {
212+
ScrollView {
213+
VStack(alignment: .leading, spacing: 0) {
214+
ForEach(filteredModelIDs, id: \.self) { modelID in
215+
Button {
216+
aiPreferredModel = modelID
217+
isModelFieldFocused = false
218+
} label: {
219+
Text(modelID)
220+
.font(.system(.body, design: .monospaced))
221+
.padding(.horizontal, 4)
222+
.padding(.vertical, 5)
223+
.frame(maxWidth: .infinity, alignment: .leading)
224+
.contentShape(Rectangle())
225+
}
226+
.buttonStyle(.plain)
227+
if modelID != filteredModelIDs.last {
228+
Divider()
229+
}
230+
}
193231
}
194-
if modelID != filteredModelIDs.last {
195-
Divider()
196232
}
233+
.frame(maxHeight: 160)
234+
.background(Color(NSColor.controlBackgroundColor))
235+
.cornerRadius(6)
236+
.overlay(
237+
RoundedRectangle(cornerRadius: 6)
238+
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
239+
)
240+
.padding(.leading, Layout.secondaryRowLeadingInset)
197241
}
198242
}
199243
}
200-
.frame(maxHeight: 160)
201-
.background(Color(NSColor.controlBackgroundColor))
202-
.cornerRadius(6)
203-
.overlay(RoundedRectangle(cornerRadius: 6).stroke(Color.secondary.opacity(0.2), lineWidth: 1))
204-
}
205-
}
206244

207-
Section {
208-
HStack(spacing: 8) {
209-
StatusIndicatorView(state: statusIndicatorState)
210-
statusLabel
211-
}
212-
.padding(.top, 4)
213-
}
245+
VStack(alignment: .leading, spacing: PlanetSettingsSharedLayout.descriptionSpacing) {
246+
PlanetSettingsControlRow(alignment: .top) {
247+
HStack(spacing: 8) {
248+
StatusIndicatorView(state: statusIndicatorState)
249+
statusLabel
250+
.frame(maxWidth: .infinity, alignment: .leading)
251+
.fixedSize(horizontal: false, vertical: true)
252+
}
253+
.padding(.leading, Layout.secondaryRowLeadingInset)
254+
}
214255

215-
Section {
216-
HStack(spacing: 8) {
217-
StatusIndicatorView(state: onDeviceAIState)
218-
onDeviceAIStatusLabel
256+
PlanetSettingsControlRow(alignment: .top) {
257+
HStack(alignment: .top, spacing: 8) {
258+
StatusIndicatorView(state: onDeviceAIState)
259+
onDeviceAIStatusLabel
260+
.frame(maxWidth: .infinity, alignment: .leading)
261+
.fixedSize(horizontal: false, vertical: true)
262+
}
263+
.padding(.leading, Layout.secondaryRowLeadingInset)
264+
}
265+
}
266+
.padding(.top, 4)
219267
}
220-
.padding(.top, 4)
221268
}
222269

223270
Spacer()

Planet/Settings/PlanetSettingsLayout.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ enum PlanetSettingsSharedLayout {
1212
static let sectionSpacing: CGFloat = 24
1313
static let descriptionSpacing: CGFloat = 6
1414
static let buttonSpacing: CGFloat = 12
15-
static let horizontalPadding: CGFloat = 20
16-
static let verticalPadding: CGFloat = 20
15+
static let horizontalPadding: CGFloat = 0
16+
static let verticalPadding: CGFloat = 0
1717
}
1818

1919
struct PlanetSettingsContainer<Content: View>: View {
@@ -56,6 +56,9 @@ struct PlanetSettingsRow<Content: View>: View {
5656
var body: some View {
5757
HStack(alignment: alignment, spacing: PlanetSettingsSharedLayout.columnSpacing) {
5858
Text(title)
59+
.multilineTextAlignment(.trailing)
60+
.lineLimit(2)
61+
.fixedSize(horizontal: false, vertical: true)
5962
.frame(width: PlanetSettingsSharedLayout.labelWidth, alignment: .trailing)
6063
content
6164
.frame(maxWidth: .infinity, alignment: .leading)

Planet/versioning.xcconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
CURRENT_PROJECT_VERSION = 2758
1+
CURRENT_PROJECT_VERSION = 2759

0 commit comments

Comments
 (0)