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