mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-12 20:49:53 +08:00
refactor(web): share prompt examples and the shape library with the MCP server
- The three XML examples (swimlanes, two edges, waypoints) that the web system prompt, the display_diagram description and the MCP drawing guide each had a copy of now live in packages/mcp-server/src/xml-examples.ts - The shape library group list and reader come from the MCP shape-library.ts; getShapeLibrary takes the folder, and the web route passes docs/shape-libraries under the working directory as before. Only the 30 known library names are read. Error texts differ slightly - next.config.ts traces docs/shape-libraries/*.md for /api/chat, since the read now happens in another module - Every prompt is byte-for-byte unchanged: the web system prompt for five model ids with and without minimal style, and the MCP drawing guide, compared before and after; the library list and swimlane example match the old tool description text exactly
This commit is contained in:
@@ -7,6 +7,13 @@
|
||||
* update this file too.
|
||||
*/
|
||||
|
||||
import {
|
||||
indent,
|
||||
SWIMLANE_EXAMPLE,
|
||||
TWO_EDGES_EXAMPLE,
|
||||
WAYPOINT_EXAMPLE,
|
||||
} from "./xml-examples.ts"
|
||||
|
||||
export const DRAWING_GUIDE = `# Draw.io drawing guide
|
||||
|
||||
## Workflow
|
||||
@@ -41,21 +48,7 @@ Rules (XML that breaks them is rejected):
|
||||
|
||||
Containers and swimlanes: children use the container id as parent and coordinates relative to the container.
|
||||
|
||||
<mxCell id="lane1" value="Frontend" style="swimlane;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="40" width="200" height="200" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="step1" value="Step 1" style="rounded=1;" vertex="1" parent="lane1">
|
||||
<mxGeometry x="20" y="60" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="lane2" value="Backend" style="swimlane;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="40" width="200" height="200" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="step2" value="Step 2" style="rounded=1;" vertex="1" parent="lane2">
|
||||
<mxGeometry x="20" y="60" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="edge1" style="edgeStyle=orthogonalEdgeStyle;endArrow=classic;" edge="1" parent="1" source="step1" target="step2">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
${indent(SWIMLANE_EXAMPLE)}
|
||||
|
||||
## Layout
|
||||
- Keep every element of a page within x 0 to 800 and y 0 to 600, so the whole diagram fits one view without a page break.
|
||||
@@ -80,23 +73,11 @@ Before sending XML, check:
|
||||
|
||||
Two edges between the same nodes:
|
||||
|
||||
<mxCell id="e1" value="A to B" style="edgeStyle=orthogonalEdgeStyle;exitX=1;exitY=0.3;entryX=0;entryY=0.3;endArrow=classic;" edge="1" parent="1" source="a" target="b">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e2" value="B to A" style="edgeStyle=orthogonalEdgeStyle;exitX=0;exitY=0.7;entryX=1;entryY=0.7;endArrow=classic;" edge="1" parent="1" source="b" target="a">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
${indent(TWO_EDGES_EXAMPLE)}
|
||||
|
||||
Waypoints go inside <Array as="points"> in the edge geometry. Example: Hotfix (right, bottom) connects to Main (center, top) while Develop (center, middle) is in between, so the edge goes right to x=750 first, then up, and enters Main from the right:
|
||||
|
||||
<mxCell id="hotfix_to_main" style="edgeStyle=orthogonalEdgeStyle;exitX=0.5;exitY=0;entryX=1;entryY=0.5;endArrow=classic;" edge="1" parent="1" source="hotfix" target="main">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="750" y="80"/>
|
||||
<mxPoint x="750" y="150"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
${indent(WAYPOINT_EXAMPLE)}
|
||||
|
||||
## Styles
|
||||
- Shapes: rounded=1, fillColor=#hex, strokeColor=#hex, whiteSpace=wrap;html=1;
|
||||
|
||||
@@ -62,7 +62,7 @@ import {
|
||||
wrapCellsInModel,
|
||||
} from "./pages.ts"
|
||||
import { Autosaver, defaultDataDir } from "./persistence.ts"
|
||||
import { getShapeLibrary, SHAPE_LIBRARY_GROUPS } from "./shape-library.ts"
|
||||
import { getShapeLibrary, SHAPE_LIBRARY_LIST } from "./shape-library.ts"
|
||||
import { validateAndFixXml } from "./xml-validation.ts"
|
||||
|
||||
// DOMParser/XMLSerializer globals for the XML helpers (Node has neither)
|
||||
@@ -224,9 +224,7 @@ server.registerTool(
|
||||
description:
|
||||
"Get the style syntax and shape names of a draw.io icon library. Call this BEFORE drawing with " +
|
||||
"cloud, network or other icon shapes, and use the exact names it returns; never guess them.\n\n" +
|
||||
`Libraries:\n${Object.entries(SHAPE_LIBRARY_GROUPS)
|
||||
.map(([group, names]) => `- ${group}: ${names.join(", ")}`)
|
||||
.join("\n")}`,
|
||||
`Libraries:\n${SHAPE_LIBRARY_LIST}`,
|
||||
inputSchema: {
|
||||
library: z
|
||||
.string()
|
||||
|
||||
@@ -25,18 +25,29 @@ export const SHAPE_LIBRARY_GROUPS: Record<string, string[]> = {
|
||||
Icons: ["webicons"],
|
||||
}
|
||||
|
||||
/** The libraries by group, one "- Group: a, b" line each, for tool descriptions */
|
||||
export const SHAPE_LIBRARY_LIST = Object.entries(SHAPE_LIBRARY_GROUPS)
|
||||
.map(([group, names]) => `- ${group}: ${names.join(", ")}`)
|
||||
.join("\n")
|
||||
|
||||
const LIBRARIES = new Set(Object.values(SHAPE_LIBRARY_GROUPS).flat())
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url))
|
||||
// The build copies the docs to dist/shape-libraries; running from src (tsx)
|
||||
// reads them from the repository instead.
|
||||
const LIBRARY_DIRS = [
|
||||
join(here, "shape-libraries"),
|
||||
resolve(here, "../../../docs/shape-libraries"),
|
||||
]
|
||||
/**
|
||||
* The build copies the docs to dist/shape-libraries; running from src (tsx)
|
||||
* reads them from the repository instead. The web app passes its own folder.
|
||||
*/
|
||||
function libraryDirs(dir?: string): string[] {
|
||||
if (dir) return [dir]
|
||||
const here = dirname(fileURLToPath(import.meta.url))
|
||||
return [
|
||||
join(here, "shape-libraries"),
|
||||
resolve(here, "../../../docs/shape-libraries"),
|
||||
]
|
||||
}
|
||||
|
||||
export async function getShapeLibrary(
|
||||
name: string,
|
||||
dir?: string,
|
||||
): Promise<{ ok: true; text: string } | { ok: false; error: string }> {
|
||||
const library = name.trim().toLowerCase()
|
||||
if (!LIBRARIES.has(library)) {
|
||||
@@ -45,11 +56,11 @@ export async function getShapeLibrary(
|
||||
error: `Library "${name}" not found. Available: ${Array.from(LIBRARIES).join(", ")}`,
|
||||
}
|
||||
}
|
||||
for (const dir of LIBRARY_DIRS) {
|
||||
for (const folder of libraryDirs(dir)) {
|
||||
try {
|
||||
return {
|
||||
ok: true,
|
||||
text: await readFile(join(dir, `${library}.md`), "utf-8"),
|
||||
text: await readFile(join(folder, `${library}.md`), "utf-8"),
|
||||
}
|
||||
} catch {
|
||||
// Try the next location
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* XML examples shared by the MCP drawing guide and the web app's system
|
||||
* prompt and tool descriptions, so both teach the model the same thing.
|
||||
*/
|
||||
|
||||
export const SWIMLANE_EXAMPLE = `<mxCell id="lane1" value="Frontend" style="swimlane;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="40" width="200" height="200" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="step1" value="Step 1" style="rounded=1;" vertex="1" parent="lane1">
|
||||
<mxGeometry x="20" y="60" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="lane2" value="Backend" style="swimlane;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="40" width="200" height="200" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="step2" value="Step 2" style="rounded=1;" vertex="1" parent="lane2">
|
||||
<mxGeometry x="20" y="60" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="edge1" style="edgeStyle=orthogonalEdgeStyle;endArrow=classic;" edge="1" parent="1" source="step1" target="step2">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>`
|
||||
|
||||
export const TWO_EDGES_EXAMPLE = `<mxCell id="e1" value="A to B" style="edgeStyle=orthogonalEdgeStyle;exitX=1;exitY=0.3;entryX=0;entryY=0.3;endArrow=classic;" edge="1" parent="1" source="a" target="b">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e2" value="B to A" style="edgeStyle=orthogonalEdgeStyle;exitX=0;exitY=0.7;entryX=1;entryY=0.7;endArrow=classic;" edge="1" parent="1" source="b" target="a">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>`
|
||||
|
||||
export const WAYPOINT_EXAMPLE = `<mxCell id="hotfix_to_main" style="edgeStyle=orthogonalEdgeStyle;exitX=0.5;exitY=0;entryX=1;entryY=0.5;endArrow=classic;" edge="1" parent="1" source="hotfix" target="main">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="750" y="80"/>
|
||||
<mxPoint x="750" y="150"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>`
|
||||
|
||||
/** Indent every line, for an indented code block in Markdown */
|
||||
export const indent = (text: string, prefix = " ") =>
|
||||
text
|
||||
.split("\n")
|
||||
.map((line) => prefix + line)
|
||||
.join("\n")
|
||||
Reference in New Issue
Block a user