mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 22:32:27 +08:00
Compare commits
6 Commits
fix-aichat
...
3e053bc904
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e053bc904 | ||
|
|
037f32973a | ||
|
|
7bdc1fe612 | ||
|
|
03ac9a79de | ||
|
|
f97934d6e0 | ||
|
|
73a36cf9de |
@@ -28,6 +28,8 @@ export default function Home() {
|
||||
} = useDiagram()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
// Extract current language from pathname (e.g., "/zh/about" → "zh")
|
||||
const currentLang = (pathname.split("/")[1] || i18n.defaultLocale) as Locale
|
||||
const [isMobile, setIsMobile] = useState(false)
|
||||
const [isChatVisible, setIsChatVisible] = useState(true)
|
||||
const [drawioUi, setDrawioUi] = useState<"min" | "sketch">("min")
|
||||
@@ -207,7 +209,7 @@ export default function Home() {
|
||||
<div className="h-full rounded-xl overflow-hidden shadow-soft-lg border border-border/30">
|
||||
{isLoaded ? (
|
||||
<DrawIoEmbed
|
||||
key={`${drawioUi}-${darkMode}`}
|
||||
key={`${drawioUi}-${darkMode}-${currentLang}`}
|
||||
ref={drawioRef}
|
||||
onExport={handleDiagramExport}
|
||||
onLoad={onDrawioLoad}
|
||||
@@ -220,6 +222,7 @@ export default function Home() {
|
||||
saveAndExit: false,
|
||||
noExitBtn: true,
|
||||
dark: darkMode,
|
||||
lang: currentLang,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -12,7 +12,11 @@ import fs from "fs/promises"
|
||||
import { jsonrepair } from "jsonrepair"
|
||||
import path from "path"
|
||||
import { z } from "zod"
|
||||
import { getAIModel, supportsPromptCaching } from "@/lib/ai-providers"
|
||||
import {
|
||||
getAIModel,
|
||||
supportsImageInput,
|
||||
supportsPromptCaching,
|
||||
} from "@/lib/ai-providers"
|
||||
import { findCachedResponse } from "@/lib/cached-responses"
|
||||
import {
|
||||
checkAndIncrementRequest,
|
||||
@@ -295,6 +299,17 @@ async function handleChatRequest(req: Request): Promise<Response> {
|
||||
lastUserMessage?.parts?.filter((part: any) => part.type === "file") ||
|
||||
[]
|
||||
|
||||
// Check if user is sending images to a model that doesn't support them
|
||||
// AI SDK silently drops unsupported parts, so we need to catch this early
|
||||
if (fileParts.length > 0 && !supportsImageInput(modelId)) {
|
||||
return Response.json(
|
||||
{
|
||||
error: `The model "${modelId}" does not support image input. Please use a vision-capable model (e.g., GPT-4o, Claude, Gemini) or remove the image.`,
|
||||
},
|
||||
{ status: 400 },
|
||||
)
|
||||
}
|
||||
|
||||
// User input only - XML is now in a separate cached system message
|
||||
const formattedUserInput = `User input:
|
||||
"""md
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
|
||||
@@ -283,7 +283,7 @@ export function ChatMessageDisplay({
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
setCopyState(messageId, isToolCall, true)
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
// Fallback for non-secure contexts (HTTP) or permission denied
|
||||
const textarea = document.createElement("textarea")
|
||||
textarea.value = text
|
||||
|
||||
@@ -905,7 +905,6 @@ export default function ChatPanel({
|
||||
className="text-sm font-medium text-muted-foreground mt-8 tracking-wide"
|
||||
style={{
|
||||
writingMode: "vertical-rl",
|
||||
transform: "rotate(180deg)",
|
||||
}}
|
||||
>
|
||||
{dict.nav.aiChat}
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
Zap,
|
||||
} from "lucide-react"
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { toast } from "sonner"
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -517,7 +516,6 @@ export function ModelConfigDialog({
|
||||
{/* Provider Details (Right Panel) */}
|
||||
<div className="flex-1 min-w-0 flex flex-col overflow-auto [&::-webkit-scrollbar]:hidden ">
|
||||
{selectedProvider ? (
|
||||
<>
|
||||
<ScrollArea className="flex-1" ref={scrollRef}>
|
||||
<div className="p-6 space-y-8">
|
||||
{/* Provider Header */}
|
||||
@@ -559,10 +557,7 @@ export function ModelConfigDialog({
|
||||
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-success-muted text-success">
|
||||
<Check className="h-3.5 w-3.5 animate-check-pop" />
|
||||
<span className="text-xs font-medium">
|
||||
{
|
||||
dict.modelConfig
|
||||
.verified
|
||||
}
|
||||
{dict.modelConfig.verified}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -575,18 +570,13 @@ export function ModelConfigDialog({
|
||||
className="text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-1.5" />
|
||||
{
|
||||
dict.modelConfig
|
||||
.deleteProvider
|
||||
}
|
||||
{dict.modelConfig.deleteProvider}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Configuration Section */}
|
||||
<ConfigSection
|
||||
title={
|
||||
dict.modelConfig.configuration
|
||||
}
|
||||
title={dict.modelConfig.configuration}
|
||||
icon={Settings2}
|
||||
>
|
||||
<ConfigCard>
|
||||
@@ -636,8 +626,7 @@ export function ModelConfigDialog({
|
||||
>
|
||||
<Key className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
{
|
||||
dict
|
||||
.modelConfig
|
||||
dict.modelConfig
|
||||
.awsAccessKeyId
|
||||
}
|
||||
</Label>
|
||||
@@ -672,8 +661,7 @@ export function ModelConfigDialog({
|
||||
>
|
||||
<Key className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
{
|
||||
dict
|
||||
.modelConfig
|
||||
dict.modelConfig
|
||||
.awsSecretAccessKey
|
||||
}
|
||||
</Label>
|
||||
@@ -689,13 +677,10 @@ export function ModelConfigDialog({
|
||||
selectedProvider.awsSecretAccessKey ||
|
||||
""
|
||||
}
|
||||
onChange={(
|
||||
e,
|
||||
) =>
|
||||
onChange={(e) =>
|
||||
handleProviderUpdate(
|
||||
"awsSecretAccessKey",
|
||||
e
|
||||
.target
|
||||
e.target
|
||||
.value,
|
||||
)
|
||||
}
|
||||
@@ -737,8 +722,7 @@ export function ModelConfigDialog({
|
||||
>
|
||||
<Link2 className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
{
|
||||
dict
|
||||
.modelConfig
|
||||
dict.modelConfig
|
||||
.awsRegion
|
||||
}
|
||||
</Label>
|
||||
@@ -817,8 +801,7 @@ export function ModelConfigDialog({
|
||||
</SelectItem>
|
||||
<SelectItem value="sa-east-1">
|
||||
sa-east-1
|
||||
(São
|
||||
Paulo)
|
||||
(São Paulo)
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -865,8 +848,7 @@ export function ModelConfigDialog({
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
dict
|
||||
.modelConfig
|
||||
dict.modelConfig
|
||||
.test
|
||||
)}
|
||||
</Button>
|
||||
@@ -922,8 +904,7 @@ export function ModelConfigDialog({
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
dict
|
||||
.modelConfig
|
||||
dict.modelConfig
|
||||
.test
|
||||
)}
|
||||
</Button>
|
||||
@@ -949,8 +930,7 @@ export function ModelConfigDialog({
|
||||
>
|
||||
<Key className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
{
|
||||
dict
|
||||
.modelConfig
|
||||
dict.modelConfig
|
||||
.apiKey
|
||||
}
|
||||
</Label>
|
||||
@@ -1067,8 +1047,7 @@ export function ModelConfigDialog({
|
||||
>
|
||||
<Link2 className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
{
|
||||
dict
|
||||
.modelConfig
|
||||
dict.modelConfig
|
||||
.baseUrl
|
||||
}
|
||||
<span className="text-muted-foreground font-normal">
|
||||
@@ -1098,8 +1077,7 @@ export function ModelConfigDialog({
|
||||
.provider
|
||||
]
|
||||
.defaultBaseUrl ||
|
||||
dict
|
||||
.modelConfig
|
||||
dict.modelConfig
|
||||
.customEndpoint
|
||||
}
|
||||
className="h-9 rounded-xl font-mono text-xs"
|
||||
@@ -1122,13 +1100,10 @@ export function ModelConfigDialog({
|
||||
dict.modelConfig
|
||||
.customModelId
|
||||
}
|
||||
value={
|
||||
customModelInput
|
||||
}
|
||||
value={customModelInput}
|
||||
onChange={(e) => {
|
||||
setCustomModelInput(
|
||||
e.target
|
||||
.value,
|
||||
e.target.value,
|
||||
)
|
||||
if (
|
||||
duplicateError
|
||||
@@ -1148,9 +1123,7 @@ export function ModelConfigDialog({
|
||||
handleAddModel(
|
||||
customModelInput.trim(),
|
||||
)
|
||||
if (
|
||||
success
|
||||
) {
|
||||
if (success) {
|
||||
setCustomModelInput(
|
||||
"",
|
||||
)
|
||||
@@ -1195,9 +1168,7 @@ export function ModelConfigDialog({
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Select
|
||||
onValueChange={(
|
||||
value,
|
||||
) => {
|
||||
onValueChange={(value) => {
|
||||
if (value) {
|
||||
handleAddModel(
|
||||
value,
|
||||
@@ -1233,9 +1204,7 @@ export function ModelConfigDialog({
|
||||
}
|
||||
className="font-mono text-xs"
|
||||
>
|
||||
{
|
||||
modelId
|
||||
}
|
||||
{modelId}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
@@ -1246,8 +1215,8 @@ export function ModelConfigDialog({
|
||||
>
|
||||
{/* Model List */}
|
||||
<div className="rounded-2xl border border-border-subtle bg-surface-2/30 overflow-hidden min-h-[120px]">
|
||||
{selectedProvider.models
|
||||
.length === 0 ? (
|
||||
{selectedProvider.models.length ===
|
||||
0 ? (
|
||||
<div className="p-6 text-center h-full flex flex-col items-center justify-center">
|
||||
<div className="inline-flex items-center justify-center w-10 h-10 rounded-full bg-surface-2 mb-3">
|
||||
<Sparkles className="h-5 w-5 text-muted-foreground" />
|
||||
@@ -1264,9 +1233,7 @@ export function ModelConfigDialog({
|
||||
{selectedProvider.models.map(
|
||||
(model, index) => (
|
||||
<div
|
||||
key={
|
||||
model.id
|
||||
}
|
||||
key={model.id}
|
||||
className={cn(
|
||||
"transition-colors duration-150 hover:bg-interactive-hover/50",
|
||||
)}
|
||||
@@ -1500,7 +1467,6 @@ export function ModelConfigDialog({
|
||||
</ConfigSection>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</>
|
||||
) : (
|
||||
<div className="h-full flex flex-col items-center justify-center p-8 text-center">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-surface-2 mb-4">
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
flattenModels,
|
||||
type ModelConfig,
|
||||
type MultiModelConfig,
|
||||
PROVIDER_INFO,
|
||||
type ProviderConfig,
|
||||
type ProviderName,
|
||||
} from "@/lib/types/model-config"
|
||||
|
||||
@@ -786,7 +786,7 @@ export function getAIModel(overrides?: ClientOverrides): ModelConfig {
|
||||
`data: ${JSON.stringify(data)}\n\n`,
|
||||
),
|
||||
)
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
// If parsing fails, forward the original message to avoid breaking the stream.
|
||||
controller.enqueue(
|
||||
new TextEncoder().encode(
|
||||
@@ -906,3 +906,34 @@ export function supportsPromptCaching(modelId: string): boolean {
|
||||
modelId.startsWith("eu.anthropic")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a model supports image/vision input.
|
||||
* Some models silently drop image parts without error (AI SDK warning only).
|
||||
*/
|
||||
export function supportsImageInput(modelId: string): boolean {
|
||||
const lowerModelId = modelId.toLowerCase()
|
||||
|
||||
// Helper to check if model has vision capability indicator
|
||||
const hasVisionIndicator =
|
||||
lowerModelId.includes("vision") || lowerModelId.includes("vl")
|
||||
|
||||
// Models that DON'T support image/vision input (unless vision variant)
|
||||
// Kimi K2 models don't support images
|
||||
if (lowerModelId.includes("kimi") && !hasVisionIndicator) {
|
||||
return false
|
||||
}
|
||||
|
||||
// DeepSeek text models (not vision variants)
|
||||
if (lowerModelId.includes("deepseek") && !hasVisionIndicator) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Qwen text models (not vision variants like qwen-vl)
|
||||
if (lowerModelId.includes("qwen") && !hasVisionIndicator) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Default: assume model supports images
|
||||
return true
|
||||
}
|
||||
|
||||
50
package-lock.json
generated
50
package-lock.json
generated
@@ -93,6 +93,11 @@
|
||||
"typescript": "^5",
|
||||
"wait-on": "^9.0.3",
|
||||
"wrangler": "4.54.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "^4.1.18",
|
||||
"lightningcss": "^1.30.2",
|
||||
"lightningcss-linux-x64-gnu": "^1.30.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@acemir/cssom": {
|
||||
@@ -8714,6 +8719,22 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
|
||||
"integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/postcss": {
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz",
|
||||
@@ -15122,7 +15143,7 @@
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
|
||||
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
@@ -15155,7 +15176,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -15169,6 +15189,26 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
|
||||
"integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lint-staged": {
|
||||
"version": "16.2.7",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz",
|
||||
@@ -17972,9 +18012,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"version": "6.14.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
|
||||
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
|
||||
12
packages/mcp-server/package-lock.json
generated
12
packages/mcp-server/package-lock.json
generated
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@next-ai-drawio/mcp-server",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.11",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@next-ai-drawio/mcp-server",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.11",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.4",
|
||||
"linkedom": "^0.18.0",
|
||||
"open": "^11.0.0",
|
||||
"zod": "^3.24.0"
|
||||
"zod": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"next-ai-drawio-mcp": "dist/index.js"
|
||||
@@ -2051,9 +2051,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.4.tgz",
|
||||
"integrity": "sha512-Zw/uYiiyF6pUT1qmKbZziChgNPRu+ZRneAsMUDU6IwmXdWt5JwcUfy2bvLOCUtz5UniaN/Zx5aFttZYbYc7O/A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-ai-drawio/mcp-server",
|
||||
"version": "0.1.10",
|
||||
"version": "0.1.11",
|
||||
"description": "MCP server for Next AI Draw.io - AI-powered diagram generation with real-time browser preview",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
@@ -39,7 +39,7 @@
|
||||
"@modelcontextprotocol/sdk": "^1.0.4",
|
||||
"linkedom": "^0.18.0",
|
||||
"open": "^11.0.0",
|
||||
"zod": "^3.24.0"
|
||||
"zod": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.0",
|
||||
|
||||
@@ -127,7 +127,12 @@ function cleanupExpiredSessions(): void {
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(cleanupExpiredSessions, 5 * 60 * 1000)
|
||||
const cleanupIntervalId = setInterval(cleanupExpiredSessions, 5 * 60 * 1000)
|
||||
|
||||
export function shutdown(): void {
|
||||
clearInterval(cleanupIntervalId)
|
||||
stopHttpServer()
|
||||
}
|
||||
|
||||
export function getServerPort(): number {
|
||||
return serverPort
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
getState,
|
||||
requestSync,
|
||||
setState,
|
||||
shutdown,
|
||||
startHttpServer,
|
||||
waitForSync,
|
||||
} from "./http-server.js"
|
||||
@@ -47,7 +48,7 @@ import { validateAndFixXml } from "./xml-validation.js"
|
||||
|
||||
// Server configuration
|
||||
const config = {
|
||||
port: parseInt(process.env.PORT || "6002"),
|
||||
port: parseInt(process.env.PORT || "6002", 10),
|
||||
}
|
||||
|
||||
// Session state (single session for simplicity)
|
||||
@@ -618,6 +619,31 @@ server.registerTool(
|
||||
},
|
||||
)
|
||||
|
||||
// Graceful shutdown handler
|
||||
let isShuttingDown = false
|
||||
function gracefulShutdown(reason: string) {
|
||||
if (isShuttingDown) return
|
||||
isShuttingDown = true
|
||||
log.info(`Shutting down: ${reason}`)
|
||||
shutdown()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// Handle stdin close (primary method - works on all platforms including Windows)
|
||||
process.stdin.on("close", () => gracefulShutdown("stdin closed"))
|
||||
process.stdin.on("end", () => gracefulShutdown("stdin ended"))
|
||||
|
||||
// Handle signals (may not work reliably on Windows)
|
||||
process.on("SIGINT", () => gracefulShutdown("SIGINT"))
|
||||
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"))
|
||||
|
||||
// Handle broken pipe (writing to closed stdout)
|
||||
process.stdout.on("error", (err) => {
|
||||
if (err.code === "EPIPE" || err.code === "ERR_STREAM_DESTROYED") {
|
||||
gracefulShutdown("stdout error")
|
||||
}
|
||||
})
|
||||
|
||||
// Start the MCP server
|
||||
async function main() {
|
||||
log.info("Starting MCP server for Next AI Draw.io (embedded mode)...")
|
||||
|
||||
@@ -253,7 +253,7 @@ async function main() {
|
||||
},
|
||||
)
|
||||
console.log("👀 Watching for preset configuration changes...")
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
// File might not exist yet, that's ok
|
||||
setTimeout(setupConfigWatcher, 5000)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user