@eventcatalog/core 5.6.1 → 5.7.1
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/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-LVOGLB6C.js → chunk-36XVRD36.js} +1 -1
- package/dist/{chunk-N7LDFUEB.js → chunk-AXWTR2FV.js} +1 -1
- package/dist/{chunk-QJXVAM76.js → chunk-CPF5WE3R.js} +1 -1
- package/dist/{chunk-2NALBBWH.js → chunk-GRC67WHC.js} +1 -1
- package/dist/{chunk-L63ED43X.js → chunk-HZ3XZKUT.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +596 -122
- package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +3 -2
- package/eventcatalog/src/components/ChatPanel/assistant-events.ts +37 -0
- package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +21 -1
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +2 -1
- package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +2 -34
- package/eventcatalog/src/features/ai/chat-api.ts +24 -5
- package/eventcatalog/src/features/mcp/apps/generated/canvas.html +75 -74
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +73 -73
- package/eventcatalog/src/features/studio/agent-choreography.ts +84 -16
- package/eventcatalog/src/features/studio/api/canvases-api.ts +3 -0
- package/eventcatalog/src/features/studio/canvas-actions.ts +330 -15
- package/eventcatalog/src/features/studio/catalog.ts +5 -3
- package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +14 -0
- package/eventcatalog/src/features/studio/components/NodeDetails.tsx +4 -0
- package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +165 -18
- package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +9 -4
- package/eventcatalog/src/features/studio/pages/[id].astro +2 -0
- package/eventcatalog/src/features/studio/server/assistant-tools.ts +207 -0
- package/eventcatalog/src/features/studio/server/canvas-agent.ts +27 -0
- package/eventcatalog/src/features/studio/server/canvas-mcp.ts +2 -14
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +4 -0
- package/eventcatalog/src/utils/node-graphs/compact-visualiser-graph.ts +14 -0
- package/eventcatalog/src/utils/node-graphs/node-resource.ts +26 -0
- package/eventcatalog/src/utils/resource-reference-icons.ts +37 -0
- package/package.json +7 -7
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { tool } from 'ai';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getCanvasMaps, getLayoutPositions, readCanvas } from '@eventcatalog/visualiser/studio';
|
|
4
|
+
import {
|
|
5
|
+
ADD_TO_CANVAS_DESCRIPTION,
|
|
6
|
+
describeCanvas,
|
|
7
|
+
edgeSpecsSchema,
|
|
8
|
+
indexCatalog,
|
|
9
|
+
nodeSpecsSchema,
|
|
10
|
+
withNotesPlaced,
|
|
11
|
+
} from '../canvas-actions';
|
|
12
|
+
import { getCatalogResources } from '../catalog-resources';
|
|
13
|
+
import { playAddToCanvas, playConnectAll, playHotspot, playLayout, playRemove, playUpdateNode } from '../agent-choreography';
|
|
14
|
+
import { getAgent } from '../agents';
|
|
15
|
+
import { agentOnCanvas } from './canvas-agent';
|
|
16
|
+
import { findStudioRuntime } from './runtime';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The assistant (Ask AI) on a Studio canvas: the canvas it was opened on, drawn on with the model the catalog has
|
|
20
|
+
* configured. Its tools play changes out like the MCP canvas tools do (same choreography), so everyone on the canvas
|
|
21
|
+
* watches the assistant's pointer draw the diagram.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const ASSISTANT = getAgent('EventCatalog AI');
|
|
25
|
+
// Someone's waiting on the assistant in the chat: it draws about three times as fast as an MCP agent
|
|
26
|
+
const ASSISTANT_PACE = 0.3;
|
|
27
|
+
|
|
28
|
+
/** What the assistant is told when it's opened on a canvas, after its usual instructions */
|
|
29
|
+
const CANVAS_ASSISTANT_PROMPT = `
|
|
30
|
+
The user is on an EventCatalog Studio canvas: a live, collaborative diagram. They opened you there to draw on it with them.
|
|
31
|
+
When they ask you to diagram, design, map out, sketch or change something, draw it on the canvas with the canvas tools; don't answer with Mermaid or text diagrams.
|
|
32
|
+
|
|
33
|
+
If you need to ask the user something first (what to diagram, or a request too vague to draw), ask straight away, without calling any tools.
|
|
34
|
+
|
|
35
|
+
How to draw:
|
|
36
|
+
1. Read the canvas before you change it (getCanvas), so you build on what's there and don't add what's already on it.
|
|
37
|
+
2. Find what they asked about in the catalog (getResources, getResource, getMessagesProducedOrConsumedByResource, getArchitectureDiagramAsMermaid) and add those resources by collection and id. Catalog resources are connected to the related resources already on the canvas for you.
|
|
38
|
+
- A domain or system from the catalog: add it with contents: true, which brings everything in it at once. Don't look up and add its services and messages yourself.
|
|
39
|
+
- A service or message with what it connects to: add it with connections (e.g. ["sends", "receives"]), rather than finding each one.
|
|
40
|
+
3. Only add new components (type, name, summary) for what isn't in the catalog yet, e.g. a proposed service or event when the user is designing something new.
|
|
41
|
+
4. Add things in one or a few addToCanvas calls (with refs and edges), rather than one call per node. Use containers (domains, systems) when the diagram covers more than one of them.
|
|
42
|
+
5. Don't lay the canvas out yourself: when you've finished, the canvas is tidied for you if you added things or connections. Only call layoutCanvas when the user asks you to tidy up.
|
|
43
|
+
|
|
44
|
+
Explaining the diagram on the canvas:
|
|
45
|
+
- Hotspots flag something people should know about a node, or a place on the canvas: a risk, a problem or something worth knowing (addHotspot: critical, warning or info, with a short title and why it matters). Use them when the user asks to flag, highlight or review things, e.g. single points of failure.
|
|
46
|
+
- Sticky notes hold free text on the canvas: add a node with type "note" and its text in summary. Use them for notes, assumptions, open questions, suggestions and explanations. Keep each one very short and precise, like a sticky note on a whiteboard: one point in a few words (at most about 12), e.g. "Kafka: ordered, replayable order events". Add as many as are useful, one point per note, each beside the part of the diagram it's about: give it x/y about 300 to the right of, or 250 above, that node's centre (from getCanvas, after you've drawn), clear of other nodes. Add notes and text after the nodes they're about, so you know where those are. A note or text that would land on a node is moved to the nearest clear spot beside it, and notes stay beside their node when the canvas is tidied.
|
|
47
|
+
- Text is words written straight on the canvas, without a note around them: add a node with type "text", what it says in summary, and fontSize for how big (22 by default; 40 to 60 for a title or a heading over part of the diagram). Use it for a diagram's title (above everything: y about 200 above the top of what's on the canvas), headings over areas (just above the domain or group they name), or short labels. Never put text where a node is. Move it with updateCanvasNode (x, y), and make it bigger or smaller with fontSize.
|
|
48
|
+
- When asked to review or improve the design: flag each problem or risk with a hotspot on the node it's about, and write your suggestions on a few sticky notes (one per idea), then sum up what you found in a sentence or two. The hotspots you flag are listed under your reply for the user to go to, so don't list them again.
|
|
49
|
+
Only add hotspots and notes when the user asks for them, or the request is about reviewing or annotating the design.
|
|
50
|
+
|
|
51
|
+
Showing and explaining what's on the canvas:
|
|
52
|
+
- showOnCanvas takes the user to nodes: their view zooms to them and they're highlighted. Use it to show them what you're talking about.
|
|
53
|
+
- Whenever you mention something that's on the canvas in your reply, write it as a reference to it, which the user can hover or click to be taken there: [Order Service](#canvas-ref=service:<node id>). The type before the colon gives it its icon and colour: service, event, command, query, channel, domain, system, container (a data store), entity, agent, flow, user, team or data-product; for anything else, leave it out: [Customer](#canvas-ref=:<node id>). Use the node ids from getCanvas (or what addToCanvas created).
|
|
54
|
+
- To walk the user through how something works (e.g. "walk me through how checkout works"), read the canvas, then call walkThrough with the steps in order: each a short title, a sentence or two on what happens, and the nodes involved (ids from getCanvas). The user sees the steps in the chat, and each takes them to its nodes on the canvas, starting with the first: don't write the steps out again in your reply, just a sentence to introduce them. If what they ask about isn't on the canvas, draw it first.
|
|
55
|
+
|
|
56
|
+
Keep your reply short: say what you drew and anything you couldn't find in the catalog.
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
/** The assistant's tools and instructions for the canvas it was opened on, or undefined if there's no such canvas */
|
|
60
|
+
export const getCanvasAssistant = (canvasId: string) => {
|
|
61
|
+
const runtime = findStudioRuntime();
|
|
62
|
+
if (!runtime?.exists(canvasId)) return undefined;
|
|
63
|
+
const agent = () => agentOnCanvas(runtime, canvasId, ASSISTANT, { pace: ASSISTANT_PACE });
|
|
64
|
+
// Added things or connections since the canvas was last laid out: it's tidied once the assistant has finished
|
|
65
|
+
let drew = false;
|
|
66
|
+
|
|
67
|
+
const layout = async () => {
|
|
68
|
+
drew = false;
|
|
69
|
+
const { nodes, edges } = await runtime.withCanvas(canvasId, readCanvas);
|
|
70
|
+
// Notes and text stay beside what they're about
|
|
71
|
+
const positions = withNotesPlaced(nodes, await getLayoutPositions(nodes, edges));
|
|
72
|
+
await playLayout(agent().stage, positions);
|
|
73
|
+
return { laidOut: positions.nodes.size };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const tools = {
|
|
77
|
+
getCanvas: tool({
|
|
78
|
+
description:
|
|
79
|
+
'Reads everything on the canvas the user has open: its nodes (with ids, names, positions (their centres), sizes and the catalog resource they stand for), the connections between them, its hotspots and the comment threads. Read it before changing the canvas, and again to see what people have changed.',
|
|
80
|
+
inputSchema: z.object({}),
|
|
81
|
+
execute: async () => runtime.withCanvas(canvasId, describeCanvas),
|
|
82
|
+
}),
|
|
83
|
+
addToCanvas: tool({
|
|
84
|
+
description: `${ADD_TO_CANVAS_DESCRIPTION} The user watches you add them one by one. Use getResources to find catalog resources first, and prefer them over new components.`,
|
|
85
|
+
inputSchema: z.object({ nodes: nodeSpecsSchema, edges: edgeSpecsSchema.optional() }),
|
|
86
|
+
execute: async ({ nodes, edges }) => {
|
|
87
|
+
const { resources, relations } = await getCatalogResources();
|
|
88
|
+
const result = await playAddToCanvas(agent().stage, { nodes, edges }, indexCatalog(resources, relations));
|
|
89
|
+
// Notes and text alone (a review) leave the layout as it is
|
|
90
|
+
if (result.connections.length || nodes.some((node) => node.type !== 'note' && node.type !== 'text')) drew = true;
|
|
91
|
+
return result;
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
updateCanvasNode: tool({
|
|
95
|
+
description:
|
|
96
|
+
'Renames, re-describes, re-versions or moves a node on the canvas (x/y: its new centre). Catalog resources keep their catalog name, summary and version. For notes and text, summary is what is written; fontSize makes text bigger or smaller.',
|
|
97
|
+
inputSchema: z.object({
|
|
98
|
+
nodeId: z.string(),
|
|
99
|
+
name: z.string().optional(),
|
|
100
|
+
summary: z.string().optional(),
|
|
101
|
+
version: z.string().optional().describe('e.g. 1.0.0'),
|
|
102
|
+
fontSize: z.number().min(8).max(200).optional().describe('For text: how big it is written, in pixels'),
|
|
103
|
+
x: z.number().optional(),
|
|
104
|
+
y: z.number().optional(),
|
|
105
|
+
}),
|
|
106
|
+
execute: async (update) => playUpdateNode(agent().stage, update),
|
|
107
|
+
}),
|
|
108
|
+
connectCanvasNodes: tool({
|
|
109
|
+
description:
|
|
110
|
+
'Draws connections between nodes already on the canvas, one at a time. Labels default to EventCatalog wording (e.g. "publishes event", "sent to").',
|
|
111
|
+
inputSchema: z.object({ edges: edgeSpecsSchema.min(1) }),
|
|
112
|
+
execute: async ({ edges }) => {
|
|
113
|
+
const result = await playConnectAll(agent().stage, edges);
|
|
114
|
+
if (result.connections.length) drew = true;
|
|
115
|
+
return result;
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
addHotspot: tool({
|
|
119
|
+
description:
|
|
120
|
+
'Flags a hotspot on a node (nodeId), or at a place on the canvas (x, y): a marker everyone sees, with how serious it is, a short title and, optionally, why it matters and what to do about it. For risks, problems and things worth knowing, e.g. a single point of failure.',
|
|
121
|
+
inputSchema: z.object({
|
|
122
|
+
severity: z.enum(['critical', 'warning', 'info']).describe('critical: a problem; warning: a risk; info: worth knowing'),
|
|
123
|
+
title: z.string().describe('What people should know, in a few words, e.g. "Single point of failure"'),
|
|
124
|
+
description: z.string().optional().describe('Why it matters, and what to do about it'),
|
|
125
|
+
nodeId: z.string().optional().describe('The node it is about (from getCanvas or addToCanvas)'),
|
|
126
|
+
x: z.number().optional(),
|
|
127
|
+
y: z.number().optional(),
|
|
128
|
+
}),
|
|
129
|
+
execute: async (hotspot) => {
|
|
130
|
+
const { stage, author } = agent();
|
|
131
|
+
return playHotspot(stage, hotspot, author);
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
showOnCanvas: tool({
|
|
135
|
+
description:
|
|
136
|
+
"Takes the user to nodes on the canvas: their view zooms to them and they're highlighted (only their view, not anyone else's). Use it to show them what you're talking about, e.g. the first step of a walkthrough.",
|
|
137
|
+
inputSchema: z.object({ nodeIds: z.array(z.string()).min(1).describe('The nodes to show (ids from getCanvas)') }),
|
|
138
|
+
execute: async ({ nodeIds }) => {
|
|
139
|
+
const onCanvas = await runtime.withCanvas(canvasId, (doc) => new Set(getCanvasMaps(doc).nodes.keys()));
|
|
140
|
+
const shown = nodeIds.filter((id) => onCanvas.has(id));
|
|
141
|
+
const missing = nodeIds.filter((id) => !onCanvas.has(id));
|
|
142
|
+
if (!shown.length)
|
|
143
|
+
return { error: `None of these are on the canvas: ${missing.join(', ')}. Use getCanvas for their ids.` };
|
|
144
|
+
return { nodeIds: shown, ...(missing.length && { notOnCanvas: missing }) };
|
|
145
|
+
},
|
|
146
|
+
}),
|
|
147
|
+
walkThrough: tool({
|
|
148
|
+
description:
|
|
149
|
+
'Walks the user through how something on the canvas works, step by step: they see the steps in the chat, and each takes them to its nodes on the canvas (starting with the first). Use it when they ask how something works or flows, e.g. "walk me through checkout".',
|
|
150
|
+
inputSchema: z.object({
|
|
151
|
+
title: z.string().describe('What it walks through, e.g. "How checkout works"'),
|
|
152
|
+
steps: z
|
|
153
|
+
.array(
|
|
154
|
+
z.object({
|
|
155
|
+
title: z.string().describe('The step, in a few words, e.g. "Customer places an order"'),
|
|
156
|
+
description: z.string().describe('What happens in this step, in a sentence or two'),
|
|
157
|
+
nodeIds: z.array(z.string()).min(1).describe('The nodes involved in this step (ids from getCanvas)'),
|
|
158
|
+
})
|
|
159
|
+
)
|
|
160
|
+
.min(1)
|
|
161
|
+
.max(20),
|
|
162
|
+
}),
|
|
163
|
+
execute: async ({ title, steps }) => {
|
|
164
|
+
const onCanvas = await runtime.withCanvas(canvasId, (doc) => new Set(getCanvasMaps(doc).nodes.keys()));
|
|
165
|
+
const missing = [...new Set(steps.flatMap((step) => step.nodeIds).filter((id) => !onCanvas.has(id)))];
|
|
166
|
+
return {
|
|
167
|
+
title,
|
|
168
|
+
steps: steps.map((step) => ({ ...step, nodeIds: step.nodeIds.filter((id) => onCanvas.has(id)) })),
|
|
169
|
+
...(missing.length && { notOnCanvas: missing }),
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
removeFromCanvas: tool({
|
|
174
|
+
description:
|
|
175
|
+
'Removes nodes (and their connections) or connections from the canvas. Only remove what the user asked for or what you added.',
|
|
176
|
+
inputSchema: z.object({ nodeIds: z.array(z.string()).optional(), connectionIds: z.array(z.string()).optional() }),
|
|
177
|
+
execute: async ({ nodeIds = [], connectionIds = [] }) => {
|
|
178
|
+
await playRemove(agent().stage, nodeIds, connectionIds);
|
|
179
|
+
return { removedNodes: nodeIds, removedConnections: connectionIds };
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
182
|
+
layoutCanvas: tool({
|
|
183
|
+
description:
|
|
184
|
+
"Lays the whole canvas out again with EventCatalog's visualiser layout (left to right, connections routed around nodes). Only when the user asks you to tidy up: what you draw is laid out for you when you've finished.",
|
|
185
|
+
inputSchema: z.object({}),
|
|
186
|
+
execute: layout,
|
|
187
|
+
}),
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
tools,
|
|
192
|
+
instructions: CANVAS_ASSISTANT_PROMPT,
|
|
193
|
+
/**
|
|
194
|
+
* The model started writing a call to a tool: a big addToCanvas takes a while to write, so the canvas shows the
|
|
195
|
+
* assistant planning it meanwhile
|
|
196
|
+
*/
|
|
197
|
+
startedWriting: (toolName: string) => {
|
|
198
|
+
if (toolName !== 'addToCanvas') return;
|
|
199
|
+
const { stage } = agent();
|
|
200
|
+
stage.present({ pointer: stage.lastPointer ?? { x: 0, y: 0 }, selection: [], activity: 'Planning the diagram' });
|
|
201
|
+
},
|
|
202
|
+
/** The assistant has finished: what it drew is laid out, so the canvas is tidy (the model doesn't always remember to) */
|
|
203
|
+
finished: async () => {
|
|
204
|
+
if (drew) await layout();
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { reopeningIfEdited, type Author } from '@eventcatalog/visualiser/studio';
|
|
2
|
+
import type { AgentStage } from '../agent-choreography';
|
|
3
|
+
import type { AgentIdentity } from '../agents';
|
|
4
|
+
import type { StudioRuntime } from './runtime';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Work on a canvas as an agent: changes are played out like a person would make them (see agent-choreography),
|
|
8
|
+
* with the agent's pointer, selection and what it's doing shown to everyone on the canvas. Used by the MCP canvas
|
|
9
|
+
* tools and the assistant's.
|
|
10
|
+
*/
|
|
11
|
+
export const agentOnCanvas = (
|
|
12
|
+
runtime: StudioRuntime,
|
|
13
|
+
canvasId: string,
|
|
14
|
+
agent: AgentIdentity,
|
|
15
|
+
{ pace }: Pick<AgentStage, 'pace'> = {}
|
|
16
|
+
) => {
|
|
17
|
+
const author = { ...agent, agent: true } as Author;
|
|
18
|
+
const stage: AgentStage = {
|
|
19
|
+
// An agent changing an accepted (or rejected) canvas makes it a draft again, like a person doing it
|
|
20
|
+
change: (fn) => runtime.withCanvas(canvasId, (doc) => reopeningIfEdited(doc, author, () => fn(doc))),
|
|
21
|
+
present: (presence) => runtime.setAgentPresence(canvasId, agent, presence),
|
|
22
|
+
lastPointer: runtime.getAgentPointer(canvasId, agent),
|
|
23
|
+
sharesMoves: true,
|
|
24
|
+
pace,
|
|
25
|
+
};
|
|
26
|
+
return { stage, author };
|
|
27
|
+
};
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
getCanvasMaps,
|
|
17
17
|
readCanvas,
|
|
18
18
|
readMeta,
|
|
19
|
-
reopeningIfEdited,
|
|
20
19
|
replyToThread,
|
|
21
20
|
setCanvasStatus,
|
|
22
21
|
setThreadResolved,
|
|
@@ -33,10 +32,10 @@ import {
|
|
|
33
32
|
playOnNode,
|
|
34
33
|
playRemove,
|
|
35
34
|
playUpdateNode,
|
|
36
|
-
type AgentStage,
|
|
37
35
|
} from '../agent-choreography';
|
|
38
36
|
import { STUDIO_SOCKET_PATH, findStudioRuntime } from './runtime';
|
|
39
37
|
import { getAgent } from '../agents';
|
|
38
|
+
import { agentOnCanvas } from './canvas-agent';
|
|
40
39
|
|
|
41
40
|
/**
|
|
42
41
|
* MCP tools for the collaborative canvas: agents create canvases, join the ones people are on, and
|
|
@@ -100,18 +99,7 @@ export function registerCanvasTools(server: McpServer, { catalogUrl, userAgent }
|
|
|
100
99
|
* Work on a canvas as an agent: changes are played out like a person would make them (see agent-choreography),
|
|
101
100
|
* with the agent's pointer, selection and what it's doing shown to everyone on the canvas.
|
|
102
101
|
*/
|
|
103
|
-
const asAgent = (id: string, name: string | undefined) =>
|
|
104
|
-
const agent = getAgent(name, userAgent);
|
|
105
|
-
const author = { ...agent, agent: true } as Author;
|
|
106
|
-
const stage: AgentStage = {
|
|
107
|
-
// An agent changing an accepted (or rejected) canvas makes it a draft again, like a person doing it
|
|
108
|
-
change: (fn) => runtime.withCanvas(id, (doc) => reopeningIfEdited(doc, author, () => fn(doc))),
|
|
109
|
-
present: (presence) => runtime.setAgentPresence(id, agent, presence),
|
|
110
|
-
lastPointer: runtime.getAgentPointer(id, agent),
|
|
111
|
-
sharesMoves: true,
|
|
112
|
-
};
|
|
113
|
-
return { stage, author };
|
|
114
|
-
};
|
|
102
|
+
const asAgent = (id: string, name: string | undefined) => agentOnCanvas(runtime, id, getAgent(name, userAgent));
|
|
115
103
|
|
|
116
104
|
// ---- The canvas in the chat (MCP App) ----
|
|
117
105
|
|
|
@@ -75,6 +75,7 @@ import {
|
|
|
75
75
|
isArchitectureGraphEnabled,
|
|
76
76
|
} from '@utils/feature';
|
|
77
77
|
import { getMDXComponentsByName, parseMdxBooleanProp } from '@utils/markdown';
|
|
78
|
+
import { isFlowGroupBy } from '@eventcatalog/visualiser';
|
|
78
79
|
|
|
79
80
|
import type { CollectionTypes } from '@types';
|
|
80
81
|
|
|
@@ -349,6 +350,8 @@ let nodeGraphs =
|
|
|
349
350
|
...graph,
|
|
350
351
|
search: parseMdxBooleanProp(nodeGraph.search, true),
|
|
351
352
|
legend: parseMdxBooleanProp(nodeGraph.legend, true),
|
|
353
|
+
// Flows only: group the steps to start with (a string in MDX, so only a way they can be grouped)
|
|
354
|
+
lanes: isFlowGroupBy(nodeGraph.lanes) ? nodeGraph.lanes : undefined,
|
|
352
355
|
};
|
|
353
356
|
}) || [];
|
|
354
357
|
const flowEmbeds = getMDXComponentsByName(props.body || '', 'Flow') || [];
|
|
@@ -649,6 +652,7 @@ if (!isAdrPage && !hasCurrentFlowEmbed && !hasCurrentPageNodeGraph) {
|
|
|
649
652
|
linksToVisualiser={true}
|
|
650
653
|
showSearch={nodeGraph.search ?? true}
|
|
651
654
|
showLegend={nodeGraph.legend ?? true}
|
|
655
|
+
lanes={collection === 'flows' ? nodeGraph.lanes : undefined}
|
|
652
656
|
href={
|
|
653
657
|
isVisualiserEnabled()
|
|
654
658
|
? {
|
|
@@ -37,6 +37,8 @@ const RESOURCE_DATA_KEYS = [
|
|
|
37
37
|
// An entity's identifier, its primary key in the Mermaid export
|
|
38
38
|
'identifier',
|
|
39
39
|
'sidebar',
|
|
40
|
+
// `visualiser: false`: it has no diagram page, so a node's card doesn't link to one
|
|
41
|
+
'visualiser',
|
|
40
42
|
] as const;
|
|
41
43
|
|
|
42
44
|
const RESOURCE_OBJECT_KEYS = new Set([
|
|
@@ -138,6 +140,18 @@ const compactVisualiserResourceData = (value: unknown): unknown => {
|
|
|
138
140
|
out.services = value.services.map(compactDomainService);
|
|
139
141
|
}
|
|
140
142
|
|
|
143
|
+
// A service's messages, by id only: a node's card uses them to tell which of a shared channel's messages it sends
|
|
144
|
+
// or receives (pointers, or hydrated messages)
|
|
145
|
+
for (const key of ['sends', 'receives'] as const) {
|
|
146
|
+
const list = value[key];
|
|
147
|
+
if (!Array.isArray(list)) continue;
|
|
148
|
+
out[key] = list.flatMap((pointer) => {
|
|
149
|
+
if (!isPlainObject(pointer)) return [];
|
|
150
|
+
const id = isPlainObject(pointer.data) ? pointer.data.id : pointer.id;
|
|
151
|
+
return id === undefined ? [] : [{ id }];
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
141
155
|
return out;
|
|
142
156
|
};
|
|
143
157
|
|
|
@@ -65,3 +65,29 @@ export const getNodeCatalogResource = (node: DiagramNode): { collection: string;
|
|
|
65
65
|
if (!collection || typeof details?.id !== 'string') return undefined;
|
|
66
66
|
return { collection, id: details.id, ...(typeof details.version === 'string' && { version: details.version }) };
|
|
67
67
|
};
|
|
68
|
+
|
|
69
|
+
// The collections with a diagram page (pages/visualiser/[type], and domains' and systems' own)
|
|
70
|
+
const DIAGRAM_COLLECTIONS = new Set([
|
|
71
|
+
'services',
|
|
72
|
+
'agents',
|
|
73
|
+
'events',
|
|
74
|
+
'commands',
|
|
75
|
+
'queries',
|
|
76
|
+
'channels',
|
|
77
|
+
'flows',
|
|
78
|
+
'containers',
|
|
79
|
+
'data-products',
|
|
80
|
+
'domains',
|
|
81
|
+
'systems',
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The path of the diagram page of the resource a node shows (e.g. `/visualiser/services/OrderService/1.0.0`), if it
|
|
86
|
+
* has one: not for actors, users, entities and the like, or a resource with `visualiser: false`
|
|
87
|
+
*/
|
|
88
|
+
export const getNodeDiagramPath = (node: DiagramNode): string | undefined => {
|
|
89
|
+
const resource = getNodeCatalogResource(node);
|
|
90
|
+
if (!resource?.version || !DIAGRAM_COLLECTIONS.has(resource.collection)) return undefined;
|
|
91
|
+
if (getNodeResourceDetails(node)?.visualiser === false) return undefined;
|
|
92
|
+
return `/visualiser/${resource.collection}/${resource.id}/${resource.version}`;
|
|
93
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Each kind of resource reference's icon (heroicons outline paths, drawn in a 24x24 viewBox with a stroke): the docs'
|
|
3
|
+
* [[service|OrderService]] references, and the assistant's references to what's on a Studio canvas
|
|
4
|
+
*/
|
|
5
|
+
const RESOURCE_REFERENCE_ICONS: Record<string, string> = {
|
|
6
|
+
agent:
|
|
7
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M12 6V3m-4.5 7.5h9m-9 3h9M7.5 18h9A2.5 2.5 0 0019 15.5v-5A2.5 2.5 0 0016.5 8h-9A2.5 2.5 0 005 10.5v5A2.5 2.5 0 007.5 18zM9 21h6" />',
|
|
8
|
+
entity:
|
|
9
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />', // Box/cube
|
|
10
|
+
service:
|
|
11
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />', // Server
|
|
12
|
+
event:
|
|
13
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />', // Bolt
|
|
14
|
+
command:
|
|
15
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z" />', // ChatBubble
|
|
16
|
+
query:
|
|
17
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />', // MagnifyingGlass
|
|
18
|
+
domain:
|
|
19
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75zM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-8.25zM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-2.25z" />', // RectangleGroup
|
|
20
|
+
flow: '<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 010 3.75H5.625a1.875 1.875 0 010-3.75z" />', // QueueList
|
|
21
|
+
channel:
|
|
22
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" />', // ArrowsRightLeft
|
|
23
|
+
diagram:
|
|
24
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M9 6.75V15m6-6v8.25m.503 3.498l4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 00-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0z" />', // Map
|
|
25
|
+
container:
|
|
26
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" />', // Database
|
|
27
|
+
user: '<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />', // User
|
|
28
|
+
team: '<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />', // UserGroup
|
|
29
|
+
doc: '<path stroke-linecap="round" stroke-linejoin="round" d="M14 2v4a2 2 0 002 2h4" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V7z" /><path stroke-linecap="round" stroke-linejoin="round" d="M10 9H8" /><path stroke-linecap="round" stroke-linejoin="round" d="M16 13H8" /><path stroke-linecap="round" stroke-linejoin="round" d="M16 17H8" />', // FileText
|
|
30
|
+
'data-product':
|
|
31
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-9-5.25L3 7.5m18 0l-9 5.25M21 7.5v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />', // Cube
|
|
32
|
+
system:
|
|
33
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75zM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-8.25zM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-2.25z" />', // Group (systems)
|
|
34
|
+
adr: '<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /><path stroke-linecap="round" stroke-linejoin="round" d="M9 7h2" />', // ClipboardList-ish (ADR)
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const getResourceReferenceIcon = (type: string) => RESOURCE_REFERENCE_ICONS[type] ?? RESOURCE_REFERENCE_ICONS.entity;
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"license": "BUSL-1.1",
|
|
9
9
|
"type": "module",
|
|
10
|
-
"version": "5.
|
|
10
|
+
"version": "5.7.1",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
@@ -75,14 +75,14 @@
|
|
|
75
75
|
"@radix-ui/react-dropdown-menu": "^2.1.12",
|
|
76
76
|
"@radix-ui/react-popover": "^1.1.15",
|
|
77
77
|
"@radix-ui/react-tooltip": "^1.1.8",
|
|
78
|
-
"@scalar/api-reference-react": "^0.9.
|
|
78
|
+
"@scalar/api-reference-react": "^0.9.82",
|
|
79
79
|
"@tailwindcss/typography": "^0.5.16",
|
|
80
80
|
"@tailwindcss/vite": "^4.3.1",
|
|
81
81
|
"@tanstack/react-table": "^8.17.3",
|
|
82
82
|
"@xyflow/react": "^12.12.0",
|
|
83
83
|
"ai": "^7.0.106",
|
|
84
84
|
"astro": "7.3.8",
|
|
85
|
-
"astro-expressive-code": "^0.44.
|
|
85
|
+
"astro-expressive-code": "^0.44.3",
|
|
86
86
|
"astro-seo": "^1.2.0",
|
|
87
87
|
"boxen": "^8.0.1",
|
|
88
88
|
"commander": "^12.1.0",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"react-syntax-highlighter": "^16.1.1",
|
|
124
124
|
"recast": "^0.23.11",
|
|
125
125
|
"rehype-autolink-headings": "^7.1.0",
|
|
126
|
-
"rehype-expressive-code": "^0.44.
|
|
126
|
+
"rehype-expressive-code": "^0.44.3",
|
|
127
127
|
"rehype-slug": "^6.0.0",
|
|
128
128
|
"remark-comment": "^1.0.0",
|
|
129
129
|
"remark-directive": "^3.0.0",
|
|
@@ -141,11 +141,11 @@
|
|
|
141
141
|
"y-protocols": "^1.0.7",
|
|
142
142
|
"yjs": "^13.6.33",
|
|
143
143
|
"zod": "^4.6.6",
|
|
144
|
-
"@eventcatalog/exporters": "0.1.
|
|
145
|
-
"@eventcatalog/fitness": "0.1.1",
|
|
144
|
+
"@eventcatalog/exporters": "0.1.4",
|
|
146
145
|
"@eventcatalog/linter": "1.3.1",
|
|
146
|
+
"@eventcatalog/fitness": "0.1.1",
|
|
147
147
|
"@eventcatalog/sdk": "2.32.0",
|
|
148
|
-
"@eventcatalog/visualiser": "^5.1
|
|
148
|
+
"@eventcatalog/visualiser": "^5.2.1"
|
|
149
149
|
},
|
|
150
150
|
"devDependencies": {
|
|
151
151
|
"@astrojs/check": "^0.9.10",
|