@cognizant-ai-lab/ui-common 1.3.3

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 (108) hide show
  1. package/dist/components/AgentChat/ChatCommon.d.ts +94 -0
  2. package/dist/components/AgentChat/ChatCommon.js +581 -0
  3. package/dist/components/AgentChat/ControlButtons.d.ts +16 -0
  4. package/dist/components/AgentChat/ControlButtons.js +24 -0
  5. package/dist/components/AgentChat/FormattedMarkdown.d.ts +32 -0
  6. package/dist/components/AgentChat/FormattedMarkdown.js +82 -0
  7. package/dist/components/AgentChat/Greetings.d.ts +1 -0
  8. package/dist/components/AgentChat/Greetings.js +38 -0
  9. package/dist/components/AgentChat/LlmChatButton.d.ts +12 -0
  10. package/dist/components/AgentChat/LlmChatButton.js +33 -0
  11. package/dist/components/AgentChat/SendButton.d.ts +12 -0
  12. package/dist/components/AgentChat/SendButton.js +28 -0
  13. package/dist/components/AgentChat/SyntaxHighlighterThemes.d.ts +14 -0
  14. package/dist/components/AgentChat/SyntaxHighlighterThemes.js +27 -0
  15. package/dist/components/AgentChat/Types.d.ts +17 -0
  16. package/dist/components/AgentChat/Types.js +26 -0
  17. package/dist/components/AgentChat/UserQueryDisplay.d.ts +5 -0
  18. package/dist/components/AgentChat/UserQueryDisplay.js +33 -0
  19. package/dist/components/AgentChat/Utils.d.ts +11 -0
  20. package/dist/components/AgentChat/Utils.js +64 -0
  21. package/dist/components/AgentChat/VoiceChat/MicrophoneButton.d.ts +29 -0
  22. package/dist/components/AgentChat/VoiceChat/MicrophoneButton.js +55 -0
  23. package/dist/components/AgentChat/VoiceChat/VoiceChat.d.ts +33 -0
  24. package/dist/components/AgentChat/VoiceChat/VoiceChat.js +180 -0
  25. package/dist/components/Authentication/Auth.d.ts +14 -0
  26. package/dist/components/Authentication/Auth.js +58 -0
  27. package/dist/components/ChatBot/ChatBot.d.ts +20 -0
  28. package/dist/components/ChatBot/ChatBot.js +75 -0
  29. package/dist/components/Common/Breadcrumbs.d.ts +6 -0
  30. package/dist/components/Common/Breadcrumbs.js +36 -0
  31. package/dist/components/Common/LlmChatOptionsButton.d.ts +9 -0
  32. package/dist/components/Common/LlmChatOptionsButton.js +31 -0
  33. package/dist/components/Common/LoadingSpinner.d.ts +10 -0
  34. package/dist/components/Common/LoadingSpinner.js +24 -0
  35. package/dist/components/Common/MUIAccordion.d.ts +17 -0
  36. package/dist/components/Common/MUIAccordion.js +76 -0
  37. package/dist/components/Common/MUIAlert.d.ts +11 -0
  38. package/dist/components/Common/MUIAlert.js +41 -0
  39. package/dist/components/Common/MUIDialog.d.ts +16 -0
  40. package/dist/components/Common/MUIDialog.js +40 -0
  41. package/dist/components/Common/Navbar.d.ts +15 -0
  42. package/dist/components/Common/Navbar.js +137 -0
  43. package/dist/components/Common/PageLoader.d.ts +5 -0
  44. package/dist/components/Common/PageLoader.js +26 -0
  45. package/dist/components/Common/Snackbar.d.ts +5 -0
  46. package/dist/components/Common/Snackbar.js +84 -0
  47. package/dist/components/Common/confirmationModal.d.ts +14 -0
  48. package/dist/components/Common/confirmationModal.js +65 -0
  49. package/dist/components/Common/notification.d.ts +18 -0
  50. package/dist/components/Common/notification.js +79 -0
  51. package/dist/components/ErrorPage/ErrorBoundary.d.ts +38 -0
  52. package/dist/components/ErrorPage/ErrorBoundary.js +77 -0
  53. package/dist/components/ErrorPage/ErrorPage.d.ts +12 -0
  54. package/dist/components/ErrorPage/ErrorPage.js +46 -0
  55. package/dist/components/MultiAgentAccelerator/AgentFlow.d.ts +21 -0
  56. package/dist/components/MultiAgentAccelerator/AgentFlow.js +394 -0
  57. package/dist/components/MultiAgentAccelerator/AgentNode.d.ts +18 -0
  58. package/dist/components/MultiAgentAccelerator/AgentNode.js +129 -0
  59. package/dist/components/MultiAgentAccelerator/GraphLayouts.d.ts +33 -0
  60. package/dist/components/MultiAgentAccelerator/GraphLayouts.js +297 -0
  61. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +17 -0
  62. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +208 -0
  63. package/dist/components/MultiAgentAccelerator/PlasmaEdge.d.ts +3 -0
  64. package/dist/components/MultiAgentAccelerator/PlasmaEdge.js +124 -0
  65. package/dist/components/MultiAgentAccelerator/Sidebar.d.ts +12 -0
  66. package/dist/components/MultiAgentAccelerator/Sidebar.js +204 -0
  67. package/dist/components/MultiAgentAccelerator/ThoughtBubbleEdge.d.ts +12 -0
  68. package/dist/components/MultiAgentAccelerator/ThoughtBubbleEdge.js +15 -0
  69. package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.d.ts +11 -0
  70. package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.js +466 -0
  71. package/dist/components/MultiAgentAccelerator/const.d.ts +7 -0
  72. package/dist/components/MultiAgentAccelerator/const.js +39 -0
  73. package/dist/const.d.ts +10 -0
  74. package/dist/const.js +30 -0
  75. package/dist/controller/agent/Agent.d.ts +56 -0
  76. package/dist/controller/agent/Agent.js +162 -0
  77. package/dist/controller/llm/LlmChat.d.ts +18 -0
  78. package/dist/controller/llm/LlmChat.js +65 -0
  79. package/dist/controller/llm/endpoints.d.ts +1 -0
  80. package/dist/controller/llm/endpoints.js +17 -0
  81. package/dist/generated/neuro-san/NeuroSanClient.d.ts +413 -0
  82. package/dist/generated/neuro-san/NeuroSanClient.js +28 -0
  83. package/dist/index.d.ts +37 -0
  84. package/dist/index.js +52 -0
  85. package/dist/state/UserInfo.d.ts +16 -0
  86. package/dist/state/UserInfo.js +27 -0
  87. package/dist/state/environment.d.ts +18 -0
  88. package/dist/state/environment.js +33 -0
  89. package/dist/tsconfig.build.tsbuildinfo +1 -0
  90. package/dist/utils/Authentication.d.ts +31 -0
  91. package/dist/utils/Authentication.js +94 -0
  92. package/dist/utils/BrowserNavigation.d.ts +5 -0
  93. package/dist/utils/BrowserNavigation.js +22 -0
  94. package/dist/utils/Theme.d.ts +7 -0
  95. package/dist/utils/Theme.js +7 -0
  96. package/dist/utils/agentConversations.d.ts +24 -0
  97. package/dist/utils/agentConversations.js +113 -0
  98. package/dist/utils/text.d.ts +28 -0
  99. package/dist/utils/text.js +64 -0
  100. package/dist/utils/title.d.ts +1 -0
  101. package/dist/utils/title.js +20 -0
  102. package/dist/utils/types.d.ts +17 -0
  103. package/dist/utils/types.js +16 -0
  104. package/dist/utils/useLocalStorage.d.ts +1 -0
  105. package/dist/utils/useLocalStorage.js +55 -0
  106. package/dist/utils/zIndexLayers.d.ts +2 -0
  107. package/dist/utils/zIndexLayers.js +29 -0
  108. package/package.json +69 -0
