diff --git a/oAI/ViewModels/ChatViewModel.swift b/oAI/ViewModels/ChatViewModel.swift index 2aa6d84..c26dc90 100644 --- a/oAI/ViewModels/ChatViewModel.swift +++ b/oAI/ViewModels/ChatViewModel.swift @@ -24,6 +24,7 @@ import Foundation import os import SwiftUI +import UniformTypeIdentifiers @Observable @MainActor @@ -1815,6 +1816,59 @@ Don't narrate future actions ("Let me...") - just use the tools. } } + /// Same export formats as `exportConversation(format:filename:)`, but lets the user pick + /// the name and location via a native save panel instead of always writing to Downloads. + #if os(macOS) + func exportConversationWithSavePanel(format: String, defaultFilename: String) { + let chatMessages = messages.filter { $0.role != .system } + guard !chatMessages.isEmpty else { + showSystemMessage("Nothing to export — no messages") + return + } + + let panel = NSSavePanel() + panel.nameFieldStringValue = defaultFilename + panel.canCreateDirectories = true + panel.allowedContentTypes = { + switch format { + case "html": return [.html] + case "pdf": return [.pdf] + default: return [UTType(filenameExtension: "md") ?? .plainText] + } + }() + if let downloads = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first { + panel.directoryURL = downloads + } + + guard panel.runModal() == .OK, let url = panel.url else { return } + + if format == "pdf" { + let name = currentConversationName ?? "conversation" + Task { @MainActor in + do { + let data = try await ConversationExportService.pdfData(name: name, messages: chatMessages) + try data.write(to: url, options: .atomic) + showSystemMessage("Exported to \(url.path)") + } catch { + showSystemMessage("Export failed: \(error.localizedDescription)") + } + } + return + } + + let content = format == "html" + ? ConversationExportService.html(name: currentConversationName ?? "conversation", messages: chatMessages) + : ConversationExportService.markdown(messages: chatMessages) + + do { + try content.write(to: url, atomically: true, encoding: .utf8) + showSystemMessage("Exported to \(url.path)") + } catch { + showSystemMessage("Export failed: \(error.localizedDescription)") + } + } + #endif + // MARK: - Auto-Save & Background Summarization /// Summarize the current conversation in the background (hidden from user) diff --git a/oAI/oAIApp.swift b/oAI/oAIApp.swift index 79c7035..ac24d0e 100644 --- a/oAI/oAIApp.swift +++ b/oAI/oAIApp.swift @@ -121,21 +121,21 @@ struct oAIApp: App { Button("Export as Markdown…") { let name = chatViewModel.currentConversationName ?? "conversation" let safe = name.components(separatedBy: .whitespacesAndNewlines).joined(separator: "-") - chatViewModel.exportConversation(format: "md", filename: "\(safe).md") + chatViewModel.exportConversationWithSavePanel(format: "md", defaultFilename: "\(safe).md") } .disabled(chatViewModel.messages.filter { $0.role != .system }.isEmpty) Button("Export as HTML…") { let name = chatViewModel.currentConversationName ?? "conversation" let safe = name.components(separatedBy: .whitespacesAndNewlines).joined(separator: "-") - chatViewModel.exportConversation(format: "html", filename: "\(safe).html") + chatViewModel.exportConversationWithSavePanel(format: "html", defaultFilename: "\(safe).html") } .disabled(chatViewModel.messages.filter { $0.role != .system }.isEmpty) Button("Export as PDF…") { let name = chatViewModel.currentConversationName ?? "conversation" let safe = name.components(separatedBy: .whitespacesAndNewlines).joined(separator: "-") - chatViewModel.exportConversation(format: "pdf", filename: "\(safe).pdf") + chatViewModel.exportConversationWithSavePanel(format: "pdf", defaultFilename: "\(safe).pdf") } .disabled(chatViewModel.messages.filter { $0.role != .system }.isEmpty) }