mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-03 23:02:31 +08:00
Compare commits
6 Commits
49b086cef3
...
fix/cascad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
345381e61a | ||
|
|
853f30ba89 | ||
|
|
8fc6a5396a | ||
|
|
acf3bc7e42 | ||
|
|
c2aa7f49be | ||
|
|
30b30550d9 |
0
renovate.json → .github/renovate.json
vendored
0
renovate.json → .github/renovate.json
vendored
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm install
|
||||||
|
|
||||||
- name: Type check
|
- name: Type check
|
||||||
run: npx tsc --noEmit
|
run: npx tsc --noEmit
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ WORKDIR /app
|
|||||||
COPY package.json package-lock.json* ./
|
COPY package.json package-lock.json* ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN npm ci
|
RUN npm install
|
||||||
|
|
||||||
# Stage 2: Build application
|
# Stage 2: Build application
|
||||||
FROM node:24-alpine AS builder
|
FROM node:24-alpine AS builder
|
||||||
|
|||||||
@@ -605,7 +605,7 @@ Notes:
|
|||||||
Operations:
|
Operations:
|
||||||
- update: Replace an existing cell by its id. Provide cell_id and complete new_xml.
|
- update: Replace an existing cell by its id. Provide cell_id and complete new_xml.
|
||||||
- add: Add a new cell. Provide cell_id (new unique id) and new_xml.
|
- add: Add a new cell. Provide cell_id (new unique id) and new_xml.
|
||||||
- delete: Remove a cell by its id. Only cell_id is needed.
|
- delete: Remove a cell. Cascade is automatic: children AND edges (source/target) are auto-deleted. Only specify ONE cell_id.
|
||||||
|
|
||||||
For update/add, new_xml must be a complete mxCell element including mxGeometry.
|
For update/add, new_xml must be a complete mxCell element including mxGeometry.
|
||||||
|
|
||||||
@@ -614,8 +614,8 @@ For update/add, new_xml must be a complete mxCell element including mxGeometry.
|
|||||||
Example - Add a rectangle:
|
Example - Add a rectangle:
|
||||||
{"operations": [{"operation": "add", "cell_id": "rect-1", "new_xml": "<mxCell id=\\"rect-1\\" value=\\"Hello\\" style=\\"rounded=0;\\" vertex=\\"1\\" parent=\\"1\\"><mxGeometry x=\\"100\\" y=\\"100\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/></mxCell>"}]}
|
{"operations": [{"operation": "add", "cell_id": "rect-1", "new_xml": "<mxCell id=\\"rect-1\\" value=\\"Hello\\" style=\\"rounded=0;\\" vertex=\\"1\\" parent=\\"1\\"><mxGeometry x=\\"100\\" y=\\"100\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/></mxCell>"}]}
|
||||||
|
|
||||||
Example - Delete a cell:
|
Example - Delete container (children & edges auto-deleted):
|
||||||
{"operations": [{"operation": "delete", "cell_id": "rect-1"}]}`,
|
{"operations": [{"operation": "delete", "cell_id": "2"}]}`,
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
operations: z
|
operations: z
|
||||||
.array(
|
.array(
|
||||||
|
|||||||
0
electron.d.ts → electron/electron.d.ts
vendored
0
electron.d.ts → electron/electron.d.ts
vendored
@@ -276,7 +276,7 @@ edit_diagram uses ID-based operations to modify cells directly by their id attri
|
|||||||
**Operations:**
|
**Operations:**
|
||||||
- **update**: Replace an existing cell. Provide cell_id and new_xml.
|
- **update**: Replace an existing cell. Provide cell_id and new_xml.
|
||||||
- **add**: Add a new cell. Provide cell_id (new unique id) and new_xml.
|
- **add**: Add a new cell. Provide cell_id (new unique id) and new_xml.
|
||||||
- **delete**: Remove a cell. Only cell_id is needed.
|
- **delete**: Remove a cell. **Cascade is automatic**: children AND edges (source/target) are auto-deleted. Only specify ONE cell_id.
|
||||||
|
|
||||||
**Input Format:**
|
**Input Format:**
|
||||||
\`\`\`json
|
\`\`\`json
|
||||||
@@ -301,9 +301,9 @@ Add new shape:
|
|||||||
{"operations": [{"operation": "add", "cell_id": "new1", "new_xml": "<mxCell id=\\"new1\\" value=\\"New Box\\" style=\\"rounded=1;fillColor=#dae8fc;\\" vertex=\\"1\\" parent=\\"1\\">\\n <mxGeometry x=\\"400\\" y=\\"200\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/>\\n</mxCell>"}]}
|
{"operations": [{"operation": "add", "cell_id": "new1", "new_xml": "<mxCell id=\\"new1\\" value=\\"New Box\\" style=\\"rounded=1;fillColor=#dae8fc;\\" vertex=\\"1\\" parent=\\"1\\">\\n <mxGeometry x=\\"400\\" y=\\"200\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/>\\n</mxCell>"}]}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
Delete cell:
|
Delete container (children & edges auto-deleted):
|
||||||
\`\`\`json
|
\`\`\`json
|
||||||
{"operations": [{"operation": "delete", "cell_id": "5"}]}
|
{"operations": [{"operation": "delete", "cell_id": "2"}]}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
**Error Recovery:**
|
**Error Recovery:**
|
||||||
|
|||||||
77
lib/utils.ts
77
lib/utils.ts
@@ -633,32 +633,77 @@ export function applyDiagramOperations(
|
|||||||
// Add to map
|
// Add to map
|
||||||
cellMap.set(op.cell_id, importedNode)
|
cellMap.set(op.cell_id, importedNode)
|
||||||
} else if (op.operation === "delete") {
|
} else if (op.operation === "delete") {
|
||||||
const existingCell = cellMap.get(op.cell_id)
|
// Protect root cells from deletion
|
||||||
if (!existingCell) {
|
if (op.cell_id === "0" || op.cell_id === "1") {
|
||||||
errors.push({
|
errors.push({
|
||||||
type: "delete",
|
type: "delete",
|
||||||
cellId: op.cell_id,
|
cellId: op.cell_id,
|
||||||
message: `Cell with id="${op.cell_id}" not found`,
|
message: `Cannot delete root cell "${op.cell_id}"`,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for edges referencing this cell (warning only, still delete)
|
const existingCell = cellMap.get(op.cell_id)
|
||||||
const referencingEdges = root.querySelectorAll(
|
if (!existingCell) {
|
||||||
`mxCell[source="${op.cell_id}"], mxCell[target="${op.cell_id}"]`,
|
// Cell not found - might have been cascade-deleted by a previous operation
|
||||||
)
|
// Skip silently instead of erroring (AI may redundantly list children/edges)
|
||||||
if (referencingEdges.length > 0) {
|
continue
|
||||||
const edgeIds = Array.from(referencingEdges)
|
}
|
||||||
.map((e) => e.getAttribute("id"))
|
|
||||||
.join(", ")
|
// Cascade delete: collect all cells to delete (children + edges + self)
|
||||||
console.warn(
|
const cellsToDelete = new Set<string>()
|
||||||
`[applyDiagramOperations] Deleting cell "${op.cell_id}" which is referenced by edges: ${edgeIds}`,
|
|
||||||
|
// Recursive function to find all descendants
|
||||||
|
const collectDescendants = (cellId: string) => {
|
||||||
|
if (cellsToDelete.has(cellId)) return
|
||||||
|
cellsToDelete.add(cellId)
|
||||||
|
|
||||||
|
// Find children (cells where parent === cellId)
|
||||||
|
const children = root.querySelectorAll(
|
||||||
|
`mxCell[parent="${cellId}"]`,
|
||||||
|
)
|
||||||
|
children.forEach((child) => {
|
||||||
|
const childId = child.getAttribute("id")
|
||||||
|
if (childId && childId !== "0" && childId !== "1") {
|
||||||
|
collectDescendants(childId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the target cell and all its descendants
|
||||||
|
collectDescendants(op.cell_id)
|
||||||
|
|
||||||
|
// Find edges referencing any of the cells to be deleted
|
||||||
|
// Also recursively collect children of those edges (e.g., edge labels)
|
||||||
|
for (const cellId of cellsToDelete) {
|
||||||
|
const referencingEdges = root.querySelectorAll(
|
||||||
|
`mxCell[source="${cellId}"], mxCell[target="${cellId}"]`,
|
||||||
|
)
|
||||||
|
referencingEdges.forEach((edge) => {
|
||||||
|
const edgeId = edge.getAttribute("id")
|
||||||
|
// Protect root cells from being added via edge references
|
||||||
|
if (edgeId && edgeId !== "0" && edgeId !== "1") {
|
||||||
|
// Recurse to collect edge's children (like labels)
|
||||||
|
collectDescendants(edgeId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log what will be deleted
|
||||||
|
if (cellsToDelete.size > 1) {
|
||||||
|
console.log(
|
||||||
|
`[applyDiagramOperations] Cascade delete "${op.cell_id}" → deleting ${cellsToDelete.size} cells: ${Array.from(cellsToDelete).join(", ")}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the node
|
// Delete all collected cells
|
||||||
existingCell.parentNode?.removeChild(existingCell)
|
for (const cellId of cellsToDelete) {
|
||||||
cellMap.delete(op.cell_id)
|
const cell = cellMap.get(cellId)
|
||||||
|
if (cell) {
|
||||||
|
cell.parentNode?.removeChild(cell)
|
||||||
|
cellMap.delete(cellId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
42926
package-lock.json
generated
42926
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -21,11 +21,11 @@
|
|||||||
"electron:compile": "npx esbuild electron/main/index.ts electron/preload/index.ts electron/preload/settings.ts --bundle --platform=node --outdir=dist-electron --external:electron --sourcemap --packages=external && npx shx cp -r electron/settings dist-electron/",
|
"electron:compile": "npx esbuild electron/main/index.ts electron/preload/index.ts electron/preload/settings.ts --bundle --platform=node --outdir=dist-electron --external:electron --sourcemap --packages=external && npx shx cp -r electron/settings dist-electron/",
|
||||||
"electron:start": "npx cross-env NODE_ENV=development npx electron .",
|
"electron:start": "npx cross-env NODE_ENV=development npx electron .",
|
||||||
"electron:prepare": "node scripts/prepare-electron-build.mjs",
|
"electron:prepare": "node scripts/prepare-electron-build.mjs",
|
||||||
"dist": "npm run electron:build && npm run electron:prepare && npx electron-builder",
|
"dist": "npm run electron:build && npm run electron:prepare && npx electron-builder --config electron/electron-builder.yml",
|
||||||
"dist:mac": "npm run electron:build && npm run electron:prepare && npx electron-builder --mac",
|
"dist:mac": "npm run electron:build && npm run electron:prepare && npx electron-builder --config electron/electron-builder.yml --mac",
|
||||||
"dist:win": "npm run electron:build && npm run electron:prepare && npx electron-builder --win",
|
"dist:win": "npm run electron:build && npm run electron:prepare && npx electron-builder --config electron/electron-builder.yml --win",
|
||||||
"dist:linux": "npm run electron:build && npm run electron:prepare && npx electron-builder --linux",
|
"dist:linux": "npm run electron:build && npm run electron:prepare && npx electron-builder --config electron/electron-builder.yml --linux",
|
||||||
"dist:all": "npm run electron:build && npm run electron:prepare && npx electron-builder --mac --win --linux"
|
"dist:all": "npm run electron:build && npm run electron:prepare && npx electron-builder --config electron/electron-builder.yml --mac --win --linux"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/amazon-bedrock": "^4.0.1",
|
"@ai-sdk/amazon-bedrock": "^4.0.1",
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ Use the standard MCP configuration with:
|
|||||||
| Tool | Description |
|
| Tool | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `start_session` | Opens browser with real-time diagram preview |
|
| `start_session` | Opens browser with real-time diagram preview |
|
||||||
| `display_diagram` | Create a new diagram from XML |
|
| `create_new_diagram` | Create a new diagram from XML (requires `xml` argument) |
|
||||||
| `edit_diagram` | Edit diagram by ID-based operations (update/add/delete cells) |
|
| `edit_diagram` | Edit diagram by ID-based operations (update/add/delete cells) |
|
||||||
| `get_diagram` | Get the current diagram XML |
|
| `get_diagram` | Get the current diagram XML |
|
||||||
| `export_diagram` | Save diagram to a `.drawio` file |
|
| `export_diagram` | Save diagram to a `.drawio` file |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@next-ai-drawio/mcp-server",
|
"name": "@next-ai-drawio/mcp-server",
|
||||||
"version": "0.1.6",
|
"version": "0.1.10",
|
||||||
"description": "MCP server for Next AI Draw.io - AI-powered diagram generation with real-time browser preview",
|
"description": "MCP server for Next AI Draw.io - AI-powered diagram generation with real-time browser preview",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -182,32 +182,77 @@ export function applyDiagramOperations(
|
|||||||
// Add to map
|
// Add to map
|
||||||
cellMap.set(op.cell_id, importedNode)
|
cellMap.set(op.cell_id, importedNode)
|
||||||
} else if (op.operation === "delete") {
|
} else if (op.operation === "delete") {
|
||||||
const existingCell = cellMap.get(op.cell_id)
|
// Protect root cells from deletion
|
||||||
if (!existingCell) {
|
if (op.cell_id === "0" || op.cell_id === "1") {
|
||||||
errors.push({
|
errors.push({
|
||||||
type: "delete",
|
type: "delete",
|
||||||
cellId: op.cell_id,
|
cellId: op.cell_id,
|
||||||
message: `Cell with id="${op.cell_id}" not found`,
|
message: `Cannot delete root cell "${op.cell_id}"`,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for edges referencing this cell (warning only, still delete)
|
const existingCell = cellMap.get(op.cell_id)
|
||||||
const referencingEdges = root.querySelectorAll(
|
if (!existingCell) {
|
||||||
`mxCell[source="${op.cell_id}"], mxCell[target="${op.cell_id}"]`,
|
// Cell not found - might have been cascade-deleted by a previous operation
|
||||||
)
|
// Skip silently instead of erroring (AI may redundantly list children/edges)
|
||||||
if (referencingEdges.length > 0) {
|
continue
|
||||||
const edgeIds = Array.from(referencingEdges)
|
}
|
||||||
.map((e) => e.getAttribute("id"))
|
|
||||||
.join(", ")
|
// Cascade delete: collect all cells to delete (children + edges + self)
|
||||||
console.warn(
|
const cellsToDelete = new Set<string>()
|
||||||
`[applyDiagramOperations] Deleting cell "${op.cell_id}" which is referenced by edges: ${edgeIds}`,
|
|
||||||
|
// Recursive function to find all descendants
|
||||||
|
const collectDescendants = (cellId: string) => {
|
||||||
|
if (cellsToDelete.has(cellId)) return
|
||||||
|
cellsToDelete.add(cellId)
|
||||||
|
|
||||||
|
// Find children (cells where parent === cellId)
|
||||||
|
const children = root.querySelectorAll(
|
||||||
|
`mxCell[parent="${cellId}"]`,
|
||||||
|
)
|
||||||
|
children.forEach((child) => {
|
||||||
|
const childId = child.getAttribute("id")
|
||||||
|
if (childId && childId !== "0" && childId !== "1") {
|
||||||
|
collectDescendants(childId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the target cell and all its descendants
|
||||||
|
collectDescendants(op.cell_id)
|
||||||
|
|
||||||
|
// Find edges referencing any of the cells to be deleted
|
||||||
|
// Also recursively collect children of those edges (e.g., edge labels)
|
||||||
|
for (const cellId of cellsToDelete) {
|
||||||
|
const referencingEdges = root.querySelectorAll(
|
||||||
|
`mxCell[source="${cellId}"], mxCell[target="${cellId}"]`,
|
||||||
|
)
|
||||||
|
referencingEdges.forEach((edge) => {
|
||||||
|
const edgeId = edge.getAttribute("id")
|
||||||
|
// Protect root cells from being added via edge references
|
||||||
|
if (edgeId && edgeId !== "0" && edgeId !== "1") {
|
||||||
|
// Recurse to collect edge's children (like labels)
|
||||||
|
collectDescendants(edgeId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log what will be deleted
|
||||||
|
if (cellsToDelete.size > 1) {
|
||||||
|
console.log(
|
||||||
|
`[applyDiagramOperations] Cascade delete "${op.cell_id}" → deleting ${cellsToDelete.size} cells: ${Array.from(cellsToDelete).join(", ")}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the node
|
// Delete all collected cells
|
||||||
existingCell.parentNode?.removeChild(existingCell)
|
for (const cellId of cellsToDelete) {
|
||||||
cellMap.delete(op.cell_id)
|
const cell = cellMap.get(cellId)
|
||||||
|
if (cell) {
|
||||||
|
cell.parentNode?.removeChild(cell)
|
||||||
|
cellMap.delete(cellId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ server.prompt(
|
|||||||
|
|
||||||
## Creating a New Diagram
|
## Creating a New Diagram
|
||||||
1. Call start_session to open the browser preview
|
1. Call start_session to open the browser preview
|
||||||
2. Use display_diagram with complete mxGraphModel XML to create a new diagram
|
2. Use create_new_diagram with complete mxGraphModel XML to create a new diagram
|
||||||
|
|
||||||
## Adding Elements to Existing Diagram
|
## Adding Elements to Existing Diagram
|
||||||
1. Use edit_diagram with "add" operation
|
1. Use edit_diagram with "add" operation
|
||||||
@@ -91,7 +91,7 @@ server.prompt(
|
|||||||
3. For update, provide the cell_id and complete new mxCell XML
|
3. For update, provide the cell_id and complete new mxCell XML
|
||||||
|
|
||||||
## Important Notes
|
## Important Notes
|
||||||
- display_diagram REPLACES the entire diagram - only use for new diagrams
|
- create_new_diagram REPLACES the entire diagram - only use for new diagrams
|
||||||
- edit_diagram PRESERVES user's manual changes (fetches browser state first)
|
- edit_diagram PRESERVES user's manual changes (fetches browser state first)
|
||||||
- Always use unique cell_ids when adding elements (e.g., "shape-1", "arrow-2")`,
|
- Always use unique cell_ids when adding elements (e.g., "shape-1", "arrow-2")`,
|
||||||
},
|
},
|
||||||
@@ -150,19 +150,59 @@ server.registerTool(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tool: display_diagram
|
// Tool: create_new_diagram
|
||||||
server.registerTool(
|
server.registerTool(
|
||||||
"display_diagram",
|
"create_new_diagram",
|
||||||
{
|
{
|
||||||
description:
|
description: `Create a NEW diagram from mxGraphModel XML. Use this when creating a diagram from scratch or replacing the current diagram entirely.
|
||||||
"Display a NEW draw.io diagram from XML. REPLACES the entire diagram. " +
|
|
||||||
"Use this for creating new diagrams from scratch. " +
|
CRITICAL: You MUST provide the 'xml' argument in EVERY call. Do NOT call this tool without xml.
|
||||||
"To ADD elements to an existing diagram, use edit_diagram with 'add' operation instead. " +
|
|
||||||
"You should generate valid draw.io/mxGraph XML format.",
|
When to use this tool:
|
||||||
|
- Creating a new diagram from scratch
|
||||||
|
- Replacing the current diagram with a completely different one
|
||||||
|
- Major structural changes that require regenerating the diagram
|
||||||
|
|
||||||
|
When to use edit_diagram instead:
|
||||||
|
- Small modifications to existing diagram
|
||||||
|
- Adding/removing individual elements
|
||||||
|
- Changing labels, colors, or positions
|
||||||
|
|
||||||
|
XML FORMAT - Full mxGraphModel structure:
|
||||||
|
<mxGraphModel>
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="2" value="Shape" style="rounded=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="100" y="100" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
|
||||||
|
LAYOUT CONSTRAINTS:
|
||||||
|
- Keep all elements within x=0-800, y=0-600 (single page viewport)
|
||||||
|
- Start from margins (x=40, y=40), keep elements grouped closely
|
||||||
|
- Use unique IDs starting from "2" (0 and 1 are reserved)
|
||||||
|
- Set parent="1" for top-level shapes
|
||||||
|
- Space shapes 150-200px apart for clear edge routing
|
||||||
|
|
||||||
|
EDGE ROUTING RULES:
|
||||||
|
- Never let multiple edges share the same path - use different exitY/entryY values
|
||||||
|
- For bidirectional connections (A↔B), use OPPOSITE sides
|
||||||
|
- Always specify exitX, exitY, entryX, entryY explicitly in edge style
|
||||||
|
- Route edges AROUND obstacles using waypoints (add 20-30px clearance)
|
||||||
|
- Use natural connection points based on flow (not corners)
|
||||||
|
|
||||||
|
COMMON STYLES:
|
||||||
|
- Shapes: rounded=1; fillColor=#hex; strokeColor=#hex
|
||||||
|
- Edges: endArrow=classic; edgeStyle=orthogonalEdgeStyle; curved=1
|
||||||
|
- Text: fontSize=14; fontStyle=1 (bold); align=center`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
xml: z
|
xml: z
|
||||||
.string()
|
.string()
|
||||||
.describe("The draw.io XML to display (mxGraphModel format)"),
|
.describe(
|
||||||
|
"REQUIRED: The complete mxGraphModel XML. Must always be provided.",
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async ({ xml: inputXml }) => {
|
async ({ xml: inputXml }) => {
|
||||||
@@ -199,7 +239,7 @@ server.registerTool(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(`Displaying diagram, ${xml.length} chars`)
|
log.info(`Setting diagram content, ${xml.length} chars`)
|
||||||
|
|
||||||
// Sync from browser state first
|
// Sync from browser state first
|
||||||
const browserState = getState(currentSession.id)
|
const browserState = getState(currentSession.id)
|
||||||
@@ -226,20 +266,20 @@ server.registerTool(
|
|||||||
// Save AI result (no SVG yet - will be captured by browser)
|
// Save AI result (no SVG yet - will be captured by browser)
|
||||||
addHistory(currentSession.id, xml, "")
|
addHistory(currentSession.id, xml, "")
|
||||||
|
|
||||||
log.info(`Diagram displayed successfully`)
|
log.info(`Diagram content set successfully`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: `Diagram displayed successfully!\n\nThe diagram is now visible in your browser.\n\nXML length: ${xml.length} characters`,
|
text: `Diagram content set successfully!\n\nThe diagram is now visible in your browser.\n\nXML length: ${xml.length} characters`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error ? error.message : String(error)
|
error instanceof Error ? error.message : String(error)
|
||||||
log.error("display_diagram failed:", message)
|
log.error("create_new_diagram failed:", message)
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: `Error: ${message}` }],
|
content: [{ type: "text", text: `Error: ${message}` }],
|
||||||
isError: true,
|
isError: true,
|
||||||
@@ -340,7 +380,7 @@ server.registerTool(
|
|||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: "Error: No diagram to edit. Please create a diagram first with display_diagram.",
|
text: "Error: No diagram to edit. Please create a diagram first with create_new_diagram.",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
isError: true,
|
isError: true,
|
||||||
@@ -474,7 +514,7 @@ server.registerTool(
|
|||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: "No diagram exists yet. Use display_diagram to create one.",
|
text: "No diagram exists yet. Use create_new_diagram to create one.",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
63
proxy.ts
63
proxy.ts
@@ -1,63 +0,0 @@
|
|||||||
import { match as matchLocale } from "@formatjs/intl-localematcher"
|
|
||||||
import Negotiator from "negotiator"
|
|
||||||
import type { NextRequest } from "next/server"
|
|
||||||
import { NextResponse } from "next/server"
|
|
||||||
import { i18n } from "./lib/i18n/config"
|
|
||||||
|
|
||||||
function getLocale(request: NextRequest): string | undefined {
|
|
||||||
// Negotiator expects plain object so we need to transform headers
|
|
||||||
const negotiatorHeaders: Record<string, string> = {}
|
|
||||||
request.headers.forEach((value, key) => {
|
|
||||||
negotiatorHeaders[key] = value
|
|
||||||
})
|
|
||||||
|
|
||||||
// @ts-expect-error locales are readonly
|
|
||||||
const locales: string[] = i18n.locales
|
|
||||||
|
|
||||||
// Use negotiator and intl-localematcher to get best locale
|
|
||||||
const languages = new Negotiator({ headers: negotiatorHeaders }).languages(
|
|
||||||
locales,
|
|
||||||
)
|
|
||||||
|
|
||||||
const locale = matchLocale(languages, locales, i18n.defaultLocale)
|
|
||||||
|
|
||||||
return locale
|
|
||||||
}
|
|
||||||
|
|
||||||
export function proxy(request: NextRequest) {
|
|
||||||
const pathname = request.nextUrl.pathname
|
|
||||||
|
|
||||||
// Skip API routes, static files, and Next.js internals
|
|
||||||
if (
|
|
||||||
pathname.startsWith("/api/") ||
|
|
||||||
pathname.startsWith("/_next/") ||
|
|
||||||
pathname.includes("/favicon") ||
|
|
||||||
/\.(.*)$/.test(pathname)
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there is any supported locale in the pathname
|
|
||||||
const pathnameIsMissingLocale = i18n.locales.every(
|
|
||||||
(locale) =>
|
|
||||||
!pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Redirect if there is no locale
|
|
||||||
if (pathnameIsMissingLocale) {
|
|
||||||
const locale = getLocale(request)
|
|
||||||
|
|
||||||
// Redirect to localized path
|
|
||||||
return NextResponse.redirect(
|
|
||||||
new URL(
|
|
||||||
`/${locale}${pathname.startsWith("/") ? "" : "/"}${pathname}`,
|
|
||||||
request.url,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
// Matcher ignoring `/_next/` and `/api/`
|
|
||||||
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
|
||||||
}
|
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"@/*": ["./*"]
|
"@/*": ["./*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"files": ["electron/electron.d.ts"],
|
||||||
"include": [
|
"include": [
|
||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
|||||||
Reference in New Issue
Block a user