@@ -0,0 +1,466 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { styled } from "@mui/material";
3
+ import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { ChatMessageType } from "../../generated/neuro-san/NeuroSanClient.js";
5
+ // Note: Removed BubblePosition interface - no longer needed for right-side positioning
6
+ // #endregion: Types
7
+ // #region: Constants
8
+ const BUBBLE_DISTANCE_FROM_RIGHT_EDGE = 20; // Fixed distance from right edge
9
+ const BUBBLE_HEIGHT = 78;
10
+ const BUBBLE_HEIGHT_PLUS_SPACING = BUBBLE_HEIGHT + 10;
11
+ const BUBBLE_STACK_OFFSET_TOP = 70;
12
+ const BUBBLE_WIDTH = 260;
13
+ const LAYOUT_BUBBLES_ANIMATION_DELAY_MS = 120; // Delay between each bubble's animation start
14
+ // Constants for connecting lines
15
+ const CONNECTING_LINE_OPACITY = 0.3; // Semi-transparent connecting line
16
+ // #endregion: Constants
17
+ // #region: Styled Components
18
+ const OverlayContainer = styled("div")({
19
+ position: "absolute",
20
+ left: 0,
21
+ top: 0,
22
+ width: "100%",
23
+ height: "100%",
24
+ pointerEvents: "none",
25
+ zIndex: 10000,
26
+ });
27
+ const ThoughtBubble = styled("div", {
28
+ shouldForwardProp: (prop) => !["isHovered", "isTruncated", "animationDelay", "bubbleIndex", "isVisible", "isExiting"].includes(prop),
29
+ })(({ theme, isHovered, isTruncated, animationDelay, bubbleIndex, isVisible = true, isExiting = false }) => ({
30
+ // Colors / theme
31
+ // TODO: Add dark mode support? For now both light and dark mode use the same bubble style and look fine.
32
+ background: "linear-gradient(135deg, rgba(255,255,255,0.98) 0%, rgba(250,250,250,0.95) 100%)",
33
+ border: "var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)",
34
+ borderRadius: "var(--bs-border-radius-lg)",
35
+ color: "var(--bs-primary)",
36
+ fontFamily: theme.typography.fontFamily, // TODO: Easy to pull from theme. Rest we need to revisit.
37
+ fontSize: "var(--bs-body-font-size-extra-small)",
38
+ fontWeight: "var(--bs-body-font-weight)",
39
+ padding: "10px 14px",
40
+ // Positioning - restore original right-side layout
41
+ position: "absolute",
42
+ right: BUBBLE_DISTANCE_FROM_RIGHT_EDGE,
43
+ top: BUBBLE_STACK_OFFSET_TOP + bubbleIndex * BUBBLE_HEIGHT_PLUS_SPACING, // Stack vertically with spacing
44
+ transform: "none",
45
+ // Dimensions
46
+ // Only expand height when hovered AND text is truncated
47
+ height: isHovered && isTruncated ? BUBBLE_HEIGHT : "auto",
48
+ maxHeight: BUBBLE_HEIGHT, // Max 3 lines always
49
+ minHeight: "auto", // Let height adjust to content
50
+ minWidth: "100px",
51
+ width: BUBBLE_WIDTH,
52
+ // Other styles
53
+ boxShadow: isHovered
54
+ ? "0 4px 20px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.08)"
55
+ : "0 2px 12px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.08)",
56
+ zIndex: isHovered ? 10002 : 10000,
57
+ lineHeight: 1.4,
58
+ backdropFilter: "blur(12px)",
59
+ WebkitBackdropFilter: "blur(12px)",
60
+ transition: `box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1),
61
+ z-index 0.15s cubic-bezier(0.4, 0, 0.2, 1),
62
+ transform 0.15s cubic-bezier(0.4, 0, 0.2, 1)`,
63
+ cursor: isTruncated ? "pointer" : "default",
64
+ userSelect: isHovered && isTruncated ? "text" : "none",
65
+ animation: isExiting
66
+ ? "fadeOutDown 0.4s cubic-bezier(0.4, 0, 0.1, 1) both"
67
+ : `fadeInUp 0.6s cubic-bezier(0.2, 0, 0.2, 1) ${animationDelay}ms both`,
68
+ opacity: isVisible ? (isExiting ? 0 : 1) : 0,
69
+ pointerEvents: "auto",
70
+ wordBreak: "break-word",
71
+ overflow: "hidden", // Always hide overflow
72
+ // Enable vertical scrolling only when hovered and truncated
73
+ overflowY: isHovered && isTruncated ? "auto" : "hidden",
74
+ whiteSpace: "normal",
75
+ }));
76
+ const TruncatedText = styled("div")(({ isHovered, isTruncated }) => ({
77
+ display: isHovered && isTruncated ? "block" : "-webkit-box",
78
+ WebkitLineClamp: isHovered && isTruncated ? "unset" : 3,
79
+ WebkitBoxOrient: isHovered && isTruncated ? "unset" : "vertical",
80
+ overflow: "hidden",
81
+ textOverflow: "ellipsis",
82
+ }));
83
+ // #endregion: Styled Components
84
+ export const ThoughtBubbleOverlay = ({ nodes, edges, showThoughtBubbles = true, isStreaming = false, onBubbleHoverChange, }) => {
85
+ // hoveredBubbleId: id of currently hovered bubble (or null)
86
+ const [hoveredBubbleId, setHoveredBubbleId] = useState(null);
87
+ // truncatedBubbles: set of edge ids whose text overflows the collapsed box
88
+ const [truncatedBubbles, setTruncatedBubbles] = useState(new Set());
89
+ // bubbleStates: track animation state of each bubble and when it's entered (used to delay line rendering
90
+ // until the bubble's entrance animation delay has passed)
91
+ const [bubbleStates, setBubbleStates] = useState(new Map());
92
+ // hoverTimeoutRef: used to debounce clearing of hovered state on mouse leave
93
+ const hoverTimeoutRef = useRef(null);
94
+ // textRefs: mapping of edge id -> DOM node for measuring scrollHeight/clientHeight
95
+ const textRefs = useRef(new Map());
96
+ // animationTimeouts: track timeouts for bubble removal
97
+ const animationTimeouts = useRef(new Map());
98
+ // Refs for SVG lines to update without re-rendering
99
+ const lineRefs = useRef(new Map());
100
+ // Ref to the overlay container so we can observe layout changes more precisely
101
+ const overlayRef = useRef(null);
102
+ // rAF ref for scheduling post-paint updates
103
+ const rafRef = useRef(null);
104
+ const mountedRef = useRef(true);
105
+ // Filter edges with meaningful text (memoized to prevent infinite re-renders)
106
+ const thoughtBubbleEdges = useMemo(() => edges.filter((e) => {
107
+ if (typeof e?.data?.text !== "string") {
108
+ return false;
109
+ }
110
+ return e.data.text;
111
+ }), [edges]);
112
+ // Find frontman node (depth === 0, similar to isFrontman logic in AgentNode.tsx)
113
+ const frontmanNode = useMemo(() => {
114
+ if (!nodes || !Array.isArray(nodes) || nodes.length === 0)
115
+ return null;
116
+ return nodes.find((n) => n.data?.depth === 0);
117
+ }, [nodes]);
118
+ // Handle bubble lifecycle (appear/disappear animations)
119
+ useEffect(() => {
120
+ const currentEdgeIds = new Set(thoughtBubbleEdges.map((e) => e.id));
121
+ const previousBubbleIds = new Set(bubbleStates.keys());
122
+ // Find new bubbles that should appear
123
+ const newBubbles = thoughtBubbleEdges.filter((e) => !previousBubbleIds.has(e.id));
124
+ // Find bubbles that should disappear
125
+ const removingBubbles = Array.from(previousBubbleIds).filter((id) => !currentEdgeIds.has(id));
126
+ setBubbleStates((prev) => {
127
+ const newState = new Map(prev);
128
+ // Add new bubbles in entering state. Record when they entered so we can delay showing
129
+ // connecting lines until the bubble's entrance animation delay.
130
+ const now = Date.now();
131
+ newBubbles.forEach((edge) => {
132
+ newState.set(edge.id, { isVisible: true, isExiting: false, enteredAt: now });
133
+ });
134
+ // Mark removing bubbles as exiting
135
+ removingBubbles.forEach((id) => {
136
+ const currentState = newState.get(id);
137
+ if (currentState) {
138
+ newState.set(id, { ...currentState, isExiting: true });
139
+ // Clear any existing timeout
140
+ const existingTimeout = animationTimeouts.current.get(id);
141
+ if (existingTimeout) {
142
+ clearTimeout(existingTimeout);
143
+ }
144
+ // Schedule removal after exit animation
145
+ const timeout = window.setTimeout(() => {
146
+ setBubbleStates((s) => {
147
+ const updatedState = new Map(s);
148
+ updatedState.delete(id);
149
+ return updatedState;
150
+ });
151
+ animationTimeouts.current.delete(id);
152
+ }, 400); // Match exit animation duration (0.4s)
153
+ animationTimeouts.current.set(id, timeout);
154
+ }
155
+ });
156
+ return newState;
157
+ });
158
+ }, [thoughtBubbleEdges]);
159
+ // Cleanup timeouts on unmount
160
+ useEffect(() => {
161
+ return () => {
162
+ animationTimeouts.current.forEach((timeout) => clearTimeout(timeout));
163
+ animationTimeouts.current.clear();
164
+ };
165
+ }, []);
166
+ // Sort edges to prioritize frontman's edges first
167
+ const sortedEdges = useMemo(() => {
168
+ if (!frontmanNode)
169
+ return thoughtBubbleEdges;
170
+ const frontmanEdges = thoughtBubbleEdges.filter((e) => e.source === frontmanNode.id || e.target === frontmanNode.id);
171
+ const otherEdges = thoughtBubbleEdges.filter((e) => e.source !== frontmanNode.id && e.target !== frontmanNode.id);
172
+ return [...frontmanEdges, ...otherEdges];
173
+ }, [thoughtBubbleEdges, frontmanNode]);
174
+ // Determine which agents are currently "active" using the same logic as AgentNode.
175
+ // An agent is active if any current conversation includes that agent's id.
176
+ const activeAgentIds = useMemo(() => {
177
+ const set = new Set();
178
+ if (!nodes || !Array.isArray(nodes))
179
+ return set;
180
+ for (const node of nodes) {
181
+ const getConversations = node.data?.getConversations;
182
+ if (typeof getConversations === "function") {
183
+ const convs = getConversations();
184
+ if (Array.isArray(convs)) {
185
+ const hasSelf = convs.some((conv) => Boolean(conv?.agents?.has?.(node.id)));
186
+ if (hasSelf) {
187
+ set.add(node.id);
188
+ }
189
+ }
190
+ }
191
+ }
192
+ return set;
193
+ }, [nodes]);
194
+ // Check truncation after render, but only when nothing is hovered
195
+ // (to avoid measuring expanded bubbles)
196
+ useEffect(() => {
197
+ // Skip truncation check if any bubble is hovered
198
+ if (hoveredBubbleId !== null)
199
+ return;
200
+ const newTruncated = new Set();
201
+ textRefs.current.forEach((element, edgeId) => {
202
+ // If scrollHeight > clientHeight then the content overflows (truncated)
203
+ if (element && element.scrollHeight > element.clientHeight) {
204
+ newTruncated.add(edgeId);
205
+ }
206
+ });
207
+ setTruncatedBubbles((prev) => {
208
+ // Only update if something changed
209
+ if (prev.size !== newTruncated.size)
210
+ return newTruncated;
211
+ // Check if the contents are the same
212
+ for (const id of newTruncated) {
213
+ if (!prev.has(id))
214
+ return newTruncated;
215
+ }
216
+ return prev;
217
+ });
218
+ }, [hoveredBubbleId, sortedEdges, textRefs]); // Re-check when edges change or hover state changes
219
+ // Notify parent when hover state changes
220
+ const handleHoverChange = useCallback((bubbleId) => {
221
+ // Clear any pending timeout
222
+ if (hoverTimeoutRef.current) {
223
+ clearTimeout(hoverTimeoutRef.current);
224
+ hoverTimeoutRef.current = null;
225
+ }
226
+ if (bubbleId === null) {
227
+ // Delay clearing the hover state when mouse leaves to prevent accidental unhover
228
+ // "window." to satisfy typescript
229
+ hoverTimeoutRef.current = window.setTimeout(() => {
230
+ setHoveredBubbleId(null);
231
+ if (onBubbleHoverChange) {
232
+ onBubbleHoverChange(null);
233
+ }
234
+ }, 200); // 200ms delay before clearing hover
235
+ }
236
+ else {
237
+ // Immediately set hover when mouse enters
238
+ setHoveredBubbleId(bubbleId);
239
+ if (onBubbleHoverChange) {
240
+ onBubbleHoverChange(bubbleId);
241
+ }
242
+ }
243
+ }, [onBubbleHoverChange]);
244
+ // Calculate line coordinates - measurement only. Can be called from rAF/update loop.
245
+ const calculateLineCoordinates = useCallback((edge, bubbleIndex, agentRectCache) => {
246
+ // Skip HUMAN conversation types - no lines for human bubbles
247
+ if (edge.data?.type === ChatMessageType.HUMAN) {
248
+ return null;
249
+ }
250
+ // Get actual bubble DOM position (fresh every time)
251
+ const bubbleElement = document.querySelector(`[data-bubble-id="${CSS.escape(edge.id)}"]`);
252
+ let bubbleX;
253
+ let bubbleY;
254
+ if (bubbleElement) {
255
+ const bubbleRect = bubbleElement.getBoundingClientRect();
256
+ // Use the left edge center of the bubble (where line should start)
257
+ bubbleX = Math.round(bubbleRect.left);
258
+ bubbleY = Math.round(bubbleRect.top + bubbleRect.height / 2);
259
+ }
260
+ else {
261
+ // Fallback: calculate approximate viewport position
262
+ bubbleX = window.innerWidth - BUBBLE_DISTANCE_FROM_RIGHT_EDGE - BUBBLE_WIDTH;
263
+ bubbleY = BUBBLE_STACK_OFFSET_TOP + bubbleIndex * BUBBLE_HEIGHT_PLUS_SPACING + BUBBLE_HEIGHT / 2;
264
+ }
265
+ // Determine which agents to point to. If the edge supplies an `agents` array in
266
+ // data (provided by AgentFlow), use that. Otherwise fallback to the explicit
267
+ // edge.target/edge.source pair (single target).
268
+ let agentIds = Array.isArray(edge.data?.agents)
269
+ ? edge.data?.agents
270
+ : [edge.target || edge.source].filter(Boolean);
271
+ if (agentIds.length === 0)
272
+ return null;
273
+ // Filter out any agents that are not currently active (we only draw lines to active agents).
274
+ // Always apply filtering based on the activeAgentIds set.
275
+ agentIds = agentIds.filter((id) => activeAgentIds.has(id));
276
+ if (agentIds.length === 0)
277
+ return null;
278
+ // For each agent id, find its visual element and calculate mid-point.
279
+ const results = [];
280
+ for (const agentId of agentIds) {
281
+ // Find the agent element by its data-id attribute
282
+ const agentElements = document.querySelectorAll(`[data-id="${CSS.escape(agentId)}"].react-flow__node`);
283
+ const foundAgentEl = agentElements?.[0] || null;
284
+ let agentX = 0;
285
+ let agentY = 0;
286
+ if (foundAgentEl) {
287
+ // Prefer the cached rect when present; otherwise compute and cache it.
288
+ const cachedRect = agentRectCache?.get(agentId);
289
+ const containerRect = cachedRect ?? foundAgentEl.getBoundingClientRect();
290
+ if (cachedRect == null) {
291
+ agentRectCache?.set(agentId, containerRect);
292
+ }
293
+ if (containerRect) {
294
+ agentX = Math.round(containerRect.left + containerRect.width / 2);
295
+ agentY = Math.round(containerRect.top + containerRect.height / 2);
296
+ }
297
+ }
298
+ results.push({ x1: bubbleX, y1: bubbleY, x2: agentX, y2: agentY, targetAgent: agentId });
299
+ }
300
+ return results;
301
+ }, [activeAgentIds]);
302
+ // Get all bubbles to render (including exiting ones)
303
+ const allBubbleIds = Array.from(bubbleStates.keys());
304
+ // Memoize the resolved edges so updateAllLines (and effects depending on it)
305
+ // doesn't get recreated on every render unnecessarily.
306
+ const renderableBubbles = useMemo(() => allBubbleIds
307
+ .map((id) => sortedEdges.find((e) => e.id === id) ?? edges.find((e) => e.id === id))
308
+ .filter((edge) => edge !== undefined), [allBubbleIds, sortedEdges, edges]);
309
+ // Update all SVG lines imperatively after paint. This reads DOM (getBoundingClientRect)
310
+ // and writes attributes on existing <line> elements stored in `lineRefs`.
311
+ const updateAllLines = useCallback(() => {
312
+ try {
313
+ renderableBubbles.forEach((edge, index) => {
314
+ const bubbleState = bubbleStates.get(edge.id) || { isVisible: true, isExiting: false, enteredAt: 0 };
315
+ if (!bubbleState.isVisible || bubbleState.isExiting)
316
+ return;
317
+ // Respect the entrance animation delay gating (lines appear only after bubble start)
318
+ const elapsed = Date.now() - (bubbleState?.enteredAt ?? 0);
319
+ const animationDelay = index * LAYOUT_BUBBLES_ANIMATION_DELAY_MS;
320
+ if (elapsed < animationDelay)
321
+ return;
322
+ const coordsArray = calculateLineCoordinates(edge, index);
323
+ if (!coordsArray || coordsArray.length === 0)
324
+ return;
325
+ for (const coords of coordsArray) {
326
+ const lineKey = `${edge.id}-${coords.targetAgent}`;
327
+ const lineEl = lineRefs.current.get(lineKey);
328
+ if (lineEl) {
329
+ // Update attributes imperatively
330
+ lineEl.setAttribute("x1", String(coords.x1));
331
+ lineEl.setAttribute("y1", String(coords.y1));
332
+ lineEl.setAttribute("x2", String(coords.x2));
333
+ lineEl.setAttribute("y2", String(coords.y2));
334
+ }
335
+ }
336
+ });
337
+ }
338
+ catch (err) {
339
+ // Guard against unexpected DOM errors during measurement
340
+ // Do not throw to avoid breaking the app
341
+ console.debug("ThoughtBubbleOverlay: updateAllLines error", err);
342
+ }
343
+ }, [renderableBubbles, bubbleStates, calculateLineCoordinates]);
344
+ // Schedule post-paint updates with rAF and ResizeObserver. Also optionally run a
345
+ // continuous loop while `isStreaming` is true to keep lines in sync during streaming.
346
+ useEffect(() => {
347
+ mountedRef.current = true;
348
+ const schedule = () => {
349
+ if (rafRef.current != null)
350
+ return;
351
+ rafRef.current = requestAnimationFrame(() => {
352
+ rafRef.current = null;
353
+ if (!mountedRef.current)
354
+ return;
355
+ updateAllLines();
356
+ });
357
+ };
358
+ // Initial schedule
359
+ schedule();
360
+ // Resize observer to detect layout/size changes
361
+ const ro = new ResizeObserver(() => schedule());
362
+ try {
363
+ if (overlayRef.current)
364
+ ro.observe(overlayRef.current);
365
+ else
366
+ ro.observe(document.body);
367
+ }
368
+ catch {
369
+ // Ignore if RO observation fails in some test environments
370
+ }
371
+ // Window resize
372
+ window.addEventListener("resize", schedule);
373
+ // If streaming, run a continuous rAF loop to keep up with frequent layout updates
374
+ let streamingRaf = null;
375
+ const startStreamingLoop = () => {
376
+ if (streamingRaf != null)
377
+ return;
378
+ const loop = () => {
379
+ updateAllLines();
380
+ streamingRaf = requestAnimationFrame(loop);
381
+ };
382
+ streamingRaf = requestAnimationFrame(loop);
383
+ };
384
+ const stopStreamingLoop = () => {
385
+ if (streamingRaf != null) {
386
+ cancelAnimationFrame(streamingRaf);
387
+ streamingRaf = null;
388
+ }
389
+ };
390
+ if (isStreaming)
391
+ startStreamingLoop();
392
+ return () => {
393
+ mountedRef.current = false;
394
+ if (rafRef.current != null)
395
+ cancelAnimationFrame(rafRef.current);
396
+ ro.disconnect();
397
+ window.removeEventListener("resize", schedule);
398
+ stopStreamingLoop();
399
+ };
400
+ }, [updateAllLines, isStreaming]);
401
+ // Don't render anything if thought bubbles are disabled
402
+ // This check is placed after all hooks so hook ordering stays consistent across renders.
403
+ if (!showThoughtBubbles)
404
+ return null;
405
+ return (_jsxs(OverlayContainer, { children: [_jsx("svg", { style: {
406
+ position: "fixed",
407
+ left: 0,
408
+ top: 0,
409
+ width: "100vw",
410
+ height: "100vh",
411
+ pointerEvents: "none",
412
+ zIndex: 9998,
413
+ opacity: 1,
414
+ }, children: renderableBubbles.map((edge, index) => {
415
+ // Per-bubble staggered animation delay in milliseconds (for line animations)
416
+ const animationDelay = index * LAYOUT_BUBBLES_ANIMATION_DELAY_MS;
417
+ const bubbleState = bubbleStates.get(edge.id) || { isVisible: true, isExiting: false, enteredAt: 0 };
418
+ if (!bubbleState.isVisible)
419
+ return null;
420
+ // Only render lines after the bubble's entrance animation delay has elapsed.
421
+ const elapsed = Date.now() - (bubbleState.enteredAt || 0);
422
+ const shouldShowLines = elapsed >= animationDelay;
423
+ if (!shouldShowLines)
424
+ return null;
425
+ // Calculate fresh coordinates for this line (may return multiple targets)
426
+ const coordsArray = calculateLineCoordinates(edge, index);
427
+ if (!coordsArray || coordsArray.length === 0)
428
+ return null;
429
+ return (_jsx("g", { children: coordsArray.map((coords) => {
430
+ const lineKey = `${edge.id}-${coords.targetAgent}`;
431
+ return (_jsx("line", { ref: (el) => {
432
+ if (el) {
433
+ lineRefs.current.set(lineKey, el);
434
+ }
435
+ else {
436
+ lineRefs.current.delete(lineKey);
437
+ }
438
+ }, x1: coords.x1, y1: coords.y1, x2: coords.x2, y2: coords.y2, stroke: "var(--thought-bubble-line-color)", strokeWidth: "3", strokeDasharray: "3,3", style: {
439
+ opacity: bubbleState.isExiting ? 0 : CONNECTING_LINE_OPACITY,
440
+ transition: (() => {
441
+ const duration = bubbleState.isExiting ? 400 : 600;
442
+ const delay = bubbleState.isExiting ? 0 : animationDelay;
443
+ return `opacity ${duration}ms cubic-bezier(0.2, 0, 0.2, 1) ${delay}ms`;
444
+ })(),
445
+ } }, `line-${lineKey}`));
446
+ }) }, `line-group-${edge.id}`));
447
+ }) }), renderableBubbles.map((edge, index) => {
448
+ const text = edge.data?.text;
449
+ if (!text)
450
+ return null;
451
+ // Per-bubble staggered animation delay in milliseconds
452
+ const animationDelay = index * LAYOUT_BUBBLES_ANIMATION_DELAY_MS;
453
+ const isHovered = hoveredBubbleId === edge.id;
454
+ const isTruncated = truncatedBubbles.has(edge.id);
455
+ const bubbleState = bubbleStates.get(edge.id) || { isVisible: true, isExiting: false };
456
+ return (_jsx(Fragment, { children: _jsx(ThoughtBubble, { "data-bubble-id": edge.id, isHovered: isHovered, isTruncated: isTruncated, animationDelay: animationDelay, bubbleIndex: index, isVisible: bubbleState.isVisible, isExiting: bubbleState.isExiting, onMouseEnter: () => handleHoverChange(edge.id), onMouseLeave: () => handleHoverChange(null), children: _jsx(TruncatedText, { isHovered: isHovered, isTruncated: isTruncated, ref: (el) => {
457
+ // Store/remove this text node in `textRefs` for truncation checks.
458
+ if (el) {
459
+ textRefs.current.set(edge.id, el);
460
+ }
461
+ else {
462
+ textRefs.current.delete(edge.id);
463
+ }
464
+ }, children: text }) }) }, edge.id));
465
+ })] }));
466
+ };
@@ -0,0 +1,7 @@
1
+ export declare const DEFAULT_FRONTMAN_X_POS = 150;
2
+ export declare const DEFAULT_FRONTMAN_Y_POS = 450;
3
+ export declare const BASE_RADIUS = 100;
4
+ export declare const LEVEL_SPACING = 150;
5
+ export declare const BACKGROUND_COLORS: string[];
6
+ export declare const BACKGROUND_COLORS_DARK_IDX = 6;
7
+ export declare const HEATMAP_COLORS: string[];
@@ -0,0 +1,39 @@
1
+ /*
2
+ Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+ export const DEFAULT_FRONTMAN_X_POS = 150;
17
+ export const DEFAULT_FRONTMAN_Y_POS = 450;
18
+ // Minimum distance from center
19
+ export const BASE_RADIUS = 100;
20
+ // Distance between depth levels
21
+ export const LEVEL_SPACING = 150;
22
+ // Palette for progressive coloring of nodes based on depth
23
+ export const BACKGROUND_COLORS = [
24
+ "#f7fbff",
25
+ "#deebf7",
26
+ "#c6dbef",
27
+ "#9ecae1",
28
+ "#6baed6",
29
+ "#4292c6",
30
+ "#2171b5",
31
+ "#08519c",
32
+ "#08306b",
33
+ "#041c45",
34
+ ];
35
+ // Zero-based index of the one where colors start to get dark. Used for displaying contrasting text colors.
36
+ export const BACKGROUND_COLORS_DARK_IDX = 6; // Somewhat subjective
37
+ // Palette for heatmap coloring of nodes
38
+ // For now, use same palette as background colors
39
+ export const HEATMAP_COLORS = BACKGROUND_COLORS;
@@ -0,0 +1,10 @@
1
+ export declare const LOGO: string;
2
+ export declare const NEURO_SAN_UI_VERSION: string;
3
+ export declare const CONTACT_US_CONFIRMATION_DIALOG_TITLE = "Contact Us";
4
+ export declare const CONTACT_US_CONFIRMATION_DIALOG_TEXT: string;
5
+ /**
6
+ * The default user image to use when the user does not have a profile picture.
7
+ */
8
+ export declare const DEFAULT_USER_IMAGE = "https://www.gravatar.com/avatar/?d=mp";
9
+ export declare const authenticationEnabled: () => boolean;
10
+ export declare const DEFAULT_NEURO_SAN_SERVER_URL = "https://neuro-san-dev.decisionai.ml";
package/dist/const.js ADDED
@@ -0,0 +1,30 @@
1
+ /*
2
+ Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+ // Name to use for application
17
+ export const LOGO = "Neuro® AI";
18
+ export const NEURO_SAN_UI_VERSION = process.env["NEXT_PUBLIC_NEURO_SAN_UI_VERSION"] ?? "Unknown Version";
19
+ export const CONTACT_US_CONFIRMATION_DIALOG_TITLE = "Contact Us";
20
+ export const CONTACT_US_CONFIRMATION_DIALOG_TEXT = "Would you like to send the Cognizant Neuro AI support team an email? " +
21
+ "You will need to have an email client installed on your device in order " +
22
+ "to continue. If you don't have an email client, you can still contact us at " +
23
+ "NeuroAiSupport@cognizant.com using a web based email client.";
24
+ /**
25
+ * The default user image to use when the user does not have a profile picture.
26
+ */
27
+ export const DEFAULT_USER_IMAGE = "https://www.gravatar.com/avatar/?d=mp";
28
+ export const authenticationEnabled = () => process.env["NEXT_PUBLIC_ENABLE_AUTHENTICATION"] !== "false";
29
+ // Default "dev URL" for NeuroSan server, to allow for "zero config" execution.
30
+ export const DEFAULT_NEURO_SAN_SERVER_URL = "https://neuro-san-dev.decisionai.ml";
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Controller module for interacting with the Agent LLM API.
3
+ */
4
+ import { ChatContext, ChatResponse, ConnectivityResponse, FunctionResponse } from "../../generated/neuro-san/NeuroSanClient.js";
5
+ export interface TestConnectionResult {
6
+ readonly success: boolean;
7
+ readonly status?: string;
8
+ readonly version?: string;
9
+ }
10
+ /**
11
+ * Test connection for a neuro-san server.
12
+ * @param url The neuro-san server URL.
13
+ * @returns A boolean indicating whether the connection was successful.
14
+ */
15
+ export declare function testConnection(url: string): Promise<TestConnectionResult>;
16
+ /**
17
+ * Get the list of available agent networks from the concierge service.
18
+ * @param url The neuro-san server URL
19
+ * @returns A promise that resolves to an array of agent network names.
20
+ */
21
+ export declare function getAgentNetworks(url: string): Promise<string[]>;
22
+ /**
23
+ * Send a chat query to the Agent LLM API. This opens a session with the agent network..
24
+ * @param url The neuro-san server URL
25
+ * @param signal The AbortSignal to use for the request. Used to cancel the request on user demand
26
+ * @param userInput The user input to send to the agent.
27
+ * In practice this "input" will actually be the output from one of the previous agents such as the data generator
28
+ * or scoping agent.
29
+ * @param targetAgent The target agent to send the request to. See CombinedAgentType for the list of available agents.
30
+ * @param callback The callback function to be called when a chunk of data is received from the server.
31
+ * @param chatContext "Opaque" conversation context for maintaining conversation state with the server. Neuro-san
32
+ * agents do not use ChatHistory directly, but rather, ChatContext, which is a collection of ChatHistory objects.
33
+ * @param slyData Data items that should not be send to the LLM. Generated by the server.
34
+ * @param userId Current user ID in the session.
35
+ * @returns The response from the agent network.
36
+ */
37
+ export declare function sendChatQuery(url: string, signal: AbortSignal, userInput: string, targetAgent: string, callback: (chunk: string) => void, chatContext: ChatContext, slyData: Record<string, unknown>, userId: string): Promise<ChatResponse>;
38
+ /**
39
+ * Gets information on the agent and tool connections within a network
40
+ * @param url The neuro-san server URL
41
+ * @param network The network to get connectivity information for
42
+ * @param userId Current user ID in the session.
43
+ * @returns The connectivity info as a <code>ConnectivityResponse</code> object
44
+ * @throws Various exceptions if anything goes wrong such as network issues or invalid agent type.
45
+ * Caller is responsible for try-catch.
46
+ */
47
+ export declare function getConnectivity(url: string, network: string, userId: string): Promise<ConnectivityResponse>;
48
+ /**
49
+ * Get the function of a specified agent meaning its brief description
50
+ * @param url The neuro-san server URL
51
+ * @param agent The agent to get the function for
52
+ * @param userId Current user ID in the session.
53
+ * @returns The function info as a <code>FunctionResponse</code> object
54
+ * @throws Various exceptions if anything goes wrong such as network issues or invalid agent type.
55
+ */
56
+ export declare function getAgentFunction(url: string, agent: string, userId: string): Promise<FunctionResponse>;