@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,229 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { ArrowLeftIcon, GlobeIcon, RotateCcwIcon, ServerIcon, SettingsIcon } from 'lucide-react';
|
|
3
|
+
import { cn } from '@/lib/utils';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogTitle,
|
|
9
|
+
DialogTrigger,
|
|
10
|
+
} from '@/components/ui/dialog';
|
|
11
|
+
import { useBuilderStore } from '../store/builderStore';
|
|
12
|
+
import { fetchAvailableAgents } from '../store/agentClient';
|
|
13
|
+
import type { AgentKind, DetectedAgent } from '../store/agentClient';
|
|
14
|
+
|
|
15
|
+
const AGENT_LABELS: Record<AgentKind, string> = {
|
|
16
|
+
codex: 'Codex',
|
|
17
|
+
claude: 'Claude',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const ENGINE_OPTIONS: Array<{
|
|
21
|
+
value: 'auto' | 'server' | 'browser';
|
|
22
|
+
label: string;
|
|
23
|
+
hint: string;
|
|
24
|
+
}> = [
|
|
25
|
+
{ value: 'auto', label: 'Auto', hint: 'runner when available (recommended)' },
|
|
26
|
+
{ value: 'server', label: 'Server', hint: 'always the local runner' },
|
|
27
|
+
{ value: 'browser', label: 'Browser', hint: 'in-tab engine, no secrets/CORS-free APIs' },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
function SectionTitle({ children }: { children: React.ReactNode }) {
|
|
31
|
+
return (
|
|
32
|
+
<div className="text-[10px] font-medium uppercase tracking-widest text-muted-foreground">
|
|
33
|
+
{children}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Settings dialog behind the toolbar gear. Absorbs the old EngineChip (execution
|
|
40
|
+
* engine + runner status) and the Reset button, keeping the toolbar itself to
|
|
41
|
+
* authoring + running.
|
|
42
|
+
*/
|
|
43
|
+
export function SettingsDialog() {
|
|
44
|
+
const mode = useBuilderStore((s) => s.executionMode);
|
|
45
|
+
const setMode = useBuilderStore((s) => s.setExecutionMode);
|
|
46
|
+
const runnerOnline = useBuilderStore((s) => s.runnerOnline);
|
|
47
|
+
const resetRun = useBuilderStore((s) => s.resetRun);
|
|
48
|
+
const agentChoice = useBuilderStore((s) => s.agentChoice);
|
|
49
|
+
const setAgentChoice = useBuilderStore((s) => s.setAgentChoice);
|
|
50
|
+
// Open state lives in the store so the Welcome checklist can deep-link here.
|
|
51
|
+
const open = useBuilderStore((s) => s.settingsOpen);
|
|
52
|
+
const setOpen = useBuilderStore((s) => s.setSettingsOpen);
|
|
53
|
+
const fromWelcome = useBuilderStore((s) => s.settingsFromWelcome);
|
|
54
|
+
const setWelcomeOpen = useBuilderStore((s) => s.setWelcomeOpen);
|
|
55
|
+
const [availableAgents, setAvailableAgents] = useState<DetectedAgent[]>([]);
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!open) return;
|
|
59
|
+
let cancelled = false;
|
|
60
|
+
void fetchAvailableAgents().then((agents) => {
|
|
61
|
+
if (!cancelled) setAvailableAgents(agents);
|
|
62
|
+
});
|
|
63
|
+
return () => {
|
|
64
|
+
cancelled = true;
|
|
65
|
+
};
|
|
66
|
+
}, [open]);
|
|
67
|
+
|
|
68
|
+
const effective = mode === 'auto' ? (runnerOnline ? 'server' : 'browser') : mode;
|
|
69
|
+
const offline = effective === 'server' && runnerOnline === false;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
73
|
+
<DialogTrigger asChild>
|
|
74
|
+
<Button
|
|
75
|
+
variant="ghost"
|
|
76
|
+
size="icon"
|
|
77
|
+
aria-label="Settings"
|
|
78
|
+
className="text-muted-foreground hover:text-foreground"
|
|
79
|
+
>
|
|
80
|
+
<SettingsIcon />
|
|
81
|
+
</Button>
|
|
82
|
+
</DialogTrigger>
|
|
83
|
+
<DialogContent className="max-w-md">
|
|
84
|
+
<div className="flex items-center gap-2">
|
|
85
|
+
{fromWelcome && (
|
|
86
|
+
<Button
|
|
87
|
+
variant="ghost"
|
|
88
|
+
size="icon"
|
|
89
|
+
onClick={() => {
|
|
90
|
+
setOpen(false);
|
|
91
|
+
setWelcomeOpen(true);
|
|
92
|
+
}}
|
|
93
|
+
aria-label="Back to setup checklist"
|
|
94
|
+
className="-ml-1.5 size-6 text-muted-foreground hover:text-foreground"
|
|
95
|
+
>
|
|
96
|
+
<ArrowLeftIcon className="size-4" />
|
|
97
|
+
</Button>
|
|
98
|
+
)}
|
|
99
|
+
<DialogTitle>Settings</DialogTitle>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div className="space-y-2">
|
|
103
|
+
<div className="flex items-center gap-2">
|
|
104
|
+
<SectionTitle>Execution engine</SectionTitle>
|
|
105
|
+
<span className="ml-auto flex items-center gap-1.5 text-[11px] text-muted-foreground">
|
|
106
|
+
{effective === 'server' ? (
|
|
107
|
+
<ServerIcon className="size-3" />
|
|
108
|
+
) : (
|
|
109
|
+
<GlobeIcon className="size-3" />
|
|
110
|
+
)}
|
|
111
|
+
<span
|
|
112
|
+
className={cn(
|
|
113
|
+
'size-1.5 rounded-full',
|
|
114
|
+
offline
|
|
115
|
+
? 'bg-destructive'
|
|
116
|
+
: effective === 'server'
|
|
117
|
+
? 'bg-success'
|
|
118
|
+
: 'bg-muted-foreground',
|
|
119
|
+
)}
|
|
120
|
+
/>
|
|
121
|
+
{effective === 'server' ? 'runner online' : offline ? 'runner offline' : 'browser'}
|
|
122
|
+
</span>
|
|
123
|
+
</div>
|
|
124
|
+
<div className="flex flex-col gap-1">
|
|
125
|
+
{ENGINE_OPTIONS.map((option) => (
|
|
126
|
+
<button
|
|
127
|
+
key={option.value}
|
|
128
|
+
onClick={() => setMode(option.value)}
|
|
129
|
+
className={cn(
|
|
130
|
+
'flex cursor-pointer flex-col items-start rounded-md border px-2.5 py-1.5 text-left transition-colors hover:bg-accent',
|
|
131
|
+
mode === option.value ? 'border-ring bg-secondary/60' : 'border-border',
|
|
132
|
+
)}
|
|
133
|
+
>
|
|
134
|
+
<span className="text-[12.5px] font-medium">{option.label}</span>
|
|
135
|
+
<span className="text-[11px] text-muted-foreground">{option.hint}</span>
|
|
136
|
+
</button>
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div className="space-y-2">
|
|
142
|
+
<SectionTitle>Coding agent</SectionTitle>
|
|
143
|
+
{availableAgents.length === 0 ? (
|
|
144
|
+
<span className="text-[12px] text-muted-foreground">
|
|
145
|
+
No agent CLIs detected on PATH (looked for codex, claude).
|
|
146
|
+
</span>
|
|
147
|
+
) : (
|
|
148
|
+
<div className="flex flex-col gap-1">
|
|
149
|
+
{availableAgents.map(({ kind, version }) => (
|
|
150
|
+
<button
|
|
151
|
+
key={kind}
|
|
152
|
+
onClick={() => setAgentChoice({ ...agentChoice, agent: kind })}
|
|
153
|
+
className={cn(
|
|
154
|
+
'flex cursor-pointer items-center rounded-md border px-2.5 py-1.5 text-left transition-colors hover:bg-accent',
|
|
155
|
+
(agentChoice.agent ?? availableAgents[0]?.kind) === kind
|
|
156
|
+
? 'border-ring bg-secondary/60'
|
|
157
|
+
: 'border-border',
|
|
158
|
+
)}
|
|
159
|
+
>
|
|
160
|
+
<span className="text-[12.5px] font-medium">
|
|
161
|
+
{AGENT_LABELS[kind]}
|
|
162
|
+
{version ? <span className="ml-1.5 font-normal text-muted-foreground">— {version}</span> : null}
|
|
163
|
+
</span>
|
|
164
|
+
</button>
|
|
165
|
+
))}
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
<label className="flex flex-col gap-1">
|
|
169
|
+
<span className="text-[11px] text-muted-foreground">Model (blank = default)</span>
|
|
170
|
+
<input
|
|
171
|
+
type="text"
|
|
172
|
+
value={agentChoice.model ?? ''}
|
|
173
|
+
onChange={(e) => setAgentChoice({ ...agentChoice, model: e.target.value.trim() || undefined })}
|
|
174
|
+
placeholder="default"
|
|
175
|
+
className="rounded-md border border-border bg-background px-2.5 py-1.5 text-[12.5px] outline-none focus:border-ring"
|
|
176
|
+
/>
|
|
177
|
+
</label>
|
|
178
|
+
<label className="flex flex-col gap-1">
|
|
179
|
+
<span className="text-[11px] text-muted-foreground">Reasoning effort</span>
|
|
180
|
+
<div className="flex gap-1">
|
|
181
|
+
{(['low', 'medium', 'high'] as const).map((level) => {
|
|
182
|
+
const active = (agentChoice.reasoning ?? 'medium') === level;
|
|
183
|
+
return (
|
|
184
|
+
<button
|
|
185
|
+
key={level}
|
|
186
|
+
type="button"
|
|
187
|
+
onClick={() => setAgentChoice({ ...agentChoice, reasoning: level })}
|
|
188
|
+
className={cn(
|
|
189
|
+
'flex-1 rounded-md border px-2 py-1.5 text-[12px] capitalize transition-colors',
|
|
190
|
+
active
|
|
191
|
+
? 'border-highlight bg-highlight/15 text-foreground'
|
|
192
|
+
: 'border-border text-muted-foreground hover:text-foreground',
|
|
193
|
+
)}
|
|
194
|
+
>
|
|
195
|
+
{level}
|
|
196
|
+
</button>
|
|
197
|
+
);
|
|
198
|
+
})}
|
|
199
|
+
</div>
|
|
200
|
+
<span className="text-[10.5px] text-muted-foreground/70">
|
|
201
|
+
Higher = smarter but slower. Default medium; the agent builds better at medium/high.
|
|
202
|
+
</span>
|
|
203
|
+
</label>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div className="space-y-2">
|
|
207
|
+
<SectionTitle>Run state</SectionTitle>
|
|
208
|
+
<div className="flex items-center justify-between gap-3">
|
|
209
|
+
<span className="text-[12px] text-muted-foreground">
|
|
210
|
+
Clear the current run, its logs and any live execution.
|
|
211
|
+
</span>
|
|
212
|
+
<Button
|
|
213
|
+
variant="outline"
|
|
214
|
+
size="sm"
|
|
215
|
+
className="shrink-0 gap-1.5"
|
|
216
|
+
onClick={() => {
|
|
217
|
+
resetRun();
|
|
218
|
+
setOpen(false);
|
|
219
|
+
}}
|
|
220
|
+
>
|
|
221
|
+
<RotateCcwIcon className="size-3.5" />
|
|
222
|
+
Reset run state
|
|
223
|
+
</Button>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</DialogContent>
|
|
227
|
+
</Dialog>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ChevronDownIcon,
|
|
4
|
+
ChevronRightIcon,
|
|
5
|
+
CopyIcon,
|
|
6
|
+
FolderIcon,
|
|
7
|
+
MoreHorizontalIcon,
|
|
8
|
+
PlusIcon,
|
|
9
|
+
SettingsIcon,
|
|
10
|
+
Trash2Icon,
|
|
11
|
+
WorkflowIcon,
|
|
12
|
+
XIcon,
|
|
13
|
+
} from 'lucide-react';
|
|
14
|
+
import { Button } from '@/components/ui/button';
|
|
15
|
+
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
|
|
16
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
17
|
+
import { Badge } from '@/components/ui/badge';
|
|
18
|
+
import { CreateModal } from './CreateModal';
|
|
19
|
+
import type { CreateModalState } from './CreateModal';
|
|
20
|
+
import { useBuilderStore } from '../store/builderStore';
|
|
21
|
+
import { buildApiTree } from '../store/apiTree';
|
|
22
|
+
import type { ApiTreeNode, OpItem } from '../store/apiTree';
|
|
23
|
+
import { cn } from '@/lib/utils';
|
|
24
|
+
|
|
25
|
+
function methodBadgeVariant(method?: string): 'success' | 'highlight' | 'default' | 'destructive' | 'outline' {
|
|
26
|
+
switch (method) {
|
|
27
|
+
case 'GET':
|
|
28
|
+
return 'success';
|
|
29
|
+
case 'POST':
|
|
30
|
+
return 'highlight';
|
|
31
|
+
case 'DELETE':
|
|
32
|
+
return 'destructive';
|
|
33
|
+
case 'PUT':
|
|
34
|
+
case 'PATCH':
|
|
35
|
+
return 'default';
|
|
36
|
+
default:
|
|
37
|
+
return 'outline';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Every operation id under this node (including nested folders). */
|
|
42
|
+
function collectOpIds(node: ApiTreeNode): string[] {
|
|
43
|
+
return node.operations.map((o) => o.id).concat(node.folders.flatMap(collectOpIds));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The hover "⋯" menu shared by API and operation rows. Reveals on the row's
|
|
48
|
+
* group-hover, stays visible while open. Currently one item — Delete — which
|
|
49
|
+
* fires immediately (no confirm dialog, per the delete-immediately spec).
|
|
50
|
+
*/
|
|
51
|
+
function RowMenu({ label, onDelete }: { label: string; onDelete: () => void }) {
|
|
52
|
+
const [open, setOpen] = useState(false);
|
|
53
|
+
return (
|
|
54
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
55
|
+
<PopoverTrigger asChild>
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
onClick={(e) => e.stopPropagation()}
|
|
59
|
+
aria-label={label}
|
|
60
|
+
title={label}
|
|
61
|
+
className="shrink-0 cursor-pointer rounded-sm p-0.5 text-muted-foreground/50 opacity-0 transition-opacity hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100"
|
|
62
|
+
>
|
|
63
|
+
<MoreHorizontalIcon className="size-3.5" />
|
|
64
|
+
</button>
|
|
65
|
+
</PopoverTrigger>
|
|
66
|
+
<PopoverContent align="end" className="w-40 p-1" onClick={(e) => e.stopPropagation()}>
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
onClick={(e) => {
|
|
70
|
+
e.stopPropagation();
|
|
71
|
+
setOpen(false);
|
|
72
|
+
onDelete();
|
|
73
|
+
}}
|
|
74
|
+
className="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left text-[12.5px] text-destructive transition-colors hover:bg-destructive/10"
|
|
75
|
+
>
|
|
76
|
+
<Trash2Icon className="size-3.5" /> Delete
|
|
77
|
+
</button>
|
|
78
|
+
</PopoverContent>
|
|
79
|
+
</Popover>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function OperationRow({ op, depth }: { op: OpItem; depth: number }) {
|
|
84
|
+
const activeId = useBuilderStore((s) => s.flow.id);
|
|
85
|
+
const switchWorkflow = useBuilderStore((s) => s.switchWorkflow);
|
|
86
|
+
const deleteOperations = useBuilderStore((s) => s.deleteOperations);
|
|
87
|
+
const isActive = op.id === activeId;
|
|
88
|
+
const rowTitle = op.method
|
|
89
|
+
? `HTTP endpoint: ${op.method} ${op.httpPath ?? ''}`.trim()
|
|
90
|
+
: 'Runs inside the studio or as a sub-step — no HTTP endpoint';
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div
|
|
94
|
+
role="button"
|
|
95
|
+
tabIndex={0}
|
|
96
|
+
onClick={() => switchWorkflow(op.id)}
|
|
97
|
+
onKeyDown={(e) => {
|
|
98
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
switchWorkflow(op.id);
|
|
101
|
+
}
|
|
102
|
+
}}
|
|
103
|
+
style={{ paddingLeft: 8 + depth * 14 }}
|
|
104
|
+
title={rowTitle}
|
|
105
|
+
className={cn(
|
|
106
|
+
'group flex w-full cursor-pointer items-center gap-2 rounded-md py-1.5 pr-2.5 text-left text-[12.5px] transition-colors',
|
|
107
|
+
isActive
|
|
108
|
+
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
|
109
|
+
: 'text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground',
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
{op.method ? (
|
|
113
|
+
<Badge variant={methodBadgeVariant(op.method)} className="shrink-0">
|
|
114
|
+
{op.method}
|
|
115
|
+
</Badge>
|
|
116
|
+
) : (
|
|
117
|
+
<span
|
|
118
|
+
className="inline-flex shrink-0 items-center gap-1 text-[10px] text-muted-foreground/50"
|
|
119
|
+
title="Runs inside the studio or as a sub-step — no HTTP endpoint"
|
|
120
|
+
>
|
|
121
|
+
<span className="size-1.5 rounded-full bg-muted-foreground/40" />
|
|
122
|
+
no endpoint
|
|
123
|
+
</span>
|
|
124
|
+
)}
|
|
125
|
+
<span className="truncate">{op.name}</span>
|
|
126
|
+
{op.httpPath && (
|
|
127
|
+
<span className="ml-auto truncate font-mono text-[10px] text-muted-foreground/60">
|
|
128
|
+
{op.httpPath}
|
|
129
|
+
</span>
|
|
130
|
+
)}
|
|
131
|
+
<span className={op.httpPath ? '' : 'ml-auto'}>
|
|
132
|
+
<RowMenu label={`Delete ${op.name}`} onDelete={() => void deleteOperations([op.id])} />
|
|
133
|
+
</span>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function countOps(node: ApiTreeNode): number {
|
|
139
|
+
return node.operations.length + node.folders.reduce((sum, f) => sum + countOps(f), 0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Every operation under an API (including nested folders) that has an HTTP trigger. */
|
|
143
|
+
function collectHttpOps(node: ApiTreeNode): OpItem[] {
|
|
144
|
+
const own = node.operations.filter((op) => op.method && op.httpPath);
|
|
145
|
+
return node.folders.reduce((acc, f) => acc.concat(collectHttpOps(f)), own);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Gear/info affordance on each API row: opens a centered modal with
|
|
150
|
+
* API-level settings. Currently shows the absolute invocation URL
|
|
151
|
+
* (origin + http path) for every operation under this API that has an
|
|
152
|
+
* HTTP trigger, with a per-row copy button — laid out as a settings panel
|
|
153
|
+
* so more API-level sections can be added here later.
|
|
154
|
+
*/
|
|
155
|
+
function ApiSettingsButton({ node }: { node: ApiTreeNode }) {
|
|
156
|
+
const [open, setOpen] = useState(false);
|
|
157
|
+
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
158
|
+
const httpOps = useMemo(() => collectHttpOps(node), [node]);
|
|
159
|
+
|
|
160
|
+
const copy = (op: OpItem) => {
|
|
161
|
+
const url = `${window.location.origin}${op.httpPath}`;
|
|
162
|
+
void navigator.clipboard.writeText(url);
|
|
163
|
+
setCopiedId(op.id);
|
|
164
|
+
window.setTimeout(() => setCopiedId((id) => (id === op.id ? null : id)), 1200);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
169
|
+
<button
|
|
170
|
+
type="button"
|
|
171
|
+
onClick={(e) => {
|
|
172
|
+
e.stopPropagation();
|
|
173
|
+
setOpen(true);
|
|
174
|
+
}}
|
|
175
|
+
aria-label={`${node.name} settings`}
|
|
176
|
+
title="API settings"
|
|
177
|
+
className="ml-1 shrink-0 cursor-pointer rounded-sm p-0.5 text-muted-foreground/50 opacity-0 transition-opacity hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100"
|
|
178
|
+
>
|
|
179
|
+
<SettingsIcon className="size-3" />
|
|
180
|
+
</button>
|
|
181
|
+
<DialogContent className="max-w-lg" onClick={(e) => e.stopPropagation()}>
|
|
182
|
+
<div>
|
|
183
|
+
<DialogTitle>{node.name}</DialogTitle>
|
|
184
|
+
<p className="mt-0.5 text-[12px] text-muted-foreground">
|
|
185
|
+
{httpOps.length} operation{httpOps.length === 1 ? '' : 's'}
|
|
186
|
+
</p>
|
|
187
|
+
</div>
|
|
188
|
+
<div className="flex flex-col gap-1.5">
|
|
189
|
+
<div className="text-[11px] font-semibold tracking-wide text-muted-foreground uppercase">
|
|
190
|
+
Endpoints
|
|
191
|
+
</div>
|
|
192
|
+
<div className="max-h-96 overflow-y-auto rounded-md border border-border">
|
|
193
|
+
{httpOps.length === 0 ? (
|
|
194
|
+
<div className="px-3 py-3 text-[12px] text-muted-foreground/70">
|
|
195
|
+
No HTTP endpoints on this API.
|
|
196
|
+
</div>
|
|
197
|
+
) : (
|
|
198
|
+
<div className="divide-y divide-border">
|
|
199
|
+
{httpOps.map((op) => {
|
|
200
|
+
const url = `${window.location.origin}${op.httpPath}`;
|
|
201
|
+
return (
|
|
202
|
+
<div key={op.id} className="flex items-center gap-2 px-2.5 py-2 hover:bg-accent">
|
|
203
|
+
<Badge variant={methodBadgeVariant(op.method)} className="shrink-0">
|
|
204
|
+
{op.method}
|
|
205
|
+
</Badge>
|
|
206
|
+
<span
|
|
207
|
+
className="min-w-0 flex-1 truncate font-mono text-[11.5px] text-foreground/90"
|
|
208
|
+
title={url}
|
|
209
|
+
>
|
|
210
|
+
{url}
|
|
211
|
+
</span>
|
|
212
|
+
<button
|
|
213
|
+
type="button"
|
|
214
|
+
onClick={() => copy(op)}
|
|
215
|
+
aria-label={`Copy URL for ${op.name}`}
|
|
216
|
+
title="Copy URL"
|
|
217
|
+
className="shrink-0 cursor-pointer rounded-sm p-1 text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground"
|
|
218
|
+
>
|
|
219
|
+
<CopyIcon className="size-3" />
|
|
220
|
+
</button>
|
|
221
|
+
{copiedId === op.id && (
|
|
222
|
+
<span className="shrink-0 text-[10px] text-success">Copied</span>
|
|
223
|
+
)}
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
})}
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</DialogContent>
|
|
232
|
+
</Dialog>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Flatten an API tree into every API/folder path (e.g. 'default', 'billing', 'billing/charges'). */
|
|
237
|
+
function flattenLocations(tree: ApiTreeNode[]): string[] {
|
|
238
|
+
const paths: string[] = [];
|
|
239
|
+
const walk = (node: ApiTreeNode, prefix: string) => {
|
|
240
|
+
const path = prefix ? `${prefix}/${node.name}` : node.name;
|
|
241
|
+
paths.push(path);
|
|
242
|
+
for (const folder of node.folders) walk(folder, path);
|
|
243
|
+
};
|
|
244
|
+
for (const api of tree) walk(api, '');
|
|
245
|
+
return paths;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function TreeNode({
|
|
249
|
+
node,
|
|
250
|
+
keyPath,
|
|
251
|
+
depth,
|
|
252
|
+
isApi,
|
|
253
|
+
collapsed,
|
|
254
|
+
toggle,
|
|
255
|
+
onAddOperation,
|
|
256
|
+
}: {
|
|
257
|
+
node: ApiTreeNode;
|
|
258
|
+
keyPath: string;
|
|
259
|
+
depth: number;
|
|
260
|
+
isApi?: boolean;
|
|
261
|
+
collapsed: Set<string>;
|
|
262
|
+
toggle: (key: string) => void;
|
|
263
|
+
/** Open the agentic create modal scoped to this API (from the hover +). */
|
|
264
|
+
onAddOperation: (location: string) => void;
|
|
265
|
+
}) {
|
|
266
|
+
const isCollapsed = collapsed.has(keyPath);
|
|
267
|
+
const deleteOperations = useBuilderStore((s) => s.deleteOperations);
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<div className={isApi ? 'group' : undefined}>
|
|
271
|
+
<div
|
|
272
|
+
role="button"
|
|
273
|
+
tabIndex={0}
|
|
274
|
+
onClick={() => toggle(keyPath)}
|
|
275
|
+
onKeyDown={(e) => {
|
|
276
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
277
|
+
e.preventDefault();
|
|
278
|
+
toggle(keyPath);
|
|
279
|
+
}
|
|
280
|
+
}}
|
|
281
|
+
style={{ paddingLeft: 8 + depth * 14 }}
|
|
282
|
+
className={cn(
|
|
283
|
+
'flex w-full cursor-pointer items-center gap-1.5 rounded-md py-1.5 pr-2 text-left transition-colors hover:bg-sidebar-accent/60',
|
|
284
|
+
isApi ? 'text-[12.5px] font-semibold text-foreground' : 'text-[12px] font-medium text-foreground/80',
|
|
285
|
+
)}
|
|
286
|
+
>
|
|
287
|
+
{isCollapsed ? (
|
|
288
|
+
<ChevronRightIcon className="size-3.5 shrink-0 text-muted-foreground" />
|
|
289
|
+
) : (
|
|
290
|
+
<ChevronDownIcon className="size-3.5 shrink-0 text-muted-foreground" />
|
|
291
|
+
)}
|
|
292
|
+
{isApi ? (
|
|
293
|
+
<WorkflowIcon className="size-3.5 shrink-0 text-muted-foreground" />
|
|
294
|
+
) : (
|
|
295
|
+
<FolderIcon className="size-3.5 shrink-0 text-muted-foreground" />
|
|
296
|
+
)}
|
|
297
|
+
<span className="truncate">{node.name}</span>
|
|
298
|
+
{isApi && <ApiSettingsButton node={node} />}
|
|
299
|
+
{isApi && (
|
|
300
|
+
<button
|
|
301
|
+
type="button"
|
|
302
|
+
onClick={(e) => {
|
|
303
|
+
e.stopPropagation();
|
|
304
|
+
onAddOperation(keyPath);
|
|
305
|
+
}}
|
|
306
|
+
aria-label={`New operation in ${node.name}`}
|
|
307
|
+
title="New operation"
|
|
308
|
+
className="shrink-0 cursor-pointer rounded-sm p-0.5 text-muted-foreground/50 opacity-0 transition-opacity hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100"
|
|
309
|
+
>
|
|
310
|
+
<PlusIcon className="size-3.5" />
|
|
311
|
+
</button>
|
|
312
|
+
)}
|
|
313
|
+
{isApi && (
|
|
314
|
+
<RowMenu
|
|
315
|
+
label={`Delete ${node.name} API`}
|
|
316
|
+
onDelete={() => void deleteOperations(collectOpIds(node))}
|
|
317
|
+
/>
|
|
318
|
+
)}
|
|
319
|
+
<span className="ml-auto font-mono text-[10px] text-muted-foreground/60">
|
|
320
|
+
{countOps(node)}
|
|
321
|
+
</span>
|
|
322
|
+
</div>
|
|
323
|
+
{!isCollapsed && (
|
|
324
|
+
<>
|
|
325
|
+
{node.folders.map((folder) => (
|
|
326
|
+
<TreeNode
|
|
327
|
+
key={folder.name}
|
|
328
|
+
node={folder}
|
|
329
|
+
keyPath={`${keyPath}/${folder.name}`}
|
|
330
|
+
depth={depth + 1}
|
|
331
|
+
collapsed={collapsed}
|
|
332
|
+
toggle={toggle}
|
|
333
|
+
onAddOperation={onAddOperation}
|
|
334
|
+
/>
|
|
335
|
+
))}
|
|
336
|
+
{node.operations.map((op) => (
|
|
337
|
+
<OperationRow key={op.id} op={op} depth={depth + 1} />
|
|
338
|
+
))}
|
|
339
|
+
</>
|
|
340
|
+
)}
|
|
341
|
+
</div>
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function Sidebar() {
|
|
346
|
+
const workflows = useBuilderStore((s) => s.workflows);
|
|
347
|
+
const toggleSidebar = useBuilderStore((s) => s.toggleSidebar);
|
|
348
|
+
const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
|
|
349
|
+
const [createState, setCreateState] = useState<CreateModalState | null>(null);
|
|
350
|
+
|
|
351
|
+
const tree = useMemo(
|
|
352
|
+
() =>
|
|
353
|
+
buildApiTree(
|
|
354
|
+
workflows.map((w) => ({
|
|
355
|
+
id: w.id,
|
|
356
|
+
name: w.name,
|
|
357
|
+
path: w.path ?? `default/${w.id}`,
|
|
358
|
+
http: w.http,
|
|
359
|
+
})),
|
|
360
|
+
),
|
|
361
|
+
[workflows],
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const locations = useMemo(() => flattenLocations(tree), [tree]);
|
|
365
|
+
|
|
366
|
+
const toggle = (key: string) => {
|
|
367
|
+
setCollapsed((prev) => {
|
|
368
|
+
const next = new Set(prev);
|
|
369
|
+
if (next.has(key)) next.delete(key);
|
|
370
|
+
else next.add(key);
|
|
371
|
+
return next;
|
|
372
|
+
});
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<aside className="flex h-full w-full flex-col bg-sidebar">
|
|
377
|
+
<div className="flex items-center justify-between px-3 pt-3 pb-2">
|
|
378
|
+
<span className="text-[10px] font-medium uppercase tracking-widest text-muted-foreground">
|
|
379
|
+
APIs
|
|
380
|
+
</span>
|
|
381
|
+
<span className="flex items-center">
|
|
382
|
+
<Button
|
|
383
|
+
variant="ghost"
|
|
384
|
+
size="xs"
|
|
385
|
+
aria-label="New operation"
|
|
386
|
+
title="New operation"
|
|
387
|
+
onClick={() => setCreateState({ mode: 'operation' })}
|
|
388
|
+
className="text-muted-foreground hover:text-foreground"
|
|
389
|
+
>
|
|
390
|
+
<PlusIcon />
|
|
391
|
+
</Button>
|
|
392
|
+
<Button
|
|
393
|
+
variant="ghost"
|
|
394
|
+
size="xs"
|
|
395
|
+
onClick={toggleSidebar}
|
|
396
|
+
aria-label="Close sidebar"
|
|
397
|
+
title="Close sidebar (⌘B)"
|
|
398
|
+
className="text-muted-foreground hover:text-foreground"
|
|
399
|
+
>
|
|
400
|
+
<XIcon />
|
|
401
|
+
</Button>
|
|
402
|
+
</span>
|
|
403
|
+
</div>
|
|
404
|
+
<nav className="flex-1 overflow-y-auto px-2 pb-2">
|
|
405
|
+
{tree.map((api) => (
|
|
406
|
+
<TreeNode
|
|
407
|
+
key={api.name}
|
|
408
|
+
node={api}
|
|
409
|
+
keyPath={api.name}
|
|
410
|
+
depth={0}
|
|
411
|
+
isApi
|
|
412
|
+
collapsed={collapsed}
|
|
413
|
+
toggle={toggle}
|
|
414
|
+
onAddOperation={(location) => setCreateState({ mode: 'operation', location })}
|
|
415
|
+
/>
|
|
416
|
+
))}
|
|
417
|
+
</nav>
|
|
418
|
+
<div className="border-t border-sidebar-border p-2">
|
|
419
|
+
<Button
|
|
420
|
+
variant="secondary"
|
|
421
|
+
size="sm"
|
|
422
|
+
className="w-full"
|
|
423
|
+
onClick={() => setCreateState({ mode: 'api' })}
|
|
424
|
+
>
|
|
425
|
+
<PlusIcon /> New API
|
|
426
|
+
</Button>
|
|
427
|
+
</div>
|
|
428
|
+
|
|
429
|
+
<CreateModal
|
|
430
|
+
state={createState}
|
|
431
|
+
onOpenChange={(open) => !open && setCreateState(null)}
|
|
432
|
+
locations={locations}
|
|
433
|
+
/>
|
|
434
|
+
</aside>
|
|
435
|
+
);
|
|
436
|
+
}
|