@flowdot.ai/mcp-server 1.0.3 → 1.0.5
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 +45 -21
- package/README.md +439 -162
- package/bin/flowdot-mcp.js +15 -15
- package/dist/api-client.d.ts +0 -6
- package/dist/api-client.js +0 -6
- package/dist/index.d.ts +0 -23
- package/dist/index.js +0 -24
- package/dist/resources/index.d.ts +1 -11
- package/dist/resources/index.js +2882 -2127
- package/dist/server.d.ts +0 -12
- package/dist/server.js +23 -21
- package/dist/tools/add-connection.d.ts +2 -8
- package/dist/tools/add-connection.js +0 -6
- package/dist/tools/add-custom-node-comment.d.ts +2 -9
- package/dist/tools/add-custom-node-comment.js +0 -7
- package/dist/tools/add-node.d.ts +2 -8
- package/dist/tools/add-node.js +0 -6
- package/dist/tools/add-recipe-step.d.ts +3 -9
- package/dist/tools/add-recipe-step.js +95 -101
- package/dist/tools/add-recipe-store.d.ts +2 -8
- package/dist/tools/add-recipe-store.js +30 -36
- package/dist/tools/add-shared-result-comment.d.ts +2 -8
- package/dist/tools/add-shared-result-comment.js +0 -7
- package/dist/tools/add-workflow-comment.d.ts +2 -8
- package/dist/tools/add-workflow-comment.js +0 -7
- package/dist/tools/agent-chat.d.ts +2 -8
- package/dist/tools/agent-chat.js +0 -10
- package/dist/tools/agent-toolkits.d.ts +3 -10
- package/dist/tools/agent-toolkits.js +294 -336
- package/dist/tools/append-app-code.d.ts +2 -10
- package/dist/tools/append-app-code.js +27 -35
- package/dist/tools/browse-recipes.d.ts +2 -8
- package/dist/tools/browse-recipes.js +0 -7
- package/dist/tools/cancel-execution.d.ts +2 -8
- package/dist/tools/cancel-execution.js +0 -7
- package/dist/tools/clone-app.d.ts +2 -9
- package/dist/tools/clone-app.js +20 -27
- package/dist/tools/copy-custom-node.d.ts +2 -9
- package/dist/tools/copy-custom-node.js +0 -7
- package/dist/tools/create-app-file.d.ts +2 -9
- package/dist/tools/create-app-file.js +18 -25
- package/dist/tools/create-app.d.ts +2 -9
- package/dist/tools/create-app.js +123 -130
- package/dist/tools/create-custom-node.d.ts +3 -10
- package/dist/tools/create-custom-node.js +66 -75
- package/dist/tools/create-input-preset.d.ts +2 -8
- package/dist/tools/create-input-preset.js +0 -7
- package/dist/tools/create-knowledge-category.d.ts +2 -9
- package/dist/tools/create-knowledge-category.js +0 -7
- package/dist/tools/create-recipe.d.ts +2 -8
- package/dist/tools/create-recipe.js +21 -27
- package/dist/tools/create-shared-result.d.ts +2 -8
- package/dist/tools/create-shared-result.js +0 -6
- package/dist/tools/create-workflow.d.ts +2 -8
- package/dist/tools/create-workflow.js +0 -6
- package/dist/tools/delete-app-file.d.ts +2 -9
- package/dist/tools/delete-app-file.js +9 -16
- package/dist/tools/delete-app.d.ts +2 -9
- package/dist/tools/delete-app.js +3 -10
- package/dist/tools/delete-connection.d.ts +2 -8
- package/dist/tools/delete-connection.js +0 -7
- package/dist/tools/delete-custom-node.d.ts +2 -9
- package/dist/tools/delete-custom-node.js +0 -7
- package/dist/tools/delete-input-preset.d.ts +2 -8
- package/dist/tools/delete-input-preset.js +0 -6
- package/dist/tools/delete-knowledge-category.d.ts +2 -8
- package/dist/tools/delete-knowledge-category.js +0 -6
- package/dist/tools/delete-knowledge-document.d.ts +2 -8
- package/dist/tools/delete-knowledge-document.js +0 -6
- package/dist/tools/delete-node.d.ts +2 -8
- package/dist/tools/delete-node.js +0 -7
- package/dist/tools/delete-recipe-step.d.ts +2 -8
- package/dist/tools/delete-recipe-step.js +2 -8
- package/dist/tools/delete-recipe-store.d.ts +2 -8
- package/dist/tools/delete-recipe-store.js +2 -8
- package/dist/tools/delete-recipe.d.ts +2 -8
- package/dist/tools/delete-recipe.js +0 -6
- package/dist/tools/delete-workflow.d.ts +2 -8
- package/dist/tools/delete-workflow.js +0 -7
- package/dist/tools/duplicate-workflow.d.ts +2 -8
- package/dist/tools/duplicate-workflow.js +0 -6
- package/dist/tools/edit-app-code.d.ts +2 -10
- package/dist/tools/edit-app-code.js +34 -43
- package/dist/tools/execute-workflow.d.ts +2 -8
- package/dist/tools/execute-workflow.js +0 -11
- package/dist/tools/favorite-custom-node.d.ts +2 -9
- package/dist/tools/favorite-custom-node.js +0 -8
- package/dist/tools/favorite-recipe.d.ts +2 -8
- package/dist/tools/favorite-recipe.js +2 -8
- package/dist/tools/favorite-workflow.d.ts +2 -8
- package/dist/tools/favorite-workflow.js +0 -7
- package/dist/tools/fork-recipe.d.ts +2 -8
- package/dist/tools/fork-recipe.js +7 -13
- package/dist/tools/get-app-file.d.ts +2 -9
- package/dist/tools/get-app-file.js +20 -28
- package/dist/tools/get-app-template.d.ts +1 -8
- package/dist/tools/get-app-template.js +827 -834
- package/dist/tools/get-app.d.ts +2 -9
- package/dist/tools/get-app.js +54 -66
- package/dist/tools/get-custom-node-comments.d.ts +2 -9
- package/dist/tools/get-custom-node-comments.js +0 -7
- package/dist/tools/get-custom-node-template.d.ts +1 -8
- package/dist/tools/get-custom-node-template.js +60 -73
- package/dist/tools/get-custom-node.d.ts +2 -9
- package/dist/tools/get-custom-node.js +0 -12
- package/dist/tools/get-execution-history.d.ts +2 -8
- package/dist/tools/get-execution-history.js +0 -7
- package/dist/tools/get-execution.d.ts +2 -8
- package/dist/tools/get-execution.js +0 -7
- package/dist/tools/get-input-preset.d.ts +2 -8
- package/dist/tools/get-input-preset.js +0 -7
- package/dist/tools/get-knowledge-document-content.d.ts +6 -0
- package/dist/tools/get-knowledge-document-content.js +42 -0
- package/dist/tools/get-knowledge-document.d.ts +2 -8
- package/dist/tools/get-knowledge-document.js +0 -6
- package/dist/tools/get-knowledge-storage.d.ts +2 -8
- package/dist/tools/get-knowledge-storage.js +0 -7
- package/dist/tools/get-node-connections.d.ts +2 -8
- package/dist/tools/get-node-connections.js +0 -7
- package/dist/tools/get-node-schema.d.ts +2 -8
- package/dist/tools/get-node-schema.js +0 -13
- package/dist/tools/get-public-workflows.d.ts +2 -8
- package/dist/tools/get-public-workflows.js +0 -7
- package/dist/tools/get-recipe-definition.d.ts +2 -10
- package/dist/tools/get-recipe-definition.js +1 -10
- package/dist/tools/get-recipe.d.ts +2 -8
- package/dist/tools/get-recipe.js +10 -19
- package/dist/tools/get-shared-result-comments.d.ts +2 -8
- package/dist/tools/get-shared-result-comments.js +0 -6
- package/dist/tools/get-shared-result.d.ts +2 -8
- package/dist/tools/get-shared-result.js +0 -8
- package/dist/tools/get-workflow-comments.d.ts +2 -8
- package/dist/tools/get-workflow-comments.js +0 -7
- package/dist/tools/get-workflow-details.d.ts +2 -8
- package/dist/tools/get-workflow-details.js +0 -8
- package/dist/tools/get-workflow-graph.d.ts +2 -8
- package/dist/tools/get-workflow-graph.js +1 -10
- package/dist/tools/get-workflow-inputs-schema.d.ts +2 -8
- package/dist/tools/get-workflow-inputs-schema.js +0 -7
- package/dist/tools/get-workflow-metrics.d.ts +2 -8
- package/dist/tools/get-workflow-metrics.js +1 -9
- package/dist/tools/get-workflow-public-url.d.ts +2 -8
- package/dist/tools/get-workflow-public-url.js +0 -6
- package/dist/tools/get-workflow-tags.d.ts +2 -8
- package/dist/tools/get-workflow-tags.js +0 -7
- package/dist/tools/index.d.ts +2 -11
- package/dist/tools/index.js +12 -139
- package/dist/tools/insert-app-code.d.ts +2 -10
- package/dist/tools/insert-app-code.js +35 -43
- package/dist/tools/link-app-workflow.d.ts +2 -9
- package/dist/tools/link-app-workflow.js +21 -28
- package/dist/tools/link-recipe.d.ts +2 -8
- package/dist/tools/link-recipe.js +18 -24
- package/dist/tools/list-app-files.d.ts +2 -9
- package/dist/tools/list-app-files.js +10 -20
- package/dist/tools/list-apps.d.ts +2 -9
- package/dist/tools/list-apps.js +8 -15
- package/dist/tools/list-available-nodes.d.ts +2 -8
- package/dist/tools/list-available-nodes.js +0 -13
- package/dist/tools/list-custom-nodes.d.ts +2 -9
- package/dist/tools/list-custom-nodes.js +0 -7
- package/dist/tools/list-input-presets.d.ts +2 -8
- package/dist/tools/list-input-presets.js +0 -7
- package/dist/tools/list-knowledge-categories.d.ts +2 -9
- package/dist/tools/list-knowledge-categories.js +0 -11
- package/dist/tools/list-knowledge-documents.d.ts +2 -9
- package/dist/tools/list-knowledge-documents.js +0 -10
- package/dist/tools/list-recipe-steps.d.ts +2 -8
- package/dist/tools/list-recipe-steps.js +1 -8
- package/dist/tools/list-recipe-stores.d.ts +2 -8
- package/dist/tools/list-recipe-stores.js +0 -6
- package/dist/tools/list-recipes.d.ts +2 -8
- package/dist/tools/list-recipes.js +22 -31
- package/dist/tools/list-shared-results.d.ts +2 -8
- package/dist/tools/list-shared-results.js +0 -6
- package/dist/tools/list-user-teams.d.ts +2 -9
- package/dist/tools/list-user-teams.js +0 -7
- package/dist/tools/list-workflows.d.ts +2 -8
- package/dist/tools/list-workflows.js +0 -8
- package/dist/tools/move-document-to-category.d.ts +2 -8
- package/dist/tools/move-document-to-category.js +0 -6
- package/dist/tools/patch-knowledge-document-section.d.ts +8 -0
- package/dist/tools/patch-knowledge-document-section.js +47 -0
- package/dist/tools/prepend-app-code.d.ts +2 -10
- package/dist/tools/prepend-app-code.js +23 -31
- package/dist/tools/publish-app.d.ts +2 -9
- package/dist/tools/publish-app.js +7 -14
- package/dist/tools/query-knowledge-base.d.ts +2 -9
- package/dist/tools/query-knowledge-base.js +1 -15
- package/dist/tools/rename-app-file.d.ts +2 -9
- package/dist/tools/rename-app-file.js +16 -23
- package/dist/tools/reprocess-document.d.ts +2 -8
- package/dist/tools/reprocess-document.js +0 -6
- package/dist/tools/retry-execution.d.ts +2 -8
- package/dist/tools/retry-execution.js +0 -6
- package/dist/tools/search-apps.d.ts +2 -9
- package/dist/tools/search-apps.js +13 -20
- package/dist/tools/search-public-custom-nodes.d.ts +2 -9
- package/dist/tools/search-public-custom-nodes.js +0 -7
- package/dist/tools/search-workflows.d.ts +2 -8
- package/dist/tools/search-workflows.js +0 -8
- package/dist/tools/search.d.ts +2 -8
- package/dist/tools/search.js +1 -7
- package/dist/tools/set-app-entry-file.d.ts +2 -9
- package/dist/tools/set-app-entry-file.js +13 -20
- package/dist/tools/set-workflow-tags.d.ts +2 -8
- package/dist/tools/set-workflow-tags.js +0 -7
- package/dist/tools/stream-execution.d.ts +2 -9
- package/dist/tools/stream-execution.js +0 -8
- package/dist/tools/toggle-community-inputs.d.ts +2 -8
- package/dist/tools/toggle-community-inputs.js +0 -6
- package/dist/tools/toggle-custom-node-visibility.d.ts +2 -9
- package/dist/tools/toggle-custom-node-visibility.js +0 -8
- package/dist/tools/toggle-workflow-public.d.ts +2 -8
- package/dist/tools/toggle-workflow-public.js +0 -7
- package/dist/tools/transfer-document-ownership.d.ts +2 -9
- package/dist/tools/transfer-document-ownership.js +0 -7
- package/dist/tools/unlink-app-workflow.d.ts +2 -9
- package/dist/tools/unlink-app-workflow.js +2 -9
- package/dist/tools/unpublish-app.d.ts +2 -9
- package/dist/tools/unpublish-app.js +2 -9
- package/dist/tools/update-app-file.d.ts +2 -9
- package/dist/tools/update-app-file.js +15 -22
- package/dist/tools/update-app.d.ts +2 -9
- package/dist/tools/update-app.js +14 -21
- package/dist/tools/update-custom-node.d.ts +3 -10
- package/dist/tools/update-custom-node.js +11 -24
- package/dist/tools/update-input-preset.d.ts +2 -8
- package/dist/tools/update-input-preset.js +0 -7
- package/dist/tools/update-knowledge-category.d.ts +2 -8
- package/dist/tools/update-knowledge-category.js +0 -6
- package/dist/tools/update-knowledge-document-content.d.ts +7 -0
- package/dist/tools/update-knowledge-document-content.js +42 -0
- package/dist/tools/update-node.d.ts +2 -8
- package/dist/tools/update-node.js +0 -7
- package/dist/tools/update-recipe-step.d.ts +2 -8
- package/dist/tools/update-recipe-step.js +6 -12
- package/dist/tools/update-recipe-store.d.ts +2 -8
- package/dist/tools/update-recipe-store.js +5 -11
- package/dist/tools/update-recipe.d.ts +2 -8
- package/dist/tools/update-recipe.js +14 -20
- package/dist/tools/upload-document-from-url.d.ts +2 -9
- package/dist/tools/upload-document-from-url.js +0 -7
- package/dist/tools/upload-text-document.d.ts +2 -9
- package/dist/tools/upload-text-document.js +0 -7
- package/dist/tools/validate-workflow.d.ts +2 -8
- package/dist/tools/validate-workflow.js +0 -8
- package/dist/tools/vote-custom-node.d.ts +2 -9
- package/dist/tools/vote-custom-node.js +0 -7
- package/dist/tools/vote-input-preset.d.ts +2 -8
- package/dist/tools/vote-input-preset.js +0 -6
- package/dist/tools/vote-recipe.d.ts +2 -8
- package/dist/tools/vote-recipe.js +2 -8
- package/dist/tools/vote-shared-result.d.ts +2 -8
- package/dist/tools/vote-shared-result.js +0 -6
- package/dist/tools/vote-workflow.d.ts +2 -8
- package/dist/tools/vote-workflow.js +0 -6
- package/dist/types.d.ts +0 -6
- package/dist/types.js +0 -6
- package/dist/utils/script-validator.d.ts +1 -18
- package/dist/utils/script-validator.js +5 -44
- package/package.json +81 -62
- package/dist/api-client.d.ts.map +0 -1
- package/dist/api-client.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/resources/index.d.ts.map +0 -1
- package/dist/resources/index.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/tools/add-connection.d.ts.map +0 -1
- package/dist/tools/add-connection.js.map +0 -1
- package/dist/tools/add-custom-node-comment.d.ts.map +0 -1
- package/dist/tools/add-custom-node-comment.js.map +0 -1
- package/dist/tools/add-node.d.ts.map +0 -1
- package/dist/tools/add-node.js.map +0 -1
- package/dist/tools/add-recipe-step.d.ts.map +0 -1
- package/dist/tools/add-recipe-step.js.map +0 -1
- package/dist/tools/add-recipe-store.d.ts.map +0 -1
- package/dist/tools/add-recipe-store.js.map +0 -1
- package/dist/tools/add-shared-result-comment.d.ts.map +0 -1
- package/dist/tools/add-shared-result-comment.js.map +0 -1
- package/dist/tools/add-workflow-comment.d.ts.map +0 -1
- package/dist/tools/add-workflow-comment.js.map +0 -1
- package/dist/tools/agent-chat.d.ts.map +0 -1
- package/dist/tools/agent-chat.js.map +0 -1
- package/dist/tools/agent-toolkits.d.ts.map +0 -1
- package/dist/tools/agent-toolkits.js.map +0 -1
- package/dist/tools/append-app-code.d.ts.map +0 -1
- package/dist/tools/append-app-code.js.map +0 -1
- package/dist/tools/browse-recipes.d.ts.map +0 -1
- package/dist/tools/browse-recipes.js.map +0 -1
- package/dist/tools/cancel-execution.d.ts.map +0 -1
- package/dist/tools/cancel-execution.js.map +0 -1
- package/dist/tools/clone-app.d.ts.map +0 -1
- package/dist/tools/clone-app.js.map +0 -1
- package/dist/tools/copy-custom-node.d.ts.map +0 -1
- package/dist/tools/copy-custom-node.js.map +0 -1
- package/dist/tools/create-app-file.d.ts.map +0 -1
- package/dist/tools/create-app-file.js.map +0 -1
- package/dist/tools/create-app.d.ts.map +0 -1
- package/dist/tools/create-app.js.map +0 -1
- package/dist/tools/create-custom-node.d.ts.map +0 -1
- package/dist/tools/create-custom-node.js.map +0 -1
- package/dist/tools/create-input-preset.d.ts.map +0 -1
- package/dist/tools/create-input-preset.js.map +0 -1
- package/dist/tools/create-knowledge-category.d.ts.map +0 -1
- package/dist/tools/create-knowledge-category.js.map +0 -1
- package/dist/tools/create-recipe.d.ts.map +0 -1
- package/dist/tools/create-recipe.js.map +0 -1
- package/dist/tools/create-shared-result.d.ts.map +0 -1
- package/dist/tools/create-shared-result.js.map +0 -1
- package/dist/tools/create-workflow.d.ts.map +0 -1
- package/dist/tools/create-workflow.js.map +0 -1
- package/dist/tools/delete-app-file.d.ts.map +0 -1
- package/dist/tools/delete-app-file.js.map +0 -1
- package/dist/tools/delete-app.d.ts.map +0 -1
- package/dist/tools/delete-app.js.map +0 -1
- package/dist/tools/delete-connection.d.ts.map +0 -1
- package/dist/tools/delete-connection.js.map +0 -1
- package/dist/tools/delete-custom-node.d.ts.map +0 -1
- package/dist/tools/delete-custom-node.js.map +0 -1
- package/dist/tools/delete-input-preset.d.ts.map +0 -1
- package/dist/tools/delete-input-preset.js.map +0 -1
- package/dist/tools/delete-knowledge-category.d.ts.map +0 -1
- package/dist/tools/delete-knowledge-category.js.map +0 -1
- package/dist/tools/delete-knowledge-document.d.ts.map +0 -1
- package/dist/tools/delete-knowledge-document.js.map +0 -1
- package/dist/tools/delete-node.d.ts.map +0 -1
- package/dist/tools/delete-node.js.map +0 -1
- package/dist/tools/delete-recipe-step.d.ts.map +0 -1
- package/dist/tools/delete-recipe-step.js.map +0 -1
- package/dist/tools/delete-recipe-store.d.ts.map +0 -1
- package/dist/tools/delete-recipe-store.js.map +0 -1
- package/dist/tools/delete-recipe.d.ts.map +0 -1
- package/dist/tools/delete-recipe.js.map +0 -1
- package/dist/tools/delete-workflow.d.ts.map +0 -1
- package/dist/tools/delete-workflow.js.map +0 -1
- package/dist/tools/duplicate-workflow.d.ts.map +0 -1
- package/dist/tools/duplicate-workflow.js.map +0 -1
- package/dist/tools/edit-app-code.d.ts.map +0 -1
- package/dist/tools/edit-app-code.js.map +0 -1
- package/dist/tools/execute-workflow.d.ts.map +0 -1
- package/dist/tools/execute-workflow.js.map +0 -1
- package/dist/tools/favorite-custom-node.d.ts.map +0 -1
- package/dist/tools/favorite-custom-node.js.map +0 -1
- package/dist/tools/favorite-recipe.d.ts.map +0 -1
- package/dist/tools/favorite-recipe.js.map +0 -1
- package/dist/tools/favorite-workflow.d.ts.map +0 -1
- package/dist/tools/favorite-workflow.js.map +0 -1
- package/dist/tools/fork-recipe.d.ts.map +0 -1
- package/dist/tools/fork-recipe.js.map +0 -1
- package/dist/tools/get-app-file.d.ts.map +0 -1
- package/dist/tools/get-app-file.js.map +0 -1
- package/dist/tools/get-app-template.d.ts.map +0 -1
- package/dist/tools/get-app-template.js.map +0 -1
- package/dist/tools/get-app.d.ts.map +0 -1
- package/dist/tools/get-app.js.map +0 -1
- package/dist/tools/get-custom-node-comments.d.ts.map +0 -1
- package/dist/tools/get-custom-node-comments.js.map +0 -1
- package/dist/tools/get-custom-node-template.d.ts.map +0 -1
- package/dist/tools/get-custom-node-template.js.map +0 -1
- package/dist/tools/get-custom-node.d.ts.map +0 -1
- package/dist/tools/get-custom-node.js.map +0 -1
- package/dist/tools/get-execution-history.d.ts.map +0 -1
- package/dist/tools/get-execution-history.js.map +0 -1
- package/dist/tools/get-execution.d.ts.map +0 -1
- package/dist/tools/get-execution.js.map +0 -1
- package/dist/tools/get-input-preset.d.ts.map +0 -1
- package/dist/tools/get-input-preset.js.map +0 -1
- package/dist/tools/get-knowledge-document.d.ts.map +0 -1
- package/dist/tools/get-knowledge-document.js.map +0 -1
- package/dist/tools/get-knowledge-storage.d.ts.map +0 -1
- package/dist/tools/get-knowledge-storage.js.map +0 -1
- package/dist/tools/get-node-connections.d.ts.map +0 -1
- package/dist/tools/get-node-connections.js.map +0 -1
- package/dist/tools/get-node-schema.d.ts.map +0 -1
- package/dist/tools/get-node-schema.js.map +0 -1
- package/dist/tools/get-public-workflows.d.ts.map +0 -1
- package/dist/tools/get-public-workflows.js.map +0 -1
- package/dist/tools/get-recipe-definition.d.ts.map +0 -1
- package/dist/tools/get-recipe-definition.js.map +0 -1
- package/dist/tools/get-recipe.d.ts.map +0 -1
- package/dist/tools/get-recipe.js.map +0 -1
- package/dist/tools/get-shared-result-comments.d.ts.map +0 -1
- package/dist/tools/get-shared-result-comments.js.map +0 -1
- package/dist/tools/get-shared-result.d.ts.map +0 -1
- package/dist/tools/get-shared-result.js.map +0 -1
- package/dist/tools/get-workflow-comments.d.ts.map +0 -1
- package/dist/tools/get-workflow-comments.js.map +0 -1
- package/dist/tools/get-workflow-details.d.ts.map +0 -1
- package/dist/tools/get-workflow-details.js.map +0 -1
- package/dist/tools/get-workflow-graph.d.ts.map +0 -1
- package/dist/tools/get-workflow-graph.js.map +0 -1
- package/dist/tools/get-workflow-inputs-schema.d.ts.map +0 -1
- package/dist/tools/get-workflow-inputs-schema.js.map +0 -1
- package/dist/tools/get-workflow-metrics.d.ts.map +0 -1
- package/dist/tools/get-workflow-metrics.js.map +0 -1
- package/dist/tools/get-workflow-public-url.d.ts.map +0 -1
- package/dist/tools/get-workflow-public-url.js.map +0 -1
- package/dist/tools/get-workflow-tags.d.ts.map +0 -1
- package/dist/tools/get-workflow-tags.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/insert-app-code.d.ts.map +0 -1
- package/dist/tools/insert-app-code.js.map +0 -1
- package/dist/tools/link-app-workflow.d.ts.map +0 -1
- package/dist/tools/link-app-workflow.js.map +0 -1
- package/dist/tools/link-recipe.d.ts.map +0 -1
- package/dist/tools/link-recipe.js.map +0 -1
- package/dist/tools/list-app-files.d.ts.map +0 -1
- package/dist/tools/list-app-files.js.map +0 -1
- package/dist/tools/list-apps.d.ts.map +0 -1
- package/dist/tools/list-apps.js.map +0 -1
- package/dist/tools/list-available-nodes.d.ts.map +0 -1
- package/dist/tools/list-available-nodes.js.map +0 -1
- package/dist/tools/list-custom-nodes.d.ts.map +0 -1
- package/dist/tools/list-custom-nodes.js.map +0 -1
- package/dist/tools/list-input-presets.d.ts.map +0 -1
- package/dist/tools/list-input-presets.js.map +0 -1
- package/dist/tools/list-knowledge-categories.d.ts.map +0 -1
- package/dist/tools/list-knowledge-categories.js.map +0 -1
- package/dist/tools/list-knowledge-documents.d.ts.map +0 -1
- package/dist/tools/list-knowledge-documents.js.map +0 -1
- package/dist/tools/list-recipe-steps.d.ts.map +0 -1
- package/dist/tools/list-recipe-steps.js.map +0 -1
- package/dist/tools/list-recipe-stores.d.ts.map +0 -1
- package/dist/tools/list-recipe-stores.js.map +0 -1
- package/dist/tools/list-recipes.d.ts.map +0 -1
- package/dist/tools/list-recipes.js.map +0 -1
- package/dist/tools/list-shared-results.d.ts.map +0 -1
- package/dist/tools/list-shared-results.js.map +0 -1
- package/dist/tools/list-user-teams.d.ts.map +0 -1
- package/dist/tools/list-user-teams.js.map +0 -1
- package/dist/tools/list-workflows.d.ts.map +0 -1
- package/dist/tools/list-workflows.js.map +0 -1
- package/dist/tools/move-document-to-category.d.ts.map +0 -1
- package/dist/tools/move-document-to-category.js.map +0 -1
- package/dist/tools/prepend-app-code.d.ts.map +0 -1
- package/dist/tools/prepend-app-code.js.map +0 -1
- package/dist/tools/publish-app.d.ts.map +0 -1
- package/dist/tools/publish-app.js.map +0 -1
- package/dist/tools/query-knowledge-base.d.ts.map +0 -1
- package/dist/tools/query-knowledge-base.js.map +0 -1
- package/dist/tools/rename-app-file.d.ts.map +0 -1
- package/dist/tools/rename-app-file.js.map +0 -1
- package/dist/tools/reprocess-document.d.ts.map +0 -1
- package/dist/tools/reprocess-document.js.map +0 -1
- package/dist/tools/retry-execution.d.ts.map +0 -1
- package/dist/tools/retry-execution.js.map +0 -1
- package/dist/tools/search-apps.d.ts.map +0 -1
- package/dist/tools/search-apps.js.map +0 -1
- package/dist/tools/search-public-custom-nodes.d.ts.map +0 -1
- package/dist/tools/search-public-custom-nodes.js.map +0 -1
- package/dist/tools/search-workflows.d.ts.map +0 -1
- package/dist/tools/search-workflows.js.map +0 -1
- package/dist/tools/search.d.ts.map +0 -1
- package/dist/tools/search.js.map +0 -1
- package/dist/tools/set-app-entry-file.d.ts.map +0 -1
- package/dist/tools/set-app-entry-file.js.map +0 -1
- package/dist/tools/set-workflow-tags.d.ts.map +0 -1
- package/dist/tools/set-workflow-tags.js.map +0 -1
- package/dist/tools/stream-execution.d.ts.map +0 -1
- package/dist/tools/stream-execution.js.map +0 -1
- package/dist/tools/toggle-community-inputs.d.ts.map +0 -1
- package/dist/tools/toggle-community-inputs.js.map +0 -1
- package/dist/tools/toggle-custom-node-visibility.d.ts.map +0 -1
- package/dist/tools/toggle-custom-node-visibility.js.map +0 -1
- package/dist/tools/toggle-workflow-public.d.ts.map +0 -1
- package/dist/tools/toggle-workflow-public.js.map +0 -1
- package/dist/tools/transfer-document-ownership.d.ts.map +0 -1
- package/dist/tools/transfer-document-ownership.js.map +0 -1
- package/dist/tools/unlink-app-workflow.d.ts.map +0 -1
- package/dist/tools/unlink-app-workflow.js.map +0 -1
- package/dist/tools/unpublish-app.d.ts.map +0 -1
- package/dist/tools/unpublish-app.js.map +0 -1
- package/dist/tools/update-app-file.d.ts.map +0 -1
- package/dist/tools/update-app-file.js.map +0 -1
- package/dist/tools/update-app.d.ts.map +0 -1
- package/dist/tools/update-app.js.map +0 -1
- package/dist/tools/update-custom-node.d.ts.map +0 -1
- package/dist/tools/update-custom-node.js.map +0 -1
- package/dist/tools/update-input-preset.d.ts.map +0 -1
- package/dist/tools/update-input-preset.js.map +0 -1
- package/dist/tools/update-knowledge-category.d.ts.map +0 -1
- package/dist/tools/update-knowledge-category.js.map +0 -1
- package/dist/tools/update-node.d.ts.map +0 -1
- package/dist/tools/update-node.js.map +0 -1
- package/dist/tools/update-recipe-step.d.ts.map +0 -1
- package/dist/tools/update-recipe-step.js.map +0 -1
- package/dist/tools/update-recipe-store.d.ts.map +0 -1
- package/dist/tools/update-recipe-store.js.map +0 -1
- package/dist/tools/update-recipe.d.ts.map +0 -1
- package/dist/tools/update-recipe.js.map +0 -1
- package/dist/tools/upload-document-from-url.d.ts.map +0 -1
- package/dist/tools/upload-document-from-url.js.map +0 -1
- package/dist/tools/upload-text-document.d.ts.map +0 -1
- package/dist/tools/upload-text-document.js.map +0 -1
- package/dist/tools/validate-workflow.d.ts.map +0 -1
- package/dist/tools/validate-workflow.js.map +0 -1
- package/dist/tools/vote-custom-node.d.ts.map +0 -1
- package/dist/tools/vote-custom-node.js.map +0 -1
- package/dist/tools/vote-input-preset.d.ts.map +0 -1
- package/dist/tools/vote-input-preset.js.map +0 -1
- package/dist/tools/vote-recipe.d.ts.map +0 -1
- package/dist/tools/vote-recipe.js.map +0 -1
- package/dist/tools/vote-shared-result.d.ts.map +0 -1
- package/dist/tools/vote-shared-result.js.map +0 -1
- package/dist/tools/vote-workflow.d.ts.map +0 -1
- package/dist/tools/vote-workflow.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils/script-validator.d.ts.map +0 -1
- package/dist/utils/script-validator.js.map +0 -1
|
@@ -1,82 +1,76 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* get_app_template Tool
|
|
3
|
-
*
|
|
4
|
-
* Get example code and templates for creating FlowDot apps.
|
|
5
|
-
* No API call required - returns pre-defined templates.
|
|
6
|
-
*/
|
|
7
1
|
export const getAppTemplateTool = {
|
|
8
2
|
name: 'get_app_template',
|
|
9
|
-
description: `Get example code and templates for creating FlowDot apps.
|
|
10
|
-
|
|
11
|
-
## EXECUTION ENVIRONMENT
|
|
12
|
-
FlowDot apps run in a sandboxed browser iframe with:
|
|
13
|
-
- React 18 (global - use React.useState, React.useEffect, etc.)
|
|
14
|
-
- Tailwind CSS (full utility classes available)
|
|
15
|
-
- FlowDot color tokens: primary-50 to primary-900, secondary-50 to secondary-900
|
|
16
|
-
- invokeWorkflow(workflowHash, inputs) - to call linked workflows
|
|
17
|
-
|
|
18
|
-
## CRITICAL CODE RULES
|
|
19
|
-
1. NO IMPORTS - React is global (use React.useState, React.useEffect, React.useRef, etc.)
|
|
20
|
-
2. MUST include export default at the end: export default MyAppName;
|
|
21
|
-
3. Function must be named: function MyAppName() { ... }
|
|
22
|
-
4. Use Tailwind CSS for ALL styling
|
|
23
|
-
5. NO FORM ELEMENTS - Never use <form> tags (sandbox blocks form submissions)
|
|
24
|
-
6. ALL BUTTONS need type="button" to prevent unwanted form submission behavior
|
|
25
|
-
|
|
26
|
-
## IMPORTANT: NO FORM ELEMENTS
|
|
27
|
-
The sandbox does not allow form submissions. NEVER use <form> tags.
|
|
28
|
-
|
|
29
|
-
❌ WRONG:
|
|
30
|
-
<form onSubmit={handleSubmit}>
|
|
31
|
-
<button type="submit">Submit</button>
|
|
32
|
-
</form>
|
|
33
|
-
|
|
34
|
-
✅ CORRECT:
|
|
35
|
-
<div>
|
|
36
|
-
<input onKeyDown={(e) => e.key === 'Enter' && handleClick()} />
|
|
37
|
-
<button type="button" onClick={handleClick}>Submit</button>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
## WORKFLOW RESPONSE FORMAT
|
|
41
|
-
invokeWorkflow returns data in this structure:
|
|
42
|
-
{
|
|
43
|
-
"data": {
|
|
44
|
-
"[nodeId]": {
|
|
45
|
-
"nodeId": "uuid",
|
|
46
|
-
"nodeTitle": "My Output Node",
|
|
47
|
-
"nodeType": "text_output",
|
|
48
|
-
"outputs": {
|
|
49
|
-
"Consolidated Text": { "value": "the actual data", "metadata": {...} }
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
IMPORTANT: Use this helper function to extract outputs safely:
|
|
56
|
-
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
57
|
-
if (!result?.data) return null;
|
|
58
|
-
const node = Object.values(result.data).find(n => n.nodeTitle === nodeTitle);
|
|
59
|
-
return node?.outputs?.[socketName]?.value;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
Example:
|
|
63
|
-
const result = await invokeWorkflow('hash', { input });
|
|
64
|
-
const data = getNodeOutput(result, 'Output Node');
|
|
65
|
-
if (data) { /* use data */ }
|
|
66
|
-
|
|
67
|
-
## DISPLAY MODES
|
|
68
|
-
Set config.displayMode to:
|
|
69
|
-
- "windowed": Standard view with FlowDot header (default)
|
|
70
|
-
- "fullscreen": Full viewport, minimal floating control bar
|
|
71
|
-
- "embedded": No FlowDot UI, for iframe embedding
|
|
72
|
-
|
|
73
|
-
Available templates:
|
|
74
|
-
- "basic" - Simple form that invokes a workflow
|
|
75
|
-
- "chat" - Chat interface with streaming
|
|
76
|
-
- "dashboard" - Dashboard with multiple workflow calls
|
|
77
|
-
- "form-builder" - Dynamic form based on workflow schema
|
|
78
|
-
- "data-viewer" - Display workflow results in tables/charts
|
|
79
|
-
|
|
3
|
+
description: `Get example code and templates for creating FlowDot apps.
|
|
4
|
+
|
|
5
|
+
## EXECUTION ENVIRONMENT
|
|
6
|
+
FlowDot apps run in a sandboxed browser iframe with:
|
|
7
|
+
- React 18 (global - use React.useState, React.useEffect, etc.)
|
|
8
|
+
- Tailwind CSS (full utility classes available)
|
|
9
|
+
- FlowDot color tokens: primary-50 to primary-900, secondary-50 to secondary-900
|
|
10
|
+
- invokeWorkflow(workflowHash, inputs) - to call linked workflows
|
|
11
|
+
|
|
12
|
+
## CRITICAL CODE RULES
|
|
13
|
+
1. NO IMPORTS - React is global (use React.useState, React.useEffect, React.useRef, etc.)
|
|
14
|
+
2. MUST include export default at the end: export default MyAppName;
|
|
15
|
+
3. Function must be named: function MyAppName() { ... }
|
|
16
|
+
4. Use Tailwind CSS for ALL styling
|
|
17
|
+
5. NO FORM ELEMENTS - Never use <form> tags (sandbox blocks form submissions)
|
|
18
|
+
6. ALL BUTTONS need type="button" to prevent unwanted form submission behavior
|
|
19
|
+
|
|
20
|
+
## IMPORTANT: NO FORM ELEMENTS
|
|
21
|
+
The sandbox does not allow form submissions. NEVER use <form> tags.
|
|
22
|
+
|
|
23
|
+
❌ WRONG:
|
|
24
|
+
<form onSubmit={handleSubmit}>
|
|
25
|
+
<button type="submit">Submit</button>
|
|
26
|
+
</form>
|
|
27
|
+
|
|
28
|
+
✅ CORRECT:
|
|
29
|
+
<div>
|
|
30
|
+
<input onKeyDown={(e) => e.key === 'Enter' && handleClick()} />
|
|
31
|
+
<button type="button" onClick={handleClick}>Submit</button>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
## WORKFLOW RESPONSE FORMAT
|
|
35
|
+
invokeWorkflow returns data in this structure:
|
|
36
|
+
{
|
|
37
|
+
"data": {
|
|
38
|
+
"[nodeId]": {
|
|
39
|
+
"nodeId": "uuid",
|
|
40
|
+
"nodeTitle": "My Output Node",
|
|
41
|
+
"nodeType": "text_output",
|
|
42
|
+
"outputs": {
|
|
43
|
+
"Consolidated Text": { "value": "the actual data", "metadata": {...} }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
IMPORTANT: Use this helper function to extract outputs safely:
|
|
50
|
+
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
51
|
+
if (!result?.data) return null;
|
|
52
|
+
const node = Object.values(result.data).find(n => n.nodeTitle === nodeTitle);
|
|
53
|
+
return node?.outputs?.[socketName]?.value;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
const result = await invokeWorkflow('hash', { input });
|
|
58
|
+
const data = getNodeOutput(result, 'Output Node');
|
|
59
|
+
if (data) { /* use data */ }
|
|
60
|
+
|
|
61
|
+
## DISPLAY MODES
|
|
62
|
+
Set config.displayMode to:
|
|
63
|
+
- "windowed": Standard view with FlowDot header (default)
|
|
64
|
+
- "fullscreen": Full viewport, minimal floating control bar
|
|
65
|
+
- "embedded": No FlowDot UI, for iframe embedding
|
|
66
|
+
|
|
67
|
+
Available templates:
|
|
68
|
+
- "basic" - Simple form that invokes a workflow
|
|
69
|
+
- "chat" - Chat interface with streaming
|
|
70
|
+
- "dashboard" - Dashboard with multiple workflow calls
|
|
71
|
+
- "form-builder" - Dynamic form based on workflow schema
|
|
72
|
+
- "data-viewer" - Display workflow results in tables/charts
|
|
73
|
+
|
|
80
74
|
You can also request "all" to see all templates at once.`,
|
|
81
75
|
inputSchema: {
|
|
82
76
|
type: 'object',
|
|
@@ -93,667 +87,667 @@ const TEMPLATES = {
|
|
|
93
87
|
basic: {
|
|
94
88
|
name: 'Basic Form App',
|
|
95
89
|
description: 'A simple form that submits data to a workflow and displays results.',
|
|
96
|
-
code: `function BasicFormApp() {
|
|
97
|
-
const [input, setInput] = React.useState('');
|
|
98
|
-
const [result, setResult] = React.useState(null);
|
|
99
|
-
const [loading, setLoading] = React.useState(false);
|
|
100
|
-
const [error, setError] = React.useState(null);
|
|
101
|
-
|
|
102
|
-
// Helper to extract output from workflow response by node title
|
|
103
|
-
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
104
|
-
if (!result?.data) return null;
|
|
105
|
-
const node = Object.values(result.data).find(n => n.nodeTitle === nodeTitle);
|
|
106
|
-
return node?.outputs?.[socketName]?.value;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const handleSubmit = async () => {
|
|
110
|
-
if (!input.trim() || loading) return;
|
|
111
|
-
|
|
112
|
-
setLoading(true);
|
|
113
|
-
setError(null);
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
// Replace 'YOUR_WORKFLOW_HASH' with your actual workflow hash
|
|
117
|
-
const response = await invokeWorkflow('YOUR_WORKFLOW_HASH', {
|
|
118
|
-
text: input
|
|
119
|
-
});
|
|
120
|
-
// Extract the output - replace 'Output' with your node's title
|
|
121
|
-
const output = getNodeOutput(response, 'Output');
|
|
122
|
-
setResult(output || response);
|
|
123
|
-
} catch (err) {
|
|
124
|
-
setError(err.message || 'An error occurred');
|
|
125
|
-
} finally {
|
|
126
|
-
setLoading(false);
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const handleKeyDown = (e) => {
|
|
131
|
-
if (e.key === 'Enter' && !loading) {
|
|
132
|
-
handleSubmit();
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
<div className="min-h-screen bg-gray-50 p-6">
|
|
138
|
-
<div className="max-w-2xl mx-auto">
|
|
139
|
-
<h1 className="text-2xl font-bold mb-6">My FlowDot App</h1>
|
|
140
|
-
|
|
141
|
-
<div className="space-y-4">
|
|
142
|
-
<div>
|
|
143
|
-
<label className="block text-sm font-medium mb-1">Input</label>
|
|
144
|
-
<input
|
|
145
|
-
type="text"
|
|
146
|
-
value={input}
|
|
147
|
-
onChange={(e) => setInput(e.target.value)}
|
|
148
|
-
onKeyDown={handleKeyDown}
|
|
149
|
-
placeholder="Enter your text..."
|
|
150
|
-
className="w-full p-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
151
|
-
disabled={loading}
|
|
152
|
-
/>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
<button
|
|
156
|
-
type="button"
|
|
157
|
-
onClick={handleSubmit}
|
|
158
|
-
disabled={loading || !input.trim()}
|
|
159
|
-
className="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 disabled:opacity-50"
|
|
160
|
-
>
|
|
161
|
-
{loading ? 'Processing...' : 'Submit'}
|
|
162
|
-
</button>
|
|
163
|
-
</div>
|
|
164
|
-
|
|
165
|
-
{error && (
|
|
166
|
-
<div className="mt-4 p-4 bg-red-50 text-red-700 rounded-lg">
|
|
167
|
-
{error}
|
|
168
|
-
</div>
|
|
169
|
-
)}
|
|
170
|
-
|
|
171
|
-
{result && (
|
|
172
|
-
<div className="mt-6 p-4 bg-gray-50 rounded-lg">
|
|
173
|
-
<h2 className="font-semibold mb-2">Result:</h2>
|
|
174
|
-
<pre className="whitespace-pre-wrap text-sm">
|
|
175
|
-
{typeof result === 'string' ? result : JSON.stringify(result, null, 2)}
|
|
176
|
-
</pre>
|
|
177
|
-
</div>
|
|
178
|
-
)}
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
90
|
+
code: `function BasicFormApp() {
|
|
91
|
+
const [input, setInput] = React.useState('');
|
|
92
|
+
const [result, setResult] = React.useState(null);
|
|
93
|
+
const [loading, setLoading] = React.useState(false);
|
|
94
|
+
const [error, setError] = React.useState(null);
|
|
95
|
+
|
|
96
|
+
// Helper to extract output from workflow response by node title
|
|
97
|
+
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
98
|
+
if (!result?.data) return null;
|
|
99
|
+
const node = Object.values(result.data).find(n => n.nodeTitle === nodeTitle);
|
|
100
|
+
return node?.outputs?.[socketName]?.value;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const handleSubmit = async () => {
|
|
104
|
+
if (!input.trim() || loading) return;
|
|
105
|
+
|
|
106
|
+
setLoading(true);
|
|
107
|
+
setError(null);
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
// Replace 'YOUR_WORKFLOW_HASH' with your actual workflow hash
|
|
111
|
+
const response = await invokeWorkflow('YOUR_WORKFLOW_HASH', {
|
|
112
|
+
text: input
|
|
113
|
+
});
|
|
114
|
+
// Extract the output - replace 'Output' with your node's title
|
|
115
|
+
const output = getNodeOutput(response, 'Output');
|
|
116
|
+
setResult(output || response);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
setError(err.message || 'An error occurred');
|
|
119
|
+
} finally {
|
|
120
|
+
setLoading(false);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const handleKeyDown = (e) => {
|
|
125
|
+
if (e.key === 'Enter' && !loading) {
|
|
126
|
+
handleSubmit();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div className="min-h-screen bg-gray-50 p-6">
|
|
132
|
+
<div className="max-w-2xl mx-auto">
|
|
133
|
+
<h1 className="text-2xl font-bold mb-6">My FlowDot App</h1>
|
|
134
|
+
|
|
135
|
+
<div className="space-y-4">
|
|
136
|
+
<div>
|
|
137
|
+
<label className="block text-sm font-medium mb-1">Input</label>
|
|
138
|
+
<input
|
|
139
|
+
type="text"
|
|
140
|
+
value={input}
|
|
141
|
+
onChange={(e) => setInput(e.target.value)}
|
|
142
|
+
onKeyDown={handleKeyDown}
|
|
143
|
+
placeholder="Enter your text..."
|
|
144
|
+
className="w-full p-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
145
|
+
disabled={loading}
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
onClick={handleSubmit}
|
|
152
|
+
disabled={loading || !input.trim()}
|
|
153
|
+
className="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 disabled:opacity-50"
|
|
154
|
+
>
|
|
155
|
+
{loading ? 'Processing...' : 'Submit'}
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{error && (
|
|
160
|
+
<div className="mt-4 p-4 bg-red-50 text-red-700 rounded-lg">
|
|
161
|
+
{error}
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{result && (
|
|
166
|
+
<div className="mt-6 p-4 bg-gray-50 rounded-lg">
|
|
167
|
+
<h2 className="font-semibold mb-2">Result:</h2>
|
|
168
|
+
<pre className="whitespace-pre-wrap text-sm">
|
|
169
|
+
{typeof result === 'string' ? result : JSON.stringify(result, null, 2)}
|
|
170
|
+
</pre>
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
184
178
|
export default BasicFormApp;`,
|
|
185
179
|
},
|
|
186
180
|
chat: {
|
|
187
181
|
name: 'Chat Interface',
|
|
188
182
|
description: 'A chat-style interface for conversational workflows.',
|
|
189
|
-
code: `function ChatApp() {
|
|
190
|
-
const [messages, setMessages] = React.useState([]);
|
|
191
|
-
const [input, setInput] = React.useState('');
|
|
192
|
-
const [loading, setLoading] = React.useState(false);
|
|
193
|
-
const messagesEndRef = React.useRef(null);
|
|
194
|
-
|
|
195
|
-
// Helper to extract output from workflow response by node title
|
|
196
|
-
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
197
|
-
const node = Object.values(result?.data || {}).find(n => n.nodeTitle === nodeTitle);
|
|
198
|
-
return node?.outputs?.[socketName]?.value;
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const scrollToBottom = () => {
|
|
202
|
-
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
React.useEffect(() => {
|
|
206
|
-
scrollToBottom();
|
|
207
|
-
}, [messages]);
|
|
208
|
-
|
|
209
|
-
const handleSend = async () => {
|
|
210
|
-
if (!input.trim() || loading) return;
|
|
211
|
-
|
|
212
|
-
const userMessage = { role: 'user', content: input };
|
|
213
|
-
setMessages(prev => [...prev, userMessage]);
|
|
214
|
-
setInput('');
|
|
215
|
-
setLoading(true);
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
// Replace 'YOUR_WORKFLOW_HASH' with your LLM workflow hash
|
|
219
|
-
const response = await invokeWorkflow('YOUR_WORKFLOW_HASH', {
|
|
220
|
-
message: input,
|
|
221
|
-
history: messages.map(m => ({ role: m.role, content: m.content }))
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
// Extract the response - replace 'Response' with your output node's title
|
|
225
|
-
const responseText = getNodeOutput(response, 'Response') || JSON.stringify(response);
|
|
226
|
-
const assistantMessage = {
|
|
227
|
-
role: 'assistant',
|
|
228
|
-
content: responseText
|
|
229
|
-
};
|
|
230
|
-
setMessages(prev => [...prev, assistantMessage]);
|
|
231
|
-
} catch (err) {
|
|
232
|
-
setMessages(prev => [...prev, {
|
|
233
|
-
role: 'error',
|
|
234
|
-
content: err.message || 'Failed to get response'
|
|
235
|
-
}]);
|
|
236
|
-
} finally {
|
|
237
|
-
setLoading(false);
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
const handleKeyPress = (e) => {
|
|
242
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
243
|
-
e.preventDefault();
|
|
244
|
-
handleSend();
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
return (
|
|
249
|
-
<div className="flex flex-col h-screen bg-gray-50">
|
|
250
|
-
<div className="bg-white border-b p-4">
|
|
251
|
-
<h1 className="text-xl font-bold">Chat Assistant</h1>
|
|
252
|
-
</div>
|
|
253
|
-
|
|
254
|
-
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
255
|
-
{messages.length === 0 && (
|
|
256
|
-
<div className="text-center text-gray-500 mt-8">
|
|
257
|
-
Start a conversation by typing a message below.
|
|
258
|
-
</div>
|
|
259
|
-
)}
|
|
260
|
-
|
|
261
|
-
{messages.map((msg, idx) => (
|
|
262
|
-
<div
|
|
263
|
-
key={idx}
|
|
264
|
-
className={\`flex \${msg.role === 'user' ? 'justify-end' : 'justify-start'}\`}
|
|
265
|
-
>
|
|
266
|
-
<div
|
|
267
|
-
className={\`max-w-[80%] p-3 rounded-lg \${
|
|
268
|
-
msg.role === 'user'
|
|
269
|
-
? 'bg-blue-600 text-white'
|
|
270
|
-
: msg.role === 'error'
|
|
271
|
-
? 'bg-red-100 text-red-700'
|
|
272
|
-
: 'bg-gray-100 text-gray-900'
|
|
273
|
-
}\`}
|
|
274
|
-
>
|
|
275
|
-
<p className="whitespace-pre-wrap">{msg.content}</p>
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
))}
|
|
279
|
-
|
|
280
|
-
{loading && (
|
|
281
|
-
<div className="flex justify-start">
|
|
282
|
-
<div className="bg-gray-100 p-3 rounded-lg">
|
|
283
|
-
<div className="flex space-x-2">
|
|
284
|
-
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
|
|
285
|
-
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-100" />
|
|
286
|
-
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-200" />
|
|
287
|
-
</div>
|
|
288
|
-
</div>
|
|
289
|
-
</div>
|
|
290
|
-
)}
|
|
291
|
-
|
|
292
|
-
<div ref={messagesEndRef} />
|
|
293
|
-
</div>
|
|
294
|
-
|
|
295
|
-
<div className="border-t bg-white p-4">
|
|
296
|
-
<div className="flex space-x-2 max-w-3xl mx-auto">
|
|
297
|
-
<input
|
|
298
|
-
type="text"
|
|
299
|
-
value={input}
|
|
300
|
-
onChange={(e) => setInput(e.target.value)}
|
|
301
|
-
onKeyPress={handleKeyPress}
|
|
302
|
-
placeholder="Type your message..."
|
|
303
|
-
className="flex-1 p-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
304
|
-
disabled={loading}
|
|
305
|
-
/>
|
|
306
|
-
<button
|
|
307
|
-
onClick={handleSend}
|
|
308
|
-
disabled={loading || !input.trim()}
|
|
309
|
-
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
|
|
310
|
-
>
|
|
311
|
-
Send
|
|
312
|
-
</button>
|
|
313
|
-
</div>
|
|
314
|
-
</div>
|
|
315
|
-
</div>
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
|
|
183
|
+
code: `function ChatApp() {
|
|
184
|
+
const [messages, setMessages] = React.useState([]);
|
|
185
|
+
const [input, setInput] = React.useState('');
|
|
186
|
+
const [loading, setLoading] = React.useState(false);
|
|
187
|
+
const messagesEndRef = React.useRef(null);
|
|
188
|
+
|
|
189
|
+
// Helper to extract output from workflow response by node title
|
|
190
|
+
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
191
|
+
const node = Object.values(result?.data || {}).find(n => n.nodeTitle === nodeTitle);
|
|
192
|
+
return node?.outputs?.[socketName]?.value;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const scrollToBottom = () => {
|
|
196
|
+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
React.useEffect(() => {
|
|
200
|
+
scrollToBottom();
|
|
201
|
+
}, [messages]);
|
|
202
|
+
|
|
203
|
+
const handleSend = async () => {
|
|
204
|
+
if (!input.trim() || loading) return;
|
|
205
|
+
|
|
206
|
+
const userMessage = { role: 'user', content: input };
|
|
207
|
+
setMessages(prev => [...prev, userMessage]);
|
|
208
|
+
setInput('');
|
|
209
|
+
setLoading(true);
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
// Replace 'YOUR_WORKFLOW_HASH' with your LLM workflow hash
|
|
213
|
+
const response = await invokeWorkflow('YOUR_WORKFLOW_HASH', {
|
|
214
|
+
message: input,
|
|
215
|
+
history: messages.map(m => ({ role: m.role, content: m.content }))
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Extract the response - replace 'Response' with your output node's title
|
|
219
|
+
const responseText = getNodeOutput(response, 'Response') || JSON.stringify(response);
|
|
220
|
+
const assistantMessage = {
|
|
221
|
+
role: 'assistant',
|
|
222
|
+
content: responseText
|
|
223
|
+
};
|
|
224
|
+
setMessages(prev => [...prev, assistantMessage]);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
setMessages(prev => [...prev, {
|
|
227
|
+
role: 'error',
|
|
228
|
+
content: err.message || 'Failed to get response'
|
|
229
|
+
}]);
|
|
230
|
+
} finally {
|
|
231
|
+
setLoading(false);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const handleKeyPress = (e) => {
|
|
236
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
237
|
+
e.preventDefault();
|
|
238
|
+
handleSend();
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<div className="flex flex-col h-screen bg-gray-50">
|
|
244
|
+
<div className="bg-white border-b p-4">
|
|
245
|
+
<h1 className="text-xl font-bold">Chat Assistant</h1>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
249
|
+
{messages.length === 0 && (
|
|
250
|
+
<div className="text-center text-gray-500 mt-8">
|
|
251
|
+
Start a conversation by typing a message below.
|
|
252
|
+
</div>
|
|
253
|
+
)}
|
|
254
|
+
|
|
255
|
+
{messages.map((msg, idx) => (
|
|
256
|
+
<div
|
|
257
|
+
key={idx}
|
|
258
|
+
className={\`flex \${msg.role === 'user' ? 'justify-end' : 'justify-start'}\`}
|
|
259
|
+
>
|
|
260
|
+
<div
|
|
261
|
+
className={\`max-w-[80%] p-3 rounded-lg \${
|
|
262
|
+
msg.role === 'user'
|
|
263
|
+
? 'bg-blue-600 text-white'
|
|
264
|
+
: msg.role === 'error'
|
|
265
|
+
? 'bg-red-100 text-red-700'
|
|
266
|
+
: 'bg-gray-100 text-gray-900'
|
|
267
|
+
}\`}
|
|
268
|
+
>
|
|
269
|
+
<p className="whitespace-pre-wrap">{msg.content}</p>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
))}
|
|
273
|
+
|
|
274
|
+
{loading && (
|
|
275
|
+
<div className="flex justify-start">
|
|
276
|
+
<div className="bg-gray-100 p-3 rounded-lg">
|
|
277
|
+
<div className="flex space-x-2">
|
|
278
|
+
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
|
|
279
|
+
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-100" />
|
|
280
|
+
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-200" />
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
|
|
286
|
+
<div ref={messagesEndRef} />
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<div className="border-t bg-white p-4">
|
|
290
|
+
<div className="flex space-x-2 max-w-3xl mx-auto">
|
|
291
|
+
<input
|
|
292
|
+
type="text"
|
|
293
|
+
value={input}
|
|
294
|
+
onChange={(e) => setInput(e.target.value)}
|
|
295
|
+
onKeyPress={handleKeyPress}
|
|
296
|
+
placeholder="Type your message..."
|
|
297
|
+
className="flex-1 p-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
298
|
+
disabled={loading}
|
|
299
|
+
/>
|
|
300
|
+
<button
|
|
301
|
+
onClick={handleSend}
|
|
302
|
+
disabled={loading || !input.trim()}
|
|
303
|
+
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
|
|
304
|
+
>
|
|
305
|
+
Send
|
|
306
|
+
</button>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
319
313
|
export default ChatApp;`,
|
|
320
314
|
},
|
|
321
315
|
dashboard: {
|
|
322
316
|
name: 'Dashboard App',
|
|
323
317
|
description: 'A dashboard that displays data from multiple workflow calls.',
|
|
324
|
-
code: `function DashboardApp() {
|
|
325
|
-
const [stats, setStats] = React.useState(null);
|
|
326
|
-
const [items, setItems] = React.useState([]);
|
|
327
|
-
const [loading, setLoading] = React.useState(true);
|
|
328
|
-
const [error, setError] = React.useState(null);
|
|
329
|
-
|
|
330
|
-
// Helper to extract output from workflow response by node title
|
|
331
|
-
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
332
|
-
const node = Object.values(result?.data || {}).find(n => n.nodeTitle === nodeTitle);
|
|
333
|
-
return node?.outputs?.[socketName]?.value;
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
const loadDashboardData = async () => {
|
|
337
|
-
setLoading(true);
|
|
338
|
-
setError(null);
|
|
339
|
-
|
|
340
|
-
try {
|
|
341
|
-
// Load data from multiple workflows in parallel
|
|
342
|
-
const [statsResult, itemsResult] = await Promise.all([
|
|
343
|
-
invokeWorkflow('STATS_WORKFLOW_HASH', {}),
|
|
344
|
-
invokeWorkflow('ITEMS_WORKFLOW_HASH', { limit: 10 })
|
|
345
|
-
]);
|
|
346
|
-
|
|
347
|
-
// Extract outputs - replace node titles with your actual node names
|
|
348
|
-
const statsData = getNodeOutput(statsResult, 'Stats Output');
|
|
349
|
-
const itemsData = getNodeOutput(itemsResult, 'Items Output');
|
|
350
|
-
|
|
351
|
-
setStats(statsData ? JSON.parse(statsData) : statsResult);
|
|
352
|
-
setItems(itemsData ? JSON.parse(itemsData) : itemsResult.items || []);
|
|
353
|
-
} catch (err) {
|
|
354
|
-
setError(err.message || 'Failed to load dashboard data');
|
|
355
|
-
} finally {
|
|
356
|
-
setLoading(false);
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
React.useEffect(() => {
|
|
361
|
-
loadDashboardData();
|
|
362
|
-
}, []);
|
|
363
|
-
|
|
364
|
-
if (loading) {
|
|
365
|
-
return (
|
|
366
|
-
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
367
|
-
<div className="text-lg text-gray-500">Loading dashboard...</div>
|
|
368
|
-
</div>
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (error) {
|
|
373
|
-
return (
|
|
374
|
-
<div className="min-h-screen bg-gray-50 p-6">
|
|
375
|
-
<div className="bg-red-50 text-red-700 p-4 rounded-lg">
|
|
376
|
-
{error}
|
|
377
|
-
<button
|
|
378
|
-
onClick={loadDashboardData}
|
|
379
|
-
className="ml-4 underline hover:no-underline"
|
|
380
|
-
>
|
|
381
|
-
Retry
|
|
382
|
-
</button>
|
|
383
|
-
</div>
|
|
384
|
-
</div>
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return (
|
|
389
|
-
<div className="min-h-screen bg-gray-50 p-6 space-y-6">
|
|
390
|
-
<div className="flex justify-between items-center">
|
|
391
|
-
<h1 className="text-2xl font-bold">Dashboard</h1>
|
|
392
|
-
<button
|
|
393
|
-
onClick={loadDashboardData}
|
|
394
|
-
className="px-4 py-2 bg-gray-100 rounded-lg hover:bg-gray-200"
|
|
395
|
-
>
|
|
396
|
-
Refresh
|
|
397
|
-
</button>
|
|
398
|
-
</div>
|
|
399
|
-
|
|
400
|
-
{/* Stats Cards */}
|
|
401
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
402
|
-
{stats && Object.entries(stats).map(([key, value]) => (
|
|
403
|
-
<div key={key} className="bg-white p-6 rounded-lg shadow">
|
|
404
|
-
<div className="text-sm text-gray-500 uppercase">{key}</div>
|
|
405
|
-
<div className="text-3xl font-bold mt-1">
|
|
406
|
-
{typeof value === 'number' ? value.toLocaleString() : value}
|
|
407
|
-
</div>
|
|
408
|
-
</div>
|
|
409
|
-
))}
|
|
410
|
-
</div>
|
|
411
|
-
|
|
412
|
-
{/* Items Table */}
|
|
413
|
-
<div className="bg-white rounded-lg shadow overflow-hidden">
|
|
414
|
-
<table className="w-full">
|
|
415
|
-
<thead className="bg-gray-50">
|
|
416
|
-
<tr>
|
|
417
|
-
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">Name</th>
|
|
418
|
-
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">Status</th>
|
|
419
|
-
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">Date</th>
|
|
420
|
-
</tr>
|
|
421
|
-
</thead>
|
|
422
|
-
<tbody className="divide-y">
|
|
423
|
-
{items.map((item, idx) => (
|
|
424
|
-
<tr key={idx} className="hover:bg-gray-50">
|
|
425
|
-
<td className="px-4 py-3">{item.name}</td>
|
|
426
|
-
<td className="px-4 py-3">
|
|
427
|
-
<span className={\`px-2 py-1 text-xs rounded-full \${
|
|
428
|
-
item.status === 'active' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
|
|
429
|
-
}\`}>
|
|
430
|
-
{item.status}
|
|
431
|
-
</span>
|
|
432
|
-
</td>
|
|
433
|
-
<td className="px-4 py-3 text-gray-500">{item.date}</td>
|
|
434
|
-
</tr>
|
|
435
|
-
))}
|
|
436
|
-
</tbody>
|
|
437
|
-
</table>
|
|
438
|
-
</div>
|
|
439
|
-
</div>
|
|
440
|
-
);
|
|
441
|
-
}
|
|
442
|
-
|
|
318
|
+
code: `function DashboardApp() {
|
|
319
|
+
const [stats, setStats] = React.useState(null);
|
|
320
|
+
const [items, setItems] = React.useState([]);
|
|
321
|
+
const [loading, setLoading] = React.useState(true);
|
|
322
|
+
const [error, setError] = React.useState(null);
|
|
323
|
+
|
|
324
|
+
// Helper to extract output from workflow response by node title
|
|
325
|
+
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
326
|
+
const node = Object.values(result?.data || {}).find(n => n.nodeTitle === nodeTitle);
|
|
327
|
+
return node?.outputs?.[socketName]?.value;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const loadDashboardData = async () => {
|
|
331
|
+
setLoading(true);
|
|
332
|
+
setError(null);
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
// Load data from multiple workflows in parallel
|
|
336
|
+
const [statsResult, itemsResult] = await Promise.all([
|
|
337
|
+
invokeWorkflow('STATS_WORKFLOW_HASH', {}),
|
|
338
|
+
invokeWorkflow('ITEMS_WORKFLOW_HASH', { limit: 10 })
|
|
339
|
+
]);
|
|
340
|
+
|
|
341
|
+
// Extract outputs - replace node titles with your actual node names
|
|
342
|
+
const statsData = getNodeOutput(statsResult, 'Stats Output');
|
|
343
|
+
const itemsData = getNodeOutput(itemsResult, 'Items Output');
|
|
344
|
+
|
|
345
|
+
setStats(statsData ? JSON.parse(statsData) : statsResult);
|
|
346
|
+
setItems(itemsData ? JSON.parse(itemsData) : itemsResult.items || []);
|
|
347
|
+
} catch (err) {
|
|
348
|
+
setError(err.message || 'Failed to load dashboard data');
|
|
349
|
+
} finally {
|
|
350
|
+
setLoading(false);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
React.useEffect(() => {
|
|
355
|
+
loadDashboardData();
|
|
356
|
+
}, []);
|
|
357
|
+
|
|
358
|
+
if (loading) {
|
|
359
|
+
return (
|
|
360
|
+
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
361
|
+
<div className="text-lg text-gray-500">Loading dashboard...</div>
|
|
362
|
+
</div>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (error) {
|
|
367
|
+
return (
|
|
368
|
+
<div className="min-h-screen bg-gray-50 p-6">
|
|
369
|
+
<div className="bg-red-50 text-red-700 p-4 rounded-lg">
|
|
370
|
+
{error}
|
|
371
|
+
<button
|
|
372
|
+
onClick={loadDashboardData}
|
|
373
|
+
className="ml-4 underline hover:no-underline"
|
|
374
|
+
>
|
|
375
|
+
Retry
|
|
376
|
+
</button>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return (
|
|
383
|
+
<div className="min-h-screen bg-gray-50 p-6 space-y-6">
|
|
384
|
+
<div className="flex justify-between items-center">
|
|
385
|
+
<h1 className="text-2xl font-bold">Dashboard</h1>
|
|
386
|
+
<button
|
|
387
|
+
onClick={loadDashboardData}
|
|
388
|
+
className="px-4 py-2 bg-gray-100 rounded-lg hover:bg-gray-200"
|
|
389
|
+
>
|
|
390
|
+
Refresh
|
|
391
|
+
</button>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
{/* Stats Cards */}
|
|
395
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
396
|
+
{stats && Object.entries(stats).map(([key, value]) => (
|
|
397
|
+
<div key={key} className="bg-white p-6 rounded-lg shadow">
|
|
398
|
+
<div className="text-sm text-gray-500 uppercase">{key}</div>
|
|
399
|
+
<div className="text-3xl font-bold mt-1">
|
|
400
|
+
{typeof value === 'number' ? value.toLocaleString() : value}
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
))}
|
|
404
|
+
</div>
|
|
405
|
+
|
|
406
|
+
{/* Items Table */}
|
|
407
|
+
<div className="bg-white rounded-lg shadow overflow-hidden">
|
|
408
|
+
<table className="w-full">
|
|
409
|
+
<thead className="bg-gray-50">
|
|
410
|
+
<tr>
|
|
411
|
+
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">Name</th>
|
|
412
|
+
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">Status</th>
|
|
413
|
+
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">Date</th>
|
|
414
|
+
</tr>
|
|
415
|
+
</thead>
|
|
416
|
+
<tbody className="divide-y">
|
|
417
|
+
{items.map((item, idx) => (
|
|
418
|
+
<tr key={idx} className="hover:bg-gray-50">
|
|
419
|
+
<td className="px-4 py-3">{item.name}</td>
|
|
420
|
+
<td className="px-4 py-3">
|
|
421
|
+
<span className={\`px-2 py-1 text-xs rounded-full \${
|
|
422
|
+
item.status === 'active' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
|
|
423
|
+
}\`}>
|
|
424
|
+
{item.status}
|
|
425
|
+
</span>
|
|
426
|
+
</td>
|
|
427
|
+
<td className="px-4 py-3 text-gray-500">{item.date}</td>
|
|
428
|
+
</tr>
|
|
429
|
+
))}
|
|
430
|
+
</tbody>
|
|
431
|
+
</table>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
443
437
|
export default DashboardApp;`,
|
|
444
438
|
},
|
|
445
439
|
'form-builder': {
|
|
446
440
|
name: 'Dynamic Form Builder',
|
|
447
441
|
description: 'A form that dynamically generates fields based on workflow input schema.',
|
|
448
|
-
code: `function DynamicFormApp() {
|
|
449
|
-
const [formData, setFormData] = React.useState({});
|
|
450
|
-
const [result, setResult] = React.useState(null);
|
|
451
|
-
const [loading, setLoading] = React.useState(false);
|
|
452
|
-
const [schemaLoading, setSchemaLoading] = React.useState(true);
|
|
453
|
-
|
|
454
|
-
// Define your workflow hash here
|
|
455
|
-
const WORKFLOW_HASH = 'YOUR_WORKFLOW_HASH';
|
|
456
|
-
|
|
457
|
-
// Helper to extract output from workflow response by node title
|
|
458
|
-
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
459
|
-
if (!result?.data) return null;
|
|
460
|
-
const node = Object.values(result.data).find(n => n.nodeTitle === nodeTitle);
|
|
461
|
-
return node?.outputs?.[socketName]?.value;
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
// Define the expected input schema
|
|
465
|
-
// This would typically come from the workflow's signature
|
|
466
|
-
const inputSchema = [
|
|
467
|
-
{ name: 'text', type: 'string', description: 'Text input', required: true },
|
|
468
|
-
{ name: 'count', type: 'number', description: 'Number of items', required: false },
|
|
469
|
-
{ name: 'enabled', type: 'boolean', description: 'Enable feature', required: false },
|
|
470
|
-
{ name: 'options', type: 'select', options: ['option1', 'option2', 'option3'], required: false },
|
|
471
|
-
];
|
|
472
|
-
|
|
473
|
-
React.useEffect(() => {
|
|
474
|
-
// Initialize form data with defaults
|
|
475
|
-
const defaults = {};
|
|
476
|
-
inputSchema.forEach(field => {
|
|
477
|
-
if (field.type === 'boolean') defaults[field.name] = false;
|
|
478
|
-
else if (field.type === 'number') defaults[field.name] = 0;
|
|
479
|
-
else defaults[field.name] = '';
|
|
480
|
-
});
|
|
481
|
-
setFormData(defaults);
|
|
482
|
-
setSchemaLoading(false);
|
|
483
|
-
}, []);
|
|
484
|
-
|
|
485
|
-
const handleChange = (name, value) => {
|
|
486
|
-
setFormData(prev => ({ ...prev, [name]: value }));
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
const handleSubmit = async () => {
|
|
490
|
-
if (loading) return;
|
|
491
|
-
|
|
492
|
-
setLoading(true);
|
|
493
|
-
|
|
494
|
-
try {
|
|
495
|
-
const response = await invokeWorkflow(WORKFLOW_HASH, formData);
|
|
496
|
-
// Extract the output - replace 'Output' with your node's title
|
|
497
|
-
const output = getNodeOutput(response, 'Output');
|
|
498
|
-
setResult(output || response);
|
|
499
|
-
} catch (err) {
|
|
500
|
-
setResult({ error: err.message });
|
|
501
|
-
} finally {
|
|
502
|
-
setLoading(false);
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
const renderField = (field) => {
|
|
507
|
-
const value = formData[field.name];
|
|
508
|
-
|
|
509
|
-
switch (field.type) {
|
|
510
|
-
case 'boolean':
|
|
511
|
-
return (
|
|
512
|
-
<label className="flex items-center space-x-2">
|
|
513
|
-
<input
|
|
514
|
-
type="checkbox"
|
|
515
|
-
checked={value || false}
|
|
516
|
-
onChange={(e) => handleChange(field.name, e.target.checked)}
|
|
517
|
-
className="rounded"
|
|
518
|
-
/>
|
|
519
|
-
<span>{field.description}</span>
|
|
520
|
-
</label>
|
|
521
|
-
);
|
|
522
|
-
case 'number':
|
|
523
|
-
return (
|
|
524
|
-
<input
|
|
525
|
-
type="number"
|
|
526
|
-
value={value || 0}
|
|
527
|
-
onChange={(e) => handleChange(field.name, Number(e.target.value))}
|
|
528
|
-
className="w-full p-2 border rounded-lg"
|
|
529
|
-
/>
|
|
530
|
-
);
|
|
531
|
-
case 'select':
|
|
532
|
-
return (
|
|
533
|
-
<select
|
|
534
|
-
value={value || ''}
|
|
535
|
-
onChange={(e) => handleChange(field.name, e.target.value)}
|
|
536
|
-
className="w-full p-2 border rounded-lg"
|
|
537
|
-
>
|
|
538
|
-
<option value="">Select...</option>
|
|
539
|
-
{field.options?.map(opt => (
|
|
540
|
-
<option key={opt} value={opt}>{opt}</option>
|
|
541
|
-
))}
|
|
542
|
-
</select>
|
|
543
|
-
);
|
|
544
|
-
default:
|
|
545
|
-
return (
|
|
546
|
-
<input
|
|
547
|
-
type="text"
|
|
548
|
-
value={value || ''}
|
|
549
|
-
onChange={(e) => handleChange(field.name, e.target.value)}
|
|
550
|
-
onKeyDown={(e) => e.key === 'Enter' && !loading && handleSubmit()}
|
|
551
|
-
placeholder={field.description}
|
|
552
|
-
className="w-full p-2 border rounded-lg"
|
|
553
|
-
/>
|
|
554
|
-
);
|
|
555
|
-
}
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
if (schemaLoading) {
|
|
559
|
-
return <div className="min-h-screen bg-gray-50 p-6 text-center">Loading form...</div>;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
return (
|
|
563
|
-
<div className="min-h-screen bg-gray-50 p-6">
|
|
564
|
-
<div className="max-w-2xl mx-auto">
|
|
565
|
-
<h1 className="text-2xl font-bold mb-6">Dynamic Form</h1>
|
|
566
|
-
|
|
567
|
-
<div className="space-y-4">
|
|
568
|
-
{inputSchema.map(field => (
|
|
569
|
-
<div key={field.name}>
|
|
570
|
-
<label className="block text-sm font-medium mb-1">
|
|
571
|
-
{field.name}
|
|
572
|
-
{field.required && <span className="text-red-500">*</span>}
|
|
573
|
-
</label>
|
|
574
|
-
{renderField(field)}
|
|
575
|
-
{field.description && field.type !== 'boolean' && (
|
|
576
|
-
<p className="text-xs text-gray-500 mt-1">{field.description}</p>
|
|
577
|
-
)}
|
|
578
|
-
</div>
|
|
579
|
-
))}
|
|
580
|
-
|
|
581
|
-
<button
|
|
582
|
-
type="button"
|
|
583
|
-
onClick={handleSubmit}
|
|
584
|
-
disabled={loading}
|
|
585
|
-
className="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 disabled:opacity-50"
|
|
586
|
-
>
|
|
587
|
-
{loading ? 'Processing...' : 'Submit'}
|
|
588
|
-
</button>
|
|
589
|
-
</div>
|
|
590
|
-
|
|
591
|
-
{result && (
|
|
592
|
-
<div className="mt-6 p-4 bg-white rounded-lg shadow">
|
|
593
|
-
<h2 className="font-semibold mb-2">Result:</h2>
|
|
594
|
-
<pre className="whitespace-pre-wrap text-sm overflow-x-auto">
|
|
595
|
-
{JSON.stringify(result, null, 2)}
|
|
596
|
-
</pre>
|
|
597
|
-
</div>
|
|
598
|
-
)}
|
|
599
|
-
</div>
|
|
600
|
-
</div>
|
|
601
|
-
);
|
|
602
|
-
}
|
|
603
|
-
|
|
442
|
+
code: `function DynamicFormApp() {
|
|
443
|
+
const [formData, setFormData] = React.useState({});
|
|
444
|
+
const [result, setResult] = React.useState(null);
|
|
445
|
+
const [loading, setLoading] = React.useState(false);
|
|
446
|
+
const [schemaLoading, setSchemaLoading] = React.useState(true);
|
|
447
|
+
|
|
448
|
+
// Define your workflow hash here
|
|
449
|
+
const WORKFLOW_HASH = 'YOUR_WORKFLOW_HASH';
|
|
450
|
+
|
|
451
|
+
// Helper to extract output from workflow response by node title
|
|
452
|
+
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
453
|
+
if (!result?.data) return null;
|
|
454
|
+
const node = Object.values(result.data).find(n => n.nodeTitle === nodeTitle);
|
|
455
|
+
return node?.outputs?.[socketName]?.value;
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// Define the expected input schema
|
|
459
|
+
// This would typically come from the workflow's signature
|
|
460
|
+
const inputSchema = [
|
|
461
|
+
{ name: 'text', type: 'string', description: 'Text input', required: true },
|
|
462
|
+
{ name: 'count', type: 'number', description: 'Number of items', required: false },
|
|
463
|
+
{ name: 'enabled', type: 'boolean', description: 'Enable feature', required: false },
|
|
464
|
+
{ name: 'options', type: 'select', options: ['option1', 'option2', 'option3'], required: false },
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
React.useEffect(() => {
|
|
468
|
+
// Initialize form data with defaults
|
|
469
|
+
const defaults = {};
|
|
470
|
+
inputSchema.forEach(field => {
|
|
471
|
+
if (field.type === 'boolean') defaults[field.name] = false;
|
|
472
|
+
else if (field.type === 'number') defaults[field.name] = 0;
|
|
473
|
+
else defaults[field.name] = '';
|
|
474
|
+
});
|
|
475
|
+
setFormData(defaults);
|
|
476
|
+
setSchemaLoading(false);
|
|
477
|
+
}, []);
|
|
478
|
+
|
|
479
|
+
const handleChange = (name, value) => {
|
|
480
|
+
setFormData(prev => ({ ...prev, [name]: value }));
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const handleSubmit = async () => {
|
|
484
|
+
if (loading) return;
|
|
485
|
+
|
|
486
|
+
setLoading(true);
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
const response = await invokeWorkflow(WORKFLOW_HASH, formData);
|
|
490
|
+
// Extract the output - replace 'Output' with your node's title
|
|
491
|
+
const output = getNodeOutput(response, 'Output');
|
|
492
|
+
setResult(output || response);
|
|
493
|
+
} catch (err) {
|
|
494
|
+
setResult({ error: err.message });
|
|
495
|
+
} finally {
|
|
496
|
+
setLoading(false);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
const renderField = (field) => {
|
|
501
|
+
const value = formData[field.name];
|
|
502
|
+
|
|
503
|
+
switch (field.type) {
|
|
504
|
+
case 'boolean':
|
|
505
|
+
return (
|
|
506
|
+
<label className="flex items-center space-x-2">
|
|
507
|
+
<input
|
|
508
|
+
type="checkbox"
|
|
509
|
+
checked={value || false}
|
|
510
|
+
onChange={(e) => handleChange(field.name, e.target.checked)}
|
|
511
|
+
className="rounded"
|
|
512
|
+
/>
|
|
513
|
+
<span>{field.description}</span>
|
|
514
|
+
</label>
|
|
515
|
+
);
|
|
516
|
+
case 'number':
|
|
517
|
+
return (
|
|
518
|
+
<input
|
|
519
|
+
type="number"
|
|
520
|
+
value={value || 0}
|
|
521
|
+
onChange={(e) => handleChange(field.name, Number(e.target.value))}
|
|
522
|
+
className="w-full p-2 border rounded-lg"
|
|
523
|
+
/>
|
|
524
|
+
);
|
|
525
|
+
case 'select':
|
|
526
|
+
return (
|
|
527
|
+
<select
|
|
528
|
+
value={value || ''}
|
|
529
|
+
onChange={(e) => handleChange(field.name, e.target.value)}
|
|
530
|
+
className="w-full p-2 border rounded-lg"
|
|
531
|
+
>
|
|
532
|
+
<option value="">Select...</option>
|
|
533
|
+
{field.options?.map(opt => (
|
|
534
|
+
<option key={opt} value={opt}>{opt}</option>
|
|
535
|
+
))}
|
|
536
|
+
</select>
|
|
537
|
+
);
|
|
538
|
+
default:
|
|
539
|
+
return (
|
|
540
|
+
<input
|
|
541
|
+
type="text"
|
|
542
|
+
value={value || ''}
|
|
543
|
+
onChange={(e) => handleChange(field.name, e.target.value)}
|
|
544
|
+
onKeyDown={(e) => e.key === 'Enter' && !loading && handleSubmit()}
|
|
545
|
+
placeholder={field.description}
|
|
546
|
+
className="w-full p-2 border rounded-lg"
|
|
547
|
+
/>
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
if (schemaLoading) {
|
|
553
|
+
return <div className="min-h-screen bg-gray-50 p-6 text-center">Loading form...</div>;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return (
|
|
557
|
+
<div className="min-h-screen bg-gray-50 p-6">
|
|
558
|
+
<div className="max-w-2xl mx-auto">
|
|
559
|
+
<h1 className="text-2xl font-bold mb-6">Dynamic Form</h1>
|
|
560
|
+
|
|
561
|
+
<div className="space-y-4">
|
|
562
|
+
{inputSchema.map(field => (
|
|
563
|
+
<div key={field.name}>
|
|
564
|
+
<label className="block text-sm font-medium mb-1">
|
|
565
|
+
{field.name}
|
|
566
|
+
{field.required && <span className="text-red-500">*</span>}
|
|
567
|
+
</label>
|
|
568
|
+
{renderField(field)}
|
|
569
|
+
{field.description && field.type !== 'boolean' && (
|
|
570
|
+
<p className="text-xs text-gray-500 mt-1">{field.description}</p>
|
|
571
|
+
)}
|
|
572
|
+
</div>
|
|
573
|
+
))}
|
|
574
|
+
|
|
575
|
+
<button
|
|
576
|
+
type="button"
|
|
577
|
+
onClick={handleSubmit}
|
|
578
|
+
disabled={loading}
|
|
579
|
+
className="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 disabled:opacity-50"
|
|
580
|
+
>
|
|
581
|
+
{loading ? 'Processing...' : 'Submit'}
|
|
582
|
+
</button>
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
{result && (
|
|
586
|
+
<div className="mt-6 p-4 bg-white rounded-lg shadow">
|
|
587
|
+
<h2 className="font-semibold mb-2">Result:</h2>
|
|
588
|
+
<pre className="whitespace-pre-wrap text-sm overflow-x-auto">
|
|
589
|
+
{JSON.stringify(result, null, 2)}
|
|
590
|
+
</pre>
|
|
591
|
+
</div>
|
|
592
|
+
)}
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
|
|
604
598
|
export default DynamicFormApp;`,
|
|
605
599
|
},
|
|
606
600
|
'data-viewer': {
|
|
607
601
|
name: 'Data Viewer',
|
|
608
602
|
description: 'Display workflow results in tables and charts.',
|
|
609
|
-
code: `function DataViewerApp() {
|
|
610
|
-
const [data, setData] = React.useState([]);
|
|
611
|
-
const [loading, setLoading] = React.useState(false);
|
|
612
|
-
const [viewMode, setViewMode] = React.useState('table'); // 'table' or 'cards'
|
|
613
|
-
const [sortField, setSortField] = React.useState(null);
|
|
614
|
-
const [sortDirection, setSortDirection] = React.useState('asc');
|
|
615
|
-
|
|
616
|
-
// Helper to extract output from workflow response by node title
|
|
617
|
-
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
618
|
-
const node = Object.values(result?.data || {}).find(n => n.nodeTitle === nodeTitle);
|
|
619
|
-
return node?.outputs?.[socketName]?.value;
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
const loadData = async () => {
|
|
623
|
-
setLoading(true);
|
|
624
|
-
try {
|
|
625
|
-
const result = await invokeWorkflow('YOUR_WORKFLOW_HASH', {});
|
|
626
|
-
// Extract the data - replace 'Data Output' with your node's title
|
|
627
|
-
const outputData = getNodeOutput(result, 'Data Output');
|
|
628
|
-
// Parse if it's JSON string, otherwise use as-is
|
|
629
|
-
const parsed = outputData ? JSON.parse(outputData) : result;
|
|
630
|
-
setData(parsed.items || parsed.data || parsed || []);
|
|
631
|
-
} catch (err) {
|
|
632
|
-
console.error('Failed to load data:', err);
|
|
633
|
-
} finally {
|
|
634
|
-
setLoading(false);
|
|
635
|
-
}
|
|
636
|
-
};
|
|
637
|
-
|
|
638
|
-
React.useEffect(() => {
|
|
639
|
-
loadData();
|
|
640
|
-
}, []);
|
|
641
|
-
|
|
642
|
-
const handleSort = (field) => {
|
|
643
|
-
if (sortField === field) {
|
|
644
|
-
setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc');
|
|
645
|
-
} else {
|
|
646
|
-
setSortField(field);
|
|
647
|
-
setSortDirection('asc');
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
|
|
651
|
-
const sortedData = React.useMemo(() => {
|
|
652
|
-
if (!sortField) return data;
|
|
653
|
-
return [...data].sort((a, b) => {
|
|
654
|
-
const aVal = a[sortField];
|
|
655
|
-
const bVal = b[sortField];
|
|
656
|
-
const direction = sortDirection === 'asc' ? 1 : -1;
|
|
657
|
-
if (typeof aVal === 'number') return (aVal - bVal) * direction;
|
|
658
|
-
return String(aVal).localeCompare(String(bVal)) * direction;
|
|
659
|
-
});
|
|
660
|
-
}, [data, sortField, sortDirection]);
|
|
661
|
-
|
|
662
|
-
// Get column headers from first item
|
|
663
|
-
const columns = data.length > 0 ? Object.keys(data[0]) : [];
|
|
664
|
-
|
|
665
|
-
return (
|
|
666
|
-
<div className="min-h-screen bg-gray-50 p-6">
|
|
667
|
-
<div className="flex justify-between items-center mb-6">
|
|
668
|
-
<h1 className="text-2xl font-bold">Data Viewer</h1>
|
|
669
|
-
<div className="flex space-x-2">
|
|
670
|
-
<button
|
|
671
|
-
onClick={() => setViewMode('table')}
|
|
672
|
-
className={\`px-3 py-1 rounded \${viewMode === 'table' ? 'bg-blue-600 text-white' : 'bg-gray-100'}\`}
|
|
673
|
-
>
|
|
674
|
-
Table
|
|
675
|
-
</button>
|
|
676
|
-
<button
|
|
677
|
-
onClick={() => setViewMode('cards')}
|
|
678
|
-
className={\`px-3 py-1 rounded \${viewMode === 'cards' ? 'bg-blue-600 text-white' : 'bg-gray-100'}\`}
|
|
679
|
-
>
|
|
680
|
-
Cards
|
|
681
|
-
</button>
|
|
682
|
-
<button
|
|
683
|
-
onClick={loadData}
|
|
684
|
-
disabled={loading}
|
|
685
|
-
className="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200 disabled:opacity-50"
|
|
686
|
-
>
|
|
687
|
-
{loading ? 'Loading...' : 'Refresh'}
|
|
688
|
-
</button>
|
|
689
|
-
</div>
|
|
690
|
-
</div>
|
|
691
|
-
|
|
692
|
-
{viewMode === 'table' ? (
|
|
693
|
-
<div className="bg-white rounded-lg shadow overflow-x-auto">
|
|
694
|
-
<table className="w-full">
|
|
695
|
-
<thead className="bg-gray-50">
|
|
696
|
-
<tr>
|
|
697
|
-
{columns.map(col => (
|
|
698
|
-
<th
|
|
699
|
-
key={col}
|
|
700
|
-
onClick={() => handleSort(col)}
|
|
701
|
-
className="px-4 py-3 text-left text-sm font-medium text-gray-500 cursor-pointer hover:bg-gray-100"
|
|
702
|
-
>
|
|
703
|
-
{col}
|
|
704
|
-
{sortField === col && (
|
|
705
|
-
<span className="ml-1">{sortDirection === 'asc' ? '↑' : '↓'}</span>
|
|
706
|
-
)}
|
|
707
|
-
</th>
|
|
708
|
-
))}
|
|
709
|
-
</tr>
|
|
710
|
-
</thead>
|
|
711
|
-
<tbody className="divide-y">
|
|
712
|
-
{sortedData.map((row, idx) => (
|
|
713
|
-
<tr key={idx} className="hover:bg-gray-50">
|
|
714
|
-
{columns.map(col => (
|
|
715
|
-
<td key={col} className="px-4 py-3 text-sm">
|
|
716
|
-
{typeof row[col] === 'object'
|
|
717
|
-
? JSON.stringify(row[col])
|
|
718
|
-
: String(row[col])
|
|
719
|
-
}
|
|
720
|
-
</td>
|
|
721
|
-
))}
|
|
722
|
-
</tr>
|
|
723
|
-
))}
|
|
724
|
-
</tbody>
|
|
725
|
-
</table>
|
|
726
|
-
{data.length === 0 && !loading && (
|
|
727
|
-
<div className="text-center py-8 text-gray-500">No data available</div>
|
|
728
|
-
)}
|
|
729
|
-
</div>
|
|
730
|
-
) : (
|
|
731
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
732
|
-
{sortedData.map((item, idx) => (
|
|
733
|
-
<div key={idx} className="bg-white p-4 rounded-lg shadow">
|
|
734
|
-
{columns.map(col => (
|
|
735
|
-
<div key={col} className="mb-2">
|
|
736
|
-
<span className="text-xs text-gray-500 uppercase">{col}</span>
|
|
737
|
-
<div className="font-medium">
|
|
738
|
-
{typeof item[col] === 'object'
|
|
739
|
-
? JSON.stringify(item[col])
|
|
740
|
-
: String(item[col])
|
|
741
|
-
}
|
|
742
|
-
</div>
|
|
743
|
-
</div>
|
|
744
|
-
))}
|
|
745
|
-
</div>
|
|
746
|
-
))}
|
|
747
|
-
</div>
|
|
748
|
-
)}
|
|
749
|
-
|
|
750
|
-
<div className="mt-4 text-sm text-gray-500">
|
|
751
|
-
Showing {data.length} items
|
|
752
|
-
</div>
|
|
753
|
-
</div>
|
|
754
|
-
);
|
|
755
|
-
}
|
|
756
|
-
|
|
603
|
+
code: `function DataViewerApp() {
|
|
604
|
+
const [data, setData] = React.useState([]);
|
|
605
|
+
const [loading, setLoading] = React.useState(false);
|
|
606
|
+
const [viewMode, setViewMode] = React.useState('table'); // 'table' or 'cards'
|
|
607
|
+
const [sortField, setSortField] = React.useState(null);
|
|
608
|
+
const [sortDirection, setSortDirection] = React.useState('asc');
|
|
609
|
+
|
|
610
|
+
// Helper to extract output from workflow response by node title
|
|
611
|
+
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
612
|
+
const node = Object.values(result?.data || {}).find(n => n.nodeTitle === nodeTitle);
|
|
613
|
+
return node?.outputs?.[socketName]?.value;
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const loadData = async () => {
|
|
617
|
+
setLoading(true);
|
|
618
|
+
try {
|
|
619
|
+
const result = await invokeWorkflow('YOUR_WORKFLOW_HASH', {});
|
|
620
|
+
// Extract the data - replace 'Data Output' with your node's title
|
|
621
|
+
const outputData = getNodeOutput(result, 'Data Output');
|
|
622
|
+
// Parse if it's JSON string, otherwise use as-is
|
|
623
|
+
const parsed = outputData ? JSON.parse(outputData) : result;
|
|
624
|
+
setData(parsed.items || parsed.data || parsed || []);
|
|
625
|
+
} catch (err) {
|
|
626
|
+
console.error('Failed to load data:', err);
|
|
627
|
+
} finally {
|
|
628
|
+
setLoading(false);
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
React.useEffect(() => {
|
|
633
|
+
loadData();
|
|
634
|
+
}, []);
|
|
635
|
+
|
|
636
|
+
const handleSort = (field) => {
|
|
637
|
+
if (sortField === field) {
|
|
638
|
+
setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc');
|
|
639
|
+
} else {
|
|
640
|
+
setSortField(field);
|
|
641
|
+
setSortDirection('asc');
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
const sortedData = React.useMemo(() => {
|
|
646
|
+
if (!sortField) return data;
|
|
647
|
+
return [...data].sort((a, b) => {
|
|
648
|
+
const aVal = a[sortField];
|
|
649
|
+
const bVal = b[sortField];
|
|
650
|
+
const direction = sortDirection === 'asc' ? 1 : -1;
|
|
651
|
+
if (typeof aVal === 'number') return (aVal - bVal) * direction;
|
|
652
|
+
return String(aVal).localeCompare(String(bVal)) * direction;
|
|
653
|
+
});
|
|
654
|
+
}, [data, sortField, sortDirection]);
|
|
655
|
+
|
|
656
|
+
// Get column headers from first item
|
|
657
|
+
const columns = data.length > 0 ? Object.keys(data[0]) : [];
|
|
658
|
+
|
|
659
|
+
return (
|
|
660
|
+
<div className="min-h-screen bg-gray-50 p-6">
|
|
661
|
+
<div className="flex justify-between items-center mb-6">
|
|
662
|
+
<h1 className="text-2xl font-bold">Data Viewer</h1>
|
|
663
|
+
<div className="flex space-x-2">
|
|
664
|
+
<button
|
|
665
|
+
onClick={() => setViewMode('table')}
|
|
666
|
+
className={\`px-3 py-1 rounded \${viewMode === 'table' ? 'bg-blue-600 text-white' : 'bg-gray-100'}\`}
|
|
667
|
+
>
|
|
668
|
+
Table
|
|
669
|
+
</button>
|
|
670
|
+
<button
|
|
671
|
+
onClick={() => setViewMode('cards')}
|
|
672
|
+
className={\`px-3 py-1 rounded \${viewMode === 'cards' ? 'bg-blue-600 text-white' : 'bg-gray-100'}\`}
|
|
673
|
+
>
|
|
674
|
+
Cards
|
|
675
|
+
</button>
|
|
676
|
+
<button
|
|
677
|
+
onClick={loadData}
|
|
678
|
+
disabled={loading}
|
|
679
|
+
className="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200 disabled:opacity-50"
|
|
680
|
+
>
|
|
681
|
+
{loading ? 'Loading...' : 'Refresh'}
|
|
682
|
+
</button>
|
|
683
|
+
</div>
|
|
684
|
+
</div>
|
|
685
|
+
|
|
686
|
+
{viewMode === 'table' ? (
|
|
687
|
+
<div className="bg-white rounded-lg shadow overflow-x-auto">
|
|
688
|
+
<table className="w-full">
|
|
689
|
+
<thead className="bg-gray-50">
|
|
690
|
+
<tr>
|
|
691
|
+
{columns.map(col => (
|
|
692
|
+
<th
|
|
693
|
+
key={col}
|
|
694
|
+
onClick={() => handleSort(col)}
|
|
695
|
+
className="px-4 py-3 text-left text-sm font-medium text-gray-500 cursor-pointer hover:bg-gray-100"
|
|
696
|
+
>
|
|
697
|
+
{col}
|
|
698
|
+
{sortField === col && (
|
|
699
|
+
<span className="ml-1">{sortDirection === 'asc' ? '↑' : '↓'}</span>
|
|
700
|
+
)}
|
|
701
|
+
</th>
|
|
702
|
+
))}
|
|
703
|
+
</tr>
|
|
704
|
+
</thead>
|
|
705
|
+
<tbody className="divide-y">
|
|
706
|
+
{sortedData.map((row, idx) => (
|
|
707
|
+
<tr key={idx} className="hover:bg-gray-50">
|
|
708
|
+
{columns.map(col => (
|
|
709
|
+
<td key={col} className="px-4 py-3 text-sm">
|
|
710
|
+
{typeof row[col] === 'object'
|
|
711
|
+
? JSON.stringify(row[col])
|
|
712
|
+
: String(row[col])
|
|
713
|
+
}
|
|
714
|
+
</td>
|
|
715
|
+
))}
|
|
716
|
+
</tr>
|
|
717
|
+
))}
|
|
718
|
+
</tbody>
|
|
719
|
+
</table>
|
|
720
|
+
{data.length === 0 && !loading && (
|
|
721
|
+
<div className="text-center py-8 text-gray-500">No data available</div>
|
|
722
|
+
)}
|
|
723
|
+
</div>
|
|
724
|
+
) : (
|
|
725
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
726
|
+
{sortedData.map((item, idx) => (
|
|
727
|
+
<div key={idx} className="bg-white p-4 rounded-lg shadow">
|
|
728
|
+
{columns.map(col => (
|
|
729
|
+
<div key={col} className="mb-2">
|
|
730
|
+
<span className="text-xs text-gray-500 uppercase">{col}</span>
|
|
731
|
+
<div className="font-medium">
|
|
732
|
+
{typeof item[col] === 'object'
|
|
733
|
+
? JSON.stringify(item[col])
|
|
734
|
+
: String(item[col])
|
|
735
|
+
}
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
))}
|
|
739
|
+
</div>
|
|
740
|
+
))}
|
|
741
|
+
</div>
|
|
742
|
+
)}
|
|
743
|
+
|
|
744
|
+
<div className="mt-4 text-sm text-gray-500">
|
|
745
|
+
Showing {data.length} items
|
|
746
|
+
</div>
|
|
747
|
+
</div>
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
757
751
|
export default DataViewerApp;`,
|
|
758
752
|
},
|
|
759
753
|
};
|
|
@@ -762,79 +756,79 @@ export async function handleGetAppTemplate(args) {
|
|
|
762
756
|
if (templateName === 'all') {
|
|
763
757
|
const allTemplates = Object.entries(TEMPLATES)
|
|
764
758
|
.map(([key, template]) => {
|
|
765
|
-
return `## ${template.name} (${key})
|
|
766
|
-
|
|
767
|
-
${template.description}
|
|
768
|
-
|
|
769
|
-
\`\`\`jsx
|
|
770
|
-
${template.code}
|
|
759
|
+
return `## ${template.name} (${key})
|
|
760
|
+
|
|
761
|
+
${template.description}
|
|
762
|
+
|
|
763
|
+
\`\`\`jsx
|
|
764
|
+
${template.code}
|
|
771
765
|
\`\`\``;
|
|
772
766
|
})
|
|
773
767
|
.join('\n\n---\n\n');
|
|
774
|
-
const text = `# FlowDot App Templates
|
|
775
|
-
|
|
776
|
-
Below are all available app templates. Each template demonstrates a different pattern for building FlowDot apps.
|
|
777
|
-
|
|
778
|
-
## EXECUTION ENVIRONMENT
|
|
779
|
-
|
|
780
|
-
Apps run in a sandboxed browser iframe with:
|
|
781
|
-
- React 18 (global - use React.useState, React.useEffect, etc.)
|
|
782
|
-
- Tailwind CSS (full utility classes available)
|
|
783
|
-
- FlowDot color tokens: primary-50 to primary-900, secondary-50 to secondary-900
|
|
784
|
-
- invokeWorkflow(workflowHash, inputs) - to call linked workflows
|
|
785
|
-
|
|
786
|
-
## CRITICAL CODE RULES
|
|
787
|
-
|
|
788
|
-
1. **NO IMPORTS** - React is global (use React.useState, React.useEffect, React.useRef, React.useMemo, React.useCallback)
|
|
789
|
-
2. **NO EXPORTS** - Just define your function, the system handles the rest
|
|
790
|
-
3. **Function naming** - Must be: function MyAppName() { ... }
|
|
791
|
-
4. **Styling** - Use Tailwind CSS for ALL styling (no inline style objects, no CSS-in-JS)
|
|
792
|
-
|
|
793
|
-
## WORKFLOW RESPONSE FORMAT
|
|
794
|
-
|
|
795
|
-
invokeWorkflow returns data in this structure:
|
|
796
|
-
\`\`\`json
|
|
797
|
-
{
|
|
798
|
-
"data": {
|
|
799
|
-
"[nodeId]": {
|
|
800
|
-
"nodeId": "uuid",
|
|
801
|
-
"nodeTitle": "My Output Node",
|
|
802
|
-
"nodeType": "text_output",
|
|
803
|
-
"outputs": {
|
|
804
|
-
"Consolidated Text": { "value": "the actual data", "metadata": {...} }
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
\`\`\`
|
|
810
|
-
|
|
811
|
-
**IMPORTANT**: Use this helper function to extract outputs by node title:
|
|
812
|
-
\`\`\`javascript
|
|
813
|
-
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
814
|
-
const node = Object.values(result?.data || {}).find(n => n.nodeTitle === nodeTitle);
|
|
815
|
-
return node?.outputs?.[socketName]?.value;
|
|
816
|
-
};
|
|
817
|
-
\`\`\`
|
|
818
|
-
|
|
819
|
-
Example: \`const weatherData = getNodeOutput(result, 'Weather Results', 'Consolidated Text');\`
|
|
820
|
-
|
|
821
|
-
## DISPLAY MODES
|
|
822
|
-
|
|
823
|
-
Set config.displayMode when creating/updating an app:
|
|
824
|
-
- "windowed": Standard view with FlowDot header (default)
|
|
825
|
-
- "fullscreen": Full viewport, minimal floating control bar
|
|
826
|
-
- "embedded": No FlowDot UI, for iframe embedding
|
|
827
|
-
|
|
828
|
-
---
|
|
829
|
-
|
|
830
|
-
${allTemplates}
|
|
831
|
-
|
|
832
|
-
## Tips
|
|
833
|
-
|
|
834
|
-
1. Replace 'YOUR_WORKFLOW_HASH' with your actual workflow hash
|
|
835
|
-
2. Link workflows to your app using link_app_workflow
|
|
836
|
-
3. Test locally before publishing
|
|
837
|
-
4. Use mobile_code for mobile-specific layouts
|
|
768
|
+
const text = `# FlowDot App Templates
|
|
769
|
+
|
|
770
|
+
Below are all available app templates. Each template demonstrates a different pattern for building FlowDot apps.
|
|
771
|
+
|
|
772
|
+
## EXECUTION ENVIRONMENT
|
|
773
|
+
|
|
774
|
+
Apps run in a sandboxed browser iframe with:
|
|
775
|
+
- React 18 (global - use React.useState, React.useEffect, etc.)
|
|
776
|
+
- Tailwind CSS (full utility classes available)
|
|
777
|
+
- FlowDot color tokens: primary-50 to primary-900, secondary-50 to secondary-900
|
|
778
|
+
- invokeWorkflow(workflowHash, inputs) - to call linked workflows
|
|
779
|
+
|
|
780
|
+
## CRITICAL CODE RULES
|
|
781
|
+
|
|
782
|
+
1. **NO IMPORTS** - React is global (use React.useState, React.useEffect, React.useRef, React.useMemo, React.useCallback)
|
|
783
|
+
2. **NO EXPORTS** - Just define your function, the system handles the rest
|
|
784
|
+
3. **Function naming** - Must be: function MyAppName() { ... }
|
|
785
|
+
4. **Styling** - Use Tailwind CSS for ALL styling (no inline style objects, no CSS-in-JS)
|
|
786
|
+
|
|
787
|
+
## WORKFLOW RESPONSE FORMAT
|
|
788
|
+
|
|
789
|
+
invokeWorkflow returns data in this structure:
|
|
790
|
+
\`\`\`json
|
|
791
|
+
{
|
|
792
|
+
"data": {
|
|
793
|
+
"[nodeId]": {
|
|
794
|
+
"nodeId": "uuid",
|
|
795
|
+
"nodeTitle": "My Output Node",
|
|
796
|
+
"nodeType": "text_output",
|
|
797
|
+
"outputs": {
|
|
798
|
+
"Consolidated Text": { "value": "the actual data", "metadata": {...} }
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
\`\`\`
|
|
804
|
+
|
|
805
|
+
**IMPORTANT**: Use this helper function to extract outputs by node title:
|
|
806
|
+
\`\`\`javascript
|
|
807
|
+
const getNodeOutput = (result, nodeTitle, socketName = 'Consolidated Text') => {
|
|
808
|
+
const node = Object.values(result?.data || {}).find(n => n.nodeTitle === nodeTitle);
|
|
809
|
+
return node?.outputs?.[socketName]?.value;
|
|
810
|
+
};
|
|
811
|
+
\`\`\`
|
|
812
|
+
|
|
813
|
+
Example: \`const weatherData = getNodeOutput(result, 'Weather Results', 'Consolidated Text');\`
|
|
814
|
+
|
|
815
|
+
## DISPLAY MODES
|
|
816
|
+
|
|
817
|
+
Set config.displayMode when creating/updating an app:
|
|
818
|
+
- "windowed": Standard view with FlowDot header (default)
|
|
819
|
+
- "fullscreen": Full viewport, minimal floating control bar
|
|
820
|
+
- "embedded": No FlowDot UI, for iframe embedding
|
|
821
|
+
|
|
822
|
+
---
|
|
823
|
+
|
|
824
|
+
${allTemplates}
|
|
825
|
+
|
|
826
|
+
## Tips
|
|
827
|
+
|
|
828
|
+
1. Replace 'YOUR_WORKFLOW_HASH' with your actual workflow hash
|
|
829
|
+
2. Link workflows to your app using link_app_workflow
|
|
830
|
+
3. Test locally before publishing
|
|
831
|
+
4. Use mobile_code for mobile-specific layouts
|
|
838
832
|
5. Use min-h-screen for fullscreen apps`;
|
|
839
833
|
return {
|
|
840
834
|
content: [{ type: 'text', text }],
|
|
@@ -847,54 +841,53 @@ ${allTemplates}
|
|
|
847
841
|
content: [{ type: 'text', text: `Unknown template: "${templateName}". Available templates: ${available}, all` }],
|
|
848
842
|
};
|
|
849
843
|
}
|
|
850
|
-
const text = `# ${template.name}
|
|
851
|
-
|
|
852
|
-
${template.description}
|
|
853
|
-
|
|
854
|
-
## Code
|
|
855
|
-
|
|
856
|
-
\`\`\`jsx
|
|
857
|
-
${template.code}
|
|
858
|
-
\`\`\`
|
|
859
|
-
|
|
860
|
-
## Usage
|
|
861
|
-
|
|
862
|
-
1. Create a new app using create_app with this code
|
|
863
|
-
2. Replace 'YOUR_WORKFLOW_HASH' with your actual workflow hash
|
|
864
|
-
3. Replace node titles in getNodeOutput() calls with your actual node names
|
|
865
|
-
4. Link the workflow using link_app_workflow
|
|
866
|
-
5. Test and publish when ready
|
|
867
|
-
|
|
868
|
-
## Workflow Response Format
|
|
869
|
-
|
|
870
|
-
invokeWorkflow returns data in this structure:
|
|
871
|
-
\`\`\`json
|
|
872
|
-
{
|
|
873
|
-
"data": {
|
|
874
|
-
"[nodeId]": {
|
|
875
|
-
"nodeId": "uuid",
|
|
876
|
-
"nodeTitle": "My Output Node",
|
|
877
|
-
"outputs": { "Consolidated Text": { "value": "the data" } }
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
\`\`\`
|
|
882
|
-
|
|
883
|
-
Use the getNodeOutput helper (included in templates) to extract by node title.
|
|
884
|
-
|
|
885
|
-
## Critical Rules
|
|
886
|
-
|
|
887
|
-
- **NO IMPORTS** - React is global (use React.useState, React.useEffect, etc.)
|
|
888
|
-
- **NO EXPORTS** - Just define your function
|
|
889
|
-
- **invokeWorkflow(hash, inputs)** - Call a linked workflow
|
|
890
|
-
- **Tailwind CSS** - Full Tailwind for styling
|
|
891
|
-
|
|
892
|
-
## Other Templates
|
|
893
|
-
|
|
894
|
-
Available templates: ${Object.keys(TEMPLATES).join(', ')}
|
|
844
|
+
const text = `# ${template.name}
|
|
845
|
+
|
|
846
|
+
${template.description}
|
|
847
|
+
|
|
848
|
+
## Code
|
|
849
|
+
|
|
850
|
+
\`\`\`jsx
|
|
851
|
+
${template.code}
|
|
852
|
+
\`\`\`
|
|
853
|
+
|
|
854
|
+
## Usage
|
|
855
|
+
|
|
856
|
+
1. Create a new app using create_app with this code
|
|
857
|
+
2. Replace 'YOUR_WORKFLOW_HASH' with your actual workflow hash
|
|
858
|
+
3. Replace node titles in getNodeOutput() calls with your actual node names
|
|
859
|
+
4. Link the workflow using link_app_workflow
|
|
860
|
+
5. Test and publish when ready
|
|
861
|
+
|
|
862
|
+
## Workflow Response Format
|
|
863
|
+
|
|
864
|
+
invokeWorkflow returns data in this structure:
|
|
865
|
+
\`\`\`json
|
|
866
|
+
{
|
|
867
|
+
"data": {
|
|
868
|
+
"[nodeId]": {
|
|
869
|
+
"nodeId": "uuid",
|
|
870
|
+
"nodeTitle": "My Output Node",
|
|
871
|
+
"outputs": { "Consolidated Text": { "value": "the data" } }
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
\`\`\`
|
|
876
|
+
|
|
877
|
+
Use the getNodeOutput helper (included in templates) to extract by node title.
|
|
878
|
+
|
|
879
|
+
## Critical Rules
|
|
880
|
+
|
|
881
|
+
- **NO IMPORTS** - React is global (use React.useState, React.useEffect, etc.)
|
|
882
|
+
- **NO EXPORTS** - Just define your function
|
|
883
|
+
- **invokeWorkflow(hash, inputs)** - Call a linked workflow
|
|
884
|
+
- **Tailwind CSS** - Full Tailwind for styling
|
|
885
|
+
|
|
886
|
+
## Other Templates
|
|
887
|
+
|
|
888
|
+
Available templates: ${Object.keys(TEMPLATES).join(', ')}
|
|
895
889
|
Use \`get_app_template(template: "all")\` to see all templates.`;
|
|
896
890
|
return {
|
|
897
891
|
content: [{ type: 'text', text }],
|
|
898
892
|
};
|
|
899
893
|
}
|
|
900
|
-
//# sourceMappingURL=get-app-template.js.map
|