@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.
- package/dist/components/AgentChat/ChatCommon.d.ts +94 -0
- package/dist/components/AgentChat/ChatCommon.js +581 -0
- package/dist/components/AgentChat/ControlButtons.d.ts +16 -0
- package/dist/components/AgentChat/ControlButtons.js +24 -0
- package/dist/components/AgentChat/FormattedMarkdown.d.ts +32 -0
- package/dist/components/AgentChat/FormattedMarkdown.js +82 -0
- package/dist/components/AgentChat/Greetings.d.ts +1 -0
- package/dist/components/AgentChat/Greetings.js +38 -0
- package/dist/components/AgentChat/LlmChatButton.d.ts +12 -0
- package/dist/components/AgentChat/LlmChatButton.js +33 -0
- package/dist/components/AgentChat/SendButton.d.ts +12 -0
- package/dist/components/AgentChat/SendButton.js +28 -0
- package/dist/components/AgentChat/SyntaxHighlighterThemes.d.ts +14 -0
- package/dist/components/AgentChat/SyntaxHighlighterThemes.js +27 -0
- package/dist/components/AgentChat/Types.d.ts +17 -0
- package/dist/components/AgentChat/Types.js +26 -0
- package/dist/components/AgentChat/UserQueryDisplay.d.ts +5 -0
- package/dist/components/AgentChat/UserQueryDisplay.js +33 -0
- package/dist/components/AgentChat/Utils.d.ts +11 -0
- package/dist/components/AgentChat/Utils.js +64 -0
- package/dist/components/AgentChat/VoiceChat/MicrophoneButton.d.ts +29 -0
- package/dist/components/AgentChat/VoiceChat/MicrophoneButton.js +55 -0
- package/dist/components/AgentChat/VoiceChat/VoiceChat.d.ts +33 -0
- package/dist/components/AgentChat/VoiceChat/VoiceChat.js +180 -0
- package/dist/components/Authentication/Auth.d.ts +14 -0
- package/dist/components/Authentication/Auth.js +58 -0
- package/dist/components/ChatBot/ChatBot.d.ts +20 -0
- package/dist/components/ChatBot/ChatBot.js +75 -0
- package/dist/components/Common/Breadcrumbs.d.ts +6 -0
- package/dist/components/Common/Breadcrumbs.js +36 -0
- package/dist/components/Common/LlmChatOptionsButton.d.ts +9 -0
- package/dist/components/Common/LlmChatOptionsButton.js +31 -0
- package/dist/components/Common/LoadingSpinner.d.ts +10 -0
- package/dist/components/Common/LoadingSpinner.js +24 -0
- package/dist/components/Common/MUIAccordion.d.ts +17 -0
- package/dist/components/Common/MUIAccordion.js +76 -0
- package/dist/components/Common/MUIAlert.d.ts +11 -0
- package/dist/components/Common/MUIAlert.js +41 -0
- package/dist/components/Common/MUIDialog.d.ts +16 -0
- package/dist/components/Common/MUIDialog.js +40 -0
- package/dist/components/Common/Navbar.d.ts +15 -0
- package/dist/components/Common/Navbar.js +137 -0
- package/dist/components/Common/PageLoader.d.ts +5 -0
- package/dist/components/Common/PageLoader.js +26 -0
- package/dist/components/Common/Snackbar.d.ts +5 -0
- package/dist/components/Common/Snackbar.js +84 -0
- package/dist/components/Common/confirmationModal.d.ts +14 -0
- package/dist/components/Common/confirmationModal.js +65 -0
- package/dist/components/Common/notification.d.ts +18 -0
- package/dist/components/Common/notification.js +79 -0
- package/dist/components/ErrorPage/ErrorBoundary.d.ts +38 -0
- package/dist/components/ErrorPage/ErrorBoundary.js +77 -0
- package/dist/components/ErrorPage/ErrorPage.d.ts +12 -0
- package/dist/components/ErrorPage/ErrorPage.js +46 -0
- package/dist/components/MultiAgentAccelerator/AgentFlow.d.ts +21 -0
- package/dist/components/MultiAgentAccelerator/AgentFlow.js +394 -0
- package/dist/components/MultiAgentAccelerator/AgentNode.d.ts +18 -0
- package/dist/components/MultiAgentAccelerator/AgentNode.js +129 -0
- package/dist/components/MultiAgentAccelerator/GraphLayouts.d.ts +33 -0
- package/dist/components/MultiAgentAccelerator/GraphLayouts.js +297 -0
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +17 -0
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +208 -0
- package/dist/components/MultiAgentAccelerator/PlasmaEdge.d.ts +3 -0
- package/dist/components/MultiAgentAccelerator/PlasmaEdge.js +124 -0
- package/dist/components/MultiAgentAccelerator/Sidebar.d.ts +12 -0
- package/dist/components/MultiAgentAccelerator/Sidebar.js +204 -0
- package/dist/components/MultiAgentAccelerator/ThoughtBubbleEdge.d.ts +12 -0
- package/dist/components/MultiAgentAccelerator/ThoughtBubbleEdge.js +15 -0
- package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.d.ts +11 -0
- package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.js +466 -0
- package/dist/components/MultiAgentAccelerator/const.d.ts +7 -0
- package/dist/components/MultiAgentAccelerator/const.js +39 -0
- package/dist/const.d.ts +10 -0
- package/dist/const.js +30 -0
- package/dist/controller/agent/Agent.d.ts +56 -0
- package/dist/controller/agent/Agent.js +162 -0
- package/dist/controller/llm/LlmChat.d.ts +18 -0
- package/dist/controller/llm/LlmChat.js +65 -0
- package/dist/controller/llm/endpoints.d.ts +1 -0
- package/dist/controller/llm/endpoints.js +17 -0
- package/dist/generated/neuro-san/NeuroSanClient.d.ts +413 -0
- package/dist/generated/neuro-san/NeuroSanClient.js +28 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +52 -0
- package/dist/state/UserInfo.d.ts +16 -0
- package/dist/state/UserInfo.js +27 -0
- package/dist/state/environment.d.ts +18 -0
- package/dist/state/environment.js +33 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/utils/Authentication.d.ts +31 -0
- package/dist/utils/Authentication.js +94 -0
- package/dist/utils/BrowserNavigation.d.ts +5 -0
- package/dist/utils/BrowserNavigation.js +22 -0
- package/dist/utils/Theme.d.ts +7 -0
- package/dist/utils/Theme.js +7 -0
- package/dist/utils/agentConversations.d.ts +24 -0
- package/dist/utils/agentConversations.js +113 -0
- package/dist/utils/text.d.ts +28 -0
- package/dist/utils/text.js +64 -0
- package/dist/utils/title.d.ts +1 -0
- package/dist/utils/title.js +20 -0
- package/dist/utils/types.d.ts +17 -0
- package/dist/utils/types.js +16 -0
- package/dist/utils/useLocalStorage.d.ts +1 -0
- package/dist/utils/useLocalStorage.js +55 -0
- package/dist/utils/zIndexLayers.d.ts +2 -0
- package/dist/utils/zIndexLayers.js +29 -0
- package/package.json +69 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import Box from "@mui/material/Box";
|
|
18
|
+
import { useRouter } from "next/router.js";
|
|
19
|
+
import { LOGO } from "../../const.js";
|
|
20
|
+
import { useEnvironmentStore } from "../../state/environment.js";
|
|
21
|
+
import { useUserInfoStore } from "../../state/UserInfo.js";
|
|
22
|
+
import { smartSignOut, useAuthentication } from "../../utils/Authentication.js";
|
|
23
|
+
import { NeuroAIBreadcrumbs } from "../Common/Breadcrumbs.js";
|
|
24
|
+
import { Navbar } from "../Common/Navbar.js";
|
|
25
|
+
/**
|
|
26
|
+
* This is the page that will be shown to users when the outer error boundary is triggered
|
|
27
|
+
* @param id HTML id for the <code>div</code> for this page
|
|
28
|
+
* @param errorText Error text to be displayed
|
|
29
|
+
*/
|
|
30
|
+
export default function ErrorPage({ id, errorText }) {
|
|
31
|
+
const { auth0ClientId, auth0Domain, supportEmailAddress } = useEnvironmentStore();
|
|
32
|
+
// Access NextJS router
|
|
33
|
+
const router = useRouter();
|
|
34
|
+
// Access user info store
|
|
35
|
+
const { currentUser, setCurrentUser, setPicture, oidcProvider } = useUserInfoStore();
|
|
36
|
+
// Infer authentication type
|
|
37
|
+
const authenticationType = currentUser ? `ALB using ${oidcProvider}` : "NextAuth";
|
|
38
|
+
const { data: { user: userInfo } = {} } = useAuthentication();
|
|
39
|
+
async function handleSignOut() {
|
|
40
|
+
// Clear our state storage variables
|
|
41
|
+
setCurrentUser(undefined);
|
|
42
|
+
setPicture(undefined);
|
|
43
|
+
await smartSignOut(currentUser, auth0Domain, auth0ClientId, oidcProvider);
|
|
44
|
+
}
|
|
45
|
+
return (_jsxs(_Fragment, { children: [_jsx(Navbar, { id: "nav-bar", logo: LOGO, query: router.query, pathname: router.pathname, authenticationType: authenticationType, signOut: handleSignOut, supportEmailAddress: supportEmailAddress, userInfo: userInfo }), _jsxs(Box, { id: id, sx: { marginLeft: "1rem" }, children: [_jsx(NeuroAIBreadcrumbs, {}), _jsx("h4", { id: "error-header", style: { color: "var(--bs-red)", marginTop: "1rem" }, children: "Oops, an internal error occurred on our end." }), _jsxs("div", { id: "error-div-1", children: [_jsxs(Box, { id: "no-need-to-worry", sx: { marginBottom: "1.5rem" }, children: [_jsx("b", { id: "bold-1", children: "There's no need to worry \u2013 you didn't do anything wrong." }), " This is a bug in our server and we'll fix it as soon as possible."] }), _jsx(Box, { id: "try-these-steps", sx: { marginBottom: "1.5rem" }, children: "To attempt to recover from the error, try these steps:" }), _jsx(Box, { id: "refresh-page", sx: { marginBottom: "1.5rem" }, children: _jsxs("ul", { id: "error-boundary-advice-list", className: "list-decimal list-inside", children: [_jsx("li", { id: "error-advice-refresh", children: "Refresh the page to see if the error goes away." }), _jsx("li", { id: "error-advice-reload", children: "Close and re-open your browser, then navigate back to the page where you encountered the error." })] }) }), "If none of that helps, please report the following error to the LEAF team:"] }), _jsx("div", { id: "error-text-div", style: { fontFamily: "monospace", marginTop: "1.5rem", marginBottom: "1.5rem" }, children: errorText }), "More information may be available in the browser console. Please include such information in your report if possible."] })] }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Dispatch, FC, SetStateAction } from "react";
|
|
2
|
+
import { Edge, EdgeProps } from "reactflow";
|
|
3
|
+
import { ConnectivityInfo } from "../../generated/neuro-san/NeuroSanClient.js";
|
|
4
|
+
import { AgentConversation } from "../../utils/agentConversations.js";
|
|
5
|
+
export interface AgentFlowProps {
|
|
6
|
+
readonly agentCounts?: Map<string, number>;
|
|
7
|
+
readonly agentsInNetwork: ConnectivityInfo[];
|
|
8
|
+
readonly currentConversations?: AgentConversation[] | null;
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly isAwaitingLlm?: boolean;
|
|
11
|
+
readonly isStreaming?: boolean;
|
|
12
|
+
readonly thoughtBubbleEdges: Map<string, {
|
|
13
|
+
edge: Edge<EdgeProps>;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
}>;
|
|
16
|
+
readonly setThoughtBubbleEdges: Dispatch<SetStateAction<Map<string, {
|
|
17
|
+
edge: Edge<EdgeProps>;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
}>>>;
|
|
20
|
+
}
|
|
21
|
+
export declare const AgentFlow: FC<AgentFlowProps>;
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import AdjustRoundedIcon from "@mui/icons-material/AdjustRounded";
|
|
18
|
+
import ChatBubbleOutlineIcon from "@mui/icons-material/ChatBubbleOutline";
|
|
19
|
+
import HubOutlinedIcon from "@mui/icons-material/HubOutlined";
|
|
20
|
+
import ScatterPlotOutlinedIcon from "@mui/icons-material/ScatterPlotOutlined";
|
|
21
|
+
import { ToggleButton, ToggleButtonGroup, useColorScheme, useTheme } from "@mui/material";
|
|
22
|
+
import Box from "@mui/material/Box";
|
|
23
|
+
import Tooltip from "@mui/material/Tooltip";
|
|
24
|
+
import Typography from "@mui/material/Typography";
|
|
25
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
26
|
+
import { applyNodeChanges, Background, ConnectionMode, ControlButton, Controls, ReactFlow, useReactFlow, useStore, } from "reactflow";
|
|
27
|
+
import { AgentNode, NODE_HEIGHT, NODE_WIDTH } from "./AgentNode.js";
|
|
28
|
+
import { BACKGROUND_COLORS, BACKGROUND_COLORS_DARK_IDX, BASE_RADIUS, DEFAULT_FRONTMAN_X_POS, DEFAULT_FRONTMAN_Y_POS, HEATMAP_COLORS, LEVEL_SPACING, } from "./const.js";
|
|
29
|
+
import { addThoughtBubbleEdge, layoutLinear, layoutRadial, removeThoughtBubbleEdge } from "./GraphLayouts.js";
|
|
30
|
+
import { PlasmaEdge } from "./PlasmaEdge.js";
|
|
31
|
+
import { ThoughtBubbleEdge } from "./ThoughtBubbleEdge.js";
|
|
32
|
+
import { ThoughtBubbleOverlay } from "./ThoughtBubbleOverlay.js";
|
|
33
|
+
import { isDarkMode } from "../../utils/Theme.js";
|
|
34
|
+
import { getZIndex } from "../../utils/zIndexLayers.js";
|
|
35
|
+
// #endregion: Types
|
|
36
|
+
// #region: Constants
|
|
37
|
+
// Timeout for thought bubbles is set to 10 seconds
|
|
38
|
+
const THOUGHT_BUBBLE_TIMEOUT_MS = 10_000;
|
|
39
|
+
// Maximum number of thought bubbles allowed on screen at any time. As more arrive, older ones are removed.
|
|
40
|
+
const MAX_THOUGHT_BUBBLES = 5;
|
|
41
|
+
// #endregion: Constants
|
|
42
|
+
export const AgentFlow = ({ agentCounts, agentsInNetwork, currentConversations, id, isAwaitingLlm, isStreaming, thoughtBubbleEdges, setThoughtBubbleEdges, }) => {
|
|
43
|
+
const theme = useTheme();
|
|
44
|
+
const { fitView } = useReactFlow();
|
|
45
|
+
// Helper functions to update thought bubble edges state immutably
|
|
46
|
+
const addThoughtBubbleEdgeHelper = useCallback((conversationId, edge) => {
|
|
47
|
+
setThoughtBubbleEdges((prev) => {
|
|
48
|
+
const newMap = new Map(prev);
|
|
49
|
+
addThoughtBubbleEdge(newMap, conversationId, edge);
|
|
50
|
+
return newMap;
|
|
51
|
+
});
|
|
52
|
+
}, [setThoughtBubbleEdges]);
|
|
53
|
+
const removeThoughtBubbleEdgeHelper = useCallback((conversationId) => {
|
|
54
|
+
setThoughtBubbleEdges((prev) => {
|
|
55
|
+
const newMap = new Map(prev);
|
|
56
|
+
removeThoughtBubbleEdge(newMap, conversationId);
|
|
57
|
+
return newMap;
|
|
58
|
+
});
|
|
59
|
+
}, [setThoughtBubbleEdges]);
|
|
60
|
+
const handleResize = useCallback(() => {
|
|
61
|
+
fitView(); // Adjusts the view to fit after resizing
|
|
62
|
+
}, [fitView, isAwaitingLlm]);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
window.addEventListener("resize", handleResize);
|
|
65
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
66
|
+
}, [handleResize]);
|
|
67
|
+
// Save this as a mutable ref so child nodes see updates
|
|
68
|
+
const conversationsRef = useRef(currentConversations);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
conversationsRef.current = currentConversations;
|
|
71
|
+
}, [currentConversations]);
|
|
72
|
+
const [layout, setLayout] = useState("radial");
|
|
73
|
+
const [coloringOption, setColoringOption] = useState("depth");
|
|
74
|
+
const [enableRadialGuides, setEnableRadialGuides] = useState(true);
|
|
75
|
+
const [showThoughtBubbles, setShowThoughtBubbles] = useState(true);
|
|
76
|
+
// State for managing active thought bubbles with timers. Use ActiveThoughtBubble which
|
|
77
|
+
// references the original conversation via `conversationId`.
|
|
78
|
+
const [activeThoughtBubbles, setActiveThoughtBubbles] = useState([]);
|
|
79
|
+
// Track conversation IDs we've already processed to prevent re-adding after expiry
|
|
80
|
+
const processedConversationIdsRef = useRef(new Set());
|
|
81
|
+
// Track which bubble is currently being hovered
|
|
82
|
+
const hoveredBubbleIdRef = useRef(null);
|
|
83
|
+
const handleBubbleHoverChange = useCallback((bubbleId) => {
|
|
84
|
+
hoveredBubbleIdRef.current = bubbleId;
|
|
85
|
+
}, []);
|
|
86
|
+
// Clear processed conversation IDs when thought bubble edges are cleared (streaming ends)
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (thoughtBubbleEdges.size === 0) {
|
|
89
|
+
processedConversationIdsRef.current.clear();
|
|
90
|
+
}
|
|
91
|
+
}, [thoughtBubbleEdges.size]);
|
|
92
|
+
// Effect to add and remove thought bubbles - only when we actually have conversations to process
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (!currentConversations || currentConversations.length === 0) {
|
|
95
|
+
return; // Skip processing when no conversations
|
|
96
|
+
}
|
|
97
|
+
setActiveThoughtBubbles((prevBubbles) => {
|
|
98
|
+
const newBubbles = [];
|
|
99
|
+
// Check the text that user sees as one level of duplicate prevention
|
|
100
|
+
const processedText = new Set();
|
|
101
|
+
thoughtBubbleEdges.forEach((thoughtBubbleEdge) => {
|
|
102
|
+
const edgeText = thoughtBubbleEdge.edge.data?.text?.trim();
|
|
103
|
+
if (edgeText) {
|
|
104
|
+
// Add edge text to prevent duplicates
|
|
105
|
+
processedText.add(edgeText);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
// Only add bubbles for conversations with unique parsed content
|
|
109
|
+
// TODO: Even though there are two duplicate checks here, some duplicates still get through
|
|
110
|
+
for (const conv of currentConversations) {
|
|
111
|
+
const convText = conv.text?.trim();
|
|
112
|
+
if (
|
|
113
|
+
// Has text
|
|
114
|
+
convText &&
|
|
115
|
+
// Haven't already displayed this text (duplicate check #1)
|
|
116
|
+
!processedText.has(convText) &&
|
|
117
|
+
// Haven't already displayed this conversation ID (duplicate check #2)
|
|
118
|
+
!processedConversationIdsRef.current.has(conv.id)) {
|
|
119
|
+
// Create an AgentConversation object representing the active thought bubble.
|
|
120
|
+
// Use the conversation id from the incoming conversation and set startedAt
|
|
121
|
+
// to "now" so the bubble timeout is measured from when the bubble appeared.
|
|
122
|
+
newBubbles.push({
|
|
123
|
+
agents: new Set(conv.agents),
|
|
124
|
+
conversationId: conv.id,
|
|
125
|
+
startedAt: new Date(),
|
|
126
|
+
type: conv.type,
|
|
127
|
+
// No text needed. This is for tracking which conversations are active.
|
|
128
|
+
});
|
|
129
|
+
// Mark this conversation ID as processed. This protects against duplicates if conversations have
|
|
130
|
+
// the same ID, although some duplicates still get through.
|
|
131
|
+
processedConversationIdsRef.current.add(conv.id);
|
|
132
|
+
const agentList = Array.from(conv.agents);
|
|
133
|
+
// TODO: Check if agentList has at least 2 agents. This allows us to set a source and target.
|
|
134
|
+
// However, this can also have more than 2 agents. We may want to think more about that case.
|
|
135
|
+
if (agentList.length >= 2) {
|
|
136
|
+
// These source and target agents are going to be useful later when we point bubbles to nodes.
|
|
137
|
+
const sourceAgent = agentList[0];
|
|
138
|
+
const targetAgent = agentList[1];
|
|
139
|
+
const edge = {
|
|
140
|
+
id: `thought-bubble-${conv.id}`,
|
|
141
|
+
source: sourceAgent,
|
|
142
|
+
target: targetAgent,
|
|
143
|
+
type: "thoughtBubbleEdge",
|
|
144
|
+
data: {
|
|
145
|
+
text: conv.text,
|
|
146
|
+
showAlways: showThoughtBubbles,
|
|
147
|
+
conversationId: conv.id,
|
|
148
|
+
// Include full agent list so overlays can point to all participants
|
|
149
|
+
agents: agentList,
|
|
150
|
+
type: conv.type, // Add conversation type for filtering
|
|
151
|
+
},
|
|
152
|
+
style: { pointerEvents: "none" },
|
|
153
|
+
};
|
|
154
|
+
addThoughtBubbleEdgeHelper(conv.id, edge);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (newBubbles.length === 0) {
|
|
159
|
+
return prevBubbles; // No changes
|
|
160
|
+
}
|
|
161
|
+
const allBubbles = [...prevBubbles, ...newBubbles];
|
|
162
|
+
// If we're over the limit, first remove expired bubbles, then oldest if still needed
|
|
163
|
+
if (allBubbles.length > MAX_THOUGHT_BUBBLES) {
|
|
164
|
+
const now = Date.now();
|
|
165
|
+
const dropped = [];
|
|
166
|
+
// First, filter out expired bubbles by checking THOUGHT_BUBBLE_TIMEOUT_MS
|
|
167
|
+
const activeBubbles = allBubbles.filter((bubble) => {
|
|
168
|
+
const age = now - bubble.startedAt.getTime();
|
|
169
|
+
const isExpired = age >= THOUGHT_BUBBLE_TIMEOUT_MS;
|
|
170
|
+
if (isExpired) {
|
|
171
|
+
dropped.push(bubble);
|
|
172
|
+
}
|
|
173
|
+
return !isExpired;
|
|
174
|
+
});
|
|
175
|
+
// If still over the limit after removing expired, remove oldest non-expired
|
|
176
|
+
let finalBubbles = activeBubbles;
|
|
177
|
+
if (activeBubbles.length > MAX_THOUGHT_BUBBLES) {
|
|
178
|
+
const sorted = activeBubbles.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
|
|
179
|
+
finalBubbles = sorted.slice(-MAX_THOUGHT_BUBBLES);
|
|
180
|
+
const additionalDropped = sorted.slice(0, -MAX_THOUGHT_BUBBLES);
|
|
181
|
+
dropped.push(...additionalDropped);
|
|
182
|
+
}
|
|
183
|
+
// Clean up all dropped bubbles from edge cache
|
|
184
|
+
dropped.forEach((bubble) => {
|
|
185
|
+
removeThoughtBubbleEdgeHelper(bubble.conversationId);
|
|
186
|
+
});
|
|
187
|
+
return finalBubbles;
|
|
188
|
+
}
|
|
189
|
+
return allBubbles;
|
|
190
|
+
});
|
|
191
|
+
}, [currentConversations, addThoughtBubbleEdgeHelper, removeThoughtBubbleEdgeHelper]);
|
|
192
|
+
// Independent thought bubble cleanup - run timer only during streaming
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
const cleanupInterval = setInterval(() => {
|
|
195
|
+
// Only clean up if we're currently streaming
|
|
196
|
+
if (!isStreaming)
|
|
197
|
+
return;
|
|
198
|
+
setActiveThoughtBubbles((prevBubbles) => {
|
|
199
|
+
if (prevBubbles.length === 0)
|
|
200
|
+
return prevBubbles;
|
|
201
|
+
const now = Date.now();
|
|
202
|
+
const filteredBubbles = prevBubbles.filter((bubble) => {
|
|
203
|
+
const age = now - bubble.startedAt.getTime();
|
|
204
|
+
const shouldKeep = age < THOUGHT_BUBBLE_TIMEOUT_MS;
|
|
205
|
+
// Keep bubble if it's being hovered, even if expired
|
|
206
|
+
const isHovered = hoveredBubbleIdRef.current === `thought-bubble-${bubble.conversationId}`;
|
|
207
|
+
if (isHovered) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
// If bubble is expiring, remove it from the edge cache
|
|
211
|
+
if (!shouldKeep) {
|
|
212
|
+
removeThoughtBubbleEdgeHelper(bubble.conversationId);
|
|
213
|
+
}
|
|
214
|
+
return shouldKeep;
|
|
215
|
+
});
|
|
216
|
+
// Only update if there are changes to prevent unnecessary re-renders
|
|
217
|
+
if (filteredBubbles.length !== prevBubbles.length) {
|
|
218
|
+
return filteredBubbles;
|
|
219
|
+
}
|
|
220
|
+
return prevBubbles;
|
|
221
|
+
});
|
|
222
|
+
}, 1000); // Check every second
|
|
223
|
+
return () => clearInterval(cleanupInterval);
|
|
224
|
+
}, [isStreaming, removeThoughtBubbleEdgeHelper]);
|
|
225
|
+
const { mode, systemMode } = useColorScheme();
|
|
226
|
+
const darkMode = isDarkMode(mode, systemMode);
|
|
227
|
+
// Shadow color for icon. TODO: use MUI theme system instead.
|
|
228
|
+
const shadowColor = darkMode ? "255, 255, 255" : "0, 0, 0";
|
|
229
|
+
const isHeatmap = coloringOption === "heatmap";
|
|
230
|
+
// Merge agents from active thought bubbles with agentsInNetwork for layout
|
|
231
|
+
// This ensures bubble edges persist even when agents disappear from the network
|
|
232
|
+
const bubbleAgentIds = useMemo(() => {
|
|
233
|
+
const ids = new Set();
|
|
234
|
+
activeThoughtBubbles.forEach((bubble) => {
|
|
235
|
+
bubble.agents.forEach((agentId) => ids.add(agentId));
|
|
236
|
+
});
|
|
237
|
+
return ids;
|
|
238
|
+
}, [activeThoughtBubbles]);
|
|
239
|
+
const mergedAgentsInNetwork = useMemo(() => {
|
|
240
|
+
// Add any missing agents from bubbles as minimal ConnectivityInfo
|
|
241
|
+
const existingIds = new Set(agentsInNetwork.map((a) => a.origin));
|
|
242
|
+
const missing = Array.from(bubbleAgentIds).filter((bubbleAgentId) => !existingIds.has(bubbleAgentId));
|
|
243
|
+
const minimalAgents = missing.map((missingId) => ({
|
|
244
|
+
origin: missingId,
|
|
245
|
+
tools: [],
|
|
246
|
+
display_as: undefined,
|
|
247
|
+
}));
|
|
248
|
+
return [...agentsInNetwork, ...minimalAgents];
|
|
249
|
+
}, [agentsInNetwork, bubbleAgentIds]);
|
|
250
|
+
// Create the flow layout depending on user preference
|
|
251
|
+
// Memoize layoutResult so it only recalculates when relevant data changes
|
|
252
|
+
const layoutResult = useMemo(() => layout === "linear"
|
|
253
|
+
? layoutLinear(isHeatmap ? agentCounts : undefined, mergedAgentsInNetwork, currentConversations, isAwaitingLlm, thoughtBubbleEdges)
|
|
254
|
+
: layoutRadial(isHeatmap ? agentCounts : undefined, mergedAgentsInNetwork, currentConversations, isAwaitingLlm, thoughtBubbleEdges), [
|
|
255
|
+
layout,
|
|
256
|
+
coloringOption,
|
|
257
|
+
agentCounts,
|
|
258
|
+
mergedAgentsInNetwork,
|
|
259
|
+
currentConversations,
|
|
260
|
+
activeThoughtBubbles,
|
|
261
|
+
thoughtBubbleEdges,
|
|
262
|
+
isAwaitingLlm,
|
|
263
|
+
showThoughtBubbles,
|
|
264
|
+
]);
|
|
265
|
+
const [nodes, setNodes] = useState(layoutResult.nodes);
|
|
266
|
+
// Sync up the nodes with the layout result
|
|
267
|
+
useEffect(() => {
|
|
268
|
+
setNodes(layoutResult.nodes);
|
|
269
|
+
}, [layoutResult.nodes]);
|
|
270
|
+
const edges = layoutResult.edges;
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
// Schedule a fitView after the layout is set to ensure the view is adjusted correctly
|
|
273
|
+
setTimeout(() => {
|
|
274
|
+
fitView();
|
|
275
|
+
}, 50);
|
|
276
|
+
}, [agentsInNetwork, layout]);
|
|
277
|
+
const onNodesChange = useCallback((changes) => {
|
|
278
|
+
setNodes((ns) => applyNodeChanges(
|
|
279
|
+
// For now, we only allow dragging, no updates
|
|
280
|
+
changes.filter((c) => c.type === "position"), ns));
|
|
281
|
+
}, []);
|
|
282
|
+
const transform = useStore((state) => state.transform);
|
|
283
|
+
// Why not just a "const"? See: https://reactflow.dev/learn/customization/custom-nodes
|
|
284
|
+
// "It’s important that the nodeTypes are memoized or defined outside the component. Otherwise, React creates
|
|
285
|
+
// a new object on every render which leads to performance issues and bugs."
|
|
286
|
+
const nodeTypes = useMemo(() => ({
|
|
287
|
+
agentNode: AgentNode,
|
|
288
|
+
}), [AgentNode]);
|
|
289
|
+
const edgeTypes = useMemo(() => ({
|
|
290
|
+
plasmaEdge: PlasmaEdge,
|
|
291
|
+
thoughtBubbleEdge: ThoughtBubbleEdge,
|
|
292
|
+
}), [PlasmaEdge, ThoughtBubbleEdge]);
|
|
293
|
+
// Figure out the maximum depth of the network
|
|
294
|
+
const maxDepth = useMemo(() => {
|
|
295
|
+
return nodes?.reduce((max, node) => Math.max(node.data.depth, max), 0) + 1;
|
|
296
|
+
}, [nodes]);
|
|
297
|
+
// Generate radial guides for the network to guide the eye in the radial layout
|
|
298
|
+
const getRadialGuides = () => {
|
|
299
|
+
const circles = Array.from({ length: maxDepth }).map((_, i) => (_jsx("circle", { id: `radial-guide-${BASE_RADIUS + (i + 1) * LEVEL_SPACING}`, cx: DEFAULT_FRONTMAN_X_POS + NODE_WIDTH / 2, cy: DEFAULT_FRONTMAN_Y_POS + NODE_HEIGHT / 2, r: BASE_RADIUS + (i + 1) * LEVEL_SPACING, stroke: "var(--bs-gray-medium)", fill: "none", opacity: "0.25" }, `radial-guide-${BASE_RADIUS + (i + 1) * LEVEL_SPACING}`)));
|
|
300
|
+
return (_jsx("svg", { id: `${id}-radial-guides`, style: {
|
|
301
|
+
position: "absolute",
|
|
302
|
+
left: 0,
|
|
303
|
+
top: 0,
|
|
304
|
+
width: "100%",
|
|
305
|
+
height: "100%",
|
|
306
|
+
}, children: _jsx("g", { id: `${id}-radial-guides-group`, transform: `translate(${transform[0]}, ${transform[1]}) scale(${transform[2]})`, children: circles }) }));
|
|
307
|
+
};
|
|
308
|
+
// Generate Legend for depth or heatmap colors
|
|
309
|
+
function getLegend() {
|
|
310
|
+
const palette = isHeatmap ? HEATMAP_COLORS : BACKGROUND_COLORS;
|
|
311
|
+
const title = isHeatmap ? "Heat" : "Depth";
|
|
312
|
+
const length = isHeatmap ? HEATMAP_COLORS.length : Math.min(maxDepth, BACKGROUND_COLORS.length);
|
|
313
|
+
return (_jsxs(Box, { id: `${id}-legend`, sx: {
|
|
314
|
+
position: "absolute",
|
|
315
|
+
top: "5px",
|
|
316
|
+
right: "10px",
|
|
317
|
+
padding: "5px",
|
|
318
|
+
borderRadius: "5px",
|
|
319
|
+
boxShadow: `0 0 5px rgba(${shadowColor}, 0.3)`,
|
|
320
|
+
display: "flex",
|
|
321
|
+
alignItems: "center",
|
|
322
|
+
zIndex: getZIndex(2, theme),
|
|
323
|
+
}, children: [_jsx(Typography, { id: `${id}-legend-label`, sx: {
|
|
324
|
+
fontSize: "10px",
|
|
325
|
+
marginLeft: "0.2rem",
|
|
326
|
+
}, children: title }), Array.from({ length }, (_, i) => (_jsx(Box, { id: `${id}-legend-depth-${i}`, style: {
|
|
327
|
+
alignItems: "center",
|
|
328
|
+
backgroundColor: palette[i],
|
|
329
|
+
borderRadius: "50%",
|
|
330
|
+
color: i < BACKGROUND_COLORS_DARK_IDX ? "var(--bs-primary)" : "var(--bs-white)",
|
|
331
|
+
display: "flex",
|
|
332
|
+
height: "15px",
|
|
333
|
+
justifyContent: "center",
|
|
334
|
+
marginLeft: "5px",
|
|
335
|
+
width: "15px",
|
|
336
|
+
}, children: _jsx(Typography, { id: `${id}-legend-depth-${i}-text`, sx: {
|
|
337
|
+
fontSize: "8px",
|
|
338
|
+
}, children: i }) }, i))), _jsxs(ToggleButtonGroup, { id: `${id}-coloring-toggle`, value: coloringOption, exclusive: true, onChange: (_, newValue) => {
|
|
339
|
+
if (newValue !== null) {
|
|
340
|
+
setColoringOption(newValue);
|
|
341
|
+
}
|
|
342
|
+
}, sx: {
|
|
343
|
+
fontSize: "2rem",
|
|
344
|
+
zIndex: 10,
|
|
345
|
+
marginLeft: "0.5rem",
|
|
346
|
+
}, size: "small", children: [_jsx(ToggleButton, { id: `${id}-depth-toggle`, size: "small", value: "depth", sx: {
|
|
347
|
+
fontSize: "0.5rem",
|
|
348
|
+
height: "1rem",
|
|
349
|
+
backgroundColor: darkMode && coloringOption === "depth" ? "var(--bs-gray-medium)" : undefined,
|
|
350
|
+
}, children: _jsx(Typography, { id: `${id}-depth-label`, sx: {
|
|
351
|
+
fontSize: "10px",
|
|
352
|
+
}, children: "Depth" }) }), _jsx(ToggleButton, { id: `${id}-heatmap-toggle`, size: "small", value: "heatmap", sx: {
|
|
353
|
+
fontSize: "0.5rem",
|
|
354
|
+
height: "1rem",
|
|
355
|
+
backgroundColor: darkMode && isHeatmap ? "var(--bs-gray-medium)" : undefined,
|
|
356
|
+
}, children: _jsx(Typography, { id: `${id}-heatmap-label`, sx: {
|
|
357
|
+
fontSize: "10px",
|
|
358
|
+
}, children: "Heatmap" }) })] })] }));
|
|
359
|
+
}
|
|
360
|
+
// Get the background color for the control buttons based on the layout and dark mode setting
|
|
361
|
+
const getControlButtonBackgroundColor = (isActive) => {
|
|
362
|
+
return isActive ? (darkMode ? "var(--bs-gray-dark)" : "var(--bs-gray-lighter)") : undefined;
|
|
363
|
+
};
|
|
364
|
+
// Only show radial guides if radial layout is selected, radial guides are enabled, and it's not just Frontman
|
|
365
|
+
const shouldShowRadialGuides = enableRadialGuides && layout === "radial" && maxDepth > 1;
|
|
366
|
+
// Generate the control bar for the flow, including layout and radial guides toggles
|
|
367
|
+
const getControls = () => {
|
|
368
|
+
return (_jsxs(Controls, { id: "react-flow-controls", position: "top-left", style: {
|
|
369
|
+
position: "absolute",
|
|
370
|
+
top: "0px",
|
|
371
|
+
left: "0px",
|
|
372
|
+
height: "auto",
|
|
373
|
+
width: "auto",
|
|
374
|
+
}, showInteractive: true, children: [_jsx(Tooltip, { id: "radial-layout-tooltip", title: "Radial layout", placement: "right", children: _jsx("span", { id: "radial-layout-span", children: _jsx(ControlButton, { id: "radial-layout-button", onClick: () => setLayout("radial"), style: {
|
|
375
|
+
backgroundColor: getControlButtonBackgroundColor(layout === "radial"),
|
|
376
|
+
}, children: _jsx(HubOutlinedIcon, { id: "radial-layout-icon", sx: { color: darkMode ? "var(--bs-white)" : "var(--bs-dark-mode-dim)" } }) }) }) }), _jsx(Tooltip, { id: "linear-layout-tooltip", title: "Linear layout", placement: "right", children: _jsx("span", { id: "linear-layout-span", children: _jsx(ControlButton, { id: "linear-layout-button", onClick: () => setLayout("linear"), style: {
|
|
377
|
+
backgroundColor: getControlButtonBackgroundColor(layout === "linear"),
|
|
378
|
+
}, children: _jsx(ScatterPlotOutlinedIcon, { id: "linear-layout-icon", sx: { color: darkMode ? "var(--bs-white)" : "var(--bs-dark-mode-dim)" } }) }) }) }), _jsx(Tooltip, { id: "radial-guides-tooltip", title: `Enable/disable radial guides${layout === "radial" ? "" : " (only available in radial layout)"}`, placement: "right", children: _jsx("span", { id: "radial-guides-span", children: _jsx(ControlButton, { id: "radial-guides-button", onClick: () => setEnableRadialGuides(!enableRadialGuides), style: {
|
|
379
|
+
backgroundColor: getControlButtonBackgroundColor(enableRadialGuides),
|
|
380
|
+
}, disabled: layout !== "radial", children: _jsx(AdjustRoundedIcon, { id: "radial-guides-icon", sx: { color: darkMode ? "var(--bs-white)" : "var(--bs-dark-mode-dim)" } }) }) }) }), _jsx(Tooltip, { id: "thought-bubble-tooltip", title: `Toggle thought bubbles ${showThoughtBubbles ? "off" : "on"}`, placement: "right", children: _jsx("span", { id: "thought-bubble-span", children: _jsx(ControlButton, { id: "thought-bubble-button", onClick: () => setShowThoughtBubbles(!showThoughtBubbles), style: {
|
|
381
|
+
backgroundColor: getControlButtonBackgroundColor(showThoughtBubbles),
|
|
382
|
+
}, children: _jsx(ChatBubbleOutlineIcon, { id: "thought-bubble-icon", sx: { color: darkMode ? "var(--bs-white)" : "var(--bs-dark-mode-dim)" } }) }) }) })] }));
|
|
383
|
+
};
|
|
384
|
+
return (_jsxs(Box, { id: `${id}-outer-box`, sx: {
|
|
385
|
+
height: "100%",
|
|
386
|
+
width: "100%",
|
|
387
|
+
"& .react-flow__node": {
|
|
388
|
+
border: "var(--bs-border-width) var(--bs-border-style) var(--bs-black)",
|
|
389
|
+
borderRadius: "var(--bs-border-radius-2xl)",
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
// Theme the "React Flow" attribution logo according to dark mode.
|
|
393
|
+
className: darkMode ? "dark" : undefined, children: [_jsx(ReactFlow, { id: `${id}-react-flow`, nodes: nodes, edges: edges, onNodesChange: onNodesChange, fitView: true, nodeTypes: nodeTypes, edgeTypes: edgeTypes, connectionMode: ConnectionMode.Loose, children: !isAwaitingLlm && (_jsxs(_Fragment, { children: [agentsInNetwork?.length ? getLegend() : null, _jsx(Background, { id: `${id}-background` }), getControls(), shouldShowRadialGuides ? getRadialGuides() : null] })) }), _jsx(ThoughtBubbleOverlay, { nodes: nodes, edges: edges, showThoughtBubbles: showThoughtBubbles, isStreaming: isStreaming, onBubbleHoverChange: handleBubbleHoverChange })] }));
|
|
394
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import { NodeProps } from "reactflow";
|
|
3
|
+
import { AgentConversation } from "../../utils/agentConversations.js";
|
|
4
|
+
export interface AgentNodeProps {
|
|
5
|
+
readonly agentCounts?: Map<string, number>;
|
|
6
|
+
readonly agentName: string;
|
|
7
|
+
readonly depth: number;
|
|
8
|
+
readonly getConversations: () => AgentConversation[] | null;
|
|
9
|
+
readonly isAwaitingLlm?: boolean;
|
|
10
|
+
readonly displayAs?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const NODE_HEIGHT = 100;
|
|
13
|
+
export declare const NODE_WIDTH = 100;
|
|
14
|
+
/**
|
|
15
|
+
* A node representing an agent in the network for use in react-flow.
|
|
16
|
+
* @param props See AgentNodeProps
|
|
17
|
+
*/
|
|
18
|
+
export declare const AgentNode: FC<NodeProps<AgentNodeProps>>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import AutoAwesomeIcon from "@mui/icons-material/AutoAwesome";
|
|
18
|
+
import HandymanIcon from "@mui/icons-material/Handyman";
|
|
19
|
+
import PersonIcon from "@mui/icons-material/Person";
|
|
20
|
+
import TravelExploreIcon from "@mui/icons-material/TravelExplore";
|
|
21
|
+
import { Tooltip, useTheme } from "@mui/material";
|
|
22
|
+
import Typography from "@mui/material/Typography";
|
|
23
|
+
import { Handle, Position } from "reactflow";
|
|
24
|
+
import { BACKGROUND_COLORS, BACKGROUND_COLORS_DARK_IDX, HEATMAP_COLORS } from "./const.js";
|
|
25
|
+
import { getZIndex } from "../../utils/zIndexLayers.js";
|
|
26
|
+
// Node dimensions
|
|
27
|
+
export const NODE_HEIGHT = 100;
|
|
28
|
+
export const NODE_WIDTH = 100;
|
|
29
|
+
// Icon sizes
|
|
30
|
+
// These are used to set the size of the icons displayed in the agent nodes.
|
|
31
|
+
const AGENT_ICON_SIZE = "2.25rem";
|
|
32
|
+
const FRONTMAN_ICON_SIZE = "4.5rem";
|
|
33
|
+
/**
|
|
34
|
+
* A node representing an agent in the network for use in react-flow.
|
|
35
|
+
* @param props See AgentNodeProps
|
|
36
|
+
*/
|
|
37
|
+
export const AgentNode = (props) => {
|
|
38
|
+
const theme = useTheme();
|
|
39
|
+
// Unpack the node-specific data
|
|
40
|
+
const data = props.data;
|
|
41
|
+
const { agentCounts, agentName, depth, displayAs, getConversations, isAwaitingLlm } = data;
|
|
42
|
+
const isFrontman = depth === 0;
|
|
43
|
+
const maxAgentCount = agentCounts ? Math.max(...Array.from(agentCounts.values())) : 0;
|
|
44
|
+
// Unpack the node-specific id
|
|
45
|
+
const agentId = props.id;
|
|
46
|
+
// "Active" agents are those at either end of the current communication from the incoming chat messages.
|
|
47
|
+
// We highlight them with a green background.
|
|
48
|
+
const conversations = getConversations();
|
|
49
|
+
const isActiveAgent = conversations?.some((conversation) => conversation.agents.has(agentId)) ?? false;
|
|
50
|
+
let backgroundColor;
|
|
51
|
+
let color;
|
|
52
|
+
const isHeatmap = agentCounts?.size > 0 && maxAgentCount > 0;
|
|
53
|
+
if (isActiveAgent) {
|
|
54
|
+
backgroundColor = "var(--bs-green)";
|
|
55
|
+
color = "var(--bs-white)";
|
|
56
|
+
}
|
|
57
|
+
else if (isHeatmap) {
|
|
58
|
+
const agentCount = agentCounts.has(agentId) ? agentCounts.get(agentId) : 0;
|
|
59
|
+
// Calculate "heat" as a fraction of the times this agent was invoked compared to the maximum agent count.
|
|
60
|
+
const colorIndex = Math.floor((agentCount / maxAgentCount) * (HEATMAP_COLORS.length - 1));
|
|
61
|
+
backgroundColor = HEATMAP_COLORS[colorIndex];
|
|
62
|
+
const isDarkBackground = colorIndex >= BACKGROUND_COLORS_DARK_IDX;
|
|
63
|
+
color = isDarkBackground ? "var(--bs-white)" : "var(--bs-dark)";
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
const colorIndex = depth % BACKGROUND_COLORS.length;
|
|
67
|
+
backgroundColor = BACKGROUND_COLORS[colorIndex];
|
|
68
|
+
const isDarkBackground = colorIndex >= BACKGROUND_COLORS_DARK_IDX;
|
|
69
|
+
color = isDarkBackground ? "var(--bs-white)" : "var(--bs-dark)";
|
|
70
|
+
}
|
|
71
|
+
// Animation style for making active agent glow and pulse
|
|
72
|
+
// TODO: more idiomatic MUI/style= way of doing this?
|
|
73
|
+
const glowAnimation = isActiveAgent
|
|
74
|
+
? `@keyframes glow {
|
|
75
|
+
0% { box-shadow: 0 0 10px 4px ${backgroundColor}; opacity: 0.60; }
|
|
76
|
+
50% { box-shadow: 0 0 30px 12px ${backgroundColor}; opacity: 0.90; }
|
|
77
|
+
100% { box-shadow: 0 0 10px 4px ${backgroundColor}; opacity: 1.0; }
|
|
78
|
+
}`
|
|
79
|
+
: "none";
|
|
80
|
+
const boxShadow = isActiveAgent ? "0 0 30px 12px var(--bs-primary), 0 0 60px 24px var(--bs-primary)" : undefined;
|
|
81
|
+
// Hide handles when awaiting LLM response ("zen mode")
|
|
82
|
+
const handleVisibility = isAwaitingLlm ? "none" : "block";
|
|
83
|
+
// Determine which icon to display based on the agent type whether it is Frontman or not
|
|
84
|
+
const getDisplayAsIcon = () => {
|
|
85
|
+
const id = `${agentId}-icon`;
|
|
86
|
+
if (isFrontman) {
|
|
87
|
+
return (
|
|
88
|
+
// Use special icon and larger size for Frontman
|
|
89
|
+
_jsx(PersonIcon, { id: id, sx: { fontSize: FRONTMAN_ICON_SIZE } }));
|
|
90
|
+
}
|
|
91
|
+
switch (displayAs) {
|
|
92
|
+
case "external_agent":
|
|
93
|
+
return (_jsx(TravelExploreIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
|
|
94
|
+
case "coded_tool":
|
|
95
|
+
return (_jsx(HandymanIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
|
|
96
|
+
case "llm_agent":
|
|
97
|
+
default:
|
|
98
|
+
return (_jsx(AutoAwesomeIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { id: agentId, style: {
|
|
102
|
+
alignItems: "center",
|
|
103
|
+
animation: isActiveAgent ? "glow 2.0s infinite" : "none",
|
|
104
|
+
backgroundColor,
|
|
105
|
+
borderRadius: "50%",
|
|
106
|
+
boxShadow,
|
|
107
|
+
color,
|
|
108
|
+
display: "flex",
|
|
109
|
+
height: NODE_HEIGHT * (isFrontman ? 1.25 : 1.0),
|
|
110
|
+
justifyContent: "center",
|
|
111
|
+
shapeOutside: "circle(50%)",
|
|
112
|
+
textAlign: "center",
|
|
113
|
+
width: NODE_WIDTH * (isFrontman ? 1.25 : 1.0),
|
|
114
|
+
zIndex: getZIndex(1, theme),
|
|
115
|
+
position: "relative",
|
|
116
|
+
}, children: [_jsx("style", { id: `${agentId}-glow-animation`, children: glowAnimation }), getDisplayAsIcon(), _jsx(Handle, { id: `${agentId}-left-handle`, position: Position.Left, type: "source", style: { display: handleVisibility } }), _jsx(Handle, { id: `${agentId}-right-handle`, position: Position.Right, type: "source", style: { display: handleVisibility } }), _jsx(Handle, { id: `${agentId}-top-handle`, position: Position.Top, type: "source", style: { display: handleVisibility } }), _jsx(Handle, { id: `${agentId}-bottom-handle`, position: Position.Bottom, type: "source", style: { display: handleVisibility } })] }), _jsx(Tooltip, { id: `${agentId}-tooltip`, title: agentName, placement: "top", disableInteractive: true, children: _jsx(Typography, { id: `${agentId}-name`, sx: {
|
|
117
|
+
display: "-webkit-box",
|
|
118
|
+
fontSize: "18px",
|
|
119
|
+
fontWeight: "bold",
|
|
120
|
+
lineHeight: "1.4em",
|
|
121
|
+
overflow: "hidden",
|
|
122
|
+
textAlign: "center",
|
|
123
|
+
textOverflow: "ellipsis",
|
|
124
|
+
WebkitBoxOrient: "vertical",
|
|
125
|
+
WebkitLineClamp: 2,
|
|
126
|
+
width: `${NODE_WIDTH}px`,
|
|
127
|
+
zIndex: 10,
|
|
128
|
+
}, children: agentName }) })] }));
|
|
129
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Edge, EdgeProps, Node as RFNode } from "reactflow";
|
|
2
|
+
import { AgentNodeProps } from "./AgentNode.js";
|
|
3
|
+
import { ConnectivityInfo } from "../../generated/neuro-san/NeuroSanClient.js";
|
|
4
|
+
import { AgentConversation } from "../../utils/agentConversations.js";
|
|
5
|
+
export declare const MAX_GLOBAL_THOUGHT_BUBBLES = 5;
|
|
6
|
+
export declare const addThoughtBubbleEdge: (thoughtBubbleEdges: Map<string, {
|
|
7
|
+
edge: Edge<EdgeProps>;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
}>, conversationId: string, edge: Edge<EdgeProps>) => void;
|
|
10
|
+
export declare const removeThoughtBubbleEdge: (thoughtBubbleEdges: Map<string, {
|
|
11
|
+
edge: Edge<EdgeProps>;
|
|
12
|
+
timestamp: number;
|
|
13
|
+
}>, conversationId: string) => void;
|
|
14
|
+
export declare const getThoughtBubbleEdges: (thoughtBubbleEdges: Map<string, {
|
|
15
|
+
edge: Edge<EdgeProps>;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
}>) => Edge<EdgeProps>[];
|
|
18
|
+
export declare const layoutRadial: (agentCounts: Map<string, number>, agentsInNetwork: ConnectivityInfo[], currentConversations: AgentConversation[] | null, // For plasma edges (live) and node highlighting
|
|
19
|
+
isAwaitingLlm: boolean, thoughtBubbleEdges: Map<string, {
|
|
20
|
+
edge: Edge<EdgeProps>;
|
|
21
|
+
timestamp: number;
|
|
22
|
+
}>) => {
|
|
23
|
+
nodes: RFNode<AgentNodeProps>[];
|
|
24
|
+
edges: Edge<EdgeProps>[];
|
|
25
|
+
};
|
|
26
|
+
export declare const layoutLinear: (agentCounts: Map<string, number>, agentsInNetwork: ConnectivityInfo[], currentConversations: AgentConversation[] | null, // For plasma edges (live) and node highlighting
|
|
27
|
+
isAwaitingLlm: boolean, thoughtBubbleEdges: Map<string, {
|
|
28
|
+
edge: Edge<EdgeProps>;
|
|
29
|
+
timestamp: number;
|
|
30
|
+
}>) => {
|
|
31
|
+
nodes: RFNode<AgentNodeProps>[];
|
|
32
|
+
edges: Edge<EdgeProps>[];
|
|
33
|
+
};
|