Files
oai-swift/oAI/Views/Main/ContentView.swift
T
runeandClaude Sonnet 5 0af644456e Update commercial licensing contact URL to oai.pm
Consolidates the mac.oai.pm subdomain references (introduced in the
PolyForm Noncommercial relicense) to the root oai.pm domain, across
the LICENSE file, README, and all Swift source file headers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:34:04 +02:00

173 lines
6.0 KiB
Swift

//
// ContentView.swift
// oAI
//
// Root navigation container — NavigationSplitView with collapsible sidebar
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI is licensed under the PolyForm Noncommercial License 1.0.0.
// You may use, study, modify, and share it for any noncommercial
// purpose. Commercial use — including selling oAI or any part of
// it, standalone or bundled into another product or service —
// requires a separate commercial license from the copyright holder.
//
// See the LICENSE file or
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://oai.pm>.
import SwiftUI
#if os(macOS)
import Darwin // uname, sysctlbyname
#endif
struct ContentView: View {
@Environment(ChatViewModel.self) var chatViewModel
private var updateService = UpdateCheckService.shared
@State private var columnVisibility: NavigationSplitViewVisibility = .all
@State private var showIntelWarning = false
var body: some View {
@Bindable var vm = chatViewModel
NavigationSplitView(columnVisibility: $columnVisibility) {
SidebarView()
.navigationSplitViewColumnWidth(min: 200, ideal: 240, max: 340)
} detail: {
ChatView(
onModelSelect: { chatViewModel.showModelSelector = true },
onProviderChange: { newProvider in
chatViewModel.changeProvider(newProvider)
}
)
}
.frame(minWidth: 860, minHeight: 560)
#if os(macOS)
.onAppear {
NSApplication.shared.windows.forEach { $0.tabbingMode = .disallowed }
checkIntelWarning()
}
.onKeyPress(.return, phases: .down) { press in
if press.modifiers.contains(.command) {
chatViewModel.sendMessage()
return .handled
}
return .ignored
}
#endif
.sheet(isPresented: $vm.showModelSelector) {
ModelSelectorView(
models: chatViewModel.availableModels,
selectedModel: chatViewModel.selectedModel,
onSelect: { model in
let oldModel = chatViewModel.selectedModel
chatViewModel.selectModel(model)
chatViewModel.showModelSelector = false
Task {
await chatViewModel.onModelSwitch(from: oldModel, to: model)
}
}
)
.task {
if chatViewModel.availableModels.count <= 10 {
await chatViewModel.loadAvailableModels()
}
}
}
.sheet(isPresented: $vm.showSettings, onDismiss: {
chatViewModel.syncFromSettings()
}) {
SettingsView(chatViewModel: chatViewModel)
}
.sheet(isPresented: $vm.showStats) {
StatsView(
stats: chatViewModel.sessionStats,
model: chatViewModel.selectedModel,
provider: chatViewModel.currentProvider
)
}
.sheet(isPresented: $vm.showHelp) {
HelpView()
}
.sheet(isPresented: $vm.showCredits) {
CreditsView(provider: chatViewModel.currentProvider)
}
.sheet(isPresented: $vm.showConversations) {
ConversationListView(
onLoad: { conversation in
chatViewModel.loadConversation(conversation)
},
onRename: { id, newName in
chatViewModel.didRenameConversation(id: id, newName: newName)
}
)
}
.sheet(item: $vm.modelInfoTarget) { model in
ModelInfoView(model: model)
}
.sheet(isPresented: $vm.showHistory) {
HistoryView(onSelect: { input in
chatViewModel.inputText = input
})
}
.alert("Intel Mac Support Ending", isPresented: $showIntelWarning) {
Button("Got It") {
UserDefaults.standard.set(true, forKey: "hasShownIntelWarning")
}
} message: {
Text("oAI v2.4 is the last version to support Intel Macs and Rosetta. Starting with macOS 28, oAI will require Apple Silicon. Consider upgrading your Mac to continue receiving updates.")
}
.alert("Software Update", isPresented: Binding(
get: { updateService.manualCheckMessage != nil },
set: { if !$0 { updateService.manualCheckMessage = nil } }
)) {
if updateService.updateAvailable {
if let url = updateService.downloadURL {
Button("Download v\(updateService.latestVersion ?? "")") {
NSWorkspace.shared.open(url)
}
}
Button("Release Page") { updateService.openReleasesPage() }
Button("Later", role: .cancel) { }
} else {
Button("OK", role: .cancel) { }
}
} message: {
Text(updateService.manualCheckMessage ?? "")
}
}
#if os(macOS)
private func checkIntelWarning() {
guard !UserDefaults.standard.bool(forKey: "hasShownIntelWarning") else { return }
guard isIntelNative || isRosetta else { return }
showIntelWarning = true
}
private var isIntelNative: Bool {
var systemInfo = utsname()
uname(&systemInfo)
let machine = withUnsafeBytes(of: &systemInfo.machine) {
String(cString: $0.bindMemory(to: CChar.self).baseAddress!)
}
return machine.contains("x86_64")
}
private var isRosetta: Bool {
var ret: Int32 = 0
var size = MemoryLayout<Int32>.size
sysctlbyname("sysctl.proc_translated", &ret, &size, nil, 0)
return ret == 1
}
#endif
}
#Preview {
ContentView()
.environment(ChatViewModel())
}