@eventcatalog/core 5.7.0 → 5.7.2

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.
Files changed (38) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-FMWQ2YYN.js → chunk-B3FUBDJN.js} +1 -1
  6. package/dist/{chunk-4CV7VC4K.js → chunk-EPI5BHEU.js} +1 -1
  7. package/dist/{chunk-MOPKQMQP.js → chunk-NNTWQNQ5.js} +1 -1
  8. package/dist/{chunk-BE5PTWNC.js → chunk-OR4VBY3F.js} +1 -1
  9. package/dist/{chunk-22KZ4TJF.js → chunk-TYZENGGJ.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/eventcatalog.cjs +1 -1
  13. package/dist/eventcatalog.js +5 -5
  14. package/dist/generate.cjs +1 -1
  15. package/dist/generate.js +3 -3
  16. package/dist/utils/cli-logger.cjs +1 -1
  17. package/dist/utils/cli-logger.js +2 -2
  18. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +702 -128
  19. package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +3 -2
  20. package/eventcatalog/src/components/ChatPanel/assistant-events.ts +37 -0
  21. package/eventcatalog/src/components/ChatPanel/panel-width.ts +28 -0
  22. package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +2 -34
  23. package/eventcatalog/src/features/ai/chat-api.ts +24 -5
  24. package/eventcatalog/src/features/mcp/apps/generated/canvas.html +72 -71
  25. package/eventcatalog/src/features/mcp/apps/generated/viewer.html +1 -1
  26. package/eventcatalog/src/features/studio/agent-choreography.ts +84 -16
  27. package/eventcatalog/src/features/studio/api/canvases-api.ts +3 -0
  28. package/eventcatalog/src/features/studio/canvas-actions.ts +330 -15
  29. package/eventcatalog/src/features/studio/catalog.ts +5 -3
  30. package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +14 -0
  31. package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +116 -13
  32. package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +1 -1
  33. package/eventcatalog/src/features/studio/pages/[id].astro +2 -0
  34. package/eventcatalog/src/features/studio/server/assistant-tools.ts +207 -0
  35. package/eventcatalog/src/features/studio/server/canvas-agent.ts +27 -0
  36. package/eventcatalog/src/features/studio/server/canvas-mcp.ts +3 -15
  37. package/eventcatalog/src/utils/resource-reference-icons.ts +37 -0
  38. package/package.json +4 -4
@@ -1,6 +1,7 @@
1
1
  import { useState, useEffect, useRef, useCallback } from 'react';
2
2
  import { Sparkles } from 'lucide-react';
3
3
  import ChatPanel from './ChatPanel';
4
+ import { OPEN_CHAT_EVENT } from './assistant-events';
4
5
 
5
6
  const ChatPanelButton = ({ configured = false }: { configured?: boolean }) => {
6
7
  const [isOpen, setIsOpen] = useState(false);
@@ -25,10 +26,10 @@ const ChatPanelButton = ({ configured = false }: { configured?: boolean }) => {
25
26
  }
26
27
  };
27
28
  window.addEventListener('keydown', handleShortcut);
28
- window.addEventListener('eventcatalog:open-chat', handleOpenChat);
29
+ window.addEventListener(OPEN_CHAT_EVENT, handleOpenChat);
29
30
  return () => {
30
31
  window.removeEventListener('keydown', handleShortcut);
31
- window.removeEventListener('eventcatalog:open-chat', handleOpenChat);
32
+ window.removeEventListener(OPEN_CHAT_EVENT, handleOpenChat);
32
33
  };
33
34
  }, []);
34
35
 
