import React, { useState, useEffect, useContext, memo } from "react"; import { Context } from "../context/ContextApi.jsx"; import { useNavigate, Link, useLocation } from "react-router-dom"; import { getTheme } from "../theme.jsx"; import { toast } from "react-toastify" import ReactJson from "react-json-view-ssr"; import { v4 as uuidv4} from "uuid"; import { validateJson, collapseField, handleReactJsonClipboard, HandleJsonCopy } from "../views/Workflows.jsx"; import { Box, Button, ButtonGroup, Typography, Chip, CircularProgress, Tooltip, IconButton, TextField, } from '@mui/material' import { CheckCircle as CheckCircleIcon, HourglassDisabled as HourglassDisabledIcon, RestartAlt as RestartAltIcon, ExpandMore as ExpandMoreIcon, ExpandLess as ExpandLessIcon, Send as SendIcon, Error as ErrorIcon, } from '@mui/icons-material' import { green, red, } from '../views/AngularWorkflow.jsx' const AgentUI = (props) => { const { globalUrl, isLoggedIn, isLoaded, userdata, } = props const [buttonState, setButtonState] = useState("timeline") const [execution, setExecution] = useState(null) const [agentActionResult, setAgentActionResult] = useState(null) const [agentRequestLoading, setAgentRequestLoading] = useState(false) const [data, setData] = useState({}) const [openIndexes, setOpenIndexes] = useState([]) const [disableButtons, setDisableButtons] = useState(false) const [originalStartTime, setOriginalStartTime] = useState(0) const [latestEndTime, setLatestEndTime] = useState(0) const [showAgentStarter, setShowAgentStarter] = useState(false) const [actionInput, setActionInput] = useState("") const {themeMode} = useContext(Context) const theme = getTheme(themeMode) const navigate = useNavigate(); const agentWrapperStyle = { width: 1000, height: 1000, margin: "auto", paddingTop: 100, } if (data.input === undefined || data.input === null) { data.input = "" } else { const verifiedInput = validateJson(data.input) if (verifiedInput.valid === true) { data.input = JSON.stringify(verifiedInput.result, null, 2) } } const findNodeData = (execution_data, node_id) => { if (execution_data === undefined || execution_data === null) { return } if (node_id === undefined || node_id === null || node_id === "") { return } var found = false for (var key in execution_data.results) { const item = execution_data.results[key] if (item?.action?.id !== node_id) { continue } setAgentActionResult(item) found = true const validate = validateJson(item.result) if (validate.valid) { setData(validate.result) } else { toast.warn("Action output result is not valid JSON!") } break } if (found === false) { toast.warn("Failed to find the relevant AI Agent result") if (execution_data?.results?.length === 1) { setAgentActionResult(execution_data.results[0]) const validatedData = validateJson(execution_data.results[0].result) if (validatedData.valid) { setData(validatedData.result) } else { toast.warn("Action output result is not valid JSON!") } } } } const GetExecution = (execution_id, node_id, authorization) => { if (execution_id === undefined || execution_id === null) { toast.error("No execution ID provided. Please provide execution_id in the URL.") return } //if (node_id === undefined || node_id === null || node_id === "") { // toast.error("No node ID provided. Please provide node_id in the URL.") // return //} if (authorization === undefined || authorization === null) { toast.error("No authorization provided. Please provide authorization in the URL.") return } const headers = {} const executionRequest = { "execution_id": execution_id, "authorization": authorization, } const url = `${globalUrl}/api/v1/streams/results` fetch(url, { method: "POST", headers: headers, body: JSON.stringify(executionRequest), credentials: "include", cors: "no-cors", }) .then((response) => { return response.json() }) .then((responseJson) => { if (responseJson.success !== false) { if (responseJson.status === "EXECUTING") { // Recursively looking for updates until it's not executing anymore setTimeout(() => { GetExecution(execution_id, node_id, authorization) }, 3000) } else { setDisableButtons(false) setDisableButtons(false) } setExecution(responseJson) findNodeData(responseJson, node_id) } else { setDisableButtons(false) if (responseJson.reason === undefined || responseJson.reason === null) { toast.error("Failed to load the agent data. Please try again and contact support@shuffler.io if this persists") } else { toast.error("Error: " + responseJson.reason) } } }) .catch((error) => { setDisableButtons(false) toast.error("Error: " + error) }) } const RerunDecision = (decision) => { if (execution.execution_id === undefined || execution.execution_id === null) { toast.error("No workflow run loaded. Please try again and contact support@shuffler.io if this persists.") return } if (agentActionResult === undefined || agentActionResult === null) { toast.error("Failed to find the relevant agent action. Please try again, and contact support@shuffler.io if it persists.") return } console.log("DECISION: ", decision) const url = `${globalUrl}/api/v1/apps/agent/run?rerun=true&decision_id=${decision?.run_details?.id}` var body = agentActionResult.action console.log("BODY: ", body) body.source_execution = execution.execution_id body.source_workflow = execution.workflow.id fetch(url, { method: "POST", body: JSON.stringify(body), credentials: "include", cors: "no-cors", }) .then((response) => { return response.json() }) .then((responseJson) => { console.log("RESP: ", responseJson) if (responseJson.success !== false) { } else { if (responseJson.reason === undefined || responseJson.reason === null) { toast.warn("Failed to restart the agent decision. Please try again and contact support@shuffler.io if this persists") } else { toast.warn(responseJson.reason) } } GetExecution(execution.execution_id, agentActionResult.action.id, execution.authorization) }) .catch((error) => { toast.error("Error: " + error) }) } useEffect(() => { const params = new URLSearchParams(window.location.search) const executionId = params.get("execution_id") const nodeId = params.get("node_id") const authorization = params.get("authorization") if (executionId !== undefined && executionId !== null && authorization !== undefined && authorization !== null) { GetExecution(executionId, nodeId, authorization) } else { setShowAgentStarter(true) //toast.warn("No execution ID or node ID provided. Please provide execution_id and node_id in the URL.") } }, []) const maxTimelineWidth = 150 const TimelineItem = (props) => { const { item, index } = props; const [hovered, setHovered] = useState(false); const parsedStatus = item.status === "RUNNING" || item.status === "WAITING" ? : item.status === "FINISHED" ? : item.status === "ABORTED" || item.status === "FAILURE" ? : const categoryStyle = { width: 20, height: 20, marginRight: 10, } const parsedCategory = item.category === "singul" ? : item.category === "ask" ? :
const validate = validateJson(item.details) const itemStartTime = item.start_time var itemEndTime = item.end_time if (itemStartTime !== undefined && itemStartTime !== originalStartTime && (itemStartTime < originalStartTime || originalStartTime === 0)) { console.log("Rerender 1") //setOriginalStartTime(itemStartTime) } if (itemEndTime !== undefined && itemEndTime > latestEndTime) { console.log("Rerender 2") setLatestEndTime(itemEndTime) } if (itemEndTime === undefined || itemEndTime === null) { // Set it to now itemEndTime = latestEndTime } const totalDuration = latestEndTime - originalStartTime const currentDuration = itemStartTime - itemEndTime var timelineMarginLeft = ((itemStartTime - originalStartTime) / totalDuration) * maxTimelineWidth var timelineWidth = ((itemEndTime - itemStartTime) / totalDuration) * maxTimelineWidth if (totalDuration === currentDuration) { timelineMarginLeft = 0 timelineWidth = maxTimelineWidth } const defaultTopPadding = 10 const open = openIndexes.includes(index) return (
{ if (!hovered) { console.log("HOVER") setHovered(true) } }} onMouseLeave={() => { if (hovered) { setHovered(false) } }} >
{ if (item.details === undefined || item.details === null || item.details === "") { if (item?.category === "agent" && item?.type === "agent") { // Show all the data if (openIndexes.includes(index)) { console.log("Rerender 3") setOpenIndexes(openIndexes.filter((i) => i !== index)) } else { console.log("Rerender 4") setOpenIndexes([...openIndexes, index]) } } else { toast.warn("No details to open") } return } if (openIndexes.includes(index)) { console.log("Rerender 5") setOpenIndexes(openIndexes.filter((i) => i !== index)) } else { console.log("Rerender 6") setOpenIndexes([...openIndexes, index]) } }} >
{parsedStatus}
{parsedCategory}
{/* To ISO string from unix time */} {new Date(item.start_time * 1000).toLocaleString()}
{item.label}
{currentDuration !== 0 && !isNaN(timelineMarginLeft) && !isNaN(timelineWidth) && timelineWidth > 0 ?
: null}
{ e.preventDefault() e.stopPropagation() toast.info("Attempting to rerun this decision by itself.") setDisableButtons(true) RerunDecision(item.details) }} > {open ? : }
{open ?
{validate.valid === true ? { return collapseField(jsonField) }} iconStyle={theme.palette.jsonIconStyle} collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength} displayArrayKey={false} enableClipboard={(copy) => { handleReactJsonClipboard(copy); }} displayDataTypes={false} onSelect={(select) => { HandleJsonCopy(validate.result, select, "exec") }} name={false} /> : {item.details} }
: null }
) } const TimelineRender = (props) => { const { agent_data } = props; const actionResult = execution?.results?.length > 0 ? execution.results[0] : execution var timelineItems = [ { "label": "AI Agent 2", "type": "agent", "category": "agent", "details": actionResult?.result, "status": agent_data?.status, "start_time": agent_data?.started_at, "end_time": agent_data?.completed_at, }, ] // Autofixer for result lol if ((agent_data?.decisions === undefined || agent_data?.decisions === null)) { const verifiedInput = validateJson(actionResult?.result) if (verifiedInput.valid === true && verifiedInput.result?.decisions !== undefined && verifiedInput.result?.decisions !== null) { agent_data.decisions = verifiedInput.result?.decisions setAgentActionResult(actionResult) } } var sortedTimelineItems = [] for (var key in agent_data?.decisions) { const item = agent_data.decisions[key] if (item.run_details.started_at === undefined || item.run_details.started_at === null) { item.run_details.started_at = originalStartTime } if (item.run_details.completed_at === undefined || item.run_details.completed_at === null) { item.run_details.completed_at = Math.floor(Date.now() / 1000) } var newTimelineItem = { "label": item.action, "type": "decision", "category": item.category, "status": item.run_details.status, "start_time": item.run_details.started_at, "end_time": item.run_details.completed_at, } newTimelineItem.details = item timelineItems.push(newTimelineItem) } timelineItems.sort((a, b) => { if (a.start_time < b.start_time) { return 1; } return 0; }) return (
{/*
{agent_data?.status === "RUNNING" || agent_data?.status === "WAITING" ? : agent_data?.status === "FINISHED" ? : } {agent_data?.status}
*/}
{timelineItems?.map((item, index) => { return ( ) })}
) } const submitInput = (inputText) => { //toast.info("Submitting AI Agent input: " + inputText); setAgentRequestLoading(true) //setShowAgentStarter(false); //GetExecution(execution?.execution_id, execution?.node_id, execution?.authorization); if (inputText === undefined || inputText === null || inputText === "") { toast.error("Please provide a valid input for the AI Agent.") return } // 1. Run the execution. Can this be a single-action run? // 2. Get the execution ID and node ID from the response. const uuid = uuidv4() const data = { "id": uuid, "name":"agent", //"app_name":"Shuffle AI", "app_name":"AI Agent", // Failover for rerun "app_id":"shuffle_agent", "app_version":"1.0.0", "environment":"cloud", "parameters":[ { "name":"app_name", "value":"openai" }, { "name":"input", "value": inputText }, { "name":"action", "value":"list_tickets" } ]} const url = `${globalUrl}/api/v1/apps/agent_starter/run` fetch(url, { method: "POST", body: JSON.stringify(data), credentials: "include", }) .then((response) => { setAgentRequestLoading(false) return response.json() }) .then((responseJson) => { //toast.success("Got response!") console.log("Agent run response: ", responseJson) if (responseJson.success === true && responseJson.authorization !== undefined && responseJson.execution_id !== undefined) { navigate("?execution_id=" + responseJson.execution_id + "&authorization=" + responseJson.authorization) setShowAgentStarter(false) GetExecution(responseJson.execution_id, "", responseJson.authorization) } }) .catch((error) => { setAgentRequestLoading(false) toast.error("Error: " + error) }) } return (
{showAgentStarter ? { e.preventDefault(); submitInput(actionInput); }}>
Shuffle AI Agents { setActionInput(e.target.value) }} InputProps={{ endAdornment: ( agentRequestLoading ? : ), }} /> :
{buttonState === "timeline" ? : null }
}
) } export default AgentUI