fix: log Editable SVG saves, use local dates in file names, drop unused Electron dialogs

- /api/log-save accepted only drawio, png and svg, so saving as Editable
  SVG (xmlsvg) got a 400 and was never recorded in Langfuse
- The default download name and the template export name used the UTC
  date, which is the previous day on mornings in East Asia
- Remove the Electron openFile/saveFile IPC handlers, their preload
  bindings and types; nothing in the app calls them
This commit is contained in:
dayuan.jiang
2026-10-04 11:52:53 +09:00
parent 899924ba98
commit 289e6bc25f
7 changed files with 48 additions and 74 deletions
+1 -1
View File
@@ -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(),
})
+2 -3
View File
@@ -642,9 +642,8 @@ export const ChatInput = forwardRef<ChatInputRef, ChatInputProps>(
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 && (
<UrlInputDialog
+2 -1
View File
@@ -224,7 +224,8 @@ export function TemplatePanel({
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = `templates-${new Date().toISOString().split("T")[0]}.json`
// Local date as YYYY-MM-DD (toISOString would give UTC)
a.download = `templates-${new Date().toLocaleDateString("sv-SE")}.json`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
-4
View File
@@ -60,10 +60,6 @@ declare global {
maximize: () => void
/** Close the window */
close: () => void
/** Open file dialog and return file path */
openFile: () => Promise<string | null>
/** Save data to file via save dialog */
saveFile: (data: string) => Promise<boolean>
/** Get proxy configuration */
getProxy: () => Promise<ProxyConfig>
/** Set proxy configuration (saves and restarts server) */
+1 -61
View File
@@ -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", () => {
-4
View File
@@ -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 }) =>
+42
View File
@@ -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)
})
})