diff --git a/package-lock.json b/package-lock.json index 7a3bd972..96c64f61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "digma-ui", - "version": "16.7.0", + "version": "16.7.1-alpha.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "digma-ui", - "version": "16.7.0", + "version": "16.7.1-alpha.0", "license": "MIT", "dependencies": { "@codemirror/lang-json": "^6.0.2", diff --git a/package.json b/package.json index a3ff00cd..3b9c7933 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "digma-ui", - "version": "16.7.0", + "version": "16.7.1-alpha.0", "description": "Digma UI", "scripts": { "lint:eslint": "eslint --cache .", diff --git a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx index 44fd94e7..6e8bbcd6 100644 --- a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx +++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx @@ -20,7 +20,7 @@ const IDE_ICONS: Record> = { export const IdeToolbar = ({ incidentId }: IdeToolbarProps) => { const [ides, setIdes] = useState(); - const handleIdeButtonClick = (ide: string) => { + const handleIdeButtonClick = (ide: string) => () => { sendUserActionTrackingEvent(trackingEvents.INCIDENT_IDE_BUTTON_CLICKED, { ide }); @@ -68,7 +68,7 @@ export const IdeToolbar = ({ incidentId }: IdeToolbarProps) => { size={"large"} key={ide.ideUriScheme} icon={(props) => } - onClick={() => handleIdeButtonClick(ide.ideUriScheme)} + onClick={handleIdeButtonClick(ide.ideUriScheme)} /> ); diff --git a/src/components/Agentic/IncidentDetails/index.tsx b/src/components/Agentic/IncidentDetails/index.tsx index 8a73aa98..e6965482 100644 --- a/src/components/Agentic/IncidentDetails/index.tsx +++ b/src/components/Agentic/IncidentDetails/index.tsx @@ -1,6 +1,6 @@ import { Allotment } from "allotment"; import "allotment/dist/style.css"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useParams } from "react-router"; import { useTheme } from "styled-components"; import { useAgenticDispatch } from "../../../containers/Agentic/hooks"; @@ -113,6 +113,20 @@ export const IncidentDetails = () => { ); }; + const handleAgentSelect = useCallback( + (id: string | null) => { + setSearchParams((params) => { + if (id) { + params.set("agent", id); + } else { + params.delete("agent"); + } + return params; + }); + }, + [setSearchParams] + ); + useEffect(() => { setAgentViewMode("summary"); }, [agentId]); @@ -137,17 +151,6 @@ export const IncidentDetails = () => { (agent) => agent.name === `${agentId}_chat` ); - const handleAgentSelect = (id: string | null) => { - setSearchParams((params) => { - if (id) { - params.set("agent", id); - } else { - params.delete("agent"); - } - return params; - }); - }; - if (!incidentData && isLoading) { return ( diff --git a/src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/styles.ts b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/styles.ts index 47381d60..77e20c9a 100644 --- a/src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/styles.ts +++ b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/styles.ts @@ -116,7 +116,7 @@ export const InstructionsTextArea = styled.textarea` color: #fff; ${/* TODO: change to typography from the theme */ ""} font-size: 14px; - resize: none; + resize: vertical; &::placeholder { color: ${({ theme }) => theme.colors.v3.text.secondary}; diff --git a/src/components/Agentic/common/AgentFlowChart/index.tsx b/src/components/Agentic/common/AgentFlowChart/index.tsx index 623c1f52..a4fa5662 100644 --- a/src/components/Agentic/common/AgentFlowChart/index.tsx +++ b/src/components/Agentic/common/AgentFlowChart/index.tsx @@ -1,4 +1,5 @@ import { Position, type Edge } from "@xyflow/react"; +import React, { useCallback, useMemo } from "react"; import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; import { trackingEvents } from "../../tracking"; import { FlowChart } from "../FlowChart"; @@ -91,7 +92,7 @@ const getFlowChartNodeData = ({ : {}; }; -export const AgentFlowChart = ({ +export const AgentFlowChartComponent = ({ agents, onAgentSelect, selectedAgentId, @@ -101,188 +102,208 @@ export const AgentFlowChart = ({ onEditMCPServer, onDeleteMCPServer }: AgentFlowChartProps) => { - const extendedAgents: ExtendedAgent[] = [ - { - name: "digma", - display_name: "Digma", - description: "Digma", - status: "waiting", - status_details: {}, - mcp_servers: [] - }, - ...agents.map((agent) => ({ - ...agent, - mcp_servers: agent.mcp_servers.map((server) => ({ - ...server - })) - })), - { - name: "validator", - display_name: "Validator", - description: "Validator", - status: "waiting", - status_details: {}, - mcp_servers: [] - } - ]; - - const handleNodeClick = (id: string) => { - switch (id) { - case "digma": { - if (!isEditMode) { - onAgentSelect(null); - } - break; + const extendedAgents: ExtendedAgent[] = useMemo( + () => [ + { + name: "digma", + display_name: "Digma", + description: "Digma", + status: "waiting", + status_details: {}, + mcp_servers: [] + }, + ...agents.map((agent) => ({ + ...agent, + mcp_servers: agent.mcp_servers.map((server) => ({ + ...server + })) + })), + { + name: "validator", + display_name: "Validator", + description: "Validator", + status: "waiting", + status_details: {}, + mcp_servers: [] } - case "watchman": - case "triager": - case "infra_resolver": - case "code_resolver": - { - if ( - extendedAgents?.find((a) => a.name === id)?.status === "skipped" - ) { - break; - } + ], + [agents] + ); - onAgentSelect(id); + const handleNodeClick = useCallback( + (id: string) => { + switch (id) { + case "digma": { + if (!isEditMode) { + onAgentSelect(null); + } + break; } - break; - case "validator": - default: - break; - } - }; + case "watchman": + case "triager": + case "infra_resolver": + case "code_resolver": + { + if ( + extendedAgents?.find((a) => a.name === id)?.status === "skipped" + ) { + break; + } - const nodes: FlowChartNode[] = [ - { - type: "flowChart", - id: "digma", - position: { x: 0, y: -31 }, // TODO: find a way to center this - data: { - ...getFlowChartNodeData({ - agent: extendedAgents?.find((a) => a.name === "digma"), - isSelected: !selectedAgentId, - isInteractive: !isEditMode - }), - orientation: "vertical", - type: "input" - } - }, - { - type: "flowChart", - id: "watchman", - position: { x: 200, y: 0 }, - data: { - ...getFlowChartNodeData({ - agent: extendedAgents?.find((a) => a.name === "watchman"), - isSelected: "watchman" === selectedAgentId, - isInteractive: - extendedAgents?.find((a) => a.name === "watchman")?.status !== - "skipped", - isEditMode, - onAddMCPServer, - onEditMCPServer, - onDeleteMCPServer - }) - } - }, - { - type: "flowChart", - id: "triager", - position: { x: 500, y: 0 }, - data: { - ...getFlowChartNodeData({ - agent: extendedAgents?.find((a) => a.name === "triager"), - isSelected: "triager" === selectedAgentId, - isInteractive: - extendedAgents?.find((a) => a.name === "triager")?.status !== - "skipped", - isEditMode, - onAddMCPServer, - onEditMCPServer, - onDeleteMCPServer - }) - } - }, - { - type: "flowChart", - id: "infra_resolver", - position: { x: 800, y: -50 }, - data: { - ...getFlowChartNodeData({ - agent: extendedAgents?.find((a) => a.name === "infra_resolver"), - isSelected: "infra_resolver" === selectedAgentId, - isInteractive: - extendedAgents?.find((a) => a.name === "infra_resolver")?.status !== - "skipped", - isEditMode, - onAddMCPServer, - onEditMCPServer, - onDeleteMCPServer - }) - } - }, - { - type: "flowChart", - id: "code_resolver", - position: { x: 800, y: 50 }, - data: { - ...getFlowChartNodeData({ - agent: extendedAgents?.find((a) => a.name === "code_resolver"), - isSelected: "code_resolver" === selectedAgentId, - isInteractive: - extendedAgents?.find((a) => a.name === "code_resolver")?.status !== - "skipped", - isEditMode, - onAddMCPServer, - onEditMCPServer, - onDeleteMCPServer - }) + onAgentSelect(id); + } + break; + case "validator": + default: + break; } }, - { - type: "flowChart", - id: "validator", - position: { x: 1100, y: 0 }, - data: { - ...getFlowChartNodeData({ - agent: extendedAgents?.find((a) => a.name === "validator"), - isSelected: false, - isInteractive: false - }), - type: "output" + [extendedAgents, isEditMode, onAgentSelect] + ); + + const nodes: FlowChartNode[] = useMemo( + () => [ + { + type: "flowChart", + id: "digma", + position: { x: 0, y: -31 }, // TODO: find a way to center this + data: { + ...getFlowChartNodeData({ + agent: extendedAgents?.find((a) => a.name === "digma"), + isSelected: !selectedAgentId, + isInteractive: !isEditMode + }), + orientation: "vertical", + type: "input" + } + }, + { + type: "flowChart", + id: "watchman", + position: { x: 200, y: 0 }, + data: { + ...getFlowChartNodeData({ + agent: extendedAgents?.find((a) => a.name === "watchman"), + isSelected: "watchman" === selectedAgentId, + isInteractive: + extendedAgents?.find((a) => a.name === "watchman")?.status !== + "skipped", + isEditMode, + onAddMCPServer, + onEditMCPServer, + onDeleteMCPServer + }) + } + }, + { + type: "flowChart", + id: "triager", + position: { x: 500, y: 0 }, + data: { + ...getFlowChartNodeData({ + agent: extendedAgents?.find((a) => a.name === "triager"), + isSelected: "triager" === selectedAgentId, + isInteractive: + extendedAgents?.find((a) => a.name === "triager")?.status !== + "skipped", + isEditMode, + onAddMCPServer, + onEditMCPServer, + onDeleteMCPServer + }) + } + }, + { + type: "flowChart", + id: "infra_resolver", + position: { x: 800, y: -50 }, + data: { + ...getFlowChartNodeData({ + agent: extendedAgents?.find((a) => a.name === "infra_resolver"), + isSelected: "infra_resolver" === selectedAgentId, + isInteractive: + extendedAgents?.find((a) => a.name === "infra_resolver") + ?.status !== "skipped", + isEditMode, + onAddMCPServer, + onEditMCPServer, + onDeleteMCPServer + }) + } + }, + { + type: "flowChart", + id: "code_resolver", + position: { x: 800, y: 50 }, + data: { + ...getFlowChartNodeData({ + agent: extendedAgents?.find((a) => a.name === "code_resolver"), + isSelected: "code_resolver" === selectedAgentId, + isInteractive: + extendedAgents?.find((a) => a.name === "code_resolver") + ?.status !== "skipped", + isEditMode, + onAddMCPServer, + onEditMCPServer, + onDeleteMCPServer + }) + } + }, + { + type: "flowChart", + id: "validator", + position: { x: 1100, y: 0 }, + data: { + ...getFlowChartNodeData({ + agent: extendedAgents?.find((a) => a.name === "validator"), + isSelected: false, + isInteractive: false + }), + type: "output" + } } - } - ]; + ], + [ + extendedAgents, + isEditMode, + selectedAgentId, + onAddMCPServer, + onEditMCPServer, + onDeleteMCPServer + ] + ); - const edges: Edge[] = [ - { id: "digma-watchman", source: "digma", target: "watchman" }, - { id: "watchman-triager", source: "watchman", target: "triager" }, - { - id: "triager-infra_resolver", - source: "triager", - target: "infra_resolver" - }, - { - id: "triager-code_resolver", - source: "triager", - target: "code_resolver" - }, - { - id: "infra_resolver-validator", - source: "infra_resolver", - target: "validator" - }, - { - id: "code_resolver-validator", - source: "code_resolver", - target: "validator" - } - ].map((edge) => ({ - ...edge, - animated: !isEditMode - })); + const edges: Edge[] = useMemo( + () => + [ + { id: "digma-watchman", source: "digma", target: "watchman" }, + { id: "watchman-triager", source: "watchman", target: "triager" }, + { + id: "triager-infra_resolver", + source: "triager", + target: "infra_resolver" + }, + { + id: "triager-code_resolver", + source: "triager", + target: "code_resolver" + }, + { + id: "infra_resolver-validator", + source: "infra_resolver", + target: "validator" + }, + { + id: "code_resolver-validator", + source: "code_resolver", + target: "validator" + } + ].map((edge) => ({ + ...edge, + animated: !isEditMode + })), + [isEditMode] + ); return ( ); }; + +export const AgentFlowChart = React.memo(AgentFlowChartComponent); diff --git a/src/components/Agentic/common/FlowChart/FlowChartNode/index.tsx b/src/components/Agentic/common/FlowChart/FlowChartNode/index.tsx index 2de6c86b..2122ef3c 100644 --- a/src/components/Agentic/common/FlowChart/FlowChartNode/index.tsx +++ b/src/components/Agentic/common/FlowChart/FlowChartNode/index.tsx @@ -4,7 +4,7 @@ import { type Node, type NodeProps } from "@xyflow/react"; -import { useState, type ReactNode } from "react"; +import React, { useState, type ReactNode } from "react"; import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent"; import { PauseIcon } from "../../../../common/icons/12px/PauseIcon"; import { ChevronIcon } from "../../../../common/icons/16px/ChevronIcon"; @@ -46,7 +46,10 @@ export type FlowChartNodeData = { export type FlowChartNode = Node; -export const FlowChartNode = ({ id, data }: NodeProps) => { +export const FlowChartNodeComponent = ({ + id, + data +}: NodeProps) => { const [isKebabMenuOpen, setIsKebabMenuOpen] = useState(false); const handleNodeClick = () => { @@ -163,3 +166,5 @@ export const FlowChartNode = ({ id, data }: NodeProps) => { ); }; + +export const FlowChartNode = React.memo(FlowChartNodeComponent); diff --git a/src/components/Agentic/common/FlowChart/index.tsx b/src/components/Agentic/common/FlowChart/index.tsx index 42a605b6..c6925b26 100644 --- a/src/components/Agentic/common/FlowChart/index.tsx +++ b/src/components/Agentic/common/FlowChart/index.tsx @@ -9,7 +9,7 @@ import { type NodeTypes } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; -import { useEffect, useMemo, type MouseEvent } from "react"; +import React, { useCallback, useEffect, useMemo, type MouseEvent } from "react"; import useDimensions from "react-cool-dimensions"; import { useTheme } from "styled-components"; import { getThemeKind } from "../../../common/App/styles"; @@ -27,7 +27,7 @@ const nodeTypes: NodeTypes = { flowChart: FlowChartNode }; -const FlowChartInner = ({ +const FlowChartInnerComponent = ({ nodes, edges, onNodeClick, @@ -38,11 +38,14 @@ const FlowChartInner = ({ const { observe, width, height } = useDimensions(); const { fitView } = useReactFlow(); - const handleNodeClick = (e: MouseEvent, node: FlowChartNode) => { - if (onNodeClick) { - onNodeClick(node.id); - } - }; + const handleNodeClick = useCallback( + (e: MouseEvent, node: FlowChartNode) => { + if (onNodeClick) { + onNodeClick(node.id); + } + }, + [onNodeClick] + ); const extendedNodes: FlowChartNode[] = useMemo( () => @@ -98,6 +101,8 @@ const FlowChartInner = ({ ); }; +export const FlowChartInner = React.memo(FlowChartInnerComponent); + export const FlowChart = (props: FlowChartProps) => (