@cognizant-ai-lab/ui-common 1.5.0 → 1.6.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/Theme/Theme.js +3 -3
- package/dist/components/AgentChat/ChatCommon/ChatCommon.d.ts +11 -1
- package/dist/components/AgentChat/ChatCommon/ChatCommon.js +284 -285
- package/dist/components/AgentChat/ChatCommon/ChatHistory.d.ts +1 -7
- package/dist/components/AgentChat/ChatCommon/ChatHistory.js +33 -22
- package/dist/components/AgentChat/ChatCommon/ControlButtons.js +2 -2
- package/dist/components/AgentChat/ChatCommon/Conversation.d.ts +13 -0
- package/dist/components/AgentChat/ChatCommon/Conversation.js +80 -0
- package/dist/components/AgentChat/ChatCommon/ConversationTurn.d.ts +23 -0
- package/dist/components/AgentChat/ChatCommon/ConversationTurn.js +11 -0
- package/dist/components/AgentChat/ChatCommon/FormattedMarkdown.js +5 -3
- package/dist/components/AgentChat/ChatCommon/SampleQueries.d.ts +3 -0
- package/dist/components/AgentChat/ChatCommon/SampleQueries.js +6 -3
- package/dist/components/AgentChat/ChatCommon/Thinking.d.ts +12 -0
- package/dist/components/AgentChat/ChatCommon/Thinking.js +51 -0
- package/dist/components/AgentChat/Common/LlmChatButton.d.ts +2 -2
- package/dist/components/AgentChat/Common/Types.d.ts +6 -5
- package/dist/components/AgentChat/Common/Types.js +5 -0
- package/dist/components/AgentChat/Common/Utils.d.ts +1 -1
- package/dist/components/AgentChat/Common/Utils.js +14 -9
- package/dist/components/Common/AccordionLite.d.ts +14 -0
- package/dist/components/Common/AccordionLite.js +25 -0
- package/dist/components/Common/ConfirmationModal.d.ts +1 -0
- package/dist/components/Common/ConfirmationModal.js +1 -1
- package/dist/components/Common/CustomerLogo.js +1 -1
- package/dist/components/Common/MUIAlert.d.ts +1 -0
- package/dist/components/Common/MUIAlert.js +3 -4
- package/dist/components/Common/Navbar.d.ts +2 -1
- package/dist/components/Common/Navbar.js +8 -4
- package/dist/components/Common/notification.d.ts +1 -1
- package/dist/components/Common/notification.js +17 -12
- package/dist/components/MultiAgentAccelerator/AgentFlow.d.ts +8 -0
- package/dist/components/MultiAgentAccelerator/AgentFlow.js +282 -82
- package/dist/components/MultiAgentAccelerator/AgentNode.d.ts +3 -1
- package/dist/components/MultiAgentAccelerator/AgentNode.js +64 -28
- package/dist/components/MultiAgentAccelerator/AgentNodePopup.d.ts +1 -4
- package/dist/components/MultiAgentAccelerator/AgentNodePopup.js +4 -5
- package/dist/components/MultiAgentAccelerator/GraphLayouts.js +19 -9
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +2 -2
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +268 -60
- package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.js +28 -12
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.js +21 -5
- package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.d.ts +4 -3
- package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +8 -2
- package/dist/components/MultiAgentAccelerator/TemporaryNetworks.d.ts +19 -2
- package/dist/components/MultiAgentAccelerator/TemporaryNetworks.js +40 -5
- package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.js +27 -14
- package/dist/components/MultiAgentAccelerator/Tour/MainTourSteps.d.ts +7 -0
- package/dist/components/MultiAgentAccelerator/Tour/MainTourSteps.js +88 -0
- package/dist/components/MultiAgentAccelerator/const.d.ts +7 -10
- package/dist/components/MultiAgentAccelerator/const.js +9 -10
- package/dist/const.d.ts +5 -1
- package/dist/const.js +5 -2
- package/dist/controller/agent/Agent.d.ts +10 -0
- package/dist/controller/agent/Agent.js +17 -1
- package/dist/controller/llm/LlmChat.js +2 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/state/TemporaryNetworks.d.ts +5 -15
- package/dist/state/TemporaryNetworks.js +15 -34
- package/dist/state/Tour.d.ts +29 -0
- package/dist/state/Tour.js +22 -0
- package/dist/state/UserInfo.d.ts +2 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils/Authentication.js +12 -3
- package/dist/utils/File.d.ts +7 -0
- package/dist/utils/File.js +14 -3
- package/dist/utils/text.js +2 -2
- package/dist/utils/title.js +1 -1
- package/dist/utils/zIndexLayers.js +3 -0
- package/package.json +15 -11
- package/dist/components/AgentChat/ChatCommon/AgentConnectivity.d.ts +0 -14
- package/dist/components/AgentChat/ChatCommon/AgentConnectivity.js +0 -23
- package/dist/components/AgentChat/ChatCommon/Greetings.d.ts +0 -1
- package/dist/components/AgentChat/ChatCommon/Greetings.js +0 -38
- package/dist/components/AgentChat/ChatCommon/UserQueryDisplay.d.ts +0 -7
- package/dist/components/AgentChat/ChatCommon/UserQueryDisplay.js +0 -32
- package/dist/components/Common/LlmChatOptionsButton.d.ts +0 -6
- package/dist/components/Common/LlmChatOptionsButton.js +0 -31
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/*
|
|
3
3
|
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
4
4
|
|
|
@@ -18,52 +18,43 @@ limitations under the License.
|
|
|
18
18
|
* See main function description.
|
|
19
19
|
*/
|
|
20
20
|
import { AIMessage, HumanMessage } from "@langchain/core/messages";
|
|
21
|
-
import AccountTreeIcon from "@mui/icons-material/AccountTree";
|
|
22
21
|
import ClearIcon from "@mui/icons-material/Clear";
|
|
23
22
|
import CloseIcon from "@mui/icons-material/Close";
|
|
24
|
-
import
|
|
25
|
-
import WrapTextIcon from "@mui/icons-material/WrapText";
|
|
23
|
+
import TuneIcon from "@mui/icons-material/Tune";
|
|
26
24
|
import Box from "@mui/material/Box";
|
|
25
|
+
import Checkbox from "@mui/material/Checkbox";
|
|
27
26
|
import CircularProgress from "@mui/material/CircularProgress";
|
|
28
27
|
import IconButton from "@mui/material/IconButton";
|
|
29
28
|
import Input from "@mui/material/Input";
|
|
30
29
|
import InputAdornment from "@mui/material/InputAdornment";
|
|
31
|
-
import
|
|
30
|
+
import ListItemIcon from "@mui/material/ListItemIcon";
|
|
31
|
+
import ListItemText from "@mui/material/ListItemText";
|
|
32
|
+
import Menu from "@mui/material/Menu";
|
|
33
|
+
import MenuItem from "@mui/material/MenuItem";
|
|
34
|
+
import { useTheme } from "@mui/material/styles";
|
|
32
35
|
import Tooltip from "@mui/material/Tooltip";
|
|
33
36
|
import Typography from "@mui/material/Typography";
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import ReactMarkdown from "react-markdown";
|
|
37
|
-
import SyntaxHighlighter from "react-syntax-highlighter";
|
|
37
|
+
import { isEmpty } from "lodash-es";
|
|
38
|
+
import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
|
|
38
39
|
import { v4 as uuid } from "uuid";
|
|
39
|
-
import { AgentConnectivity } from "./AgentConnectivity.js";
|
|
40
40
|
import { ChatHistory } from "./ChatHistory.js";
|
|
41
41
|
import { ControlButtons } from "./ControlButtons.js";
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
42
|
+
import { Conversation } from "./Conversation.js";
|
|
43
|
+
import { MessageRole } from "./ConversationTurn.js";
|
|
44
44
|
import { SampleQueries } from "./SampleQueries.js";
|
|
45
45
|
import { SendButton } from "./SendButton.js";
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
import { getAgentFunction, getConnectivity, sendChatQuery } from "../../../controller/agent/Agent.js";
|
|
46
|
+
import { Thinking } from "./Thinking.js";
|
|
47
|
+
import { sendChatQuery } from "../../../controller/agent/Agent.js";
|
|
49
48
|
import { sendLlmRequest, StreamingUnit } from "../../../controller/llm/LlmChat.js";
|
|
50
|
-
import { ChatMessageType
|
|
49
|
+
import { ChatMessageType } from "../../../generated/neuro-san/NeuroSanClient.js";
|
|
51
50
|
import { useAgentChatHistoryStore } from "../../../state/ChatHistory.js";
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
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";
|
|
51
|
+
import { hasOnlyWhitespace } from "../../../utils/text.js";
|
|
52
|
+
import { givesFinalAnswer, isLegacyAgentType } from "../Common/Types.js";
|
|
58
53
|
import { chatMessageFromChunk, checkError, cleanUpAgentName, removeTrailingUuid } from "../Common/Utils.js";
|
|
59
54
|
import { MicrophoneButton } from "../VoiceChat/MicrophoneButton.js";
|
|
60
55
|
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";
|
|
63
56
|
// Define fancy EMPTY constant to avoid linter error about using object literals as default props
|
|
64
57
|
const EMPTY = {};
|
|
65
|
-
// Avatar to use for agents in chat
|
|
66
|
-
const AGENT_IMAGE = "/agent.svg";
|
|
67
58
|
// How many times to retry the entire agent interaction process. Some networks have a well-defined success condition.
|
|
68
59
|
// For others, it's just "whenever the stream is done".
|
|
69
60
|
const MAX_AGENT_RETRIES = 3;
|
|
@@ -73,24 +64,23 @@ const MAX_AGENT_RETRIES = 3;
|
|
|
73
64
|
* @returns The final answer from the agent, if it exists or undefined if it doesn't
|
|
74
65
|
*/
|
|
75
66
|
const extractFinalAnswer = (response) => /Final Answer: (?<finalAnswerText>.*)/su.exec(response)?.groups?.["finalAnswerText"];
|
|
76
|
-
// Maximum number of
|
|
77
|
-
const
|
|
67
|
+
// Maximum number of turns to save
|
|
68
|
+
export const MAX_TURNS = 50;
|
|
78
69
|
/**
|
|
79
70
|
* Common chat component for agent chat. This component is used by all agent chat components to provide a consistent
|
|
80
71
|
* experience for users when chatting with agents. It handles user input as well as displaying and nicely formatting
|
|
81
72
|
* agent responses. Customization for inputs and outputs is provided via event handlers-like props.
|
|
82
73
|
*/
|
|
83
74
|
export const ChatCommon = ({ ref, ...props }) => {
|
|
84
|
-
const {
|
|
75
|
+
const { customAgentGreetings = EMPTY, agentPlaceholders = EMPTY, backgroundColor, currentUser, extraParams, extraSlyData, id, isAwaitingLlm, legacyAgentEndpoint, networkDescription, neuroSanURL, onChunkReceived, onClose, onSend, onStreamingComplete, onStreamingStarted, sampleQueries, setIsAwaitingLlm, setPreviousResponse, targetAgent, title, } = props;
|
|
85
76
|
// MUI theme
|
|
86
77
|
const theme = useTheme();
|
|
87
|
-
const shadowColor = theme.palette.mode === "dark" ? theme.palette.common.white : theme.palette.common.black;
|
|
88
78
|
// User LLM chat input
|
|
89
79
|
const [chatInput, setChatInput] = useState("");
|
|
90
80
|
// Previous user query (for "regenerate" feature)
|
|
91
|
-
const previousUserQuery =
|
|
92
|
-
//
|
|
93
|
-
const [
|
|
81
|
+
const [previousUserQuery, setPreviousUserQuery] = useState("");
|
|
82
|
+
// Turns within the current conversation
|
|
83
|
+
const [turns, setTurns] = useState([]);
|
|
94
84
|
// To accumulate current response, which will be different from the contents of the output window if there is a
|
|
95
85
|
// chat session
|
|
96
86
|
const currentResponse = useRef("");
|
|
@@ -102,28 +92,22 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
102
92
|
const controller = useRef(null);
|
|
103
93
|
// For tracking if we're auto-scrolling. A button allows the user to enable or disable auto-scrolling.
|
|
104
94
|
const [autoScrollEnabled, setAutoScrollEnabled] = useState(true);
|
|
105
|
-
// ref for same
|
|
106
|
-
const autoScrollEnabledRef = useRef(autoScrollEnabled);
|
|
107
95
|
// Whether to wrap output text
|
|
108
96
|
const [shouldWrapOutput, setShouldWrapOutput] = useState(true);
|
|
109
|
-
//
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
const finalAnswerKey = useRef("");
|
|
97
|
+
// Options menu control
|
|
98
|
+
const [optionsMenuAnchorEl, setOptionsMenuAnchorEl] = useState(null);
|
|
99
|
+
const [optionsMenuOpen, setOptionsMenuOpen] = useState(false);
|
|
113
100
|
// Persistent agent chat history store, which is where we store both kinds of chat histories
|
|
114
101
|
// (see store implementation for details)
|
|
115
102
|
const storedChatHistory = useAgentChatHistoryStore((state) => state?.history?.[targetAgent]);
|
|
116
103
|
const agentChatHistory = useMemo(() => storedChatHistory ?? { chatHistory: [], chatContext: null, slyData: {} }, [storedChatHistory]);
|
|
117
|
-
const [agentSampleQueries, setAgentSampleQueries] = useState([]);
|
|
118
104
|
// Access store for context items
|
|
119
105
|
const updateChatContext = useAgentChatHistoryStore((state) => state.updateChatContext);
|
|
120
106
|
const updateChatHistory = useAgentChatHistoryStore((state) => state.updateChatHistory);
|
|
121
107
|
const updateSlyData = useAgentChatHistoryStore((state) => state.updateSlyData);
|
|
122
108
|
const resetHistory = useAgentChatHistoryStore((state) => state.resetHistory);
|
|
123
|
-
// Ref
|
|
124
|
-
const
|
|
125
|
-
// Track state of "show thinking" toggle
|
|
126
|
-
const [showThinking, setShowThinking] = useState(false);
|
|
109
|
+
// Ref copy of current turns, so we can safely use it in callbacks without worrying about stale closures
|
|
110
|
+
const turnsRef = useRef([]);
|
|
127
111
|
// Microphone state for voice input
|
|
128
112
|
const [isMicOn, setIsMicOn] = useState(false);
|
|
129
113
|
// Ref for speech recognition
|
|
@@ -151,8 +135,6 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
151
135
|
};
|
|
152
136
|
// Keeps track of whether the agent completed its task
|
|
153
137
|
const succeeded = useRef(false);
|
|
154
|
-
const darkMode = theme.palette.mode === "dark";
|
|
155
|
-
const { atelierDuneDark, a11yLight } = HLJS_THEMES;
|
|
156
138
|
const agentDisplayName = useMemo(() => cleanUpAgentName(removeTrailingUuid(targetAgent)), [targetAgent]);
|
|
157
139
|
useEffect(() => {
|
|
158
140
|
// Set up speech recognition
|
|
@@ -160,103 +142,51 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
160
142
|
// Clean up function
|
|
161
143
|
return () => cleanupAndStopSpeechRecognition(speechRecognitionRef, handlers);
|
|
162
144
|
}, []);
|
|
163
|
-
// Sync ref with state variable for use within timer etc.
|
|
164
|
-
useEffect(() => {
|
|
165
|
-
autoScrollEnabledRef.current = autoScrollEnabled;
|
|
166
|
-
}, [autoScrollEnabled]);
|
|
167
145
|
useEffect(() => {
|
|
168
146
|
// Delay for a second before focusing on the input area; gets around ChatBot stealing focus.
|
|
169
147
|
setTimeout(() => chatInputRef?.current?.focus(), 1000);
|
|
170
148
|
}, []);
|
|
171
149
|
// Auto scroll chat output window when new content is added
|
|
172
150
|
useEffect(() => {
|
|
173
|
-
|
|
174
|
-
if (
|
|
175
|
-
chatOutputRef.current.scrollTop = finalAnswerRef.current.offsetTop - 50;
|
|
151
|
+
const container = chatOutputRef.current;
|
|
152
|
+
if (!container)
|
|
176
153
|
return;
|
|
154
|
+
// Live-streaming auto-scroll
|
|
155
|
+
if (autoScrollEnabled) {
|
|
156
|
+
container.scrollTop = container.scrollHeight;
|
|
177
157
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
* @param messageType The type of the message (AI, LEGACY_LOGS etc.). Used for displaying certain message types
|
|
188
|
-
* differently
|
|
189
|
-
* @param isFinalAnswer If true, the log line is the final answer from the agent. This will be highlighted in some
|
|
190
|
-
* way to draw the user's attention to it.
|
|
191
|
-
* @param summary Used as the "title" for the accordion block. Something like an agent name or "Final Answer"
|
|
192
|
-
* @returns A React component representing the log line (agent message)
|
|
193
|
-
*/
|
|
194
|
-
const processLogLine = useCallback((logLine, summary, messageType, isFinalAnswer) => {
|
|
195
|
-
// extract the parts of the line
|
|
196
|
-
let repairedJson;
|
|
197
|
-
try {
|
|
198
|
-
// Attempt to parse as JSON
|
|
199
|
-
// First, repair it. Also replace "escaped newlines" with actual newlines for better display.
|
|
200
|
-
repairedJson = jsonrepair(logLine);
|
|
201
|
-
// Now try to parse it. We don't care about the result, only if it throws on parsing.
|
|
202
|
-
JSON.parse(repairedJson);
|
|
203
|
-
repairedJson = repairedJson.replace(/\\n/gu, "\n").replace(/\\"/gu, "'");
|
|
204
|
-
}
|
|
205
|
-
catch {
|
|
206
|
-
// Not valid JSON
|
|
207
|
-
repairedJson = null;
|
|
208
|
-
}
|
|
209
|
-
const hashedSummary = hashString(summary);
|
|
210
|
-
const isAIMessage = messageType === ChatMessageType.AI;
|
|
211
|
-
if (isAIMessage && !isFinalAnswer) {
|
|
212
|
-
lastAIMessage.current = logLine;
|
|
213
|
-
}
|
|
214
|
-
if (isFinalAnswer) {
|
|
215
|
-
// Save key of final answer for highlighting
|
|
216
|
-
finalAnswerKey.current = hashedSummary;
|
|
217
|
-
}
|
|
218
|
-
return (_jsx(MUIAccordion, { id: `${hashedSummary}-panel`, defaultExpandedPanelKey: isFinalAnswer ? 1 : null, items: [
|
|
219
|
-
{
|
|
220
|
-
title: summary,
|
|
221
|
-
content: (_jsx("div", { id: `${summary}-details`, children: repairedJson ? (_jsx(SyntaxHighlighter, { id: "syntax-highlighter", language: "json", style: darkMode ? atelierDuneDark : a11yLight, showLineNumbers: false, wrapLongLines: shouldWrapOutput, children: repairedJson })) : (_jsx(ReactMarkdown, { children: logLine }, hashString(logLine))) })),
|
|
222
|
-
},
|
|
223
|
-
], sx: {
|
|
224
|
-
fontSize: "large",
|
|
225
|
-
marginBottom: "1rem",
|
|
226
|
-
boxShadow: isFinalAnswer
|
|
227
|
-
? `0 6px 16px 0 ${alpha(shadowColor, 0.08)}, 0 3px 6px -4px ${alpha(shadowColor, 0.12)},
|
|
228
|
-
0 9px 28px 8px ${alpha(shadowColor, 0.05)}`
|
|
229
|
-
: "none",
|
|
230
|
-
} }, hashedSummary));
|
|
231
|
-
}, [a11yLight, atelierDuneDark, darkMode, shadowColor, shouldWrapOutput]);
|
|
232
|
-
/**
|
|
233
|
-
* Handles adding content to the output window. We only store the last MAX_CHAT_OUTPUT_ITEMS items to keep
|
|
234
|
-
* memory usage down.
|
|
235
|
-
* @param node A ReactNode to add to the output window -- text, spinner, etc. but could also be simple string
|
|
236
|
-
* @returns Nothing, but updates the output window with the new content.
|
|
237
|
-
*/
|
|
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;
|
|
158
|
+
}, [autoScrollEnabled, isAwaitingLlm, turns]);
|
|
159
|
+
// Keep a ref copy of the turns array
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
turnsRef.current = turns;
|
|
162
|
+
}, [turns]);
|
|
163
|
+
const addTurn = useCallback((turn) => {
|
|
164
|
+
setTurns((current) => {
|
|
165
|
+
const next = [...current, turn];
|
|
166
|
+
return next.length > MAX_TURNS ? next.slice(-MAX_TURNS) : next;
|
|
242
167
|
});
|
|
243
168
|
}, []);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
return;
|
|
169
|
+
// We use this to update the same "turn" as chunks come in from legacy agents
|
|
170
|
+
const legacyTurnIdRef = useRef(null);
|
|
171
|
+
const handleLegacyAgentChunk = useCallback((chunk) => {
|
|
172
|
+
currentResponse.current += chunk;
|
|
173
|
+
if (!legacyTurnIdRef.current) {
|
|
174
|
+
// We don't yet have a turn for this response, so create one. On subsequent chunks, we'll just
|
|
175
|
+
// update the text of this turn.
|
|
176
|
+
legacyTurnIdRef.current = uuid();
|
|
177
|
+
addTurn({
|
|
178
|
+
id: legacyTurnIdRef.current,
|
|
179
|
+
messageType: ChatMessageType.AGENT,
|
|
180
|
+
role: MessageRole.Agent,
|
|
181
|
+
text: currentResponse.current,
|
|
182
|
+
});
|
|
259
183
|
}
|
|
184
|
+
else {
|
|
185
|
+
// We already have a turn for this response, so just update the text of that turn.
|
|
186
|
+
setTurns((prev) => prev.map((t) => (t.id === legacyTurnIdRef.current ? { ...t, text: currentResponse.current } : t)));
|
|
187
|
+
}
|
|
188
|
+
}, [addTurn]);
|
|
189
|
+
const handleNeuroSanAgentChunk = useCallback((chunk) => {
|
|
260
190
|
// For Neuro-san agents, we expect a ChatMessage structure in the chunk.
|
|
261
191
|
const chatMessage = chatMessageFromChunk(chunk);
|
|
262
192
|
if (!chatMessage) {
|
|
@@ -275,35 +205,53 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
275
205
|
updateChatContext(targetAgent, chatMessage.chat_context);
|
|
276
206
|
}
|
|
277
207
|
// Check if there is an error block in the "structure" field of the chat message.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
208
|
+
const errorMessage = checkError(chatMessage.structure);
|
|
209
|
+
if (errorMessage) {
|
|
210
|
+
// If there is an error block, display it.
|
|
211
|
+
addTurn({
|
|
212
|
+
id: uuid(),
|
|
213
|
+
role: MessageRole.Warning,
|
|
214
|
+
text: errorMessage,
|
|
215
|
+
});
|
|
216
|
+
succeeded.current = false;
|
|
285
217
|
}
|
|
286
|
-
else if (chatMessage?.text?.trim()
|
|
287
|
-
// Not an error, so output it if it has text
|
|
288
|
-
//
|
|
289
|
-
//
|
|
218
|
+
else if (chatMessage?.text?.trim().length > 0 || chatMessage.structure) {
|
|
219
|
+
// Not an error, so output it if it has text or a structure.
|
|
220
|
+
// This is the normal happy path for an incoming message.
|
|
221
|
+
// The backend sometimes sends messages with no text content, and we don't want to display those to the
|
|
222
|
+
// user. Agent name is the last tool in the origin array. If it's not there, use a default name.
|
|
290
223
|
const agentName = chatMessage.origin?.length > 0
|
|
291
224
|
? cleanUpAgentName(chatMessage.origin[chatMessage.origin.length - 1].tool)
|
|
292
|
-
: "Agent
|
|
293
|
-
|
|
294
|
-
|
|
225
|
+
: "Agent";
|
|
226
|
+
addTurn({
|
|
227
|
+
agentName,
|
|
228
|
+
id: uuid(),
|
|
229
|
+
messageType: chatMessage.type,
|
|
230
|
+
role: MessageRole.Agent,
|
|
231
|
+
structure: chatMessage.structure,
|
|
232
|
+
text: chatMessage.text,
|
|
233
|
+
});
|
|
234
|
+
if (chatMessage?.text?.trim().length > 0) {
|
|
235
|
+
// Append to current response if present
|
|
236
|
+
currentResponse.current += chatMessage.text;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}, [addTurn, targetAgent, updateChatContext, updateSlyData]);
|
|
240
|
+
/**
|
|
241
|
+
* Handle a chunk of response from the server. Called each time the server streams a chunk.
|
|
242
|
+
*/
|
|
243
|
+
const handleChunk = useCallback((chunk) => {
|
|
244
|
+
// Give container a chance to process the chunk first
|
|
245
|
+
const onChunkReceivedResult = onChunkReceived?.(chunk) ?? true;
|
|
246
|
+
succeeded.current = succeeded.current || onChunkReceivedResult;
|
|
247
|
+
if (isLegacyAgentType(targetAgent)) {
|
|
248
|
+
// For legacy agents, we either get plain text or Markdown. Just output it as-is.
|
|
249
|
+
handleLegacyAgentChunk(chunk);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
handleNeuroSanAgentChunk(chunk);
|
|
295
253
|
}
|
|
296
|
-
}, [onChunkReceived,
|
|
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]);
|
|
254
|
+
}, [onChunkReceived, targetAgent, handleNeuroSanAgentChunk, handleLegacyAgentChunk]);
|
|
307
255
|
/**
|
|
308
256
|
* Reset the state of the component. This is called after a request is completed, regardless of success or failure.
|
|
309
257
|
*/
|
|
@@ -311,11 +259,9 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
311
259
|
// Reset state, whatever happened during request
|
|
312
260
|
setIsAwaitingLlm(false);
|
|
313
261
|
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
262
|
setPreviousResponse?.(targetAgent, currentResponse.current);
|
|
318
263
|
currentResponse.current = "";
|
|
264
|
+
legacyTurnIdRef.current = null;
|
|
319
265
|
}, [setIsAwaitingLlm, setPreviousResponse, targetAgent]);
|
|
320
266
|
/*
|
|
321
267
|
* The main logic for sending a query to the server, with retries on errors.
|
|
@@ -353,12 +299,17 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
353
299
|
if (error instanceof Error) {
|
|
354
300
|
console.error(error, error.stack);
|
|
355
301
|
}
|
|
356
|
-
|
|
302
|
+
addTurn({
|
|
303
|
+
id: uuid(),
|
|
304
|
+
role: MessageRole.Error,
|
|
305
|
+
text: `Error occurred: ${error}`,
|
|
306
|
+
});
|
|
357
307
|
}
|
|
358
308
|
}
|
|
359
309
|
} while (attemptNumber < MAX_AGENT_RETRIES && !succeeded.current);
|
|
360
310
|
return wasAborted;
|
|
361
311
|
}, [
|
|
312
|
+
addTurn,
|
|
362
313
|
agentChatHistory,
|
|
363
314
|
currentUser,
|
|
364
315
|
extraParams,
|
|
@@ -367,8 +318,85 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
367
318
|
legacyAgentEndpoint,
|
|
368
319
|
neuroSanURL,
|
|
369
320
|
targetAgent,
|
|
370
|
-
updateOutput,
|
|
371
321
|
]);
|
|
322
|
+
const getFinalAnswerErrorTurn = () => ({
|
|
323
|
+
id: uuid(),
|
|
324
|
+
role: MessageRole.Error,
|
|
325
|
+
text: "The agent did not provide a final answer in the expected format. This is an internal error.",
|
|
326
|
+
});
|
|
327
|
+
const handleFinalAnswerLegacyAgent = useCallback(() => {
|
|
328
|
+
const currentTurns = turnsRef.current;
|
|
329
|
+
// Prefer the most recent matching turn
|
|
330
|
+
const idx = currentTurns.reduceRight((foundIndex, turn, i) => foundIndex !== -1 || extractFinalAnswer(turn.text) === undefined ? foundIndex : i, -1);
|
|
331
|
+
if (idx === -1) {
|
|
332
|
+
if (givesFinalAnswer(targetAgent)) {
|
|
333
|
+
// This agent is supposed to give final answers, but didn't this time. An error.
|
|
334
|
+
setTurns((prev) => [...prev, getFinalAnswerErrorTurn()]);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
// Use the last received turn as the final answer
|
|
339
|
+
const lastTurn = currentTurns.slice(-1)[0];
|
|
340
|
+
if (!lastTurn)
|
|
341
|
+
return;
|
|
342
|
+
// Just set the last turn as the final answer
|
|
343
|
+
setTurns((prev) => prev.map((turn) => (turn.id === lastTurn.id ? { ...turn, role: MessageRole.FinalAnswer } : turn)));
|
|
344
|
+
// Save it to chat history
|
|
345
|
+
updateChatHistory(targetAgent, [new AIMessage({ content: lastTurn.text, id: uuid() })]);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const sourceTurn = currentTurns[idx];
|
|
350
|
+
// Save item to chat history (same as original behavior)
|
|
351
|
+
updateChatHistory(targetAgent, [new AIMessage({ content: sourceTurn.text, id: uuid() })]);
|
|
352
|
+
// Extract the final answer from the turn.
|
|
353
|
+
const finalAnswer = extractFinalAnswer(sourceTurn.text)?.trim();
|
|
354
|
+
// Update the turn to be a final answer turn, and add a new final answer turn with just the final answer text.
|
|
355
|
+
setTurns((prev) => {
|
|
356
|
+
const sourceTurnIndex = prev.findIndex(({ id: itemId }) => itemId === sourceTurn.id);
|
|
357
|
+
const updated = sourceTurnIndex === -1
|
|
358
|
+
? [...prev]
|
|
359
|
+
: prev.map((turn, index) => (index === sourceTurnIndex ? { ...turn, text: sourceTurn.text } : turn));
|
|
360
|
+
// Add explicit final answer as a new terminal turn
|
|
361
|
+
updated.push({
|
|
362
|
+
id: uuid(),
|
|
363
|
+
role: MessageRole.FinalAnswer,
|
|
364
|
+
text: finalAnswer,
|
|
365
|
+
});
|
|
366
|
+
return updated;
|
|
367
|
+
});
|
|
368
|
+
}, [targetAgent, updateChatHistory]);
|
|
369
|
+
/**
|
|
370
|
+
* Extract the final answer from the turns for a Neuro-san agent. For Neuro-san agents, we expect the final answer
|
|
371
|
+
* to be the most recent turn messageType === ChatMessageType.AGENT_FRAMEWORK.
|
|
372
|
+
*/
|
|
373
|
+
const handleFinalAnswerNeuroSanAgent = useCallback(() => {
|
|
374
|
+
// Get current turns snapshot
|
|
375
|
+
const currentTurns = turnsRef.current;
|
|
376
|
+
// Find the most recent turn that is from the agent framework, which should be the one that contains the
|
|
377
|
+
// final answer.
|
|
378
|
+
const idx = currentTurns.reduceRight((found, turn, i) => (found !== -1 || turn.messageType !== ChatMessageType.AGENT_FRAMEWORK ? found : i), -1);
|
|
379
|
+
// Check for final answer
|
|
380
|
+
if (idx === -1) {
|
|
381
|
+
// No final answer found in the turns. Should never happen for a Neuro-san agent.
|
|
382
|
+
setTurns((prev) => [...prev, getFinalAnswerErrorTurn()]);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
// Extract final answer from that turn
|
|
386
|
+
const finalAnswerTurn = currentTurns[idx];
|
|
387
|
+
const hasFinalAnswer = finalAnswerTurn.text?.trim().length > 0 || !isEmpty(finalAnswerTurn.structure);
|
|
388
|
+
if (hasFinalAnswer) {
|
|
389
|
+
// Update relevant turn to be the final answer
|
|
390
|
+
setTurns((prev) => prev.map((turn, i) => (i === idx ? { ...turn, role: MessageRole.FinalAnswer } : turn)));
|
|
391
|
+
// Save final answer to chat history
|
|
392
|
+
const finalAnswerContent = finalAnswerTurn.text || JSON.stringify(finalAnswerTurn.structure, null, 2);
|
|
393
|
+
updateChatHistory(targetAgent, [new AIMessage({ content: finalAnswerContent, id: uuid() })]);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
// No final answer found, display error
|
|
397
|
+
setTurns((prev) => [...prev, getFinalAnswerErrorTurn()]);
|
|
398
|
+
}
|
|
399
|
+
}, [targetAgent, updateChatHistory]);
|
|
372
400
|
const handleSend = useCallback(async (query) => {
|
|
373
401
|
// Record user query in chat history. Discard anything beyond MAX_CHAT_HISTORY_ITEMS
|
|
374
402
|
const userQueryMessage = new HumanMessage({ content: query, id: uuid() });
|
|
@@ -377,53 +405,45 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
377
405
|
const queryToSend = onSend?.(query) ?? query;
|
|
378
406
|
// Save query for "regenerate" use. Again we save the real user input, not the modified query. It will again
|
|
379
407
|
// get intercepted and re-modified (if applicable) on "regenerate".
|
|
380
|
-
|
|
408
|
+
setPreviousUserQuery(query);
|
|
381
409
|
setIsAwaitingLlm(true);
|
|
382
410
|
// Always start output by echoing user query.
|
|
383
411
|
// Note: we display the original user query, not the modified one. The modified one could be a monstrosity
|
|
384
412
|
// that we generated behind their back. Ultimately, we shouldn't need to generate a fake query on behalf
|
|
385
413
|
// of the user, but currently we do for orchestration.
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
414
|
+
addTurn({
|
|
415
|
+
id: uuid(),
|
|
416
|
+
role: MessageRole.User,
|
|
417
|
+
text: query,
|
|
418
|
+
});
|
|
389
419
|
// Allow clients to do something when streaming starts
|
|
390
420
|
onStreamingStarted?.();
|
|
391
421
|
// Set up the abort controller
|
|
392
422
|
controller.current = new AbortController();
|
|
393
423
|
setIsAwaitingLlm(true);
|
|
394
|
-
if (showThinking) {
|
|
395
|
-
updateOutput(_jsx(MUIAccordion, { id: "initiating-orchestration-accordion", items: [
|
|
396
|
-
{
|
|
397
|
-
title: `Contacting ${agentDisplayName}...`,
|
|
398
|
-
content: `Query: ${queryToSend}`,
|
|
399
|
-
},
|
|
400
|
-
], sx: { marginBottom: "1rem" } }));
|
|
401
|
-
}
|
|
402
424
|
try {
|
|
403
425
|
// Invoke the logic to send the request and retry as necessary
|
|
404
426
|
const wasAborted = await doRetryLoop(queryToSend);
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
427
|
+
// Abort condition is handled elsewhere
|
|
428
|
+
if (!wasAborted) {
|
|
429
|
+
if (succeeded.current) {
|
|
430
|
+
// Success: infer final answer depending on agent type
|
|
431
|
+
if (isLegacyAgentType(targetAgent)) {
|
|
432
|
+
handleFinalAnswerLegacyAgent();
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
handleFinalAnswerNeuroSanAgent();
|
|
436
|
+
}
|
|
413
437
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
438
|
+
else {
|
|
439
|
+
// Exhausted retries without success. Display error to user.
|
|
440
|
+
addTurn({
|
|
441
|
+
id: uuid(),
|
|
442
|
+
role: MessageRole.Error,
|
|
443
|
+
text: `Gave up after ${MAX_AGENT_RETRIES} attempts.`,
|
|
444
|
+
});
|
|
418
445
|
}
|
|
419
446
|
}
|
|
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() })]);
|
|
424
|
-
}
|
|
425
|
-
// Add a blank line after response
|
|
426
|
-
updateOutput("\n");
|
|
427
447
|
}
|
|
428
448
|
finally {
|
|
429
449
|
resetState();
|
|
@@ -431,66 +451,32 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
431
451
|
onStreamingComplete?.();
|
|
432
452
|
}
|
|
433
453
|
}, [
|
|
434
|
-
|
|
435
|
-
currentUser,
|
|
454
|
+
addTurn,
|
|
436
455
|
doRetryLoop,
|
|
456
|
+
handleFinalAnswerLegacyAgent,
|
|
457
|
+
handleFinalAnswerNeuroSanAgent,
|
|
437
458
|
onSend,
|
|
438
459
|
onStreamingComplete,
|
|
439
460
|
onStreamingStarted,
|
|
440
|
-
processLogLine,
|
|
441
461
|
resetState,
|
|
442
462
|
setIsAwaitingLlm,
|
|
443
|
-
showThinking,
|
|
444
463
|
targetAgent,
|
|
445
464
|
updateChatHistory,
|
|
446
|
-
updateOutput,
|
|
447
|
-
userImage,
|
|
448
465
|
]);
|
|
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
466
|
const handleStop = useCallback(() => {
|
|
481
467
|
try {
|
|
482
468
|
controller?.current?.abort();
|
|
483
469
|
controller.current = null;
|
|
484
|
-
|
|
470
|
+
addTurn({
|
|
471
|
+
id: uuid(),
|
|
472
|
+
role: MessageRole.Warning,
|
|
473
|
+
text: "Request cancelled.",
|
|
474
|
+
});
|
|
485
475
|
}
|
|
486
476
|
finally {
|
|
487
477
|
resetState();
|
|
488
478
|
}
|
|
489
|
-
}, [
|
|
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]);
|
|
479
|
+
}, [addTurn, resetState]);
|
|
494
480
|
// Regex to check if user has typed anything besides whitespace
|
|
495
481
|
const userInputEmpty = !chatInput || chatInput.length === 0 || hasOnlyWhitespace(chatInput);
|
|
496
482
|
// Enable Send button when there is user input and not awaiting a response
|
|
@@ -498,46 +484,30 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
498
484
|
// Enable regenerate button when there is a previous query to resent, and we're not awaiting a response
|
|
499
485
|
const shouldEnableRegenerateButton = previousUserQuery && !isAwaitingLlm;
|
|
500
486
|
// Enable Clear Chat button if not awaiting response and there is chat output to clear
|
|
501
|
-
const enableClearChatButton = !isAwaitingLlm &&
|
|
487
|
+
const enableClearChatButton = !isAwaitingLlm && (turns.length > 0 || agentChatHistory?.chatHistory?.length > 0);
|
|
502
488
|
const getPlaceholder = () => !targetAgent ? null : agentPlaceholders[targetAgent] || `Chat with ${agentDisplayName}`;
|
|
503
489
|
const handleClearChat = useCallback(() => {
|
|
504
|
-
|
|
490
|
+
setTurns([]);
|
|
505
491
|
resetHistory(targetAgent);
|
|
506
|
-
|
|
492
|
+
setPreviousUserQuery("");
|
|
507
493
|
currentResponse.current = "";
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
} }));
|
|
494
|
+
}, [resetHistory, targetAgent]);
|
|
495
|
+
// Expose the handleStop and handleClearChat methods to parent components via ref for external control
|
|
496
|
+
useImperativeHandle(ref, () => ({
|
|
497
|
+
handleStop,
|
|
498
|
+
handleClearChat,
|
|
499
|
+
}), [handleStop, handleClearChat]);
|
|
500
|
+
const getNoAgentOverlay = () => (_jsx(Tooltip, { title: "Please select a Network from the list to start the chat.", placement: "auto", children: _jsx(Box, { id: "chat-disabled-overlay", sx: {
|
|
501
|
+
position: "absolute",
|
|
502
|
+
top: 0,
|
|
503
|
+
left: 0,
|
|
504
|
+
right: 0,
|
|
505
|
+
bottom: 0,
|
|
506
|
+
zIndex: theme.zIndex.modal - 1,
|
|
507
|
+
cursor: "not-allowed",
|
|
508
|
+
// Capture all pointer events to prevent interaction with the chat when no agent is selected
|
|
509
|
+
pointerEvents: "all",
|
|
510
|
+
} }) }));
|
|
541
511
|
const getTitle = () => (_jsxs(Box, { id: `llm-chat-title-container-${id}`, sx: {
|
|
542
512
|
alignItems: "center",
|
|
543
513
|
borderTopLeftRadius: "var(--bs-border-radius)",
|
|
@@ -549,32 +519,61 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
549
519
|
paddingTop: "0.25rem",
|
|
550
520
|
paddingBottom: "0.25rem",
|
|
551
521
|
}, 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
|
|
522
|
+
const getOptionsMenuButton = () => (_jsx(Box, { sx: {
|
|
523
|
+
position: "absolute",
|
|
524
|
+
top: "0.25rem",
|
|
525
|
+
right: "0.0rem",
|
|
526
|
+
}, children: _jsx(IconButton, { onClick: (e) => {
|
|
527
|
+
setOptionsMenuAnchorEl(e.currentTarget);
|
|
528
|
+
setOptionsMenuOpen(true);
|
|
529
|
+
}, children: _jsx(TuneIcon, { sx: { fontSize: "1.2rem" } }) }) }));
|
|
530
|
+
const agentGreeting = customAgentGreetings[targetAgent] ?? "Hi, how can I help?";
|
|
531
|
+
const handleOptionsMenuClose = () => {
|
|
532
|
+
setOptionsMenuAnchorEl(null);
|
|
533
|
+
setOptionsMenuOpen(false);
|
|
534
|
+
};
|
|
535
|
+
const handleToggleAutoScroll = () => {
|
|
536
|
+
setAutoScrollEnabled((prev) => !prev);
|
|
537
|
+
};
|
|
538
|
+
const handleToggleWrapOutput = () => {
|
|
539
|
+
setShouldWrapOutput((prev) => !prev);
|
|
540
|
+
};
|
|
541
|
+
const getOptionsMenu = () => (_jsxs(Menu, { id: `${id}-options-menu`, anchorEl: optionsMenuAnchorEl, open: optionsMenuOpen, onClose: handleOptionsMenuClose, slotProps: {
|
|
542
|
+
list: {
|
|
543
|
+
dense: true,
|
|
544
|
+
sx: {
|
|
545
|
+
py: 0,
|
|
546
|
+
"& .MuiMenuItem-root": { minHeight: 30, py: 0.5, px: 1 },
|
|
547
|
+
"& .MuiCheckbox-root": { p: 0.5 },
|
|
548
|
+
"& .MuiListItemText-primary": { fontSize: "smaller" },
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
}, children: [_jsxs(MenuItem, { onClick: handleToggleAutoScroll, children: [_jsx(ListItemIcon, { children: _jsx(Checkbox, { checked: autoScrollEnabled, tabIndex: -1, sx: { pointerEvents: "none" } }) }), _jsx(ListItemText, { primary: "Auto-scroll output" })] }), _jsxs(MenuItem, { onClick: handleToggleWrapOutput, children: [_jsx(ListItemIcon, { children: _jsx(Checkbox, { checked: shouldWrapOutput, tabIndex: -1, sx: { pointerEvents: "none" } }) }), _jsx(ListItemText, { primary: "Wrap output" })] })] }));
|
|
553
552
|
const getResponseBox = () => (_jsxs(Box, { id: "llm-response-div", sx: {
|
|
554
553
|
...divStyle,
|
|
555
554
|
border: "var(--bs-border-width) var(--bs-border-style)",
|
|
556
555
|
borderRadius: "var(--bs-border-radius)",
|
|
557
556
|
display: "flex",
|
|
558
557
|
flexGrow: 1,
|
|
559
|
-
|
|
560
|
-
margin: "10px",
|
|
558
|
+
marginLeft: "10px",
|
|
561
559
|
position: "relative",
|
|
562
560
|
overflowY: "auto",
|
|
563
|
-
}, children: [
|
|
564
|
-
backgroundColor
|
|
565
|
-
borderWidth: "1px",
|
|
561
|
+
}, children: [_jsxs(Box, { id: "llm-responses", ref: chatOutputRef, sx: {
|
|
562
|
+
backgroundColor,
|
|
566
563
|
borderRadius: "0.5rem",
|
|
567
|
-
fontSize: "
|
|
568
|
-
|
|
569
|
-
overflowY: "auto", // Enable vertical scrollbar
|
|
564
|
+
fontSize: "16px",
|
|
565
|
+
overflowY: "auto",
|
|
570
566
|
paddingBottom: "60px",
|
|
571
|
-
paddingTop: "7.5px",
|
|
572
567
|
paddingLeft: "15px",
|
|
573
568
|
paddingRight: "15px",
|
|
569
|
+
paddingTop: "7.5px",
|
|
570
|
+
scrollbarGutter: "stable",
|
|
574
571
|
width: "100%",
|
|
575
|
-
}, tabIndex: -1, children: [agentChatHistory?.chatHistory?.length > 0 && (_jsx(ChatHistory, {
|
|
572
|
+
}, tabIndex: -1, children: [getOptionsMenu(), getOptionsMenuButton(), agentChatHistory?.chatHistory?.length > 0 && (_jsx(ChatHistory, { id: id, messages: agentChatHistory.chatHistory })), _jsxs(Box, { sx: { marginBottom: "0.5rem", marginTop: "1rem", color: "var(--bs-gray)" }, children: [_jsxs(Typography, { component: "span", sx: { fontWeight: 700 }, variant: "inherit", children: [targetAgent, networkDescription && ":"] }), networkDescription && (_jsxs(Typography, { component: "span", sx: { ml: 0.5 }, variant: "inherit", children: [" ", networkDescription] }))] }), _jsx(Box, { sx: { marginBottom: "0.5rem", marginTop: "1rem" }, children: agentGreeting }), _jsx(SampleQueries, { disabled: isAwaitingLlm, handleSend: handleSend, sampleQueries: sampleQueries }), _jsx(Conversation, { id: id, includeAgentMessages: !givesFinalAnswer(targetAgent), shouldWrapOutput: shouldWrapOutput, turns: turns }), !isAwaitingLlm && turns.length > 0 && (
|
|
573
|
+
// Only show thinking once streaming is complete
|
|
574
|
+
_jsx(Thinking, { id: id, turns: turns })), 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
575
|
color: "var(--bs-primary)",
|
|
577
|
-
}, size: "1rem" })] }))] }), _jsx(ControlButtons, {
|
|
576
|
+
}, size: "1rem" })] }))] }), _jsx(ControlButtons, { enableClearChatButton: enableClearChatButton, handleClearChat: handleClearChat, handleSend: handleSend, handleStop: handleStop, isAwaitingLlm: isAwaitingLlm, previousUserQuery: previousUserQuery, shouldEnableRegenerateButton: shouldEnableRegenerateButton })] }));
|
|
578
577
|
const getUserInputBox = () => (_jsxs(Box, { id: "user-input-div", sx: {
|
|
579
578
|
...divStyle,
|
|
580
579
|
display: "flex",
|
|
@@ -586,7 +585,7 @@ export const ChatCommon = ({ ref, ...props }) => {
|
|
|
586
585
|
borderRadius: "var(--bs-border-radius)",
|
|
587
586
|
display: "flex",
|
|
588
587
|
flexGrow: 1,
|
|
589
|
-
fontSize: "
|
|
588
|
+
fontSize: "17px",
|
|
590
589
|
marginRight: "0.75rem",
|
|
591
590
|
paddingBottom: "0.5rem",
|
|
592
591
|
paddingTop: "0.5rem",
|