UI redesign Phase 1: NavigationSplitView with collapsible sidebar
- Replace root VStack with NavigationSplitView (2-column, collapsible sidebar) - Add SidebarView: new chat button, conversation search, list with swipe actions - Slim HeaderView to text-only (provider + model + star); remove all icon rows - Move status pills (Online, MCP, Synced) to footer right side - Remove version number and shortcut hints from footer - Add resizable InputBar with drag handle (persisted height) and globe/network.slash online toggle - Fix Norwegian menu appearing on English systems (CFBundleLocalizations in Info.plist) - Add View menu (Model Info, History, Stats, Credits, Online Mode toggle ⌘⇧O) - Add ⌘L as alias for Search Conversations (muscle memory for /load users) - Add Check for Updates to Help menu with download URL from Gitea API - Add one-time Intel/Rosetta deprecation warning on first launch - Swift 6: fix self.Self.isoString() call sites in DatabaseService Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,11 @@ final class UpdateCheckService {
|
||||
|
||||
var updateAvailable: Bool = false
|
||||
var latestVersion: String? = nil
|
||||
var downloadURL: URL? = nil
|
||||
|
||||
// Manual check state — drives the update alert in ContentView
|
||||
var isCheckingManually: Bool = false
|
||||
var manualCheckMessage: String? = nil
|
||||
|
||||
private let apiURL = "https://gitlab.pm/api/v1/repos/rune/oai-swift/releases/latest"
|
||||
private let releasesURL = URL(string: "https://gitlab.pm/rune/oai-swift/releases")!
|
||||
@@ -48,6 +53,24 @@ final class UpdateCheckService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Manual check triggered from the Help menu. Non-blocking — result surfaces via manualCheckMessage.
|
||||
func checkForUpdatesManually() {
|
||||
guard !isCheckingManually else { return }
|
||||
isCheckingManually = true
|
||||
Task.detached(priority: .background) {
|
||||
await self.performCheck()
|
||||
let current = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
|
||||
await MainActor.run {
|
||||
if self.updateAvailable, let v = self.latestVersion {
|
||||
self.manualCheckMessage = String(localized: "Version \(v) is available.")
|
||||
} else {
|
||||
self.manualCheckMessage = String(localized: "You're up to date (v\(current)).")
|
||||
}
|
||||
self.isCheckingManually = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func performCheck() async {
|
||||
guard let url = URL(string: apiURL) else { return }
|
||||
|
||||
@@ -69,9 +92,16 @@ final class UpdateCheckService {
|
||||
let latestVer = tagName.hasPrefix("v") ? String(tagName.dropFirst()) : tagName
|
||||
let currentVer = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
||||
|
||||
// Extract direct DMG download URL from release assets
|
||||
let dmgURL: URL? = (release["assets"] as? [[String: Any]])?
|
||||
.first { ($0["name"] as? String ?? "").lowercased().hasSuffix(".dmg") }
|
||||
.flatMap { $0["browser_download_url"] as? String }
|
||||
.flatMap { URL(string: $0) }
|
||||
|
||||
if isNewer(latestVer, than: currentVer) {
|
||||
await MainActor.run {
|
||||
self.latestVersion = latestVer
|
||||
self.downloadURL = dmgURL
|
||||
self.updateAvailable = true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user