From 4db953f3eb526391b300940534b2b00d2b3e4b1f Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Fri, 13 Jun 2025 13:36:34 +0200 Subject: [PATCH 1/6] Add incident manual creation chat --- .../IncidentAgentChat/index.tsx | 40 +++++ .../Agentic/IncidentDetails/index.tsx | 4 +- .../AddMCPServerDialog/index.tsx | 19 +-- .../CreateIncidentChatOverlay/index.tsx | 147 ++++++++++++++++++ .../CreateIncidentChatOverlay/styles.ts | 15 ++ .../CreateIncidentChatOverlay/types.ts | 10 ++ .../Agentic/IncidentsContainer/index.tsx | 19 ++- src/components/Agentic/Sidebar/index.tsx | 18 ++- src/components/Agentic/Sidebar/styles.ts | 6 + .../Chat => common/AgentChat}/index.tsx | 96 +++++------- .../Agentic/common/AgentChat/styles.ts | 14 ++ .../Agentic/common/AgentChat/types.ts | 8 + src/components/Agentic/common/Chat/index.tsx | 48 ++++++ .../Chat/styles.ts | 7 - src/components/Agentic/common/Chat/types.ts | 10 ++ .../Agentic/common/Dialog/index.tsx | 18 +++ .../Agentic/common/Dialog/styles.ts | 34 ++++ src/components/Agentic/common/Dialog/types.ts | 7 + .../Agentic/common/PromptInput/index.tsx | 8 +- .../Agentic/common/PromptInput/styles.ts | 4 +- .../Agentic/common/PromptInput/types.ts | 2 + src/components/Agentic/tracking.ts | 7 +- src/redux/services/digma.ts | 31 ++-- src/redux/services/types.ts | 17 +- src/redux/slices/incidentsSlice.ts | 13 +- 25 files changed, 490 insertions(+), 112 deletions(-) create mode 100644 src/components/Agentic/IncidentDetails/IncidentAgentChat/index.tsx create mode 100644 src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx create mode 100644 src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/styles.ts create mode 100644 src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/types.ts rename src/components/Agentic/{IncidentDetails/Chat => common/AgentChat}/index.tsx (60%) create mode 100644 src/components/Agentic/common/AgentChat/styles.ts create mode 100644 src/components/Agentic/common/AgentChat/types.ts create mode 100644 src/components/Agentic/common/Chat/index.tsx rename src/components/Agentic/{IncidentDetails => common}/Chat/styles.ts (73%) create mode 100644 src/components/Agentic/common/Chat/types.ts create mode 100644 src/components/Agentic/common/Dialog/index.tsx create mode 100644 src/components/Agentic/common/Dialog/styles.ts create mode 100644 src/components/Agentic/common/Dialog/types.ts diff --git a/src/components/Agentic/IncidentDetails/IncidentAgentChat/index.tsx b/src/components/Agentic/IncidentDetails/IncidentAgentChat/index.tsx new file mode 100644 index 000000000..07d20a0ca --- /dev/null +++ b/src/components/Agentic/IncidentDetails/IncidentAgentChat/index.tsx @@ -0,0 +1,40 @@ +import { useParams } from "react-router"; +import { useStableSearchParams } from "../../../../hooks/useStableSearchParams"; +import { useSendMessageToIncidentAgentChatMutation } from "../../../../redux/services/digma"; +import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; +import { AgentChat } from "../../common/AgentChat"; +import { trackingEvents } from "../../tracking"; + +export const IncidentAgentChat = () => { + const params = useParams(); + const incidentId = params.id; + const [searchParams] = useStableSearchParams(); + const agentId = searchParams.get("agent"); + + const [sendMessage, { isLoading: isMessageSending }] = + useSendMessageToIncidentAgentChatMutation(); + + const handleMessageSend = (text: string) => { + sendUserActionTrackingEvent( + trackingEvents.INCIDENT_AGENT_MESSAGE_SUBMITTED, + { + agentName: agentId ?? "" + } + ); + + void sendMessage({ + incidentId: incidentId ?? "", + agentId: agentId ?? "", + data: { text } + }); + }; + + return ( + + ); +}; diff --git a/src/components/Agentic/IncidentDetails/index.tsx b/src/components/Agentic/IncidentDetails/index.tsx index ac95de3a4..4d8957a4d 100644 --- a/src/components/Agentic/IncidentDetails/index.tsx +++ b/src/components/Agentic/IncidentDetails/index.tsx @@ -18,7 +18,7 @@ import { trackingEvents } from "../tracking"; import { AdditionalInfo } from "./AdditionalInfo"; import { AgentEvents } from "./AgentEvents"; import { AgentFlowChart } from "./AgentFlowChart"; -import { Chat } from "./Chat"; +import { IncidentAgentChat } from "./IncidentAgentChat"; import { IncidentMetaData } from "./IncidentMetaData"; import * as s from "./styles"; import type { AgentViewMode } from "./types"; @@ -185,7 +185,7 @@ export const IncidentDetails = () => { {agentId ? ( agentViewMode === "chat" ? ( - + ) : ( ) diff --git a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/index.tsx b/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/index.tsx index 10e4e1972..30bd5936f 100644 --- a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/index.tsx +++ b/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/index.tsx @@ -1,9 +1,8 @@ import { useState } from "react"; import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; -import { CrossIcon } from "../../../common/icons/12px/CrossIcon"; +import { Dialog } from "../../common/Dialog"; import { trackingEvents } from "../../tracking"; import { ServerStep } from "./ServerStep"; -import * as s from "./styles"; import { ToolsStep } from "./ToolsStep"; import type { AddMCPServerDialogProps } from "./types"; @@ -45,24 +44,16 @@ export const AddMCPServerDialog = ({ /> ]; - const handleCloseButtonClick = () => { + const handleDialogClose = () => { sendUserActionTrackingEvent( - trackingEvents.INCIDENT_TEMPLATE_ADD_MCP_DIALOG_CLOSE_BUTTON_CLICKED + trackingEvents.INCIDENT_TEMPLATE_ADD_MCP_DIALOG_CLOSED ); onClose(); }; return ( - - - - Wizard - - - - - + {steps[currentStep]} - + ); }; diff --git a/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx b/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx new file mode 100644 index 000000000..a722fdf39 --- /dev/null +++ b/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx @@ -0,0 +1,147 @@ +import { fetchEventSource } from "@microsoft/fetch-event-source"; +import { useEffect, useRef, useState } from "react"; +import { useAgenticDispatch } from "../../../../containers/Agentic/hooks"; +import { useSendMessageToIncidentCreationChatMutation } from "../../../../redux/services/digma"; +import { setIsCreateIncidentChatOpen } from "../../../../redux/slices/incidentsSlice"; +import { isString } from "../../../../typeGuards/isString"; +import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; +import { CancelConfirmation } from "../../../common/CancelConfirmation"; +import { Dialog } from "../../common/Dialog"; +import { trackingEvents } from "../../tracking"; +import * as s from "./styles"; + +const AGENT_ID = "incident_entry"; +const PROMPT_FONT_SIZE = 14; // in pixels + +export const CreateIncidentChatOverlay = () => { + const [incidentId, setIncidentId] = useState(); + const [ + isCloseConfirmationDialogVisible, + setIsCloseConfirmationDialogVisible + ] = useState(false); + const [isStartMessageSending, setIsStartMessageSending] = useState(false); + const abortControllerRef = useRef(null); + + const dispatch = useAgenticDispatch(); + + const [sendMessage, { isLoading: isMessageSending }] = + useSendMessageToIncidentCreationChatMutation(); + + const handleCreateIncidentChatMessageSend = (text: string) => { + // Send first message to start the incident creation chat + if (!incidentId) { + // Stop any existing connection + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + + abortControllerRef.current = new AbortController(); + + setIsStartMessageSending(true); + void fetchEventSource( + `${ + isString(window.digmaApiProxyPrefix) + ? window.digmaApiProxyPrefix + : "/api/" + }Agentic/incident-entry`, + { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + text + }), + onopen: (response: Response) => { + if (response.ok) { + setIncidentId( + response.headers.get("agentic-conversation-id") ?? "" + ); + setIsStartMessageSending(false); + return Promise.resolve(); + } else { + setIsStartMessageSending(false); + return Promise.reject( + new Error(`HTTP ${response.status}: ${response.statusText}`) + ); + } + }, + onerror: (err: unknown) => { + abortControllerRef.current = null; + setIsStartMessageSending(false); + if (err instanceof Error) { + // eslint-disable-next-line no-console + console.error("Error starting incident creation chat:", err); + } else { + // eslint-disable-next-line no-console + console.error("Unknown error starting incident creation chat"); + } + } + } + ); + } + + // Send subsequent messages to the incident creation chat + if (incidentId) { + void sendMessage({ + incidentId, + data: { text } + }); + } + }; + + const handleCreateIncidentChatDialogClose = () => { + setIsCloseConfirmationDialogVisible(true); + }; + + const handleCloseConfirmationDialogClose = () => { + setIsCloseConfirmationDialogVisible(false); + }; + + const handleCloseConfirmationDialogConfirm = () => { + sendUserActionTrackingEvent( + trackingEvents.INCIDENT_CREATION_CHAT_DIALOG_CLOSED + ); + setIsCloseConfirmationDialogVisible(false); + dispatch(setIsCreateIncidentChatOpen(false)); + }; + + useEffect(() => { + return () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }; + }, []); + + return ( + <> + + + + + + {isCloseConfirmationDialogVisible && ( + + + + )} + + ); +}; diff --git a/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/styles.ts b/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/styles.ts new file mode 100644 index 000000000..342d9dac4 --- /dev/null +++ b/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/styles.ts @@ -0,0 +1,15 @@ +import styled from "styled-components"; +import { Overlay } from "../../../common/Overlay"; +import { AgentChat } from "../../common/AgentChat"; + +export const StyledOverlay = styled(Overlay)` + align-items: center; +`; + +export const StyledAgentChat = styled(AgentChat)` + ${/* TODO: change to color from the theme */ ""} + background: #000; + border-radius: 8px; + padding: 24px; + gap: 12px; +`; diff --git a/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/types.ts b/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/types.ts new file mode 100644 index 000000000..c1c7d30b1 --- /dev/null +++ b/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/types.ts @@ -0,0 +1,10 @@ +export interface OverlayProps { + $transitionDuration: number; + $transitionClassName: string; + $isVisible: boolean; +} + +export interface PopupContainerProps { + $transitionDuration: number; + $transitionClassName: string; +} diff --git a/src/components/Agentic/IncidentsContainer/index.tsx b/src/components/Agentic/IncidentsContainer/index.tsx index 689313a49..365986d1c 100644 --- a/src/components/Agentic/IncidentsContainer/index.tsx +++ b/src/components/Agentic/IncidentsContainer/index.tsx @@ -1,8 +1,17 @@ import { Outlet } from "react-router"; +import { useAgenticSelector } from "../../../containers/Agentic/hooks"; +import { CreateIncidentChatOverlay } from "./CreateIncidentChatOverlay"; import * as s from "./styles"; -export const IncidentsContainer = () => ( - - - -); +export const IncidentsContainer = () => { + const isCreateIncidentChatOpen = useAgenticSelector( + (state) => state.incidents.isCreateIncidentChatOpen + ); + + return ( + + + {isCreateIncidentChatOpen && } + + ); +}; diff --git a/src/components/Agentic/Sidebar/index.tsx b/src/components/Agentic/Sidebar/index.tsx index 0beb72041..41a8ed0b7 100644 --- a/src/components/Agentic/Sidebar/index.tsx +++ b/src/components/Agentic/Sidebar/index.tsx @@ -2,14 +2,19 @@ import { usePostHog } from "posthog-js/react"; import { useEffect, useMemo, useState } from "react"; import { useLocation, useNavigate, useParams } from "react-router"; import { useTheme } from "styled-components"; -import { useAgenticSelector } from "../../../containers/Agentic/hooks"; +import { + useAgenticDispatch, + useAgenticSelector +} from "../../../containers/Agentic/hooks"; import { useLogoutMutation } from "../../../redux/services/auth"; import { useGetIncidentsQuery } from "../../../redux/services/digma"; import type { IncidentResponseItem } from "../../../redux/services/types"; +import { setIsCreateIncidentChatOpen } from "../../../redux/slices/incidentsSlice"; import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; import { getThemeKind } from "../../common/App/styles"; import { LogoutIcon } from "../../common/icons/16px/LogoutIcon"; import { NewPopover } from "../../common/NewPopover"; +import { NewButton } from "../../common/v3/NewButton"; import { Tooltip } from "../../common/v3/Tooltip"; import { MenuList } from "../../Navigation/common/MenuList"; import { Popup } from "../../Navigation/common/Popup"; @@ -31,6 +36,7 @@ export const Sidebar = () => { const [isUserMenuOpen, setIsUserMenuOpen] = useState(false); const posthog = usePostHog(); const location = useLocation(); + const dispatch = useAgenticDispatch(); const { data } = useGetIncidentsQuery(undefined, { pollingInterval: REFRESH_INTERVAL @@ -49,6 +55,11 @@ export const Sidebar = () => { void navigate(`/incidents/${id}`); }; + const handleCreateButtonClick = () => { + sendUserActionTrackingEvent(trackingEvents.SIDEBAR_CREATE_BUTTON_CLICKED); + dispatch(setIsCreateIncidentChatOpen(true)); + }; + const handleTemplateButtonClick = () => { sendUserActionTrackingEvent(trackingEvents.SIDEBAR_TEMPLATE_BUTTON_CLICKED); void navigate("/incidents/template"); @@ -111,7 +122,10 @@ export const Sidebar = () => { /> - Incidents + + Incidents + + {sortedIncidents.map((incident) => ( theme.colors.v3.text.primary}; diff --git a/src/components/Agentic/IncidentDetails/Chat/index.tsx b/src/components/Agentic/common/AgentChat/index.tsx similarity index 60% rename from src/components/Agentic/IncidentDetails/Chat/index.tsx rename to src/components/Agentic/common/AgentChat/index.tsx index f97303472..4e6127589 100644 --- a/src/components/Agentic/IncidentDetails/Chat/index.tsx +++ b/src/components/Agentic/common/AgentChat/index.tsx @@ -1,69 +1,53 @@ import { Fragment, useEffect, useMemo, useState } from "react"; -import { useParams } from "react-router"; -import { useStableSearchParams } from "../../../../hooks/useStableSearchParams"; -import { - useGetIncidentAgentChatEventsQuery, - useSendMessageToIncidentAgentChatMutation -} from "../../../../redux/services/digma"; +import { Link } from "react-router"; +import { useGetIncidentAgentChatEventsQuery } from "../../../../redux/services/digma"; import type { IncidentAgentChatEvent } from "../../../../redux/services/types"; import { isNumber } from "../../../../typeGuards/isNumber"; import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; -import { ThreeCirclesSpinner } from "../../../common/ThreeCirclesSpinner"; -import { Spinner } from "../../../common/v3/Spinner"; -import { PromptInput } from "../../common/PromptInput"; +import { Chat } from "../../common/Chat"; +import { Accordion } from "../../IncidentDetails/AgentEvents/Accordion"; +import { TypingMarkdown } from "../../IncidentDetails/TypingMarkdown"; +import { convertToMarkdown } from "../../IncidentDetails/utils/convertToMarkdown"; import { trackingEvents } from "../../tracking"; -import { Accordion } from "../AgentEvents/Accordion"; -import { TypingMarkdown } from "../TypingMarkdown"; -import { useAutoScroll } from "../useAutoScroll"; -import { convertToMarkdown } from "../utils/convertToMarkdown"; import * as s from "./styles"; +import type { AgentChatProps } from "./types"; const REFRESH_INTERVAL = 10 * 1000; // in milliseconds const REFRESH_INTERVAL_DURING_STREAMING = 3 * 1000; // in milliseconds const TYPING_SPEED = 3; // in milliseconds per character -export const Chat = () => { - const [inputValue, setInputValue] = useState(""); - const params = useParams(); - const incidentId = params.id; - const [searchParams] = useStableSearchParams(); - const agentId = searchParams.get("agent"); - const { elementRef, handleElementScroll, scrollToBottom } = - useAutoScroll(); +export const AgentChat = ({ + incidentId, + agentId, + onMessageSend, + isMessageSending, + className +}: AgentChatProps) => { const [initialEventsCount, setInitialEventsCount] = useState(); const [eventsVisibleCount, setEventsVisibleCount] = useState(); - const [sendMessage, { isLoading: isMessageSending }] = - useSendMessageToIncidentAgentChatMutation(); - const { data, isLoading } = useGetIncidentAgentChatEventsQuery( { incidentId: incidentId ?? "", agentId: agentId ?? "" }, { - skip: !incidentId || !agentId!, + skip: !incidentId || !agentId, pollingInterval: isMessageSending ? REFRESH_INTERVAL_DURING_STREAMING : REFRESH_INTERVAL } ); - const handleInputSubmit = () => { + const handleMessageSend = (text: string) => { sendUserActionTrackingEvent( trackingEvents.INCIDENT_AGENT_MESSAGE_SUBMITTED, { agentName: agentId ?? "" } ); - setInputValue(""); - scrollToBottom(); - void sendMessage({ - incidentId: incidentId ?? "", - agentId: agentId ?? "", - data: { text: inputValue } - }); + onMessageSend(text); }; const handleMarkdownTypingComplete = (i: number) => () => { @@ -127,32 +111,36 @@ export const Chat = () => { ); case "human": return {event.message}; - + case "agent_end": { + if (event.agent_name === "incident_entry") { + return ( + + New incident has been created.{" "} + View + + ); + } + break; + } default: return null; } }; return ( - - - {!data && isLoading && ( - - - - )} - {visibleEvents?.map((x, i) => ( - {renderChatEvent(x, i)} - ))} - {isMessageSending && } - - - + + {visibleEvents?.map((x, i) => ( + {renderChatEvent(x, i)} + ))} + + } + /> ); }; diff --git a/src/components/Agentic/common/AgentChat/styles.ts b/src/components/Agentic/common/AgentChat/styles.ts new file mode 100644 index 000000000..64085ca00 --- /dev/null +++ b/src/components/Agentic/common/AgentChat/styles.ts @@ -0,0 +1,14 @@ +import styled from "styled-components"; +import { subheading1RegularTypography } from "../../../common/App/typographies"; + +export const HumanMessage = styled.div` + background: ${({ theme }) => theme.colors.v3.surface.highlight}; + padding: 8px; + border-radius: 8px; + align-self: flex-end; +`; + +export const AgentMessage = styled.div` + ${subheading1RegularTypography} + color: ${({ theme }) => theme.colors.v3.text.secondary}; +`; diff --git a/src/components/Agentic/common/AgentChat/types.ts b/src/components/Agentic/common/AgentChat/types.ts new file mode 100644 index 000000000..7549ee4cf --- /dev/null +++ b/src/components/Agentic/common/AgentChat/types.ts @@ -0,0 +1,8 @@ +export interface AgentChatProps { + incidentId?: string; + agentId?: string; + onMessageSend: (text: string) => void; + isMessageSending: boolean; + className?: string; + promptFontSize?: number; +} diff --git a/src/components/Agentic/common/Chat/index.tsx b/src/components/Agentic/common/Chat/index.tsx new file mode 100644 index 000000000..406b13d87 --- /dev/null +++ b/src/components/Agentic/common/Chat/index.tsx @@ -0,0 +1,48 @@ +import { useState } from "react"; +import { ThreeCirclesSpinner } from "../../../common/ThreeCirclesSpinner"; +import { Spinner } from "../../../common/v3/Spinner"; +import { useAutoScroll } from "../../IncidentDetails/useAutoScroll"; +import { PromptInput } from "../PromptInput"; +import * as s from "./styles"; +import type { ChatProps } from "./types"; + +export const Chat = ({ + isInitialLoading, + isMessageSending, + onMessageSend, + chatContent, + className, + promptFontSize +}: ChatProps) => { + const [inputValue, setInputValue] = useState(""); + const { elementRef, handleElementScroll, scrollToBottom } = + useAutoScroll(); + + const handleInputSubmit = () => { + setInputValue(""); + scrollToBottom(); + onMessageSend(inputValue); + }; + + return ( + + + {isInitialLoading && ( + + + + )} + {chatContent} + {isMessageSending && } + + + + ); +}; diff --git a/src/components/Agentic/IncidentDetails/Chat/styles.ts b/src/components/Agentic/common/Chat/styles.ts similarity index 73% rename from src/components/Agentic/IncidentDetails/Chat/styles.ts rename to src/components/Agentic/common/Chat/styles.ts index 4c00eb705..4bfa2103d 100644 --- a/src/components/Agentic/IncidentDetails/Chat/styles.ts +++ b/src/components/Agentic/common/Chat/styles.ts @@ -18,13 +18,6 @@ export const ChatHistory = styled.div` padding: 8px; `; -export const HumanMessage = styled.div` - background: ${({ theme }) => theme.colors.v3.surface.highlight}; - padding: 8px; - border-radius: 8px; - align-self: flex-end; -`; - export const LoadingContainer = styled.div` display: flex; justify-content: center; diff --git a/src/components/Agentic/common/Chat/types.ts b/src/components/Agentic/common/Chat/types.ts new file mode 100644 index 000000000..b1550ec4d --- /dev/null +++ b/src/components/Agentic/common/Chat/types.ts @@ -0,0 +1,10 @@ +import type { ReactNode } from "react"; + +export interface ChatProps { + isInitialLoading: boolean; + isMessageSending: boolean; + onMessageSend: (text: string) => void; + chatContent: ReactNode; + className?: string; + promptFontSize?: number; // in pixels +} diff --git a/src/components/Agentic/common/Dialog/index.tsx b/src/components/Agentic/common/Dialog/index.tsx new file mode 100644 index 000000000..2669b5383 --- /dev/null +++ b/src/components/Agentic/common/Dialog/index.tsx @@ -0,0 +1,18 @@ +import { isString } from "../../../../typeGuards/isString"; +import { CrossIcon } from "../../../common/icons/12px/CrossIcon"; +import * as s from "./styles"; +import type { DialogProps } from "./types"; + +export const Dialog = ({ title, onClose, children }: DialogProps) => ( + + + + {isString(title) ? title : null} + + + + + + {children} + +); diff --git a/src/components/Agentic/common/Dialog/styles.ts b/src/components/Agentic/common/Dialog/styles.ts new file mode 100644 index 000000000..f0ea0331e --- /dev/null +++ b/src/components/Agentic/common/Dialog/styles.ts @@ -0,0 +1,34 @@ +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + width: 635px; + height: 420px; + padding: 12px; + gap: 16px; + border-radius: 4px; + background: ${({ theme }) => theme.colors.v3.surface.primary}; +`; + +export const Header = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + color: ${({ theme }) => theme.colors.v3.text.primary}; + ${/* TODO: change to typography from the theme*/ ""} + font-size: 14px; + font-weight: 600; + width: 100%; +`; + +export const CloseButton = styled.button` + display: flex; + align-items: center; + justify-content: center; + color: ${({ theme }) => theme.colors.v3.text.secondary}; + background: none; + border: none; + cursor: pointer; + margin-left: auto; +`; diff --git a/src/components/Agentic/common/Dialog/types.ts b/src/components/Agentic/common/Dialog/types.ts new file mode 100644 index 000000000..9ff98e2aa --- /dev/null +++ b/src/components/Agentic/common/Dialog/types.ts @@ -0,0 +1,7 @@ +import type { ReactNode } from "react"; + +export interface DialogProps { + title?: string; + onClose: () => void; + children: ReactNode; +} diff --git a/src/components/Agentic/common/PromptInput/index.tsx b/src/components/Agentic/common/PromptInput/index.tsx index 6f8a8ba57..543ce6dd3 100644 --- a/src/components/Agentic/common/PromptInput/index.tsx +++ b/src/components/Agentic/common/PromptInput/index.tsx @@ -17,7 +17,8 @@ export const PromptInput = ({ isSubmitting, className, placeholder, - isDisabled + isDisabled, + fontSize = s.TEXT_AREA_DEFAULT_FONT_SIZE }: PromptInputProps) => { const isSubmittingDisabled = isSubmitting ?? value.trim() === ""; const formRef = useRef(null); @@ -68,7 +69,7 @@ export const PromptInput = ({ if (textArea) { const MAX_LINES = 3; const linesCount = value.split("\n").length; - const lineHeight = s.TEXT_AREA_FONT_SIZE * s.TEXT_AREA_LINE_HEIGHT; + const lineHeight = fontSize * s.TEXT_AREA_LINE_HEIGHT; const newLinesHeight = Math.min( lineHeight * linesCount, lineHeight * MAX_LINES @@ -76,7 +77,7 @@ export const PromptInput = ({ setTextAreaHeight(Math.max(newLinesHeight, s.TEXT_AREA_MIN_HEIGHT)); } - }, [value]); + }, [value, fontSize]); const formHeight = textAreaHeight + s.FORM_TOP_BOTTOM_PADDING * 2; @@ -89,6 +90,7 @@ export const PromptInput = ({ > ` @@ -21,7 +21,7 @@ export const Form = styled.form` export const TextArea = styled.textarea` color: ${({ theme }) => theme.colors.v3.text.tertiary}; - font-size: ${TEXT_AREA_FONT_SIZE}px; + font-size: ${({ $fontSize }) => $fontSize}px; background: none; border: none; outline: none; diff --git a/src/components/Agentic/common/PromptInput/types.ts b/src/components/Agentic/common/PromptInput/types.ts index 2f36b2c09..60bfacc9b 100644 --- a/src/components/Agentic/common/PromptInput/types.ts +++ b/src/components/Agentic/common/PromptInput/types.ts @@ -6,6 +6,7 @@ export interface PromptInputProps { className?: string; placeholder?: string; isDisabled?: boolean; + fontSize?: number; // in pixels } export interface FormProps { @@ -14,4 +15,5 @@ export interface FormProps { export interface TextAreaProps { $height?: number; + $fontSize?: number; } diff --git a/src/components/Agentic/tracking.ts b/src/components/Agentic/tracking.ts index da069db65..b9805a50b 100644 --- a/src/components/Agentic/tracking.ts +++ b/src/components/Agentic/tracking.ts @@ -8,7 +8,10 @@ export const trackingEvents = addPrefix( SIDEBAR_USER_MENU_OPEN_CHANGED: "sidebar user menu open changed", SIDEBAR_USER_MENU_ITEM_CLICKED: "sidebar user menu item clicked", SIDEBAR_INCIDENTS_LIST_ITEM_CLICKED: "sidebar incidents list item clicked", + SIDEBAR_CREATE_BUTTON_CLICKED: "sidebar create button clicked", SIDEBAR_TEMPLATE_BUTTON_CLICKED: "sidebar template button clicked", + INCIDENT_CREATION_CHAT_DIALOG_CLOSED: + "incident creation chat dialog closed", FLOW_CHART_NODE_CLICKED: "flow chart node clicked", FLOW_CHART_NODE_KEBAB_MENU_CLICKED: "flow chart node kebab menu clicked", FLOW_CHART_NODE_KEBAB_MENU_ITEM_CLICKED: @@ -23,8 +26,8 @@ export const trackingEvents = addPrefix( "incident template add mcp dialog connect button clicked", INCIDENT_TEMPLATE_ADD_MCP_DIALOG_CANCEL_BUTTON_CLICKED: "incident template add mcp dialog cancel button clicked", - INCIDENT_TEMPLATE_ADD_MCP_DIALOG_CLOSE_BUTTON_CLICKED: - "incident template add mcp dialog close button clicked", + INCIDENT_TEMPLATE_ADD_MCP_DIALOG_CLOSED: + "incident template add mcp dialog closed", INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_OPENED: "incident template edit mcp dialog opened", INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_SAVE_BUTTON_CLICKED: diff --git a/src/redux/services/digma.ts b/src/redux/services/digma.ts index 2d6537945..f02965173 100644 --- a/src/redux/services/digma.ts +++ b/src/redux/services/digma.ts @@ -86,7 +86,8 @@ import type { PinErrorPayload, RecheckInsightPayload, ResendConfirmationEmailPayload, - sendMessageToIncidentAgentChatPayload, + SendMessageToIncidentAgentChatPayload, + SendMessageToIncidentCreationChatPayload, SetEndpointsIssuesPayload, SetMetricsReportDataPayload, SetServiceEndpointsPayload, @@ -380,7 +381,7 @@ export const digmaApi = createApi({ url: "Insights/get_insights_view", params: data }), - transformResponse: (response: GetInsightsResponse, meta, arg) => ({ + transformResponse: (response: GetInsightsResponse, _, arg) => ({ data: response, extra: arg.extra }), @@ -394,7 +395,7 @@ export const digmaApi = createApi({ url: "Insights/statistics", params: data }), - transformResponse: (response: GetInsightsStatsResponse, meta, arg) => ({ + transformResponse: (response: GetInsightsStatsResponse, _, arg) => ({ data: response, extra: { spanCodeObjectId: arg.scopedSpanCodeObjectId @@ -536,11 +537,7 @@ export const digmaApi = createApi({ url: "Spans/environments", params: data }), - transformResponse: ( - response: GetSpanEnvironmentsResponse, - meta, - arg - ) => ({ + transformResponse: (response: GetSpanEnvironmentsResponse, _, arg) => ({ data: response, extra: { spanCodeObjectId: arg.spanCodeObjectId @@ -596,8 +593,8 @@ export const digmaApi = createApi({ providesTags: ["IncidentAgentChatEvent"] }), sendMessageToIncidentAgentChat: builder.mutation< - void, // text/event-stream - sendMessageToIncidentAgentChatPayload + unknown, // text/event-stream + SendMessageToIncidentAgentChatPayload >({ query: ({ incidentId, agentId, data }) => ({ url: `Agentic/incidents/${window.encodeURIComponent( @@ -607,6 +604,17 @@ export const digmaApi = createApi({ body: data }), invalidatesTags: ["IncidentAgentChatEvent"] + }), + sendMessageToIncidentCreationChat: builder.mutation< + unknown, // text/event-stream + SendMessageToIncidentCreationChatPayload + >({ + query: ({ incidentId, data }) => ({ + url: `Agentic/incident-entry/${window.encodeURIComponent(incidentId)}`, + method: "POST", + body: data + }), + invalidatesTags: ["IncidentAgentChatEvent"] }) }) }); @@ -666,5 +674,6 @@ export const { useGetIncidentAgentsQuery, useGetIncidentAgentEventsQuery, useGetIncidentAgentChatEventsQuery, - useSendMessageToIncidentAgentChatMutation + useSendMessageToIncidentAgentChatMutation, + useSendMessageToIncidentCreationChatMutation } = digmaApi; diff --git a/src/redux/services/types.ts b/src/redux/services/types.ts index 5151c6ebe..78ef255f9 100644 --- a/src/redux/services/types.ts +++ b/src/redux/services/types.ts @@ -1174,13 +1174,13 @@ export interface GetIncidentAgentEventsPayload { agentId: string; } -export interface IncidentAgentEventToken { +export interface IncidentAgentTokenEvent { type: "token"; agent_name: string; message: string; } -export interface IncidentAgentEventTool { +export interface IncidentAgentToolEvent { type: "tool"; agent_name: string; message: string; @@ -1189,8 +1189,8 @@ export interface IncidentAgentEventTool { } export type GetIncidentAgentEventsResponse = ( - | IncidentAgentEventToken - | IncidentAgentEventTool + | IncidentAgentTokenEvent + | IncidentAgentToolEvent )[]; export interface GetIncidentAgentChatEventsPayload { @@ -1199,7 +1199,7 @@ export interface GetIncidentAgentChatEventsPayload { } export interface IncidentAgentChatEvent { - type: "ai" | "human" | "tool"; + type: "ai" | "human" | "tool" | "error" | "agent_end" | "input_user_required"; message: string; agent_name: string; tool_name: string | null; @@ -1208,8 +1208,13 @@ export interface IncidentAgentChatEvent { export type GetIncidentAgentChatEventsResponse = IncidentAgentChatEvent[]; -export interface sendMessageToIncidentAgentChatPayload { +export interface SendMessageToIncidentAgentChatPayload { incidentId: string; agentId: string; data: { text: string }; } + +export interface SendMessageToIncidentCreationChatPayload { + incidentId: string; + data: { text: string }; +} diff --git a/src/redux/slices/incidentsSlice.ts b/src/redux/slices/incidentsSlice.ts index c45311967..1980bffa7 100644 --- a/src/redux/slices/incidentsSlice.ts +++ b/src/redux/slices/incidentsSlice.ts @@ -3,17 +3,22 @@ import { globalClear } from "../actions"; import { STATE_VERSION } from "../constants"; import type { BaseState } from "./types"; -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface IncidentsState extends BaseState {} +export interface IncidentsState extends BaseState { + isCreateIncidentChatOpen: boolean; +} const initialState: IncidentsState = { - version: STATE_VERSION + version: STATE_VERSION, + isCreateIncidentChatOpen: false }; export const incidentsSlice = createSlice({ name: "incidents", initialState, reducers: { + setIsCreateIncidentChatOpen: (state, action: { payload: boolean }) => { + state.isCreateIncidentChatOpen = action.payload; + }, clear: () => initialState }, extraReducers: (builder) => { @@ -21,6 +26,6 @@ export const incidentsSlice = createSlice({ } }); -export const { clear } = incidentsSlice.actions; +export const { setIsCreateIncidentChatOpen, clear } = incidentsSlice.actions; export default incidentsSlice.reducer; From c2e1c51d0fd134410162d0247e8e00acace5029b Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Fri, 13 Jun 2025 13:47:01 +0200 Subject: [PATCH 2/6] Add dependency --- package-lock.json | 7 +++++++ package.json | 1 + 2 files changed, 8 insertions(+) diff --git a/package-lock.json b/package-lock.json index f890c1137..9e435dc25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@floating-ui/react": "^0.25.1", "@formkit/auto-animate": "^0.8.2", + "@microsoft/fetch-event-source": "^2.0.1", "@react-oauth/google": "^0.12.1", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.7.8", @@ -3757,6 +3758,12 @@ "react": ">=16" } }, + "node_modules/@microsoft/fetch-event-source": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz", + "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==", + "license": "MIT" + }, "node_modules/@mjackson/form-data-parser": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@mjackson/form-data-parser/-/form-data-parser-0.4.0.tgz", diff --git a/package.json b/package.json index 1b631815a..770561719 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "dependencies": { "@floating-ui/react": "^0.25.1", "@formkit/auto-animate": "^0.8.2", + "@microsoft/fetch-event-source": "^2.0.1", "@react-oauth/google": "^0.12.1", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.7.8", From c2488af00ee490e0f2c2c8a36693bb11dba363b8 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Fri, 13 Jun 2025 13:52:57 +0200 Subject: [PATCH 3/6] Fix headers and add abort signal --- .../IncidentsContainer/CreateIncidentChatOverlay/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx b/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx index a722fdf39..250d461ad 100644 --- a/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx +++ b/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx @@ -46,9 +46,11 @@ export const CreateIncidentChatOverlay = () => { }Agentic/incident-entry`, { method: "POST", + credentials: "same-origin", headers: { "Content-Type": "application/json" }, + signal: abortControllerRef.current.signal, body: JSON.stringify({ text }), From 415f58369a9d8ad1c592f08b5dba4ce46997bb9c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Jun 2025 12:04:19 +0000 Subject: [PATCH 4/6] Bump version to 15.2.0-alpha.1 [skip ci] --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9e435dc25..d0fb01d8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "digma-ui", - "version": "15.2.0-alpha.0", + "version": "15.2.0-alpha.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "digma-ui", - "version": "15.2.0-alpha.0", + "version": "15.2.0-alpha.1", "license": "MIT", "dependencies": { "@floating-ui/react": "^0.25.1", diff --git a/package.json b/package.json index 770561719..141516ff4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "digma-ui", - "version": "15.2.0-alpha.0", + "version": "15.2.0-alpha.1", "description": "Digma UI", "scripts": { "lint:eslint": "eslint --cache .", From e62a84ec8f5ea03e01b70d66e4405af8fc64b236 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Fri, 13 Jun 2025 15:02:32 +0200 Subject: [PATCH 5/6] Fix chat events --- .../IncidentAgentChat/index.tsx | 32 ++++++---- .../CreateIncidentChatOverlay/index.tsx | 60 ++++++++++++++++++- .../Agentic/common/AgentChat/index.tsx | 27 +++------ .../Agentic/common/AgentChat/types.ts | 4 ++ src/redux/services/digma.ts | 8 ++- src/redux/services/types.ts | 36 ++++------- 6 files changed, 108 insertions(+), 59 deletions(-) diff --git a/src/components/Agentic/IncidentDetails/IncidentAgentChat/index.tsx b/src/components/Agentic/IncidentDetails/IncidentAgentChat/index.tsx index 07d20a0ca..9170e905e 100644 --- a/src/components/Agentic/IncidentDetails/IncidentAgentChat/index.tsx +++ b/src/components/Agentic/IncidentDetails/IncidentAgentChat/index.tsx @@ -1,9 +1,13 @@ import { useParams } from "react-router"; import { useStableSearchParams } from "../../../../hooks/useStableSearchParams"; -import { useSendMessageToIncidentAgentChatMutation } from "../../../../redux/services/digma"; -import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; +import { + useGetIncidentAgentChatEventsQuery, + useSendMessageToIncidentAgentChatMutation +} from "../../../../redux/services/digma"; import { AgentChat } from "../../common/AgentChat"; -import { trackingEvents } from "../../tracking"; + +const REFRESH_INTERVAL = 10 * 1000; // in milliseconds +const REFRESH_INTERVAL_DURING_STREAMING = 3 * 1000; // in milliseconds export const IncidentAgentChat = () => { const params = useParams(); @@ -15,13 +19,6 @@ export const IncidentAgentChat = () => { useSendMessageToIncidentAgentChatMutation(); const handleMessageSend = (text: string) => { - sendUserActionTrackingEvent( - trackingEvents.INCIDENT_AGENT_MESSAGE_SUBMITTED, - { - agentName: agentId ?? "" - } - ); - void sendMessage({ incidentId: incidentId ?? "", agentId: agentId ?? "", @@ -29,8 +26,23 @@ export const IncidentAgentChat = () => { }); }; + const { data, isLoading } = useGetIncidentAgentChatEventsQuery( + { + incidentId: incidentId ?? "", + agentId: agentId ?? "" + }, + { + skip: !incidentId || !agentId, + pollingInterval: isMessageSending + ? REFRESH_INTERVAL_DURING_STREAMING + : REFRESH_INTERVAL + } + ); + return ( { const [incidentId, setIncidentId] = useState(); @@ -21,15 +27,41 @@ export const CreateIncidentChatOverlay = () => { ] = useState(false); const [isStartMessageSending, setIsStartMessageSending] = useState(false); const abortControllerRef = useRef(null); + const [accumulatedData, setAccumulatedData] = + useState(); const dispatch = useAgenticDispatch(); - const [sendMessage, { isLoading: isMessageSending }] = + const [sendMessage, { isLoading: isSubsequentMessageSending }] = useSendMessageToIncidentCreationChatMutation(); + const isMessageSending = isStartMessageSending || isSubsequentMessageSending; + + const { data, isLoading } = useGetIncidentAgentEventsQuery( + { + incidentId: incidentId ?? "", + agentId: AGENT_ID + }, + { + skip: !incidentId, + pollingInterval: isMessageSending + ? REFRESH_INTERVAL_DURING_STREAMING + : REFRESH_INTERVAL + } + ); + const handleCreateIncidentChatMessageSend = (text: string) => { // Send first message to start the incident creation chat if (!incidentId) { + setAccumulatedData([ + { + type: "human", + agent_name: "incident_entry", + message: text, + tool_name: null, + mcp_name: null + } + ]); // Stop any existing connection if (abortControllerRef.current) { abortControllerRef.current.abort(); @@ -68,6 +100,26 @@ export const CreateIncidentChatOverlay = () => { ); } }, + // onmessage: (message: EventSourceMessage) => { + // if (message.data) { + // try { + // const parsedData = JSON.parse( + // message.data + // ) as IncidentAgentEvent; + // if (["human", "token"].includes(parsedData.type)) { + // setAccumulatedData((prev) => + // prev ? [...prev, parsedData] : [parsedData] + // ); + // } + // if (parsedData.type === "input_user_required") { + // setIsStartMessageSending(false); + // } + // } catch (error) { + // // eslint-disable-next-line no-console + // console.error("Error parsing message data:", error); + // } + // } + // }, onerror: (err: unknown) => { abortControllerRef.current = null; setIsStartMessageSending(false); @@ -126,8 +178,10 @@ export const CreateIncidentChatOverlay = () => { 0 ? data : accumulatedData} + isDataLoading={isLoading} onMessageSend={handleCreateIncidentChatMessageSend} - isMessageSending={isMessageSending || isStartMessageSending} + isMessageSending={isMessageSending} promptFontSize={PROMPT_FONT_SIZE} /> diff --git a/src/components/Agentic/common/AgentChat/index.tsx b/src/components/Agentic/common/AgentChat/index.tsx index 4e6127589..aa68d9aa0 100644 --- a/src/components/Agentic/common/AgentChat/index.tsx +++ b/src/components/Agentic/common/AgentChat/index.tsx @@ -1,7 +1,6 @@ import { Fragment, useEffect, useMemo, useState } from "react"; import { Link } from "react-router"; -import { useGetIncidentAgentChatEventsQuery } from "../../../../redux/services/digma"; -import type { IncidentAgentChatEvent } from "../../../../redux/services/types"; +import type { IncidentAgentEvent } from "../../../../redux/services/types"; import { isNumber } from "../../../../typeGuards/isNumber"; import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; import { Chat } from "../../common/Chat"; @@ -12,8 +11,6 @@ import { trackingEvents } from "../../tracking"; import * as s from "./styles"; import type { AgentChatProps } from "./types"; -const REFRESH_INTERVAL = 10 * 1000; // in milliseconds -const REFRESH_INTERVAL_DURING_STREAMING = 3 * 1000; // in milliseconds const TYPING_SPEED = 3; // in milliseconds per character export const AgentChat = ({ @@ -21,24 +18,13 @@ export const AgentChat = ({ agentId, onMessageSend, isMessageSending, - className + className, + data, + isDataLoading }: AgentChatProps) => { const [initialEventsCount, setInitialEventsCount] = useState(); const [eventsVisibleCount, setEventsVisibleCount] = useState(); - const { data, isLoading } = useGetIncidentAgentChatEventsQuery( - { - incidentId: incidentId ?? "", - agentId: agentId ?? "" - }, - { - skip: !incidentId || !agentId, - pollingInterval: isMessageSending - ? REFRESH_INTERVAL_DURING_STREAMING - : REFRESH_INTERVAL - } - ); - const handleMessageSend = (text: string) => { sendUserActionTrackingEvent( trackingEvents.INCIDENT_AGENT_MESSAGE_SUBMITTED, @@ -86,9 +72,10 @@ export const AgentChat = ({ const shouldShowTypingForEvent = (index: number) => Boolean(initialEventsCount && index >= initialEventsCount); - const renderChatEvent = (event: IncidentAgentChatEvent, i: number) => { + const renderChatEvent = (event: IncidentAgentEvent, i: number) => { switch (event.type) { case "ai": + case "token": return ( Date: Fri, 13 Jun 2025 15:14:56 +0200 Subject: [PATCH 6/6] Close chat on navigation to the new incident --- .../CreateIncidentChatOverlay/index.tsx | 6 ++++++ .../Agentic/common/AgentChat/index.tsx | 16 +++++++++++++--- .../Agentic/common/AgentChat/styles.ts | 5 +++++ src/components/Agentic/common/AgentChat/types.ts | 1 + src/components/Agentic/tracking.ts | 1 + 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx b/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx index 3e123f0ec..ab27feba5 100644 --- a/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx +++ b/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx @@ -144,6 +144,11 @@ export const CreateIncidentChatOverlay = () => { } }; + const handleIncidentNavigate = (id: string) => { + dispatch(setIsCreateIncidentChatOpen(false)); + setIncidentId(id); + }; + const handleCreateIncidentChatDialogClose = () => { setIsCloseConfirmationDialogVisible(true); }; @@ -183,6 +188,7 @@ export const CreateIncidentChatOverlay = () => { onMessageSend={handleCreateIncidentChatMessageSend} isMessageSending={isMessageSending} promptFontSize={PROMPT_FONT_SIZE} + onNavigateToIncident={handleIncidentNavigate} /> diff --git a/src/components/Agentic/common/AgentChat/index.tsx b/src/components/Agentic/common/AgentChat/index.tsx index aa68d9aa0..b623b2e82 100644 --- a/src/components/Agentic/common/AgentChat/index.tsx +++ b/src/components/Agentic/common/AgentChat/index.tsx @@ -1,5 +1,4 @@ import { Fragment, useEffect, useMemo, useState } from "react"; -import { Link } from "react-router"; import type { IncidentAgentEvent } from "../../../../redux/services/types"; import { isNumber } from "../../../../typeGuards/isNumber"; import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; @@ -20,7 +19,8 @@ export const AgentChat = ({ isMessageSending, className, data, - isDataLoading + isDataLoading, + onNavigateToIncident }: AgentChatProps) => { const [initialEventsCount, setInitialEventsCount] = useState(); const [eventsVisibleCount, setEventsVisibleCount] = useState(); @@ -36,6 +36,14 @@ export const AgentChat = ({ onMessageSend(text); }; + const handleViewIncidentLinkClick = () => { + sendUserActionTrackingEvent(trackingEvents.VIEW_NEW_INCIDENT_LINK_CLICKED); + + if (incidentId) { + onNavigateToIncident?.(incidentId); + } + }; + const handleMarkdownTypingComplete = (i: number) => () => { const events = data ?? []; const aiEventsIndexes = events.reduce((acc, event, index) => { @@ -103,7 +111,9 @@ export const AgentChat = ({ return ( New incident has been created.{" "} - View + + View + ); } diff --git a/src/components/Agentic/common/AgentChat/styles.ts b/src/components/Agentic/common/AgentChat/styles.ts index 64085ca00..182935d40 100644 --- a/src/components/Agentic/common/AgentChat/styles.ts +++ b/src/components/Agentic/common/AgentChat/styles.ts @@ -1,5 +1,6 @@ import styled from "styled-components"; import { subheading1RegularTypography } from "../../../common/App/typographies"; +import { Link } from "../../../common/Link"; export const HumanMessage = styled.div` background: ${({ theme }) => theme.colors.v3.surface.highlight}; @@ -12,3 +13,7 @@ export const AgentMessage = styled.div` ${subheading1RegularTypography} color: ${({ theme }) => theme.colors.v3.text.secondary}; `; + +export const StyledLink = styled(Link)` + font-size: inherit; +`; diff --git a/src/components/Agentic/common/AgentChat/types.ts b/src/components/Agentic/common/AgentChat/types.ts index 7f499959f..8e8aff202 100644 --- a/src/components/Agentic/common/AgentChat/types.ts +++ b/src/components/Agentic/common/AgentChat/types.ts @@ -9,4 +9,5 @@ export interface AgentChatProps { promptFontSize?: number; data?: IncidentAgentEvent[]; isDataLoading: boolean; + onNavigateToIncident?: (incidentId: string) => void; } diff --git a/src/components/Agentic/tracking.ts b/src/components/Agentic/tracking.ts index b9805a50b..51ced1caf 100644 --- a/src/components/Agentic/tracking.ts +++ b/src/components/Agentic/tracking.ts @@ -12,6 +12,7 @@ export const trackingEvents = addPrefix( SIDEBAR_TEMPLATE_BUTTON_CLICKED: "sidebar template button clicked", INCIDENT_CREATION_CHAT_DIALOG_CLOSED: "incident creation chat dialog closed", + VIEW_NEW_INCIDENT_LINK_CLICKED: "view incident link clicked", FLOW_CHART_NODE_CLICKED: "flow chart node clicked", FLOW_CHART_NODE_KEBAB_MENU_CLICKED: "flow chart node kebab menu clicked", FLOW_CHART_NODE_KEBAB_MENU_ITEM_CLICKED: