openfree commited on
Commit
143e728
·
verified ·
1 Parent(s): 55ca5fc

Delete src/frontend/Index.svelte

Browse files
Files changed (1) hide show
  1. src/frontend/Index.svelte +0 -2583
src/frontend/Index.svelte DELETED
@@ -1,2583 +0,0 @@
1
- <script lang="ts">
2
- import { createEventDispatcher, onMount } from 'svelte';
3
-
4
- export let value: { nodes: any[]; edges: any[] } = { nodes: [], edges: [] };
5
- export let elem_id = "";
6
- export let elem_classes: string[] = [];
7
- export let visible = true;
8
- export const container = true;
9
- export const scale: number | null = null;
10
- export let min_width: number | undefined = undefined;
11
- export const gradio: any = {};
12
-
13
- const dispatch = createEventDispatcher<{
14
- change: { nodes: any[]; edges: any[] };
15
- input: { nodes: any[]; edges: any[] };
16
- }>();
17
-
18
- // State management
19
- let canvas: HTMLDivElement;
20
- let canvasContainer: HTMLDivElement;
21
- let isDragging = false;
22
- let isDraggingFromSidebar = false;
23
- let dragNode: any = null;
24
- let dragOffset = { x: 0, y: 0 };
25
- let isConnecting = false;
26
- let connectionStart: any = null;
27
- let mousePos = { x: 0, y: 0 };
28
- let selectedNode: any = null;
29
- let sidebarCollapsed = false;
30
- let propertyPanelCollapsed = false;
31
-
32
- // Workflow metadata
33
- let workflowName = "My Workflow";
34
- let workflowId = "workflow-" + Date.now();
35
-
36
- // Zoom and pan state
37
- let zoomLevel = 0.6;
38
- let panOffset = { x: 0, y: 0 };
39
- let isPanning = false;
40
- let lastPanPoint = { x: 0, y: 0 };
41
-
42
- // Define default workflow
43
- const defaultWorkflow = {
44
- workflowName = "My Workflow";
45
- workflowId = "workflow-" + Date.now();
46
- nodes: [],
47
- edges: []
48
- };
49
-
50
-
51
-
52
- // Initialize nodes and edges
53
- let nodes = [];
54
- let edges = [];
55
- // let nodes = value?.nodes?.length > 0 ? [...value.nodes] : defaultWorkflow.nodes;
56
- // let edges = value?.edges?.length > 0 ? [...value.edges] : defaultWorkflow.edges;
57
-
58
- // Initialize workflow metadata
59
- if (value?.workflow_name) {
60
- workflowName = value.workflow_name;
61
- }
62
- if (value?.workflow_id) {
63
- workflowId = value.workflow_id;
64
- }
65
-
66
-
67
- $: if (!value) {
68
- value = { nodes: [], edges: [] };
69
- }
70
-
71
- // Component categories with new node types
72
- const componentCategories = {
73
- 'Input/Output': {
74
- icon: '📥',
75
- components: {
76
- ChatInput: {
77
- label: 'Chat Input',
78
- icon: '💬',
79
- color: '#4CAF50',
80
- defaultData: {
81
- display_name: 'Chat Input',
82
- template: {
83
- input_value: {
84
- display_name: 'User Message',
85
- type: 'string',
86
- value: '',
87
- is_handle: true
88
- }
89
- },
90
- resources: {
91
- cpu: 0.1,
92
- memory: '128Mi',
93
- gpu: 'none'
94
- }
95
- }
96
- },
97
- ChatOutput: {
98
- label: 'Chat Output',
99
- icon: '💭',
100
- color: '#F44336',
101
- defaultData: {
102
- display_name: 'Chat Output',
103
- template: {
104
- response: {
105
- display_name: 'AI Response',
106
- type: 'string',
107
- is_handle: true
108
- }
109
- },
110
- resources: {
111
- cpu: 0.1,
112
- memory: '128Mi',
113
- gpu: 'none'
114
- }
115
- }
116
- },
117
- Input: {
118
- label: 'Input',
119
- icon: '📥',
120
- color: '#2196F3',
121
- defaultData: {
122
- display_name: 'Source Data',
123
- template: {
124
- data_type: {
125
- display_name: 'Data Type',
126
- type: 'options',
127
- options: ['string', 'image', 'video', 'audio', 'file'],
128
- value: 'string'
129
- },
130
- value: {
131
- display_name: 'Value or Path',
132
- type: 'string',
133
- value: 'This is the initial text.'
134
- },
135
- data: {
136
- display_name: 'Output Data',
137
- type: 'object',
138
- is_handle: true
139
- }
140
- },
141
- resources: {
142
- cpu: 0.1,
143
- memory: '128Mi',
144
- gpu: 'none'
145
- }
146
- }
147
- },
148
- Output: {
149
- label: 'Output',
150
- icon: '📤',
151
- color: '#FF9800',
152
- defaultData: {
153
- display_name: 'Final Result',
154
- template: {
155
- input_data: {
156
- display_name: 'Input Data',
157
- type: 'object',
158
- is_handle: true
159
- }
160
- },
161
- resources: {
162
- cpu: 0.1,
163
- memory: '128Mi',
164
- gpu: 'none'
165
- }
166
- }
167
- }
168
- }
169
- },
170
- 'AI & Language': {
171
- icon: '🤖',
172
- components: {
173
- llmNode: { // ① 새 노드
174
- label: 'AI Processing',
175
- icon: '🧠',
176
- color: '#2563eb',
177
- defaultData: {
178
- display_name: 'AI Processing',
179
- template: {
180
- provider: { display_name: 'Provider', type: 'options',
181
- options: ['VIDraft', 'OpenAI'], value: 'VIDraft' },
182
- model: { display_name: 'Model', type: 'string',
183
- value: 'Gemma-3-r1984-27B' },
184
- temperature: { display_name: 'Temperature', type: 'number',
185
- value: 0.7, min: 0, max: 2, step: 0.1 },
186
- system_prompt:{ display_name: 'System Prompt', type: 'string',
187
- value: 'You are a helpful assistant.' },
188
- user_prompt: { display_name: 'User Prompt', type: 'string',
189
- value: '', is_handle: true }, // ⬅ 입력 핸들
190
- response: { display_name: 'Response', type: 'string',
191
- value: '', is_handle: true } // ⬅ 출력 핸들
192
- }
193
- }
194
- },
195
- textNode: { // ② 새 노드
196
- label: 'Markdown',
197
- icon: '📝',
198
- color: '#4b5563',
199
- defaultData: {
200
- display_name: 'Markdown',
201
- template: {
202
- text: { display_name: 'Markdown', type: 'string',
203
- value: '### Write any markdown here', is_handle: true }
204
- }
205
- }
206
- },
207
-
208
-
209
-
210
- OpenAIModel: {
211
- label: 'OpenAI Model',
212
- icon: '🎯',
213
- color: '#9C27B0',
214
- defaultData: {
215
- display_name: 'OpenAI Model',
216
- template: {
217
- model: {
218
- display_name: 'Model',
219
- type: 'options',
220
- value: 'gpt-4',
221
- options: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo']
222
- },
223
- temperature: {
224
- display_name: 'Temperature',
225
- type: 'number',
226
- value: 0.7,
227
- min: 0,
228
- max: 1
229
- },
230
- max_tokens: {
231
- display_name: 'Max Tokens',
232
- type: 'number',
233
- value: 2048,
234
- min: 1,
235
- max: 4096
236
- },
237
- api_key: {
238
- display_name: 'API Key',
239
- type: 'SecretStr',
240
- value: '',
241
- env_var: 'OPENAI_API_KEY'
242
- },
243
- prompt: {
244
- display_name: 'Prompt',
245
- type: 'string',
246
- is_handle: true
247
- },
248
- response: {
249
- display_name: 'Response',
250
- type: 'string',
251
- is_handle: true
252
- }
253
- },
254
- resources: {
255
- cpu: 0.5,
256
- memory: '512Mi',
257
- gpu: 'none'
258
- }
259
- }
260
- },
261
- ChatModel: {
262
- label: 'Chat Model',
263
- icon: '💭',
264
- color: '#673AB7',
265
- defaultData: {
266
- display_name: 'Chat Model',
267
- template: {
268
- provider: {
269
- display_name: 'Provider',
270
- type: 'options',
271
- options: ['OpenAI', 'Anthropic'],
272
- value: 'OpenAI'
273
- },
274
- model: {
275
- display_name: 'Model',
276
- type: 'string',
277
- value: 'gpt-4o-mini'
278
- },
279
- api_key: {
280
- display_name: 'API Key',
281
- type: 'SecretStr',
282
- required: true,
283
- env_var: 'OPENAI_API_KEY'
284
- },
285
- system_prompt: {
286
- display_name: 'System Prompt',
287
- type: 'string',
288
- value: 'You are a helpful assistant.'
289
- },
290
- prompt: {
291
- display_name: 'Prompt',
292
- type: 'string',
293
- is_handle: true
294
- },
295
- response: {
296
- display_name: 'Response',
297
- type: 'string',
298
- is_handle: true
299
- }
300
- },
301
- resources: {
302
- cpu: 0.5,
303
- memory: '512Mi',
304
- gpu: 'none'
305
- }
306
- }
307
- },
308
- Prompt: {
309
- label: 'Prompt',
310
- icon: '📝',
311
- color: '#3F51B5',
312
- defaultData: {
313
- display_name: 'Prompt',
314
- template: {
315
- prompt_template: {
316
- display_name: 'Template',
317
- type: 'string',
318
- value: '{{input}}',
319
- is_handle: true
320
- }
321
- },
322
- resources: {
323
- cpu: 0.1,
324
- memory: '128Mi',
325
- gpu: 'none'
326
- }
327
- }
328
- },
329
- HFTextGeneration: {
330
- label: 'HF Text Generation',
331
- icon: '🤗',
332
- color: '#E91E63',
333
- defaultData: {
334
- display_name: 'HF Text Generation',
335
- template: {
336
- model: {
337
- display_name: 'Model',
338
- type: 'string',
339
- value: 'gpt2'
340
- },
341
- temperature: {
342
- display_name: 'Temperature',
343
- type: 'number',
344
- value: 0.7,
345
- min: 0,
346
- max: 1
347
- },
348
- max_tokens: {
349
- display_name: 'Max Tokens',
350
- type: 'number',
351
- value: 2048,
352
- min: 1,
353
- max: 4096
354
- },
355
- api_key: {
356
- display_name: 'API Key',
357
- type: 'SecretStr',
358
- value: '',
359
- env_var: 'HF_API_KEY'
360
- },
361
- prompt: {
362
- display_name: 'Prompt',
363
- type: 'string',
364
- is_handle: true
365
- },
366
- response: {
367
- display_name: 'Response',
368
- type: 'string',
369
- is_handle: true
370
- }
371
- },
372
- resources: {
373
- cpu: 0.3,
374
- memory: '256Mi',
375
- gpu: 'none'
376
- }
377
- }
378
- },
379
- ReActAgent: {
380
- label: 'ReAct Agent',
381
- icon: '🤖',
382
- color: '#9C27B0',
383
- defaultData: {
384
- display_name: 'LlamaIndex ReAct Agent',
385
- template: {
386
- tools_input: {
387
- display_name: 'Available Tools',
388
- type: 'list',
389
- is_handle: true,
390
- info: 'Connect WebSearch, ExecutePython, APIRequest, and other tool nodes'
391
- },
392
- llm_model: {
393
- display_name: 'LLM Model',
394
- type: 'options',
395
- options: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo', 'gpt-4', 'gpt-3.5-turbo-16k'],
396
- value: 'gpt-4o-mini'
397
- },
398
- api_key: {
399
- display_name: 'OpenAI API Key',
400
- type: 'SecretStr',
401
- required: true,
402
- env_var: 'OPENAI_API_KEY'
403
- },
404
- system_prompt: {
405
- display_name: 'System Prompt',
406
- type: 'string',
407
- value: 'You are a helpful AI assistant with access to various tools. Use the available tools to answer user questions accurately and efficiently.',
408
- multiline: true
409
- },
410
- user_query: {
411
- display_name: 'User Query',
412
- type: 'string',
413
- is_handle: true
414
- },
415
- max_iterations: {
416
- display_name: 'Max Iterations',
417
- type: 'number',
418
- value: 8
419
- },
420
- temperature: {
421
- display_name: 'Temperature',
422
- type: 'number',
423
- value: 0.1,
424
- min: 0,
425
- max: 2,
426
- step: 0.1
427
- },
428
- verbose: {
429
- display_name: 'Verbose Output',
430
- type: 'boolean',
431
- value: true
432
- },
433
- agent_response: {
434
- display_name: 'Agent Response',
435
- type: 'string',
436
- is_handle: true
437
- }
438
- },
439
- resources: {
440
- cpu: 0.5,
441
- memory: '512Mi',
442
- gpu: 'none'
443
- }
444
- }
445
- }
446
- }
447
- },
448
- 'API & Web': {
449
- icon: '🌐',
450
- components: {
451
- APIRequest: {
452
- label: 'API Request',
453
- icon: '🔌',
454
- color: '#00BCD4',
455
- defaultData: {
456
- display_name: 'API Request',
457
- template: {
458
- url: {
459
- display_name: 'URL',
460
- type: 'string',
461
- value: ''
462
- },
463
- method: {
464
- display_name: 'Method',
465
- type: 'options',
466
- value: 'GET',
467
- options: ['GET', 'POST', 'PUT', 'DELETE']
468
- },
469
- headers: {
470
- display_name: 'Headers',
471
- type: 'dict',
472
- value: {}
473
- },
474
- body: {
475
- display_name: 'Body',
476
- type: 'string',
477
- value: ''
478
- },
479
- response: {
480
- display_name: 'Response',
481
- type: 'object',
482
- is_handle: true
483
- }
484
- },
485
- resources: {
486
- cpu: 0.2,
487
- memory: '256Mi',
488
- gpu: 'none'
489
- }
490
- }
491
- },
492
- WebSearch: {
493
- label: 'Web Search',
494
- icon: '🔍',
495
- color: '#009688',
496
- defaultData: {
497
- display_name: 'Web Search',
498
- template: {
499
- query: {
500
- display_name: 'Query',
501
- type: 'string',
502
- value: '',
503
- is_handle: true
504
- },
505
- num_results: {
506
- display_name: 'Number of Results',
507
- type: 'number',
508
- value: 5,
509
- min: 1,
510
- max: 10
511
- },
512
- api_key: {
513
- display_name: 'API Key',
514
- type: 'SecretStr',
515
- value: '',
516
- env_var: 'SERPAPI_KEY'
517
- },
518
- results: {
519
- display_name: 'Search Results',
520
- type: 'list',
521
- is_handle: true
522
- }
523
- },
524
- resources: {
525
- cpu: 0.2,
526
- memory: '256Mi',
527
- gpu: 'none'
528
- }
529
- }
530
- }
531
- }
532
- },
533
- 'Data Processing': {
534
- icon: '⚙️',
535
- components: {
536
- ExecutePython: {
537
- label: 'Execute Python',
538
- icon: '🐍',
539
- color: '#FF5722',
540
- defaultData: {
541
- display_name: 'Execute Python',
542
- template: {
543
- code: {
544
- display_name: 'Python Code',
545
- type: 'string',
546
- value: 'def process(input_data):\n return input_data'
547
- },
548
- timeout: {
549
- display_name: 'Timeout',
550
- type: 'number',
551
- value: 30,
552
- min: 1,
553
- max: 300
554
- },
555
- input_data: {
556
- display_name: 'Input Data',
557
- type: 'object',
558
- is_handle: true
559
- },
560
- output_data: {
561
- display_name: 'Output Data',
562
- type: 'object',
563
- is_handle: true
564
- }
565
- },
566
- resources: {
567
- cpu: 0.3,
568
- memory: '256Mi',
569
- gpu: 'none'
570
- }
571
- }
572
- },
573
- ConditionalLogic: {
574
- label: 'Conditional Logic',
575
- icon: '🔀',
576
- color: '#795548',
577
- defaultData: {
578
- display_name: 'Conditional Logic',
579
- template: {
580
- condition: {
581
- display_name: 'Condition',
582
- type: 'string',
583
- value: '{{input}} == True'
584
- },
585
- input: {
586
- display_name: 'Input',
587
- type: 'object',
588
- is_handle: true
589
- },
590
- true_output: {
591
- display_name: 'True Output',
592
- type: 'object',
593
- is_handle: true
594
- },
595
- false_output: {
596
- display_name: 'False Output',
597
- type: 'object',
598
- is_handle: true
599
- }
600
- },
601
- resources: {
602
- cpu: 0.1,
603
- memory: '128Mi',
604
- gpu: 'none'
605
- }
606
- }
607
- },
608
- Wait: {
609
- label: 'Wait',
610
- icon: '⏳',
611
- color: '#607D8B',
612
- defaultData: {
613
- display_name: 'Wait',
614
- template: {
615
- seconds: {
616
- display_name: 'Seconds',
617
- type: 'number',
618
- value: 1,
619
- min: 1,
620
- max: 3600
621
- },
622
- input: {
623
- display_name: 'Input',
624
- type: 'object',
625
- is_handle: true
626
- },
627
- output: {
628
- display_name: 'Output',
629
- type: 'object',
630
- is_handle: true
631
- }
632
- },
633
- resources: {
634
- cpu: 0.1,
635
- memory: '128Mi',
636
- gpu: 'none'
637
- }
638
- }
639
- }
640
- }
641
- },
642
- 'RAG & Knowledge': {
643
- icon: '📚',
644
- components: {
645
- KnowledgeBase: {
646
- label: 'Knowledge Base',
647
- icon: '📖',
648
- color: '#8BC34A',
649
- defaultData: {
650
- display_name: 'Knowledge Base',
651
- template: {
652
- kb_name: {
653
- display_name: 'Knowledge Base Name',
654
- type: 'string',
655
- value: ''
656
- },
657
- source_type: {
658
- display_name: 'Source Type',
659
- type: 'options',
660
- options: ['Directory', 'URL'],
661
- value: 'Directory'
662
- },
663
- path_or_url: {
664
- display_name: 'Path or URL',
665
- type: 'string',
666
- value: ''
667
- },
668
- knowledge_base: {
669
- display_name: 'Knowledge Base',
670
- type: 'object',
671
- is_handle: true
672
- }
673
- },
674
- resources: {
675
- cpu: 0.2,
676
- memory: '512Mi',
677
- gpu: 'none'
678
- }
679
- }
680
- },
681
- RAGQuery: {
682
- label: 'RAG Query',
683
- icon: '🔎',
684
- color: '#FFC107',
685
- defaultData: {
686
- display_name: 'RAG Query',
687
- template: {
688
- query: {
689
- display_name: 'Query',
690
- type: 'string',
691
- is_handle: true
692
- },
693
- knowledge_base: {
694
- display_name: 'Knowledge Base',
695
- type: 'object',
696
- is_handle: true
697
- },
698
- num_results: {
699
- display_name: 'Number of Results',
700
- type: 'number',
701
- value: 3,
702
- min: 1,
703
- max: 10
704
- },
705
- rag_prompt: {
706
- display_name: 'RAG Prompt',
707
- type: 'string',
708
- is_handle: true
709
- }
710
- },
711
- resources: {
712
- cpu: 0.3,
713
- memory: '512Mi',
714
- gpu: 'none'
715
- }
716
- }
717
- }
718
- }
719
- },
720
- 'Speech & Vision': {
721
- icon: '👁️',
722
- components: {
723
- HFSpeechToText: {
724
- label: 'HF Speech to Text',
725
- icon: '🎤',
726
- color: '#9E9E9E',
727
- defaultData: {
728
- display_name: 'HF Speech to Text',
729
- template: {
730
- model: {
731
- display_name: 'Model',
732
- type: 'string',
733
- value: 'facebook/wav2vec2-base-960h'
734
- },
735
- api_key: {
736
- display_name: 'API Key',
737
- type: 'SecretStr',
738
- value: '',
739
- env_var: 'HF_API_KEY'
740
- },
741
- audio_input: {
742
- display_name: 'Audio Input',
743
- type: 'file',
744
- is_handle: true
745
- },
746
- text_output: {
747
- display_name: 'Text Output',
748
- type: 'string',
749
- is_handle: true
750
- }
751
- },
752
- resources: {
753
- cpu: 0.4,
754
- memory: '512Mi',
755
- gpu: 'optional'
756
- }
757
- }
758
- },
759
- HFTextToSpeech: {
760
- label: 'HF Text to Speech',
761
- icon: '🔊',
762
- color: '#CDDC39',
763
- defaultData: {
764
- display_name: 'HF Text to Speech',
765
- template: {
766
- model: {
767
- display_name: 'Model',
768
- type: 'string',
769
- value: 'facebook/fastspeech2-en-ljspeech'
770
- },
771
- api_key: {
772
- display_name: 'API Key',
773
- type: 'SecretStr',
774
- value: '',
775
- env_var: 'HF_API_KEY'
776
- },
777
- text_input: {
778
- display_name: 'Text Input',
779
- type: 'string',
780
- is_handle: true
781
- },
782
- audio_output: {
783
- display_name: 'Audio Output',
784
- type: 'file',
785
- is_handle: true
786
- }
787
- },
788
- resources: {
789
- cpu: 0.4,
790
- memory: '512Mi',
791
- gpu: 'optional'
792
- }
793
- }
794
- },
795
- HFSVisionModel: {
796
- label: 'HF Vision Model',
797
- icon: '👁️',
798
- color: '#FF9800',
799
- defaultData: {
800
- display_name: 'HF Vision Model',
801
- template: {
802
- model: {
803
- display_name: 'Model',
804
- type: 'string',
805
- value: 'google/vit-base-patch16-224'
806
- },
807
- api_key: {
808
- display_name: 'API Key',
809
- type: 'SecretStr',
810
- value: '',
811
- env_var: 'HF_API_KEY'
812
- },
813
- image_input: {
814
- display_name: 'Image Input',
815
- type: 'file',
816
- is_handle: true
817
- },
818
- prediction: {
819
- display_name: 'Prediction',
820
- type: 'object',
821
- is_handle: true
822
- }
823
- },
824
- resources: {
825
- cpu: 0.4,
826
- memory: '512Mi',
827
- gpu: 'required'
828
- }
829
- }
830
- }
831
- }
832
- },
833
- 'Image Generation': {
834
- icon: '🎨',
835
- components: {
836
- HFImageGeneration: {
837
- label: 'HF Image Generation',
838
- icon: '🎨',
839
- color: '#E91E63',
840
- defaultData: {
841
- display_name: 'HF Image Generation',
842
- template: {
843
- model: {
844
- display_name: 'Model',
845
- type: 'string',
846
- value: 'stabilityai/stable-diffusion-2'
847
- },
848
- prompt: {
849
- display_name: 'Prompt',
850
- type: 'string',
851
- value: '',
852
- is_handle: true
853
- },
854
- num_images: {
855
- display_name: 'Number of Images',
856
- type: 'number',
857
- value: 1,
858
- min: 1,
859
- max: 4
860
- },
861
- api_key: {
862
- display_name: 'API Key',
863
- type: 'SecretStr',
864
- value: '',
865
- env_var: 'HF_API_KEY'
866
- },
867
- images: {
868
- display_name: 'Generated Images',
869
- type: 'list',
870
- is_handle: true
871
- }
872
- },
873
- resources: {
874
- cpu: 0.5,
875
- memory: '1Gi',
876
- gpu: 'required'
877
- }
878
- }
879
- },
880
- NebiusImage: {
881
- label: 'Nebius Image',
882
- icon: '🖼️',
883
- color: '#2196F3',
884
- defaultData: {
885
- display_name: 'Nebius Image',
886
- template: {
887
- model: {
888
- display_name: 'Model',
889
- type: 'options',
890
- options: ['black-forest-labs/flux-dev', 'black-forest-labs/flux-schnell', 'stability-ai/sdxl'],
891
- value: 'black-forest-labs/flux-dev'
892
- },
893
- prompt: {
894
- display_name: 'Prompt',
895
- type: 'string',
896
- value: '',
897
- is_handle: true
898
- },
899
- negative_prompt: {
900
- display_name: 'Negative Prompt',
901
- type: 'string',
902
- value: ''
903
- },
904
- width: {
905
- display_name: 'Width',
906
- type: 'number',
907
- value: 1024
908
- },
909
- height: {
910
- display_name: 'Height',
911
- type: 'number',
912
- value: 1024
913
- },
914
- num_inference_steps: {
915
- display_name: 'Inference Steps',
916
- type: 'number',
917
- value: 28
918
- },
919
- seed: {
920
- display_name: 'Seed',
921
- type: 'number',
922
- value: -1
923
- },
924
- api_key: {
925
- display_name: 'API Key',
926
- type: 'SecretStr',
927
- value: '',
928
- env_var: 'NEBIUS_API_KEY'
929
- },
930
- image: {
931
- display_name: 'Generated Image',
932
- type: 'file',
933
- is_handle: true
934
- }
935
- },
936
- resources: {
937
- cpu: 0.5,
938
- memory: '1Gi',
939
- gpu: 'required'
940
- }
941
- }
942
- }
943
- }
944
- },
945
- 'MCP Integration': {
946
- icon: '🤝',
947
- components: {
948
- MCPConnection: {
949
- label: 'MCP Connection',
950
- icon: '🔌',
951
- color: '#673AB7',
952
- defaultData: {
953
- display_name: 'MCP Connection',
954
- template: {
955
- server_url: {
956
- display_name: 'Server URL',
957
- type: 'string',
958
- value: ''
959
- },
960
- connection_type: {
961
- display_name: 'Connection Type',
962
- type: 'options',
963
- options: ['http', 'stdio'],
964
- value: 'http'
965
- },
966
- allowed_tools: {
967
- display_name: 'Allowed Tools',
968
- type: 'string',
969
- value: ''
970
- },
971
- api_key: {
972
- display_name: 'API Key',
973
- type: 'SecretStr',
974
- value: '',
975
- env_var: 'MCP_API_KEY'
976
- },
977
- connection: {
978
- display_name: 'MCP Connection',
979
- type: 'object',
980
- is_handle: true
981
- }
982
- },
983
- resources: {
984
- cpu: 0.2,
985
- memory: '256Mi',
986
- gpu: 'none'
987
- }
988
- }
989
- },
990
- MCPAgent: {
991
- label: 'MCP Agent',
992
- icon: '🤖',
993
- color: '#3F51B5',
994
- defaultData: {
995
- display_name: 'MCP Agent',
996
- template: {
997
- llm_model: {
998
- display_name: 'LLM Model',
999
- type: 'options',
1000
- options: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo', 'gpt-4', 'gpt-3.5-turbo-16k'],
1001
- value: 'gpt-4o'
1002
- },
1003
- api_key: {
1004
- display_name: 'OpenAI API Key',
1005
- type: 'SecretStr',
1006
- required: true,
1007
- env_var: 'OPENAI_API_KEY'
1008
- },
1009
- system_prompt: {
1010
- display_name: 'System Prompt',
1011
- type: 'string',
1012
- value: 'You are a helpful AI assistant.',
1013
- multiline: true
1014
- },
1015
- max_iterations: {
1016
- display_name: 'Max Iterations',
1017
- type: 'number',
1018
- value: 10,
1019
- min: 1,
1020
- max: 20
1021
- },
1022
- temperature: {
1023
- display_name: 'Temperature',
1024
- type: 'number',
1025
- value: 0.1,
1026
- min: 0,
1027
- max: 2,
1028
- step: 0.1
1029
- },
1030
- verbose: {
1031
- display_name: 'Verbose Output',
1032
- type: 'boolean',
1033
- value: false
1034
- },
1035
- user_query: {
1036
- display_name: 'User Query',
1037
- type: 'string',
1038
- is_handle: true
1039
- },
1040
- mcp_connection: {
1041
- display_name: 'MCP Connection',
1042
- type: 'object',
1043
- is_handle: true
1044
- },
1045
- agent_response: {
1046
- display_name: 'Agent Response',
1047
- type: 'string',
1048
- is_handle: true
1049
- }
1050
- },
1051
- resources: {
1052
- cpu: 0.5,
1053
- memory: '512Mi',
1054
- gpu: 'none'
1055
- }
1056
- }
1057
- }
1058
- }
1059
- }
1060
- };
1061
-
1062
- // Property fields for each node type
1063
- const propertyFields = {
1064
-
1065
- llmNode: [ // ③ AI Processing 속성 폼
1066
- { key: 'display_name', label: 'Display Name', type: 'text' },
1067
- { key: 'template.provider.value', label: 'Provider', type: 'select',
1068
- options: ['VIDraft', 'OpenAI'] },
1069
- { key: 'template.model.value', label: 'Model', type: 'text' },
1070
- { key: 'template.temperature.value', label: 'Temperature', type: 'number',
1071
- min: 0, max: 2, step: 0.1 },
1072
- { key: 'template.system_prompt.value', label: 'System Prompt',
1073
- type: 'textarea' }
1074
- ],
1075
-
1076
- textNode: [ // ④ Markdown 노드 속성 폼
1077
- { key: 'display_name', label: 'Display Name', type: 'text' },
1078
- { key: 'template.text.value', label: 'Markdown Text', type: 'textarea' }
1079
- ],
1080
-
1081
-
1082
-
1083
-
1084
- // Input/Output nodes
1085
- ChatInput: [
1086
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1087
- { key: 'template.input_value.display_name', label: 'Input Field Label', type: 'text', help: 'Label shown in the chat input field' },
1088
- { key: 'template.input_value.value', label: 'Default Message', type: 'textarea', help: 'Default message shown in the input field' }
1089
- ],
1090
- ChatOutput: [
1091
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1092
- { key: 'template.response.display_name', label: 'Response Field Label', type: 'text', help: 'Label shown in the chat output field' }
1093
- ],
1094
- Input: [
1095
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1096
- { key: 'template.data_type.value', label: 'Data Type', type: 'select', options: ['string', 'image', 'video', 'audio', 'file'], help: 'Type of data this node will handle' },
1097
- { key: 'template.value.value', label: 'Default Value', type: 'textarea', help: 'Default value or path' }
1098
- ],
1099
- Output: [
1100
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' }
1101
- ],
1102
-
1103
- // AI & Language nodes
1104
- OpenAIModel: [
1105
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1106
- { key: 'template.model.value', label: 'Model', type: 'select', options: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo'] },
1107
- { key: 'template.temperature.value', label: 'Temperature', type: 'number', min: 0, max: 1, step: 0.1 },
1108
- { key: 'template.max_tokens.value', label: 'Max Tokens', type: 'number', min: 1, max: 4096 }
1109
- ],
1110
- ChatModel: [
1111
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1112
- { key: 'template.provider.value', label: 'Provider', type: 'select', options: ['OpenAI', 'Anthropic'], help: 'AI model provider' },
1113
- { key: 'template.model.value', label: 'Model', type: 'text', help: 'Model name' },
1114
- { key: 'template.system_prompt.value', label: 'System Prompt', type: 'textarea', help: 'Optional system prompt' }
1115
- ],
1116
- Prompt: [
1117
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1118
- { key: 'template.prompt_template.value', label: 'Prompt Template', type: 'textarea', help: 'Prompt template' }
1119
- ],
1120
- HFTextGeneration: [
1121
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1122
- { key: 'template.model.value', label: 'Model', type: 'text', help: 'Model name' },
1123
- { key: 'template.temperature.value', label: 'Temperature', type: 'number', min: 0, max: 1, step: 0.1, help: 'Model temperature' },
1124
- { key: 'template.max_tokens.value', label: 'Max Tokens', type: 'number', min: 1, max: 4096, help: 'Maximum tokens' }
1125
- ],
1126
- ReActAgent: [
1127
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1128
- { key: 'template.llm_model.value', label: 'LLM Model', type: 'select', options: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo', 'gpt-4', 'gpt-3.5-turbo-16k'], help: 'Model to use for the agent' },
1129
- { key: 'template.system_prompt.value', label: 'System Prompt', type: 'textarea', help: 'System prompt for the agent', multiline: true },
1130
- { key: 'template.max_iterations.value', label: 'Max Iterations', type: 'number', min: 1, max: 20, help: 'Maximum number of agent iterations' },
1131
- { key: 'template.temperature.value', label: 'Temperature', type: 'number', min: 0, max: 2, step: 0.1, help: 'Model temperature (0-2)' },
1132
- { key: 'template.verbose.value', label: 'Verbose Output', type: 'checkbox', help: 'Show detailed agent reasoning' }
1133
- ],
1134
-
1135
- // API & Web nodes
1136
- APIRequest: [
1137
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1138
- { key: 'template.url.value', label: 'URL', type: 'text', help: 'API endpoint URL' },
1139
- { key: 'template.method.value', label: 'Method', type: 'select', options: ['GET', 'POST', 'PUT', 'DELETE'], help: 'HTTP method' }
1140
- ],
1141
- WebSearch: [
1142
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1143
- { key: 'template.num_results.value', label: 'Number of Results', type: 'number', help: 'Number of search results' }
1144
- ],
1145
-
1146
- // Data Processing nodes
1147
- ExecutePython: [
1148
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1149
- { key: 'template.code.value', label: 'Python Code', type: 'textarea', help: 'Python code to execute' },
1150
- { key: 'template.timeout.value', label: 'Timeout', type: 'number', help: 'Execution timeout' }
1151
- ],
1152
- ConditionalLogic: [
1153
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1154
- { key: 'template.condition.value', label: 'Condition', type: 'text', help: 'Condition expression' }
1155
- ],
1156
- Wait: [
1157
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1158
- { key: 'template.seconds.value', label: 'Seconds', type: 'number', help: 'Wait time in seconds' }
1159
- ],
1160
-
1161
- // RAG nodes
1162
- KnowledgeBase: [
1163
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1164
- { key: 'template.kb_name.value', label: 'Knowledge Base Name', type: 'text', help: 'Name for the knowledge base' },
1165
- { key: 'template.source_type.value', label: 'Source Type', type: 'select', options: ['Directory', 'URL'], help: 'Type of source' },
1166
- { key: 'template.path_or_url.value', label: 'Path or URL', type: 'text', help: 'Source location' }
1167
- ],
1168
- RAGQuery: [
1169
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1170
- { key: 'template.num_results.value', label: 'Number of Results', type: 'number', help: 'Number of results to retrieve' }
1171
- ],
1172
-
1173
- // Speech & Vision nodes
1174
- HFSpeechToText: [
1175
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1176
- { key: 'template.model.value', label: 'Model', type: 'text', help: 'HuggingFace model ID' }
1177
- ],
1178
- HFTextToSpeech: [
1179
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1180
- { key: 'template.model.value', label: 'Model', type: 'text', help: 'HuggingFace model ID' }
1181
- ],
1182
- HFSVisionModel: [
1183
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1184
- { key: 'template.model.value', label: 'Model', type: 'text', help: 'HuggingFace model ID' }
1185
- ],
1186
-
1187
- // Image Generation nodes
1188
- HFImageGeneration: [
1189
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1190
- { key: 'template.model.value', label: 'Model', type: 'text', help: 'HuggingFace model ID' },
1191
- { key: 'template.num_images.value', label: 'Number of Images', type: 'number', help: 'Number of images to generate' }
1192
- ],
1193
- NebiusImage: [
1194
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1195
- { key: 'template.model.value', label: 'Model', type: 'select', options: ['black-forest-labs/flux-dev', 'black-forest-labs/flux-schnell', 'stability-ai/sdxl'], help: 'Nebius model to use' },
1196
- { key: 'template.width.value', label: 'Width', type: 'number', help: 'Image width' },
1197
- { key: 'template.height.value', label: 'Height', type: 'number', help: 'Image height' },
1198
- { key: 'template.num_inference_steps.value', label: 'Inference Steps', type: 'number', help: 'Number of inference steps' },
1199
- { key: 'template.seed.value', label: 'Seed', type: 'number', help: 'Random seed (-1 for random)' }
1200
- ],
1201
-
1202
- // MCP nodes
1203
- MCPConnection: [
1204
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1205
- { key: 'template.server_url.value', label: 'Server URL', type: 'text', help: 'MCP server URL' },
1206
- { key: 'template.connection_type.value', label: 'Connection Type', type: 'select', options: ['http', 'stdio'], help: 'Connection type' },
1207
- { key: 'template.allowed_tools.value', label: 'Allowed Tools', type: 'text', help: 'Optional list of allowed tools' }
1208
- ],
1209
- MCPAgent: [
1210
- { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1211
- { key: 'template.llm_model.value', label: 'LLM Model', type: 'select', options: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo', 'gpt-4', 'gpt-3.5-turbo-16k'], help: 'Model to use for the agent' },
1212
- { key: 'template.system_prompt.value', label: 'System Prompt', type: 'textarea', help: 'System prompt for the agent', multiline: true },
1213
- { key: 'template.max_iterations.value', label: 'Max Iterations', type: 'number', min: 1, max: 20, help: 'Maximum number of agent iterations' },
1214
- { key: 'template.temperature.value', label: 'Temperature', type: 'number', min: 0, max: 2, step: 0.1, help: 'Model temperature (0-2)' },
1215
- { key: 'template.verbose.value', label: 'Verbose Output', type: 'checkbox', help: 'Show detailed agent reasoning' }
1216
- ]
1217
- };
1218
-
1219
- // Update parent component when data changes
1220
- $: {
1221
- const newValue = { nodes, edges };
1222
- if (JSON.stringify(newValue) !== JSON.stringify(value)) {
1223
- value = newValue;
1224
- dispatch('change', newValue);
1225
- }
1226
- }
1227
-
1228
- // Export workflow to JSON
1229
-
1230
- // Clear workflow function
1231
- function clearWorkflow() {
1232
- nodes = [];
1233
- edges = [];
1234
- selectedNode = null;
1235
- workflowName = "My Workflow";
1236
- workflowId = "workflow-" + Date.now();
1237
- }
1238
-
1239
-
1240
- function exportWorkflow() {
1241
- const exportData = {
1242
- workflow_id: workflowId,
1243
- workflow_name: workflowName,
1244
- nodes: nodes.map(node => ({
1245
- id: node.id,
1246
- type: node.type,
1247
- data: {
1248
- display_name: node.data.display_name,
1249
- template: node.data.template,
1250
- resources: node.data.resources || {
1251
- cpu: 0.1,
1252
- memory: "128Mi",
1253
- gpu: "none"
1254
- }
1255
- }
1256
- })),
1257
- edges: edges.map(edge => ({
1258
- source: edge.source,
1259
- source_handle: edge.source_handle || 'output',
1260
- target: edge.target,
1261
- target_handle: edge.target_handle || 'input'
1262
- }))
1263
- };
1264
-
1265
- const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
1266
- const url = URL.createObjectURL(blob);
1267
- const a = document.createElement('a');
1268
- a.href = url;
1269
- a.download = `${workflowName.replace(/\s+/g, '-').toLowerCase()}.json`;
1270
- document.body.appendChild(a);
1271
- a.click();
1272
- document.body.removeChild(a);
1273
- URL.revokeObjectURL(url);
1274
- }
1275
-
1276
- // Zoom functions
1277
- function zoomIn() {
1278
- zoomLevel = Math.min(zoomLevel * 1.2, 3);
1279
- }
1280
-
1281
- function zoomOut() {
1282
- zoomLevel = Math.max(zoomLevel / 1.2, 0.3);
1283
- }
1284
-
1285
- function resetZoom() {
1286
- zoomLevel = 1;
1287
- panOffset = { x: 0, y: 0 };
1288
- }
1289
-
1290
- function handleWheel(event: WheelEvent) {
1291
- event.preventDefault();
1292
- if (event.ctrlKey || event.metaKey) {
1293
- const delta = event.deltaY > 0 ? 0.9 : 1.1;
1294
- zoomLevel = Math.max(0.3, Math.min(3, zoomLevel * delta));
1295
- } else {
1296
- panOffset.x -= event.deltaX * 0.5;
1297
- panOffset.y -= event.deltaY * 0.5;
1298
- panOffset = { ...panOffset };
1299
- }
1300
- }
1301
-
1302
- // Pan functions
1303
- function startPanning(event: MouseEvent) {
1304
- if (event.button === 1 || (event.button === 0 && event.altKey)) {
1305
- isPanning = true;
1306
- lastPanPoint = { x: event.clientX, y: event.clientY };
1307
- event.preventDefault();
1308
- }
1309
- }
1310
-
1311
- function handlePanning(event: MouseEvent) {
1312
- if (isPanning) {
1313
- const deltaX = event.clientX - lastPanPoint.x;
1314
- const deltaY = event.clientY - lastPanPoint.y;
1315
- panOffset.x += deltaX;
1316
- panOffset.y += deltaY;
1317
- panOffset = { ...panOffset };
1318
- lastPanPoint = { x: event.clientX, y: event.clientY };
1319
- }
1320
- }
1321
-
1322
- function stopPanning() {
1323
- isPanning = false;
1324
- }
1325
-
1326
- // Drag and drop from sidebar
1327
- function handleSidebarDragStart(event: DragEvent, componentType: string, componentData: any) {
1328
- if (event.dataTransfer) {
1329
- event.dataTransfer.setData('application/json', JSON.stringify({
1330
- type: componentType,
1331
- data: componentData
1332
- }));
1333
- isDraggingFromSidebar = true;
1334
- }
1335
- }
1336
-
1337
- function handleCanvasDropFromSidebar(event: DragEvent) {
1338
- event.preventDefault();
1339
- if (!isDraggingFromSidebar) return;
1340
-
1341
- const rect = canvas.getBoundingClientRect();
1342
- const x = (event.clientX - rect.left - panOffset.x) / zoomLevel;
1343
- const y = (event.clientY - rect.top - panOffset.y) / zoomLevel;
1344
-
1345
- try {
1346
- const dropData = JSON.parse(event.dataTransfer?.getData('application/json') || '{}');
1347
- if (dropData.type && dropData.data) {
1348
- const newNode = {
1349
- id: `${dropData.type}-${Date.now()}`,
1350
- type: dropData.type,
1351
- position: { x: Math.max(20, x - 160), y: Math.max(20, y - 80) },
1352
- data: { ...dropData.data.defaultData, label: dropData.data.label }
1353
- };
1354
- nodes = [...nodes, newNode];
1355
- }
1356
- } catch (error) {
1357
- console.error('Failed to parse drop data:', error);
1358
- }
1359
-
1360
- isDraggingFromSidebar = false;
1361
- }
1362
-
1363
- function handleCanvasDragOver(event: DragEvent) {
1364
- event.preventDefault();
1365
- }
1366
-
1367
- // Node interaction handlers with proper event handling
1368
- function handleMouseDown(event: MouseEvent, node: any) {
1369
- // Only start dragging if clicking on the node header or empty areas
1370
- if (event.target.closest('.node-property') ||
1371
- event.target.closest('.property-input') ||
1372
- event.target.closest('.property-select') ||
1373
- event.target.closest('.property-checkbox')) {
1374
- return; // Don't start dragging if clicking on form controls
1375
- }
1376
-
1377
- if (event.button !== 0) return;
1378
-
1379
- isDragging = true;
1380
- dragNode = node;
1381
- const rect = canvas.getBoundingClientRect();
1382
- const nodeScreenX = node.position.x * zoomLevel + panOffset.x;
1383
- const nodeScreenY = node.position.y * zoomLevel + panOffset.y;
1384
- dragOffset.x = event.clientX - rect.left - nodeScreenX;
1385
- dragOffset.y = event.clientY - rect.top - nodeScreenY;
1386
-
1387
- event.preventDefault();
1388
- event.stopPropagation();
1389
- }
1390
-
1391
- function handleNodeClick(event: MouseEvent, node: any) {
1392
- event.stopPropagation();
1393
- selectedNode = { ...node };
1394
- }
1395
-
1396
- function handleMouseMove(event: MouseEvent) {
1397
- const rect = canvas.getBoundingClientRect();
1398
- mousePos.x = (event.clientX - rect.left - panOffset.x) / zoomLevel;
1399
- mousePos.y = (event.clientY - rect.top - panOffset.y) / zoomLevel;
1400
-
1401
- if (isDragging && dragNode) {
1402
- const nodeIndex = nodes.findIndex(n => n.id === dragNode.id);
1403
- if (nodeIndex >= 0) {
1404
- const newX = Math.max(0, (event.clientX - rect.left - dragOffset.x - panOffset.x) / zoomLevel);
1405
- const newY = Math.max(0, (event.clientY - rect.top - dragOffset.y - panOffset.y) / zoomLevel);
1406
- nodes[nodeIndex].position.x = newX;
1407
- nodes[nodeIndex].position.y = newY;
1408
- nodes = [...nodes];
1409
-
1410
- if (selectedNode?.id === dragNode.id) {
1411
- selectedNode = { ...nodes[nodeIndex] };
1412
- }
1413
- }
1414
- }
1415
-
1416
- handlePanning(event);
1417
- }
1418
-
1419
- function handleMouseUp() {
1420
- isDragging = false;
1421
- dragNode = null;
1422
- isConnecting = false;
1423
- connectionStart = null;
1424
- stopPanning();
1425
- }
1426
-
1427
- // Connection handling
1428
- function startConnection(event: MouseEvent, nodeId: string) {
1429
- event.stopPropagation();
1430
- isConnecting = true;
1431
- connectionStart = nodeId;
1432
- }
1433
-
1434
- function endConnection(event: MouseEvent, nodeId: string) {
1435
- event.stopPropagation();
1436
- if (isConnecting && connectionStart && connectionStart !== nodeId) {
1437
- const existingEdge = edges.find(e =>
1438
- (e.source === connectionStart && e.target === nodeId) ||
1439
- (e.source === nodeId && e.target === connectionStart)
1440
- );
1441
-
1442
- if (!existingEdge) {
1443
- const newEdge = {
1444
- id: `e-${connectionStart}-${nodeId}-${Date.now()}`,
1445
- source: connectionStart,
1446
- target: nodeId
1447
- };
1448
- edges = [...edges, newEdge];
1449
- }
1450
- }
1451
- isConnecting = false;
1452
- connectionStart = null;
1453
- }
1454
-
1455
- // Node and edge management
1456
- function deleteNode(nodeId: string) {
1457
- nodes = nodes.filter(n => n.id !== nodeId);
1458
- edges = edges.filter(e => e.source !== nodeId && e.target !== nodeId);
1459
- if (selectedNode?.id === nodeId) {
1460
- selectedNode = null;
1461
- }
1462
- }
1463
-
1464
- function deleteEdge(edgeId: string) {
1465
- edges = edges.filter(e => e.id !== edgeId);
1466
- }
1467
-
1468
- // Property updates with proper reactivity
1469
- function updateNodeProperty(nodeId: string, key: string, value: any) {
1470
- const nodeIndex = nodes.findIndex(n => n.id === nodeId);
1471
- if (nodeIndex >= 0) {
1472
- // Handle nested property paths
1473
- const keyParts = key.split('.');
1474
- let target = nodes[nodeIndex].data;
1475
-
1476
- for (let i = 0; i < keyParts.length - 1; i++) {
1477
- if (!target[keyParts[i]]) {
1478
- target[keyParts[i]] = {};
1479
- }
1480
- target = target[keyParts[i]];
1481
- }
1482
-
1483
- target[keyParts[keyParts.length - 1]] = value;
1484
- nodes = [...nodes]; // Trigger reactivity
1485
-
1486
- if (selectedNode?.id === nodeId) {
1487
- selectedNode = { ...nodes[nodeIndex] };
1488
- }
1489
- }
1490
- }
1491
-
1492
- function getNodeProperty(node: any, key: string) {
1493
- const keyParts = key.split('.');
1494
- let value = node.data;
1495
-
1496
- for (const part of keyParts) {
1497
- value = value?.[part];
1498
- }
1499
-
1500
- return value;
1501
- }
1502
-
1503
- // Panel toggle functions
1504
- function toggleSidebar() {
1505
- sidebarCollapsed = !sidebarCollapsed;
1506
- }
1507
-
1508
- function togglePropertyPanel() {
1509
- propertyPanelCollapsed = !propertyPanelCollapsed;
1510
- }
1511
-
1512
- // Helper functions
1513
- function getComponentConfig(type: string) {
1514
- for (const category of Object.values(componentCategories)) {
1515
- if (category.components[type]) {
1516
- return category.components[type];
1517
- }
1518
- }
1519
- return { label: type, icon: '⚡', color: '#6b7280' };
1520
- }
1521
-
1522
- function getConnectionPoints(sourceNode: any, targetNode: any) {
1523
- const sourceX = sourceNode.position.x + 320;
1524
- const sourceY = sourceNode.position.y + 80;
1525
- const targetX = targetNode.position.x;
1526
- const targetY = targetNode.position.y + 80;
1527
-
1528
- return { sourceX, sourceY, targetX, targetY };
1529
- }
1530
-
1531
- // Canvas setup
1532
- onMount(() => {
1533
- document.addEventListener('mousemove', handleMouseMove);
1534
- document.addEventListener('mouseup', handleMouseUp);
1535
-
1536
- return () => {
1537
- document.removeEventListener('mousemove', handleMouseMove);
1538
- document.removeEventListener('mouseup', handleMouseUp);
1539
- };
1540
- });
1541
- </script>
1542
-
1543
- <div
1544
- class="workflow-builder {elem_classes.join(' ')}"
1545
- class:hide={!visible}
1546
- style:min-width={min_width && min_width + "px"}
1547
- id={elem_id}
1548
- >
1549
- <!-- Top Section: Main Workflow Area -->
1550
- <div class="top-section">
1551
- <!-- Left Sidebar -->
1552
- <div class="sidebar" class:collapsed={sidebarCollapsed}>
1553
- <div class="sidebar-header">
1554
- {#if !sidebarCollapsed}
1555
- <h3>Components</h3>
1556
- {/if}
1557
- <button
1558
- class="toggle-btn sidebar-toggle"
1559
- on:click={toggleSidebar}
1560
- title={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
1561
- >
1562
- {sidebarCollapsed ? '→' : '←'}
1563
- </button>
1564
- </div>
1565
-
1566
- {#if !sidebarCollapsed}
1567
- <div class="sidebar-content">
1568
- {#each Object.entries(componentCategories) as [categoryName, category]}
1569
- <div class="category">
1570
- <div class="category-header">
1571
- <span class="category-icon">{category.icon}</span>
1572
- <span class="category-name">{categoryName}</span>
1573
- </div>
1574
-
1575
- <div class="category-components">
1576
- {#each Object.entries(category.components) as [componentType, component]}
1577
- <div
1578
- class="component-item"
1579
- draggable="true"
1580
- on:dragstart={(e) => handleSidebarDragStart(e, componentType, component)}
1581
- >
1582
- <span class="component-icon">{component.icon}</span>
1583
- <span class="component-label">{component.label}</span>
1584
- </div>
1585
- {/each}
1586
- </div>
1587
- </div>
1588
- {/each}
1589
- </div>
1590
- {/if}
1591
- </div>
1592
-
1593
- <!-- Main Canvas Area -->
1594
- <div class="canvas-area">
1595
- <!-- Toolbar -->
1596
- <div class="toolbar">
1597
- <div class="toolbar-left">
1598
- <input
1599
- class="workflow-name-input"
1600
- type="text"
1601
- bind:value={workflowName}
1602
- placeholder="Workflow Name"
1603
- title="Enter workflow name"
1604
- />
1605
- </div>
1606
- <div class="toolbar-center">
1607
- <!-- Zoom Controls -->
1608
- <div class="zoom-controls">
1609
- <button class="zoom-btn" on:click={zoomOut} title="Zoom Out">-</button>
1610
- <span class="zoom-level">{Math.round(zoomLevel * 100)}%</span>
1611
- <button class="zoom-btn" on:click={zoomIn} title="Zoom In">+</button>
1612
- <button class="zoom-btn reset" on:click={resetZoom} title="Reset View">⌂</button>
1613
- </div>
1614
- </div>
1615
- <div class="toolbar-right">
1616
- <span class="node-count">Nodes: {nodes.length}</span>
1617
- <span class="edge-count">Edges: {edges.length}</span>
1618
- <button class="clear-btn" on:click={clearWorkflow} title="Clear Workflow">
1619
- 🗑️ Clear
1620
- </button>
1621
- </div>
1622
- </div>
1623
-
1624
- <!-- Canvas Container -->
1625
- <div class="canvas-container" bind:this={canvasContainer}>
1626
- <div
1627
- class="canvas"
1628
- bind:this={canvas}
1629
- style="transform: scale({zoomLevel}) translate({panOffset.x / zoomLevel}px, {panOffset.y / zoomLevel}px);"
1630
- on:drop={handleCanvasDropFromSidebar}
1631
- on:dragover={handleCanvasDragOver}
1632
- on:wheel={handleWheel}
1633
- on:mousedown={startPanning}
1634
- on:click={() => { selectedNode = null; }}
1635
- >
1636
- <!-- Grid Background -->
1637
- <div class="grid-background"></div>
1638
-
1639
- <!-- Edges (SVG) -->
1640
- <svg class="edges-layer">
1641
- {#each edges as edge (edge.id)}
1642
- {@const sourceNode = nodes.find(n => n.id === edge.source)}
1643
- {@const targetNode = nodes.find(n => n.id === edge.target)}
1644
- {#if sourceNode && targetNode}
1645
- {@const points = getConnectionPoints(sourceNode, targetNode)}
1646
- <g class="edge-group">
1647
- <path
1648
- d="M {points.sourceX} {points.sourceY} C {points.sourceX + 80} {points.sourceY} {points.targetX - 80} {points.targetY} {points.targetX} {points.targetY}"
1649
- stroke="#64748b"
1650
- stroke-width="2"
1651
- fill="none"
1652
- class="edge-path"
1653
- />
1654
- <circle
1655
- cx={points.targetX}
1656
- cy={points.targetY}
1657
- r="4"
1658
- fill="#64748b"
1659
- />
1660
- <circle
1661
- cx={(points.sourceX + points.targetX) / 2}
1662
- cy={(points.sourceY + points.targetY) / 2}
1663
- r="10"
1664
- fill="#ef4444"
1665
- class="edge-delete"
1666
- on:click|stopPropagation={() => deleteEdge(edge.id)}
1667
- />
1668
- <text
1669
- x={(points.sourceX + points.targetX) / 2}
1670
- y={(points.sourceY + points.targetY) / 2 + 4}
1671
- text-anchor="middle"
1672
- class="edge-delete-text"
1673
- on:click|stopPropagation={() => deleteEdge(edge.id)}
1674
- >
1675
-
1676
- </text>
1677
- </g>
1678
- {/if}
1679
- {/each}
1680
-
1681
- <!-- Connection preview -->
1682
- {#if isConnecting && connectionStart}
1683
- {@const startNode = nodes.find(n => n.id === connectionStart)}
1684
- {#if startNode}
1685
- <path
1686
- d="M {startNode.position.x + 320} {startNode.position.y + 80} L {mousePos.x} {mousePos.y}"
1687
- stroke="#3b82f6"
1688
- stroke-width="3"
1689
- stroke-dasharray="8,4"
1690
- fill="none"
1691
- opacity="0.8"
1692
- />
1693
- {/if}
1694
- {/if}
1695
- </svg>
1696
-
1697
- <!-- FIXED: Nodes with guaranteed connection points -->
1698
- {#each nodes as node (node.id)}
1699
- {@const config = getComponentConfig(node.type)}
1700
- <div
1701
- class="node"
1702
- class:selected={selectedNode?.id === node.id}
1703
- style="left: {node.position.x}px; top: {node.position.y}px; border-color: {config.color};"
1704
- on:mousedown={(e) => handleMouseDown(e, node)}
1705
- on:click={(e) => handleNodeClick(e, node)}
1706
- >
1707
- <div class="node-header" style="background: {config.color};">
1708
- <span class="node-icon">{config.icon}</span>
1709
- <span class="node-title">{node.data.display_name || node.data.label}</span>
1710
- <button
1711
- class="node-delete"
1712
- on:click|stopPropagation={() => deleteNode(node.id)}
1713
- title="Delete node"
1714
- >
1715
-
1716
- </button>
1717
- </div>
1718
-
1719
- <div class="node-content">
1720
- <!-- Dynamic property rendering based on node type -->
1721
- {#if propertyFields[node.type]}
1722
- {#each propertyFields[node.type].slice(0, 3) as field}
1723
- <div class="node-property">
1724
- <label class="property-label">{field.label}:</label>
1725
- {#if field.type === 'select'}
1726
- <select
1727
- class="property-select"
1728
- value={getNodeProperty(node, field.key) || ''}
1729
- on:change={(e) => updateNodeProperty(node.id, field.key, e.target.value)}
1730
- on:click|stopPropagation
1731
- >
1732
- {#each field.options as option}
1733
- <option value={option}>{option}</option>
1734
- {/each}
1735
- </select>
1736
- {:else if field.type === 'number'}
1737
- <input
1738
- class="property-input"
1739
- type="number"
1740
- min={field.min}
1741
- max={field.max}
1742
- step={field.step}
1743
- value={getNodeProperty(node, field.key) || 0}
1744
- on:input={(e) => updateNodeProperty(node.id, field.key, Number(e.target.value))}
1745
- on:click|stopPropagation
1746
- />
1747
- {:else if field.type === 'checkbox'}
1748
- <label class="property-checkbox">
1749
- <input
1750
- type="checkbox"
1751
- checked={getNodeProperty(node, field.key) || false}
1752
- on:change={(e) => updateNodeProperty(node.id, field.key, e.target.checked)}
1753
- on:click|stopPropagation
1754
- />
1755
- <span>Yes</span>
1756
- </label>
1757
- {:else if field.type === 'textarea'}
1758
- <textarea
1759
- class="property-input"
1760
- value={getNodeProperty(node, field.key) || ''}
1761
- on:input={(e) => updateNodeProperty(node.id, field.key, e.target.value)}
1762
- on:click|stopPropagation
1763
- rows="2"
1764
- ></textarea>
1765
- {:else}
1766
- <input
1767
- class="property-input"
1768
- type="text"
1769
- value={getNodeProperty(node, field.key) || ''}
1770
- on:input={(e) => updateNodeProperty(node.id, field.key, e.target.value)}
1771
- on:click|stopPropagation
1772
- />
1773
- {/if}
1774
- </div>
1775
- {/each}
1776
- {:else}
1777
- <div class="node-status">Ready</div>
1778
- {/if}
1779
- </div>
1780
-
1781
- <!-- FIXED: Connection points with fallback system -->
1782
- {#if node.data.template}
1783
- <!-- Try to create dynamic connection points based on template -->
1784
- {@const templateHandles = Object.entries(node.data.template).filter(([_, handle]) => handle.is_handle)}
1785
- {#each templateHandles as [handleId, handle], index}
1786
- {#if handle.type === 'string' || handle.type === 'object' || handle.type === 'list' || handle.type === 'file'}
1787
- <div
1788
- class="connection-point {handle.type === 'string' || handle.type === 'list' || handle.type === 'file' ? 'output' : 'input'}"
1789
- style="top: {index * 25 + 40}px; {(handle.type === 'string' || handle.type === 'list' || handle.type === 'file') ? 'right: -6px;' : 'left: -6px;'}"
1790
- on:mouseup={(e) => (handle.type === 'object') && endConnection(e, node.id)}
1791
- on:mousedown={(e) => (handle.type === 'string' || handle.type === 'list' || handle.type === 'file') && startConnection(e, node.id)}
1792
- title={`${handle.display_name || handleId} (${handle.type})`}
1793
- ></div>
1794
- {/if}
1795
- {/each}
1796
-
1797
- <!-- FALLBACK: Ensure every node has at least basic connection points -->
1798
- {@const hasInputHandles = templateHandles.some(([_, h]) => h.type === 'object')}
1799
- {@const hasOutputHandles = templateHandles.some(([_, h]) => h.type === 'string' || h.type === 'list' || h.type === 'file')}
1800
-
1801
- {#if !hasInputHandles}
1802
- <div
1803
- class="connection-point input"
1804
- style="top: 50%; left: -6px; transform: translateY(-50%);"
1805
- on:mouseup={(e) => endConnection(e, node.id)}
1806
- title="Input"
1807
- ></div>
1808
- {/if}
1809
-
1810
- {#if !hasOutputHandles}
1811
- <div
1812
- class="connection-point output"
1813
- style="top: 50%; right: -6px; transform: translateY(-50%);"
1814
- on:mousedown={(e) => startConnection(e, node.id)}
1815
- title="Output"
1816
- ></div>
1817
- {/if}
1818
- {:else}
1819
- <!-- FALLBACK: Nodes without templates get basic connection points -->
1820
- <div
1821
- class="connection-point input"
1822
- style="top: 50%; left: -6px; transform: translateY(-50%);"
1823
- on:mouseup={(e) => endConnection(e, node.id)}
1824
- title="Input"
1825
- ></div>
1826
- <div
1827
- class="connection-point output"
1828
- style="top: 50%; right: -6px; transform: translateY(-50%);"
1829
- on:mousedown={(e) => startConnection(e, node.id)}
1830
- title="Output"
1831
- ></div>
1832
- {/if}
1833
- </div>
1834
- {/each}
1835
- </div>
1836
- </div>
1837
- </div>
1838
-
1839
- <!-- Right Property Panel -->
1840
- <div class="property-panel" class:collapsed={propertyPanelCollapsed}>
1841
- <div class="property-header">
1842
- {#if !propertyPanelCollapsed}
1843
- <h3>Properties</h3>
1844
- {/if}
1845
- <button
1846
- class="toggle-btn property-toggle"
1847
- on:click={togglePropertyPanel}
1848
- title={propertyPanelCollapsed ? 'Expand properties' : 'Collapse properties'}
1849
- >
1850
- {propertyPanelCollapsed ? '←' : '→'}
1851
- </button>
1852
- </div>
1853
-
1854
- {#if !propertyPanelCollapsed}
1855
- <div class="property-content">
1856
- {#if selectedNode && propertyFields[selectedNode.type]}
1857
- <div class="property-node-info">
1858
- <h4>{selectedNode.data.display_name || selectedNode.data.label}</h4>
1859
- <p class="property-node-type">TYPE: {selectedNode.type.toUpperCase()}</p>
1860
- </div>
1861
-
1862
- <div class="property-fields">
1863
- {#each propertyFields[selectedNode.type] as field}
1864
- <div class="property-field">
1865
- <label for={field.key}>{field.label}</label>
1866
- {#if field.help}
1867
- <small class="field-help">{field.help}</small>
1868
- {/if}
1869
-
1870
- {#if field.type === 'text'}
1871
- <input
1872
- type="text"
1873
- id={field.key}
1874
- value={getNodeProperty(selectedNode, field.key) || ''}
1875
- on:input={(e) => updateNodeProperty(selectedNode.id, field.key, e.target.value)}
1876
- />
1877
- {:else if field.type === 'number'}
1878
- <input
1879
- type="number"
1880
- id={field.key}
1881
- value={getNodeProperty(selectedNode, field.key) || 0}
1882
- min={field.min}
1883
- max={field.max}
1884
- step={field.step}
1885
- on:input={(e) => updateNodeProperty(selectedNode.id, field.key, Number(e.target.value))}
1886
- />
1887
- {:else if field.type === 'checkbox'}
1888
- <label class="checkbox-label">
1889
- <input
1890
- type="checkbox"
1891
- id={field.key}
1892
- checked={getNodeProperty(selectedNode, field.key) || false}
1893
- on:change={(e) => updateNodeProperty(selectedNode.id, field.key, e.target.checked)}
1894
- />
1895
- <span class="checkbox-text">Enable</span>
1896
- </label>
1897
- {:else if field.type === 'select'}
1898
- <select
1899
- id={field.key}
1900
- value={getNodeProperty(selectedNode, field.key) || ''}
1901
- on:change={(e) => updateNodeProperty(selectedNode.id, field.key, e.target.value)}
1902
- >
1903
- {#each field.options as option}
1904
- <option value={option}>{option}</option>
1905
- {/each}
1906
- </select>
1907
- {:else if field.type === 'textarea'}
1908
- <textarea
1909
- id={field.key}
1910
- value={getNodeProperty(selectedNode, field.key) || ''}
1911
- on:input={(e) => updateNodeProperty(selectedNode.id, field.key, e.target.value)}
1912
- rows="4"
1913
- ></textarea>
1914
- {/if}
1915
- </div>
1916
- {/each}
1917
- </div>
1918
- {:else}
1919
- <div class="property-empty">
1920
- <div class="empty-icon">🎯</div>
1921
- <p>Select a node to edit properties</p>
1922
- <small>Click on any node to configure its detailed settings</small>
1923
- </div>
1924
- {/if}
1925
- </div>
1926
- {/if}
1927
- </div>
1928
- </div>
1929
- </div>
1930
-
1931
- <style>
1932
- /* Base styles with proper sizing */
1933
- .workflow-builder {
1934
- width: 100%;
1935
- height: 700px;
1936
- border: 1px solid #e2e8f0;
1937
- border-radius: 12px;
1938
- display: flex;
1939
- flex-direction: column;
1940
- background: #ffffff;
1941
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
1942
- overflow: hidden;
1943
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
1944
- }
1945
-
1946
- .hide {
1947
- display: none;
1948
- }
1949
-
1950
- .top-section {
1951
- flex: 1;
1952
- display: flex;
1953
- min-height: 0;
1954
- }
1955
-
1956
- /* Sidebar Styles */
1957
- .sidebar {
1958
- width: 240px;
1959
- min-width: 240px;
1960
- background: #f8fafc;
1961
- border-right: 1px solid #e2e8f0;
1962
- display: flex;
1963
- flex-direction: column;
1964
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1965
- position: relative;
1966
- }
1967
-
1968
- .sidebar.collapsed {
1969
- width: 48px;
1970
- min-width: 48px;
1971
- }
1972
-
1973
- .sidebar-header {
1974
- padding: 12px;
1975
- border-bottom: 1px solid #e2e8f0;
1976
- display: flex;
1977
- align-items: center;
1978
- justify-content: space-between;
1979
- background: white;
1980
- min-height: 50px;
1981
- box-sizing: border-box;
1982
- }
1983
-
1984
- .sidebar-header h3 {
1985
- margin: 0;
1986
- font-size: 15px;
1987
- font-weight: 600;
1988
- color: #1e293b;
1989
- }
1990
-
1991
- .toggle-btn {
1992
- background: #f1f5f9;
1993
- border: 1px solid #e2e8f0;
1994
- border-radius: 6px;
1995
- padding: 6px 8px;
1996
- cursor: pointer;
1997
- color: #64748b;
1998
- font-size: 14px;
1999
- transition: all 0.2s;
2000
- min-width: 28px;
2001
- height: 28px;
2002
- display: flex;
2003
- align-items: center;
2004
- justify-content: center;
2005
- z-index: 10;
2006
- position: relative;
2007
- }
2008
-
2009
- .toggle-btn:hover {
2010
- background: #e2e8f0;
2011
- color: #475569;
2012
- }
2013
-
2014
- .sidebar-toggle {
2015
- position: absolute;
2016
- right: 8px;
2017
- top: 50%;
2018
- transform: translateY(-50%);
2019
- }
2020
-
2021
- .sidebar-content {
2022
- flex: 1;
2023
- overflow-y: auto;
2024
- padding: 12px;
2025
- }
2026
-
2027
- .category {
2028
- margin-bottom: 12px;
2029
- }
2030
-
2031
- .category-header {
2032
- display: flex;
2033
- align-items: center;
2034
- padding: 6px 0;
2035
- font-weight: 600;
2036
- font-size: 12px;
2037
- color: #374151;
2038
- border-bottom: 1px solid #e5e7eb;
2039
- margin-bottom: 6px;
2040
- }
2041
-
2042
- .category-icon {
2043
- margin-right: 6px;
2044
- font-size: 14px;
2045
- }
2046
-
2047
- .component-item {
2048
- display: flex;
2049
- align-items: center;
2050
- padding: 6px 8px;
2051
- margin-bottom: 3px;
2052
- background: white;
2053
- border: 1px solid #e5e7eb;
2054
- border-radius: 6px;
2055
- cursor: grab;
2056
- transition: all 0.2s ease;
2057
- font-size: 12px;
2058
- }
2059
-
2060
- .component-item:hover {
2061
- background: #f8fafc;
2062
- border-color: #cbd5e1;
2063
- transform: translateX(2px);
2064
- }
2065
-
2066
- .component-item:active {
2067
- cursor: grabbing;
2068
- }
2069
-
2070
- .component-icon {
2071
- margin-right: 6px;
2072
- font-size: 14px;
2073
- }
2074
-
2075
- .component-label {
2076
- font-weight: 500;
2077
- color: #374151;
2078
- }
2079
-
2080
- /* Canvas Area Styles */
2081
- .canvas-area {
2082
- flex: 1;
2083
- display: flex;
2084
- flex-direction: column;
2085
- min-width: 400px;
2086
- }
2087
-
2088
- .toolbar {
2089
- height: 50px;
2090
- border-bottom: 1px solid #e2e8f0;
2091
- display: flex;
2092
- align-items: center;
2093
- justify-content: space-between;
2094
- padding: 0 16px;
2095
- background: white;
2096
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
2097
- }
2098
-
2099
- .workflow-name-input {
2100
- font-size: 16px;
2101
- font-weight: 600;
2102
- color: #1e293b;
2103
- border: none;
2104
- background: transparent;
2105
- outline: none;
2106
- padding: 4px 8px;
2107
- border-radius: 4px;
2108
- transition: background 0.2s;
2109
- }
2110
-
2111
- .workflow-name-input:hover,
2112
- .workflow-name-input:focus {
2113
- background: #f1f5f9;
2114
- }
2115
-
2116
- .toolbar-center {
2117
- display: flex;
2118
- align-items: center;
2119
- }
2120
-
2121
- .zoom-controls {
2122
- display: flex;
2123
- align-items: center;
2124
- gap: 4px;
2125
- background: #f1f5f9;
2126
- padding: 4px;
2127
- border-radius: 8px;
2128
- border: 1px solid #e2e8f0;
2129
- }
2130
-
2131
- .zoom-btn {
2132
- background: white;
2133
- border: none;
2134
- width: 28px;
2135
- height: 28px;
2136
- border-radius: 4px;
2137
- cursor: pointer;
2138
- font-weight: 600;
2139
- display: flex;
2140
- align-items: center;
2141
- justify-content: center;
2142
- transition: all 0.2s;
2143
- font-size: 14px;
2144
- }
2145
-
2146
- .zoom-btn:hover {
2147
- background: #e2e8f0;
2148
- }
2149
-
2150
- .zoom-btn.reset {
2151
- font-size: 12px;
2152
- }
2153
-
2154
- .zoom-level {
2155
- font-size: 12px;
2156
- font-weight: 600;
2157
- color: #64748b;
2158
- min-width: 40px;
2159
- text-align: center;
2160
- }
2161
-
2162
- .toolbar-right {
2163
- display: flex;
2164
- gap: 12px;
2165
- font-size: 12px;
2166
- align-items: center;
2167
- }
2168
-
2169
- .node-count, .edge-count {
2170
- color: #64748b;
2171
- background: #f1f5f9;
2172
- padding: 4px 8px;
2173
- border-radius: 12px;
2174
- font-weight: 500;
2175
- }
2176
-
2177
- .export-btn {
2178
- background: #3b82f6;
2179
- color: white;
2180
- border: none;
2181
- padding: 6px 12px;
2182
- border-radius: 6px;
2183
- font-size: 12px;
2184
- font-weight: 500;
2185
- cursor: pointer;
2186
- transition: all 0.2s;
2187
- display: flex;
2188
- align-items: center;
2189
- gap: 4px;
2190
- }
2191
-
2192
- .export-btn:hover {
2193
- background: #2563eb;
2194
- transform: translateY(-1px);
2195
- }
2196
-
2197
- .canvas-container {
2198
- flex: 1;
2199
- position: relative;
2200
- overflow: hidden;
2201
- background: #fafbfc;
2202
- cursor: grab;
2203
- }
2204
-
2205
- .canvas-container:active {
2206
- cursor: grabbing;
2207
- }
2208
-
2209
- .canvas {
2210
- position: absolute;
2211
- top: 0;
2212
- left: 0;
2213
- width: 4000px;
2214
- height: 4000px;
2215
- transform-origin: 0 0;
2216
- }
2217
-
2218
- .grid-background {
2219
- position: absolute;
2220
- top: 0;
2221
- left: 0;
2222
- width: 100%;
2223
- height: 100%;
2224
- background-image:
2225
- radial-gradient(circle, #e2e8f0 1px, transparent 1px);
2226
- background-size: 20px 20px;
2227
- pointer-events: none;
2228
- opacity: 0.6;
2229
- }
2230
-
2231
- .edges-layer {
2232
- position: absolute;
2233
- top: 0;
2234
- left: 0;
2235
- width: 100%;
2236
- height: 100%;
2237
- pointer-events: none;
2238
- z-index: 1;
2239
- }
2240
-
2241
- .edge-delete, .edge-delete-text {
2242
- pointer-events: all;
2243
- cursor: pointer;
2244
- }
2245
-
2246
- .edge-delete-text {
2247
- font-size: 10px;
2248
- fill: white;
2249
- text-anchor: middle;
2250
- user-select: none;
2251
- }
2252
-
2253
- .edge-delete:hover {
2254
- fill: #dc2626;
2255
- }
2256
-
2257
- /* Node styles with proper sizing and no overflow */
2258
- .node {
2259
- position: absolute;
2260
- width: 320px;
2261
- min-height: 160px;
2262
- background: white;
2263
- border: 2px solid #e2e8f0;
2264
- border-radius: 10px;
2265
- cursor: move;
2266
- user-select: none;
2267
- z-index: 2;
2268
- transition: all 0.2s ease;
2269
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
2270
- overflow: visible;
2271
- }
2272
-
2273
- .node:hover {
2274
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
2275
- transform: translateY(-1px);
2276
- }
2277
-
2278
- .node.selected {
2279
- border-color: #3b82f6;
2280
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1), 0 4px 16px rgba(0, 0, 0, 0.15);
2281
- }
2282
-
2283
- .node-header {
2284
- display: flex;
2285
- align-items: center;
2286
- padding: 12px 16px;
2287
- color: white;
2288
- font-weight: 600;
2289
- font-size: 14px;
2290
- border-radius: 8px 8px 0 0;
2291
- min-height: 24px;
2292
- }
2293
-
2294
- .node-icon {
2295
- margin-right: 8px;
2296
- font-size: 16px;
2297
- flex-shrink: 0;
2298
- }
2299
-
2300
- .node-title {
2301
- flex: 1;
2302
- overflow: hidden;
2303
- text-overflow: ellipsis;
2304
- white-space: nowrap;
2305
- }
2306
-
2307
- .node-delete {
2308
- background: rgba(255, 255, 255, 0.2);
2309
- border: none;
2310
- color: white;
2311
- cursor: pointer;
2312
- font-size: 12px;
2313
- padding: 4px 6px;
2314
- border-radius: 4px;
2315
- transition: all 0.2s;
2316
- flex-shrink: 0;
2317
- }
2318
-
2319
- .node-delete:hover {
2320
- background: rgba(255, 255, 255, 0.3);
2321
- }
2322
-
2323
- .node-content {
2324
- padding: 12px 16px;
2325
- max-height: 200px;
2326
- overflow-y: auto;
2327
- overflow-x: hidden;
2328
- }
2329
-
2330
- .node-property {
2331
- display: flex;
2332
- flex-direction: column;
2333
- gap: 4px;
2334
- margin-bottom: 12px;
2335
- font-size: 12px;
2336
- }
2337
-
2338
- .property-label {
2339
- font-weight: 600;
2340
- color: #374151;
2341
- font-size: 11px;
2342
- margin-bottom: 2px;
2343
- }
2344
-
2345
- .property-input, .property-select {
2346
- width: 100%;
2347
- padding: 6px 8px;
2348
- border: 1px solid #d1d5db;
2349
- border-radius: 4px;
2350
- font-size: 11px;
2351
- background: white;
2352
- transition: all 0.2s;
2353
- box-sizing: border-box;
2354
- resize: vertical;
2355
- }
2356
-
2357
- .property-input:focus, .property-select:focus {
2358
- outline: none;
2359
- border-color: #3b82f6;
2360
- box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
2361
- }
2362
-
2363
- .property-input:hover, .property-select:hover {
2364
- border-color: #9ca3af;
2365
- }
2366
-
2367
- .property-checkbox {
2368
- display: flex;
2369
- align-items: center;
2370
- gap: 6px;
2371
- font-size: 11px;
2372
- color: #374151;
2373
- cursor: pointer;
2374
- }
2375
-
2376
- .property-checkbox input[type="checkbox"] {
2377
- width: auto;
2378
- margin: 0;
2379
- cursor: pointer;
2380
- }
2381
-
2382
- .node-status {
2383
- font-size: 12px;
2384
- color: #64748b;
2385
- text-align: center;
2386
- padding: 20px;
2387
- font-style: italic;
2388
- }
2389
-
2390
- /* FIXED: Connection points that work for ALL nodes */
2391
- .connection-point {
2392
- position: absolute;
2393
- width: 12px;
2394
- height: 12px;
2395
- border-radius: 50%;
2396
- background: #3b82f6;
2397
- border: 2px solid white;
2398
- cursor: crosshair;
2399
- z-index: 3;
2400
- transition: all 0.2s ease;
2401
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
2402
- }
2403
-
2404
- .connection-point.input {
2405
- left: -6px;
2406
- }
2407
-
2408
- .connection-point.output {
2409
- right: -6px;
2410
- }
2411
-
2412
- .connection-point:hover {
2413
- background: #2563eb;
2414
- transform: scale(1.2);
2415
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
2416
- }
2417
-
2418
- /* Property Panel Styles */
2419
- .property-panel {
2420
- width: 280px;
2421
- min-width: 280px;
2422
- background: #f8fafc;
2423
- border-left: 1px solid #e2e8f0;
2424
- display: flex;
2425
- flex-direction: column;
2426
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2427
- position: relative;
2428
- }
2429
-
2430
- .property-panel.collapsed {
2431
- width: 48px;
2432
- min-width: 48px;
2433
- }
2434
-
2435
- .property-header {
2436
- padding: 12px;
2437
- border-bottom: 1px solid #e2e8f0;
2438
- display: flex;
2439
- align-items: center;
2440
- justify-content: space-between;
2441
- background: white;
2442
- min-height: 50px;
2443
- box-sizing: border-box;
2444
- }
2445
-
2446
- .property-header h3 {
2447
- margin: 0;
2448
- font-size: 15px;
2449
- font-weight: 600;
2450
- color: #1e293b;
2451
- }
2452
-
2453
- .property-toggle {
2454
- position: absolute;
2455
- left: 8px;
2456
- top: 50%;
2457
- transform: translateY(-50%);
2458
- }
2459
-
2460
- .property-content {
2461
- flex: 1;
2462
- overflow-y: auto;
2463
- padding: 16px;
2464
- }
2465
-
2466
- .property-node-info {
2467
- margin-bottom: 20px;
2468
- padding: 12px;
2469
- background: white;
2470
- border-radius: 8px;
2471
- border: 1px solid #e2e8f0;
2472
- }
2473
-
2474
- .property-node-info h4 {
2475
- margin: 0 0 4px 0;
2476
- font-size: 16px;
2477
- color: #1e293b;
2478
- }
2479
-
2480
- .property-node-type {
2481
- margin: 0;
2482
- font-size: 11px;
2483
- color: #64748b;
2484
- text-transform: uppercase;
2485
- font-weight: 600;
2486
- }
2487
-
2488
- .property-field {
2489
- margin-bottom: 16px;
2490
- }
2491
-
2492
- .property-field label {
2493
- display: block;
2494
- margin-bottom: 6px;
2495
- font-size: 13px;
2496
- font-weight: 600;
2497
- color: #374151;
2498
- }
2499
-
2500
- .field-help {
2501
- display: block;
2502
- margin-bottom: 4px;
2503
- font-size: 11px;
2504
- color: #64748b;
2505
- font-style: italic;
2506
- }
2507
-
2508
- .property-field input,
2509
- .property-field select,
2510
- .property-field textarea {
2511
- width: 100%;
2512
- padding: 8px 10px;
2513
- border: 1px solid #d1d5db;
2514
- border-radius: 6px;
2515
- font-size: 13px;
2516
- background: white;
2517
- transition: border-color 0.2s;
2518
- box-sizing: border-box;
2519
- }
2520
-
2521
- .property-field input:focus,
2522
- .property-field select:focus,
2523
- .property-field textarea:focus {
2524
- outline: none;
2525
- border-color: #3b82f6;
2526
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
2527
- }
2528
-
2529
- .checkbox-label {
2530
- display: flex !important;
2531
- align-items: center;
2532
- margin-bottom: 0 !important;
2533
- cursor: pointer;
2534
- }
2535
-
2536
- .checkbox-label input[type="checkbox"] {
2537
- width: auto !important;
2538
- margin-right: 8px !important;
2539
- }
2540
-
2541
- .property-empty {
2542
- text-align: center;
2543
- padding: 40px 16px;
2544
- color: #64748b;
2545
- }
2546
-
2547
- .empty-icon {
2548
- font-size: 32px;
2549
- margin-bottom: 12px;
2550
- opacity: 0.5;
2551
- }
2552
-
2553
- .property-empty p {
2554
- margin: 0 0 6px 0;
2555
- font-size: 14px;
2556
- font-weight: 500;
2557
- }
2558
-
2559
- .property-empty small {
2560
- font-size: 12px;
2561
- opacity: 0.7;
2562
- }
2563
- .clear-btn {
2564
- background: #ef4444;
2565
- color: white;
2566
- border: none;
2567
- padding: 6px 12px;
2568
- border-radius: 6px;
2569
- font-size: 12px;
2570
- font-weight: 500;
2571
- cursor: pointer;
2572
- transition: all 0.2s;
2573
- display: flex;
2574
- align-items: center;
2575
- gap: 4px;
2576
- }
2577
-
2578
- .clear-btn:hover {
2579
- background: #dc2626;
2580
- transform: translateY(-1px);
2581
- }
2582
-
2583
- </style>