dodraw-mcp-server 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/create_class_diagram_example.js +136 -0
- package/dist/src/create_termination_reference.js +88 -0
- package/dist/src/tools/diagramTools.js +280 -11
- package/package.json +1 -1
- package/src/create_class_diagram_example.ts +151 -0
- package/src/create_termination_reference.ts +108 -0
- package/src/tools/diagramTools.ts +295 -11
- package/src/types.ts +29 -13
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const diagramTools_1 = require("./tools/diagramTools");
|
|
4
|
+
async function createClassDiagram() {
|
|
5
|
+
const FILE_PATH = "class_diagram_example.3duml";
|
|
6
|
+
console.log(`Creating class diagram example at ${FILE_PATH}...`);
|
|
7
|
+
// 1. Create Diagram
|
|
8
|
+
try {
|
|
9
|
+
await (0, diagramTools_1.handleToolCall)("create_new_diagram", { filePath: FILE_PATH });
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
console.log("File might exist, continuing...");
|
|
13
|
+
}
|
|
14
|
+
// 2. Add Layer
|
|
15
|
+
await (0, diagramTools_1.handleToolCall)("add_layer", { filePath: FILE_PATH, name: "Classes" });
|
|
16
|
+
// 3. Create Classes
|
|
17
|
+
// Abstract Class: GraphicObject
|
|
18
|
+
await (0, diagramTools_1.handleToolCall)("create_uml_class", {
|
|
19
|
+
filePath: FILE_PATH,
|
|
20
|
+
className: "GraphicObject",
|
|
21
|
+
attributes: ["x: int", "y: int"],
|
|
22
|
+
methods: ["move(dx: int, dy: int)", "draw()"],
|
|
23
|
+
x: 0, y: -4,
|
|
24
|
+
id: "GraphicObject"
|
|
25
|
+
});
|
|
26
|
+
// Subclass: Circle
|
|
27
|
+
await (0, diagramTools_1.handleToolCall)("create_uml_class", {
|
|
28
|
+
filePath: FILE_PATH,
|
|
29
|
+
className: "Circle",
|
|
30
|
+
attributes: ["radius: float"],
|
|
31
|
+
methods: ["draw()", "resize(scale: float)"],
|
|
32
|
+
x: -4, y: 0,
|
|
33
|
+
id: "Circle"
|
|
34
|
+
});
|
|
35
|
+
// Subclass: Rectangle
|
|
36
|
+
await (0, diagramTools_1.handleToolCall)("create_uml_class", {
|
|
37
|
+
filePath: FILE_PATH,
|
|
38
|
+
className: "Rectangle",
|
|
39
|
+
attributes: ["width: float", "height: float"],
|
|
40
|
+
methods: ["draw()", "resize(scale: float)"],
|
|
41
|
+
x: 4, y: 0,
|
|
42
|
+
id: "Rectangle"
|
|
43
|
+
});
|
|
44
|
+
// Container: Group
|
|
45
|
+
await (0, diagramTools_1.handleToolCall)("create_uml_class", {
|
|
46
|
+
filePath: FILE_PATH,
|
|
47
|
+
className: "Group",
|
|
48
|
+
attributes: [],
|
|
49
|
+
methods: ["add(obj: GraphicObject)", "remove(obj: GraphicObject)", "draw()"],
|
|
50
|
+
x: 0, y: 4,
|
|
51
|
+
id: "Group"
|
|
52
|
+
});
|
|
53
|
+
// Client: Canvas
|
|
54
|
+
await (0, diagramTools_1.handleToolCall)("create_uml_class", {
|
|
55
|
+
filePath: FILE_PATH,
|
|
56
|
+
className: "Canvas",
|
|
57
|
+
attributes: ["width: int", "height: int", "backgroundColor: string"],
|
|
58
|
+
methods: ["render()", "clear()"],
|
|
59
|
+
x: 8, y: -4,
|
|
60
|
+
id: "Canvas"
|
|
61
|
+
});
|
|
62
|
+
// 4. Create Edges (Relationships)
|
|
63
|
+
// Inheritance: Circle -> GraphicObject
|
|
64
|
+
await (0, diagramTools_1.handleToolCall)("add_edge", {
|
|
65
|
+
filePath: FILE_PATH,
|
|
66
|
+
sourceId: "Circle",
|
|
67
|
+
targetId: "GraphicObject",
|
|
68
|
+
sourcePointIndex: 0, // Top
|
|
69
|
+
targetPointIndex: 3, // Left (or Bottom of GraphicObject depending on layout) -> GraphicObject is at -4 Z (UP). Circle at 0 Z.
|
|
70
|
+
// Circle (0,0) is BELOW GraphicObject (0, -4).
|
|
71
|
+
// So Circle TOP (0) connects to GraphicObject BOTTOM (2).
|
|
72
|
+
// Wait, Z is vertical in 2D top-down view?
|
|
73
|
+
// Logic: Z decreases going UP.
|
|
74
|
+
// GraphicObject (z=-4) is UP. Circle (z=0) is DOWN.
|
|
75
|
+
// Connect Circle Top (0) to GraphicObject Bottom (2).
|
|
76
|
+
terminationEnd: "triangle-empty", // Inheritance arrow on Parent
|
|
77
|
+
terminationStart: "none",
|
|
78
|
+
routingType: "orthogonal",
|
|
79
|
+
label: "extends",
|
|
80
|
+
id: "edge_circle_extends"
|
|
81
|
+
});
|
|
82
|
+
// Inheritance: Rectangle -> GraphicObject
|
|
83
|
+
await (0, diagramTools_1.handleToolCall)("add_edge", {
|
|
84
|
+
filePath: FILE_PATH,
|
|
85
|
+
sourceId: "Rectangle",
|
|
86
|
+
targetId: "GraphicObject",
|
|
87
|
+
sourcePointIndex: 0, // Top
|
|
88
|
+
targetPointIndex: 2, // Bottom
|
|
89
|
+
terminationEnd: "triangle-empty",
|
|
90
|
+
terminationStart: "none",
|
|
91
|
+
routingType: "orthogonal",
|
|
92
|
+
id: "edge_rect_extends"
|
|
93
|
+
});
|
|
94
|
+
// Aggregation: Group has GraphicObjects
|
|
95
|
+
// Group (z=4) is BELOW GraphicObject (z=-4)?
|
|
96
|
+
// No, Group is at z=4 (Down). GraphicObject is at z=-4 (Up).
|
|
97
|
+
// Connection: Group (Top/0) -> GraphicObject (Bottom/2) ??
|
|
98
|
+
// Actually Group "contains" GraphicObject.
|
|
99
|
+
// The Diamond is on the Group end.
|
|
100
|
+
// Source: Group. Target: GraphicObject.
|
|
101
|
+
// Diamond at Source. Arrow/None at Target.
|
|
102
|
+
await (0, diagramTools_1.handleToolCall)("add_edge", {
|
|
103
|
+
filePath: FILE_PATH,
|
|
104
|
+
sourceId: "Group",
|
|
105
|
+
targetId: "GraphicObject",
|
|
106
|
+
sourcePointIndex: 0, // Top
|
|
107
|
+
targetPointIndex: 2, // Bottom
|
|
108
|
+
terminationStart: "diamond-empty", // Aggregation at Source
|
|
109
|
+
terminationEnd: "arrow", // Association at Target (or none)
|
|
110
|
+
routingType: "orthogonal",
|
|
111
|
+
label: "contains",
|
|
112
|
+
id: "edge_group_aggregation"
|
|
113
|
+
});
|
|
114
|
+
// Composition: Canvas owns GraphicObjects?
|
|
115
|
+
// Or maybe Canvas owns a Root Group?
|
|
116
|
+
// Let's say Canvas has a list of Shapes.
|
|
117
|
+
// Canvas (8, -4) -> GraphicObject (0, -4).
|
|
118
|
+
// Canvas Left (3) -> GraphicObject Right (1).
|
|
119
|
+
await (0, diagramTools_1.handleToolCall)("add_edge", {
|
|
120
|
+
filePath: FILE_PATH,
|
|
121
|
+
sourceId: "Canvas",
|
|
122
|
+
targetId: "GraphicObject",
|
|
123
|
+
sourcePointIndex: 3, // Left
|
|
124
|
+
targetPointIndex: 1, // Right
|
|
125
|
+
terminationStart: "diamond-filled", // Composition (Canvas owns Objects)
|
|
126
|
+
terminationEnd: "none",
|
|
127
|
+
routingType: "straight",
|
|
128
|
+
label: "renders",
|
|
129
|
+
id: "edge_canvas_composition"
|
|
130
|
+
});
|
|
131
|
+
console.log("Class Diagram created successfully: " + FILE_PATH);
|
|
132
|
+
}
|
|
133
|
+
createClassDiagram().catch(err => {
|
|
134
|
+
console.error("Error creating class diagram:", err);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const diagramTools_1 = require("./tools/diagramTools");
|
|
4
|
+
async function createReference() {
|
|
5
|
+
const FILE_PATH = "edge_terminations_test.3duml";
|
|
6
|
+
console.log(`Creating diagram at ${FILE_PATH}...`);
|
|
7
|
+
// 1. Create Diagram
|
|
8
|
+
try {
|
|
9
|
+
await (0, diagramTools_1.handleToolCall)("create_new_diagram", { filePath: FILE_PATH });
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
console.log("File might exist, continuing...");
|
|
13
|
+
}
|
|
14
|
+
// 2. Add Layer
|
|
15
|
+
await (0, diagramTools_1.handleToolCall)("add_layer", { filePath: FILE_PATH, name: "Terminations" });
|
|
16
|
+
// 3. Define Types
|
|
17
|
+
const types = [
|
|
18
|
+
{ id: 'none', label: 'None', description: 'No visual marker' },
|
|
19
|
+
{ id: 'arrow', label: 'Arrow (Association)', description: 'Solid cone/V-shape' },
|
|
20
|
+
{ id: 'triangle-empty', label: 'Inheritance', description: 'Hollow triangle' },
|
|
21
|
+
{ id: 'triangle-empty-dashed', label: 'Realization', description: 'Hollow triangle (dashed edge)', isDashed: true },
|
|
22
|
+
{ id: 'diamond-filled', label: 'Composition', description: 'Filled diamond' },
|
|
23
|
+
{ id: 'diamond-empty', label: 'Aggregation', description: 'Hollow diamond' },
|
|
24
|
+
{ id: 'arrow-open', label: 'Dependency', description: 'Open V-shape (dashed)', isDashed: true },
|
|
25
|
+
{ id: 'crows-one', label: 'Crow One', description: 'Perpendicular bar |' },
|
|
26
|
+
{ id: 'crows-many', label: 'Crow Many', description: 'Trident |<' },
|
|
27
|
+
{ id: 'crows-zero-one', label: 'Crow Zero-One', description: 'Circle and bar O|' },
|
|
28
|
+
{ id: 'crows-zero-many', label: 'Crow Zero-Many', description: 'Circle and trident O|<' },
|
|
29
|
+
{ id: 'circle', label: 'Circle', description: 'Hollow circle' },
|
|
30
|
+
{ id: 'circle-plus', label: 'Socket', description: 'Circle with plus (Socket)' }
|
|
31
|
+
];
|
|
32
|
+
let z = 0;
|
|
33
|
+
const SPACING_Z = 3.5;
|
|
34
|
+
// Use manual IDs to avoid importing randomUUID
|
|
35
|
+
let counter = 0;
|
|
36
|
+
for (const t of types) {
|
|
37
|
+
const sourceId = `src_${t.id}`;
|
|
38
|
+
const targetId = `tgt_${t.id}`;
|
|
39
|
+
// Start Node
|
|
40
|
+
await (0, diagramTools_1.handleToolCall)("add_node", {
|
|
41
|
+
filePath: FILE_PATH,
|
|
42
|
+
label: `Start`,
|
|
43
|
+
id: sourceId,
|
|
44
|
+
x: -4, y: z,
|
|
45
|
+
width: 2, height: 1.5,
|
|
46
|
+
color: "#eeeeee"
|
|
47
|
+
});
|
|
48
|
+
// End Node
|
|
49
|
+
await (0, diagramTools_1.handleToolCall)("add_node", {
|
|
50
|
+
filePath: FILE_PATH,
|
|
51
|
+
label: `End`,
|
|
52
|
+
id: targetId,
|
|
53
|
+
x: 4, y: z,
|
|
54
|
+
width: 2, height: 1.5,
|
|
55
|
+
color: "#eeeeee"
|
|
56
|
+
});
|
|
57
|
+
// Description Note (Left side)
|
|
58
|
+
await (0, diagramTools_1.handleToolCall)("add_node", {
|
|
59
|
+
filePath: FILE_PATH,
|
|
60
|
+
label: `${t.label}\n${t.description}`,
|
|
61
|
+
shape: 'note',
|
|
62
|
+
color: '#ffffcc',
|
|
63
|
+
x: -10, y: z,
|
|
64
|
+
width: 4, height: 2,
|
|
65
|
+
id: `note_${t.id}`
|
|
66
|
+
});
|
|
67
|
+
// Edge
|
|
68
|
+
await (0, diagramTools_1.handleToolCall)("add_edge", {
|
|
69
|
+
filePath: FILE_PATH,
|
|
70
|
+
sourceId: sourceId,
|
|
71
|
+
targetId: targetId,
|
|
72
|
+
sourcePointIndex: 1, // Right
|
|
73
|
+
targetPointIndex: 3, // Left
|
|
74
|
+
label: t.id,
|
|
75
|
+
borderStyle: t.isDashed ? 'dashed' : 'solid',
|
|
76
|
+
terminationEnd: t.id,
|
|
77
|
+
terminationStart: 'none',
|
|
78
|
+
id: `edge_${t.id}`
|
|
79
|
+
});
|
|
80
|
+
z += SPACING_Z;
|
|
81
|
+
counter++;
|
|
82
|
+
}
|
|
83
|
+
console.log("Diagram created successfully: " + FILE_PATH);
|
|
84
|
+
}
|
|
85
|
+
createReference().catch(err => {
|
|
86
|
+
console.error("Error creating reference:", err);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
});
|
|
@@ -35,14 +35,32 @@ exports.toolDefinitions = [
|
|
|
35
35
|
properties: {
|
|
36
36
|
filePath: { type: "string" },
|
|
37
37
|
label: { type: "string" },
|
|
38
|
-
shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table"] },
|
|
38
|
+
shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table", "start-state", "end-state", "fork-join"] },
|
|
39
39
|
x: { type: "number", description: "Optional. defaults to auto-placement" },
|
|
40
40
|
y: { type: "number", description: "Optional. defaults to 0 or same as last node" },
|
|
41
41
|
z: { type: "number" },
|
|
42
42
|
width: { type: "number", description: "Default: 2. Min: 0.1, Max: 50" },
|
|
43
43
|
height: { type: "number", description: "Default: 1.5. Min: 0.1, Max: 50" },
|
|
44
44
|
layerId: { type: "string", description: "Optional layer ID, defaults to active layer" },
|
|
45
|
-
color: { type: "string" }
|
|
45
|
+
color: { type: "string" },
|
|
46
|
+
description: { type: "string", description: "Optional description or metadata for the node" },
|
|
47
|
+
tableData: {
|
|
48
|
+
type: "object",
|
|
49
|
+
description: "Required if shape is 'table'",
|
|
50
|
+
properties: {
|
|
51
|
+
rows: { type: "number" },
|
|
52
|
+
columns: { type: "number" },
|
|
53
|
+
hasRowHeader: { type: "boolean" },
|
|
54
|
+
hasColumnHeader: { type: "boolean" },
|
|
55
|
+
cells: {
|
|
56
|
+
type: "object",
|
|
57
|
+
description: "Map of 'row,col' string keys to cell content strings. E.g. {'0,0': 'Header'}"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
id: { type: "string", description: "Optional explicit ID" },
|
|
62
|
+
attributes: { type: "array", items: { type: "string" }, description: "Optional list of attributes for Class nodes" },
|
|
63
|
+
methods: { type: "array", items: { type: "string" }, description: "Optional list of methods for Class nodes" }
|
|
46
64
|
},
|
|
47
65
|
required: ["filePath", "label"]
|
|
48
66
|
}
|
|
@@ -60,7 +78,13 @@ exports.toolDefinitions = [
|
|
|
60
78
|
targetPointIndex: { type: "number", minimum: 0, maximum: 3 },
|
|
61
79
|
label: { type: "string" },
|
|
62
80
|
color: { type: "string", description: "Edge color (hex)" },
|
|
63
|
-
thickness: { type: "number", description: "Default: 0.01. Min: 0.005, Max: 0.5" }
|
|
81
|
+
thickness: { type: "number", description: "Default: 0.01. Min: 0.005, Max: 0.5" },
|
|
82
|
+
style: { type: "string", enum: ["line", "arrow-source", "arrow-target", "arrow-both"], description: "Visual style of arrowheads" },
|
|
83
|
+
routingType: { type: "string", enum: ["straight", "orthogonal"], description: "Type of path routing" },
|
|
84
|
+
borderStyle: { type: "string", enum: ["solid", "dashed", "dotted"], description: "Line pattern style" },
|
|
85
|
+
terminationStart: { type: "string", enum: ['none', 'arrow', 'triangle-empty', 'triangle-filled', 'diamond-empty', 'diamond-filled', 'circle', 'circle-plus', 'crows-one', 'crows-many', 'crows-zero-one', 'crows-zero-many'], description: "Start termination shape" },
|
|
86
|
+
terminationEnd: { type: "string", enum: ['none', 'arrow', 'triangle-empty', 'triangle-filled', 'diamond-empty', 'diamond-filled', 'circle', 'circle-plus', 'crows-one', 'crows-many', 'crows-zero-one', 'crows-zero-many'], description: "End termination shape" },
|
|
87
|
+
id: { type: "string", description: "Optional explicit ID" }
|
|
64
88
|
},
|
|
65
89
|
required: ["filePath", "sourceId", "targetId", "sourcePointIndex", "targetPointIndex"]
|
|
66
90
|
}
|
|
@@ -116,7 +140,7 @@ exports.toolDefinitions = [
|
|
|
116
140
|
},
|
|
117
141
|
{
|
|
118
142
|
name: "add_directional_node",
|
|
119
|
-
description: "Add a new node relative to an existing node and connect them. PRIMARY method for creating diagrams.",
|
|
143
|
+
description: "Add a new node relative to an existing node and connect them. PRIMARY method for creating diagrams, including Class and Database diagrams. Supports 'columns' for DB tables.",
|
|
120
144
|
inputSchema: {
|
|
121
145
|
type: "object",
|
|
122
146
|
properties: {
|
|
@@ -124,14 +148,90 @@ exports.toolDefinitions = [
|
|
|
124
148
|
sourceNodeId: { type: "string" },
|
|
125
149
|
direction: { type: "string", enum: ["UP", "DOWN", "LEFT", "RIGHT"], description: "Direction to place the new node relative to source." },
|
|
126
150
|
label: { type: "string" },
|
|
127
|
-
shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table"] },
|
|
151
|
+
shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table", "start-state", "end-state", "fork-join"] },
|
|
128
152
|
width: { type: "number", description: "Default: 2. Min: 0.1, Max: 50" },
|
|
129
153
|
height: { type: "number", description: "Default: 1.5. Min: 0.1, Max: 50" },
|
|
130
154
|
color: { type: "string" },
|
|
131
|
-
edgeLabel: { type: "string", description: "Optional label for the connecting edge" }
|
|
155
|
+
edgeLabel: { type: "string", description: "Optional label for the connecting edge" },
|
|
156
|
+
attributes: { type: "array", items: { type: "string" }, description: "Optional list of attributes for Class nodes" },
|
|
157
|
+
methods: { type: "array", items: { type: "string" }, description: "Optional list of methods for Class nodes" },
|
|
158
|
+
columns: {
|
|
159
|
+
type: "array",
|
|
160
|
+
description: "Optional list of columns for Table nodes",
|
|
161
|
+
items: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
name: { type: "string" },
|
|
165
|
+
type: { type: "string" },
|
|
166
|
+
isPk: { type: "boolean" },
|
|
167
|
+
isFk: { type: "boolean" }
|
|
168
|
+
},
|
|
169
|
+
required: ["name", "type"]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
132
172
|
},
|
|
133
173
|
required: ["filePath", "sourceNodeId", "direction", "label"]
|
|
134
174
|
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "create_uml_class",
|
|
178
|
+
description: "Create a UML Class node. Use this for the FIRST class. For connected classes, use 'add_directional_node' to ensure correct relative positioning.",
|
|
179
|
+
inputSchema: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {
|
|
182
|
+
filePath: { type: "string" },
|
|
183
|
+
className: { type: "string" },
|
|
184
|
+
attributes: { type: "array", items: { type: "string" }, description: "List of attributes e.g. ['- id: int', '+ name: string']" },
|
|
185
|
+
methods: { type: "array", items: { type: "string" }, description: "List of methods e.g. ['+ getName(): string', '+ save(): void']" },
|
|
186
|
+
x: { type: "number", description: "Optional X position" },
|
|
187
|
+
y: { type: "number", description: "Optional Y position (mapped to Z in 3DUML)" },
|
|
188
|
+
id: { type: "string", description: "Optional explicit ID" }
|
|
189
|
+
},
|
|
190
|
+
required: ["filePath", "className"]
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "create_db_table",
|
|
195
|
+
description: "Create a Database Entity (ERD Table). Use this for the FIRST table. For connected tables, use 'add_directional_node' with 'columns' to ensure correct relative positioning.",
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
filePath: { type: "string" },
|
|
200
|
+
tableName: { type: "string" },
|
|
201
|
+
columns: {
|
|
202
|
+
type: "array",
|
|
203
|
+
items: {
|
|
204
|
+
type: "object",
|
|
205
|
+
properties: {
|
|
206
|
+
name: { type: "string" },
|
|
207
|
+
type: { type: "string" },
|
|
208
|
+
isPk: { type: "boolean" },
|
|
209
|
+
isFk: { type: "boolean" }
|
|
210
|
+
},
|
|
211
|
+
required: ["name", "type"]
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
x: { type: "number" },
|
|
215
|
+
y: { type: "number" },
|
|
216
|
+
id: { type: "string", description: "Optional explicit ID" }
|
|
217
|
+
},
|
|
218
|
+
required: ["filePath", "tableName", "columns"]
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "connect_entities",
|
|
223
|
+
description: "Connect two entities semantically (e.g. Inherits, Composes)",
|
|
224
|
+
inputSchema: {
|
|
225
|
+
type: "object",
|
|
226
|
+
properties: {
|
|
227
|
+
filePath: { type: "string" },
|
|
228
|
+
sourceName: { type: "string", description: "Label of source node (e.g. Class Name)" },
|
|
229
|
+
targetName: { type: "string", description: "Label of target node" },
|
|
230
|
+
relationType: { type: "string", enum: ["association", "inheritance", "implementation", "dependency", "aggregation", "composition", "one-to-one", "one-to-many", "many-to-many"] },
|
|
231
|
+
label: { type: "string", description: "Optional edge label" }
|
|
232
|
+
},
|
|
233
|
+
required: ["filePath", "sourceName", "targetName", "relationType"]
|
|
234
|
+
}
|
|
135
235
|
}
|
|
136
236
|
];
|
|
137
237
|
async function handleToolCall(name, args) {
|
|
@@ -200,7 +300,7 @@ async function handleToolCall(name, args) {
|
|
|
200
300
|
}
|
|
201
301
|
}
|
|
202
302
|
const newNode = {
|
|
203
|
-
id: (0, crypto_1.randomUUID)(),
|
|
303
|
+
id: args.id || (0, crypto_1.randomUUID)(),
|
|
204
304
|
x: x,
|
|
205
305
|
y: 0.05,
|
|
206
306
|
z: z,
|
|
@@ -210,6 +310,12 @@ async function handleToolCall(name, args) {
|
|
|
210
310
|
shape: args.shape || "rectangle",
|
|
211
311
|
layerId: args.layerId || state.activeLayerId,
|
|
212
312
|
backgroundColor: args.color,
|
|
313
|
+
description: args.description,
|
|
314
|
+
tableData: args.tableData,
|
|
315
|
+
classData: (args.attributes || args.methods) ? {
|
|
316
|
+
attributes: args.attributes || [],
|
|
317
|
+
methods: args.methods || []
|
|
318
|
+
} : undefined,
|
|
213
319
|
textAlignVertical: 'center',
|
|
214
320
|
textAlignHorizontal: 'center'
|
|
215
321
|
};
|
|
@@ -306,6 +412,18 @@ async function handleToolCall(name, args) {
|
|
|
306
412
|
shape: args.shape || "rectangle",
|
|
307
413
|
layerId: sourceNode.layerId, // Inherit layer
|
|
308
414
|
backgroundColor: args.color || sourceNode.backgroundColor, // Inherit or new
|
|
415
|
+
classData: (args.attributes || args.methods) ? {
|
|
416
|
+
attributes: args.attributes || [],
|
|
417
|
+
methods: args.methods || []
|
|
418
|
+
} : undefined,
|
|
419
|
+
dbTableData: args.columns ? {
|
|
420
|
+
columns: args.columns.map((c) => ({
|
|
421
|
+
name: c.name,
|
|
422
|
+
type: c.type,
|
|
423
|
+
isPk: !!c.isPk,
|
|
424
|
+
isFk: !!c.isFk
|
|
425
|
+
}))
|
|
426
|
+
} : undefined,
|
|
309
427
|
textAlignVertical: 'center',
|
|
310
428
|
textAlignHorizontal: 'center'
|
|
311
429
|
};
|
|
@@ -338,18 +456,20 @@ async function handleToolCall(name, args) {
|
|
|
338
456
|
if (!targetExists)
|
|
339
457
|
throw new Error(`Target node ${args.targetId} not found`);
|
|
340
458
|
const newEdge = {
|
|
341
|
-
id: (0, crypto_1.randomUUID)(),
|
|
459
|
+
id: args.id || (0, crypto_1.randomUUID)(),
|
|
342
460
|
sourceId: args.sourceId,
|
|
343
461
|
targetId: args.targetId,
|
|
344
462
|
sourcePointIndex: Number(args.sourcePointIndex),
|
|
345
463
|
targetPointIndex: Number(args.targetPointIndex),
|
|
346
464
|
label: args.label,
|
|
347
|
-
style: 'line',
|
|
348
|
-
routingType: 'straight',
|
|
465
|
+
style: args.style || 'line',
|
|
466
|
+
routingType: args.routingType || 'straight',
|
|
349
467
|
color: args.color || '#000000',
|
|
350
468
|
thickness: Math.max(0.005, Math.min(0.5, args.thickness ? Number(args.thickness) : 0.01)),
|
|
351
469
|
fontSize: 20,
|
|
352
|
-
borderStyle: 'solid'
|
|
470
|
+
borderStyle: args.borderStyle || 'solid',
|
|
471
|
+
terminationStart: args.terminationStart,
|
|
472
|
+
terminationEnd: args.terminationEnd
|
|
353
473
|
};
|
|
354
474
|
state.edges.push(newEdge);
|
|
355
475
|
await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
|
|
@@ -420,6 +540,155 @@ async function handleToolCall(name, args) {
|
|
|
420
540
|
await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
|
|
421
541
|
return { content: [{ type: "text", text: `Added layer '${args.name}' (ID: ${newLayerId}) at Y=${position.y}` }] };
|
|
422
542
|
}
|
|
543
|
+
case "create_uml_class": {
|
|
544
|
+
const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
|
|
545
|
+
const newNodeId = args.id || (0, crypto_1.randomUUID)();
|
|
546
|
+
// Standard size for class
|
|
547
|
+
const width = 3;
|
|
548
|
+
// dynamic height?
|
|
549
|
+
const attrCount = (args.attributes || []).length;
|
|
550
|
+
const methodCount = (args.methods || []).length;
|
|
551
|
+
const estimatedHeight = 0.8 + (attrCount + methodCount) * 0.4;
|
|
552
|
+
const height = Math.max(2, estimatedHeight);
|
|
553
|
+
// Auto-placement logic if X/Y not provided
|
|
554
|
+
let x = args.x !== undefined ? Number(args.x) : 0;
|
|
555
|
+
let z = args.y !== undefined ? Number(args.y) : 0;
|
|
556
|
+
if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
|
|
557
|
+
const lastNode = state.nodes[state.nodes.length - 1];
|
|
558
|
+
x = lastNode.x + 5;
|
|
559
|
+
z = lastNode.z;
|
|
560
|
+
}
|
|
561
|
+
const newNode = {
|
|
562
|
+
id: newNodeId,
|
|
563
|
+
x: x,
|
|
564
|
+
y: 0,
|
|
565
|
+
z: z,
|
|
566
|
+
width: width,
|
|
567
|
+
height: height,
|
|
568
|
+
label: args.className,
|
|
569
|
+
shape: "class",
|
|
570
|
+
layerId: state.layers[0]?.id || (state.nodes.length > 0 ? state.nodes[0].layerId : "default"),
|
|
571
|
+
backgroundColor: "#ffffff",
|
|
572
|
+
classData: {
|
|
573
|
+
attributes: args.attributes || [],
|
|
574
|
+
methods: args.methods || []
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
state.nodes.push(newNode);
|
|
578
|
+
await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
|
|
579
|
+
return { content: [{ type: "text", text: `Created UML Class '${args.className}'` }] };
|
|
580
|
+
}
|
|
581
|
+
case "create_db_table": {
|
|
582
|
+
const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
|
|
583
|
+
const newNodeId = args.id || (0, crypto_1.randomUUID)();
|
|
584
|
+
const cols = args.columns || [];
|
|
585
|
+
// Header + items
|
|
586
|
+
const estimatedHeight = 0.8 + cols.length * 0.4;
|
|
587
|
+
const height = Math.max(2, estimatedHeight);
|
|
588
|
+
const width = 3;
|
|
589
|
+
// Auto-placement logic if X/Y not provided
|
|
590
|
+
let x = args.x !== undefined ? Number(args.x) : 0;
|
|
591
|
+
let z = args.y !== undefined ? Number(args.y) : 0;
|
|
592
|
+
if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
|
|
593
|
+
const lastNode = state.nodes[state.nodes.length - 1];
|
|
594
|
+
x = lastNode.x + 5;
|
|
595
|
+
z = lastNode.z;
|
|
596
|
+
}
|
|
597
|
+
const newNode = {
|
|
598
|
+
id: newNodeId,
|
|
599
|
+
x: x,
|
|
600
|
+
y: 0,
|
|
601
|
+
z: z,
|
|
602
|
+
width: width,
|
|
603
|
+
height: height,
|
|
604
|
+
label: args.tableName,
|
|
605
|
+
shape: "db-table",
|
|
606
|
+
layerId: state.layers[0]?.id || (state.nodes.length > 0 ? state.nodes[0].layerId : "default"),
|
|
607
|
+
backgroundColor: "#ffffff",
|
|
608
|
+
dbTableData: {
|
|
609
|
+
columns: cols.map((c) => ({
|
|
610
|
+
name: c.name,
|
|
611
|
+
type: c.type,
|
|
612
|
+
isPk: !!c.isPk,
|
|
613
|
+
isFk: !!c.isFk
|
|
614
|
+
}))
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
state.nodes.push(newNode);
|
|
618
|
+
await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
|
|
619
|
+
return { content: [{ type: "text", text: `Created DB Table '${args.tableName}'` }] };
|
|
620
|
+
}
|
|
621
|
+
case "connect_entities": {
|
|
622
|
+
const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
|
|
623
|
+
// Find nodes by Label (tolerant search?)
|
|
624
|
+
const sourceNode = state.nodes.find(n => n.label === args.sourceName);
|
|
625
|
+
const targetNode = state.nodes.find(n => n.label === args.targetName);
|
|
626
|
+
if (!sourceNode)
|
|
627
|
+
throw new Error(`Source entity '${args.sourceName}' not found`);
|
|
628
|
+
if (!targetNode)
|
|
629
|
+
throw new Error(`Target entity '${args.targetName}' not found`);
|
|
630
|
+
let terminationStart = 'none';
|
|
631
|
+
let terminationEnd = 'none';
|
|
632
|
+
let style = 'line';
|
|
633
|
+
let borderStyle = 'solid';
|
|
634
|
+
// Map relationship to terminations
|
|
635
|
+
switch (args.relationType) {
|
|
636
|
+
case 'inheritance':
|
|
637
|
+
terminationEnd = 'triangle-empty';
|
|
638
|
+
break;
|
|
639
|
+
case 'implementation':
|
|
640
|
+
terminationEnd = 'triangle-empty';
|
|
641
|
+
borderStyle = 'dashed';
|
|
642
|
+
break;
|
|
643
|
+
case 'dependency':
|
|
644
|
+
terminationEnd = 'arrow';
|
|
645
|
+
borderStyle = 'dashed';
|
|
646
|
+
break;
|
|
647
|
+
case 'association':
|
|
648
|
+
terminationEnd = 'none'; // simple line? or arrow? normally association is simple line OR arrow
|
|
649
|
+
// Let's use arrow for directed association if implied?
|
|
650
|
+
// Usually "connect A to B" implies direction.
|
|
651
|
+
terminationEnd = 'arrow';
|
|
652
|
+
break;
|
|
653
|
+
case 'aggregation':
|
|
654
|
+
terminationStart = 'diamond-empty';
|
|
655
|
+
break;
|
|
656
|
+
case 'composition':
|
|
657
|
+
terminationStart = 'diamond-filled';
|
|
658
|
+
break;
|
|
659
|
+
case 'one-to-many':
|
|
660
|
+
terminationStart = 'crows-one';
|
|
661
|
+
terminationEnd = 'crows-many';
|
|
662
|
+
break;
|
|
663
|
+
case 'many-to-many':
|
|
664
|
+
terminationStart = 'crows-many';
|
|
665
|
+
terminationEnd = 'crows-many';
|
|
666
|
+
break;
|
|
667
|
+
case 'one-to-one':
|
|
668
|
+
terminationStart = 'crows-one';
|
|
669
|
+
terminationEnd = 'crows-one';
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
const newEdge = {
|
|
673
|
+
id: (0, crypto_1.randomUUID)(),
|
|
674
|
+
sourceId: sourceNode.id,
|
|
675
|
+
targetId: targetNode.id,
|
|
676
|
+
sourcePointIndex: 2, // Bottom of source
|
|
677
|
+
targetPointIndex: 0, // Top of target (simple default)
|
|
678
|
+
label: args.label,
|
|
679
|
+
style: style,
|
|
680
|
+
routingType: 'orthogonal', // Semantic diagrams usually look better with orthogonal
|
|
681
|
+
color: '#000000',
|
|
682
|
+
thickness: 0.01,
|
|
683
|
+
fontSize: 20,
|
|
684
|
+
borderStyle: borderStyle,
|
|
685
|
+
terminationStart: terminationStart,
|
|
686
|
+
terminationEnd: terminationEnd
|
|
687
|
+
};
|
|
688
|
+
state.edges.push(newEdge);
|
|
689
|
+
await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
|
|
690
|
+
return { content: [{ type: "text", text: `Connected '${args.sourceName}' to '${args.targetName}' via ${args.relationType}` }] };
|
|
691
|
+
}
|
|
423
692
|
default:
|
|
424
693
|
throw new Error(`Unknown tool: ${name}`);
|
|
425
694
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
|
|
2
|
+
import { handleToolCall } from './tools/diagramTools';
|
|
3
|
+
|
|
4
|
+
async function createClassDiagram() {
|
|
5
|
+
const FILE_PATH = "class_diagram_example.3duml";
|
|
6
|
+
|
|
7
|
+
console.log(`Creating class diagram example at ${FILE_PATH}...`);
|
|
8
|
+
|
|
9
|
+
// 1. Create Diagram
|
|
10
|
+
try {
|
|
11
|
+
await handleToolCall("create_new_diagram", { filePath: FILE_PATH });
|
|
12
|
+
} catch (e) {
|
|
13
|
+
console.log("File might exist, continuing...");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 2. Add Layer
|
|
17
|
+
await handleToolCall("add_layer", { filePath: FILE_PATH, name: "Classes" });
|
|
18
|
+
|
|
19
|
+
// 3. Create Classes
|
|
20
|
+
|
|
21
|
+
// Abstract Class: GraphicObject
|
|
22
|
+
await handleToolCall("create_uml_class", {
|
|
23
|
+
filePath: FILE_PATH,
|
|
24
|
+
className: "GraphicObject",
|
|
25
|
+
attributes: ["x: int", "y: int"],
|
|
26
|
+
methods: ["move(dx: int, dy: int)", "draw()"],
|
|
27
|
+
x: 0, y: -4,
|
|
28
|
+
id: "GraphicObject"
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Subclass: Circle
|
|
32
|
+
await handleToolCall("create_uml_class", {
|
|
33
|
+
filePath: FILE_PATH,
|
|
34
|
+
className: "Circle",
|
|
35
|
+
attributes: ["radius: float"],
|
|
36
|
+
methods: ["draw()", "resize(scale: float)"],
|
|
37
|
+
x: -4, y: 0,
|
|
38
|
+
id: "Circle"
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Subclass: Rectangle
|
|
42
|
+
await handleToolCall("create_uml_class", {
|
|
43
|
+
filePath: FILE_PATH,
|
|
44
|
+
className: "Rectangle",
|
|
45
|
+
attributes: ["width: float", "height: float"],
|
|
46
|
+
methods: ["draw()", "resize(scale: float)"],
|
|
47
|
+
x: 4, y: 0,
|
|
48
|
+
id: "Rectangle"
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Container: Group
|
|
52
|
+
await handleToolCall("create_uml_class", {
|
|
53
|
+
filePath: FILE_PATH,
|
|
54
|
+
className: "Group",
|
|
55
|
+
attributes: [],
|
|
56
|
+
methods: ["add(obj: GraphicObject)", "remove(obj: GraphicObject)", "draw()"],
|
|
57
|
+
x: 0, y: 4,
|
|
58
|
+
id: "Group"
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Client: Canvas
|
|
62
|
+
await handleToolCall("create_uml_class", {
|
|
63
|
+
filePath: FILE_PATH,
|
|
64
|
+
className: "Canvas",
|
|
65
|
+
attributes: ["width: int", "height: int", "backgroundColor: string"],
|
|
66
|
+
methods: ["render()", "clear()"],
|
|
67
|
+
x: 8, y: -4,
|
|
68
|
+
id: "Canvas"
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// 4. Create Edges (Relationships)
|
|
72
|
+
|
|
73
|
+
// Inheritance: Circle -> GraphicObject
|
|
74
|
+
await handleToolCall("add_edge", {
|
|
75
|
+
filePath: FILE_PATH,
|
|
76
|
+
sourceId: "Circle",
|
|
77
|
+
targetId: "GraphicObject",
|
|
78
|
+
sourcePointIndex: 0, // Top
|
|
79
|
+
targetPointIndex: 3, // Left (or Bottom of GraphicObject depending on layout) -> GraphicObject is at -4 Z (UP). Circle at 0 Z.
|
|
80
|
+
// Circle (0,0) is BELOW GraphicObject (0, -4).
|
|
81
|
+
// So Circle TOP (0) connects to GraphicObject BOTTOM (2).
|
|
82
|
+
// Wait, Z is vertical in 2D top-down view?
|
|
83
|
+
// Logic: Z decreases going UP.
|
|
84
|
+
// GraphicObject (z=-4) is UP. Circle (z=0) is DOWN.
|
|
85
|
+
// Connect Circle Top (0) to GraphicObject Bottom (2).
|
|
86
|
+
terminationEnd: "triangle-empty", // Inheritance arrow on Parent
|
|
87
|
+
terminationStart: "none",
|
|
88
|
+
routingType: "orthogonal",
|
|
89
|
+
label: "extends",
|
|
90
|
+
id: "edge_circle_extends"
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Inheritance: Rectangle -> GraphicObject
|
|
94
|
+
await handleToolCall("add_edge", {
|
|
95
|
+
filePath: FILE_PATH,
|
|
96
|
+
sourceId: "Rectangle",
|
|
97
|
+
targetId: "GraphicObject",
|
|
98
|
+
sourcePointIndex: 0, // Top
|
|
99
|
+
targetPointIndex: 2, // Bottom
|
|
100
|
+
terminationEnd: "triangle-empty",
|
|
101
|
+
terminationStart: "none",
|
|
102
|
+
routingType: "orthogonal",
|
|
103
|
+
id: "edge_rect_extends"
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Aggregation: Group has GraphicObjects
|
|
107
|
+
// Group (z=4) is BELOW GraphicObject (z=-4)?
|
|
108
|
+
// No, Group is at z=4 (Down). GraphicObject is at z=-4 (Up).
|
|
109
|
+
// Connection: Group (Top/0) -> GraphicObject (Bottom/2) ??
|
|
110
|
+
// Actually Group "contains" GraphicObject.
|
|
111
|
+
// The Diamond is on the Group end.
|
|
112
|
+
// Source: Group. Target: GraphicObject.
|
|
113
|
+
// Diamond at Source. Arrow/None at Target.
|
|
114
|
+
await handleToolCall("add_edge", {
|
|
115
|
+
filePath: FILE_PATH,
|
|
116
|
+
sourceId: "Group",
|
|
117
|
+
targetId: "GraphicObject",
|
|
118
|
+
sourcePointIndex: 0, // Top
|
|
119
|
+
targetPointIndex: 2, // Bottom
|
|
120
|
+
terminationStart: "diamond-empty", // Aggregation at Source
|
|
121
|
+
terminationEnd: "arrow", // Association at Target (or none)
|
|
122
|
+
routingType: "orthogonal",
|
|
123
|
+
label: "contains",
|
|
124
|
+
id: "edge_group_aggregation"
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Composition: Canvas owns GraphicObjects?
|
|
128
|
+
// Or maybe Canvas owns a Root Group?
|
|
129
|
+
// Let's say Canvas has a list of Shapes.
|
|
130
|
+
// Canvas (8, -4) -> GraphicObject (0, -4).
|
|
131
|
+
// Canvas Left (3) -> GraphicObject Right (1).
|
|
132
|
+
await handleToolCall("add_edge", {
|
|
133
|
+
filePath: FILE_PATH,
|
|
134
|
+
sourceId: "Canvas",
|
|
135
|
+
targetId: "GraphicObject",
|
|
136
|
+
sourcePointIndex: 3, // Left
|
|
137
|
+
targetPointIndex: 1, // Right
|
|
138
|
+
terminationStart: "diamond-filled", // Composition (Canvas owns Objects)
|
|
139
|
+
terminationEnd: "none",
|
|
140
|
+
routingType: "straight",
|
|
141
|
+
label: "renders",
|
|
142
|
+
id: "edge_canvas_composition"
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
console.log("Class Diagram created successfully: " + FILE_PATH);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
createClassDiagram().catch(err => {
|
|
149
|
+
console.error("Error creating class diagram:", err);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
|
|
2
|
+
import { handleToolCall } from './tools/diagramTools';
|
|
3
|
+
|
|
4
|
+
interface TerminationType {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
description: string;
|
|
8
|
+
isDashed?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function createReference() {
|
|
12
|
+
const FILE_PATH = "edge_terminations_test.3duml";
|
|
13
|
+
|
|
14
|
+
console.log(`Creating diagram at ${FILE_PATH}...`);
|
|
15
|
+
|
|
16
|
+
// 1. Create Diagram
|
|
17
|
+
try {
|
|
18
|
+
await handleToolCall("create_new_diagram", { filePath: FILE_PATH });
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.log("File might exist, continuing...");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 2. Add Layer
|
|
24
|
+
await handleToolCall("add_layer", { filePath: FILE_PATH, name: "Terminations" });
|
|
25
|
+
|
|
26
|
+
// 3. Define Types
|
|
27
|
+
const types: TerminationType[] = [
|
|
28
|
+
{ id: 'none', label: 'None', description: 'No visual marker' },
|
|
29
|
+
{ id: 'arrow', label: 'Arrow (Association)', description: 'Solid cone/V-shape' },
|
|
30
|
+
{ id: 'triangle-empty', label: 'Inheritance', description: 'Hollow triangle' },
|
|
31
|
+
{ id: 'triangle-empty-dashed', label: 'Realization', description: 'Hollow triangle (dashed edge)', isDashed: true },
|
|
32
|
+
{ id: 'diamond-filled', label: 'Composition', description: 'Filled diamond' },
|
|
33
|
+
{ id: 'diamond-empty', label: 'Aggregation', description: 'Hollow diamond' },
|
|
34
|
+
{ id: 'arrow-open', label: 'Dependency', description: 'Open V-shape (dashed)', isDashed: true },
|
|
35
|
+
{ id: 'crows-one', label: 'Crow One', description: 'Perpendicular bar |' },
|
|
36
|
+
{ id: 'crows-many', label: 'Crow Many', description: 'Trident |<' },
|
|
37
|
+
{ id: 'crows-zero-one', label: 'Crow Zero-One', description: 'Circle and bar O|' },
|
|
38
|
+
{ id: 'crows-zero-many', label: 'Crow Zero-Many', description: 'Circle and trident O|<' },
|
|
39
|
+
{ id: 'circle', label: 'Circle', description: 'Hollow circle' },
|
|
40
|
+
{ id: 'circle-plus', label: 'Socket', description: 'Circle with plus (Socket)' }
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
let z = 0;
|
|
44
|
+
const SPACING_Z = 3.5;
|
|
45
|
+
|
|
46
|
+
// Use manual IDs to avoid importing randomUUID
|
|
47
|
+
let counter = 0;
|
|
48
|
+
|
|
49
|
+
for (const t of types) {
|
|
50
|
+
const sourceId = `src_${t.id}`;
|
|
51
|
+
const targetId = `tgt_${t.id}`;
|
|
52
|
+
|
|
53
|
+
// Start Node
|
|
54
|
+
await handleToolCall("add_node", {
|
|
55
|
+
filePath: FILE_PATH,
|
|
56
|
+
label: `Start`,
|
|
57
|
+
id: sourceId,
|
|
58
|
+
x: -4, y: z,
|
|
59
|
+
width: 2, height: 1.5,
|
|
60
|
+
color: "#eeeeee"
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// End Node
|
|
64
|
+
await handleToolCall("add_node", {
|
|
65
|
+
filePath: FILE_PATH,
|
|
66
|
+
label: `End`,
|
|
67
|
+
id: targetId,
|
|
68
|
+
x: 4, y: z,
|
|
69
|
+
width: 2, height: 1.5,
|
|
70
|
+
color: "#eeeeee"
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Description Note (Left side)
|
|
74
|
+
await handleToolCall("add_node", {
|
|
75
|
+
filePath: FILE_PATH,
|
|
76
|
+
label: `${t.label}\n${t.description}`,
|
|
77
|
+
shape: 'note',
|
|
78
|
+
color: '#ffffcc',
|
|
79
|
+
x: -10, y: z,
|
|
80
|
+
width: 4, height: 2,
|
|
81
|
+
id: `note_${t.id}`
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Edge
|
|
85
|
+
await handleToolCall("add_edge", {
|
|
86
|
+
filePath: FILE_PATH,
|
|
87
|
+
sourceId: sourceId,
|
|
88
|
+
targetId: targetId,
|
|
89
|
+
sourcePointIndex: 1, // Right
|
|
90
|
+
targetPointIndex: 3, // Left
|
|
91
|
+
label: t.id,
|
|
92
|
+
borderStyle: t.isDashed ? 'dashed' : 'solid',
|
|
93
|
+
terminationEnd: t.id,
|
|
94
|
+
terminationStart: 'none',
|
|
95
|
+
id: `edge_${t.id}`
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
z += SPACING_Z;
|
|
99
|
+
counter++;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log("Diagram created successfully: " + FILE_PATH);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
createReference().catch(err => {
|
|
106
|
+
console.error("Error creating reference:", err);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
});
|
|
@@ -35,14 +35,32 @@ export const toolDefinitions: Tool[] = [
|
|
|
35
35
|
properties: {
|
|
36
36
|
filePath: { type: "string" },
|
|
37
37
|
label: { type: "string" },
|
|
38
|
-
shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table"] },
|
|
38
|
+
shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table", "start-state", "end-state", "fork-join"] },
|
|
39
39
|
x: { type: "number", description: "Optional. defaults to auto-placement" },
|
|
40
40
|
y: { type: "number", description: "Optional. defaults to 0 or same as last node" },
|
|
41
41
|
z: { type: "number" },
|
|
42
42
|
width: { type: "number", description: "Default: 2. Min: 0.1, Max: 50" },
|
|
43
43
|
height: { type: "number", description: "Default: 1.5. Min: 0.1, Max: 50" },
|
|
44
44
|
layerId: { type: "string", description: "Optional layer ID, defaults to active layer" },
|
|
45
|
-
color: { type: "string" }
|
|
45
|
+
color: { type: "string" },
|
|
46
|
+
description: { type: "string", description: "Optional description or metadata for the node" },
|
|
47
|
+
tableData: {
|
|
48
|
+
type: "object",
|
|
49
|
+
description: "Required if shape is 'table'",
|
|
50
|
+
properties: {
|
|
51
|
+
rows: { type: "number" },
|
|
52
|
+
columns: { type: "number" },
|
|
53
|
+
hasRowHeader: { type: "boolean" },
|
|
54
|
+
hasColumnHeader: { type: "boolean" },
|
|
55
|
+
cells: {
|
|
56
|
+
type: "object",
|
|
57
|
+
description: "Map of 'row,col' string keys to cell content strings. E.g. {'0,0': 'Header'}"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
id: { type: "string", description: "Optional explicit ID" },
|
|
62
|
+
attributes: { type: "array", items: { type: "string" }, description: "Optional list of attributes for Class nodes" },
|
|
63
|
+
methods: { type: "array", items: { type: "string" }, description: "Optional list of methods for Class nodes" }
|
|
46
64
|
},
|
|
47
65
|
required: ["filePath", "label"]
|
|
48
66
|
}
|
|
@@ -60,7 +78,13 @@ export const toolDefinitions: Tool[] = [
|
|
|
60
78
|
targetPointIndex: { type: "number", minimum: 0, maximum: 3 },
|
|
61
79
|
label: { type: "string" },
|
|
62
80
|
color: { type: "string", description: "Edge color (hex)" },
|
|
63
|
-
thickness: { type: "number", description: "Default: 0.01. Min: 0.005, Max: 0.5" }
|
|
81
|
+
thickness: { type: "number", description: "Default: 0.01. Min: 0.005, Max: 0.5" },
|
|
82
|
+
style: { type: "string", enum: ["line", "arrow-source", "arrow-target", "arrow-both"], description: "Visual style of arrowheads" },
|
|
83
|
+
routingType: { type: "string", enum: ["straight", "orthogonal"], description: "Type of path routing" },
|
|
84
|
+
borderStyle: { type: "string", enum: ["solid", "dashed", "dotted"], description: "Line pattern style" },
|
|
85
|
+
terminationStart: { type: "string", enum: ['none', 'arrow', 'triangle-empty', 'triangle-filled', 'diamond-empty', 'diamond-filled', 'circle', 'circle-plus', 'crows-one', 'crows-many', 'crows-zero-one', 'crows-zero-many'], description: "Start termination shape" },
|
|
86
|
+
terminationEnd: { type: "string", enum: ['none', 'arrow', 'triangle-empty', 'triangle-filled', 'diamond-empty', 'diamond-filled', 'circle', 'circle-plus', 'crows-one', 'crows-many', 'crows-zero-one', 'crows-zero-many'], description: "End termination shape" },
|
|
87
|
+
id: { type: "string", description: "Optional explicit ID" }
|
|
64
88
|
},
|
|
65
89
|
required: ["filePath", "sourceId", "targetId", "sourcePointIndex", "targetPointIndex"]
|
|
66
90
|
}
|
|
@@ -116,7 +140,7 @@ export const toolDefinitions: Tool[] = [
|
|
|
116
140
|
},
|
|
117
141
|
{
|
|
118
142
|
name: "add_directional_node",
|
|
119
|
-
description: "Add a new node relative to an existing node and connect them. PRIMARY method for creating diagrams.",
|
|
143
|
+
description: "Add a new node relative to an existing node and connect them. PRIMARY method for creating diagrams, including Class and Database diagrams. Supports 'columns' for DB tables.",
|
|
120
144
|
inputSchema: {
|
|
121
145
|
type: "object",
|
|
122
146
|
properties: {
|
|
@@ -124,14 +148,90 @@ export const toolDefinitions: Tool[] = [
|
|
|
124
148
|
sourceNodeId: { type: "string" },
|
|
125
149
|
direction: { type: "string", enum: ["UP", "DOWN", "LEFT", "RIGHT"], description: "Direction to place the new node relative to source." },
|
|
126
150
|
label: { type: "string" },
|
|
127
|
-
shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table"] },
|
|
151
|
+
shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table", "start-state", "end-state", "fork-join"] },
|
|
128
152
|
width: { type: "number", description: "Default: 2. Min: 0.1, Max: 50" },
|
|
129
153
|
height: { type: "number", description: "Default: 1.5. Min: 0.1, Max: 50" },
|
|
130
154
|
color: { type: "string" },
|
|
131
|
-
edgeLabel: { type: "string", description: "Optional label for the connecting edge" }
|
|
155
|
+
edgeLabel: { type: "string", description: "Optional label for the connecting edge" },
|
|
156
|
+
attributes: { type: "array", items: { type: "string" }, description: "Optional list of attributes for Class nodes" },
|
|
157
|
+
methods: { type: "array", items: { type: "string" }, description: "Optional list of methods for Class nodes" },
|
|
158
|
+
columns: {
|
|
159
|
+
type: "array",
|
|
160
|
+
description: "Optional list of columns for Table nodes",
|
|
161
|
+
items: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
name: { type: "string" },
|
|
165
|
+
type: { type: "string" },
|
|
166
|
+
isPk: { type: "boolean" },
|
|
167
|
+
isFk: { type: "boolean" }
|
|
168
|
+
},
|
|
169
|
+
required: ["name", "type"]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
132
172
|
},
|
|
133
173
|
required: ["filePath", "sourceNodeId", "direction", "label"]
|
|
134
174
|
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "create_uml_class",
|
|
178
|
+
description: "Create a UML Class node. Use this for the FIRST class. For connected classes, use 'add_directional_node' to ensure correct relative positioning.",
|
|
179
|
+
inputSchema: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {
|
|
182
|
+
filePath: { type: "string" },
|
|
183
|
+
className: { type: "string" },
|
|
184
|
+
attributes: { type: "array", items: { type: "string" }, description: "List of attributes e.g. ['- id: int', '+ name: string']" },
|
|
185
|
+
methods: { type: "array", items: { type: "string" }, description: "List of methods e.g. ['+ getName(): string', '+ save(): void']" },
|
|
186
|
+
x: { type: "number", description: "Optional X position" },
|
|
187
|
+
y: { type: "number", description: "Optional Y position (mapped to Z in 3DUML)" },
|
|
188
|
+
id: { type: "string", description: "Optional explicit ID" }
|
|
189
|
+
},
|
|
190
|
+
required: ["filePath", "className"]
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "create_db_table",
|
|
195
|
+
description: "Create a Database Entity (ERD Table). Use this for the FIRST table. For connected tables, use 'add_directional_node' with 'columns' to ensure correct relative positioning.",
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
filePath: { type: "string" },
|
|
200
|
+
tableName: { type: "string" },
|
|
201
|
+
columns: {
|
|
202
|
+
type: "array",
|
|
203
|
+
items: {
|
|
204
|
+
type: "object",
|
|
205
|
+
properties: {
|
|
206
|
+
name: { type: "string" },
|
|
207
|
+
type: { type: "string" },
|
|
208
|
+
isPk: { type: "boolean" },
|
|
209
|
+
isFk: { type: "boolean" }
|
|
210
|
+
},
|
|
211
|
+
required: ["name", "type"]
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
x: { type: "number" },
|
|
215
|
+
y: { type: "number" },
|
|
216
|
+
id: { type: "string", description: "Optional explicit ID" }
|
|
217
|
+
},
|
|
218
|
+
required: ["filePath", "tableName", "columns"]
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "connect_entities",
|
|
223
|
+
description: "Connect two entities semantically (e.g. Inherits, Composes)",
|
|
224
|
+
inputSchema: {
|
|
225
|
+
type: "object",
|
|
226
|
+
properties: {
|
|
227
|
+
filePath: { type: "string" },
|
|
228
|
+
sourceName: { type: "string", description: "Label of source node (e.g. Class Name)" },
|
|
229
|
+
targetName: { type: "string", description: "Label of target node" },
|
|
230
|
+
relationType: { type: "string", enum: ["association", "inheritance", "implementation", "dependency", "aggregation", "composition", "one-to-one", "one-to-many", "many-to-many"] },
|
|
231
|
+
label: { type: "string", description: "Optional edge label" }
|
|
232
|
+
},
|
|
233
|
+
required: ["filePath", "sourceName", "targetName", "relationType"]
|
|
234
|
+
}
|
|
135
235
|
}
|
|
136
236
|
];
|
|
137
237
|
|
|
@@ -205,7 +305,7 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
|
|
|
205
305
|
}
|
|
206
306
|
|
|
207
307
|
const newNode: NodeData = {
|
|
208
|
-
id: randomUUID(),
|
|
308
|
+
id: args.id || randomUUID(),
|
|
209
309
|
x: x,
|
|
210
310
|
y: 0.05,
|
|
211
311
|
z: z,
|
|
@@ -215,6 +315,12 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
|
|
|
215
315
|
shape: args.shape || "rectangle",
|
|
216
316
|
layerId: args.layerId || state.activeLayerId,
|
|
217
317
|
backgroundColor: args.color,
|
|
318
|
+
description: args.description,
|
|
319
|
+
tableData: args.tableData,
|
|
320
|
+
classData: (args.attributes || args.methods) ? {
|
|
321
|
+
attributes: args.attributes || [],
|
|
322
|
+
methods: args.methods || []
|
|
323
|
+
} : undefined,
|
|
218
324
|
textAlignVertical: 'center',
|
|
219
325
|
textAlignHorizontal: 'center'
|
|
220
326
|
};
|
|
@@ -321,6 +427,18 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
|
|
|
321
427
|
shape: args.shape || "rectangle",
|
|
322
428
|
layerId: sourceNode.layerId, // Inherit layer
|
|
323
429
|
backgroundColor: args.color || sourceNode.backgroundColor, // Inherit or new
|
|
430
|
+
classData: (args.attributes || args.methods) ? {
|
|
431
|
+
attributes: args.attributes || [],
|
|
432
|
+
methods: args.methods || []
|
|
433
|
+
} : undefined,
|
|
434
|
+
dbTableData: args.columns ? {
|
|
435
|
+
columns: args.columns.map((c: any) => ({
|
|
436
|
+
name: c.name,
|
|
437
|
+
type: c.type,
|
|
438
|
+
isPk: !!c.isPk,
|
|
439
|
+
isFk: !!c.isFk
|
|
440
|
+
}))
|
|
441
|
+
} : undefined,
|
|
324
442
|
textAlignVertical: 'center',
|
|
325
443
|
textAlignHorizontal: 'center'
|
|
326
444
|
};
|
|
@@ -355,18 +473,20 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
|
|
|
355
473
|
if (!targetExists) throw new Error(`Target node ${args.targetId} not found`);
|
|
356
474
|
|
|
357
475
|
const newEdge: EdgeData = {
|
|
358
|
-
id: randomUUID(),
|
|
476
|
+
id: args.id || randomUUID(),
|
|
359
477
|
sourceId: args.sourceId,
|
|
360
478
|
targetId: args.targetId,
|
|
361
479
|
sourcePointIndex: Number(args.sourcePointIndex),
|
|
362
480
|
targetPointIndex: Number(args.targetPointIndex),
|
|
363
481
|
label: args.label,
|
|
364
|
-
style: 'line',
|
|
365
|
-
routingType: 'straight',
|
|
482
|
+
style: args.style || 'line',
|
|
483
|
+
routingType: args.routingType || 'straight',
|
|
366
484
|
color: args.color || '#000000',
|
|
367
485
|
thickness: Math.max(0.005, Math.min(0.5, args.thickness ? Number(args.thickness) : 0.01)),
|
|
368
486
|
fontSize: 20,
|
|
369
|
-
borderStyle: 'solid'
|
|
487
|
+
borderStyle: args.borderStyle || 'solid',
|
|
488
|
+
terminationStart: args.terminationStart,
|
|
489
|
+
terminationEnd: args.terminationEnd
|
|
370
490
|
};
|
|
371
491
|
state.edges.push(newEdge);
|
|
372
492
|
await saveDiagramFile(args.filePath, state);
|
|
@@ -434,6 +554,170 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
|
|
|
434
554
|
await saveDiagramFile(args.filePath, state);
|
|
435
555
|
return { content: [{ type: "text", text: `Added layer '${args.name}' (ID: ${newLayerId}) at Y=${position.y}` }] };
|
|
436
556
|
}
|
|
557
|
+
case "create_uml_class": {
|
|
558
|
+
const state = await readDiagramFile(args.filePath);
|
|
559
|
+
const newNodeId = args.id || randomUUID();
|
|
560
|
+
|
|
561
|
+
// Standard size for class
|
|
562
|
+
const width = 3;
|
|
563
|
+
// dynamic height?
|
|
564
|
+
const attrCount = (args.attributes || []).length;
|
|
565
|
+
const methodCount = (args.methods || []).length;
|
|
566
|
+
const estimatedHeight = 0.8 + (attrCount + methodCount) * 0.4;
|
|
567
|
+
const height = Math.max(2, estimatedHeight);
|
|
568
|
+
|
|
569
|
+
// Auto-placement logic if X/Y not provided
|
|
570
|
+
let x = args.x !== undefined ? Number(args.x) : 0;
|
|
571
|
+
let z = args.y !== undefined ? Number(args.y) : 0;
|
|
572
|
+
|
|
573
|
+
if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
|
|
574
|
+
const lastNode = state.nodes[state.nodes.length - 1];
|
|
575
|
+
x = lastNode.x + 5;
|
|
576
|
+
z = lastNode.z;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const newNode: NodeData = {
|
|
580
|
+
id: newNodeId,
|
|
581
|
+
x: x,
|
|
582
|
+
y: 0,
|
|
583
|
+
z: z,
|
|
584
|
+
width: width,
|
|
585
|
+
height: height,
|
|
586
|
+
label: args.className,
|
|
587
|
+
shape: "class",
|
|
588
|
+
layerId: state.layers[0]?.id || (state.nodes.length > 0 ? state.nodes[0].layerId : "default"),
|
|
589
|
+
backgroundColor: "#ffffff",
|
|
590
|
+
classData: {
|
|
591
|
+
attributes: args.attributes || [],
|
|
592
|
+
methods: args.methods || []
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
state.nodes.push(newNode);
|
|
597
|
+
await saveDiagramFile(args.filePath, state);
|
|
598
|
+
return { content: [{ type: "text", text: `Created UML Class '${args.className}'` }] };
|
|
599
|
+
}
|
|
600
|
+
case "create_db_table": {
|
|
601
|
+
const state = await readDiagramFile(args.filePath);
|
|
602
|
+
const newNodeId = args.id || randomUUID();
|
|
603
|
+
|
|
604
|
+
const cols = args.columns || [];
|
|
605
|
+
// Header + items
|
|
606
|
+
const estimatedHeight = 0.8 + cols.length * 0.4;
|
|
607
|
+
const height = Math.max(2, estimatedHeight);
|
|
608
|
+
|
|
609
|
+
const width = 3;
|
|
610
|
+
|
|
611
|
+
// Auto-placement logic if X/Y not provided
|
|
612
|
+
let x = args.x !== undefined ? Number(args.x) : 0;
|
|
613
|
+
let z = args.y !== undefined ? Number(args.y) : 0;
|
|
614
|
+
|
|
615
|
+
if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
|
|
616
|
+
const lastNode = state.nodes[state.nodes.length - 1];
|
|
617
|
+
x = lastNode.x + 5;
|
|
618
|
+
z = lastNode.z;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const newNode: NodeData = {
|
|
622
|
+
id: newNodeId,
|
|
623
|
+
x: x,
|
|
624
|
+
y: 0,
|
|
625
|
+
z: z,
|
|
626
|
+
width: width,
|
|
627
|
+
height: height,
|
|
628
|
+
label: args.tableName,
|
|
629
|
+
shape: "db-table",
|
|
630
|
+
layerId: state.layers[0]?.id || (state.nodes.length > 0 ? state.nodes[0].layerId : "default"),
|
|
631
|
+
backgroundColor: "#ffffff",
|
|
632
|
+
dbTableData: {
|
|
633
|
+
columns: cols.map((c: any) => ({
|
|
634
|
+
name: c.name,
|
|
635
|
+
type: c.type,
|
|
636
|
+
isPk: !!c.isPk,
|
|
637
|
+
isFk: !!c.isFk
|
|
638
|
+
}))
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
state.nodes.push(newNode);
|
|
643
|
+
await saveDiagramFile(args.filePath, state);
|
|
644
|
+
return { content: [{ type: "text", text: `Created DB Table '${args.tableName}'` }] };
|
|
645
|
+
}
|
|
646
|
+
case "connect_entities": {
|
|
647
|
+
const state = await readDiagramFile(args.filePath);
|
|
648
|
+
|
|
649
|
+
// Find nodes by Label (tolerant search?)
|
|
650
|
+
const sourceNode = state.nodes.find(n => n.label === args.sourceName);
|
|
651
|
+
const targetNode = state.nodes.find(n => n.label === args.targetName);
|
|
652
|
+
|
|
653
|
+
if (!sourceNode) throw new Error(`Source entity '${args.sourceName}' not found`);
|
|
654
|
+
if (!targetNode) throw new Error(`Target entity '${args.targetName}' not found`);
|
|
655
|
+
|
|
656
|
+
let terminationStart = 'none';
|
|
657
|
+
let terminationEnd = 'none';
|
|
658
|
+
let style: 'line' | 'arrow-source' | 'arrow-target' | 'arrow-both' = 'line';
|
|
659
|
+
let borderStyle = 'solid';
|
|
660
|
+
|
|
661
|
+
// Map relationship to terminations
|
|
662
|
+
switch (args.relationType) {
|
|
663
|
+
case 'inheritance':
|
|
664
|
+
terminationEnd = 'triangle-empty';
|
|
665
|
+
break;
|
|
666
|
+
case 'implementation':
|
|
667
|
+
terminationEnd = 'triangle-empty';
|
|
668
|
+
borderStyle = 'dashed';
|
|
669
|
+
break;
|
|
670
|
+
case 'dependency':
|
|
671
|
+
terminationEnd = 'arrow';
|
|
672
|
+
borderStyle = 'dashed';
|
|
673
|
+
break;
|
|
674
|
+
case 'association':
|
|
675
|
+
terminationEnd = 'none'; // simple line? or arrow? normally association is simple line OR arrow
|
|
676
|
+
// Let's use arrow for directed association if implied?
|
|
677
|
+
// Usually "connect A to B" implies direction.
|
|
678
|
+
terminationEnd = 'arrow';
|
|
679
|
+
break;
|
|
680
|
+
case 'aggregation':
|
|
681
|
+
terminationStart = 'diamond-empty';
|
|
682
|
+
break;
|
|
683
|
+
case 'composition':
|
|
684
|
+
terminationStart = 'diamond-filled';
|
|
685
|
+
break;
|
|
686
|
+
case 'one-to-many':
|
|
687
|
+
terminationStart = 'crows-one';
|
|
688
|
+
terminationEnd = 'crows-many';
|
|
689
|
+
break;
|
|
690
|
+
case 'many-to-many':
|
|
691
|
+
terminationStart = 'crows-many';
|
|
692
|
+
terminationEnd = 'crows-many';
|
|
693
|
+
break;
|
|
694
|
+
case 'one-to-one':
|
|
695
|
+
terminationStart = 'crows-one';
|
|
696
|
+
terminationEnd = 'crows-one';
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const newEdge: EdgeData = {
|
|
701
|
+
id: randomUUID(),
|
|
702
|
+
sourceId: sourceNode.id,
|
|
703
|
+
targetId: targetNode.id,
|
|
704
|
+
sourcePointIndex: 2, // Bottom of source
|
|
705
|
+
targetPointIndex: 0, // Top of target (simple default)
|
|
706
|
+
label: args.label,
|
|
707
|
+
style: style,
|
|
708
|
+
routingType: 'orthogonal', // Semantic diagrams usually look better with orthogonal
|
|
709
|
+
color: '#000000',
|
|
710
|
+
thickness: 0.01,
|
|
711
|
+
fontSize: 20,
|
|
712
|
+
borderStyle: borderStyle as any,
|
|
713
|
+
terminationStart: terminationStart as any,
|
|
714
|
+
terminationEnd: terminationEnd as any
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
state.edges.push(newEdge);
|
|
718
|
+
await saveDiagramFile(args.filePath, state);
|
|
719
|
+
return { content: [{ type: "text", text: `Connected '${args.sourceName}' to '${args.targetName}' via ${args.relationType}` }] };
|
|
720
|
+
}
|
|
437
721
|
default:
|
|
438
722
|
throw new Error(`Unknown tool: ${name}`);
|
|
439
723
|
}
|
package/src/types.ts
CHANGED
|
@@ -6,32 +6,46 @@ export interface NodeData {
|
|
|
6
6
|
z: number;
|
|
7
7
|
width: number;
|
|
8
8
|
height: number;
|
|
9
|
-
layerId: string;
|
|
10
9
|
label: string;
|
|
11
|
-
shape?: 'rectangle' | 'rounded' | 'ellipse' | 'diamond' | 'actor' | 'cylinder' | 'note' | 'cloud' | 'class' | 'text' | 'table';
|
|
12
|
-
|
|
10
|
+
shape?: 'rectangle' | 'rounded' | 'ellipse' | 'diamond' | 'actor' | 'cylinder' | 'note' | 'cloud' | 'class' | 'text' | 'table' | 'start-state' | 'end-state' | 'fork-join' | 'db-table';
|
|
11
|
+
layerId?: string;
|
|
12
|
+
backgroundColor?: string;
|
|
13
13
|
borderColor?: string;
|
|
14
14
|
borderWidth?: number;
|
|
15
15
|
borderStyle?: 'solid' | 'dashed' | 'dotted';
|
|
16
|
-
|
|
16
|
+
textColor?: string;
|
|
17
|
+
fontSize?: number;
|
|
17
18
|
opacity?: number;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
parentId?: string;
|
|
21
|
-
imageUrl?: string;
|
|
19
|
+
|
|
20
|
+
// Font styles
|
|
22
21
|
isBold?: boolean;
|
|
23
22
|
isItalic?: boolean;
|
|
24
23
|
isUnderline?: boolean;
|
|
25
24
|
isStrikethrough?: boolean;
|
|
26
25
|
isVerticalText?: boolean;
|
|
26
|
+
textAlignment?: 'left' | 'center' | 'right';
|
|
27
|
+
textAlignHorizontal?: 'left' | 'center' | 'right';
|
|
28
|
+
textVerticalAlignment?: 'top' | 'center' | 'bottom';
|
|
29
|
+
textAlignVertical?: 'top' | 'center' | 'bottom';
|
|
30
|
+
|
|
27
31
|
description?: string;
|
|
28
32
|
url?: string;
|
|
33
|
+
imageUrl?: string;
|
|
34
|
+
parentId?: string;
|
|
35
|
+
|
|
29
36
|
tableData?: {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
rows: number;
|
|
38
|
+
columns: number;
|
|
39
|
+
hasRowHeader: boolean;
|
|
40
|
+
hasColumnHeader: boolean;
|
|
41
|
+
cells?: Record<string, string>;
|
|
42
|
+
};
|
|
43
|
+
classData?: {
|
|
44
|
+
methods: string[];
|
|
45
|
+
attributes: string[];
|
|
46
|
+
};
|
|
47
|
+
dbTableData?: {
|
|
48
|
+
columns: { name: string; type: string; isPk: boolean; isFk: boolean }[];
|
|
35
49
|
};
|
|
36
50
|
}
|
|
37
51
|
|
|
@@ -54,6 +68,8 @@ export interface EdgeData {
|
|
|
54
68
|
isItalic?: boolean;
|
|
55
69
|
isUnderline?: boolean;
|
|
56
70
|
isStrikethrough?: boolean;
|
|
71
|
+
terminationStart?: 'none' | 'arrow' | 'triangle-empty' | 'triangle-filled' | 'diamond-empty' | 'diamond-filled' | 'circle' | 'circle-plus' | 'crows-one' | 'crows-many' | 'crows-zero-one' | 'crows-zero-many';
|
|
72
|
+
terminationEnd?: 'none' | 'arrow' | 'triangle-empty' | 'triangle-filled' | 'diamond-empty' | 'diamond-filled' | 'circle' | 'circle-plus' | 'crows-one' | 'crows-many' | 'crows-zero-one' | 'crows-zero-many';
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
export interface LayerData {
|