SPB Git

spb/zyquo-cloud Public MIT

Native macOS AI chat client for 12 cloud providers — your keys, every cloud model, one beautiful chat.

Swift 97.4% Shell 1.7% Makefile 1%
4.4 KB · 135 lines swift
Raw Blame History
1//2//  ParametersEditorView.swift3//  Zyquo Cloud4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  Per-conversation generation parameters. Only the controls the selected9//  model actually supports are shown (ParameterSupport gates each row).10//1112import SwiftUI1314struct ParametersEditorView: View {15    @Binding var parameters: ChatParameters16    let support: ParameterSupport1718    var body: some View {19        VStack(alignment: .leading, spacing: ZyquoSpacing.sm) {20            Text("Parameters")21                .font(ZyquoFont.bodyEmphasis())22                .foregroundStyle(ZyquoColor.textPrimary)2324            if support.temperature {25                optionalSlider(26                    "Temperature", value: $parameters.temperature,27                    range: 0...2, defaultValue: 1, format: "%.2f"28                )29            }30            if support.topP {31                optionalSlider(32                    "Top P", value: $parameters.topP,33                    range: 0...1, defaultValue: 1, format: "%.2f"34                )35            }36            maxTokensRow37            if support.frequencyPenalty {38                optionalSlider(39                    "Frequency penalty", value: $parameters.frequencyPenalty,40                    range: -2...2, defaultValue: 0, format: "%.1f"41                )42            }43            if support.presencePenalty {44                optionalSlider(45                    "Presence penalty", value: $parameters.presencePenalty,46                    range: -2...2, defaultValue: 0, format: "%.1f"47                )48            }49            if support.reasoningEffort {50                reasoningEffortRow51            }52            if support.thinkingToggle {53                Toggle("Extended thinking", isOn: Binding(54                    get: { parameters.thinkingEnabled ?? false },55                    set: { parameters.thinkingEnabled = $0 }56                ))57                .font(ZyquoFont.body(size: 12.5))58            }59        }60        .padding(ZyquoSpacing.md)61        .frame(width: 300)62    }6364    private var maxTokensRow: some View {65        HStack {66            Text("Max tokens")67                .font(ZyquoFont.body(size: 12.5))68            Spacer()69            TextField(70                "default",71                value: Binding(72                    get: { parameters.maxTokens },73                    set: { parameters.maxTokens = $0 }74                ),75                format: .number76            )77            .textFieldStyle(.roundedBorder)78            .frame(width: 90)79            .font(ZyquoFont.body(size: 12))80        }81    }8283    private var reasoningEffortRow: some View {84        HStack {85            Text("Reasoning effort")86                .font(ZyquoFont.body(size: 12.5))87            Spacer()88            Picker("", selection: Binding(89                get: { parameters.reasoningEffort ?? "default" },90                set: { parameters.reasoningEffort = $0 == "default" ? nil : $0 }91            )) {92                Text("Default").tag("default")93                Text("Low").tag("low")94                Text("Medium").tag("medium")95                Text("High").tag("high")96            }97            .pickerStyle(.menu)98            .frame(width: 110)99        }100    }101102    /// A slider with an enable checkbox: unchecked = provider default (omitted).103    private func optionalSlider(104        _ title: String,105        value: Binding<Double?>,106        range: ClosedRange<Double>,107        defaultValue: Double,108        format: String109    ) -> some View {110        VStack(alignment: .leading, spacing: 2) {111            HStack {112                Toggle(isOn: Binding(113                    get: { value.wrappedValue != nil },114                    set: { value.wrappedValue = $0 ? defaultValue : nil }115                )) {116                    Text(title).font(ZyquoFont.body(size: 12.5))117                }118                .toggleStyle(.checkbox)119                Spacer()120                Text(value.wrappedValue.map { String(format: format, $0) } ?? "default")121                    .font(ZyquoFont.caption)122                    .foregroundStyle(ZyquoColor.textTertiary)123                    .monospacedDigit()124            }125            if let current = value.wrappedValue {126                Slider(127                    value: Binding(get: { current }, set: { value.wrappedValue = $0 }),128                    in: range129                )130                .controlSize(.small)131            }132        }133    }134}135