diff --git a/app/api/log-save/route.ts b/app/api/log-save/route.ts index fc73fb2b..eb30e0fe 100644 --- a/app/api/log-save/route.ts +++ b/app/api/log-save/route.ts @@ -4,7 +4,7 @@ import { getLangfuseClient } from "@/lib/langfuse" const saveSchema = z.object({ filename: z.string().min(1).max(255), - format: z.enum(["drawio", "png", "svg"]), + format: z.enum(["drawio", "png", "svg", "xmlsvg"]), sessionId: z.string().min(1).max(200).optional(), }) diff --git a/components/chat-input.tsx b/components/chat-input.tsx index d0834a68..0f375925 100644 --- a/components/chat-input.tsx +++ b/components/chat-input.tsx @@ -642,9 +642,8 @@ export const ChatInput = forwardRef( dict.save.savedSuccessfully, ) } - defaultFilename={`diagram-${new Date() - .toISOString() - .slice(0, 10)}`} + // Local date as YYYY-MM-DD (toISOString would give UTC) + defaultFilename={`diagram-${new Date().toLocaleDateString("sv-SE")}`} /> {onUrlChange && ( void /** Close the window */ close: () => void - /** Open file dialog and return file path */ - openFile: () => Promise - /** Save data to file via save dialog */ - saveFile: (data: string) => Promise /** Get proxy configuration */ getProxy: () => Promise /** Set proxy configuration (saves and restarts server) */ diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 3e0f4aca..1a7458c1 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -1,10 +1,4 @@ -import { - app, - BrowserWindow, - dialog, - type IpcMainInvokeEvent, - ipcMain, -} from "electron" +import { app, BrowserWindow, type IpcMainInvokeEvent, ipcMain } from "electron" import { rebuildAppMenu, switchPreset } from "./app-menu" import { type ConfigPreset, @@ -104,60 +98,6 @@ export function registerIpcHandlers(): void { win?.close() }) - // ==================== File Dialogs ==================== - - handle("dialog-open-file", async (event) => { - const win = BrowserWindow.fromWebContents(event.sender) - if (!win) return null - - const result = await dialog.showOpenDialog(win, { - properties: ["openFile"], - filters: [ - { name: "Draw.io Files", extensions: ["drawio", "xml"] }, - { name: "All Files", extensions: ["*"] }, - ], - }) - - if (result.canceled || result.filePaths.length === 0) { - return null - } - - // Read the file content - const fs = await import("node:fs/promises") - try { - const content = await fs.readFile(result.filePaths[0], "utf-8") - return content - } catch (error) { - console.error("Failed to read file:", error) - return null - } - }) - - handle("dialog-save-file", async (event, data: string) => { - const win = BrowserWindow.fromWebContents(event.sender) - if (!win || typeof data !== "string") return false - - const result = await dialog.showSaveDialog(win, { - filters: [ - { name: "Draw.io Files", extensions: ["drawio"] }, - { name: "XML Files", extensions: ["xml"] }, - ], - }) - - if (result.canceled || !result.filePath) { - return false - } - - const fs = await import("node:fs/promises") - try { - await fs.writeFile(result.filePath, data, "utf-8") - return true - } catch (error) { - console.error("Failed to save file:", error) - return false - } - }) - // ==================== Config Presets ==================== handle("config-presets:get-all", () => { diff --git a/electron/preload/index.ts b/electron/preload/index.ts index b648ca13..9e42c6b0 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -18,10 +18,6 @@ contextBridge.exposeInMainWorld("electronAPI", { maximize: () => ipcRenderer.send("window-maximize"), close: () => ipcRenderer.send("window-close"), - // File operations - openFile: () => ipcRenderer.invoke("dialog-open-file"), - saveFile: (data: string) => ipcRenderer.invoke("dialog-save-file", data), - // Proxy settings getProxy: () => ipcRenderer.invoke("get-proxy"), setProxy: (config: { httpProxy?: string; httpsProxy?: string }) => diff --git a/tests/unit/log-save-route.test.ts b/tests/unit/log-save-route.test.ts new file mode 100644 index 00000000..e1f61eaf --- /dev/null +++ b/tests/unit/log-save-route.test.ts @@ -0,0 +1,42 @@ +// @vitest-environment node +import { describe, expect, it, vi } from "vitest" +import { POST as logSave } from "@/app/api/log-save/route" + +// A Langfuse client with a trace to attach the save score to +const batch = vi.fn(async () => ({})) +vi.mock("@/lib/langfuse", () => ({ + getLangfuseClient: () => ({ + api: { + trace: { list: async () => ({ data: [{ id: "trace-1" }] }) }, + ingestion: { batch }, + }, + }), +})) + +function post(body: unknown): Request { + return new Request("http://localhost/api/log-save", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }) +} + +describe("POST /api/log-save", () => { + it.each(["drawio", "png", "svg", "xmlsvg"])( + "logs a save in %s format", + async (format) => { + const res = await logSave( + post({ filename: "diagram", format, sessionId: "s1" }), + ) + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ success: true, logged: true }) + }, + ) + + it("rejects an unknown format", async () => { + const res = await logSave( + post({ filename: "diagram", format: "pdf", sessionId: "s1" }), + ) + expect(res.status).toBe(400) + }) +})