@@ -0,0 +1,37 @@
1
+ /** Window events between the assistant (Ask AI) and the page it's on, e.g. a Studio canvas */
2
+
3
+ /** Opens the assistant's panel */
4
+ export const OPEN_CHAT_EVENT = 'eventcatalog:open-chat';
5
+
6
+ /** The assistant was asked something on a Studio canvas (detail: { canvasId }), which it may draw on */
7
+ export const ASSISTANT_DRAWING_EVENT = 'eventcatalog:assistant-drawing';
8
+
9
+ /** Takes the person to nodes on the Studio canvas they have open (detail: { canvasId, nodeIds }): zoomed to, and selected */
10
+ export const CANVAS_FOCUS_EVENT = 'eventcatalog:canvas-focus';
11
+
12
+ /** Links the assistant writes that take the person to nodes on the canvas: `#canvas-focus=<node id>,<node id>` */
13
+ const FOCUS_LINK = '#canvas-focus=';
14
+ export const nodesInFocusLink = (href: string | undefined) =>
15
+ href?.startsWith(FOCUS_LINK)
16
+ ? href
17
+ .slice(FOCUS_LINK.length)
18
+ .split(',')
19
+ .map((id) => decodeURIComponent(id.trim()))
20
+ .filter(Boolean)
21
+ : undefined;
22
+
23
+ /** Takes the person to a hotspot on the Studio canvas they have open, and opens it (detail: { canvasId, hotspotId }) */
24
+ export const CANVAS_HOTSPOT_EVENT = 'eventcatalog:canvas-hotspot';
25
+
26
+ /**
27
+ * References the assistant writes to something on the canvas, drawn like the docs' resource references and taking
28
+ * the person to it: `#canvas-ref=<type>:<node id>` (the type, e.g. service or event, gives its icon and colour)
29
+ */
30
+ const REF_LINK = '#canvas-ref=';
31
+ export const canvasRefIn = (href: string | undefined) => {
32
+ if (!href?.startsWith(REF_LINK)) return undefined;
33
+ const value = decodeURIComponent(href.slice(REF_LINK.length)).trim();
34
+ const split = value.indexOf(':');
35
+ const [type, nodeId] = split === -1 ? ['', value] : [value.slice(0, split).trim(), value.slice(split + 1).trim()];
36
+ return nodeId ? { type: type.toLowerCase(), nodeId } : undefined;
37
+ };
@@ -0,0 +1,28 @@
1
+ /** How wide the assistant's panel is: dragged wider (or back) by its left edge, and remembered in this browser */
2
+ export const PANEL_WIDTH = 400;
3
+ export const MIN_PANEL_WIDTH = 360;
4
+ export const MAX_PANEL_WIDTH = 800;
5
+ // How much of the page stays beside it, at least
6
+ const MIN_PAGE_WIDTH = 480;
7
+ const STORAGE_KEY = 'eventcatalog-chat-panel-width';
8
+
9
+ /** Within the panel's limits, leaving room for the page in a window this wide */
10
+ export const clampPanelWidth = (width: number, viewportWidth: number) =>
11
+ Math.round(Math.max(MIN_PANEL_WIDTH, Math.min(width, MAX_PANEL_WIDTH, viewportWidth - MIN_PAGE_WIDTH)));
12
+
13
+ /** The width they left it at, or the default */
14
+ export const readPanelWidth = (viewportWidth: number) => {
15
+ try {
16
+ const saved = Number(localStorage.getItem(STORAGE_KEY));
17
+ return saved ? clampPanelWidth(saved, viewportWidth) : PANEL_WIDTH;
18
+ } catch {
19
+ return PANEL_WIDTH;
20
+ }
21
+ };
22
+
23
+ export const savePanelWidth = (width: number) => {
24
+ try {
25
+ if (width === PANEL_WIDTH) localStorage.removeItem(STORAGE_KEY);
26
+ else localStorage.setItem(STORAGE_KEY, String(width));
27
+ } catch {}
28
+ };
@@ -15,6 +15,7 @@ import {
15
15
  import { getCollection } from 'astro:content';
16
16
  import { getServiceSpecifications, getSpecUrl, getSpecLabel } from '@components/Grids/specification-utils';
17
17
  import { getResourceReferenceStyle } from '@utils/resource-reference-colors';
18
+ import { getResourceReferenceIcon } from '@utils/resource-reference-icons';
18
19
  import { isIconPath, resolveIconUrl } from '@utils/icon';
19
20
  import {
20
21
  getResourceReferenceUrl,
@@ -88,41 +89,8 @@ const typeLabels: Record<string, string> = {
88
89
  'data-product': 'Data Product',
89
90
  };
90
91
 
91
- // SVG icons for each type (from heroicons outline)
92
- const typeIcons: Record<string, string> = {
93
- agent:
94
- '<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" />',
95
- entity:
96
- '<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
97
- service:
98
- '<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
99
- event:
100
- '<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
101
- command:
102
- '<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
103
- query:
104
- '<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
105
- domain:
106
- '<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
107
- 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
108
- channel:
109
- '<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
110
- diagram:
111
- '<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
112
- container:
113
- '<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
114
- 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
115
- 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
116
- 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
117
- 'data-product':
118
- '<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
119
- system:
120
- '<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)
121
- 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)
122
- };
123
-
124
92
  const label = typeLabels[type] || typeLabels.entity;
125
- const iconPath = typeIcons[type] || typeIcons.entity;
93
+ const iconPath = getResourceReferenceIcon(type);
126
94
  const resourceReferenceStyle = getResourceReferenceStyle(type);
127
95
 
128
96
  let resource: any = null;
@@ -10,7 +10,7 @@ import {
10
10
  type UIMessage,
11
11
  } from 'ai';
12
12
  import { join } from 'node:path';
13
- import { isDevMode } from '@utils/feature';
13
+ import { isCanvasEnabled, isDevMode } from '@utils/feature';
14
14
  import { getCollection, getEntry } from 'astro:content';
15
15
  import { z } from 'astro/zod';
16
16
  import { getConsumersOfMessage, getProducersOfMessage } from '@utils/collections/services';
@@ -38,6 +38,8 @@ const catalogDirectory = process.env.PROJECT_DIR || process.cwd();
38
38
  // How many model calls one answer may take. Questions across several domains need many tool calls, so the last
39
39
  // step has tools turned off: the model has to answer with what it found, rather than stop without a reply.
40
40
  const MAX_STEPS = 10;
41
+ // Drawing a diagram on a Studio canvas takes more: finding the resources, adding them, connecting and tidying
42
+ const MAX_CANVAS_STEPS = 20;
41
43
 
42
44
  export const defaultConfiguration = {
43
45
  temperature: 0.4,
@@ -204,7 +206,8 @@ export const GET = async ({ request }: APIContext<{ question: string; messages:
204
206
  };
205
207
 
206
208
  export const POST = async ({ request }: APIContext<{ question: string; messages: Message[]; additionalContext?: string }>) => {
207
- const { messages }: { messages: UIMessage[] } = await request.json();
209
+ // canvasId: the Studio canvas the assistant was opened on, which it can draw on
210
+ const { messages, canvasId }: { messages: UIMessage[]; canvasId?: string } = await request.json();
208
211
 
209
212
  if (!hasChatConfiguration) {
210
213
  return chatConfigurationErrorResponse();
@@ -214,6 +217,12 @@ export const POST = async ({ request }: APIContext<{ question: string; messages:
214
217
  const referrer = request.headers.get('referer');
215
218
 
216
219
  try {
220
+ const canvas =
221
+ canvasId && isCanvasEnabled()
222
+ ? (await import('@features/studio/server/assistant-tools')).getCanvasAssistant(canvasId)
223
+ : undefined;
224
+ const maxSteps = canvas ? MAX_CANVAS_STEPS : MAX_STEPS;
225
+
217
226
  const tools = {
218
227
  getResources: tool({
219
228
  description: toolDescriptions.getResources,
@@ -390,19 +399,29 @@ export const POST = async ({ request }: APIContext<{ question: string; messages:
390
399
  return { suggestions: questions };
391
400
  },
392
401
  }),
402
+ ...canvas?.tools,
393
403
  ...extendedTools,
394
404
  };
395
405
 
396
406
  const result = streamText({
397
407
  model,
398
- instructions: getBaseSystemPrompt(referrer ?? ''),
408
+ instructions: getBaseSystemPrompt(referrer ?? '') + (canvas?.instructions ?? ''),
399
409
  messages: await convertToModelMessages(messages),
400
410
  temperature: modelConfiguration?.temperature ?? 0.7,
401
- stopWhen: isStepCount(MAX_STEPS),
402
- prepareStep: ({ stepNumber }) => (stepNumber === MAX_STEPS - 1 ? { toolChoice: 'none' } : undefined),
411
+ // The provider's own options (e.g. { anthropic: { effort: 'low' } }), from eventcatalog.chat.js's configuration
412
+ providerOptions: modelConfiguration?.providerOptions,
413
+ stopWhen: isStepCount(maxSteps),
414
+ prepareStep: ({ stepNumber }) => (stepNumber === maxSteps - 1 ? { toolChoice: 'none' } : undefined),
403
415
  onError: (error) => {
404
416
  console.error('[Chat] On error', error);
405
417
  },
418
+ // On a canvas: shown planning while it writes what to add, and what it drew tidied once it's finished
419
+ ...(canvas && {
420
+ onChunk: ({ chunk }) => {
421
+ if (chunk.type === 'tool-input-start') canvas.startedWriting(chunk.toolName);
422
+ },
423
+ onEnd: () => canvas.finished().catch((error) => console.error('[Chat] Could not tidy the canvas', error)),
424
+ }),
406
425
  tools,
407
426
  });
408
427