Compare commits

...

7 Commits

Author SHA1 Message Date
dayuan.jiang
eb21d7e3fa fix(docker): fix invalid YAML syntax in docker-compose.yml
Empty environment mapping caused validation error:
'services.next-ai-draw-io.environment must be a mapping'
2026-01-03 13:37:10 +09:00
renovate[bot]
8c1cc19d94 fix(deps): update dependency ollama-ai-provider-v2 to v2 (#497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-03 13:32:49 +09:00
renovate[bot]
03c3ae6d5b chore(deps): update core framework packages (#495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-03 12:16:46 +09:00
renovate[bot]
ddde0654a6 chore(deps): update minor and patch dependencies (#496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-03 12:16:02 +09:00
Yu Peng
bc5709267c fix(mcp): prevent stuck spinner by initializing blank session state (#494)
* fix(mcp): initialize blank state to avoid stuck spinner

* style: fix formatting

---------

Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2026-01-03 12:05:38 +09:00
Dayuan Jiang
6fbc7b340f fix: move toast notifications to bottom-left (#489) 2026-01-01 22:08:05 +09:00
Dayuan Jiang
3c8f420c3c docs: add Cline MCP configuration instructions (#488) 2026-01-01 21:47:46 +09:00
6 changed files with 3378 additions and 301 deletions

View File

@@ -943,10 +943,9 @@ export default function ChatPanel({
)} )}
> >
<Toaster <Toaster
position="bottom-center" position="bottom-left"
richColors richColors
expand expand
style={{ position: "absolute" }}
toastOptions={{ toastOptions={{
style: { style: {
maxWidth: "480px", maxWidth: "480px",

View File

@@ -11,7 +11,7 @@ services:
# - NEXT_PUBLIC_BASE_PATH=/nextaidrawio # - NEXT_PUBLIC_BASE_PATH=/nextaidrawio
ports: ["3000:3000"] ports: ["3000:3000"]
env_file: .env env_file: .env
environment: # environment:
# For subdirectory deployment, uncomment and set your path: # # For subdirectory deployment, uncomment and set your path:
# NEXT_PUBLIC_BASE_PATH: /nextaidrawio # NEXT_PUBLIC_BASE_PATH: /nextaidrawio
depends_on: [drawio] depends_on: [drawio]

3627
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -71,7 +71,7 @@
"motion": "^12.23.25", "motion": "^12.23.25",
"negotiator": "^1.0.0", "negotiator": "^1.0.0",
"next": "^16.0.7", "next": "^16.0.7",
"ollama-ai-provider-v2": "^1.5.4", "ollama-ai-provider-v2": "^2.0.0",
"pako": "^2.1.0", "pako": "^2.1.0",
"prism-react-renderer": "^2.4.1", "prism-react-renderer": "^2.4.1",
"react": "^19.1.2", "react": "^19.1.2",

View File

@@ -64,6 +64,24 @@ Add to Cursor MCP config (`~/.cursor/mcp.json`):
} }
``` ```
### Cline (VS Code Extension)
1. Click the **MCP Servers** icon in Cline's top menu bar
2. Select the **Configure** tab
3. Click **Configure MCP Servers** to edit `cline_mcp_settings.json`
4. Add the drawio server:
```json
{
"mcpServers": {
"drawio": {
"command": "npx",
"args": ["@next-ai-drawio/mcp-server@latest"]
}
}
}
```
### Claude Code CLI ### Claude Code CLI
```bash ```bash

View File

@@ -29,12 +29,29 @@ function getOrigin(url: string): string {
const DRAWIO_ORIGIN = getOrigin(DRAWIO_BASE_URL) const DRAWIO_ORIGIN = getOrigin(DRAWIO_BASE_URL)
// Minimal blank diagram used to bootstrap new sessions.
// This avoids the draw.io embed spinner (spin=1) getting stuck when no `load(xml)` is ever sent.
const DEFAULT_DIAGRAM_XML = `<mxfile host="app.diagrams.net"><diagram id="blank" name="Page-1"><mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel></diagram></mxfile>`
// Normalize URL for iframe src - ensure no double slashes // Normalize URL for iframe src - ensure no double slashes
function normalizeUrl(url: string): string { function normalizeUrl(url: string): string {
// Remove trailing slash to avoid double slashes // Remove trailing slash to avoid double slashes
return url.replace(/\/$/, "") return url.replace(/\/$/, "")
} }
function isLikelyMcpSessionId(sessionId: string): boolean {
// Keep this cheap and conservative to avoid creating state for arbitrary IDs.
return sessionId.startsWith("mcp-") && sessionId.length <= 128
}
function ensureSessionStateInitialized(sessionId: string): void {
if (!sessionId) return
if (!isLikelyMcpSessionId(sessionId)) return
if (stateStore.has(sessionId)) return
setState(sessionId, DEFAULT_DIAGRAM_XML)
}
interface SessionState { interface SessionState {
xml: string xml: string
version: number version: number
@@ -177,8 +194,11 @@ function handleRequest(
} }
if (url.pathname === "/" || url.pathname === "/index.html") { if (url.pathname === "/" || url.pathname === "/index.html") {
const sessionId = url.searchParams.get("mcp") || ""
ensureSessionStateInitialized(sessionId)
res.writeHead(200, { "Content-Type": "text/html" }) res.writeHead(200, { "Content-Type": "text/html" })
res.end(getHtmlPage(url.searchParams.get("mcp") || "")) res.end(getHtmlPage(sessionId))
} else if (url.pathname === "/api/state") { } else if (url.pathname === "/api/state") {
handleStateApi(req, res, url) handleStateApi(req, res, url)
} else if (url.pathname === "/api/history") { } else if (url.pathname === "/api/history") {
@@ -205,6 +225,7 @@ function handleStateApi(
res.end(JSON.stringify({ error: "sessionId required" })) res.end(JSON.stringify({ error: "sessionId required" }))
return return
} }
ensureSessionStateInitialized(sessionId)
const state = stateStore.get(sessionId) const state = stateStore.get(sessionId)
res.writeHead(200, { "Content-Type": "application/json" }) res.writeHead(200, { "Content-Type": "application/json" })
res.end( res.end(