New version v2.3.6

This commit is contained in:
2026-03-04 10:19:16 +01:00
parent 65a35cd508
commit 49f842f119
52 changed files with 14034 additions and 358 deletions

View File

@@ -142,7 +142,7 @@ It's better to admit "I need more information" or "I cannot do that" than to fak
tabButton(7, icon: "brain", label: "Skills")
tabButton(4, icon: "arrow.triangle.2.circlepath", label: "Sync")
tabButton(5, icon: "envelope", label: "Email")
tabButton(8, icon: "doc.text", label: "Paperless")
tabButton(8, icon: "doc.text", label: "Paperless", beta: true)
tabButton(9, icon: "icloud.and.arrow.up", label: "Backup")
}
.padding(.horizontal, 16)
@@ -283,10 +283,42 @@ It's better to admit "I need more information" or "I cannot do that" than to fak
.toggleStyle(.switch)
}
rowDivider()
row("MCP (File Access)") {
Toggle("", isOn: $settingsService.mcpEnabled)
row("Reasoning (Thinking)") {
Toggle("", isOn: $settingsService.reasoningEnabled)
.toggleStyle(.switch)
}
if settingsService.reasoningEnabled {
rowDivider()
row("Reasoning Effort") {
Picker("", selection: $settingsService.reasoningEffort) {
Text("High (~80%)").tag("high")
Text("Medium (~50%)").tag("medium")
Text("Low (~20%)").tag("low")
Text("Minimal (~10%)").tag("minimal")
}
.labelsHidden()
.fixedSize()
}
VStack(alignment: .leading, spacing: 2) {
Text(reasoningEffortDescription)
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.horizontal, 12)
.padding(.bottom, 4)
rowDivider()
row("Hide Reasoning in Response") {
Toggle("", isOn: $settingsService.reasoningExclude)
.toggleStyle(.switch)
}
VStack(alignment: .leading, spacing: 2) {
Text("Model thinks internally but reasoning is not shown in chat")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.horizontal, 12)
.padding(.bottom, 4)
}
}
}
@@ -2050,13 +2082,25 @@ It's better to admit "I need more information" or "I cannot do that" than to fak
// MARK: - Tab Navigation
private func tabButton(_ tag: Int, icon: String, label: String) -> some View {
private func tabButton(_ tag: Int, icon: String, label: LocalizedStringKey, beta: Bool = false) -> some View {
Button(action: { selectedTab = tag }) {
VStack(spacing: 3) {
Image(systemName: icon)
.font(.system(size: 22))
.frame(height: 28)
.foregroundStyle(selectedTab == tag ? .blue : .secondary)
ZStack(alignment: .topTrailing) {
Image(systemName: icon)
.font(.system(size: 22))
.frame(height: 28)
.foregroundStyle(selectedTab == tag ? .blue : .secondary)
if beta {
Text("β")
.font(.system(size: 8, weight: .bold))
.foregroundStyle(.white)
.padding(.horizontal, 3)
.padding(.vertical, 1)
.background(Color.orange)
.clipShape(Capsule())
.offset(x: 6, y: -2)
}
}
Text(label)
.font(.system(size: 11))
.foregroundStyle(selectedTab == tag ? .blue : .secondary)
@@ -2070,7 +2114,7 @@ It's better to admit "I need more information" or "I cannot do that" than to fak
.buttonStyle(.plain)
}
private func tabTitle(_ tag: Int) -> String {
private func tabTitle(_ tag: Int) -> LocalizedStringKey {
switch tag {
case 0: return "General"
case 1: return "MCP"
@@ -2086,13 +2130,23 @@ It's better to admit "I need more information" or "I cannot do that" than to fak
}
}
// MARK: - Reasoning Helpers
private var reasoningEffortDescription: LocalizedStringKey {
switch settingsService.reasoningEffort {
case "high": return "Uses ~80% of max tokens for reasoning — best for hard problems"
case "medium": return "Uses ~50% of max tokens for reasoning — balanced default"
case "low": return "Uses ~20% of max tokens for reasoning — faster, cheaper"
case "minimal": return "Uses ~10% of max tokens for reasoning — lightest thinking"
default: return "Uses ~50% of max tokens for reasoning — balanced default"
}
}
// MARK: - Layout Helpers
private func row<Content: View>(_ label: String, @ViewBuilder content: () -> Content) -> some View {
private func row<Content: View>(_ label: LocalizedStringKey, @ViewBuilder content: () -> Content) -> some View {
HStack(alignment: .center, spacing: 12) {
if !label.isEmpty {
Text(label).font(.system(size: 14))
}
Text(label).font(.system(size: 14))
Spacer()
content()
}
@@ -2100,7 +2154,7 @@ It's better to admit "I need more information" or "I cannot do that" than to fak
.padding(.vertical, 10)
}
private func sectionHeader(_ title: String) -> some View {
private func sectionHeader(_ title: LocalizedStringKey) -> some View {
Text(title)
.font(.system(size: 12, weight: .semibold))
.foregroundStyle(.secondary)
@@ -2205,7 +2259,7 @@ It's better to admit "I need more information" or "I cannot do that" than to fak
return .green
}
private var syncStatusText: String {
private var syncStatusText: LocalizedStringKey {
guard settingsService.syncEnabled else { return "Disabled" }
guard settingsService.syncConfigured else { return "Not configured" }
guard gitSync.syncStatus.isCloned else { return "Not cloned" }
@@ -2318,19 +2372,9 @@ It's better to admit "I need more information" or "I cannot do that" than to fak
}
private func timeAgo(_ date: Date) -> String {
let seconds = Int(Date().timeIntervalSince(date))
if seconds < 60 {
return "just now"
} else if seconds < 3600 {
let minutes = seconds / 60
return "\(minutes) minute\(minutes == 1 ? "" : "s") ago"
} else if seconds < 86400 {
let hours = seconds / 3600
return "\(hours) hour\(hours == 1 ? "" : "s") ago"
} else {
let days = seconds / 86400
return "\(days) day\(days == 1 ? "" : "s") ago"
}
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
return formatter.localizedString(for: date, relativeTo: .now)
}
}