@xdelivered/emberflow 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +153 -0
- package/bin/commands.test.ts +185 -0
- package/bin/commands.ts +306 -0
- package/bin/emberflow.mjs +38 -0
- package/bin/init.test.ts +165 -0
- package/bin/init.ts +297 -0
- package/bin/runtime.mjs +105 -0
- package/bin/runtime.test.ts +167 -0
- package/bin/skillTargets.test.ts +108 -0
- package/bin/skillTargets.ts +36 -0
- package/bin/skillTemplates.test.ts +37 -0
- package/bin/tsconfig.json +23 -0
- package/dist/bin/commands.d.ts +24 -0
- package/dist/bin/commands.js +252 -0
- package/dist/bin/init.d.ts +17 -0
- package/dist/bin/init.js +241 -0
- package/dist/bin/runtime.d.mts +39 -0
- package/dist/bin/runtime.mjs +96 -0
- package/dist/bin/skillTargets.d.ts +13 -0
- package/dist/bin/skillTargets.js +22 -0
- package/dist/server/agents/claudeAdapter.d.ts +8 -0
- package/dist/server/agents/claudeAdapter.js +190 -0
- package/dist/server/agents/claudeParse.d.ts +2 -0
- package/dist/server/agents/claudeParse.js +162 -0
- package/dist/server/agents/codexAdapter.d.ts +13 -0
- package/dist/server/agents/codexAdapter.js +174 -0
- package/dist/server/agents/codexParse.d.ts +2 -0
- package/dist/server/agents/codexParse.js +74 -0
- package/dist/server/agents/detect.d.ts +23 -0
- package/dist/server/agents/detect.js +35 -0
- package/dist/server/agents/gitScope.d.ts +24 -0
- package/dist/server/agents/gitScope.js +95 -0
- package/dist/server/agents/modelRejectionHint.d.ts +8 -0
- package/dist/server/agents/modelRejectionHint.js +12 -0
- package/dist/server/agents/prompt.d.ts +79 -0
- package/dist/server/agents/prompt.js +364 -0
- package/dist/server/agents/runManager.d.ts +90 -0
- package/dist/server/agents/runManager.js +259 -0
- package/dist/server/agents/types.d.ts +15 -0
- package/dist/server/agents/types.js +1 -0
- package/dist/server/apiStore.d.ts +60 -0
- package/dist/server/apiStore.js +249 -0
- package/dist/server/auth/enforce.d.ts +1 -0
- package/dist/server/auth/enforce.js +6 -0
- package/dist/server/auth/types.d.ts +1 -0
- package/dist/server/auth/types.js +1 -0
- package/dist/server/auth/verifiers.d.ts +1 -0
- package/dist/server/auth/verifiers.js +4 -0
- package/dist/server/authAttach.d.ts +19 -0
- package/dist/server/authAttach.js +79 -0
- package/dist/server/cli.d.ts +7 -0
- package/dist/server/cli.js +804 -0
- package/dist/server/client.d.ts +130 -0
- package/dist/server/client.js +247 -0
- package/dist/server/environments.d.ts +103 -0
- package/dist/server/environments.js +486 -0
- package/dist/server/flowStore.d.ts +37 -0
- package/dist/server/flowStore.js +117 -0
- package/dist/server/httpOperations.d.ts +32 -0
- package/dist/server/httpOperations.js +62 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1022 -0
- package/dist/server/infrastructure.d.ts +51 -0
- package/dist/server/infrastructure.js +116 -0
- package/dist/server/login.d.ts +21 -0
- package/dist/server/login.js +44 -0
- package/dist/server/mcp.d.ts +1 -0
- package/dist/server/mcp.js +182 -0
- package/dist/server/migrateFlows.d.ts +8 -0
- package/dist/server/migrateFlows.js +25 -0
- package/dist/server/mockHandler.d.ts +15 -0
- package/dist/server/mockHandler.js +35 -0
- package/dist/server/nodesPayload.d.ts +14 -0
- package/dist/server/nodesPayload.js +14 -0
- package/dist/server/normalizeFlow.d.ts +21 -0
- package/dist/server/normalizeFlow.js +48 -0
- package/dist/server/openBrowser.d.ts +8 -0
- package/dist/server/openBrowser.js +16 -0
- package/dist/server/operationResult.d.ts +12 -0
- package/dist/server/operationResult.js +26 -0
- package/dist/server/pathGuard.d.ts +1 -0
- package/dist/server/pathGuard.js +16 -0
- package/dist/server/pathSafety.d.ts +20 -0
- package/dist/server/pathSafety.js +35 -0
- package/dist/server/projectConfig.d.ts +47 -0
- package/dist/server/projectConfig.js +61 -0
- package/dist/server/projectMode.d.ts +26 -0
- package/dist/server/projectMode.js +44 -0
- package/dist/server/redact.d.ts +6 -0
- package/dist/server/redact.js +37 -0
- package/dist/server/runRegistry.d.ts +84 -0
- package/dist/server/runRegistry.js +200 -0
- package/dist/server/runtime/RuntimeAdapter.d.ts +22 -0
- package/dist/server/runtime/RuntimeAdapter.js +1 -0
- package/dist/server/runtime/expressAdapter.d.ts +19 -0
- package/dist/server/runtime/expressAdapter.js +56 -0
- package/dist/server/scenarioTest.d.ts +14 -0
- package/dist/server/scenarioTest.js +66 -0
- package/dist/server/subflowRunner.d.ts +32 -0
- package/dist/server/subflowRunner.js +72 -0
- package/dist/server/testRunner.d.ts +70 -0
- package/dist/server/testRunner.js +151 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +2 -0
- package/dist/src/engine/authVerify.d.ts +33 -0
- package/dist/src/engine/authVerify.js +63 -0
- package/dist/src/engine/diagnostics.d.ts +62 -0
- package/dist/src/engine/diagnostics.js +148 -0
- package/dist/src/engine/executor.d.ts +151 -0
- package/dist/src/engine/executor.js +684 -0
- package/dist/src/engine/httpError.d.ts +7 -0
- package/dist/src/engine/httpError.js +14 -0
- package/dist/src/engine/index.d.ts +11 -0
- package/dist/src/engine/index.js +11 -0
- package/dist/src/engine/output.d.ts +11 -0
- package/dist/src/engine/output.js +20 -0
- package/dist/src/engine/publish.d.ts +30 -0
- package/dist/src/engine/publish.js +95 -0
- package/dist/src/engine/registry.d.ts +34 -0
- package/dist/src/engine/registry.js +67 -0
- package/dist/src/engine/scenarios.d.ts +12 -0
- package/dist/src/engine/scenarios.js +19 -0
- package/dist/src/engine/schemaCheck.d.ts +4 -0
- package/dist/src/engine/schemaCheck.js +34 -0
- package/dist/src/engine/trace.d.ts +12 -0
- package/dist/src/engine/trace.js +12 -0
- package/dist/src/engine/types.d.ts +308 -0
- package/dist/src/engine/types.js +1 -0
- package/dist/src/engine/validation.d.ts +26 -0
- package/dist/src/engine/validation.js +318 -0
- package/dist/src/flows/anomaly-flows.d.ts +23 -0
- package/dist/src/flows/anomaly-flows.js +151 -0
- package/dist/src/flows/city-sweep-flow.d.ts +10 -0
- package/dist/src/flows/city-sweep-flow.js +127 -0
- package/dist/src/flows/login-flow.d.ts +13 -0
- package/dist/src/flows/login-flow.js +145 -0
- package/dist/src/flows/pradar-flows.d.ts +5 -0
- package/dist/src/flows/pradar-flows.js +375 -0
- package/dist/src/flows/weather-flow.d.ts +12 -0
- package/dist/src/flows/weather-flow.js +150 -0
- package/dist/src/lib/pathParams.d.ts +3 -0
- package/dist/src/lib/pathParams.js +6 -0
- package/dist/src/nodes/anomaly.d.ts +2 -0
- package/dist/src/nodes/anomaly.js +556 -0
- package/dist/src/nodes/flow-control.d.ts +7 -0
- package/dist/src/nodes/flow-control.js +270 -0
- package/dist/src/nodes/index.d.ts +3 -0
- package/dist/src/nodes/index.js +20 -0
- package/dist/src/nodes/login.d.ts +7 -0
- package/dist/src/nodes/login.js +158 -0
- package/dist/src/nodes/loops.d.ts +11 -0
- package/dist/src/nodes/loops.js +60 -0
- package/dist/src/nodes/pradar/index.d.ts +7 -0
- package/dist/src/nodes/pradar/index.js +9 -0
- package/dist/src/nodes/pradar/logic.d.ts +189 -0
- package/dist/src/nodes/pradar/logic.js +370 -0
- package/dist/src/nodes/pradar/nodes.d.ts +42 -0
- package/dist/src/nodes/pradar/nodes.js +801 -0
- package/dist/src/nodes/requireAuth.d.ts +20 -0
- package/dist/src/nodes/requireAuth.js +48 -0
- package/dist/src/nodes/response.d.ts +8 -0
- package/dist/src/nodes/response.js +33 -0
- package/dist/src/nodes/weather.d.ts +33 -0
- package/dist/src/nodes/weather.js +260 -0
- package/package.json +92 -0
- package/server/agentRoute.test.ts +157 -0
- package/server/agents/__fixtures__/fake-claude-writer.mjs +23 -0
- package/server/agents/__fixtures__/fake-claude.mjs +55 -0
- package/server/agents/__fixtures__/fake-codex-writer.mjs +27 -0
- package/server/agents/__fixtures__/fake-codex.mjs +52 -0
- package/server/agents/__fixtures__/fake-version-empty.mjs +3 -0
- package/server/agents/__fixtures__/fake-version-fail.mjs +4 -0
- package/server/agents/__fixtures__/fake-version-garbage.mjs +4 -0
- package/server/agents/__fixtures__/fake-version-good.mjs +4 -0
- package/server/agents/__fixtures__/fake-version-hang.mjs +3 -0
- package/server/agents/claudeAdapter.test.ts +127 -0
- package/server/agents/claudeAdapter.ts +212 -0
- package/server/agents/claudeParse.ts +193 -0
- package/server/agents/codexAdapter.test.ts +125 -0
- package/server/agents/codexAdapter.ts +198 -0
- package/server/agents/codexParse.test.ts +58 -0
- package/server/agents/codexParse.ts +126 -0
- package/server/agents/detect.test.ts +65 -0
- package/server/agents/detect.ts +43 -0
- package/server/agents/gitScope.test.ts +129 -0
- package/server/agents/gitScope.ts +110 -0
- package/server/agents/modelRejectionHint.test.ts +30 -0
- package/server/agents/modelRejectionHint.ts +14 -0
- package/server/agents/prompt.test.ts +613 -0
- package/server/agents/prompt.ts +527 -0
- package/server/agents/runManager.test.ts +251 -0
- package/server/agents/runManager.ts +286 -0
- package/server/agents/types.ts +17 -0
- package/server/apiMount.test.ts +591 -0
- package/server/apiStore.test.ts +324 -0
- package/server/apiStore.ts +241 -0
- package/server/auth/enforce.test.ts +61 -0
- package/server/auth/enforce.ts +6 -0
- package/server/auth/types.ts +5 -0
- package/server/auth/verifiers.test.ts +37 -0
- package/server/auth/verifiers.ts +9 -0
- package/server/authAttach.test.ts +175 -0
- package/server/authAttach.ts +99 -0
- package/server/cli.ts +847 -0
- package/server/cliAuth.test.ts +229 -0
- package/server/cliCreate.test.ts +131 -0
- package/server/cliDoctor.test.ts +326 -0
- package/server/cliTest.test.ts +735 -0
- package/server/client.ts +314 -0
- package/server/demoProject.test.ts +29 -0
- package/server/diagnosticsRoute.test.ts +169 -0
- package/server/environments.test.ts +620 -0
- package/server/environments.ts +603 -0
- package/server/environmentsRoute.test.ts +385 -0
- package/server/errorWorkflow.test.ts +205 -0
- package/server/flowStore.test.ts +84 -0
- package/server/flowStore.ts +125 -0
- package/server/httpOperations.test.ts +377 -0
- package/server/httpOperations.ts +96 -0
- package/server/index.ts +1111 -0
- package/server/infrastructure.test.ts +154 -0
- package/server/infrastructure.ts +161 -0
- package/server/infrastructureRoute.test.ts +105 -0
- package/server/login.test.ts +153 -0
- package/server/login.ts +67 -0
- package/server/mcp.ts +278 -0
- package/server/migrateFlows.test.ts +55 -0
- package/server/migrateFlows.ts +23 -0
- package/server/mockHandler.test.ts +138 -0
- package/server/mockHandler.ts +52 -0
- package/server/mockRun.test.ts +367 -0
- package/server/mockServe.test.ts +467 -0
- package/server/nodesPayload.test.ts +29 -0
- package/server/nodesPayload.ts +26 -0
- package/server/normalizeFlow.test.ts +80 -0
- package/server/normalizeFlow.ts +59 -0
- package/server/openBrowser.test.ts +20 -0
- package/server/openBrowser.ts +22 -0
- package/server/operationResult.test.ts +67 -0
- package/server/operationResult.ts +27 -0
- package/server/pathGuard.test.ts +19 -0
- package/server/pathGuard.ts +12 -0
- package/server/pathSafety.test.ts +38 -0
- package/server/pathSafety.ts +33 -0
- package/server/projectConfig.test.ts +67 -0
- package/server/projectConfig.ts +101 -0
- package/server/projectMode.test.ts +106 -0
- package/server/projectMode.ts +59 -0
- package/server/redact.test.ts +88 -0
- package/server/redact.ts +35 -0
- package/server/redactEgress.test.ts +281 -0
- package/server/runRegistry.ts +270 -0
- package/server/runsAuth.test.ts +119 -0
- package/server/runtime/RuntimeAdapter.ts +24 -0
- package/server/runtime/expressAdapter.test.ts +38 -0
- package/server/runtime/expressAdapter.ts +70 -0
- package/server/scenarioTest.test.ts +148 -0
- package/server/scenarioTest.ts +88 -0
- package/server/setupStatus.test.ts +127 -0
- package/server/subflowRunner.ts +104 -0
- package/server/testRunner.ts +216 -0
- package/server/tsconfig.json +20 -0
- package/server/workflowTestRoute.test.ts +297 -0
- package/src/App.tsx +233 -0
- package/src/components/AgentConsole.tsx +461 -0
- package/src/components/BranchRulesEditor.tsx +156 -0
- package/src/components/CreateModal.tsx +275 -0
- package/src/components/Dock.tsx +487 -0
- package/src/components/EnvironmentDialog.tsx +569 -0
- package/src/components/EnvironmentPicker.tsx +357 -0
- package/src/components/EnvironmentsDialog.tsx +144 -0
- package/src/components/ExecutionPager.tsx +67 -0
- package/src/components/InfraPanel.test.tsx +103 -0
- package/src/components/InfraPanel.tsx +171 -0
- package/src/components/Inspector.tsx +1182 -0
- package/src/components/Json.tsx +50 -0
- package/src/components/JsonModal.tsx +52 -0
- package/src/components/LogMessage.tsx +61 -0
- package/src/components/NodeRunModal.tsx +213 -0
- package/src/components/RunLogPanel.tsx +397 -0
- package/src/components/RunbookView.tsx +1085 -0
- package/src/components/Scenarios.tsx +476 -0
- package/src/components/SettingsDialog.tsx +229 -0
- package/src/components/Sidebar.tsx +436 -0
- package/src/components/StatusBar.tsx +302 -0
- package/src/components/Toolbar.tsx +663 -0
- package/src/components/WelcomeDialog.test.tsx +107 -0
- package/src/components/WelcomeDialog.tsx +412 -0
- package/src/components/agentMarkdown.test.tsx +91 -0
- package/src/components/agentMarkdown.tsx +158 -0
- package/src/components/ui/badge.tsx +31 -0
- package/src/components/ui/button.tsx +46 -0
- package/src/components/ui/combobox.tsx +114 -0
- package/src/components/ui/command.tsx +82 -0
- package/src/components/ui/dialog.tsx +73 -0
- package/src/components/ui/input.tsx +20 -0
- package/src/components/ui/popover.tsx +32 -0
- package/src/components/ui/resizable.tsx +44 -0
- package/src/components/ui/tabs.tsx +36 -0
- package/src/config.ts +4 -0
- package/src/engine/authVerify.ts +81 -0
- package/src/engine/diagnostics.test.ts +437 -0
- package/src/engine/diagnostics.ts +211 -0
- package/src/engine/executor.test.ts +1339 -0
- package/src/engine/executor.ts +840 -0
- package/src/engine/httpError.test.ts +12 -0
- package/src/engine/httpError.ts +16 -0
- package/src/engine/index.ts +11 -0
- package/src/engine/output.ts +21 -0
- package/src/engine/publish.test.ts +176 -0
- package/src/engine/publish.ts +127 -0
- package/src/engine/registry.test.ts +64 -0
- package/src/engine/registry.ts +80 -0
- package/src/engine/scenarios.test.ts +55 -0
- package/src/engine/scenarios.ts +29 -0
- package/src/engine/schemaCheck.test.ts +21 -0
- package/src/engine/schemaCheck.ts +35 -0
- package/src/engine/trace.test.ts +19 -0
- package/src/engine/trace.ts +23 -0
- package/src/engine/types.test.ts +22 -0
- package/src/engine/types.ts +316 -0
- package/src/engine/validation.test.ts +314 -0
- package/src/engine/validation.ts +354 -0
- package/src/flows/anomaly-flows.test.ts +81 -0
- package/src/flows/anomaly-flows.ts +202 -0
- package/src/flows/city-sweep-flow.ts +131 -0
- package/src/flows/flows.test.ts +30 -0
- package/src/flows/login-flow.ts +150 -0
- package/src/flows/pradar-flows.test.ts +204 -0
- package/src/flows/pradar-flows.ts +419 -0
- package/src/flows/weather-flow.ts +154 -0
- package/src/globals.css +147 -0
- package/src/lib/pathParams.test.ts +24 -0
- package/src/lib/pathParams.ts +6 -0
- package/src/lib/registerLens.test.ts +79 -0
- package/src/lib/registerLens.ts +59 -0
- package/src/lib/runbookModel.test.ts +61 -0
- package/src/lib/runbookModel.ts +290 -0
- package/src/lib/runbookProjection.test.ts +417 -0
- package/src/lib/runbookProjection.ts +254 -0
- package/src/lib/useTailAnchor.ts +53 -0
- package/src/lib/utils.ts +7 -0
- package/src/main.tsx +13 -0
- package/src/nodes/anomaly.test.ts +98 -0
- package/src/nodes/anomaly.ts +637 -0
- package/src/nodes/effects-scan.test.ts +44 -0
- package/src/nodes/flow-control.test.ts +280 -0
- package/src/nodes/flow-control.ts +305 -0
- package/src/nodes/index.ts +22 -0
- package/src/nodes/login.test.ts +81 -0
- package/src/nodes/login.ts +186 -0
- package/src/nodes/loops.ts +69 -0
- package/src/nodes/pradar/index.ts +11 -0
- package/src/nodes/pradar/logic.test.ts +288 -0
- package/src/nodes/pradar/logic.ts +582 -0
- package/src/nodes/pradar/nodes.test.ts +146 -0
- package/src/nodes/pradar/nodes.ts +955 -0
- package/src/nodes/requireAuth.test.ts +75 -0
- package/src/nodes/requireAuth.ts +55 -0
- package/src/nodes/response.test.ts +33 -0
- package/src/nodes/response.ts +39 -0
- package/src/nodes/traceDetail.test.ts +39 -0
- package/src/nodes/weather.test.ts +65 -0
- package/src/nodes/weather.ts +306 -0
- package/src/store/agentClient.test.ts +103 -0
- package/src/store/agentClient.ts +131 -0
- package/src/store/apiTree.test.ts +17 -0
- package/src/store/apiTree.ts +73 -0
- package/src/store/builderStore.test.ts +1234 -0
- package/src/store/builderStore.ts +1879 -0
- package/src/store/infraClient.ts +63 -0
- package/src/store/nodeMeta.test.ts +24 -0
- package/src/store/nodeMeta.ts +21 -0
- package/src/store/persistence.test.ts +15 -0
- package/src/store/persistence.ts +54 -0
- package/src/store/serverRunner.ts +381 -0
- package/src/store/setupClient.ts +57 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/studio-dist/assets/index-DNJwW-hM.css +1 -0
- package/studio-dist/assets/index-O26dKRjW.js +65 -0
- package/studio-dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/studio-dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/studio-dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/studio-dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/studio-dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/studio-dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/studio-dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/studio-dist/favicon.svg +1 -0
- package/studio-dist/icons.svg +24 -0
- package/studio-dist/index.html +14 -0
- package/templates/skills/emberflow-basics/SKILL.md +316 -0
- package/templates/skills/emberflow-model-process/SKILL.md +209 -0
- package/templates/skills/emberflow-new-workflow/SKILL.md +211 -0
- package/templates/skills/emberflow-review-workflow/SKILL.md +148 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { FlaskConicalIcon, Loader2Icon, PencilIcon, PlayIcon, PlusIcon, SparklesIcon, Trash2Icon } from 'lucide-react';
|
|
3
|
+
import { Button } from '@/components/ui/button';
|
|
4
|
+
import { Input } from '@/components/ui/input';
|
|
5
|
+
import { useBuilderStore } from '../store/builderStore';
|
|
6
|
+
import { cn } from '@/lib/utils';
|
|
7
|
+
import type {
|
|
8
|
+
FieldDefinition, ScenarioDefinition, ScenarioExpectation, WorkflowDefinition,
|
|
9
|
+
} from '../engine';
|
|
10
|
+
import type { ScenarioTestResult } from '../store/serverRunner';
|
|
11
|
+
|
|
12
|
+
const statusDot: Record<string, string> = {
|
|
13
|
+
succeeded: 'bg-success',
|
|
14
|
+
failed: 'bg-destructive',
|
|
15
|
+
cancelled: 'bg-muted-foreground',
|
|
16
|
+
running: 'bg-highlight animate-pulse',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Compact single-line payload preview: {points: 30, spikeIndex: 25}. */
|
|
20
|
+
function previewPayload(input: Record<string, unknown>): string {
|
|
21
|
+
const parts = Object.entries(input).map(([k, v]) => `${k}: ${JSON.stringify(v)}`);
|
|
22
|
+
return `{${parts.join(', ')}}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Starter payload from the Input node: defaults, plus blanks for declared fields. */
|
|
26
|
+
function seedPayload(flow: WorkflowDefinition): Record<string, unknown> {
|
|
27
|
+
const inputNode = flow.nodes.find((n) => n.type === 'Input');
|
|
28
|
+
if (!inputNode) return {};
|
|
29
|
+
const fields = Array.isArray(inputNode.config.fields)
|
|
30
|
+
? (inputNode.config.fields as FieldDefinition[])
|
|
31
|
+
: [];
|
|
32
|
+
const defaults =
|
|
33
|
+
inputNode.config.defaults && typeof inputNode.config.defaults === 'object'
|
|
34
|
+
? (inputNode.config.defaults as Record<string, unknown>)
|
|
35
|
+
: {};
|
|
36
|
+
const blank: Record<string, unknown> = {
|
|
37
|
+
string: '', number: 0, boolean: false, object: {}, array: [], enum: '', datetime: '',
|
|
38
|
+
};
|
|
39
|
+
const seed: Record<string, unknown> = {};
|
|
40
|
+
for (const f of fields) {
|
|
41
|
+
seed[f.name] = f.name in defaults ? defaults[f.name] : (blank[f.type] ?? '');
|
|
42
|
+
}
|
|
43
|
+
return seed;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface EditorState {
|
|
47
|
+
/** Scenario id being edited, or 'new'. */
|
|
48
|
+
target: string;
|
|
49
|
+
name: string;
|
|
50
|
+
description: string;
|
|
51
|
+
payload: string;
|
|
52
|
+
/** Expect fields, all as raw editor strings; empty = unset. */
|
|
53
|
+
expectStatus: string;
|
|
54
|
+
expectNodes: string;
|
|
55
|
+
expectBody: string;
|
|
56
|
+
error: string | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Editor strings → ScenarioExpectation (undefined when all empty), or an error. */
|
|
60
|
+
function buildExpect(state: EditorState): { expect?: ScenarioExpectation } | { error: string } {
|
|
61
|
+
const expect: ScenarioExpectation = {};
|
|
62
|
+
if (state.expectStatus.trim()) {
|
|
63
|
+
const status = Number(state.expectStatus.trim());
|
|
64
|
+
if (!Number.isInteger(status) || status <= 0) {
|
|
65
|
+
return { error: 'Expected status must be a positive integer' };
|
|
66
|
+
}
|
|
67
|
+
expect.status = status;
|
|
68
|
+
}
|
|
69
|
+
const nodes = state.expectNodes.split(',').map((s) => s.trim()).filter(Boolean);
|
|
70
|
+
if (nodes.length > 0) expect.executedNodes = nodes;
|
|
71
|
+
if (state.expectBody.trim()) {
|
|
72
|
+
try {
|
|
73
|
+
expect.body = JSON.parse(state.expectBody) as unknown;
|
|
74
|
+
} catch {
|
|
75
|
+
return { error: 'Expected body must be valid JSON' };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return Object.keys(expect).length > 0 ? { expect } : {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Compact chip text for a scenario's expectation: "→ 401 · body · nodes". */
|
|
82
|
+
function expectChipText(expect: ScenarioExpectation): string {
|
|
83
|
+
const parts: string[] = [];
|
|
84
|
+
if (expect.status !== undefined) parts.push(String(expect.status));
|
|
85
|
+
if (expect.body !== undefined) parts.push('body');
|
|
86
|
+
if (expect.executedNodes?.length) parts.push('nodes');
|
|
87
|
+
return `→ ${parts.join(' · ')}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function ScenarioEditor({
|
|
91
|
+
state,
|
|
92
|
+
onChange,
|
|
93
|
+
onSave,
|
|
94
|
+
onCancel,
|
|
95
|
+
}: {
|
|
96
|
+
state: EditorState;
|
|
97
|
+
onChange: (next: EditorState) => void;
|
|
98
|
+
onSave: () => void;
|
|
99
|
+
onCancel: () => void;
|
|
100
|
+
}) {
|
|
101
|
+
return (
|
|
102
|
+
<div className="mt-1 space-y-2 rounded-md border border-border bg-card/50 p-3">
|
|
103
|
+
<div className="grid grid-cols-2 gap-2">
|
|
104
|
+
<Input
|
|
105
|
+
value={state.name}
|
|
106
|
+
onChange={(e) => onChange({ ...state, name: e.target.value })}
|
|
107
|
+
placeholder="Scenario name"
|
|
108
|
+
autoFocus
|
|
109
|
+
/>
|
|
110
|
+
<Input
|
|
111
|
+
value={state.description}
|
|
112
|
+
onChange={(e) => onChange({ ...state, description: e.target.value })}
|
|
113
|
+
placeholder="What this scenario demonstrates (optional)"
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
<textarea
|
|
117
|
+
value={state.payload}
|
|
118
|
+
onChange={(e) => onChange({ ...state, payload: e.target.value, error: null })}
|
|
119
|
+
spellCheck={false}
|
|
120
|
+
rows={5}
|
|
121
|
+
className="w-full resize-y rounded-md border border-border bg-background p-3 font-mono text-[12px] leading-relaxed outline-none focus-visible:ring-ring/50 focus-visible:ring-[3px]"
|
|
122
|
+
/>
|
|
123
|
+
<div className="space-y-1.5 rounded-md border border-border/60 bg-background/50 p-2.5">
|
|
124
|
+
<div className="text-[10px] font-medium uppercase tracking-widest text-muted-foreground">
|
|
125
|
+
Expect <span className="normal-case tracking-normal">(optional — makes this scenario a test for `emberflow test` and the Test all button)</span>
|
|
126
|
+
</div>
|
|
127
|
+
<div className="grid grid-cols-2 gap-2">
|
|
128
|
+
<Input
|
|
129
|
+
value={state.expectStatus}
|
|
130
|
+
onChange={(e) => onChange({ ...state, expectStatus: e.target.value, error: null })}
|
|
131
|
+
placeholder="status, e.g. 200"
|
|
132
|
+
spellCheck={false}
|
|
133
|
+
className="font-mono text-[12px]"
|
|
134
|
+
/>
|
|
135
|
+
<Input
|
|
136
|
+
value={state.expectNodes}
|
|
137
|
+
onChange={(e) => onChange({ ...state, expectNodes: e.target.value, error: null })}
|
|
138
|
+
placeholder="executed nodes, e.g. response-ok"
|
|
139
|
+
spellCheck={false}
|
|
140
|
+
className="font-mono text-[12px]"
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
<textarea
|
|
144
|
+
value={state.expectBody}
|
|
145
|
+
onChange={(e) => onChange({ ...state, expectBody: e.target.value, error: null })}
|
|
146
|
+
spellCheck={false}
|
|
147
|
+
rows={2}
|
|
148
|
+
placeholder='body subset, e.g. {"ok": true} — extra keys in the real response are fine'
|
|
149
|
+
className="w-full resize-y rounded-md border border-border bg-background p-2.5 font-mono text-[11.5px] leading-relaxed outline-none placeholder:text-muted-foreground/50 focus-visible:ring-ring/50 focus-visible:ring-[3px]"
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
{state.error && <div className="text-[12px] text-destructive">{state.error}</div>}
|
|
153
|
+
<div className="flex items-center gap-2">
|
|
154
|
+
<Button size="sm" onClick={onSave} disabled={state.name.trim().length === 0}>
|
|
155
|
+
Save scenario
|
|
156
|
+
</Button>
|
|
157
|
+
<Button size="sm" variant="ghost" onClick={onCancel}>
|
|
158
|
+
Cancel
|
|
159
|
+
</Button>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Storybook-style list of a flow's named test inputs. Each scenario runs the
|
|
167
|
+
* whole flow with its payload so branches can be exercised one story at a time.
|
|
168
|
+
*/
|
|
169
|
+
export function ScenariosPanel({ autoNew = false }: { autoNew?: boolean } = {}) {
|
|
170
|
+
const flow = useBuilderStore((s) => s.flow);
|
|
171
|
+
const run = useBuilderStore((s) => s.run);
|
|
172
|
+
const runHistory = useBuilderStore((s) => s.runHistory);
|
|
173
|
+
const runScenario = useBuilderStore((s) => s.runScenario);
|
|
174
|
+
const addScenario = useBuilderStore((s) => s.addScenario);
|
|
175
|
+
const updateScenario = useBuilderStore((s) => s.updateScenario);
|
|
176
|
+
const removeScenario = useBuilderStore((s) => s.removeScenario);
|
|
177
|
+
const testWorkflow = useBuilderStore((s) => s.testWorkflow);
|
|
178
|
+
const testReport = useBuilderStore((s) => s.scenarioTestReports[s.flow.id]);
|
|
179
|
+
const testPending = useBuilderStore((s) => s.scenarioTestPending === s.flow.id);
|
|
180
|
+
const runnerOnline = useBuilderStore((s) => s.runnerOnline);
|
|
181
|
+
const runAgent = useBuilderStore((s) => s.runAgent);
|
|
182
|
+
const agentRunning = useBuilderStore((s) => s.agentRun?.status === 'running');
|
|
183
|
+
const [editor, setEditor] = useState<EditorState | null>(null);
|
|
184
|
+
const [testError, setTestError] = useState<string | null>(null);
|
|
185
|
+
|
|
186
|
+
const scenarios = flow.scenarios ?? [];
|
|
187
|
+
const hasInputNode = flow.nodes.some((n) => n.type === 'Input');
|
|
188
|
+
const busy = run?.status === 'running';
|
|
189
|
+
const hasExpects = scenarios.some((sc) => sc.expect && Object.keys(sc.expect).length > 0);
|
|
190
|
+
|
|
191
|
+
const resultFor = (sc: ScenarioDefinition): ScenarioTestResult | undefined =>
|
|
192
|
+
testReport?.results.find((r) => r.scenario === sc.name);
|
|
193
|
+
|
|
194
|
+
const onCoverWithAi = () =>
|
|
195
|
+
void runAgent({
|
|
196
|
+
action: 'cover-operation',
|
|
197
|
+
flowId: flow.id,
|
|
198
|
+
instruction:
|
|
199
|
+
'Cover this operation: one scenario per branch, each with an expect, verified with the test command.',
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const onTestAll = async () => {
|
|
203
|
+
setTestError(null);
|
|
204
|
+
try {
|
|
205
|
+
await testWorkflow(flow.id);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
setTestError(err instanceof Error ? err.message : 'Test run failed');
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Opened via "New scenario…": jump straight into the create editor.
|
|
212
|
+
const blankEditor = (): EditorState => ({
|
|
213
|
+
target: 'new',
|
|
214
|
+
name: '',
|
|
215
|
+
description: '',
|
|
216
|
+
payload: JSON.stringify(seedPayload(flow), null, 2),
|
|
217
|
+
expectStatus: '',
|
|
218
|
+
expectNodes: '',
|
|
219
|
+
expectBody: '',
|
|
220
|
+
error: null,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
if (autoNew) setEditor(blankEditor());
|
|
225
|
+
// Seed once on mount for the create entry point.
|
|
226
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
227
|
+
}, [autoNew]);
|
|
228
|
+
|
|
229
|
+
const lastRunFor = (sc: ScenarioDefinition) =>
|
|
230
|
+
runHistory.find((r) => r.workflowId === flow.id && r.scenarioName === sc.name);
|
|
231
|
+
|
|
232
|
+
const openEditor = (sc?: ScenarioDefinition) =>
|
|
233
|
+
setEditor(
|
|
234
|
+
sc
|
|
235
|
+
? {
|
|
236
|
+
target: sc.id,
|
|
237
|
+
name: sc.name,
|
|
238
|
+
description: sc.description ?? '',
|
|
239
|
+
payload: JSON.stringify(sc.input, null, 2),
|
|
240
|
+
expectStatus: sc.expect?.status !== undefined ? String(sc.expect.status) : '',
|
|
241
|
+
expectNodes: sc.expect?.executedNodes?.join(', ') ?? '',
|
|
242
|
+
expectBody:
|
|
243
|
+
sc.expect?.body !== undefined ? JSON.stringify(sc.expect.body, null, 2) : '',
|
|
244
|
+
error: null,
|
|
245
|
+
}
|
|
246
|
+
: blankEditor(),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const saveEditor = () => {
|
|
250
|
+
if (!editor) return;
|
|
251
|
+
let input: Record<string, unknown>;
|
|
252
|
+
try {
|
|
253
|
+
const parsed: unknown = JSON.parse(editor.payload);
|
|
254
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
255
|
+
throw new Error('Payload must be a JSON object');
|
|
256
|
+
}
|
|
257
|
+
input = parsed as Record<string, unknown>;
|
|
258
|
+
} catch (err) {
|
|
259
|
+
setEditor({ ...editor, error: err instanceof Error ? err.message : String(err) });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const built = buildExpect(editor);
|
|
263
|
+
if ('error' in built) {
|
|
264
|
+
setEditor({ ...editor, error: built.error });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const description = editor.description.trim() || undefined;
|
|
268
|
+
if (editor.target === 'new') {
|
|
269
|
+
addScenario(editor.name.trim(), input, description, built.expect);
|
|
270
|
+
} else {
|
|
271
|
+
// `expect: undefined` deliberately clears a removed expectation —
|
|
272
|
+
// JSON serialization drops the key on save.
|
|
273
|
+
updateScenario(editor.target, {
|
|
274
|
+
name: editor.name.trim(),
|
|
275
|
+
description,
|
|
276
|
+
input,
|
|
277
|
+
expect: built.expect,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
setEditor(null);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
if (scenarios.length === 0 && !editor) {
|
|
284
|
+
return (
|
|
285
|
+
<div className="px-1 py-2.5">
|
|
286
|
+
<div className="text-[12.5px] leading-relaxed text-muted-foreground/70">
|
|
287
|
+
Scenarios are example inputs for this operation — one per situation you want to see
|
|
288
|
+
it handle. Give a scenario an <span className="font-mono text-[11.5px]">expect</span>{' '}
|
|
289
|
+
and it becomes a test: <span className="font-mono text-[11.5px]">emberflow test</span>{' '}
|
|
290
|
+
and the Test all button will assert it.
|
|
291
|
+
</div>
|
|
292
|
+
<div className="mt-2.5 flex gap-2">
|
|
293
|
+
<Button
|
|
294
|
+
size="sm"
|
|
295
|
+
variant="outline"
|
|
296
|
+
className="gap-1.5"
|
|
297
|
+
disabled={agentRunning}
|
|
298
|
+
onClick={onCoverWithAi}
|
|
299
|
+
>
|
|
300
|
+
<SparklesIcon className="size-3.5" />
|
|
301
|
+
Cover with AI
|
|
302
|
+
</Button>
|
|
303
|
+
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground" onClick={() => openEditor()}>
|
|
304
|
+
<PlusIcon className="size-3.5" />
|
|
305
|
+
Add manually
|
|
306
|
+
</Button>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
<div className="space-y-0.5">
|
|
314
|
+
{!hasInputNode && (
|
|
315
|
+
<div className="px-2 py-1.5 text-[12px] text-muted-foreground/70">
|
|
316
|
+
This flow has no Input node, so scenario payloads are never consumed. Add an
|
|
317
|
+
Input node to drive it.
|
|
318
|
+
</div>
|
|
319
|
+
)}
|
|
320
|
+
<div className="flex items-center gap-2.5 px-2 pb-1.5">
|
|
321
|
+
<Button
|
|
322
|
+
size="sm"
|
|
323
|
+
variant="ghost"
|
|
324
|
+
className="h-6 gap-1.5 text-muted-foreground"
|
|
325
|
+
disabled={agentRunning}
|
|
326
|
+
title="Ask the agent to write one scenario per branch of this operation, each with an expect, and verify them"
|
|
327
|
+
onClick={onCoverWithAi}
|
|
328
|
+
>
|
|
329
|
+
<SparklesIcon className="size-3" />
|
|
330
|
+
Cover with AI
|
|
331
|
+
</Button>
|
|
332
|
+
{hasExpects && (
|
|
333
|
+
<>
|
|
334
|
+
<Button
|
|
335
|
+
size="sm"
|
|
336
|
+
variant="outline"
|
|
337
|
+
className="h-6 gap-1.5"
|
|
338
|
+
disabled={testPending || runnerOnline !== true}
|
|
339
|
+
title={runnerOnline !== true ? 'Runner offline — tests run on the runner' : 'Run every scenario that has an expect and check its assertions'}
|
|
340
|
+
onClick={() => void onTestAll()}
|
|
341
|
+
>
|
|
342
|
+
{testPending ? (
|
|
343
|
+
<Loader2Icon className="size-3 animate-spin" />
|
|
344
|
+
) : (
|
|
345
|
+
<FlaskConicalIcon className="size-3" />
|
|
346
|
+
)}
|
|
347
|
+
Test all
|
|
348
|
+
</Button>
|
|
349
|
+
{testReport && !testPending && (
|
|
350
|
+
<span className="text-[11.5px] text-muted-foreground">
|
|
351
|
+
<span className={cn(testReport.failed > 0 ? 'text-destructive' : 'text-success')}>
|
|
352
|
+
{testReport.failed > 0
|
|
353
|
+
? `${testReport.failed} failed`
|
|
354
|
+
: `${testReport.passed} passed`}
|
|
355
|
+
</span>
|
|
356
|
+
{testReport.failed > 0 && ` · ${testReport.passed} passed`}
|
|
357
|
+
{testReport.skipped > 0 && ` · ${testReport.skipped} skipped`}
|
|
358
|
+
</span>
|
|
359
|
+
)}
|
|
360
|
+
</>
|
|
361
|
+
)}
|
|
362
|
+
{testError && <span className="truncate text-[11.5px] text-destructive">{testError}</span>}
|
|
363
|
+
</div>
|
|
364
|
+
{scenarios.map((sc) => {
|
|
365
|
+
const last = lastRunFor(sc);
|
|
366
|
+
const result = resultFor(sc);
|
|
367
|
+
return (
|
|
368
|
+
<div key={sc.id}>
|
|
369
|
+
<div className="group flex items-center gap-2.5 rounded-md px-2 py-1 hover:bg-card">
|
|
370
|
+
<Button
|
|
371
|
+
variant="ghost"
|
|
372
|
+
size="icon"
|
|
373
|
+
className="size-6 shrink-0 text-muted-foreground hover:text-highlight"
|
|
374
|
+
disabled={busy}
|
|
375
|
+
onClick={() => void runScenario(sc.id)}
|
|
376
|
+
title={`Run "${sc.name}"`}
|
|
377
|
+
>
|
|
378
|
+
<PlayIcon className="size-3.5" />
|
|
379
|
+
</Button>
|
|
380
|
+
<span
|
|
381
|
+
className={cn(
|
|
382
|
+
'size-1.5 shrink-0 rounded-full',
|
|
383
|
+
last ? statusDot[last.status] : 'bg-border',
|
|
384
|
+
)}
|
|
385
|
+
title={last ? `Last run ${last.status}` : 'Not run yet'}
|
|
386
|
+
/>
|
|
387
|
+
<span className="shrink-0 text-[12.5px] font-medium text-foreground">{sc.name}</span>
|
|
388
|
+
{sc.expect && Object.keys(sc.expect).length > 0 && (
|
|
389
|
+
<span
|
|
390
|
+
className={cn(
|
|
391
|
+
'shrink-0 rounded-sm border border-border/60 px-1 font-mono text-[10.5px]',
|
|
392
|
+
result?.status === 'passed' && 'border-success/40 text-success',
|
|
393
|
+
result?.status === 'failed' && 'border-destructive/40 text-destructive',
|
|
394
|
+
!result && 'text-muted-foreground/70',
|
|
395
|
+
)}
|
|
396
|
+
title={
|
|
397
|
+
result?.status === 'passed'
|
|
398
|
+
? 'Last test passed'
|
|
399
|
+
: result?.status === 'failed'
|
|
400
|
+
? 'Last test failed'
|
|
401
|
+
: 'Expectation — run Test all to assert'
|
|
402
|
+
}
|
|
403
|
+
>
|
|
404
|
+
{result?.status === 'passed' && '✓ '}
|
|
405
|
+
{result?.status === 'failed' && '✗ '}
|
|
406
|
+
{expectChipText(sc.expect)}
|
|
407
|
+
</span>
|
|
408
|
+
)}
|
|
409
|
+
{sc.description && (
|
|
410
|
+
<span className="truncate text-[12px] text-muted-foreground">{sc.description}</span>
|
|
411
|
+
)}
|
|
412
|
+
<span className="ml-auto max-w-72 truncate font-mono text-[11px] text-muted-foreground/60">
|
|
413
|
+
{previewPayload(sc.input)}
|
|
414
|
+
</span>
|
|
415
|
+
<span className="flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
|
|
416
|
+
<Button
|
|
417
|
+
variant="ghost"
|
|
418
|
+
size="icon"
|
|
419
|
+
className="size-6 text-muted-foreground"
|
|
420
|
+
onClick={() => openEditor(sc)}
|
|
421
|
+
title="Edit scenario"
|
|
422
|
+
>
|
|
423
|
+
<PencilIcon className="size-3" />
|
|
424
|
+
</Button>
|
|
425
|
+
<Button
|
|
426
|
+
variant="ghost"
|
|
427
|
+
size="icon"
|
|
428
|
+
className="size-6 text-muted-foreground hover:text-destructive"
|
|
429
|
+
onClick={() => removeScenario(sc.id)}
|
|
430
|
+
title="Delete scenario"
|
|
431
|
+
>
|
|
432
|
+
<Trash2Icon className="size-3" />
|
|
433
|
+
</Button>
|
|
434
|
+
</span>
|
|
435
|
+
</div>
|
|
436
|
+
{result?.status === 'failed' && result.failures && (
|
|
437
|
+
<div className="ml-11 space-y-0.5 pb-1">
|
|
438
|
+
{result.failures.map((f, i) => (
|
|
439
|
+
<div key={i} className="font-mono text-[11px] text-destructive/90">
|
|
440
|
+
{f}
|
|
441
|
+
</div>
|
|
442
|
+
))}
|
|
443
|
+
</div>
|
|
444
|
+
)}
|
|
445
|
+
{editor?.target === sc.id && (
|
|
446
|
+
<ScenarioEditor
|
|
447
|
+
state={editor}
|
|
448
|
+
onChange={setEditor}
|
|
449
|
+
onSave={saveEditor}
|
|
450
|
+
onCancel={() => setEditor(null)}
|
|
451
|
+
/>
|
|
452
|
+
)}
|
|
453
|
+
</div>
|
|
454
|
+
);
|
|
455
|
+
})}
|
|
456
|
+
{editor?.target === 'new' ? (
|
|
457
|
+
<ScenarioEditor
|
|
458
|
+
state={editor}
|
|
459
|
+
onChange={setEditor}
|
|
460
|
+
onSave={saveEditor}
|
|
461
|
+
onCancel={() => setEditor(null)}
|
|
462
|
+
/>
|
|
463
|
+
) : (
|
|
464
|
+
<Button
|
|
465
|
+
size="sm"
|
|
466
|
+
variant="ghost"
|
|
467
|
+
className="mt-1 gap-1.5 text-muted-foreground"
|
|
468
|
+
onClick={() => openEditor()}
|
|
469
|
+
>
|
|
470
|
+
<PlusIcon className="size-3.5" />
|
|
471
|
+
New scenario
|
|
472
|
+
</Button>
|
|
473
|
+
)}
|
|
474
|
+
</div>
|
|
475
|
+
);
|
|
476
|
+
}
|