@d34dman/flowdrop 0.0.1
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 +293 -0
- package/dist/adapters/WorkflowAdapter.d.ts +166 -0
- package/dist/adapters/WorkflowAdapter.js +337 -0
- package/dist/api/client.d.ts +79 -0
- package/dist/api/client.js +208 -0
- package/dist/app.css +0 -0
- package/dist/clients/ApiClient.d.ts +203 -0
- package/dist/clients/ApiClient.js +212 -0
- package/dist/components/App.svelte +237 -0
- package/dist/components/App.svelte.d.ts +3 -0
- package/dist/components/CanvasBanner.svelte +51 -0
- package/dist/components/CanvasBanner.svelte.d.ts +22 -0
- package/dist/components/LoadingSpinner.svelte +36 -0
- package/dist/components/LoadingSpinner.svelte.d.ts +8 -0
- package/dist/components/Node.svelte +38 -0
- package/dist/components/Node.svelte.d.ts +4 -0
- package/dist/components/NodeSidebar.svelte +500 -0
- package/dist/components/NodeSidebar.svelte.d.ts +9 -0
- package/dist/components/WorkflowEditor.svelte +542 -0
- package/dist/components/WorkflowEditor.svelte.d.ts +10 -0
- package/dist/components/WorkflowNode.svelte +558 -0
- package/dist/components/WorkflowNode.svelte.d.ts +11 -0
- package/dist/data/samples.d.ts +17 -0
- package/dist/data/samples.js +1193 -0
- package/dist/examples/adapter-usage.d.ts +66 -0
- package/dist/examples/adapter-usage.js +138 -0
- package/dist/examples/api-client-usage.d.ts +31 -0
- package/dist/examples/api-client-usage.js +241 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +27 -0
- package/dist/services/api.d.ts +110 -0
- package/dist/services/api.js +149 -0
- package/dist/services/workflowStorage.d.ts +37 -0
- package/dist/services/workflowStorage.js +116 -0
- package/dist/styles/base.css +858 -0
- package/dist/svelte-app.d.ts +17 -0
- package/dist/svelte-app.js +30 -0
- package/dist/types/index.d.ts +179 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/colors.d.ts +121 -0
- package/dist/utils/colors.js +240 -0
- package/dist/utils/connections.d.ts +47 -0
- package/dist/utils/connections.js +240 -0
- package/dist/utils/icons.d.ts +102 -0
- package/dist/utils/icons.js +149 -0
- package/package.json +99 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowDrop API Client
|
|
3
|
+
* Type-safe client for the FlowDrop API
|
|
4
|
+
*/
|
|
5
|
+
import type { NodeMetadata, Workflow, ApiResponse, NodesResponse, WorkflowResponse, WorkflowsResponse } from "../types/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* API Client Configuration
|
|
8
|
+
*/
|
|
9
|
+
export interface ApiClientConfig {
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
timeout?: number;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Request options for API calls
|
|
17
|
+
*/
|
|
18
|
+
export interface RequestOptions {
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Pagination parameters
|
|
24
|
+
*/
|
|
25
|
+
export interface PaginationParams {
|
|
26
|
+
limit?: number;
|
|
27
|
+
offset?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Node types query parameters
|
|
31
|
+
*/
|
|
32
|
+
export interface NodeTypesQuery {
|
|
33
|
+
category?: string;
|
|
34
|
+
search?: string;
|
|
35
|
+
limit?: number;
|
|
36
|
+
offset?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Workflows query parameters
|
|
40
|
+
*/
|
|
41
|
+
export interface WorkflowsQuery {
|
|
42
|
+
search?: string;
|
|
43
|
+
tags?: string;
|
|
44
|
+
limit?: number;
|
|
45
|
+
offset?: number;
|
|
46
|
+
sort?: "created_at" | "updated_at" | "name";
|
|
47
|
+
order?: "asc" | "desc";
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create workflow request
|
|
51
|
+
*/
|
|
52
|
+
export interface CreateWorkflowRequest {
|
|
53
|
+
name: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
nodes?: any[];
|
|
56
|
+
edges?: any[];
|
|
57
|
+
tags?: string[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Update workflow request
|
|
61
|
+
*/
|
|
62
|
+
export interface UpdateWorkflowRequest {
|
|
63
|
+
name?: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
nodes?: any[];
|
|
66
|
+
edges?: any[];
|
|
67
|
+
tags?: string[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Execute workflow request
|
|
71
|
+
*/
|
|
72
|
+
export interface ExecuteWorkflowRequest {
|
|
73
|
+
inputs: Record<string, unknown>;
|
|
74
|
+
options?: {
|
|
75
|
+
timeout?: number;
|
|
76
|
+
maxSteps?: number;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* FlowDrop API Client Class
|
|
81
|
+
*/
|
|
82
|
+
export declare class ApiClient {
|
|
83
|
+
private config;
|
|
84
|
+
private defaultHeaders;
|
|
85
|
+
constructor(config: ApiClientConfig);
|
|
86
|
+
/**
|
|
87
|
+
* Make an HTTP request
|
|
88
|
+
*/
|
|
89
|
+
private request;
|
|
90
|
+
/**
|
|
91
|
+
* Check API health
|
|
92
|
+
*/
|
|
93
|
+
healthCheck(): Promise<{
|
|
94
|
+
status: string;
|
|
95
|
+
timestamp: string;
|
|
96
|
+
version: string;
|
|
97
|
+
}>;
|
|
98
|
+
/**
|
|
99
|
+
* Get all node types
|
|
100
|
+
*/
|
|
101
|
+
getNodeTypes(query?: NodeTypesQuery): Promise<NodesResponse>;
|
|
102
|
+
/**
|
|
103
|
+
* Get node type by ID
|
|
104
|
+
*/
|
|
105
|
+
getNodeType(id: string): Promise<NodeTypeResponse>;
|
|
106
|
+
/**
|
|
107
|
+
* Get all workflows
|
|
108
|
+
*/
|
|
109
|
+
getWorkflows(query?: WorkflowsQuery): Promise<WorkflowsResponse>;
|
|
110
|
+
/**
|
|
111
|
+
* Get workflow by ID
|
|
112
|
+
*/
|
|
113
|
+
getWorkflow(id: string): Promise<WorkflowResponse>;
|
|
114
|
+
/**
|
|
115
|
+
* Create a new workflow
|
|
116
|
+
*/
|
|
117
|
+
createWorkflow(data: CreateWorkflowRequest): Promise<WorkflowResponse>;
|
|
118
|
+
/**
|
|
119
|
+
* Update workflow
|
|
120
|
+
*/
|
|
121
|
+
updateWorkflow(id: string, data: UpdateWorkflowRequest): Promise<WorkflowResponse>;
|
|
122
|
+
/**
|
|
123
|
+
* Delete workflow
|
|
124
|
+
*/
|
|
125
|
+
deleteWorkflow(id: string): Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* Execute workflow
|
|
128
|
+
*/
|
|
129
|
+
executeWorkflow(id: string, data: ExecuteWorkflowRequest): Promise<ExecutionResponse>;
|
|
130
|
+
/**
|
|
131
|
+
* Get execution status
|
|
132
|
+
*/
|
|
133
|
+
getExecutionStatus(id: string): Promise<ExecutionStatusResponse>;
|
|
134
|
+
/**
|
|
135
|
+
* Cancel execution
|
|
136
|
+
*/
|
|
137
|
+
cancelExecution(id: string): Promise<ExecutionResponse>;
|
|
138
|
+
/**
|
|
139
|
+
* Export workflow
|
|
140
|
+
*/
|
|
141
|
+
exportWorkflow(id: string, format?: "json" | "yaml"): Promise<Workflow>;
|
|
142
|
+
/**
|
|
143
|
+
* Import workflow
|
|
144
|
+
*/
|
|
145
|
+
importWorkflow(workflow: Workflow): Promise<WorkflowResponse>;
|
|
146
|
+
/**
|
|
147
|
+
* Validate workflow
|
|
148
|
+
*/
|
|
149
|
+
validateWorkflow(workflow: Workflow): Promise<ValidationResponse>;
|
|
150
|
+
/**
|
|
151
|
+
* Wait for execution completion
|
|
152
|
+
*/
|
|
153
|
+
waitForExecution(id: string, pollInterval?: number): Promise<ExecutionStatusResponse>;
|
|
154
|
+
/**
|
|
155
|
+
* Get workflows by tag
|
|
156
|
+
*/
|
|
157
|
+
getWorkflowsByTag(tag: string, query?: Omit<WorkflowsQuery, "tags">): Promise<WorkflowsResponse>;
|
|
158
|
+
/**
|
|
159
|
+
* Get node types by category
|
|
160
|
+
*/
|
|
161
|
+
getNodeTypesByCategory(category: string, query?: Omit<NodeTypesQuery, "category">): Promise<NodesResponse>;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* API Error Class
|
|
165
|
+
*/
|
|
166
|
+
export declare class ApiError extends Error {
|
|
167
|
+
status: number;
|
|
168
|
+
code?: string;
|
|
169
|
+
details?: unknown;
|
|
170
|
+
constructor(status: number, message: string, code?: string, details?: unknown);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Execution response types
|
|
174
|
+
*/
|
|
175
|
+
export interface ExecutionResponse extends ApiResponse<{
|
|
176
|
+
executionId: string;
|
|
177
|
+
status: "pending" | "running" | "completed" | "failed" | "cancelled";
|
|
178
|
+
message: string;
|
|
179
|
+
}> {
|
|
180
|
+
}
|
|
181
|
+
export interface ExecutionStatusResponse extends ApiResponse<{
|
|
182
|
+
executionId: string;
|
|
183
|
+
status: "pending" | "running" | "completed" | "failed" | "cancelled";
|
|
184
|
+
result?: unknown;
|
|
185
|
+
error?: string;
|
|
186
|
+
startTime: string;
|
|
187
|
+
endTime?: string;
|
|
188
|
+
duration?: number;
|
|
189
|
+
nodeResults?: Record<string, unknown>;
|
|
190
|
+
}> {
|
|
191
|
+
}
|
|
192
|
+
export interface ValidationResponse extends ApiResponse<{
|
|
193
|
+
valid: boolean;
|
|
194
|
+
errors: string[];
|
|
195
|
+
warnings: string[];
|
|
196
|
+
suggestions?: string[];
|
|
197
|
+
}> {
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Type for node type response
|
|
201
|
+
*/
|
|
202
|
+
export interface NodeTypeResponse extends ApiResponse<NodeMetadata> {
|
|
203
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowDrop API Client
|
|
3
|
+
* Type-safe client for the FlowDrop API
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* FlowDrop API Client Class
|
|
7
|
+
*/
|
|
8
|
+
export class ApiClient {
|
|
9
|
+
config;
|
|
10
|
+
defaultHeaders;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.config = {
|
|
13
|
+
timeout: 30000,
|
|
14
|
+
...config
|
|
15
|
+
};
|
|
16
|
+
this.defaultHeaders = {
|
|
17
|
+
"Content-Type": "application/json",
|
|
18
|
+
...config.headers
|
|
19
|
+
};
|
|
20
|
+
if (config.apiKey) {
|
|
21
|
+
this.defaultHeaders.Authorization = `Bearer ${config.apiKey}`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Make an HTTP request
|
|
26
|
+
*/
|
|
27
|
+
async request(method, path, data, options = {}) {
|
|
28
|
+
const url = `${this.config.baseUrl}${path}`;
|
|
29
|
+
const headers = { ...this.defaultHeaders, ...options.headers };
|
|
30
|
+
const timeout = options.timeout || this.config.timeout;
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(url, {
|
|
35
|
+
method,
|
|
36
|
+
headers,
|
|
37
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
38
|
+
signal: controller.signal
|
|
39
|
+
});
|
|
40
|
+
clearTimeout(timeoutId);
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const errorData = await response.json().catch(() => ({}));
|
|
43
|
+
throw new ApiError(response.status, errorData.error || `HTTP ${response.status}`, errorData.code, errorData.details);
|
|
44
|
+
}
|
|
45
|
+
return await response.json();
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
clearTimeout(timeoutId);
|
|
49
|
+
if (error instanceof ApiError) {
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
53
|
+
throw new ApiError(408, "Request timeout", "TIMEOUT");
|
|
54
|
+
}
|
|
55
|
+
throw new ApiError(500, error instanceof Error ? error.message : "Network error", "NETWORK_ERROR");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// ===== HEALTH CHECK =====
|
|
59
|
+
/**
|
|
60
|
+
* Check API health
|
|
61
|
+
*/
|
|
62
|
+
async healthCheck() {
|
|
63
|
+
return this.request("GET", "/health");
|
|
64
|
+
}
|
|
65
|
+
// ===== NODE TYPES =====
|
|
66
|
+
/**
|
|
67
|
+
* Get all node types
|
|
68
|
+
*/
|
|
69
|
+
async getNodeTypes(query) {
|
|
70
|
+
const params = new URLSearchParams();
|
|
71
|
+
if (query?.category)
|
|
72
|
+
params.append("category", query.category);
|
|
73
|
+
if (query?.search)
|
|
74
|
+
params.append("search", query.search);
|
|
75
|
+
if (query?.limit)
|
|
76
|
+
params.append("limit", query.limit.toString());
|
|
77
|
+
if (query?.offset)
|
|
78
|
+
params.append("offset", query.offset.toString());
|
|
79
|
+
const path = `/nodes${params.toString() ? `?${params.toString()}` : ""}`;
|
|
80
|
+
return this.request("GET", path);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get node type by ID
|
|
84
|
+
*/
|
|
85
|
+
async getNodeType(id) {
|
|
86
|
+
return this.request("GET", `/nodes/${id}`);
|
|
87
|
+
}
|
|
88
|
+
// ===== WORKFLOWS =====
|
|
89
|
+
/**
|
|
90
|
+
* Get all workflows
|
|
91
|
+
*/
|
|
92
|
+
async getWorkflows(query) {
|
|
93
|
+
const params = new URLSearchParams();
|
|
94
|
+
if (query?.search)
|
|
95
|
+
params.append("search", query.search);
|
|
96
|
+
if (query?.tags)
|
|
97
|
+
params.append("tags", query.tags);
|
|
98
|
+
if (query?.limit)
|
|
99
|
+
params.append("limit", query.limit.toString());
|
|
100
|
+
if (query?.offset)
|
|
101
|
+
params.append("offset", query.offset.toString());
|
|
102
|
+
if (query?.sort)
|
|
103
|
+
params.append("sort", query.sort);
|
|
104
|
+
if (query?.order)
|
|
105
|
+
params.append("order", query.order);
|
|
106
|
+
const path = `/workflows${params.toString() ? `?${params.toString()}` : ""}`;
|
|
107
|
+
return this.request("GET", path);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get workflow by ID
|
|
111
|
+
*/
|
|
112
|
+
async getWorkflow(id) {
|
|
113
|
+
return this.request("GET", `/workflows/${id}`);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Create a new workflow
|
|
117
|
+
*/
|
|
118
|
+
async createWorkflow(data) {
|
|
119
|
+
return this.request("POST", "/workflows", data);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Update workflow
|
|
123
|
+
*/
|
|
124
|
+
async updateWorkflow(id, data) {
|
|
125
|
+
return this.request("PUT", `/workflows/${id}`, data);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Delete workflow
|
|
129
|
+
*/
|
|
130
|
+
async deleteWorkflow(id) {
|
|
131
|
+
return this.request("DELETE", `/workflows/${id}`);
|
|
132
|
+
}
|
|
133
|
+
// ===== WORKFLOW EXECUTION =====
|
|
134
|
+
/**
|
|
135
|
+
* Execute workflow
|
|
136
|
+
*/
|
|
137
|
+
async executeWorkflow(id, data) {
|
|
138
|
+
return this.request("POST", `/workflows/${id}/execute`, data);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get execution status
|
|
142
|
+
*/
|
|
143
|
+
async getExecutionStatus(id) {
|
|
144
|
+
return this.request("GET", `/executions/${id}`);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Cancel execution
|
|
148
|
+
*/
|
|
149
|
+
async cancelExecution(id) {
|
|
150
|
+
return this.request("POST", `/executions/${id}/cancel`);
|
|
151
|
+
}
|
|
152
|
+
// ===== IMPORT/EXPORT =====
|
|
153
|
+
/**
|
|
154
|
+
* Export workflow
|
|
155
|
+
*/
|
|
156
|
+
async exportWorkflow(id, format = "json") {
|
|
157
|
+
return this.request("GET", `/workflows/${id}/export?format=${format}`);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Import workflow
|
|
161
|
+
*/
|
|
162
|
+
async importWorkflow(workflow) {
|
|
163
|
+
return this.request("POST", "/workflows/import", workflow);
|
|
164
|
+
}
|
|
165
|
+
// ===== VALIDATION =====
|
|
166
|
+
/**
|
|
167
|
+
* Validate workflow
|
|
168
|
+
*/
|
|
169
|
+
async validateWorkflow(workflow) {
|
|
170
|
+
return this.request("POST", "/workflows/validate", workflow);
|
|
171
|
+
}
|
|
172
|
+
// ===== UTILITY METHODS =====
|
|
173
|
+
/**
|
|
174
|
+
* Wait for execution completion
|
|
175
|
+
*/
|
|
176
|
+
async waitForExecution(id, pollInterval = 1000) {
|
|
177
|
+
while (true) {
|
|
178
|
+
const status = await this.getExecutionStatus(id);
|
|
179
|
+
if (status.data?.status === "completed" || status.data?.status === "failed" || status.data?.status === "cancelled") {
|
|
180
|
+
return status;
|
|
181
|
+
}
|
|
182
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get workflows by tag
|
|
187
|
+
*/
|
|
188
|
+
async getWorkflowsByTag(tag, query) {
|
|
189
|
+
return this.getWorkflows({ ...query, tags: tag });
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get node types by category
|
|
193
|
+
*/
|
|
194
|
+
async getNodeTypesByCategory(category, query) {
|
|
195
|
+
return this.getNodeTypes({ ...query, category });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* API Error Class
|
|
200
|
+
*/
|
|
201
|
+
export class ApiError extends Error {
|
|
202
|
+
status;
|
|
203
|
+
code;
|
|
204
|
+
details;
|
|
205
|
+
constructor(status, message, code, details) {
|
|
206
|
+
super(message);
|
|
207
|
+
this.status = status;
|
|
208
|
+
this.code = code;
|
|
209
|
+
this.details = details;
|
|
210
|
+
this.name = "ApiError";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
FlowDrop Demo Page
|
|
3
|
+
Demonstrates the FlowDrop library with sample data
|
|
4
|
+
Styled with BEM syntax
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import { onMount } from "svelte";
|
|
9
|
+
import WorkflowEditor from "./WorkflowEditor.svelte";
|
|
10
|
+
import { api } from "../services/api.js";
|
|
11
|
+
import type { NodeMetadata, Workflow } from "../types/index.js";
|
|
12
|
+
import { getDefaultIcon } from '../utils/icons.js';
|
|
13
|
+
|
|
14
|
+
let nodes = $state<NodeMetadata[]>([]);
|
|
15
|
+
let workflow = $state<Workflow | undefined>(undefined);
|
|
16
|
+
let error = $state<string | null>(null);
|
|
17
|
+
let loading = $state(true);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fetch node types from the server
|
|
21
|
+
*/
|
|
22
|
+
async function fetchNodeTypes(): Promise<void> {
|
|
23
|
+
try {
|
|
24
|
+
loading = true;
|
|
25
|
+
error = null;
|
|
26
|
+
|
|
27
|
+
console.log("Fetching node types from server...");
|
|
28
|
+
const fetchedNodes = await api.nodes.getNodes();
|
|
29
|
+
|
|
30
|
+
console.log("✅ Fetched", fetchedNodes.length, "node types from server");
|
|
31
|
+
nodes = fetchedNodes;
|
|
32
|
+
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error("❌ Failed to fetch node types:", err);
|
|
35
|
+
error = err instanceof Error ? err.message : "Failed to load node types";
|
|
36
|
+
} finally {
|
|
37
|
+
loading = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Retry loading node types
|
|
43
|
+
*/
|
|
44
|
+
function retryLoad(): void {
|
|
45
|
+
fetchNodeTypes();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Load node types on mount
|
|
49
|
+
onMount(() => {
|
|
50
|
+
fetchNodeTypes();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Handle workflow export
|
|
55
|
+
*/
|
|
56
|
+
function handleWorkflowExport(): void {
|
|
57
|
+
if (!workflow) return;
|
|
58
|
+
|
|
59
|
+
const dataStr = JSON.stringify(workflow, null, 2);
|
|
60
|
+
const dataBlob = new Blob([dataStr], { type: "application/json" });
|
|
61
|
+
const url = URL.createObjectURL(dataBlob);
|
|
62
|
+
const link = document.createElement("a");
|
|
63
|
+
link.href = url;
|
|
64
|
+
link.download = `${workflow.name}.json`;
|
|
65
|
+
link.click();
|
|
66
|
+
URL.revokeObjectURL(url);
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<svelte:head>
|
|
71
|
+
<title>FlowDrop - LLM Workflow Editor</title>
|
|
72
|
+
<meta name="description" content="A modern drag-and-drop workflow editor for LLM applications" />
|
|
73
|
+
</svelte:head>
|
|
74
|
+
|
|
75
|
+
<div class="flowdrop-app" style="min-height: 1200px;">
|
|
76
|
+
<!-- Header -->
|
|
77
|
+
<div class="flowdrop-navbar">
|
|
78
|
+
<div class="flowdrop-navbar__start">
|
|
79
|
+
<!-- Logo and Title -->
|
|
80
|
+
<div class="flowdrop-flex flowdrop-gap--3">
|
|
81
|
+
<div class="flowdrop-logo flowdrop-logo--header">
|
|
82
|
+
FD
|
|
83
|
+
</div>
|
|
84
|
+
<div>
|
|
85
|
+
<h1 class="flowdrop-text--lg flowdrop-font--bold">FlowDrop</h1>
|
|
86
|
+
<p class="flowdrop-text--xs flowdrop-text--gray">LLM Workflow Editor</p>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div class="flowdrop-navbar__center">
|
|
92
|
+
<!-- TODO: Add navigation -->
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<div class="flowdrop-navbar__end">
|
|
96
|
+
<!-- TODO: Add user menu -->
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<!-- Main Content -->
|
|
101
|
+
<main class="flowdrop-main" style="height: calc(100vh - 60px);">
|
|
102
|
+
<!-- Status Display -->
|
|
103
|
+
{#if loading}
|
|
104
|
+
<div class="flowdrop-status flowdrop-status--loading">
|
|
105
|
+
<div class="flowdrop-status__content">
|
|
106
|
+
<div class="flowdrop-flex flowdrop-gap--3">
|
|
107
|
+
<div class="flowdrop-spinner"></div>
|
|
108
|
+
<span class="flowdrop-text--sm flowdrop-font--medium">Loading node types...</span>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
{:else if error}
|
|
113
|
+
<div class="flowdrop-status flowdrop-status--error">
|
|
114
|
+
<div class="flowdrop-status__content">
|
|
115
|
+
<div class="flowdrop-flex flowdrop-gap--3">
|
|
116
|
+
<div class="flowdrop-status__indicator flowdrop-status__indicator--error"></div>
|
|
117
|
+
<span class="flowdrop-text--sm flowdrop-font--medium">Error: {error}</span>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="flowdrop-flex flowdrop-gap--2">
|
|
120
|
+
<button
|
|
121
|
+
class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--outline"
|
|
122
|
+
onclick={retryLoad}
|
|
123
|
+
type="button"
|
|
124
|
+
>
|
|
125
|
+
Retry
|
|
126
|
+
</button>
|
|
127
|
+
<button
|
|
128
|
+
class="flowdrop-btn flowdrop-btn--ghost flowdrop-btn--sm"
|
|
129
|
+
onclick={() => error = null}
|
|
130
|
+
type="button"
|
|
131
|
+
>
|
|
132
|
+
✕
|
|
133
|
+
</button>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
{/if}
|
|
138
|
+
|
|
139
|
+
<!-- Workflow Editor -->
|
|
140
|
+
<div class="flowdrop-editor-container" style="height: {(loading || error) ? 'calc(100% - 60px)' : '100%'};">
|
|
141
|
+
<WorkflowEditor
|
|
142
|
+
nodes={nodes}
|
|
143
|
+
workflow={workflow}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
</main>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<style>
|
|
150
|
+
.flowdrop-app {
|
|
151
|
+
background: linear-gradient(135deg, #f9fafb 0%, #e0e7ff 50%, #c7d2fe 100%);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.flowdrop-navbar {
|
|
155
|
+
height: 60px;
|
|
156
|
+
display: flex;
|
|
157
|
+
align-items: center;
|
|
158
|
+
justify-content: space-between;
|
|
159
|
+
padding: 0 1rem;
|
|
160
|
+
background-color: #ffffff;
|
|
161
|
+
border-bottom: 1px solid #e5e7eb;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.flowdrop-navbar__start {
|
|
165
|
+
display: flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.flowdrop-logo {
|
|
170
|
+
width: 2rem;
|
|
171
|
+
height: 2rem;
|
|
172
|
+
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
|
173
|
+
border-radius: 0.5rem;
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
justify-content: center;
|
|
177
|
+
color: #ffffff;
|
|
178
|
+
font-weight: 700;
|
|
179
|
+
font-size: 0.875rem;
|
|
180
|
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.flowdrop-logo--header {
|
|
184
|
+
width: 40px;
|
|
185
|
+
height: 40px;
|
|
186
|
+
font-size: 1.25rem;
|
|
187
|
+
margin-top: 15px;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.flowdrop-main {
|
|
191
|
+
position: relative;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.flowdrop-status {
|
|
195
|
+
background-color: #eff6ff;
|
|
196
|
+
border-bottom: 1px solid #bfdbfe;
|
|
197
|
+
padding: 1rem;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.flowdrop-status--error {
|
|
201
|
+
background-color: #fef2f2;
|
|
202
|
+
border-bottom: 1px solid #fecaca;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.flowdrop-status__content {
|
|
206
|
+
max-width: 80rem;
|
|
207
|
+
margin: 0 auto;
|
|
208
|
+
display: flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
justify-content: space-between;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.flowdrop-status__indicator {
|
|
214
|
+
width: 0.5rem;
|
|
215
|
+
height: 0.5rem;
|
|
216
|
+
border-radius: 50%;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.flowdrop-status__indicator--error {
|
|
220
|
+
background-color: #ef4444;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.flowdrop-editor-container {
|
|
224
|
+
position: relative;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Ensure full height for screens larger than 1200px */
|
|
228
|
+
@media (min-height: 1200px) {
|
|
229
|
+
:global(html), :global(body) {
|
|
230
|
+
height: 100%;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
:global(#svelte) {
|
|
234
|
+
height: 100%;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
</style>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Icon from '@iconify/svelte';
|
|
3
|
+
export let title: string;
|
|
4
|
+
export let description: string;
|
|
5
|
+
export let iconName: string | undefined;
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div class="flowdrop-canvas-banner">
|
|
9
|
+
<div class="flowdrop-card">
|
|
10
|
+
<div class="flowdrop-card__body flowdrop-text--center">
|
|
11
|
+
<div class="flowdrop-canvas-banner__icon">
|
|
12
|
+
{#if iconName}
|
|
13
|
+
<Icon icon={iconName} class="flowdrop-canvas-banner__icon-svg" />
|
|
14
|
+
{/if}
|
|
15
|
+
</div>
|
|
16
|
+
<h3 class="flowdrop-canvas-banner__title">{title}</h3>
|
|
17
|
+
<p class="flowdrop-canvas-banner__description">{description}</p>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<style>
|
|
23
|
+
.flowdrop-canvas-banner {
|
|
24
|
+
position: absolute;
|
|
25
|
+
inset: 0;
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.flowdrop-canvas-banner__icon {
|
|
33
|
+
font-size: 3.75rem;
|
|
34
|
+
margin-bottom: 1rem;
|
|
35
|
+
display: flex;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
align-items: center;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.flowdrop-canvas-banner__title {
|
|
41
|
+
font-size: 1.125rem;
|
|
42
|
+
font-weight: 700;
|
|
43
|
+
margin-bottom: 0.5rem;
|
|
44
|
+
color: #111827;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.flowdrop-canvas-banner__description {
|
|
48
|
+
font-size: 0.875rem;
|
|
49
|
+
color: #6b7280;
|
|
50
|
+
}
|
|
51
|
+
</style>
|