-
-
- Click required to flip
-
- }
- onBlur={(e) => {
- console.log("IN BLUR: ", e.target.value);
- urlPathQueries[queryIndex].name = e.target.value.replaceAll("=", "");
- setUrlPathQueries(urlPathQueries);
- }}
- style={{flex: 3}}
- InputProps={{
- style: {
- color: theme.palette.text.primary,
- },
- }}
- />
- {
- urlPathQueries[queryIndex].example = e.target.value.replaceAll(
- "=",
- ""
- )
-
- setUrlPathQueries(urlPathQueries)
- }}
- style={{flex: 2}}
- InputProps={{
- style: {
- color: theme.palette.text.primary,
- },
- }}
- />
-
+
+ {
+ urlPathQueries[queryIndex].name = e.target.value.replaceAll("=", "")
+ setUrlPathQueries(urlPathQueries)
+ }}
+ style={{flex: 3}}
+ InputProps={{
+ style: {
+ color: theme.palette.text.primary,
+ },
+ }}
+ />
+ {
+ // E.g. for Jira -> JQL -> requires = in param
+ urlPathQueries[queryIndex].example = e.target.value.replaceAll("=","=")
+ setUrlPathQueries(urlPathQueries)
+ }}
+ style={{flex: 2}}
+ InputProps={{
+ style: {
+ color: theme.palette.text.primary,
+ },
+ }}
+ />
+
{
@@ -3654,7 +3651,7 @@ const AppCreator = (defaultprops) => {
deletePathQuery(queryIndex);
}}
>
-
+
);
@@ -4106,22 +4103,22 @@ const AppCreator = (defaultprops) => {
if (request.header !== undefined && request.header !== null) {
var headers = [];
for (let [key, value] of Object.entries(request.header)) {
- if (value === undefined) {
- if (key.includes(":")) {
- const keysplit = key.split(":")
- key = keysplit[0].trim()
- value = keysplit[1].trim()
+ if (value === undefined) {
+ if (key.includes(":")) {
+ const keysplit = key.split(":")
+ key = keysplit[0].trim()
+ value = keysplit[1].trim()
- } else if (key.includes("=")) {
- const keysplit = key.split("=")
- key = keysplit[0].trim()
- value = keysplit[1].trim()
+ } else if (key.includes("=")) {
+ const keysplit = key.split("=")
+ key = keysplit[0].trim()
+ value = keysplit[1].trim()
- } else {
- toast("Removed key: ", key)
- continue
- }
- }
+ } else {
+ toast("Removed key: ", key)
+ continue
+ }
+ }
if (
parameterName !== undefined &&
@@ -4392,9 +4389,8 @@ const AppCreator = (defaultprops) => {
variant={urlPath.length > 0 ? "contained" : "outlined"}
style={{ }}
onClick={() => {
- //console.log(urlPathQueries)
- //console.log(urlPath)
console.log(currentAction);
+
const errors = getActionErrors();
addActionToView(errors);
setActionsModalOpen(false);
@@ -4460,7 +4456,7 @@ const AppCreator = (defaultprops) => {
return (
- {newActionModal}
+ {newActionModal}
{error}
diff --git a/frontend/src/views/AppExplorer.jsx b/frontend/src/views/AppExplorer.jsx
index 1f434a83..38e7cc4f 100644
--- a/frontend/src/views/AppExplorer.jsx
+++ b/frontend/src/views/AppExplorer.jsx
@@ -3075,7 +3075,7 @@ const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)";
const appEnding = app?.public === true ? app?.app_version : app?.id
- return `curl -L \ \\\n "${globalUrl}/api/v1/download_docker_image?image=frikky/shuffle:${app?.name.toLowerCase().replaceAll(' ', '_')}_${appEnding}" \\\n -H \"Authorization: Bearer APIKEY" \\\n -o image.zip; \\\n docker load -i image.zip`
+ return `curl -L \ \\\n "${globalUrl}/api/v1/download_docker_image?image=frikky/shuffle:${app?.name.toLowerCase().replaceAll(' ', '_')}_${appEnding}" \\\n -H \"Authorization: Bearer APIKEY" \\\n -o image.zip; \\\n docker load -i image.zip${!app?.public ? ` \\\n docker tag frikky/shuffle:${app?.name.toLowerCase().replaceAll(' ', '_')}_${appEnding} frikky/shuffle:${app?.name.toLowerCase().replaceAll(' ', '_')}_${app.app_version}` : ``}`
}
const renderedActionOptions = deduplicateByName((
@@ -3405,7 +3405,7 @@ const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)";
{
if (!isLoggedIn) {
- //navigate("/login?message=You must be logged in to activate this app&view=/apps/" + params.appid);
- toast("You must be logged in to activate apps! Go to /login first.")
+ navigate("/login?message=You must be logged in to activate this app&view=/apps/" + params.appid);
+ // toast("You must be logged in to activate apps! Go to /login first.")
return;
}
diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx
index d8d031b3..afb9d25d 100644
--- a/frontend/src/views/Apps2.jsx
+++ b/frontend/src/views/Apps2.jsx
@@ -42,6 +42,7 @@ import { debounce } from "lodash";
import AppSelection from "../components/AppSelection.jsx";
import AppModal from "../components/AppModal.jsx";
import AppCreationModal from "../components/AppCreationModal.jsx";
+import Dropzone from "../components/Dropzone.jsx";
const searchClient = algoliasearch(
@@ -1136,6 +1137,7 @@ const Apps2 = (props) => {
const [field2, setField2] = useState("");
const [validation, setValidation] = useState(null);
const [createAppModalOpen, setCreateAppModalOpen] = useState(false);
+ const [openApiData, setOpenApiData] = useState("");
const {themeMode, brandColor} = useContext(Context);
const theme = getTheme(themeMode, brandColor);
@@ -1736,6 +1738,31 @@ const Apps2 = (props) => {
// setOpenModal(true);
};
+ const uploadFile = (e) => {
+ const isFromDropzone = e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0;
+ const files = isFromDropzone ? e.dataTransfer.files : e.target.files;
+
+ const reader = new FileReader();
+
+ try {
+ reader.addEventListener("load", (ev) => {
+ const content = ev.target.result;
+ setOpenApiData(content);
+ setCreateAppModalOpen(true);
+ });
+ } catch (err) {
+ console.log("Error in dropzone: ", err);
+ }
+
+ try {
+ reader.readAsText(files[0]);
+ } catch (error) {
+ toast("Failed to read file");
+ }
+ };
+
+ // Validation and redirect are handled inside AppCreationModal
+
useEffect(() => {
const apps = currTab === 1 ? userApps : orgApps;
const filteredUserAppdata = filterApps(apps, searchQuery, selectedCategory, selectedLabel);
@@ -1853,6 +1880,10 @@ const Apps2 = (props) => {
}
return (
+
{
theme={theme}
globalUrl={globalUrl}
isCloud={isCloud}
+ startOpenApi={openApiData?.length > 0}
+ prefillOpenApiData={openApiData}
/>
{appsModalLoad}
@@ -2198,6 +2231,24 @@ const Apps2 = (props) => {
>
)}
+
{
Create an App
+
@@ -2365,6 +2417,7 @@ const Apps2 = (props) => {
+
);
};
diff --git a/frontend/src/views/DashboardViews.jsx b/frontend/src/views/DashboardViews.jsx
index 0748e80a..98d03344 100644
--- a/frontend/src/views/DashboardViews.jsx
+++ b/frontend/src/views/DashboardViews.jsx
@@ -8,7 +8,7 @@ import { useNavigate, Link, useParams } from "react-router-dom";
import { ToastContainer, toast } from "react-toastify"
import Draggable from "react-draggable";
-import DashboardBarchart, { LoadStats } from '../components/DashboardBarchart.jsx';
+import { LoadStats } from '../components/LineChartWrapper.jsx';
import {
Autocomplete,
@@ -828,9 +828,14 @@ const Dashboard = (props) => {
}
-
diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx
index 8b4772c6..ac1babe2 100755
--- a/frontend/src/views/Docs.jsx
+++ b/frontend/src/views/Docs.jsx
@@ -400,6 +400,10 @@ const Docs = (defaultprops) => {
if (propkey === "app_creation") {
navigate('/docs/apps#app-creation-introduction')
}
+
+ if (propkey === "api") {
+ navigate('/docs/API')
+ }
}
@@ -690,7 +694,8 @@ const Docs = (defaultprops) => {
const Heading = (props) => {
const [hover, setHover] = useState(false);
- var id = props.children[0].toLowerCase().toString()
+
+ var id = (props.children?.[0] ?? props.children ?? '').toString().toLowerCase();
if (props.level <= 3) {
id = props.children[0].toLowerCase().toString().replaceAll(" ", "-");
}
diff --git a/frontend/src/views/NewDashboard.jsx b/frontend/src/views/NewDashboard.jsx
new file mode 100644
index 00000000..630e6dec
--- /dev/null
+++ b/frontend/src/views/NewDashboard.jsx
@@ -0,0 +1,266 @@
+import React, { useEffect, useState, useContext, useRef, useCallback } from 'react';
+import {
+ Typography,
+ Grid,
+ Paper,
+ Box,
+ Stack,
+ Chip,
+ Avatar,
+ Divider,
+ Select,
+ MenuItem,
+} from '@mui/material';
+import TrendingUpIcon from '@mui/icons-material/TrendingUp';
+import TrendingDownIcon from '@mui/icons-material/TrendingDown';
+import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
+import TaskAltIcon from '@mui/icons-material/TaskAlt';
+import SuccessFailedRunsWidget from '../components/SuccessFailedRunsWidget.jsx';
+import RunsOverTimeWidget from '../components/RunsOverTimeWidget.jsx';
+import { Context } from '../context/ContextApi.jsx';
+import CircularProgress from '@mui/material/CircularProgress';
+import { useNavigate } from 'react-router-dom';
+import DashboardOnboarding from '../components/DashboardOnboarding.jsx';
+
+const NewDashboard = (props) => {
+ const { globalUrl, userdata } = props;
+
+ // const [workflows, setWorkflows] = useState([]);
+ const { leftSideBarOpenByClick } = useContext(Context);
+ const [sfwControls, setSfwControls] = useState(null);
+ const [loadingSfw, setLoadingSfw] = useState(true);
+ const [loadingRot, setLoadingRot] = useState(true);
+ const [loadingNoti, setLoadingNoti] = useState(true);
+ const [showOverlay, setShowOverlay] = useState(true);
+ const [totals, setTotals] = useState({ days: 30, mode: 'workflows', totalRuns: 0, successRuns: 0, failedRuns: 0, activeDays: 0, timeSavedMinutes: 0, moneySavedDollars: 0 });
+ const [notifications, setNotifications] = useState([]);
+ const [onboardingOpen, setOnboardingOpen] = useState(() => {
+ try {
+ return localStorage.getItem("dashboard_onboarding_complete") === "true" ? false : true;
+ } catch {
+ return true;
+ }
+ });
+ const [overrideDays, setOverrideDays] = useState(undefined);
+ const [rotMonthOverride, setRotMonthOverride] = useState(undefined);
+
+ const navigate = useNavigate();
+ const handleSfwControls = useCallback((node) => {
+ setSfwControls(node);
+ }, []);
+
+ const formatCurrencyCompact = (value) => {
+ const n = Math.max(0, Number(value) || 0);
+ const abs = Math.abs(n);
+ const fmt = (x, suffix) => `${(Math.round(x * 10) / 10).toString().replace(/\.0$/, '')}${suffix}`;
+ if (abs >= 1e9) return `$${fmt(n / 1e9, 'B')}`;
+ if (abs >= 1e6) return `$${fmt(n / 1e6, 'M')}`;
+ if (abs >= 1e3) return `$${fmt(n / 1e3, 'k')}`;
+ return `$${Math.round(n).toLocaleString()}`;
+ };
+
+ const formatTimeDisplay = (mins) => {
+ const totalMins = Math.max(0, Math.round(mins || 0));
+ if (totalMins < 60) return { display: `${totalMins}m`, title: `${totalMins} minutes` };
+ const totalHours = Math.floor(totalMins / 60);
+ if (totalHours >= 24) {
+ const days = Math.floor(totalHours / 24);
+ return { display: `${days}d`, title: `${totalHours} hours` };
+ }
+ return { display: `${totalHours}h`, title: `${totalHours} hours` };
+ };
+
+ const timeFmt = formatTimeDisplay(totals.timeSavedMinutes);
+ const STATIC_TIME_PERCENT = '62%';
+ const STATIC_MONEY_PERCENT = '46%';
+
+ const unreadCount = notifications.filter(n => n && n.read === false).length;
+ const readCount = notifications.filter(n => n && n.read === true).length;
+
+ // Current values
+ // 1 Workflow run = 15 minutes
+ // 1 Workflow run = $25
+
+ const kpis = [
+ { value: timeFmt.display, title: timeFmt.title, label: 'Time saved', icon:
, percentage: STATIC_TIME_PERCENT, color: '#5cc879' },
+ { value: formatCurrencyCompact(totals.moneySavedDollars), label: 'Money saved', icon:
, percentage: STATIC_MONEY_PERCENT, color: '#5cc879' },
+ { value: String(unreadCount), label: 'Total errors', icon:
, percentage: "", color: '#f87171' },
+ { value: String(readCount), label: 'Errors resolved', icon:
, percentage: "", color: '#5cc879' },
+ ];
+
+ const getGreeting = () => {
+ try {
+ const hour = new Date().getHours();
+ if (hour < 5) return 'Good night';
+ if (hour < 12) return 'Good morning';
+ if (hour < 18) return 'Good afternoon';
+ return 'Good evening';
+ } catch {
+ return 'Hey';
+ }
+ };
+
+ const displayName = userdata !== undefined && userdata?.username !== undefined ? userdata?.username?.split('@')[0]?.charAt(0)?.toUpperCase() + userdata?.username?.split('@')[0]?.slice(1) : 'User';
+
+ useEffect(() => {
+ let t;
+ const anyLoading = loadingSfw || loadingRot || loadingNoti;
+ if (anyLoading) {
+ t = setShowOverlay(true);
+ } else {
+ setShowOverlay(false);
+ }
+ return () => { if (t) clearTimeout(t); };
+ }, [loadingSfw, loadingRot, loadingNoti]);
+
+ // Auto-open onboarding when there aren't enough active days of stats
+ useEffect(() => {
+ try {
+ const alreadyDone = localStorage.getItem("dashboard_onboarding_complete") === "true";
+ if (alreadyDone) {
+ setOnboardingOpen(false);
+ return;
+ }
+ const active = Number(totals?.activeDays || 0);
+ setOnboardingOpen(active < 5);
+ } catch {
+ setOnboardingOpen(true);
+ }
+ }, [totals?.activeDays]);
+
+ // Load notifications
+ useEffect(() => {
+ const loadNotifications = async () => {
+ try {
+ const resp = await fetch(`${globalUrl}/api/v1/notifications`, {
+ method: 'GET',
+ credentials: 'include',
+ headers: { 'Content-Type': 'application/json' },
+ });
+ if (resp.status !== 200) {
+ setNotifications([]);
+ return;
+ }
+ const data = await resp.json();
+ const list = Array.isArray(data?.notifications) ? data.notifications : (Array.isArray(data) ? data : []);
+ setNotifications(list.filter(Boolean));
+ } catch (e) {
+ setNotifications([]);
+ } finally {
+ setLoadingNoti(false);
+ }
+ };
+
+ loadNotifications();
+ }, [globalUrl]);
+
+ // useEffect(() => {
+ // // Lightweight workflows list for selector in success/failed widget
+ // const loadWorkflows = async () => {
+ // try {
+ // const resp = await fetch(`${globalUrl}/api/v1/workflows`, {
+ // method: 'GET',
+ // credentials: 'include',
+ // headers: { 'Content-Type': 'application/json' },
+ // });
+ // if (resp.status !== 200) {
+ // return;
+ // }
+ // const data = await resp.json();
+ // const list = Array.isArray(data?.workflows) ? data.workflows : (Array.isArray(data) ? data : []);
+ // const normalized = list.filter(Boolean).map((w, idx) => ({ id: w?.id || w?.ID || `${idx}`, name: w?.name || w?.Name || `Workflow ${idx+1}` }));
+ // setWorkflows(normalized);
+ // } catch (e) {
+ // // ignore
+ // }
+ // };
+
+ // loadWorkflows();
+ // }, [globalUrl]);
+
+ return (
+
+
setOnboardingOpen(false)}
+ onExplore={() => {
+ // Ensure overrides are set before closing modal
+ setOverrideDays(5);
+ setRotMonthOverride(new Date(new Date().getFullYear(), new Date().getMonth(), 1));
+
+ // Close modal immediately to trigger data fetching
+ setOnboardingOpen(false);
+ }}
+ headerTitle="Unlock your Dashboard"
+ headerSubtitle="Complete these steps to start seeing insights."
+ />
+ {showOverlay && (
+
+
+
+ Loading dashboard…
+
+
+ )}
+ {/* Header / Greeting */}
+
+ {`${getGreeting()}, ${displayName ?? 'User'}!`}
+ <>
+ {sfwControls}
+ >
+
+
+ {/* KPI cards */}
+
+ {kpis.map((kpi) => (
+
+ {
+ if (kpi.label.toLowerCase().includes('total errors')) {
+ // navigate to notifications page
+ navigate('/admin?admin_tab=notifications');
+ }
+ }}
+
+ >
+
+
+ {kpi.value}
+ {kpi.label}
+
+
+ {kpi.icon}
+ {kpi.percentage}
+
+
+
+
+ ))}
+
+
+ {/* Success/Failed widget uses its own internal sub-cards; make wrapper transparent */}
+
+
+
+
+ {/* Runs over time section */}
+
+
+
+
+ );
+};
+
+export default NewDashboard;
+
+
diff --git a/frontend/src/views/RunWorkflow.jsx b/frontend/src/views/RunWorkflow.jsx
index 654646b7..cf8e9ad8 100644
--- a/frontend/src/views/RunWorkflow.jsx
+++ b/frontend/src/views/RunWorkflow.jsx
@@ -72,6 +72,7 @@ const RunWorkflow = (defaultprops) => {
const [executionLoading, setExecutionLoading] = useState(false);
const [executionData, setExecutionData] = React.useState({});
const [executionRunning, setExecutionRunning] = useState(false);
+ const [disableButtons, setDisableButtons] = useState(false);
const [workflowQuestion, setWorkflowQuestion] = useState("");
const [selectedOrganization, setSelectedOrganization] = React.useState(undefined);
const [apps, setApps] = React.useState([]);
@@ -84,12 +85,14 @@ const RunWorkflow = (defaultprops) => {
const [workflows, setWorkflows] = React.useState([])
const [boxWidth, setBoxWidth] = React.useState(500)
const [inputQuestions, setInputQuestions] = React.useState([])
+ const [agentic, setAgentic] = React.useState(false)
const searchParams = new URLSearchParams(window.location.search)
const answer = searchParams.get("answer")
const execution_id = searchParams.get("reference_execution")
const authorization = searchParams.get("authorization")
const sourceNode = searchParams.get("source_node")
+ const decisionId = searchParams.get("decision_id") // ONLY for agentic workflows
const backendUrl = searchParams.get("backend_url") || globalUrl
useEffect(() => {
@@ -162,11 +165,8 @@ const RunWorkflow = (defaultprops) => {
}
}
- // Used to swap from login to register. True = login, false = register
-
// Error messages etc
const [executionInfo, setExecutionInfo] = useState("");
-
const handleValidateForm = (executionArgument) => {
// Check if every field exists
if (executionArgument === undefined || executionArgument === null) {
@@ -184,9 +184,12 @@ const RunWorkflow = (defaultprops) => {
}
}
- //console.log("EXEC: ", executionArgument)
+ // FIXME: Error with User Input + Required arg (?)
+ // Somehow validation is not happening as it should, and it just checks all
+ // questions if none are selected
for (var key in executionArgument) {
if (executionArgument[key] === undefined || executionArgument[key] === null || executionArgument[key] === "") {
+ console.log("Unanswered, required question: ", key)
return false
}
}
@@ -334,17 +337,18 @@ const RunWorkflow = (defaultprops) => {
}
const validate = validateJson(executionData.result)
-
return (
{workflowQuestion !== "" ? null :
-
+
}
{workflowQuestion !== "" ? null :
validate.valid === false ?
-
+ {validate?.result !== undefined && validate?.result !== null && validate?.result.length > 0 ?
+
+ : null }
{
stop()
setMessage("")
- setExecutionLoading(true)
setExecutionData({})
setExecutionInfo("")
+ setTimeout(() => {
+ setExecutionLoading(true)
+ }, 2500)
+
var data = {
"execution_argument": executionArgument,
"execution_source": "form",
@@ -462,6 +469,14 @@ const RunWorkflow = (defaultprops) => {
fetchBody.body = JSON.stringify(data)
}
+ if (agentic === true) {
+ if (url.includes("?")) {
+ url += `&agentic=true&decision_id=${decisionId}`
+ } else {
+ url += `?agentic=true&decision_id=${decisionId}`
+ }
+ }
+
// IF there is an execution argument, we should use it
fetch(url, fetchBody)
.then((response) => {
@@ -480,25 +495,30 @@ const RunWorkflow = (defaultprops) => {
}
}
- if ((response.status === 401 || response.status === 403) && authorization === undefined || authorization === null || authorization.length === 0) {
- toast(`This form is not available for you to run. If you this is an error, contact ${supportEmail} with a link to this form`)
- }
+ //if ((response.status === 401 || response.status === 403) && authorization === undefined || authorization === null || authorization?.length === 0) {
+ // toast(`This form is not available for you to run. If you this is an error, contact ${supportEmail} with a link to this form (2)`)
+ //}
return response.json()
})
.then(responseJson => {
+ //if (responseJson.success === true) {
+ // setDisableButtons(true)
+ //}
+
setExecutionLoading(false)
- if (responseJson.execution_id !== undefined && responseJson.execution_id !== null && responseJson.execution_id.length > 0) {
+ if (responseJson?.execution_id !== undefined && responseJson?.execution_id !== null && responseJson?.execution_id?.length > 0) {
navigate(`?execution_id=${responseJson.execution_id}`)
}
if (responseJson.success === false) {
+
console.log("Failed sending execution request")
- if (responseJson.reason !== undefined && responseJson.reason !== null) {
+ if (responseJson?.reason !== undefined && responseJson?.reason !== null) {
if (responseJson?.reason?.toLowerCase().includes("already clicked")) {
- setMessage("Already answered. You may close this window (2).")
+ setMessage("This form has been answered. You may close this window.")
} else {
- toast.warn(responseJson.reason)
+ toast.warn(responseJson?.reason)
}
}
@@ -520,11 +540,17 @@ const RunWorkflow = (defaultprops) => {
setExecutionRequest(responseJson)
start()
}
+
+ // If execution_id or authorization, add them to the URL
+ if (responseJson?.execution_id !== undefined && responseJson?.execution_id !== null && responseJson?.execution_id?.length > 0 && responseJson?.authorization !== undefined && responseJson?.authorization !== null && responseJson?.authorization?.length > 0) {
+ navigate(`?execution_id=${responseJson.execution_id}&authorization=${responseJson.authorization}`)
+ }
}
})
.catch(error => {
//setExecutionInfo("Error in workflow startup: " + error)
- toast.warn("Error submitting form. Please try again.")
+ console.log("Error starting workflow: ", error)
+ toast.warn(`Error submitting form. Please try again: ${error}`)
stop()
setMessage("")
@@ -597,8 +623,8 @@ const RunWorkflow = (defaultprops) => {
if (realtimeMarkdown !== undefined && realtimeMarkdown !== null && realtimeMarkdown.length > 0) {
const newmarkdown = realtimeMarkdown.replace(`{{ ${workflow_id} }}`, "", -1)
setRealtimeMarkdown(newmarkdown)
- } else if (inputWorkflow.form_control.input_markdown !== undefined && inputWorkflow.form_control.input_markdown !== null && inputWorkflow.form_control.input_markdown.length > 0) {
- const newmarkdown = inputWorkflow.form_control.input_markdown.replace(`{{ ${workflow_id} }}`, "", -1)
+ } else if (inputWorkflow?.form_control?.input_markdown !== undefined && inputWorkflow?.form_control?.input_markdown !== null && inputWorkflow?.form_control?.input_markdown.length > 0) {
+ const newmarkdown = inputWorkflow?.form_control?.input_markdown.replace(`{{ ${workflow_id} }}`, "", -1)
setRealtimeMarkdown(newmarkdown)
}
}
@@ -608,10 +634,10 @@ const RunWorkflow = (defaultprops) => {
console.log("Get workflow error: ", error.toString())
if (realtimeMarkdown !== undefined && realtimeMarkdown !== null && realtimeMarkdown.length > 0) {
- const newmarkdown = inputWorkflow.form_control.input_markdown.replace(`{{ ${workflow_id} }}`, "", -1)
+ const newmarkdown = inputWorkflow?.form_control?.input_markdown.replace(`{{ ${workflow_id} }}`, "", -1)
setRealtimeMarkdown(newmarkdown)
- } else if (inputWorkflow.form_control.input_markdown !== undefined && inputWorkflow.form_control.input_markdown !== null && inputWorkflow.form_control.input_markdown.length > 0) {
- const newmarkdown = inputWorkflow.form_control.input_markdown.replace(`{{ ${workflow_id} }}`, "", -1)
+ } else if (inputWorkflow?.form_control?.input_markdown !== undefined && inputWorkflow?.form_control?.input_markdown !== null && inputWorkflow?.form_control?.input_markdown.length > 0) {
+ const newmarkdown = inputWorkflow?.form_control?.input_markdown.replace(`{{ ${workflow_id} }}`, "", -1)
setRealtimeMarkdown(newmarkdown)
}
})
@@ -646,6 +672,7 @@ const RunWorkflow = (defaultprops) => {
trig.parameters = []
}
+ newexec = {}
for (var paramkey in trig.parameters) {
const param = trig.parameters[paramkey]
if (param.name !== "input_questions") {
@@ -683,6 +710,7 @@ const RunWorkflow = (defaultprops) => {
}
}
+ console.log("Setting exec arg: ", newexec)
setExecutionArgument(newexec)
}
@@ -733,10 +761,10 @@ const RunWorkflow = (defaultprops) => {
setInputQuestions(workflow.input_questions)
}
- if (workflow.form_control.input_markdown !== undefined && workflow.form_control.input_markdown !== null && workflow.form_control.input_markdown.length > 0) {
+ if (workflow?.form_control?.input_markdown !== undefined && workflow?.form_control?.input_markdown !== null && workflow?.form_control?.input_markdown.length > 0) {
// Look for {{ uuid }} format, and try to run that workflow with their account
// This is a hack, but a fun one.
- var newmarkdown = workflow.form_control.input_markdown.replace("", "")
+ var newmarkdown = workflow?.form_control?.input_markdown.replace("", "")
const uuidRegex = /{{\s[a-f0-9-]+\s}}/g
const found = newmarkdown.match(uuidRegex)
@@ -784,8 +812,8 @@ const RunWorkflow = (defaultprops) => {
}
}
- if (workflow.status !== "WAITING") {
- setMessage("Already answered. You may close this window (3).")
+ if (workflow.status === "EXECUTING" || workflow.status === "SUCCESS" || workflow.status === "ABORTED" || workflow.status === "STOPPED" || workflow.status === "FAILURE" || workflow.status === "FINISHED") {
+ setMessage("Already handled. You may close this window.")
}
}
@@ -806,13 +834,17 @@ const RunWorkflow = (defaultprops) => {
console.log("Status not 200 for workflows :O!");
}
- if ((response.status === 401 || response.status === 403) && authorization === undefined || authorization === null || authorization.length === 0) {
- toast(`This form is not available to you. If you think this is an error, please contact ${supportEmail} with the URL.`)
- }
+ //if (response.status >= 400 && authorization === undefined || authorization === null || authorization.length === 0) {
+ // toast.warn(`This form may not be available to you. If you think this is an error, please contact ${supportEmail} with the URL.`)
+ //}
return response.json()
})
.then((responseJson) => {
+ if (responseJson.success === false) {
+ return
+ }
+
// Not sure why this is necessary.
if (responseJson.isValid === undefined) {
responseJson.isValid = true;
@@ -1008,14 +1040,78 @@ const RunWorkflow = (defaultprops) => {
return response.json();
})
.then((responseJson) => {
- if (responseJson.success == false) {
+ if (responseJson?.success == false) {
return
}
- if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null && execution_id.length > 0 && authorization.length > 0 && (workflow.id === undefined || workflow.id === null || workflow.id.length === 0) && responseJson.workflow !== undefined && responseJson.workflow !== null) {
+ if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null && execution_id.length > 0 && authorization.length > 0 && disableButtons === false && responseJson?.status !== "" && responseJson?.status !== "WAITING") {
+ setDisableButtons(true)
+ }
+ //if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null && execution_id.length > 0 && authorization.length > 0 && (workflow.id === undefined || workflow.id === null || workflow.id.length === 0) && responseJson.workflow !== undefined && responseJson.workflow !== null) {
+ if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null && execution_id.length > 0 && authorization.length > 0 && responseJson.workflow !== undefined && responseJson.workflow !== null) {
setupSourcenode(responseJson.workflow, sourceNode)
setWorkflow(responseJson.workflow)
+
+ //const decisionId = searchParams.get("decision_id") // ONLY for agentic workflows
+ // Check for decision_id in url
+ if (decisionId?.length > 0 && responseJson?.workflow?.actions?.length > 0 && sourceNode?.length > 0 && responseJson?.results?.length > 0) {
+ console.log("Setting workflow: ", responseJson.workflow, ", EXEC RESULTS: ", responseJson.results)
+
+ setAgentic(true)
+
+ for (var resultkey in responseJson.results) {
+ const result = responseJson.results[resultkey]
+ if (result.action.id !== sourceNode) {
+ continue
+ }
+
+ const validated = validateJson(result.result)
+ if (!validated.valid) {
+ console.log("Error parsing result: ", validated.error)
+ continue
+ }
+
+ var parsedresult = validated.result
+ console.log("PARSED RES: ", parsedresult)
+ if (parsedresult?.decisions?.length > 0) {
+ var newexec = executionArgument
+ if (newexec === undefined || newexec === null || Object.keys(newexec).length === 0) {
+ newexec = {}
+ }
+
+ for (var decisionkey in parsedresult?.decisions) {
+ const decision = parsedresult.decisions[decisionkey]
+ if (decision?.run_details?.id !== decisionId) {
+ continue
+ }
+
+ for (var fieldkey in decision?.fields) {
+ const field = decision.fields[fieldkey]
+ if (field.key === "question" && !inputQuestions.find(q => q.name=== field.value)) {
+ console.log("QUESTION: ", field)
+ const newquestion = {
+ "name": field.value,
+ "value": field.key+"_"+fieldkey,
+ }
+
+ inputQuestions.push(newquestion)
+
+ newexec[newquestion.value] = ""
+ }
+ }
+ }
+
+ setInputQuestions([...inputQuestions] )
+ console.log("EXEC: ", newexec)
+ setExecutionArgument(newexec)
+
+ responseJson.workflow.input_questions = inputQuestions
+ setWorkflow(responseJson?.workflow)
+ setDisableButtons(false)
+ }
+ }
+ }
}
@@ -1031,12 +1127,12 @@ const RunWorkflow = (defaultprops) => {
localStorage.setItem(storageKey, JSON.stringify(value))
}
- if (realtimeMarkdown !== undefined && realtimeMarkdown !== null && realtimeMarkdown.length > 0) {
+ if (realtimeMarkdown !== undefined && realtimeMarkdown !== null && realtimeMarkdown?.length > 0) {
const newmarkdown = realtimeMarkdown.replace(`{{ ${responseJson.workflow.id} }}`, responseJson.result, -1)
setRealtimeMarkdown(newmarkdown)
- } else if (workflow.form_control.input_markdown !== undefined && workflow.form_control.input_markdown !== null && workflow.form_control.input_markdown.length > 0) {
- const newmarkdown = workflow.form_control.input_markdown.replace(`{{ ${responseJson.workflow.id} }}`, responseJson.result, -1)
+ } else if (workflow?.form_control?.input_markdown !== undefined && workflow?.form_control?.input_markdown !== null && workflow?.form_control?.input_markdown.length > 0) {
+ const newmarkdown = workflow?.form_control?.input_markdown.replace(`{{ ${responseJson.workflow.id} }}`, responseJson.result, -1)
setRealtimeMarkdown(newmarkdown)
}
@@ -1072,7 +1168,6 @@ const RunWorkflow = (defaultprops) => {
getWorkflow(props.match.params.key, sourceNode)
if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null) {
- console.log("Get execution: ", execution_id)
fetchUpdates(execution_id, authorization, true)
}
@@ -1136,13 +1231,13 @@ const RunWorkflow = (defaultprops) => {
const buttonStyle = {borderRadius: 25, height: 50, fontSize: 18, backgroundImage: handleValidateForm(executionArgument) || executionLoading ? buttonBackground : "grey", color: "white"}
// Check if all fields are filled in?
- var disabledButtons = executionLoading || executionRunning || message.length > 0
+ var disabledButtons = executionLoading || executionRunning || message.length > 0 || disableButtons
if (disabledButtons === false && workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0) {
// Check field values
//disabledButtons = handleValidateForm(executionArgument)
}
- const organization = selectedOrganization !== undefined && selectedOrganization !== null ? selectedOrganization.name : "Unknown"
+ const organization = selectedOrganization !== undefined && selectedOrganization !== null ? selectedOrganization.name : ""
const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.org !== undefined && selectedOrganization.org !== null? selectedOrganization.org : "support@shuffler.io"
//const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.contact !== undefined && selectedOrganization.contact !== null? selectedOrganization.contact : "support@shuffler.io"
@@ -1321,12 +1416,12 @@ const RunWorkflow = (defaultprops) => {
- Loading Form Details...
+ Loading Details...
:
- {workflowQuestion !== "" || (workflow.form_control.input_markdown !== undefined && workflow.form_control.input_markdown !== null && workflow.form_control.input_markdown.length > 0) ?
+ {workflowQuestion !== "" || (workflow?.form_control?.input_markdown !== undefined && workflow?.form_control?.input_markdown !== null && workflow?.form_control?.input_markdown.length > 0) ?
{
}}
rehypePlugins={[rehypeRaw]}
>
- {workflowQuestion !== "" ? workflowQuestion : realtimeMarkdown !== undefined && realtimeMarkdown !== null && realtimeMarkdown.length > 0 ? realtimeMarkdown : workflow.form_control.input_markdown}
+ {workflowQuestion !== "" ? workflowQuestion : realtimeMarkdown !== undefined && realtimeMarkdown !== null && realtimeMarkdown.length > 0 ? realtimeMarkdown : workflow?.form_control?.input_markdown}
: null}