// // ContentView.swift // oAI // // Root navigation container — NavigationSplitView with collapsible sidebar // // SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (C) 2026 Rune Olsen // // This file is part of oAI. // // oAI is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // oAI is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General // Public License for more details. // // You should have received a copy of the GNU Affero General Public // License along with oAI. If not, see . 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.size sysctlbyname("sysctl.proc_translated", &ret, &size, nil, 0) return ret == 1 } #endif } #Preview { ContentView() .environment(ChatViewModel()) }