mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-09 11:17:04 +08:00
- A redirect followed for a custom base URL also drops the key headers of providers that do not use Authorization (x-api-key, x-goog-api-key, api-key) when it goes to another origin. - A second Enter or click while a message is being prepared (attachments read, diagram exported) no longer sends it twice. - The admin Test on the deployment's own endpoints (EdgeOne, the server's keyless Ollama, an address on the server's network) counts toward the quota like a chat; the chat and the Test share one rule for it. The Test of an Azure entry set up by AZURE_RESOURCE_NAME only goes where chat goes. - EdgeOne's function is called at the site root again, as on main: EdgeOne serves edge functions there, outside Next's base path. - MCP History: the state before a write is kept unless the browser saved no change of the user's since the last server write (draw.io's sync copy of it adds no entry), and the dedupe compares the exact text again, so a change of page size or other settings only is its own version. - MCP: an edit keeps untouched labels as draw.io shows them (a literal line break in an attribute is a space); a new document of empty pages the user named is auto-saved; load_diagram reads only regular files, so a pipe cannot hold up the other write tools; the preview does not load back its own push still on its way (an undo made meanwhile is saved). - Two overlapping saves of a new chat no longer reload the canvas from the older copy. - At most three screenshot checks per user turn, passed or failed, as documented. - Desktop: the main window navigates only within the app (draw.io stays in its frame); a presets file that is not JSON and cannot be moved aside is not overwritten. - A last self-closing cell with a raw "<" in a value is not taken for cut off output. - README: Material Design shapes load their icons from fonts.gstatic.com.
141 lines
4.3 KiB
TypeScript
141 lines
4.3 KiB
TypeScript
import { app, BrowserWindow, dialog, shell } from "electron"
|
|
import { buildAppMenu } from "./app-menu"
|
|
import { applyCurrentPresetToEnv } from "./config-manager"
|
|
import { loadEnvFile } from "./env-loader"
|
|
import { registerIpcHandlers } from "./ipc-handlers"
|
|
import { startNextServer, stopNextServer } from "./next-server"
|
|
import { applyProxyToEnv } from "./proxy-manager"
|
|
import { registerSettingsWindowHandlers } from "./settings-window"
|
|
import {
|
|
createWindow,
|
|
getAppUrl,
|
|
getMainWindow,
|
|
isAppUrl,
|
|
} from "./window-manager"
|
|
|
|
// Single instance lock
|
|
const gotTheLock = app.requestSingleInstanceLock()
|
|
|
|
if (!gotTheLock) {
|
|
app.quit()
|
|
} else {
|
|
app.on("second-instance", () => {
|
|
const mainWindow = getMainWindow()
|
|
if (mainWindow) {
|
|
if (mainWindow.isMinimized()) mainWindow.restore()
|
|
mainWindow.focus()
|
|
}
|
|
})
|
|
|
|
// Load environment variables from .env files
|
|
loadEnvFile()
|
|
|
|
// Apply proxy settings from saved config
|
|
applyProxyToEnv()
|
|
|
|
const isDev = !app.isPackaged
|
|
|
|
app.whenReady().then(async () => {
|
|
// Apply saved preset environment variables (overrides .env).
|
|
// Must run after ready: on Windows and Linux safeStorage can't
|
|
// decrypt the API key before that.
|
|
applyCurrentPresetToEnv()
|
|
|
|
// Register IPC handlers
|
|
registerIpcHandlers()
|
|
registerSettingsWindowHandlers()
|
|
|
|
// Build application menu
|
|
buildAppMenu()
|
|
|
|
try {
|
|
let serverUrl: string
|
|
if (isDev) {
|
|
// Development: use the dev server URL
|
|
serverUrl =
|
|
process.env.ELECTRON_DEV_URL || "http://localhost:6002"
|
|
console.log(`Development mode: connecting to ${serverUrl}`)
|
|
} else {
|
|
// Production: start Next.js standalone server
|
|
serverUrl = await startNextServer()
|
|
}
|
|
|
|
// Create main window
|
|
createWindow(serverUrl)
|
|
} catch (error) {
|
|
console.error("Failed to start application:", error)
|
|
dialog.showErrorBox(
|
|
"Startup Error",
|
|
`Failed to start the application: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
)
|
|
app.quit()
|
|
}
|
|
|
|
app.on("activate", () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
const appUrl = getAppUrl()
|
|
if (appUrl) {
|
|
createWindow(appUrl)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
app.on("window-all-closed", () => {
|
|
if (process.platform !== "darwin") {
|
|
stopNextServer()
|
|
app.quit()
|
|
}
|
|
})
|
|
|
|
app.on("before-quit", () => {
|
|
stopNextServer()
|
|
})
|
|
|
|
// Pages allowed inside app windows: the app server and draw.io
|
|
const isInAppUrl = (url: string): boolean => {
|
|
if (isAppUrl(url)) return true
|
|
try {
|
|
const { hostname } = new URL(url)
|
|
return ["diagrams.net", "draw.io"].some(
|
|
(domain) =>
|
|
hostname === domain || hostname.endsWith(`.${domain}`),
|
|
)
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
const isWebUrl = (url: string): boolean =>
|
|
url.startsWith("http://") || url.startsWith("https://")
|
|
|
|
// Open external links in default browser
|
|
app.on("web-contents-created", (_, contents) => {
|
|
contents.setWindowOpenHandler(({ url }) => {
|
|
if (isInAppUrl(url)) {
|
|
return { action: "allow" }
|
|
}
|
|
// Open other links in external browser
|
|
if (isWebUrl(url)) {
|
|
shell.openExternal(url)
|
|
return { action: "deny" }
|
|
}
|
|
return { action: "allow" }
|
|
})
|
|
|
|
// Clicking a plain link would otherwise replace the app page with
|
|
// an external site that keeps the preload API. Only the page
|
|
// itself may navigate there; draw.io stays in its frame (this event
|
|
// is for the main frame only)
|
|
contents.on("will-navigate", (event) => {
|
|
if (isAppUrl(event.url)) {
|
|
return
|
|
}
|
|
event.preventDefault()
|
|
if (isWebUrl(event.url)) {
|
|
shell.openExternal(event.url)
|
|
}
|
|
})
|
|
})
|
|
}
|