From 675183152aaec5bc118be27f75d63e49dcf6f4a9 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Sat, 21 Mar 2026 09:32:33 +0100 Subject: [PATCH] fix: repaired scene prefs --- .../Views/GenerationSettingsEditor.swift | 279 ++++++++++++------ 1 file changed, 195 insertions(+), 84 deletions(-) diff --git a/MLXServer/Views/GenerationSettingsEditor.swift b/MLXServer/Views/GenerationSettingsEditor.swift index a4022d1..38cf748 100644 --- a/MLXServer/Views/GenerationSettingsEditor.swift +++ b/MLXServer/Views/GenerationSettingsEditor.swift @@ -1,66 +1,18 @@ import SwiftUI -private let generationDoubleFormat = FloatingPointFormatStyle.number.precision(.fractionLength(0...2)) -private let generationIntegerFormat = IntegerFormatStyle.number.grouping(.never) - struct GenerationDefaultsEditor: View { @Binding var settings: GenerationSettings var body: some View { Toggle("Enable thinking mode", isOn: $settings.thinkingEnabled) - doubleRow("Temperature", value: $settings.temperature) - doubleRow("Top P", value: $settings.topP) - intRow("Top K", value: $settings.topK) - doubleRow("Min P", value: $settings.minP) - intRow("Max tokens", value: $settings.maxTokens) - optionalDoubleRow("Repetition penalty", value: $settings.repetitionPenalty) - optionalDoubleRow("Presence penalty", value: $settings.presencePenalty) - optionalDoubleRow("Frequency penalty", value: $settings.frequencyPenalty) - } - - private func doubleRow(_ title: String, value: Binding) -> some View { - HStack { - Text(title) - Spacer() - TextField(title, value: value, format: generationDoubleFormat) - .multilineTextAlignment(.trailing) - .frame(width: 90) - } - } - - private func intRow(_ title: String, value: Binding) -> some View { - HStack { - Text(title) - Spacer() - TextField(title, value: value, format: generationIntegerFormat) - .multilineTextAlignment(.trailing) - .frame(width: 90) - } - } - - private func optionalDoubleRow(_ title: String, value: Binding) -> some View { - HStack { - Text(title) - Spacer() - TextField(title, value: binding(for: value), format: generationDoubleFormat) - .multilineTextAlignment(.trailing) - .frame(width: 90) - Button(value.wrappedValue == nil ? "Set" : "Clear") { - if value.wrappedValue == nil { - value.wrappedValue = 1.0 - } else { - value.wrappedValue = nil - } - } - .buttonStyle(.link) - } - } - - private func binding(for value: Binding) -> Binding { - Binding( - get: { value.wrappedValue ?? 1.0 }, - set: { value.wrappedValue = $0 } - ) + DecimalSettingRow(title: "Temperature", value: $settings.temperature) + DecimalSettingRow(title: "Top P", value: $settings.topP) + IntegerSettingRow(title: "Top K", value: $settings.topK) + DecimalSettingRow(title: "Min P", value: $settings.minP) + IntegerSettingRow(title: "Max tokens", value: $settings.maxTokens) + OptionalDecimalSettingRow(title: "Repetition penalty", value: $settings.repetitionPenalty, fallbackValue: 1.0) + OptionalDecimalSettingRow(title: "Presence penalty", value: $settings.presencePenalty, fallbackValue: 0.0) + OptionalDecimalSettingRow(title: "Frequency penalty", value: $settings.frequencyPenalty, fallbackValue: 0.0) } } @@ -76,69 +28,228 @@ struct GenerationOverridesEditor: View { Text("Disabled").tag(Optional(false)) } - optionalDoubleRow("Temperature", value: $overrides.temperature, inheritedValue: inheritedSettings.temperature) - optionalDoubleRow("Top P", value: $overrides.topP, inheritedValue: inheritedSettings.topP) - optionalIntRow("Top K", value: $overrides.topK, inheritedValue: inheritedSettings.topK) - optionalDoubleRow("Min P", value: $overrides.minP, inheritedValue: inheritedSettings.minP) - optionalIntRow("Max tokens", value: $overrides.maxTokens, inheritedValue: inheritedSettings.maxTokens) - optionalDoubleRow("Repetition penalty", value: $overrides.repetitionPenalty, inheritedValue: inheritedSettings.repetitionPenalty ?? 0) - optionalDoubleRow("Presence penalty", value: $overrides.presencePenalty, inheritedValue: inheritedSettings.presencePenalty ?? 0) - optionalDoubleRow("Frequency penalty", value: $overrides.frequencyPenalty, inheritedValue: inheritedSettings.frequencyPenalty ?? 0) + OptionalDecimalSettingRow(title: "Temperature", value: $overrides.temperature, fallbackValue: inheritedSettings.temperature, inherited: true) + OptionalDecimalSettingRow(title: "Top P", value: $overrides.topP, fallbackValue: inheritedSettings.topP, inherited: true) + OptionalIntegerSettingRow(title: "Top K", value: $overrides.topK, fallbackValue: inheritedSettings.topK, inherited: true) + OptionalDecimalSettingRow(title: "Min P", value: $overrides.minP, fallbackValue: inheritedSettings.minP, inherited: true) + OptionalIntegerSettingRow(title: "Max tokens", value: $overrides.maxTokens, fallbackValue: inheritedSettings.maxTokens, inherited: true) + OptionalDecimalSettingRow(title: "Repetition penalty", value: $overrides.repetitionPenalty, fallbackValue: inheritedSettings.repetitionPenalty ?? 0, inherited: true) + OptionalDecimalSettingRow(title: "Presence penalty", value: $overrides.presencePenalty, fallbackValue: inheritedSettings.presencePenalty ?? 0, inherited: true) + OptionalDecimalSettingRow(title: "Frequency penalty", value: $overrides.frequencyPenalty, fallbackValue: inheritedSettings.frequencyPenalty ?? 0, inherited: true) Text("Unset fields inherit from \(inheritedSource). The values shown are the effective starting values for this scene.") .font(.caption) .foregroundStyle(.secondary) } +} - private func optionalDoubleRow(_ title: String, value: Binding, inheritedValue: Double) -> some View { +private struct DecimalSettingRow: View { + let title: String + @Binding var value: Double + @State private var text: String + + init(title: String, value: Binding) { + self.title = title + self._value = value + self._text = State(initialValue: NumericFieldFormatting.doubleString(value.wrappedValue)) + } + + var body: some View { HStack { Text(title) Spacer() - TextField(title, value: Binding( - get: { value.wrappedValue ?? inheritedValue }, - set: { value.wrappedValue = $0 } - ), format: generationDoubleFormat) + TextField("", text: $text) .multilineTextAlignment(.trailing) .frame(width: 90) - if value.wrappedValue == nil { + .onChange(of: text) { + if let parsed = NumericFieldFormatting.parseDouble(text) { + value = parsed + } + } + .onChange(of: value) { + let formatted = NumericFieldFormatting.doubleString(value) + if text != formatted { + text = formatted + } + } + } + } +} + +private struct IntegerSettingRow: View { + let title: String + @Binding var value: Int + @State private var text: String + + init(title: String, value: Binding) { + self.title = title + self._value = value + self._text = State(initialValue: NumericFieldFormatting.intString(value.wrappedValue)) + } + + var body: some View { + HStack { + Text(title) + Spacer() + TextField("", text: $text) + .multilineTextAlignment(.trailing) + .frame(width: 90) + .onChange(of: text) { + if let parsed = NumericFieldFormatting.parseInt(text) { + value = parsed + } + } + .onChange(of: value) { + let formatted = NumericFieldFormatting.intString(value) + if text != formatted { + text = formatted + } + } + } + } +} + +private struct OptionalDecimalSettingRow: View { + let title: String + @Binding var value: Double? + let fallbackValue: Double + var inherited = false + @State private var text: String + + init(title: String, value: Binding, fallbackValue: Double, inherited: Bool = false) { + self.title = title + self._value = value + self.fallbackValue = fallbackValue + self.inherited = inherited + self._text = State(initialValue: NumericFieldFormatting.doubleString(value.wrappedValue ?? fallbackValue)) + } + + var body: some View { + HStack { + Text(title) + Spacer() + TextField("", text: $text) + .multilineTextAlignment(.trailing) + .frame(width: 90) + .onChange(of: text) { + if let parsed = NumericFieldFormatting.parseDouble(text) { + value = parsed + } + } + .onChange(of: value) { + syncText() + } + .onChange(of: fallbackValue) { + if value == nil { + syncText() + } + } + if inherited && value == nil { Text("Inherited") .font(.caption) .foregroundStyle(.secondary) } - Button(value.wrappedValue == nil ? "Override" : "Clear") { - if value.wrappedValue == nil { - value.wrappedValue = inheritedValue + Button(value == nil ? "Override" : "Clear") { + if value == nil { + value = fallbackValue } else { - value.wrappedValue = nil + value = nil } + syncText() } .buttonStyle(.link) } } - private func optionalIntRow(_ title: String, value: Binding, inheritedValue: Int) -> some View { + private func syncText() { + let formatted = NumericFieldFormatting.doubleString(value ?? fallbackValue) + if text != formatted { + text = formatted + } + } +} + +private struct OptionalIntegerSettingRow: View { + let title: String + @Binding var value: Int? + let fallbackValue: Int + var inherited = false + @State private var text: String + + init(title: String, value: Binding, fallbackValue: Int, inherited: Bool = false) { + self.title = title + self._value = value + self.fallbackValue = fallbackValue + self.inherited = inherited + self._text = State(initialValue: NumericFieldFormatting.intString(value.wrappedValue ?? fallbackValue)) + } + + var body: some View { HStack { Text(title) Spacer() - TextField(title, value: Binding( - get: { value.wrappedValue ?? inheritedValue }, - set: { value.wrappedValue = $0 } - ), format: generationIntegerFormat) + TextField("", text: $text) .multilineTextAlignment(.trailing) .frame(width: 90) - if value.wrappedValue == nil { + .onChange(of: text) { + if let parsed = NumericFieldFormatting.parseInt(text) { + value = parsed + } + } + .onChange(of: value) { + syncText() + } + .onChange(of: fallbackValue) { + if value == nil { + syncText() + } + } + if inherited && value == nil { Text("Inherited") .font(.caption) .foregroundStyle(.secondary) } - Button(value.wrappedValue == nil ? "Override" : "Clear") { - if value.wrappedValue == nil { - value.wrappedValue = inheritedValue + Button(value == nil ? "Override" : "Clear") { + if value == nil { + value = fallbackValue } else { - value.wrappedValue = nil + value = nil } + syncText() } .buttonStyle(.link) } } + + private func syncText() { + let formatted = NumericFieldFormatting.intString(value ?? fallbackValue) + if text != formatted { + text = formatted + } + } +} + +private enum NumericFieldFormatting { + static func parseDouble(_ text: String) -> Double? { + let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return nil } + return Double(trimmed.replacingOccurrences(of: ",", with: ".")) + } + + static func parseInt(_ text: String) -> Int? { + let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return nil } + return Int(trimmed) + } + + static func doubleString(_ value: Double) -> String { + if value.rounded() == value { + return String(Int(value)) + } + return String(value) + } + + static func intString(_ value: Int) -> String { + String(value) + } } \ No newline at end of file