fix: corrected loading of app with doubleclick on chat document
This commit is contained in:
@@ -14,6 +14,8 @@ struct ContentView: View {
|
|||||||
@State private var exportDocument: ChatExportDocument?
|
@State private var exportDocument: ChatExportDocument?
|
||||||
@State private var documentErrorMessage: String?
|
@State private var documentErrorMessage: String?
|
||||||
@State private var exportErrorMessage: String?
|
@State private var exportErrorMessage: String?
|
||||||
|
@State private var startupTask: Task<Void, Never>?
|
||||||
|
@State private var isOpeningDocument = false
|
||||||
|
|
||||||
private var isRunningTests: Bool {
|
private var isRunningTests: Bool {
|
||||||
ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
|
ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
|
||||||
@@ -37,14 +39,9 @@ struct ContentView: View {
|
|||||||
if Preferences.apiAutoStart && !isRunningTests {
|
if Preferences.apiAutoStart && !isRunningTests {
|
||||||
vm.startAPIServer()
|
vm.startAPIServer()
|
||||||
}
|
}
|
||||||
// Restore autosaved session if no document is being opened
|
|
||||||
if !documentController.hasPendingOpenRequests && !isRunningTests {
|
|
||||||
Task {
|
|
||||||
await vm.restoreFromAutosave()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheduleStartupWork()
|
||||||
processPendingOpenRequests()
|
processPendingOpenRequests()
|
||||||
}
|
}
|
||||||
.onChange(of: modelManager.currentModel) {
|
.onChange(of: modelManager.currentModel) {
|
||||||
@@ -62,6 +59,7 @@ struct ContentView: View {
|
|||||||
showLoadError = modelManager.errorMessage != nil
|
showLoadError = modelManager.errorMessage != nil
|
||||||
}
|
}
|
||||||
.onChange(of: documentController.openRequestNonce) {
|
.onChange(of: documentController.openRequestNonce) {
|
||||||
|
startupTask?.cancel()
|
||||||
processPendingOpenRequests()
|
processPendingOpenRequests()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -376,11 +374,58 @@ struct ContentView: View {
|
|||||||
|
|
||||||
Task {
|
Task {
|
||||||
while let url = documentController.consumeNextOpenRequest() {
|
while let url = documentController.consumeNextOpenRequest() {
|
||||||
|
startupTask?.cancel()
|
||||||
await openDocument(at: url)
|
await openDocument(at: url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func scheduleStartupWork() {
|
||||||
|
guard let chatVM else { return }
|
||||||
|
|
||||||
|
startupTask?.cancel()
|
||||||
|
startupTask = Task {
|
||||||
|
try? await Task.sleep(nanoseconds: 250_000_000)
|
||||||
|
guard !Task.isCancelled else { return }
|
||||||
|
|
||||||
|
if documentController.hasPendingOpenRequests {
|
||||||
|
await MainActor.run {
|
||||||
|
processPendingOpenRequests()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard !isOpeningDocument else { return }
|
||||||
|
|
||||||
|
if !isRunningTests, ChatViewModel.hasAutosavedSession {
|
||||||
|
let restored = await chatVM.restoreFromAutosave()
|
||||||
|
guard !Task.isCancelled else { return }
|
||||||
|
guard !isOpeningDocument else { return }
|
||||||
|
if restored || documentController.hasPendingOpenRequests {
|
||||||
|
await MainActor.run {
|
||||||
|
processPendingOpenRequests()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard !Task.isCancelled else { return }
|
||||||
|
guard !isOpeningDocument else { return }
|
||||||
|
guard !documentController.hasPendingOpenRequests else {
|
||||||
|
await MainActor.run {
|
||||||
|
processPendingOpenRequests()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard modelManager.currentModel == nil else { return }
|
||||||
|
|
||||||
|
let modelId = Preferences.defaultModelId ?? Preferences.lastModelId ?? ModelConfig.default.id
|
||||||
|
if let config = ModelConfig.availableModels.first(where: { $0.id == modelId }) {
|
||||||
|
await modelManager.loadModel(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func openDocument(at url: URL, skipUnsavedCheck: Bool = false) async {
|
private func openDocument(at url: URL, skipUnsavedCheck: Bool = false) async {
|
||||||
if !skipUnsavedCheck {
|
if !skipUnsavedCheck {
|
||||||
let shouldContinue = confirmDiscardUnsavedChanges(
|
let shouldContinue = confirmDiscardUnsavedChanges(
|
||||||
@@ -390,6 +435,10 @@ struct ContentView: View {
|
|||||||
guard shouldContinue else { return }
|
guard shouldContinue else { return }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startupTask?.cancel()
|
||||||
|
isOpeningDocument = true
|
||||||
|
defer { isOpeningDocument = false }
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await chatVM?.loadDocument(from: url)
|
try await chatVM?.loadDocument(from: url)
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -46,15 +46,6 @@ struct MLXServerApp: App {
|
|||||||
.environment(documentController)
|
.environment(documentController)
|
||||||
.environment(modelManager)
|
.environment(modelManager)
|
||||||
.environment(sceneStore)
|
.environment(sceneStore)
|
||||||
.task {
|
|
||||||
guard !documentController.hasPendingOpenRequests else { return }
|
|
||||||
guard !ChatViewModel.hasAutosavedSession else { return }
|
|
||||||
// Auto-load: configured default → last used → built-in default
|
|
||||||
let modelId = Preferences.defaultModelId ?? Preferences.lastModelId ?? ModelConfig.default.id
|
|
||||||
if let config = ModelConfig.availableModels.first(where: { $0.id == modelId }) {
|
|
||||||
await modelManager.loadModel(config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.windowStyle(.titleBar)
|
.windowStyle(.titleBar)
|
||||||
.defaultSize(width: 800, height: 700)
|
.defaultSize(width: 800, height: 700)
|
||||||
|
|||||||
@@ -34,6 +34,22 @@ final class ModelManager {
|
|||||||
|
|
||||||
private var idleTimer: Timer?
|
private var idleTimer: Timer?
|
||||||
private(set) var lastUsed: Date?
|
private(set) var lastUsed: Date?
|
||||||
|
private var latestLoadRequestID = UUID()
|
||||||
|
|
||||||
|
private func clearLoadedState() {
|
||||||
|
idleTimer?.invalidate()
|
||||||
|
idleTimer = nil
|
||||||
|
lastUsed = nil
|
||||||
|
modelContainer = nil
|
||||||
|
currentModel = nil
|
||||||
|
isLoading = false
|
||||||
|
isDownloading = false
|
||||||
|
downloadProgress = 0
|
||||||
|
loadingModelName = ""
|
||||||
|
downloadFilesTotal = 0
|
||||||
|
downloadFilesCompleted = 0
|
||||||
|
downloadSpeed = 0
|
||||||
|
}
|
||||||
|
|
||||||
/// Load a model, unloading the current one first.
|
/// Load a model, unloading the current one first.
|
||||||
/// Prefers the local snapshot from ~/.cache/huggingface/hub/ (shared with the Python server).
|
/// Prefers the local snapshot from ~/.cache/huggingface/hub/ (shared with the Python server).
|
||||||
@@ -43,7 +59,10 @@ final class ModelManager {
|
|||||||
return // already loaded
|
return // already loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
unloadModel()
|
let requestID = UUID()
|
||||||
|
latestLoadRequestID = requestID
|
||||||
|
clearLoadedState()
|
||||||
|
MLX.GPU.clearCache()
|
||||||
isLoading = true
|
isLoading = true
|
||||||
downloadProgress = 0
|
downloadProgress = 0
|
||||||
loadingModelName = config.displayName
|
loadingModelName = config.displayName
|
||||||
@@ -94,15 +113,18 @@ final class ModelManager {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard latestLoadRequestID == requestID else { return }
|
||||||
self.isDownloading = false
|
self.isDownloading = false
|
||||||
self.modelContainer = container
|
self.modelContainer = container
|
||||||
self.currentModel = config
|
self.currentModel = config
|
||||||
touchActivity()
|
touchActivity()
|
||||||
} catch {
|
} catch {
|
||||||
|
guard latestLoadRequestID == requestID else { return }
|
||||||
self.isDownloading = false
|
self.isDownloading = false
|
||||||
self.errorMessage = "Failed to load model: \(error.localizedDescription)"
|
self.errorMessage = "Failed to load model: \(error.localizedDescription)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard latestLoadRequestID == requestID else { return }
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,11 +137,8 @@ final class ModelManager {
|
|||||||
|
|
||||||
/// Unload the current model and free GPU memory.
|
/// Unload the current model and free GPU memory.
|
||||||
func unloadModel() {
|
func unloadModel() {
|
||||||
idleTimer?.invalidate()
|
latestLoadRequestID = UUID()
|
||||||
idleTimer = nil
|
clearLoadedState()
|
||||||
lastUsed = nil
|
|
||||||
modelContainer = nil
|
|
||||||
currentModel = nil
|
|
||||||
MLX.GPU.clearCache()
|
MLX.GPU.clearCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user