import {download, multipart, post} from "./api.js"; export function createWorkspaceTransfer({conversation, getChatById, setNotice, promptPopup, confirmPopup, setChatLoading, loadChatSessions}) { async function exportCurrentChat() { const chat = getChatById(conversation.activeChatId); if (!chat || !conversation.currentMessages.length) return; const format = await promptPopup({ eyebrow: "Export", title: "Export format", description: "Use txt, md, or json.", label: "Format", value: "md", placeholder: "md", confirmLabel: "Download" }); if (format === null) return; const normalized = ["txt", "md", "json"].includes(String(format || "").trim().toLowerCase()) ? String(format).trim().toLowerCase() : "md"; const response = await download(`/exports/chats/${chat.id}?format=${normalized}`); if (response.error) return setNotice(response.error, "error"); const {blob} = response; const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = `${(chat.title.replace(/[^a-z0-9-_]+/gi, "-").replace(/^-+|-+$/g, "") || "chat")}.${normalized}`; document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(url); setNotice(`Chat exported as ${normalized}.`, "success"); } async function backupWorkspace() { const response = await download("/exports/workspace/archive"); if (response.error) return setNotice(response.error, "error"); const {blob} = response; const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = `workspace-backup-${new Date().toISOString().slice(0, 10)}.tar.gz`; document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(url); setNotice("Workspace archive downloaded with images.", "success"); } function formatImportPreview(preview, warnings = []) { const counts = [ `${preview.personas || 0} persona${preview.personas === 1 ? "" : "s"}`, `${preview.chats || 0} chat${preview.chats === 1 ? "" : "s"}`, `${preview.messages || 0} message${preview.messages === 1 ? "" : "s"}`, `${preview.templates || 0} template${preview.templates === 1 ? "" : "s"}` ].join(", "); const warningText = warnings.length ? ` Warnings: ${warnings.slice(0, 4).join("; ")}${warnings.length > 4 ? "; ..." : ""}` : ""; return `Import preview: ${counts}.${warningText}`; } async function importWorkspaceFromFile(file) { if (!file) return; setChatLoading(true, "Reading workspace backup..."); const isArchive = file.name.toLowerCase().endsWith(".tar.gz"); if (isArchive) { const submit = async (path, allowDuplicates = false) => { const body = new FormData(); body.append("archive", file); if (allowDuplicates) body.append("allowDuplicates", "true"); return multipart(path, body); }; try { const preview = await submit("/imports/workspace/archive/preview"); setChatLoading(false); if (preview.error || preview.ok === false) return setNotice((preview.errors || []).slice(0, 4).join("; ") || preview.error || "Invalid archive.", "error"); const confirmed = await confirmPopup({eyebrow: "Workspace import", title: "Review import", description: formatImportPreview(preview.preview || {}, preview.warnings || []), confirmLabel: "Import"}); if (!confirmed) return; setChatLoading(true, "Importing workspace and images..."); const result = await submit("/imports/workspace/archive", Boolean(preview.preview?.duplicates?.length)); if (result.error) return setNotice(result.error, "error"); await loadChatSessions(); setNotice(`Workspace imported: ${result.imported.chats} chats with their images.`, "success"); } catch { setNotice("Workspace archive could not be imported.", "error"); } finally { setChatLoading(false); } return; } let workspace; try { workspace = JSON.parse(await file.text()); } catch { setChatLoading(false); return setNotice("Import failed: choose a valid JSON workspace backup.", "error"); } const preview = await post("/imports/workspace/preview", { workspace }); if (preview.error || preview.ok === false) { setChatLoading(false); const detail = (preview.errors || []).slice(0, 4).join("; "); return setNotice(detail || preview.error || "Workspace import validation failed.", "error"); } setChatLoading(false); const confirmed = await confirmPopup({ eyebrow: "Workspace import", title: "Review import", description: formatImportPreview(preview.preview || {}, preview.warnings || []), confirmLabel: "Import" }); if (!confirmed) return; setChatLoading(true, "Importing workspace..."); const imported = await post("/imports/workspace", { workspace, allowDuplicates: Boolean(preview.preview?.duplicates?.length) }); if (imported.error) { setChatLoading(false); const detail = (imported.errors || imported.warnings || []).slice(0, 4).join("; "); return setNotice(detail || imported.error, "error"); } await loadChatSessions(); setChatLoading(false); setNotice(`Workspace imported: ${imported.imported.personas} personas, ${imported.imported.chats} chats, ${imported.imported.templates} templates.`, "success"); } return {exportCurrentChat, backupWorkspace, importWorkspaceFromFile}; }