@cognizant-ai-lab/ui-common 1.4.1 → 1.5.0
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/AgentConnectivity.d.ts +14 -0
- package/dist/components/AgentChat/ChatCommon/AgentConnectivity.js +23 -0
- package/dist/components/AgentChat/{ChatCommon.d.ts → ChatCommon/ChatCommon.d.ts} +8 -4
- package/dist/components/AgentChat/{ChatCommon.js → ChatCommon/ChatCommon.js} +318 -307
- package/dist/components/AgentChat/ChatCommon/ChatHistory.d.ts +17 -0
- package/dist/components/AgentChat/ChatCommon/ChatHistory.js +27 -0
- package/dist/components/AgentChat/{ControlButtons.d.ts → ChatCommon/ControlButtons.d.ts} +1 -1
- package/dist/components/AgentChat/ChatCommon/ControlButtons.js +26 -0
- package/dist/components/AgentChat/{FormattedMarkdown.js → ChatCommon/FormattedMarkdown.js} +1 -1
- package/dist/components/AgentChat/ChatCommon/SampleQueries.d.ts +16 -0
- package/dist/components/AgentChat/ChatCommon/SampleQueries.js +29 -0
- package/dist/components/AgentChat/{SendButton.js → ChatCommon/SendButton.js} +1 -1
- package/dist/components/AgentChat/ChatCommon/UserQueryDisplay.d.ts +7 -0
- package/dist/components/AgentChat/{UserQueryDisplay.js → ChatCommon/UserQueryDisplay.js} +4 -3
- package/dist/components/AgentChat/{LlmChatButton.d.ts → Common/LlmChatButton.d.ts} +2 -2
- package/dist/components/AgentChat/{Utils.d.ts → Common/Utils.d.ts} +1 -1
- package/dist/components/AgentChat/{Utils.js → Common/Utils.js} +2 -1
- package/dist/components/AgentChat/VoiceChat/MicrophoneButton.js +1 -1
- package/dist/components/ChatBot/ChatBot.js +2 -2
- package/dist/components/Common/CustomerLogo.js +1 -1
- package/dist/components/Common/LlmChatOptionsButton.d.ts +1 -1
- package/dist/components/Common/MUIDialog.d.ts +1 -0
- package/dist/components/Common/MUIDialog.js +2 -2
- package/dist/components/MultiAgentAccelerator/AgentCounts.d.ts +2 -2
- package/dist/components/MultiAgentAccelerator/AgentFlow.d.ts +13 -1
- package/dist/components/MultiAgentAccelerator/AgentFlow.js +193 -20
- package/dist/components/MultiAgentAccelerator/AgentNetworkDesigner.d.ts +10 -0
- package/dist/components/MultiAgentAccelerator/AgentNetworkDesigner.js +20 -0
- package/dist/components/MultiAgentAccelerator/AgentNode.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/AgentNode.js +9 -4
- package/dist/components/MultiAgentAccelerator/AgentNodePopup.d.ts +33 -0
- package/dist/components/MultiAgentAccelerator/AgentNodePopup.js +81 -0
- package/dist/components/MultiAgentAccelerator/GraphLayouts.d.ts +4 -4
- package/dist/components/MultiAgentAccelerator/GraphLayouts.js +12 -8
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +103 -65
- package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.js +24 -5
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.js +34 -23
- package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +1 -1
- package/dist/components/MultiAgentAccelerator/TemporaryNetworks.d.ts +21 -0
- package/dist/components/MultiAgentAccelerator/TemporaryNetworks.js +42 -2
- package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.js +8 -7
- package/dist/components/MultiAgentAccelerator/const.d.ts +26 -0
- package/dist/components/MultiAgentAccelerator/const.js +23 -0
- package/dist/controller/llm/LlmChat.js +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.js +8 -7
- package/dist/state/ChatHistory.d.ts +50 -0
- package/dist/state/ChatHistory.js +98 -0
- package/dist/state/IndexedDBStorage.d.ts +14 -0
- package/dist/state/IndexedDBStorage.js +65 -0
- package/dist/state/TemporaryNetworks.d.ts +24 -0
- package/dist/state/TemporaryNetworks.js +43 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils/File.d.ts +29 -0
- package/dist/utils/File.js +61 -0
- package/package.json +16 -11
- package/dist/components/AgentChat/ControlButtons.js +0 -26
- package/dist/components/AgentChat/UserQueryDisplay.d.ts +0 -5
- /package/dist/components/AgentChat/{FormattedMarkdown.d.ts → ChatCommon/FormattedMarkdown.d.ts} +0 -0
- /package/dist/components/AgentChat/{Greetings.d.ts → ChatCommon/Greetings.d.ts} +0 -0
- /package/dist/components/AgentChat/{Greetings.js → ChatCommon/Greetings.js} +0 -0
- /package/dist/components/AgentChat/{SendButton.d.ts → ChatCommon/SendButton.d.ts} +0 -0
- /package/dist/components/AgentChat/{SyntaxHighlighterThemes.d.ts → ChatCommon/SyntaxHighlighterThemes.d.ts} +0 -0
- /package/dist/components/AgentChat/{SyntaxHighlighterThemes.js → ChatCommon/SyntaxHighlighterThemes.js} +0 -0
- /package/dist/components/AgentChat/{LlmChatButton.js → Common/LlmChatButton.js} +0 -0
- /package/dist/components/AgentChat/{Types.d.ts → Common/Types.d.ts} +0 -0
- /package/dist/components/AgentChat/{Types.js → Common/Types.js} +0 -0
|
@@ -24,7 +24,6 @@ import CloseIcon from "@mui/icons-material/Close";
|
|
|
24
24
|
import VerticalAlignBottomIcon from "@mui/icons-material/VerticalAlignBottom";
|
|
25
25
|
import WrapTextIcon from "@mui/icons-material/WrapText";
|
|
26
26
|
import Box from "@mui/material/Box";
|
|
27
|
-
import Chip from "@mui/material/Chip";
|
|
28
27
|
import CircularProgress from "@mui/material/CircularProgress";
|
|
29
28
|
import IconButton from "@mui/material/IconButton";
|
|
30
29
|
import Input from "@mui/material/Input";
|
|
@@ -33,27 +32,34 @@ import { alpha, useTheme } from "@mui/material/styles";
|
|
|
33
32
|
import Tooltip from "@mui/material/Tooltip";
|
|
34
33
|
import Typography from "@mui/material/Typography";
|
|
35
34
|
import { jsonrepair } from "jsonrepair";
|
|
36
|
-
import { isValidElement, useEffect, useImperativeHandle, useRef, useState, } from "react";
|
|
35
|
+
import { isValidElement, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
|
|
37
36
|
import ReactMarkdown from "react-markdown";
|
|
38
37
|
import SyntaxHighlighter from "react-syntax-highlighter";
|
|
38
|
+
import { v4 as uuid } from "uuid";
|
|
39
|
+
import { AgentConnectivity } from "./AgentConnectivity.js";
|
|
40
|
+
import { ChatHistory } from "./ChatHistory.js";
|
|
39
41
|
import { ControlButtons } from "./ControlButtons.js";
|
|
40
42
|
import { FormattedMarkdown } from "./FormattedMarkdown.js";
|
|
41
43
|
import { AGENT_GREETINGS } from "./Greetings.js";
|
|
44
|
+
import { SampleQueries } from "./SampleQueries.js";
|
|
42
45
|
import { SendButton } from "./SendButton.js";
|
|
43
46
|
import { HLJS_THEMES } from "./SyntaxHighlighterThemes.js";
|
|
44
|
-
import { isLegacyAgentType } from "./Types.js";
|
|
45
47
|
import { UserQueryDisplay } from "./UserQueryDisplay.js";
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
48
|
+
import { getAgentFunction, getConnectivity, sendChatQuery } from "../../../controller/agent/Agent.js";
|
|
49
|
+
import { sendLlmRequest, StreamingUnit } from "../../../controller/llm/LlmChat.js";
|
|
50
|
+
import { ChatMessageType, } from "../../../generated/neuro-san/NeuroSanClient.js";
|
|
51
|
+
import { useAgentChatHistoryStore } from "../../../state/ChatHistory.js";
|
|
52
|
+
import { hashString, hasOnlyWhitespace } from "../../../utils/text.js";
|
|
53
|
+
import { LlmChatOptionsButton } from "../../Common/LlmChatOptionsButton.js";
|
|
54
|
+
import { MUIAccordion } from "../../Common/MUIAccordion.js";
|
|
55
|
+
import { MUIAlert } from "../../Common/MUIAlert.js";
|
|
56
|
+
import { NotificationType, sendNotification } from "../../Common/notification.js";
|
|
57
|
+
import { isLegacyAgentType } from "../Common/Types.js";
|
|
58
|
+
import { chatMessageFromChunk, checkError, cleanUpAgentName, removeTrailingUuid } from "../Common/Utils.js";
|
|
59
|
+
import { MicrophoneButton } from "../VoiceChat/MicrophoneButton.js";
|
|
60
|
+
import { cleanupAndStopSpeechRecognition, setupSpeechRecognition } from "../VoiceChat/VoiceChat.js";
|
|
61
|
+
// Key for the chat history, which gets special treatment; always visible even if "show thinking" is off.
|
|
62
|
+
const CHAT_HISTORY_KEY = "chat-history-accordion";
|
|
57
63
|
// Define fancy EMPTY constant to avoid linter error about using object literals as default props
|
|
58
64
|
const EMPTY = {};
|
|
59
65
|
// Avatar to use for agents in chat
|
|
@@ -61,29 +67,28 @@ const AGENT_IMAGE = "/agent.svg";
|
|
|
61
67
|
// How many times to retry the entire agent interaction process. Some networks have a well-defined success condition.
|
|
62
68
|
// For others, it's just "whenever the stream is done".
|
|
63
69
|
const MAX_AGENT_RETRIES = 3;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Extract the final answer from the response from a legacy agent
|
|
72
|
+
* @param response The response from the legacy agent
|
|
73
|
+
* @returns The final answer from the agent, if it exists or undefined if it doesn't
|
|
74
|
+
*/
|
|
75
|
+
const extractFinalAnswer = (response) => /Final Answer: (?<finalAnswerText>.*)/su.exec(response)?.groups?.["finalAnswerText"];
|
|
76
|
+
// Maximum number of items to keep in the chat output window
|
|
77
|
+
const MAX_CHAT_OUTPUT_ITEMS = 50;
|
|
68
78
|
/**
|
|
69
79
|
* Common chat component for agent chat. This component is used by all agent chat components to provide a consistent
|
|
70
80
|
* experience for users when chatting with agents. It handles user input as well as displaying and nicely formatting
|
|
71
81
|
* agent responses. Customization for inputs and outputs is provided via event handlers-like props.
|
|
72
82
|
*/
|
|
73
83
|
export const ChatCommon = ({ ref, ...props }) => {
|
|
74
|
-
const
|
|
75
|
-
const { id, currentUser, userImage, setIsAwaitingLlm, isAwaitingLlm, onChunkReceived, onStreamingStarted, onStreamingComplete, onSend, setPreviousResponse, targetAgent, legacyAgentEndpoint, agentPlaceholders = EMPTY, clearChatOnNewAgent = false, extraParams, backgroundColor, title, onClose, neuroSanURL, } = props;
|
|
76
|
-
// Expose the handleStop method to parent components via ref for external control (e.g., to cancel chat requests)
|
|
77
|
-
useImperativeHandle(ref, () => ({
|
|
78
|
-
handleStop,
|
|
79
|
-
}));
|
|
84
|
+
const { agentGreetings = EMPTY, agentPlaceholders = EMPTY, backgroundColor, currentUser, extraParams, extraSlyData, id, isAwaitingLlm, legacyAgentEndpoint, neuroSanURL, onChunkReceived, onClose, onSend, onStreamingComplete, onStreamingStarted, setIsAwaitingLlm, setPreviousResponse, targetAgent, title, userImage, } = props;
|
|
80
85
|
// MUI theme
|
|
81
86
|
const theme = useTheme();
|
|
82
87
|
const shadowColor = theme.palette.mode === "dark" ? theme.palette.common.white : theme.palette.common.black;
|
|
83
88
|
// User LLM chat input
|
|
84
89
|
const [chatInput, setChatInput] = useState("");
|
|
85
90
|
// Previous user query (for "regenerate" feature)
|
|
86
|
-
const
|
|
91
|
+
const previousUserQuery = useRef("");
|
|
87
92
|
// Chat output window contents
|
|
88
93
|
const [chatOutput, setChatOutput] = useState([]);
|
|
89
94
|
// To accumulate current response, which will be different from the contents of the output window if there is a
|
|
@@ -95,7 +100,7 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
95
100
|
const chatInputRef = useRef(null);
|
|
96
101
|
// Controller for cancelling fetch request
|
|
97
102
|
const controller = useRef(null);
|
|
98
|
-
// For tracking if we're
|
|
103
|
+
// For tracking if we're auto-scrolling. A button allows the user to enable or disable auto-scrolling.
|
|
99
104
|
const [autoScrollEnabled, setAutoScrollEnabled] = useState(true);
|
|
100
105
|
// ref for same
|
|
101
106
|
const autoScrollEnabledRef = useRef(autoScrollEnabled);
|
|
@@ -105,18 +110,19 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
105
110
|
const lastAIMessage = useRef("");
|
|
106
111
|
// Ref for the final answer key, so we can highlight the accordion
|
|
107
112
|
const finalAnswerKey = useRef("");
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
// Persistent agent chat history store, which is where we store both kinds of chat histories
|
|
114
|
+
// (see store implementation for details)
|
|
115
|
+
const storedChatHistory = useAgentChatHistoryStore((state) => state?.history?.[targetAgent]);
|
|
116
|
+
const agentChatHistory = useMemo(() => storedChatHistory ?? { chatHistory: [], chatContext: null, slyData: {} }, [storedChatHistory]);
|
|
117
|
+
const [agentSampleQueries, setAgentSampleQueries] = useState([]);
|
|
118
|
+
// Access store for context items
|
|
119
|
+
const updateChatContext = useAgentChatHistoryStore((state) => state.updateChatContext);
|
|
120
|
+
const updateChatHistory = useAgentChatHistoryStore((state) => state.updateChatHistory);
|
|
121
|
+
const updateSlyData = useAgentChatHistoryStore((state) => state.updateSlyData);
|
|
122
|
+
const resetHistory = useAgentChatHistoryStore((state) => state.resetHistory);
|
|
123
|
+
// Ref to the item we think is the Final Answer from the agent
|
|
119
124
|
const finalAnswerRef = useRef(null);
|
|
125
|
+
// Track state of "show thinking" toggle
|
|
120
126
|
const [showThinking, setShowThinking] = useState(false);
|
|
121
127
|
// Microphone state for voice input
|
|
122
128
|
const [isMicOn, setIsMicOn] = useState(false);
|
|
@@ -147,6 +153,7 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
147
153
|
const succeeded = useRef(false);
|
|
148
154
|
const darkMode = theme.palette.mode === "dark";
|
|
149
155
|
const { atelierDuneDark, a11yLight } = HLJS_THEMES;
|
|
156
|
+
const agentDisplayName = useMemo(() => cleanUpAgentName(removeTrailingUuid(targetAgent)), [targetAgent]);
|
|
150
157
|
useEffect(() => {
|
|
151
158
|
// Set up speech recognition
|
|
152
159
|
const handlers = setupSpeechRecognition(setChatInput, setVoiceInputState, speechRecognitionRef);
|
|
@@ -165,21 +172,13 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
165
172
|
useEffect(() => {
|
|
166
173
|
// Scroll the final answer into view
|
|
167
174
|
if (finalAnswerRef.current && !isAwaitingLlm) {
|
|
168
|
-
|
|
169
|
-
chatOutputRef.current.scrollTop = finalAnswerRef.current.offsetTop - offset;
|
|
175
|
+
chatOutputRef.current.scrollTop = finalAnswerRef.current.offsetTop - 50;
|
|
170
176
|
return;
|
|
171
177
|
}
|
|
172
178
|
if (autoScrollEnabledRef.current && chatOutputRef?.current) {
|
|
173
179
|
chatOutputRef.current.scrollTop = chatOutputRef.current.scrollHeight;
|
|
174
180
|
}
|
|
175
|
-
}, [chatOutput]);
|
|
176
|
-
useEffect(() => {
|
|
177
|
-
// Clear chat output on change of neuro-san URL
|
|
178
|
-
// TODO: We want to revise this in the future to not need a useEffect
|
|
179
|
-
setChatOutput([]);
|
|
180
|
-
currentResponse.current = "";
|
|
181
|
-
setShowThinking(false);
|
|
182
|
-
}, [neuroSanURL]);
|
|
181
|
+
}, [chatOutput, isAwaitingLlm]);
|
|
183
182
|
/**
|
|
184
183
|
* Process a log line from the agent and format it nicely using the syntax highlighter and Accordion components.
|
|
185
184
|
* By the time we get to here, it's assumed things like errors and termination conditions have already been handled.
|
|
@@ -192,7 +191,7 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
192
191
|
* @param summary Used as the "title" for the accordion block. Something like an agent name or "Final Answer"
|
|
193
192
|
* @returns A React component representing the log line (agent message)
|
|
194
193
|
*/
|
|
195
|
-
const processLogLine = (logLine, summary, messageType, isFinalAnswer) => {
|
|
194
|
+
const processLogLine = useCallback((logLine, summary, messageType, isFinalAnswer) => {
|
|
196
195
|
// extract the parts of the line
|
|
197
196
|
let repairedJson;
|
|
198
197
|
try {
|
|
@@ -229,146 +228,20 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
229
228
|
0 9px 28px 8px ${alpha(shadowColor, 0.05)}`
|
|
230
229
|
: "none",
|
|
231
230
|
} }, hashedSummary));
|
|
232
|
-
};
|
|
233
|
-
const agentDisplayName = cleanUpAgentName(removeTrailingUuid(targetAgent));
|
|
234
|
-
const introduceAgent = () => {
|
|
235
|
-
/**
|
|
236
|
-
* Introduce the agent to the user with a friendly greeting
|
|
237
|
-
*/
|
|
238
|
-
updateOutput(_jsx(UserQueryDisplay, { userQuery: agentDisplayName, title: targetAgent, userImage: AGENT_IMAGE }));
|
|
239
|
-
// Random greeting
|
|
240
|
-
const greeting = AGENT_GREETINGS[Math.floor(Math.random() * AGENT_GREETINGS.length)];
|
|
241
|
-
updateOutput(greeting);
|
|
242
|
-
};
|
|
243
|
-
/**
|
|
244
|
-
* Render the connectivity info as a list of origins and their tools
|
|
245
|
-
* @param connectivityInfo The connectivity info to render
|
|
246
|
-
* @returns A ReactNode representing the connectivity info with agents and their tools
|
|
247
|
-
*/
|
|
248
|
-
const renderConnectivityInfo = (connectivityInfo) => (_jsx(_Fragment, { children: connectivityInfo
|
|
249
|
-
// Don't show connection to self
|
|
250
|
-
.filter((info) => info.origin.toLowerCase() !== targetAgent.toLowerCase())
|
|
251
|
-
// Sort by origin name
|
|
252
|
-
.sort((a, b) => a.origin.localeCompare(b.origin))
|
|
253
|
-
// Render each origin and its tools
|
|
254
|
-
.map((info) => (_jsxs("li", { id: info.origin, children: [_jsx("b", { id: info.origin, children: info.origin }), _jsx("ul", { id: `${info.origin}-tools`, style: { marginLeft: "8px" }, children: info?.tools?.map((tool) => (_jsx("li", { id: tool, children: tool }, tool))) })] }, info.origin))) }));
|
|
255
|
-
/**
|
|
256
|
-
* Render sample queries as clickable chips. Agents may or may not have sample queries defined.
|
|
257
|
-
* @param sampleQueries The sample queries to render (from "connectivity" API)
|
|
258
|
-
* @returns A ReactNode representing the sample queries as clickable chips. If a user clicks a chip, it will
|
|
259
|
-
* send the query to the agent.
|
|
260
|
-
*/
|
|
261
|
-
const renderSampleQueries = (sampleQueries) => {
|
|
262
|
-
return sampleQueries?.length > 0 ? (_jsx(Box, { id: "sample-queries-box", sx: { marginTop: "2rem", marginBottom: "1rem" }, children: sampleQueries.slice(0, MAX_SAMPLE_QUERIES).map((query) => (_jsx(Tooltip, { id: `tooltip-${query}`, title: `Click to send query: "${query}"`, children: _jsx(Chip, { id: `sample-query-${query}`, label: query.length > QUERY_TRUNCATE_LENGTH
|
|
263
|
-
? `${query.slice(0, QUERY_TRUNCATE_LENGTH)}...`
|
|
264
|
-
: query, onClick: async () => {
|
|
265
|
-
await handleSend(query);
|
|
266
|
-
}, sx: {
|
|
267
|
-
color: "var(--bs-white)",
|
|
268
|
-
marginRight: "1rem",
|
|
269
|
-
marginBottom: "1rem",
|
|
270
|
-
backgroundColor: "var(--bs-accent1-medium)",
|
|
271
|
-
"&:hover": {
|
|
272
|
-
backgroundColor: "var(--bs-accent1-dark)",
|
|
273
|
-
},
|
|
274
|
-
} }, query) }, `tooltip-${query}`))) })) : null;
|
|
275
|
-
};
|
|
276
|
-
useEffect(() => {
|
|
277
|
-
const newAgent = async () => {
|
|
278
|
-
if (clearChatOnNewAgent) {
|
|
279
|
-
// New agent, so clear chat context if desired
|
|
280
|
-
chatContext.current = null;
|
|
281
|
-
currentResponse.current = "";
|
|
282
|
-
slyData.current = null;
|
|
283
|
-
setChatOutput([]);
|
|
284
|
-
}
|
|
285
|
-
// Introduce the agent to the user
|
|
286
|
-
introduceAgent();
|
|
287
|
-
// if not neuro san agent return since we won't get connectivity info
|
|
288
|
-
if (isLegacyAgentType(targetAgent)) {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
let agentFunction;
|
|
292
|
-
// It is a Neuro-san agent, so get the function and connectivity info
|
|
293
|
-
try {
|
|
294
|
-
agentFunction = await getAgentFunction(neuroSanURL, targetAgent, currentUser);
|
|
295
|
-
}
|
|
296
|
-
catch {
|
|
297
|
-
// For now, just return. May be a legacy agent without a functional description in Neuro-san.
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
try {
|
|
301
|
-
const connectivity = await getConnectivity(neuroSanURL, targetAgent, currentUser);
|
|
302
|
-
updateOutput(_jsx(MUIAccordion, { id: `${id}-agent-details`, sx: {
|
|
303
|
-
marginTop: "1rem",
|
|
304
|
-
marginBottom: "1rem",
|
|
305
|
-
}, items: [
|
|
306
|
-
{
|
|
307
|
-
title: "Network Details",
|
|
308
|
-
content: [
|
|
309
|
-
`My description is: "${agentFunction?.function?.description}"`,
|
|
310
|
-
_jsx("h6", { id: "connectivity-header", style: { marginTop: "1rem" }, children: "I can connect you to the following agents" }, "item-1"),
|
|
311
|
-
_jsx("ul", { id: "connectivity-list", "aria-labelledby": "connectivity-header", style: { marginTop: "1rem" }, children: renderConnectivityInfo(connectivity?.connectivity_info.concat()) }, "item-2"),
|
|
312
|
-
],
|
|
313
|
-
},
|
|
314
|
-
] }));
|
|
315
|
-
updateOutput(renderSampleQueries(connectivity?.metadata?.["sample_queries"] ?? []));
|
|
316
|
-
}
|
|
317
|
-
catch (e) {
|
|
318
|
-
sendNotification(NotificationType.error, `Failed to get connectivity info for ${cleanUpAgentName(targetAgent)}. Error: ${e}`);
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
if (targetAgent) {
|
|
322
|
-
void newAgent();
|
|
323
|
-
}
|
|
324
|
-
}, [targetAgent]);
|
|
231
|
+
}, [a11yLight, atelierDuneDark, darkMode, shadowColor, shouldWrapOutput]);
|
|
325
232
|
/**
|
|
326
|
-
* Handles adding content to the output window.
|
|
233
|
+
* Handles adding content to the output window. We only store the last MAX_CHAT_OUTPUT_ITEMS items to keep
|
|
234
|
+
* memory usage down.
|
|
327
235
|
* @param node A ReactNode to add to the output window -- text, spinner, etc. but could also be simple string
|
|
328
|
-
* @returns Nothing, but updates the output window with the new content.
|
|
236
|
+
* @returns Nothing, but updates the output window with the new content.
|
|
329
237
|
*/
|
|
330
|
-
const updateOutput = (node) => {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const resetState = () => {
|
|
338
|
-
// Reset state, whatever happened during request
|
|
339
|
-
setIsAwaitingLlm(false);
|
|
340
|
-
setChatInput("");
|
|
341
|
-
lastAIMessage.current = "";
|
|
342
|
-
finalAnswerRef.current = null;
|
|
343
|
-
// Get agent name, either from the enum (Neuro-san) or from the targetAgent string directly (legacy)
|
|
344
|
-
setPreviousResponse?.(targetAgent, currentResponse.current);
|
|
345
|
-
currentResponse.current = "";
|
|
346
|
-
};
|
|
347
|
-
const handleStop = () => {
|
|
348
|
-
try {
|
|
349
|
-
controller?.current?.abort();
|
|
350
|
-
controller.current = null;
|
|
351
|
-
updateOutput(_jsx(MUIAlert, { id: "opp-finder-error-occurred-alert", severity: "warning", children: "Request cancelled." }));
|
|
352
|
-
}
|
|
353
|
-
finally {
|
|
354
|
-
resetState();
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
// Regex to check if user has typed anything besides whitespace
|
|
358
|
-
const userInputEmpty = !chatInput || chatInput.length === 0 || hasOnlyWhitespace(chatInput);
|
|
359
|
-
// Enable Send button when there is user input and not awaiting a response
|
|
360
|
-
const shouldEnableSendButton = !userInputEmpty && !isAwaitingLlm;
|
|
361
|
-
// Enable regenerate button when there is a previous query to resent, and we're not awaiting a response
|
|
362
|
-
const shouldEnableRegenerateButton = previousUserQuery && !isAwaitingLlm;
|
|
363
|
-
// Enable Clear Chat button if not awaiting response and there is chat output to clear
|
|
364
|
-
const enableClearChatButton = !isAwaitingLlm && chatOutput.length > 0;
|
|
365
|
-
/**
|
|
366
|
-
* Extract the final answer from the response from a legacy agent
|
|
367
|
-
* @param response The response from the legacy agent
|
|
368
|
-
* @returns The final answer from the agent, if it exists or null if it doesn't
|
|
369
|
-
*/
|
|
370
|
-
const extractFinalAnswer = (response) => /Final Answer: (?<finalAnswerText>.*)/su.exec(response)?.groups?.["finalAnswerText"];
|
|
371
|
-
const handleChunk = (chunk) => {
|
|
238
|
+
const updateOutput = useCallback((node) => {
|
|
239
|
+
setChatOutput((current) => {
|
|
240
|
+
const next = [...current, node];
|
|
241
|
+
return next.length > MAX_CHAT_OUTPUT_ITEMS ? next.slice(-MAX_CHAT_OUTPUT_ITEMS) : next;
|
|
242
|
+
});
|
|
243
|
+
}, []);
|
|
244
|
+
const handleChunk = useCallback((chunk) => {
|
|
372
245
|
// Give container a chance to process the chunk first
|
|
373
246
|
const onChunkReceivedResult = onChunkReceived?.(chunk) ?? true;
|
|
374
247
|
succeeded.current = succeeded.current || onChunkReceivedResult;
|
|
@@ -376,6 +249,7 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
376
249
|
if (isLegacyAgentType(targetAgent)) {
|
|
377
250
|
// Display output as-is
|
|
378
251
|
updateOutput(chunk);
|
|
252
|
+
currentResponse.current += chunk;
|
|
379
253
|
// Check for Final Answer from legacy agent
|
|
380
254
|
const finalAnswerMatch = extractFinalAnswer(currentResponse.current);
|
|
381
255
|
if (finalAnswerMatch) {
|
|
@@ -390,17 +264,15 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
390
264
|
// But don't want to spam output by logging errors for every bad message.
|
|
391
265
|
return;
|
|
392
266
|
}
|
|
267
|
+
// Shallow merge existing slyData with incoming chatMessage.sly_data
|
|
268
|
+
if (chatMessage.sly_data) {
|
|
269
|
+
updateSlyData(targetAgent, chatMessage.sly_data);
|
|
270
|
+
}
|
|
393
271
|
// It's a ChatMessage. Does it have chat context? Only AGENT_FRAMEWORK messages can have chat context.
|
|
394
272
|
if (chatMessage.type === ChatMessageType.AGENT_FRAMEWORK && chatMessage.chat_context) {
|
|
395
273
|
// Save the chat context, potentially overwriting any previous ones we received during this session.
|
|
396
274
|
// We only care about the last one received.
|
|
397
|
-
|
|
398
|
-
// Nothing more to do with this message. It's just a message to give us the chat context, so return
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
// Merge slyData.current with incoming chatMessage.sly_data
|
|
402
|
-
if (chatMessage.sly_data) {
|
|
403
|
-
slyData.current = { ...slyData.current, ...chatMessage.sly_data };
|
|
275
|
+
updateChatContext(targetAgent, chatMessage.chat_context);
|
|
404
276
|
}
|
|
405
277
|
// Check if there is an error block in the "structure" field of the chat message.
|
|
406
278
|
if (chatMessage.structure) {
|
|
@@ -419,9 +291,36 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
419
291
|
? cleanUpAgentName(chatMessage.origin[chatMessage.origin.length - 1].tool)
|
|
420
292
|
: "Agent message";
|
|
421
293
|
updateOutput(processLogLine(chatMessage.text, agentName, chatMessage.type));
|
|
294
|
+
currentResponse.current += chatMessage.text;
|
|
422
295
|
}
|
|
423
|
-
};
|
|
424
|
-
const
|
|
296
|
+
}, [onChunkReceived, processLogLine, updateSlyData, targetAgent, updateChatContext, updateOutput]);
|
|
297
|
+
const introduceAgent = useCallback(() => {
|
|
298
|
+
/**
|
|
299
|
+
* Introduce the agent to the user with a friendly greeting
|
|
300
|
+
*/
|
|
301
|
+
updateOutput(_jsx(UserQueryDisplay, { userQuery: agentDisplayName, title: targetAgent, userImage: AGENT_IMAGE }));
|
|
302
|
+
// Random greeting
|
|
303
|
+
const greeting = agentGreetings[targetAgent] ?? AGENT_GREETINGS[Math.floor(Math.random() * AGENT_GREETINGS.length)];
|
|
304
|
+
updateOutput(greeting);
|
|
305
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- updateOutput is stable (empty useCallback deps)
|
|
306
|
+
}, [agentDisplayName, targetAgent]);
|
|
307
|
+
/**
|
|
308
|
+
* Reset the state of the component. This is called after a request is completed, regardless of success or failure.
|
|
309
|
+
*/
|
|
310
|
+
const resetState = useCallback(() => {
|
|
311
|
+
// Reset state, whatever happened during request
|
|
312
|
+
setIsAwaitingLlm(false);
|
|
313
|
+
setChatInput("");
|
|
314
|
+
lastAIMessage.current = "";
|
|
315
|
+
finalAnswerRef.current = null;
|
|
316
|
+
// Get agent name, either from the enum (Neuro-san) or from the targetAgent string directly (legacy)
|
|
317
|
+
setPreviousResponse?.(targetAgent, currentResponse.current);
|
|
318
|
+
currentResponse.current = "";
|
|
319
|
+
}, [setIsAwaitingLlm, setPreviousResponse, targetAgent]);
|
|
320
|
+
/*
|
|
321
|
+
* The main logic for sending a query to the server, with retries on errors.
|
|
322
|
+
*/
|
|
323
|
+
const doRetryLoop = useCallback(async (query) => {
|
|
425
324
|
succeeded.current = false;
|
|
426
325
|
let attemptNumber = 0;
|
|
427
326
|
let wasAborted = false;
|
|
@@ -431,15 +330,16 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
431
330
|
attemptNumber += 1;
|
|
432
331
|
// Check which agent type we are dealing with
|
|
433
332
|
if (isLegacyAgentType(targetAgent)) {
|
|
434
|
-
// It's a legacy agent (these go directly to the LLM and are different from
|
|
333
|
+
// It's a legacy agent (these go directly to the LLM and are different from
|
|
334
|
+
// the Neuro-san agents).
|
|
435
335
|
// Send the chat query to the server. This will block until the stream ends from the server
|
|
436
|
-
await sendLlmRequest(handleChunk, controller?.current.signal, legacyAgentEndpoint, extraParams, query, chatHistory
|
|
336
|
+
await sendLlmRequest(handleChunk, controller?.current.signal, legacyAgentEndpoint, extraParams, query, agentChatHistory.chatHistory, null, StreamingUnit.Chunk);
|
|
437
337
|
}
|
|
438
338
|
else {
|
|
439
339
|
// It's a Neuro-san agent.
|
|
440
340
|
// Some coded tools (data generator...) expect the username provided in slyData.
|
|
441
|
-
const slyDataWithUserName = { ...slyData
|
|
442
|
-
await sendChatQuery(neuroSanURL, controller?.current.signal, query, targetAgent, handleChunk, chatContext
|
|
341
|
+
const slyDataWithUserName = { ...agentChatHistory?.slyData, ...extraSlyData, login: currentUser };
|
|
342
|
+
await sendChatQuery(neuroSanURL, controller?.current.signal, query, targetAgent, handleChunk, agentChatHistory.chatContext, slyDataWithUserName, currentUser, StreamingUnit.Line);
|
|
443
343
|
}
|
|
444
344
|
}
|
|
445
345
|
catch (error) {
|
|
@@ -457,24 +357,35 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
457
357
|
}
|
|
458
358
|
}
|
|
459
359
|
} while (attemptNumber < MAX_AGENT_RETRIES && !succeeded.current);
|
|
460
|
-
return
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
360
|
+
return wasAborted;
|
|
361
|
+
}, [
|
|
362
|
+
agentChatHistory,
|
|
363
|
+
currentUser,
|
|
364
|
+
extraParams,
|
|
365
|
+
extraSlyData,
|
|
366
|
+
handleChunk,
|
|
367
|
+
legacyAgentEndpoint,
|
|
368
|
+
neuroSanURL,
|
|
369
|
+
targetAgent,
|
|
370
|
+
updateOutput,
|
|
371
|
+
]);
|
|
372
|
+
const handleSend = useCallback(async (query) => {
|
|
373
|
+
// Record user query in chat history. Discard anything beyond MAX_CHAT_HISTORY_ITEMS
|
|
374
|
+
const userQueryMessage = new HumanMessage({ content: query, id: uuid() });
|
|
375
|
+
updateChatHistory(targetAgent, [userQueryMessage]);
|
|
465
376
|
// Allow parent to intercept and modify the query before sending if needed
|
|
466
377
|
const queryToSend = onSend?.(query) ?? query;
|
|
467
378
|
// Save query for "regenerate" use. Again we save the real user input, not the modified query. It will again
|
|
468
379
|
// get intercepted and re-modified (if applicable) on "regenerate".
|
|
469
|
-
|
|
380
|
+
previousUserQuery.current = query;
|
|
470
381
|
setIsAwaitingLlm(true);
|
|
471
382
|
// Always start output by echoing user query.
|
|
472
383
|
// Note: we display the original user query, not the modified one. The modified one could be a monstrosity
|
|
473
|
-
// that we generated behind their back. Ultimately, we shouldn't need to generate a fake query on behalf
|
|
474
|
-
// user, but currently we do for orchestration.
|
|
384
|
+
// that we generated behind their back. Ultimately, we shouldn't need to generate a fake query on behalf
|
|
385
|
+
// of the user, but currently we do for orchestration.
|
|
475
386
|
updateOutput(_jsx(UserQueryDisplay, { userQuery: query, title: currentUser, userImage: userImage }));
|
|
476
387
|
// Add ID block for agent
|
|
477
|
-
updateOutput(_jsx(UserQueryDisplay, { userQuery:
|
|
388
|
+
updateOutput(_jsx(UserQueryDisplay, { userQuery: agentDisplayName, title: targetAgent, userImage: AGENT_IMAGE }));
|
|
478
389
|
// Allow clients to do something when streaming starts
|
|
479
390
|
onStreamingStarted?.();
|
|
480
391
|
// Set up the abort controller
|
|
@@ -483,13 +394,14 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
483
394
|
if (showThinking) {
|
|
484
395
|
updateOutput(_jsx(MUIAccordion, { id: "initiating-orchestration-accordion", items: [
|
|
485
396
|
{
|
|
486
|
-
title: `Contacting ${
|
|
397
|
+
title: `Contacting ${agentDisplayName}...`,
|
|
487
398
|
content: `Query: ${queryToSend}`,
|
|
488
399
|
},
|
|
489
400
|
], sx: { marginBottom: "1rem" } }));
|
|
490
401
|
}
|
|
491
402
|
try {
|
|
492
|
-
|
|
403
|
+
// Invoke the logic to send the request and retry as necessary
|
|
404
|
+
const wasAborted = await doRetryLoop(queryToSend);
|
|
493
405
|
if (!wasAborted && !succeeded.current) {
|
|
494
406
|
updateOutput(_jsx(MUIAlert, { id: "opp-finder-max-retries-exceeded-alert", severity: "error", children: `Gave up after ${MAX_AGENT_RETRIES} attempts.` }));
|
|
495
407
|
}
|
|
@@ -500,118 +412,217 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
500
412
|
updateOutput(" \n\n");
|
|
501
413
|
}
|
|
502
414
|
updateOutput(_jsx("div", { id: "final-answer-div", ref: finalAnswerRef, style: { marginBottom: "1rem" }, children: processLogLine(lastAIMessage.current, "Final Answer", ChatMessageType.AI, true) }));
|
|
415
|
+
// Record bot answer in history.
|
|
416
|
+
if (currentResponse?.current?.length > 0) {
|
|
417
|
+
updateChatHistory(targetAgent, [new AIMessage({ content: lastAIMessage.current, id: uuid() })]);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
else if (isLegacyAgentType(targetAgent) && currentResponse.current.length > 0) {
|
|
421
|
+
// It's a legacy agent that didn't provide a "Final Answer", so just record the whole response
|
|
422
|
+
// as the bot answer in that case.
|
|
423
|
+
updateChatHistory(targetAgent, [new AIMessage({ content: currentResponse.current, id: uuid() })]);
|
|
503
424
|
}
|
|
504
425
|
// Add a blank line after response
|
|
505
426
|
updateOutput("\n");
|
|
506
|
-
// Record bot answer in history.
|
|
507
|
-
if (currentResponse?.current?.length > 0) {
|
|
508
|
-
chatHistory.current = [...chatHistory.current, new AIMessage(currentResponse.current)];
|
|
509
|
-
}
|
|
510
427
|
}
|
|
511
428
|
finally {
|
|
512
429
|
resetState();
|
|
513
430
|
// Allow parent components to do something when streaming is complete
|
|
514
431
|
onStreamingComplete?.();
|
|
515
432
|
}
|
|
516
|
-
}
|
|
433
|
+
}, [
|
|
434
|
+
agentDisplayName,
|
|
435
|
+
currentUser,
|
|
436
|
+
doRetryLoop,
|
|
437
|
+
onSend,
|
|
438
|
+
onStreamingComplete,
|
|
439
|
+
onStreamingStarted,
|
|
440
|
+
processLogLine,
|
|
441
|
+
resetState,
|
|
442
|
+
setIsAwaitingLlm,
|
|
443
|
+
showThinking,
|
|
444
|
+
targetAgent,
|
|
445
|
+
updateChatHistory,
|
|
446
|
+
updateOutput,
|
|
447
|
+
userImage,
|
|
448
|
+
]);
|
|
449
|
+
useEffect(() => {
|
|
450
|
+
if (targetAgent) {
|
|
451
|
+
introduceAgent();
|
|
452
|
+
}
|
|
453
|
+
}, [targetAgent, introduceAgent]);
|
|
454
|
+
useEffect(() => {
|
|
455
|
+
const fetchAgentDetails = async () => {
|
|
456
|
+
let agentFunction;
|
|
457
|
+
// It is a Neuro-san agent, so get the function and connectivity info
|
|
458
|
+
try {
|
|
459
|
+
agentFunction = await getAgentFunction(neuroSanURL, targetAgent, currentUser);
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// For now, just return. May be a legacy agent without a functional description in Neuro-san.
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
try {
|
|
466
|
+
const connectivity = await getConnectivity(neuroSanURL, targetAgent, currentUser);
|
|
467
|
+
updateOutput(_jsx(AgentConnectivity, { id: id, description: agentFunction?.function?.description, connectivityInfo: connectivity?.connectivity_info, targetAgent: targetAgent }));
|
|
468
|
+
const sampleQueries = (connectivity?.metadata?.["sample_queries"] || []);
|
|
469
|
+
setAgentSampleQueries(sampleQueries);
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
sendNotification(NotificationType.error, `Failed to get connectivity info for ${agentDisplayName}. Error: ${e}`);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
if (targetAgent && !isLegacyAgentType(targetAgent)) {
|
|
476
|
+
void fetchAgentDetails();
|
|
477
|
+
}
|
|
478
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- only want to run this when targetAgent changes
|
|
479
|
+
}, [targetAgent]);
|
|
480
|
+
const handleStop = useCallback(() => {
|
|
481
|
+
try {
|
|
482
|
+
controller?.current?.abort();
|
|
483
|
+
controller.current = null;
|
|
484
|
+
updateOutput(_jsx(MUIAlert, { id: "opp-finder-error-occurred-alert", severity: "warning", children: "Request cancelled." }));
|
|
485
|
+
}
|
|
486
|
+
finally {
|
|
487
|
+
resetState();
|
|
488
|
+
}
|
|
489
|
+
}, [resetState, updateOutput]);
|
|
490
|
+
// Expose the handleStop method to parent components via ref for external control (e.g., to cancel chat requests)
|
|
491
|
+
useImperativeHandle(ref, () => ({
|
|
492
|
+
handleStop,
|
|
493
|
+
}), [handleStop]);
|
|
494
|
+
// Regex to check if user has typed anything besides whitespace
|
|
495
|
+
const userInputEmpty = !chatInput || chatInput.length === 0 || hasOnlyWhitespace(chatInput);
|
|
496
|
+
// Enable Send button when there is user input and not awaiting a response
|
|
497
|
+
const shouldEnableSendButton = !userInputEmpty && !isAwaitingLlm;
|
|
498
|
+
// Enable regenerate button when there is a previous query to resent, and we're not awaiting a response
|
|
499
|
+
const shouldEnableRegenerateButton = previousUserQuery && !isAwaitingLlm;
|
|
500
|
+
// Enable Clear Chat button if not awaiting response and there is chat output to clear
|
|
501
|
+
const enableClearChatButton = !isAwaitingLlm && chatOutput.length > 0;
|
|
517
502
|
const getPlaceholder = () => !targetAgent ? null : agentPlaceholders[targetAgent] || `Chat with ${agentDisplayName}`;
|
|
518
|
-
|
|
503
|
+
const handleClearChat = useCallback(() => {
|
|
504
|
+
setChatOutput([]);
|
|
505
|
+
resetHistory(targetAgent);
|
|
506
|
+
previousUserQuery.current = "";
|
|
507
|
+
currentResponse.current = "";
|
|
508
|
+
lastAIMessage.current = "";
|
|
509
|
+
introduceAgent();
|
|
510
|
+
}, [introduceAgent, resetHistory, targetAgent]);
|
|
511
|
+
/**
|
|
512
|
+
* Extract the list of React nodes to display in the output window, potentially filtering out "thinking"
|
|
513
|
+
* nodes if the user has chosen to hide them. Nodes that aren't to be shown are not even added to the DOM.
|
|
514
|
+
* There are a couple of special nodes that are always shown: chat history (collapsible accordion) and whatever
|
|
515
|
+
* we detected as the "final answer" (also a collapsible accordion).
|
|
516
|
+
*
|
|
517
|
+
* We use the MUIAccordion check as a proxy for "lines received from the agents"; everything that isn't
|
|
518
|
+
* a MUIAccordion (e.g. alerts, connectivity info, greetings) is not something we would want to hide when
|
|
519
|
+
* "show thinking" is off, so we always show those regardless of the "show thinking" setting.
|
|
520
|
+
*/
|
|
521
|
+
const nodesList = useMemo(() => chatOutput
|
|
522
|
+
.map((item) => {
|
|
523
|
+
if (isValidElement(item) && item.type === MUIAccordion) {
|
|
524
|
+
const shouldShow = showThinking || item.key === finalAnswerKey.current || item.key === CHAT_HISTORY_KEY;
|
|
525
|
+
return shouldShow ? item : null;
|
|
526
|
+
}
|
|
527
|
+
return item;
|
|
528
|
+
})
|
|
529
|
+
.filter((item) => item !== null), [chatOutput, showThinking]);
|
|
530
|
+
const getNoAgentOverlay = () => (_jsx(Box, { id: "chat-disabled-overlay", sx: {
|
|
531
|
+
position: "absolute",
|
|
532
|
+
top: 0,
|
|
533
|
+
left: 0,
|
|
534
|
+
right: 0,
|
|
535
|
+
bottom: 0,
|
|
536
|
+
zIndex: theme.zIndex.modal - 1,
|
|
537
|
+
cursor: "not-allowed",
|
|
538
|
+
// Capture all pointer events to prevent interaction with the chat when no agent is selected
|
|
539
|
+
pointerEvents: "all",
|
|
540
|
+
} }));
|
|
541
|
+
const getTitle = () => (_jsxs(Box, { id: `llm-chat-title-container-${id}`, sx: {
|
|
542
|
+
alignItems: "center",
|
|
543
|
+
borderTopLeftRadius: "var(--bs-border-radius)",
|
|
544
|
+
borderTopRightRadius: "var(--bs-border-radius)",
|
|
545
|
+
display: "flex",
|
|
546
|
+
justifyContent: "space-between",
|
|
547
|
+
paddingLeft: "1rem",
|
|
548
|
+
paddingRight: "0.5rem",
|
|
549
|
+
paddingTop: "0.25rem",
|
|
550
|
+
paddingBottom: "0.25rem",
|
|
551
|
+
}, children: [_jsx(Typography, { id: `llm-chat-title-${id}-text`, sx: { fontSize: "0.9rem" }, children: title }), onClose && (_jsx(IconButton, { "data-testid": `close-button-${id}`, id: `close-button-${id}`, onClick: onClose, children: _jsx(CloseIcon, { id: `close-icon-${id}` }) }))] }));
|
|
552
|
+
const getOptionsButtons = () => (_jsxs(_Fragment, { children: [_jsx(Tooltip, { id: "show-thinking", title: showThinking ? "Displaying agent thinking" : "Hiding agent thinking", children: _jsx("span", { id: "show-thinking-span", children: _jsx(LlmChatOptionsButton, { enabled: showThinking, id: "show-thinking-button", onClick: () => setShowThinking(!showThinking), posRight: 150, disabled: isAwaitingLlm, children: _jsx(AccountTreeIcon, { id: "show-thinking-icon", sx: { color: "var(--bs-white)", fontSize: "0.85rem" } }) }) }) }), _jsx(Tooltip, { id: "enable-autoscroll", title: autoScrollEnabled ? "Autoscroll enabled" : "Autoscroll disabled", children: _jsx(LlmChatOptionsButton, { enabled: autoScrollEnabled, id: "autoscroll-button", onClick: () => setAutoScrollEnabled(!autoScrollEnabled), posRight: 80, children: _jsx(VerticalAlignBottomIcon, { id: "autoscroll-icon", sx: { color: "var(--bs-white)", fontSize: "0.85rem" } }) }) }), _jsx(Tooltip, { id: "wrap-tooltip", title: shouldWrapOutput ? "Text wrapping enabled" : "Text wrapping disabled", children: _jsx(LlmChatOptionsButton, { enabled: shouldWrapOutput, id: "wrap-button", onClick: () => setShouldWrapOutput(!shouldWrapOutput), posRight: 10, children: _jsx(WrapTextIcon, { id: "wrap-icon", sx: { color: "var(--bs-white)", fontSize: "0.85rem" } }) }) })] }));
|
|
553
|
+
const getResponseBox = () => (_jsxs(Box, { id: "llm-response-div", sx: {
|
|
554
|
+
...divStyle,
|
|
555
|
+
border: "var(--bs-border-width) var(--bs-border-style)",
|
|
556
|
+
borderRadius: "var(--bs-border-radius)",
|
|
519
557
|
display: "flex",
|
|
520
|
-
flexDirection: "column",
|
|
521
558
|
flexGrow: 1,
|
|
522
559
|
height: "100%",
|
|
560
|
+
margin: "10px",
|
|
523
561
|
position: "relative",
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
562
|
+
overflowY: "auto",
|
|
563
|
+
}, children: [getOptionsButtons(), _jsxs(Box, { id: "llm-responses", ref: chatOutputRef, sx: {
|
|
564
|
+
backgroundColor: backgroundColor || undefined,
|
|
565
|
+
borderWidth: "1px",
|
|
566
|
+
borderRadius: "0.5rem",
|
|
567
|
+
fontSize: "smaller",
|
|
568
|
+
resize: "none",
|
|
569
|
+
overflowY: "auto", // Enable vertical scrollbar
|
|
570
|
+
paddingBottom: "60px",
|
|
571
|
+
paddingTop: "7.5px",
|
|
572
|
+
paddingLeft: "15px",
|
|
573
|
+
paddingRight: "15px",
|
|
574
|
+
width: "100%",
|
|
575
|
+
}, tabIndex: -1, children: [agentChatHistory?.chatHistory?.length > 0 && (_jsx(ChatHistory, { agentImage: AGENT_IMAGE, agentDisplayName: agentDisplayName, chatHistoryKey: CHAT_HISTORY_KEY, currentUser: currentUser, id: `${id}-chat-history`, messages: agentChatHistory.chatHistory, targetAgent: targetAgent, userImage: userImage })), _jsx(FormattedMarkdown, { id: `${id}-formatted-markdown`, nodesList: nodesList, style: darkMode ? atelierDuneDark : a11yLight, wrapLongLines: shouldWrapOutput }), _jsx(SampleQueries, { disabled: isAwaitingLlm, handleSend: handleSend, sampleQueries: agentSampleQueries }), isAwaitingLlm && (_jsxs(Box, { id: "awaitingOutputContainer", sx: { display: "flex", alignItems: "center", fontSize: "smaller" }, children: [_jsx("span", { id: "working-span", style: { marginRight: "1rem" }, children: "Working..." }), _jsx(CircularProgress, { id: "awaitingOutputSpinner", sx: {
|
|
576
|
+
color: "var(--bs-primary)",
|
|
577
|
+
}, size: "1rem" })] }))] }), _jsx(ControlButtons, { handleClearChat: handleClearChat, enableClearChatButton: enableClearChatButton, isAwaitingLlm: isAwaitingLlm, handleSend: handleSend, handleStop: handleStop, previousUserQuery: previousUserQuery.current, shouldEnableRegenerateButton: shouldEnableRegenerateButton })] }));
|
|
578
|
+
const getUserInputBox = () => (_jsxs(Box, { id: "user-input-div", sx: {
|
|
579
|
+
...divStyle,
|
|
580
|
+
display: "flex",
|
|
581
|
+
margin: "10px",
|
|
582
|
+
alignItems: "flex-end",
|
|
583
|
+
position: "relative",
|
|
584
|
+
}, children: [_jsx(Input, { autoComplete: "off", id: "user-input", multiline: true, placeholder: getPlaceholder(), ref: chatInputRef, sx: {
|
|
585
|
+
border: "var(--bs-border-style) var(--bs-border-width) var(--bs-gray-light)",
|
|
586
|
+
borderRadius: "var(--bs-border-radius)",
|
|
535
587
|
display: "flex",
|
|
536
|
-
flexDirection: "column",
|
|
537
588
|
flexGrow: 1,
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
return item;
|
|
579
|
-
}), style: darkMode ? atelierDuneDark : a11yLight, wrapLongLines: shouldWrapOutput }), isAwaitingLlm && (_jsxs(Box, { id: "awaitingOutputContainer", sx: { display: "flex", alignItems: "center", fontSize: "smaller" }, children: [_jsx("span", { id: "working-span", style: { marginRight: "1rem" }, children: "Working..." }), _jsx(CircularProgress, { id: "awaitingOutputSpinner", sx: {
|
|
580
|
-
color: "var(--bs-primary)",
|
|
581
|
-
}, size: "1rem" })] }))] }), _jsx(ControlButtons, { clearChatOnClickCallback: () => {
|
|
582
|
-
setChatOutput([]);
|
|
583
|
-
chatHistory.current = [];
|
|
584
|
-
chatContext.current = null;
|
|
585
|
-
setPreviousUserQuery("");
|
|
586
|
-
currentResponse.current = "";
|
|
587
|
-
lastAIMessage.current = "";
|
|
588
|
-
introduceAgent();
|
|
589
|
-
}, enableClearChatButton: enableClearChatButton, isAwaitingLlm: isAwaitingLlm, handleSend: handleSend, handleStop: handleStop, previousUserQuery: previousUserQuery, shouldEnableRegenerateButton: shouldEnableRegenerateButton })] }), _jsxs(Box, { id: "user-input-div", style: { ...divStyle, display: "flex", margin: "10px", alignItems: "flex-end", position: "relative" }, children: [_jsx(Input, { autoComplete: "off", id: "user-input", multiline: true, placeholder: getPlaceholder(), ref: chatInputRef, sx: {
|
|
590
|
-
border: "var(--bs-border-style) var(--bs-border-width) var(--bs-gray-light)",
|
|
591
|
-
borderRadius: "var(--bs-border-radius)",
|
|
592
|
-
display: "flex",
|
|
593
|
-
flexGrow: 1,
|
|
594
|
-
fontSize: "smaller",
|
|
595
|
-
marginRight: "0.75rem",
|
|
596
|
-
paddingBottom: "0.5rem",
|
|
597
|
-
paddingTop: "0.5rem",
|
|
598
|
-
paddingLeft: "1rem",
|
|
599
|
-
paddingRight: "1rem",
|
|
600
|
-
transition: "margin-right 0.2s",
|
|
601
|
-
}, onChange: (event) => {
|
|
602
|
-
setChatInput(event.target.value);
|
|
603
|
-
}, onKeyDown: async (event) => {
|
|
604
|
-
if (event.key === "Enter" && !event.shiftKey) {
|
|
605
|
-
event.preventDefault();
|
|
606
|
-
await handleSend(chatInput);
|
|
607
|
-
}
|
|
608
|
-
}, value: chatInput, endAdornment: _jsxs(InputAdornment, { id: "input-adornments", position: "end", disableTypography: true, children: [voiceInputState.isProcessingSpeech && (_jsx(CircularProgress, { size: 16, sx: {
|
|
609
|
-
color: "var(--bs-primary)",
|
|
610
|
-
marginRight: "0.5rem",
|
|
611
|
-
} })), _jsx(IconButton, { id: "clear-input-button", onClick: () => {
|
|
612
|
-
setChatInput("");
|
|
613
|
-
}, sx: {
|
|
614
|
-
color: "var(--bs-primary)",
|
|
615
|
-
opacity: userInputEmpty ? "25%" : "100%",
|
|
616
|
-
}, disabled: userInputEmpty, tabIndex: -1, edge: "end", children: _jsx(ClearIcon, { id: "clear-input-icon" }) })] }) }), _jsx(MicrophoneButton, { isMicOn: isMicOn, onMicToggle: setIsMicOn, speechRecognitionRef: speechRecognitionRef, voiceInputState: voiceInputState, setVoiceInputState: setVoiceInputState }), _jsx(SendButton, { enableSendButton: shouldEnableSendButton, id: "submit-query-button", onClickCallback: () => handleSend(chatInput) })] })] })] }));
|
|
589
|
+
fontSize: "smaller",
|
|
590
|
+
marginRight: "0.75rem",
|
|
591
|
+
paddingBottom: "0.5rem",
|
|
592
|
+
paddingTop: "0.5rem",
|
|
593
|
+
paddingLeft: "1rem",
|
|
594
|
+
paddingRight: "1rem",
|
|
595
|
+
transition: "margin-right 0.2s",
|
|
596
|
+
}, onChange: (event) => {
|
|
597
|
+
setChatInput(event.target.value);
|
|
598
|
+
}, onKeyDown: async (event) => {
|
|
599
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
600
|
+
event.preventDefault();
|
|
601
|
+
await handleSend(chatInput);
|
|
602
|
+
}
|
|
603
|
+
}, value: chatInput, endAdornment: _jsxs(InputAdornment, { id: "input-adornments", position: "end", disableTypography: true, children: [voiceInputState.isProcessingSpeech && (_jsx(CircularProgress, { size: 16, sx: {
|
|
604
|
+
color: "var(--bs-primary)",
|
|
605
|
+
marginRight: "0.5rem",
|
|
606
|
+
} })), _jsx(IconButton, { id: "clear-input-button", onClick: () => {
|
|
607
|
+
setChatInput("");
|
|
608
|
+
}, sx: {
|
|
609
|
+
color: "var(--bs-primary)",
|
|
610
|
+
opacity: userInputEmpty ? "25%" : "100%",
|
|
611
|
+
}, disabled: userInputEmpty, tabIndex: -1, edge: "end", children: _jsx(ClearIcon, { id: "clear-input-icon" }) })] }) }), _jsx(MicrophoneButton, { isMicOn: isMicOn, onMicToggle: setIsMicOn, speechRecognitionRef: speechRecognitionRef, voiceInputState: voiceInputState, setVoiceInputState: setVoiceInputState }), _jsx(SendButton, { enableSendButton: shouldEnableSendButton, id: "submit-query-button", onClickCallback: () => handleSend(chatInput) })] }));
|
|
612
|
+
const getChatBox = () => (_jsxs(Box, { id: `llm-chat-${id}`, sx: {
|
|
613
|
+
display: "flex",
|
|
614
|
+
flexDirection: "column",
|
|
615
|
+
flexGrow: 1,
|
|
616
|
+
height: "100%",
|
|
617
|
+
opacity: targetAgent ? 1 : 0.4,
|
|
618
|
+
pointerEvents: targetAgent ? "auto" : "none",
|
|
619
|
+
position: "relative",
|
|
620
|
+
}, children: [title && getTitle(), getResponseBox(), getUserInputBox()] }));
|
|
621
|
+
return (_jsx(Box, { id: `llm-chat-${id}`, sx: {
|
|
622
|
+
display: "flex",
|
|
623
|
+
flexDirection: "column",
|
|
624
|
+
flexGrow: 1,
|
|
625
|
+
height: "100%",
|
|
626
|
+
position: "relative",
|
|
627
|
+
}, children: targetAgent ? getChatBox() : getNoAgentOverlay() }));
|
|
617
628
|
};
|