Let File > Export as HTML/PDF/Markdown choose name and location
Previously always silently wrote to Downloads with a fixed name — no way to pick where it goes or rename it. Added exportConversationWithSavePanel(format:defaultFilename:), which shows a native NSSavePanel (defaulting to Downloads + the same filename as before) before writing. Wired into all three File menu export buttons. The /export slash command and the conversation list's per-row Export submenu are unchanged (still write straight to Downloads) — the slash command already takes an explicit filename argument inline, and the row export is meant as a quick one-click action rather than a save-as workflow.
This commit is contained in:
@@ -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)
|
||||
|
||||
+3
-3
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user