@cognizant-ai-lab/ui-common 1.5.1 → 1.7.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/ChatCommon.d.ts +16 -6
- package/dist/components/AgentChat/ChatCommon/ChatCommon.js +250 -166
- package/dist/components/AgentChat/ChatCommon/ChatHistory.d.ts +1 -7
- package/dist/components/AgentChat/ChatCommon/ChatHistory.js +33 -22
- package/dist/components/AgentChat/ChatCommon/Conversation.d.ts +3 -5
- package/dist/components/AgentChat/ChatCommon/Conversation.js +35 -57
- package/dist/components/AgentChat/ChatCommon/ConversationTurn.d.ts +9 -5
- package/dist/components/AgentChat/ChatCommon/ConversationTurn.js +3 -2
- 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 +13 -7
- package/dist/components/ChatBot/ChatBot.d.ts +0 -4
- package/dist/components/ChatBot/ChatBot.js +2 -2
- 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 -1
- package/dist/components/Common/CustomerLogo.js +1 -3
- package/dist/components/Common/MUIAlert.d.ts +1 -0
- package/dist/components/Common/MUIAlert.js +3 -4
- package/dist/components/MultiAgentAccelerator/AgentFlow.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/AgentFlow.js +154 -59
- package/dist/components/MultiAgentAccelerator/AgentNode.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/AgentNode.js +46 -45
- package/dist/components/MultiAgentAccelerator/GraphLayouts.js +12 -4
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +103 -24
- package/dist/components/Settings/ApiKeyInput.d.ts +16 -0
- package/dist/components/Settings/ApiKeyInput.js +70 -0
- package/dist/components/Settings/SettingsDialog.js +30 -3
- package/dist/controller/llm/Providers.d.ts +2 -0
- package/dist/controller/llm/Providers.js +41 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/state/Settings.d.ts +2 -0
- package/dist/state/Settings.js +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/dist/components/AgentChat/ChatCommon/AgentConnectivity.d.ts +0 -14
- package/dist/components/AgentChat/ChatCommon/AgentConnectivity.js +0 -23
- package/dist/components/AgentChat/ChatCommon/AgentIntro.d.ts +0 -12
- package/dist/components/AgentChat/ChatCommon/AgentIntro.js +0 -19
- package/dist/components/AgentChat/ChatCommon/AgentMetadata.d.ts +0 -14
- package/dist/components/AgentChat/ChatCommon/AgentMetadata.js +0 -43
- package/dist/components/AgentChat/ChatCommon/Const.d.ts +0 -1
- package/dist/components/AgentChat/ChatCommon/Const.js +0 -2
- 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,14 +1,8 @@
|
|
|
1
1
|
import { BaseMessage } from "@langchain/core/messages";
|
|
2
2
|
import { FC } from "react";
|
|
3
3
|
interface ChatHistoryProps {
|
|
4
|
-
readonly agentDisplayName: string;
|
|
5
|
-
readonly agentImage: string;
|
|
6
|
-
readonly chatHistoryKey: string;
|
|
7
|
-
readonly currentUser: string;
|
|
8
|
-
readonly id: string;
|
|
9
4
|
readonly messages: BaseMessage[];
|
|
10
|
-
readonly
|
|
11
|
-
readonly userImage: string;
|
|
5
|
+
readonly id: string;
|
|
12
6
|
}
|
|
13
7
|
/**
|
|
14
8
|
* Component for displaying chat history from previous interactions with the agent.
|
|
@@ -1,27 +1,38 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { useTheme } from "@mui/material/styles";
|
|
3
|
+
import { Conversation } from "./Conversation.js";
|
|
4
|
+
import { MessageRole } from "./ConversationTurn.js";
|
|
5
|
+
import { adjustBrightness } from "../../../Theme/Theme.js";
|
|
6
|
+
import { AccordionLite } from "../../Common/AccordionLite.js";
|
|
6
7
|
// #endregion: Types
|
|
8
|
+
/**
|
|
9
|
+
* Helper function to convert from BaseMessage format used in persisted chat history to ConversationTurn format used for
|
|
10
|
+
* rendering the conversation.
|
|
11
|
+
* @param chatHistory
|
|
12
|
+
*/
|
|
13
|
+
const toTurns = (chatHistory) => chatHistory
|
|
14
|
+
.filter((message) => message.type === "human" || message.type === "ai")
|
|
15
|
+
.map((message) => {
|
|
16
|
+
const role = message.type === "human" ? MessageRole.User : MessageRole.Agent;
|
|
17
|
+
return {
|
|
18
|
+
id: message.id,
|
|
19
|
+
text: message.content.toString(),
|
|
20
|
+
role,
|
|
21
|
+
};
|
|
22
|
+
});
|
|
7
23
|
/**
|
|
8
24
|
* Component for displaying chat history from previous interactions with the agent.
|
|
9
25
|
*/
|
|
10
|
-
export const ChatHistory = ({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
}),
|
|
26
|
-
},
|
|
27
|
-
] }, chatHistoryKey));
|
|
26
|
+
export const ChatHistory = ({ id, messages }) => {
|
|
27
|
+
const theme = useTheme();
|
|
28
|
+
const turns = toTurns(messages);
|
|
29
|
+
const conversation = (_jsx(Conversation, { id: `${id}-conversation`,
|
|
30
|
+
// Pass "true" here to include final answers, which get persisted as agent
|
|
31
|
+
// messages in chat history.
|
|
32
|
+
includeAgentMessages: true, shouldWrapOutput: true, turns: turns }, `${id}-conversation`));
|
|
33
|
+
return (_jsx(AccordionLite, { id: `${id}-history-items`, items: conversation, contentSx: {
|
|
34
|
+
// Slightly darker background to differentiate from main chat
|
|
35
|
+
backgroundColor: adjustBrightness(theme.palette.background.paper, -5),
|
|
36
|
+
opacity: 0.5,
|
|
37
|
+
}, title: "Chat History" }));
|
|
38
|
+
};
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { FC
|
|
1
|
+
import { FC } from "react";
|
|
2
2
|
import { ConversationTurn } from "./ConversationTurn.js";
|
|
3
3
|
interface ConversationProps {
|
|
4
4
|
readonly id: string;
|
|
5
|
-
readonly
|
|
6
|
-
readonly finalAnswerRef?: Ref<HTMLDivElement>;
|
|
7
|
-
readonly userImage?: string;
|
|
8
|
-
readonly showThinking: boolean;
|
|
5
|
+
readonly includeAgentMessages?: boolean;
|
|
9
6
|
readonly shouldWrapOutput: boolean;
|
|
10
7
|
readonly turns: ConversationTurn[];
|
|
11
8
|
}
|
|
12
9
|
/**
|
|
10
|
+
* Component to render a conversation between the user and the agent.
|
|
13
11
|
*/
|
|
14
12
|
export declare const Conversation: FC<ConversationProps>;
|
|
15
13
|
export {};
|
|
@@ -1,63 +1,51 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import Box from "@mui/material/Box";
|
|
3
|
+
import { styled, useTheme } from "@mui/material/styles";
|
|
4
4
|
import { useMemo } from "react";
|
|
5
|
-
import ReactMarkdown from "react-markdown";
|
|
6
|
-
import SyntaxHighlighter from "react-syntax-highlighter";
|
|
7
|
-
import { AGENT_IMAGE } from "./Const.js";
|
|
8
5
|
import { MessageRole } from "./ConversationTurn.js";
|
|
9
6
|
import { FormattedMarkdown } from "./FormattedMarkdown.js";
|
|
10
7
|
import { HLJS_THEMES } from "./SyntaxHighlighterThemes.js";
|
|
11
|
-
import { UserQueryDisplay } from "./UserQueryDisplay.js";
|
|
12
|
-
import { MUIAccordion } from "../../Common/MUIAccordion.js";
|
|
13
8
|
import { MUIAlert } from "../../Common/MUIAlert.js";
|
|
14
9
|
const { atelierDuneDark, a11yLight } = HLJS_THEMES;
|
|
15
10
|
/**
|
|
16
|
-
*
|
|
17
|
-
* @param darkMode Whether the current theme is in dark mode. Used to determine syntax highlighter theme.
|
|
18
|
-
* @param shadowColor The color to use for shadows on the final answer accordion.
|
|
19
|
-
* @param shouldWrapOutput Whether to wrap long lines in the output. Passed down to the syntax highlighter component.
|
|
20
|
-
* @param turn The turn to render. @see ConversationTurn type for more info
|
|
21
|
-
* @returns A React component representing the "turn"
|
|
11
|
+
* Styled component for a user turn bubble.
|
|
22
12
|
*/
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
13
|
+
const UserTurnBubble = styled(Box)(({ theme }) => ({
|
|
14
|
+
backgroundColor: theme.palette.background.paper,
|
|
15
|
+
borderRadius: "1rem",
|
|
16
|
+
marginBottom: "1rem",
|
|
17
|
+
marginLeft: "auto",
|
|
18
|
+
overflowWrap: "anywhere",
|
|
19
|
+
paddingLeft: "1rem",
|
|
20
|
+
paddingRight: "1rem",
|
|
21
|
+
paddingTop: "0.5rem",
|
|
22
|
+
paddingBottom: "0.5rem",
|
|
23
|
+
whiteSpace: "pre-wrap",
|
|
24
|
+
width: "60%",
|
|
25
|
+
}));
|
|
26
|
+
/**
|
|
27
|
+
* Format the content of a conversation turn for display. Pretty-print JSON.
|
|
28
|
+
* @param turn The conversation turn to format.
|
|
29
|
+
* @returns The formatted content of the turn with JSON fences as appropriate.
|
|
30
|
+
*/
|
|
31
|
+
const formatTurnContent = (turn) => {
|
|
32
|
+
if (turn.text) {
|
|
33
|
+
return turn.text;
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
if (turn.structure != null) {
|
|
36
|
+
return `\`\`\`json\n${JSON.stringify(turn.structure, null, 2)}\n\`\`\``;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
return "";
|
|
37
40
|
}
|
|
38
|
-
const isFinalAnswer = turn.role === MessageRole.FinalAnswer;
|
|
39
|
-
const summary = isFinalAnswer ? "Final Answer" : (turn.agentName ?? "Agent");
|
|
40
|
-
return (_jsx(MUIAccordion, { id: `${turn.id}-panel`, defaultExpandedPanelKey: isFinalAnswer ? 1 : null, items: [
|
|
41
|
-
{
|
|
42
|
-
title: summary,
|
|
43
|
-
content: (_jsx("div", { id: turn.id, children: repairedJson ? (_jsx(SyntaxHighlighter, { id: "syntax-highlighter", language: "json", style: darkMode ? atelierDuneDark : a11yLight, showLineNumbers: false, wrapLongLines: shouldWrapOutput, children: repairedJson })) : (_jsx(ReactMarkdown, { children: turn.text })) })),
|
|
44
|
-
},
|
|
45
|
-
], sx: {
|
|
46
|
-
fontSize: "large",
|
|
47
|
-
marginBottom: "1rem",
|
|
48
|
-
boxShadow: isFinalAnswer
|
|
49
|
-
? `0 6px 16px 0 ${alpha(shadowColor, 0.08)}, 0 3px 6px -4px ${alpha(shadowColor, 0.12)},
|
|
50
|
-
0 9px 28px 8px ${alpha(shadowColor, 0.05)}`
|
|
51
|
-
: "none",
|
|
52
|
-
} }, turn.id));
|
|
53
41
|
};
|
|
54
42
|
/**
|
|
43
|
+
* Component to render a conversation between the user and the agent.
|
|
55
44
|
*/
|
|
56
|
-
export const Conversation = ({
|
|
45
|
+
export const Conversation = ({ id, includeAgentMessages = false, shouldWrapOutput, turns }) => {
|
|
57
46
|
// MUI theme
|
|
58
47
|
const theme = useTheme();
|
|
59
48
|
const darkMode = theme.palette.mode === "dark";
|
|
60
|
-
const shadowColor = darkMode ? theme.palette.common.white : theme.palette.common.black;
|
|
61
49
|
/**
|
|
62
50
|
* Render the list of conversation turns.
|
|
63
51
|
*/
|
|
@@ -65,22 +53,12 @@ export const Conversation = ({ currentUser, finalAnswerRef, id, shouldWrapOutput
|
|
|
65
53
|
switch (turn.role) {
|
|
66
54
|
case MessageRole.User:
|
|
67
55
|
return [
|
|
68
|
-
_jsx(
|
|
56
|
+
_jsx(UserTurnBubble, { id: turn.id, children: turn.text }, turn.id),
|
|
69
57
|
];
|
|
70
58
|
case MessageRole.Agent:
|
|
71
|
-
return
|
|
72
|
-
? [renderTurn(darkMode, shadowColor, shouldWrapOutput, turn)]
|
|
73
|
-
: [];
|
|
74
|
-
case MessageRole.AgentHeader:
|
|
75
|
-
return [
|
|
76
|
-
_jsx(UserQueryDisplay, { title: turn.agentName ?? "Agent", userImage: AGENT_IMAGE, userQuery: turn.agentDisplayName }, turn.id),
|
|
77
|
-
];
|
|
78
|
-
case MessageRole.LegacyAgent:
|
|
79
|
-
return [turn.text];
|
|
59
|
+
return includeAgentMessages ? [formatTurnContent(turn)] : [];
|
|
80
60
|
case MessageRole.FinalAnswer:
|
|
81
|
-
return [
|
|
82
|
-
_jsx("div", { id: "final-answer-div", ref: finalAnswerRef, style: { marginBottom: "1rem" }, children: renderTurn(darkMode, shadowColor, shouldWrapOutput, turn) }, turn.id),
|
|
83
|
-
];
|
|
61
|
+
return [formatTurnContent(turn)];
|
|
84
62
|
case MessageRole.Warning:
|
|
85
63
|
return [
|
|
86
64
|
_jsx(MUIAlert, { id: `warning-${turn.id}-alert`, severity: "warning", children: turn.text }, turn.id),
|
|
@@ -97,6 +75,6 @@ export const Conversation = ({ currentUser, finalAnswerRef, id, shouldWrapOutput
|
|
|
97
75
|
throw new Error(`Unhandled message role: ${_exhaustive}`);
|
|
98
76
|
}
|
|
99
77
|
}
|
|
100
|
-
}), [
|
|
78
|
+
}), [includeAgentMessages, turns]);
|
|
101
79
|
return (_jsx(FormattedMarkdown, { id: `${id}-conversation`, nodesList: nodesList, style: darkMode ? atelierDuneDark : a11yLight, wrapLongLines: shouldWrapOutput }));
|
|
102
80
|
};
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { ChatMessageType } from "../../../generated/neuro-san/NeuroSanClient.js";
|
|
2
|
+
/**
|
|
3
|
+
* The various messages roles in a conversation turn.
|
|
4
|
+
*/
|
|
2
5
|
export declare enum MessageRole {
|
|
3
|
-
"AgentHeader" = "AgentHeader",
|
|
4
6
|
"Agent" = "Agent",
|
|
5
7
|
"Error" = "Error",
|
|
6
8
|
"FinalAnswer" = "FinalAnswer",
|
|
7
|
-
"LegacyAgent" = "LegacyAgent",
|
|
8
9
|
"User" = "User",
|
|
9
10
|
"Warning" = "Warning"
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Represents a single turn in a conversation, which may include a message from the user or agent, or an error
|
|
14
|
+
*/
|
|
11
15
|
export interface ConversationTurn {
|
|
12
|
-
readonly agentName?: string;
|
|
13
16
|
readonly agentDisplayName?: string;
|
|
14
|
-
readonly
|
|
17
|
+
readonly agentName?: string;
|
|
15
18
|
readonly id: string;
|
|
16
19
|
readonly messageType?: ChatMessageType;
|
|
17
20
|
readonly role: MessageRole;
|
|
18
|
-
readonly
|
|
21
|
+
readonly structure?: Record<string, unknown>;
|
|
22
|
+
readonly text?: string;
|
|
19
23
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The various messages roles in a conversation turn.
|
|
3
|
+
*/
|
|
1
4
|
export var MessageRole;
|
|
2
5
|
(function (MessageRole) {
|
|
3
|
-
MessageRole["AgentHeader"] = "AgentHeader";
|
|
4
6
|
MessageRole["Agent"] = "Agent";
|
|
5
7
|
MessageRole["Error"] = "Error";
|
|
6
8
|
MessageRole["FinalAnswer"] = "FinalAnswer";
|
|
7
|
-
MessageRole["LegacyAgent"] = "LegacyAgent";
|
|
8
9
|
MessageRole["User"] = "User";
|
|
9
10
|
MessageRole["Warning"] = "Warning";
|
|
10
11
|
})(MessageRole || (MessageRole = {}));
|
|
@@ -14,11 +14,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
14
14
|
See the License for the specific language governing permissions and
|
|
15
15
|
limitations under the License.
|
|
16
16
|
*/
|
|
17
|
+
import Box from "@mui/material/Box";
|
|
17
18
|
import { Fragment } from "react";
|
|
18
19
|
import ReactMarkdown from "react-markdown";
|
|
19
20
|
import SyntaxHighlighter from "react-syntax-highlighter";
|
|
20
21
|
import rehypeRaw from "rehype-raw";
|
|
21
22
|
import rehypeSlug from "rehype-slug";
|
|
23
|
+
import remarkGfm from "remark-gfm";
|
|
22
24
|
import { hashString } from "../../../utils/text.js";
|
|
23
25
|
/**
|
|
24
26
|
* Format the output to ensure that text nodes are formatted as Markdown but other nodes are passed along as-is.
|
|
@@ -34,11 +36,11 @@ export const FormattedMarkdown = ({ id, nodesList, style, wrapLongLines = false,
|
|
|
34
36
|
* @param index The index of the string in the nodes list. Used as "salt" to generate a unique key.
|
|
35
37
|
* @returns The formatted Markdown.
|
|
36
38
|
*/
|
|
37
|
-
const getFormattedMarkdown = (stringToFormat, index) => (_jsx(ReactMarkdown, { rehypePlugins: [rehypeRaw, rehypeSlug], components: {
|
|
39
|
+
const getFormattedMarkdown = (stringToFormat, index) => (_jsx(ReactMarkdown, { rehypePlugins: [rehypeRaw, rehypeSlug], remarkPlugins: [remarkGfm], components: {
|
|
38
40
|
code: (codeProps) => {
|
|
39
41
|
const { children, className, ...rest } = codeProps;
|
|
40
42
|
const match = /language-(?<language>\w+)/u.exec(className || "");
|
|
41
|
-
return match ? (_jsx(SyntaxHighlighter, { id: `syntax-highlighter-${match.groups["language"]}`,
|
|
43
|
+
return match ? (_jsx(SyntaxHighlighter, { PreTag: "div", id: `syntax-highlighter-${match.groups["language"]}`, language: match.groups["language"], style: style, wrapLongLines: wrapLongLines, children: String(children).replace(/\n$/u, "") })) : (_jsx("code", { id: `code-${className}`, ...rest, className: className, style: wrapLongLines ? { whiteSpace: "pre-wrap", wordBreak: "break-word" } : {}, children: children }));
|
|
42
44
|
},
|
|
43
45
|
// Handle links specially since we want them to open in a new tab
|
|
44
46
|
a: ({ ...codeProps }) => (_jsx("a", { ...codeProps, id: "reference-link", target: "_blank", rel: "noopener noreferrer", children: codeProps.children })),
|
|
@@ -76,5 +78,5 @@ export const FormattedMarkdown = ({ id, nodesList, style, wrapLongLines = false,
|
|
|
76
78
|
const concatenatedText = getFormattedMarkdown(currentTextNodes.join(""), nodesList.length);
|
|
77
79
|
formattedOutput.push(concatenatedText);
|
|
78
80
|
}
|
|
79
|
-
return _jsx(
|
|
81
|
+
return _jsx(Box, { id: id, children: formattedOutput });
|
|
80
82
|
};
|
|
@@ -8,6 +8,9 @@ interface SampleQueriesProps {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Render sample queries as clickable chips. Agents may or may not have sample queries defined.
|
|
11
|
+
* @param disabled Whether the chips should be disabled or not. Chips should be disabled when the agent is
|
|
12
|
+
* thinking/responding to prevent multiple simultaneous queries being sent to the agent.
|
|
13
|
+
* @param handleSend Function to handle sending a query to the agent when a chip is clicked.
|
|
11
14
|
* @param sampleQueries The sample queries to render (from "connectivity" API)
|
|
12
15
|
* @returns A ReactNode representing the sample queries as clickable chips. If a user clicks a chip, it will
|
|
13
16
|
* send the query to the agent.
|
|
@@ -9,11 +9,14 @@ export const MAX_SAMPLE_QUERIES = 5;
|
|
|
9
9
|
export const QUERY_TRUNCATE_LENGTH = 80;
|
|
10
10
|
/**
|
|
11
11
|
* Render sample queries as clickable chips. Agents may or may not have sample queries defined.
|
|
12
|
+
* @param disabled Whether the chips should be disabled or not. Chips should be disabled when the agent is
|
|
13
|
+
* thinking/responding to prevent multiple simultaneous queries being sent to the agent.
|
|
14
|
+
* @param handleSend Function to handle sending a query to the agent when a chip is clicked.
|
|
12
15
|
* @param sampleQueries The sample queries to render (from "connectivity" API)
|
|
13
16
|
* @returns A ReactNode representing the sample queries as clickable chips. If a user clicks a chip, it will
|
|
14
17
|
* send the query to the agent.
|
|
15
18
|
*/
|
|
16
|
-
export const SampleQueries = ({ disabled, handleSend, sampleQueries }) => sampleQueries?.length > 0 ? (_jsx(Box, { id: "sample-queries-box", sx: { marginTop: "
|
|
19
|
+
export const SampleQueries = ({ disabled, handleSend, sampleQueries }) => sampleQueries?.length > 0 ? (_jsx(Box, { id: "sample-queries-box", sx: { marginTop: "1rem" }, children: sampleQueries.slice(0, MAX_SAMPLE_QUERIES).map((query) => {
|
|
17
20
|
const hashedQuery = hashString(query);
|
|
18
21
|
return (_jsx(Tooltip, { title: `Click to send query: "${query}"`, children: _jsx(Chip, { disabled: disabled, label: query.length > QUERY_TRUNCATE_LENGTH
|
|
19
22
|
? `${query.slice(0, QUERY_TRUNCATE_LENGTH)}...`
|
|
@@ -23,7 +26,7 @@ export const SampleQueries = ({ disabled, handleSend, sampleQueries }) => sample
|
|
|
23
26
|
color: "var(--bs-white)",
|
|
24
27
|
marginRight: "1rem",
|
|
25
28
|
marginBottom: "1rem",
|
|
26
|
-
backgroundColor: "var(--bs-
|
|
27
|
-
"&:hover": { backgroundColor: "var(--bs-
|
|
29
|
+
backgroundColor: "var(--bs-accent2-medium)",
|
|
30
|
+
"&:hover": { backgroundColor: "var(--bs-accent2-dark)" },
|
|
28
31
|
} }) }, `tooltip-${hashedQuery}`));
|
|
29
32
|
}) })) : null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import { ConversationTurn } from "./ConversationTurn.js";
|
|
3
|
+
interface ThinkingProps {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly turns: ConversationTurn[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Component to render the "thinking" section of the chat, which includes all messages from the agent that are
|
|
9
|
+
* of a type included in THINKING_MESSAGE_TYPES.
|
|
10
|
+
*/
|
|
11
|
+
export declare const Thinking: FC<ThinkingProps>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import Box from "@mui/material/Box";
|
|
3
|
+
import { isEmpty } from "lodash-es";
|
|
4
|
+
import { useMemo } from "react";
|
|
5
|
+
import ReactMarkdown from "react-markdown";
|
|
6
|
+
import { MessageRole } from "./ConversationTurn.js";
|
|
7
|
+
import { ChatMessageType } from "../../../generated/neuro-san/NeuroSanClient.js";
|
|
8
|
+
import { AccordionLite } from "../../Common/AccordionLite.js";
|
|
9
|
+
/**
|
|
10
|
+
* Set of message types that should be included in the "thinking" section.
|
|
11
|
+
*/
|
|
12
|
+
const THINKING_MESSAGE_TYPES = new Set([
|
|
13
|
+
ChatMessageType.AI,
|
|
14
|
+
ChatMessageType.AGENT,
|
|
15
|
+
ChatMessageType.AGENT_PROGRESS,
|
|
16
|
+
ChatMessageType.SYSTEM,
|
|
17
|
+
]);
|
|
18
|
+
const DEFAULT_AGENT_NAME = "Agent";
|
|
19
|
+
const formatTurn = (turn) => {
|
|
20
|
+
const headerLine = `**${turn.agentName ?? DEFAULT_AGENT_NAME}**: ${turn.text || ""}`;
|
|
21
|
+
const structureLine = turn.structure != null ? `\n\`${JSON.stringify(turn.structure, null, 2)}\`` : "";
|
|
22
|
+
return `${headerLine}${structureLine}`;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Component to render the "thinking" section of the chat, which includes all messages from the agent that are
|
|
26
|
+
* of a type included in THINKING_MESSAGE_TYPES.
|
|
27
|
+
*/
|
|
28
|
+
export const Thinking = ({ id, turns }) => {
|
|
29
|
+
const thinkingText = useMemo(() => {
|
|
30
|
+
// Find start of latest interaction (most recent user prompt)
|
|
31
|
+
const lastUserTurnIndex = turns.map((t) => t.role).lastIndexOf(MessageRole.User);
|
|
32
|
+
// Only include thinking emitted after latest user prompt
|
|
33
|
+
// Deliberately allow -1 result to fall through and include all thinking messages if there are no user turns
|
|
34
|
+
return turns
|
|
35
|
+
.slice(lastUserTurnIndex + 1)
|
|
36
|
+
.filter((turn) => THINKING_MESSAGE_TYPES.has(turn.messageType))
|
|
37
|
+
.filter((turn) => turn.text?.trim().length > 0 || !isEmpty(turn.structure))
|
|
38
|
+
.map((turn) => formatTurn(turn))
|
|
39
|
+
.join("\n\n");
|
|
40
|
+
}, [turns]);
|
|
41
|
+
return (thinkingText.length > 0 && (_jsx(AccordionLite, { id: `${id}-thinking`, items: _jsx(Box, { sx: {
|
|
42
|
+
marginLeft: "0.5rem",
|
|
43
|
+
fontSize: "smaller",
|
|
44
|
+
fontStyle: "italic",
|
|
45
|
+
}, children: _jsx(ReactMarkdown, { children: thinkingText }) }), contentSx: {
|
|
46
|
+
color: "text.secondary",
|
|
47
|
+
minWidth: 0,
|
|
48
|
+
padding: 0,
|
|
49
|
+
textTransform: "none",
|
|
50
|
+
}, title: "Show Thinking" })));
|
|
51
|
+
};
|
|
@@ -3,6 +3,6 @@ type LLMChatGroupConfigBtnProps = {
|
|
|
3
3
|
posRight?: number;
|
|
4
4
|
posBottom?: number;
|
|
5
5
|
};
|
|
6
|
-
export declare const LlmChatButton: import("@emotion/styled").StyledComponent<import("@mui/material/Button").ButtonOwnProps & Omit<import("@mui/material/ButtonBase").ButtonBaseOwnProps, keyof import("@mui/material/Button").ButtonOwnProps> & Omit<import("@mui/material/ButtonBase").ButtonBaseOwnProps, "tabIndex" | "type" | "action" | "centerRipple" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "nativeButton" | "onFocusVisible" | "TouchRippleProps" | "touchRippleRef" | keyof import("@mui/material/Button").ButtonOwnProps> & import("@mui/material/OverridableComponent").CommonProps & Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "style" | "
|
|
7
|
-
export declare const SmallLlmChatButton: import("@emotion/styled").StyledComponent<import("@mui/material/Button").ButtonOwnProps & Omit<import("@mui/material/ButtonBase").ButtonBaseOwnProps, keyof import("@mui/material/Button").ButtonOwnProps> & Omit<import("@mui/material/ButtonBase").ButtonBaseOwnProps, "tabIndex" | "type" | "action" | "centerRipple" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "nativeButton" | "onFocusVisible" | "TouchRippleProps" | "touchRippleRef" | keyof import("@mui/material/Button").ButtonOwnProps> & import("@mui/material/OverridableComponent").CommonProps & Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "style" | "
|
|
6
|
+
export declare const LlmChatButton: import("@emotion/styled").StyledComponent<import("@mui/material/Button").ButtonOwnProps & Omit<import("@mui/material/ButtonBase").ButtonBaseOwnProps, keyof import("@mui/material/Button").ButtonOwnProps> & Omit<import("@mui/material/ButtonBase").ButtonBaseOwnProps, "tabIndex" | "type" | "action" | "centerRipple" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "nativeButton" | "onFocusVisible" | "TouchRippleProps" | "touchRippleRef" | keyof import("@mui/material/Button").ButtonOwnProps> & import("@mui/material/OverridableComponent").CommonProps & Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "style" | "children" | "sx" | "className" | "tabIndex" | "color" | "href" | "type" | "classes" | "variant" | "action" | "size" | "disabled" | "centerRipple" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "nativeButton" | "onFocusVisible" | "TouchRippleProps" | "touchRippleRef" | "disableFocusRipple" | "loading" | "loadingIndicator" | "disableElevation" | "endIcon" | "fullWidth" | "loadingPosition" | "startIcon"> & import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme> & LLMChatGroupConfigBtnProps, {}, {}>;
|
|
7
|
+
export declare const SmallLlmChatButton: import("@emotion/styled").StyledComponent<import("@mui/material/Button").ButtonOwnProps & Omit<import("@mui/material/ButtonBase").ButtonBaseOwnProps, keyof import("@mui/material/Button").ButtonOwnProps> & Omit<import("@mui/material/ButtonBase").ButtonBaseOwnProps, "tabIndex" | "type" | "action" | "centerRipple" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "nativeButton" | "onFocusVisible" | "TouchRippleProps" | "touchRippleRef" | keyof import("@mui/material/Button").ButtonOwnProps> & import("@mui/material/OverridableComponent").CommonProps & Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "style" | "children" | "sx" | "className" | "tabIndex" | "color" | "href" | "type" | "classes" | "variant" | "action" | "size" | "disabled" | "centerRipple" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "nativeButton" | "onFocusVisible" | "TouchRippleProps" | "touchRippleRef" | "disableFocusRipple" | "loading" | "loadingIndicator" | "disableElevation" | "endIcon" | "fullWidth" | "loadingPosition" | "startIcon"> & import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme> & LLMChatGroupConfigBtnProps, {}, {}>;
|
|
8
8
|
export {};
|
|
@@ -6,12 +6,13 @@ export declare enum LegacyAgentType {
|
|
|
6
6
|
ChatBot = "ChatBot"
|
|
7
7
|
}
|
|
8
8
|
export declare const isLegacyAgentType: (agent: string) => boolean;
|
|
9
|
+
export declare const givesFinalAnswer: (agent: CombinedAgentType) => boolean;
|
|
9
10
|
export type CombinedAgentType = LegacyAgentType | string;
|
|
10
11
|
/**
|
|
11
12
|
* Models the error we receive from neuro-san agents.
|
|
12
13
|
*/
|
|
13
|
-
export
|
|
14
|
-
error: string;
|
|
15
|
-
traceback?: string;
|
|
16
|
-
tool?: string;
|
|
17
|
-
}
|
|
14
|
+
export type AgentErrorProps = {
|
|
15
|
+
readonly error: string;
|
|
16
|
+
readonly traceback?: string;
|
|
17
|
+
readonly tool?: string;
|
|
18
|
+
};
|
|
@@ -24,3 +24,8 @@ export var LegacyAgentType;
|
|
|
24
24
|
export const isLegacyAgentType = (agent) => {
|
|
25
25
|
return Object.keys(LegacyAgentType).includes(agent);
|
|
26
26
|
};
|
|
27
|
+
// Agents which are known to provide a predictable "Final Answer" response
|
|
28
|
+
export const givesFinalAnswer = (agent) => {
|
|
29
|
+
return (!isLegacyAgentType(agent) ||
|
|
30
|
+
[LegacyAgentType.ChatBot, LegacyAgentType.DMSChat].includes(agent));
|
|
31
|
+
};
|
|
@@ -2,7 +2,7 @@ import { ChatMessage, ChatMessageType } from "../../../generated/neuro-san/Neuro
|
|
|
2
2
|
export declare const KNOWN_MESSAGE_TYPES: ChatMessageType[];
|
|
3
3
|
export declare const KNOWN_MESSAGE_TYPES_FOR_PLASMA: ChatMessageType[];
|
|
4
4
|
export declare const chatMessageFromChunk: (chunk: string) => ChatMessage | null;
|
|
5
|
-
export declare const checkError: (chatMessageJson:
|
|
5
|
+
export declare const checkError: (chatMessageJson: Record<string, unknown>) => string | null;
|
|
6
6
|
export declare const removeTrailingUuid: (agentName: string) => string;
|
|
7
7
|
/**
|
|
8
8
|
* Convert FOO_BAR to more human "Foo Bar".
|
|
@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
|
+
import { isEmpty } from "lodash-es";
|
|
16
17
|
import startCase from "lodash-es/startCase.js";
|
|
17
18
|
import { ChatMessageType } from "../../../generated/neuro-san/NeuroSanClient.js";
|
|
18
19
|
// We ignore any messages that are not of these types
|
|
@@ -43,16 +44,21 @@ export const chatMessageFromChunk = (chunk) => {
|
|
|
43
44
|
}
|
|
44
45
|
return chatResponse.response;
|
|
45
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Type guard to check if a value has the shape of an AgentErrorProps, which indicates it's an error message from
|
|
49
|
+
* an agent. Note: not all Neuro-san agents follow this convention.
|
|
50
|
+
* @param value The value to check.
|
|
51
|
+
* @returns True if the value is an AgentErrorProps, false otherwise.
|
|
52
|
+
*/
|
|
53
|
+
const isAgentErrorLike = (value) => {
|
|
54
|
+
return typeof value?.error === "string";
|
|
55
|
+
};
|
|
46
56
|
export const checkError = (chatMessageJson) => {
|
|
47
|
-
if (chatMessageJson
|
|
48
|
-
const agentError = chatMessageJson;
|
|
49
|
-
return (`Error occurred. Error: "${agentError.error}", ` +
|
|
50
|
-
`traceback: "${agentError?.traceback}", ` +
|
|
51
|
-
`tool: "${agentError?.tool}"`);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
57
|
+
if (isEmpty(chatMessageJson) || !isAgentErrorLike(chatMessageJson)) {
|
|
54
58
|
return null;
|
|
55
59
|
}
|
|
60
|
+
const { error, traceback, tool } = chatMessageJson;
|
|
61
|
+
return `Error occurred. Error: "${error}", traceback: "${traceback}", tool: "${tool}"`;
|
|
56
62
|
};
|
|
57
63
|
export const removeTrailingUuid = (agentName) => {
|
|
58
64
|
return agentName?.replace(/-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/u, "");
|
|
@@ -29,7 +29,7 @@ import { LegacyAgentType } from "../AgentChat/Common/Types.js";
|
|
|
29
29
|
* Site-wide Chatbot component.
|
|
30
30
|
*/
|
|
31
31
|
// Temporarily disabled but will be used once we migrated the backend to use Neuro-san RAG.
|
|
32
|
-
export const ChatBot = ({ id,
|
|
32
|
+
export const ChatBot = ({ id, pageContext }) => {
|
|
33
33
|
const [chatOpen, setChatOpen] = useState(false);
|
|
34
34
|
const [isAwaitingLlm, setIsAwaitingLlm] = useState(false);
|
|
35
35
|
const { user: { name: currentUser }, } = useAuthentication().data;
|
|
@@ -51,7 +51,7 @@ export const ChatBot = ({ id, userAvatar, pageContext }) => {
|
|
|
51
51
|
borderWidth: 1,
|
|
52
52
|
borderColor: darkMode ? "var(--bs-white)" : "var(--bs-gray-light)",
|
|
53
53
|
zIndex: getZIndex(2, theme),
|
|
54
|
-
}, children: _jsx(ChatCommon, { id: "chatbot-window", currentUser: currentUser, setIsAwaitingLlm: setIsAwaitingLlm, isAwaitingLlm: isAwaitingLlm,
|
|
54
|
+
}, children: _jsx(ChatCommon, { id: "chatbot-window", currentUser: currentUser, setIsAwaitingLlm: setIsAwaitingLlm, isAwaitingLlm: isAwaitingLlm, selectedNetwork: LegacyAgentType.ChatBot, legacyAgentEndpoint: CHATBOT_ENDPOINT, extraParams: { pageContext }, backgroundColor: darkMode ? "var(--bs-gray-dark)" : "var(--bs-tertiary-blue)", title: "Cognizant Neuro AI Assistant", onClose: () => setChatOpen(false) }) }) }), !chatOpen && (_jsx(Box, { id: `chatbot-icon-${id}`, sx: {
|
|
55
55
|
display: "flex",
|
|
56
56
|
alignItems: "center",
|
|
57
57
|
backgroundColor: darkMode ? "var(--bs-dark-mode-dim)" : "var(--bs-white)",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SxProps } from "@mui/material/styles";
|
|
2
|
+
import { FC, ReactNode } from "react";
|
|
3
|
+
interface AccordionLiteProps {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly items: ReactNode;
|
|
6
|
+
readonly contentSx?: SxProps;
|
|
7
|
+
readonly title: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A lightweight accordion component that can be used to show/hide content.
|
|
11
|
+
* @see {MUIAccordion} for a more heavyweight accordion.
|
|
12
|
+
*/
|
|
13
|
+
export declare const AccordionLite: FC<AccordionLiteProps>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
3
|
+
import Box from "@mui/material/Box";
|
|
4
|
+
import Button from "@mui/material/Button";
|
|
5
|
+
import Collapse from "@mui/material/Collapse";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
/**
|
|
8
|
+
* A lightweight accordion component that can be used to show/hide content.
|
|
9
|
+
* @see {MUIAccordion} for a more heavyweight accordion.
|
|
10
|
+
*/
|
|
11
|
+
export const AccordionLite = ({ id, items, contentSx, title }) => {
|
|
12
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
13
|
+
return (_jsxs(Box, { id: id, children: [_jsx(Button, { onClick: () => setIsExpanded((prev) => !prev), startIcon: _jsx(ExpandMoreIcon, { sx: {
|
|
14
|
+
fontSize: "1rem",
|
|
15
|
+
transform: isExpanded ? "rotate(0deg)" : "rotate(270deg)",
|
|
16
|
+
transition: "transform 150ms ease",
|
|
17
|
+
} }), sx: {
|
|
18
|
+
"&:hover": { backgroundColor: "transparent", textDecoration: "underline" },
|
|
19
|
+
color: "text.secondary",
|
|
20
|
+
fontSize: "smaller",
|
|
21
|
+
minWidth: 0,
|
|
22
|
+
padding: 0,
|
|
23
|
+
textTransform: "none",
|
|
24
|
+
}, children: title }), _jsx(Collapse, { id: `${id}-items`, in: isExpanded, timeout: "auto", unmountOnExit: false, children: _jsx(Box, { sx: [...(Array.isArray(contentSx) ? contentSx : [contentSx])], children: items }) })] }));
|
|
25
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FC, ReactNode } from "react";
|
|
2
|
-
export declare const StyledButton: import("@emotion/styled").StyledComponent<import("@mui/material/Button").ButtonOwnProps & Omit<import("@mui/material/ButtonBase").ButtonBaseOwnProps, keyof import("@mui/material/Button").ButtonOwnProps> & Omit<import("@mui/material/ButtonBase").ButtonBaseOwnProps, "tabIndex" | "type" | "action" | "centerRipple" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "nativeButton" | "onFocusVisible" | "TouchRippleProps" | "touchRippleRef" | keyof import("@mui/material/Button").ButtonOwnProps> & import("@mui/material/OverridableComponent").CommonProps & Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "style" | "
|
|
2
|
+
export declare const StyledButton: import("@emotion/styled").StyledComponent<import("@mui/material/Button").ButtonOwnProps & Omit<import("@mui/material/ButtonBase").ButtonBaseOwnProps, keyof import("@mui/material/Button").ButtonOwnProps> & Omit<import("@mui/material/ButtonBase").ButtonBaseOwnProps, "tabIndex" | "type" | "action" | "centerRipple" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "nativeButton" | "onFocusVisible" | "TouchRippleProps" | "touchRippleRef" | keyof import("@mui/material/Button").ButtonOwnProps> & import("@mui/material/OverridableComponent").CommonProps & Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "style" | "children" | "sx" | "className" | "tabIndex" | "color" | "href" | "type" | "classes" | "variant" | "action" | "size" | "disabled" | "centerRipple" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "nativeButton" | "onFocusVisible" | "TouchRippleProps" | "touchRippleRef" | "disableFocusRipple" | "loading" | "loadingIndicator" | "disableElevation" | "endIcon" | "fullWidth" | "loadingPosition" | "startIcon"> & import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme>, {}, {}>;
|
|
3
3
|
interface ConfirmationModalProps {
|
|
4
4
|
cancelBtnLabel?: string;
|
|
5
5
|
closeable?: boolean;
|
|
@@ -43,7 +43,5 @@ export const CustomerLogo = ({ fallbackElement, logoServiceToken }) => {
|
|
|
43
43
|
const logoUrl = logoServiceToken && customer?.trim().length > 0
|
|
44
44
|
? `https://img.logo.dev/name/${encodeURIComponent(customer)}?token=${logoServiceToken}&theme=dark&format=png&size=75`
|
|
45
45
|
: null;
|
|
46
|
-
return logoUrl ? (
|
|
47
|
-
// eslint-disable-next-line @next/next/no-img-element
|
|
48
|
-
_jsx("img", { src: logoUrl, alt: `${customer} Logo`, width: 40, height: 40, style: { borderRadius: "50%" } })) : (fallbackElement);
|
|
46
|
+
return logoUrl ? (_jsx("img", { src: logoUrl, alt: `${customer} Logo`, width: 40, height: 40, style: { borderRadius: "50%" } })) : (fallbackElement);
|
|
49
47
|
};
|