mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-13 04:59:51 +08:00
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:
@@ -4,7 +4,7 @@ import { getLangfuseClient } from "@/lib/langfuse"
|
|||||||
|
|
||||||
const saveSchema = z.object({
|
const saveSchema = z.object({
|
||||||
filename: z.string().min(1).max(255),
|
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(),
|
sessionId: z.string().min(1).max(200).optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -642,9 +642,8 @@ export const ChatInput = forwardRef<ChatInputRef, ChatInputProps>(
|
|||||||
dict.save.savedSuccessfully,
|
dict.save.savedSuccessfully,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
defaultFilename={`diagram-${new Date()
|
// Local date as YYYY-MM-DD (toISOString would give UTC)
|
||||||
.toISOString()
|
defaultFilename={`diagram-${new Date().toLocaleDateString("sv-SE")}`}
|
||||||
.slice(0, 10)}`}
|
|
||||||
/>
|
/>
|
||||||
{onUrlChange && (
|
{onUrlChange && (
|
||||||
<UrlInputDialog
|
<UrlInputDialog
|
||||||
|
|||||||
@@ -224,7 +224,8 @@ export function TemplatePanel({
|
|||||||
const url = URL.createObjectURL(blob)
|
const url = URL.createObjectURL(blob)
|
||||||
const a = document.createElement("a")
|
const a = document.createElement("a")
|
||||||
a.href = url
|
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)
|
document.body.appendChild(a)
|
||||||
a.click()
|
a.click()
|
||||||
document.body.removeChild(a)
|
document.body.removeChild(a)
|
||||||
|
|||||||
Vendored
-4
@@ -60,10 +60,6 @@ declare global {
|
|||||||
maximize: () => void
|
maximize: () => void
|
||||||
/** Close the window */
|
/** Close the window */
|
||||||
close: () => void
|
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 */
|
/** Get proxy configuration */
|
||||||
getProxy: () => Promise<ProxyConfig>
|
getProxy: () => Promise<ProxyConfig>
|
||||||
/** Set proxy configuration (saves and restarts server) */
|
/** Set proxy configuration (saves and restarts server) */
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import {
|
import { app, BrowserWindow, type IpcMainInvokeEvent, ipcMain } from "electron"
|
||||||
app,
|
|
||||||
BrowserWindow,
|
|
||||||
dialog,
|
|
||||||
type IpcMainInvokeEvent,
|
|
||||||
ipcMain,
|
|
||||||
} from "electron"
|
|
||||||
import { rebuildAppMenu, switchPreset } from "./app-menu"
|
import { rebuildAppMenu, switchPreset } from "./app-menu"
|
||||||
import {
|
import {
|
||||||
type ConfigPreset,
|
type ConfigPreset,
|
||||||
@@ -104,60 +98,6 @@ export function registerIpcHandlers(): void {
|
|||||||
win?.close()
|
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 ====================
|
// ==================== Config Presets ====================
|
||||||
|
|
||||||
handle("config-presets:get-all", () => {
|
handle("config-presets:get-all", () => {
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
|||||||
maximize: () => ipcRenderer.send("window-maximize"),
|
maximize: () => ipcRenderer.send("window-maximize"),
|
||||||
close: () => ipcRenderer.send("window-close"),
|
close: () => ipcRenderer.send("window-close"),
|
||||||
|
|
||||||
// File operations
|
|
||||||
openFile: () => ipcRenderer.invoke("dialog-open-file"),
|
|
||||||
saveFile: (data: string) => ipcRenderer.invoke("dialog-save-file", data),
|
|
||||||
|
|
||||||
// Proxy settings
|
// Proxy settings
|
||||||
getProxy: () => ipcRenderer.invoke("get-proxy"),
|
getProxy: () => ipcRenderer.invoke("get-proxy"),
|
||||||
setProxy: (config: { httpProxy?: string; httpsProxy?: string }) =>
|
setProxy: (config: { httpProxy?: string; httpsProxy?: string }) =>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user