TrisL commited on
Commit
c9ae51a
·
verified ·
1 Parent(s): 8c56820

import React, { useState, useCallback, useRef, useEffect } from 'react';

Browse files

import ReactFlow, {
addEdge,
useNodesState,
useEdgesState,
Controls,
Background,
BackgroundVariant,
Connection,
Edge,
ReactFlowProvider,
Node,
NodeChange,
EdgeChange,
OnNodesChange,
OnEdgesChange,
} from 'reactflow';
import Sidebar from './components/Sidebar';
import CustomNode from './components/CustomNode';
import FloatingOverlay from './components/FloatingOverlay';
import { INITIAL_NODES, INITIAL_EDGES } from './constants';
import { PhoenixNode, PhoenixEdge, NodeType } from './types';
import { Plus } from 'lucide-react';

const nodeTypes = {
custom: CustomNode,
};

interface HistoryItem {
nodes: PhoenixNode[];
edges: PhoenixEdge[];
}

const FlowArea = () => {
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const [nodes, setNodes, onNodesChange] = useNodesState(INITIAL_NODES);
const [edges, setEdges, onEdgesChange] = useEdgesState(INITIAL_EDGES);
const [isExecuting, setIsExecuting] = useState(false);

// Undo/Redo History State
const [past, setPast] = useState<HistoryItem[]>([]);
const [future, setFuture] = useState<HistoryItem[]>([]);

// Capture current state into history
const takeSnapshot = useCallback(() => {
setPast((p) => {
const newPast = [...p, { nodes, edges }];
// Limit history depth to 50
return newPast.length > 50 ? newPast.slice(1) : newPast;
});
setFuture([]);
}, [nodes, edges]);

const undo = useCallback(() => {
if (past.length === 0) return;
const previous = past[past.length - 1];
const newPast = past.slice(0, past.length - 1);

setFuture((f) => [{ nodes, edges }, ...f]);
setPast(newPast);
setNodes(previous.nodes);
setEdges(previous.edges);
}, [past, nodes, edges, setNodes, setEdges]);

const redo = useCallback(() => {
if (future.length === 0) return;
const next = future[0];
const newFuture = future.slice(1);

setPast((p) => [...p, { nodes, edges }]);
setFuture(newFuture);
setNodes(next.nodes);
setEdges(next.edges);
}, [future, nodes, edges, setNodes, setEdges]);

// Keyboard Shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Undo: Ctrl+Z or Cmd+Z
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
e.preventDefault();
undo();
}
// Redo: Ctrl+Y, Cmd+Y, or Ctrl+Shift+Z
if (
((e.ctrlKey || e.metaKey) && e.key === 'y') ||
((e.ctrlKey || e.metaKey) && e.key === 'z' && e.shiftKey)
) {
e.preventDefault();
redo();
}
};

window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [undo, redo]);

// Wrapped Event Handlers to trigger Snapshots

const onNodesChangeWrapper: OnNodesChange = useCallback((changes) => {
// Snapshot on explicit removal (e.g. Backspace)
// Note: We don't snapshot on 'dimensions' or 'select' changes to avoid spam
if (changes.some(c => c.type === 'remove')) {
takeSnapshot();
}
onNodesChange(changes);
}, [onNodesChange, takeSnapshot]);

const onEdgesChangeWrapper: OnEdgesChange = useCallback((changes) => {
if (changes.some(c => c.type === 'remove')) {
takeSnapshot();
}
onEdgesChange(changes);
}, [onEdgesChange, takeSnapshot]);

const onConnect = useCallback(
(params: Connection) => {
takeSnapshot(); // Save state before connection
setEdges((eds) => addEdge({ ...params, animated: true, style: { stroke: '#64748b', strokeWidth: 2 } }, eds));
},
[setEdges, takeSnapshot]
);

const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, []);

const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault();

const type = event.dataTransfer.getData('application/reactflow');
const label = event.dataTransfer.getData('application/reactflow-label');

if (typeof type === 'undefined' || !type) {
return;
}

const reactFlowBounds = reactFlowWrapper.current?.getBoundingClientRect();
const position = reactFlowBounds ? {
x: event.clientX - reactFlowBounds.left - 100,
y: event.clientY - reactFlowBounds.top - 20
} : { x: 0, y: 0 };

const newNode: PhoenixNode = {
id: `node_${Date.now()}`,
type: 'custom',
position,
data: {
label: label,
icon: type,
status: 'idle'
},
};

takeSnapshot(); // Save state before adding node
setNodes((nds) => nds.concat(newNode));
},
[setNodes, takeSnapshot]
);

const onNodeDragStart = useCallback(() => {
takeSnapshot(); // Save state before moving node
}, [takeSnapshot]);

const handleGeminiGeneration = useCallback((newNodes: PhoenixNode[], newEdges: PhoenixEdge[]) => {
takeSnapshot(); // Save state before AI generation overwrites

const formattedNodes = newNodes.map(n => ({
...n,
type: 'custom',
data: {
...n.data,
icon: n.data.icon || (n.type === NodeType.WEBHOOK ? 'webhook' : 'code'),
status: 'idle'
}
}));

const formattedEdges = newEdges.map(e => ({
...e,
animated: true,
style: { stroke: '#64748b', strokeWidth: 2 }
}));

setNodes(formattedNodes);
setEdges(formattedEdges);
}, [setNodes, setEdges, takeSnapshot]);

const runWorkflow = useCallback(async () => {
setIsExecuting(true);
const nodeIds = nodes.map(n => n.id);

for (const id of nodeIds) {
setNodes(nds => nds.map(n => n.id === id ? { ...n, data: { ...n.data, status: 'running' } } : n));
await new Promise(r => setTimeout(r, 800));
setNodes(nds => nds.map(n => n.id === id ? { ...n, data: { ...n.data, status: 'success' } } : n));
}

setIsExecuting(false);
}, [nodes, setNodes]);

return (
<div className="flex h-screen bg-black overflow-hidden">
<Sidebar />

<div className="flex-1 relative h-full" ref={reactFlowWrapper}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChangeWrapper}
onEdgesChange={onEdgesChangeWrapper}
onConnect={onConnect}
onDragOver={onDragOver}
onDrop={onDrop}
onNodeDragStart={onNodeDragStart}
nodeTypes={nodeTypes}
fitView
proOptions={{ hideAttribution: true }}
snapToGrid={true}
snapGrid={[20, 20]}
>
<Background
variant={BackgroundVariant.Dots}
gap={20}
size={1}
color="#334155"
className="bg-void-950"
/>
<Controls className="bg-slate-800 border-slate-700 fill-white" />
</ReactFlow>

<FloatingOverlay
onGenerate={handleGeminiGeneration}
onRun={runWorkflow}
isExecuting={isExecuting}
onUndo={undo}
onRedo={redo}
canUndo={past.length > 0}
canRedo={future.length > 0}
/>

{nodes.length === 0 && (
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center pointer-events-none opacity-50">
<div className="w-16 h-16 bg-slate-800 rounded-full mx-auto mb-4 flex items-center justify-center">
<Plus className="w-8 h-8 text-slate-500" />
</div>
<h3 className="text-xl font-bold text-slate-300">Start Building</h3>
<p className="text-slate-500">Drag nodes from the sidebar or ask AI to generate a flow.</p>
</div>
)}
</div>
</div>
);
};

export default function App() {
return (
<ReactFlowProvider>
<FlowArea />
</ReactFlowProvider>
);
}

README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Phoenix Flow Builder
3
- emoji: 📉
4
- colorFrom: yellow
5
- colorTo: green
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: Phoenix Flow Builder 🔥
3
+ colorFrom: purple
4
+ colorTo: pink
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
components/custom-node.js ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class CustomNode extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ .node-container {
7
+ background: rgba(15, 23, 42, 0.9);
8
+ border: 1px solid rgba(71, 85, 105, 0.5);
9
+ border-radius: 0.5rem;
10
+ padding: 1rem;
11
+ min-width: 200px;
12
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
13
+ transition: all 0.2s ease;
14
+ }
15
+
16
+ .node-container:hover {
17
+ border-color: #fb923c;
18
+ box-shadow: 0 10px 15px -3px rgba(251, 146, 60, 0.1);
19
+ }
20
+
21
+ .node-header {
22
+ display: flex;
23
+ align-items: center;
24
+ margin-bottom: 0.75rem;
25
+ }
26
+
27
+ .node-icon {
28
+ width: 2rem;
29
+ height: 2rem;
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ background: rgba(251, 146, 60, 0.1);
34
+ border-radius: 0.25rem;
35
+ margin-right: 0.75rem;
36
+ color: #fb923c;
37
+ }
38
+
39
+ .node-title {
40
+ font-weight: 600;
41
+ color: #e2e8f0;
42
+ }
43
+
44
+ .node-body {
45
+ font-size: 0.875rem;
46
+ color: #94a3b8;
47
+ }
48
+
49
+ .node-status {
50
+ height: 0.5rem;
51
+ width: 0.5rem;
52
+ border-radius: 50%;
53
+ margin-left: auto;
54
+ }
55
+
56
+ .status-idle {
57
+ background-color: #64748b;
58
+ }
59
+
60
+ .status-running {
61
+ background-color: #f59e0b;
62
+ animation: pulse 1.5s infinite;
63
+ }
64
+
65
+ .status-success {
66
+ background-color: #10b981;
67
+ }
68
+
69
+ .status-error {
70
+ background-color: #ef4444;
71
+ }
72
+
73
+ @keyframes pulse {
74
+ 0%, 100% {
75
+ opacity: 1;
76
+ }
77
+ 50% {
78
+ opacity: 0.5;
79
+ }
80
+ }
81
+ </style>
82
+
83
+ <div class="node-container">
84
+ <div class="node-header">
85
+ <div class="node-icon">
86
+ <i data-feather="code"></i>
87
+ </div>
88
+ <div class="node-title">Node</div>
89
+ <div class="node-status status-idle"></div>
90
+ </div>
91
+ <div class="node-body">
92
+ Drag handles to connect nodes
93
+ </div>
94
+ </div>
95
+ `;
96
+ }
97
+ }
98
+
99
+ customElements.define('custom-node', CustomNode);
components/floating-overlay.js ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class FloatingOverlay extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ :host {
7
+ position: absolute;
8
+ bottom: 1rem;
9
+ right: 1rem;
10
+ z-index: 10;
11
+ }
12
+
13
+ .overlay-container {
14
+ display: flex;
15
+ gap: 0.5rem;
16
+ }
17
+
18
+ .action-button {
19
+ width: 3rem;
20
+ height: 3rem;
21
+ border-radius: 50%;
22
+ background: rgba(15, 23, 42, 0.9);
23
+ border: 1px solid rgba(71, 85, 105, 0.5);
24
+ color: #94a3b8;
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ cursor: pointer;
29
+ transition: all 0.2s ease;
30
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
31
+ }
32
+
33
+ .action-button:hover {
34
+ background: rgba(251, 146, 60, 0.1);
35
+ color: #fb923c;
36
+ border-color: #fb923c;
37
+ }
38
+
39
+ .primary-button {
40
+ background: #fb923c;
41
+ color: white;
42
+ border-color: #fb923c;
43
+ }
44
+
45
+ .primary-button:hover {
46
+ background: #ea580c;
47
+ }
48
+
49
+ .disabled {
50
+ opacity: 0.5;
51
+ pointer-events: none;
52
+ }
53
+ </style>
54
+
55
+ <div class="overlay-container">
56
+ <div class="action-button" id="undo-btn" title="Undo (Ctrl+Z)">
57
+ <i data-feather="corner-up-left"></i>
58
+ </div>
59
+ <div class="action-button" id="redo-btn" title="Redo (Ctrl+Y)">
60
+ <i data-feather="corner-up-right"></i>
61
+ </div>
62
+ <div class="action-button" id="ai-btn" title="Generate with AI">
63
+ <i data-feather="sparkles"></i>
64
+ </div>
65
+ <div class="action-button primary-button" id="run-btn" title="Run Workflow">
66
+ <i data-feather="play"></i>
67
+ </div>
68
+ </div>
69
+ `;
70
+
71
+ this.setupEventListeners();
72
+ this.updateButtonStates();
73
+ }
74
+
75
+ setupEventListeners() {
76
+ this.shadowRoot.getElementById('undo-btn').addEventListener('click', () => {
77
+ this.dispatchEvent(new CustomEvent('undo'));
78
+ });
79
+
80
+ this.shadowRoot.getElementById('redo-btn').addEventListener('click', () => {
81
+ this.dispatchEvent(new CustomEvent('redo'));
82
+ });
83
+
84
+ this.shadowRoot.getElementById('ai-btn').addEventListener('click', () => {
85
+ this.dispatchEvent(new CustomEvent('generate'));
86
+ });
87
+
88
+ this.shadowRoot.getElementById('run-btn').addEventListener('click', () => {
89
+ this.dispatchEvent(new CustomEvent('run'));
90
+ });
91
+ }
92
+
93
+ updateButtonStates() {
94
+ const canUndo = this.hasAttribute('can-undo');
95
+ const canRedo = this.hasAttribute('can-redo');
96
+ const isExecuting = this.hasAttribute('is-executing');
97
+
98
+ const undoBtn = this.shadowRoot.getElementById('undo-btn');
99
+ const redoBtn = this.shadowRoot.getElementById('redo-btn');
100
+ const runBtn = this.shadowRoot.getElementById('run-btn');
101
+
102
+ undoBtn.classList.toggle('disabled', !canUndo);
103
+ redoBtn.classList.toggle('disabled', !canRedo);
104
+ runBtn.classList.toggle('disabled', isExecuting);
105
+
106
+ if (isExecuting) {
107
+ runBtn.innerHTML = '<i data-feather="loader" class="animate-spin"></i>';
108
+ } else {
109
+ runBtn.innerHTML = '<i data-feather="play"></i>';
110
+ }
111
+ }
112
+
113
+ static get observedAttributes() {
114
+ return ['can-undo', 'can-redo', 'is-executing'];
115
+ }
116
+
117
+ attributeChangedCallback(name, oldValue, newValue) {
118
+ if (name === 'can-undo' || name === 'can-redo' || name === 'is-executing') {
119
+ this.updateButtonStates();
120
+ }
121
+ }
122
+ }
123
+
124
+ customElements.define('floating-overlay', FloatingOverlay);
components/sidebar.js ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class CustomSidebar extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ :host {
7
+ display: block;
8
+ width: 280px;
9
+ height: 100vh;
10
+ background: rgba(15, 23, 42, 0.95);
11
+ border-right: 1px solid rgba(71, 85, 105, 0.5);
12
+ color: #e2e8f0;
13
+ overflow-y: auto;
14
+ position: relative;
15
+ z-index: 10;
16
+ }
17
+
18
+ .sidebar-header {
19
+ padding: 1.5rem;
20
+ border-bottom: 1px solid rgba(71, 85, 105, 0.5);
21
+ }
22
+
23
+ .sidebar-title {
24
+ font-size: 1.25rem;
25
+ font-weight: 600;
26
+ color: #fb923c;
27
+ margin-bottom: 0.5rem;
28
+ }
29
+
30
+ .sidebar-subtitle {
31
+ font-size: 0.875rem;
32
+ color: #94a3b8;
33
+ }
34
+
35
+ .node-category {
36
+ padding: 1rem 1.5rem;
37
+ border-bottom: 1px solid rgba(71, 85, 105, 0.5);
38
+ }
39
+
40
+ .category-title {
41
+ font-size: 0.875rem;
42
+ font-weight: 500;
43
+ color: #94a3b8;
44
+ margin-bottom: 0.75rem;
45
+ text-transform: uppercase;
46
+ letter-spacing: 0.05em;
47
+ }
48
+
49
+ .node-item {
50
+ display: flex;
51
+ align-items: center;
52
+ padding: 0.75rem;
53
+ margin-bottom: 0.5rem;
54
+ border-radius: 0.375rem;
55
+ background: rgba(30, 41, 59, 0.5);
56
+ cursor: grab;
57
+ transition: all 0.2s ease;
58
+ }
59
+
60
+ .node-item:hover {
61
+ background: rgba(251, 146, 60, 0.1);
62
+ border-color: #fb923c;
63
+ }
64
+
65
+ .node-icon {
66
+ width: 2rem;
67
+ height: 2rem;
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ background: rgba(251, 146, 60, 0.1);
72
+ border-radius: 0.25rem;
73
+ margin-right: 0.75rem;
74
+ color: #fb923c;
75
+ }
76
+
77
+ .node-label {
78
+ font-size: 0.875rem;
79
+ font-weight: 500;
80
+ }
81
+ </style>
82
+
83
+ <div class="sidebar-header">
84
+ <div class="sidebar-title">Phoenix Flow Builder</div>
85
+ <div class="sidebar-subtitle">Drag nodes to the canvas</div>
86
+ </div>
87
+
88
+ <div class="node-category">
89
+ <div class="category-title">Triggers</div>
90
+ <div class="node-item" draggable="true" data-type="trigger" data-label="Webhook">
91
+ <div class="node-icon">
92
+ <i data-feather="activity"></i>
93
+ </div>
94
+ <div class="node-label">Webhook Trigger</div>
95
+ </div>
96
+ <div class="node-item" draggable="true" data-type="trigger" data-label="Schedule">
97
+ <div class="node-icon">
98
+ <i data-feather="clock"></i>
99
+ </div>
100
+ <div class="node-label">Scheduled Trigger</div>
101
+ </div>
102
+ </div>
103
+
104
+ <div class="node-category">
105
+ <div class="category-title">Actions</div>
106
+ <div class="node-item" draggable="true" data-type="action" data-label="API Request">
107
+ <div class="node-icon">
108
+ <i data-feather="send"></i>
109
+ </div>
110
+ <div class="node-label">API Request</div>
111
+ </div>
112
+ <div class="node-item" draggable="true" data-type="action" data-label="Database Query">
113
+ <div class="node-icon">
114
+ <i data-feather="database"></i>
115
+ </div>
116
+ <div class="node-label">Database Query</div>
117
+ </div>
118
+ </div>
119
+
120
+ <div class="node-category">
121
+ <div class="category-title">Logic</div>
122
+ <div class="node-item" draggable="true" data-type="condition" data-label="If Condition">
123
+ <div class="node-icon">
124
+ <i data-feather="code"></i>
125
+ </div>
126
+ <div class="node-label">If Condition</div>
127
+ </div>
128
+ <div class="node-item" draggable="true" data-type="condition" data-label="Switch">
129
+ <div class="node-icon">
130
+ <i data-feather="git-branch"></i>
131
+ </div>
132
+ <div class="node-label">Switch</div>
133
+ </div>
134
+ </div>
135
+ `;
136
+
137
+ this.setupDragEvents();
138
+ }
139
+
140
+ setupDragEvents() {
141
+ const nodeItems = this.shadowRoot.querySelectorAll('.node-item');
142
+
143
+ nodeItems.forEach(node => {
144
+ node.addEventListener('dragstart', (event) => {
145
+ event.dataTransfer.setData('application/reactflow', node.dataset.type);
146
+ event.dataTransfer.setData('application/reactflow-label', node.dataset.label);
147
+ event.dataTransfer.effectAllowed = 'move';
148
+ });
149
+ });
150
+ }
151
+ }
152
+
153
+ customElements.define('custom-sidebar', CustomSidebar);
index.html CHANGED
@@ -1,19 +1,44 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Phoenix Flow Builder</title>
7
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
8
+ <link rel="stylesheet" href="style.css">
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <script>
11
+ tailwind.config = {
12
+ theme: {
13
+ extend: {
14
+ colors: {
15
+ primary: {
16
+ 50: '#fff7ed',
17
+ 100: '#ffedd5',
18
+ 200: '#fed7aa',
19
+ 300: '#fdba74',
20
+ 400: '#fb923c',
21
+ 500: '#f97316',
22
+ 600: '#ea580c',
23
+ 700: '#c2410c',
24
+ 800: '#9a3412',
25
+ 900: '#7c2d12',
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ </script>
32
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
33
+ <script src="https://unpkg.com/feather-icons"></script>
34
+ </head>
35
+ <body class="bg-gray-900 text-gray-100">
36
+ <div id="app" class="h-screen"></div>
37
+ <script src="components/sidebar.js"></script>
38
+ <script src="components/custom-node.js"></script>
39
+ <script src="components/floating-overlay.js"></script>
40
+ <script src="script.js"></script>
41
+ <script>feather.replace();</script>
42
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
43
+ </body>
44
+ </html>
script.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Main application logic will go here
2
+ document.addEventListener('DOMContentLoaded', () => {
3
+ // Initialize the app
4
+ console.log('Phoenix Flow Builder initialized');
5
+
6
+ // Dark mode by default
7
+ document.documentElement.classList.add('dark');
8
+ });
9
+
10
+ // Constants for initial nodes and edges
11
+ const INITIAL_NODES = [];
12
+ const INITIAL_EDGES = [];
13
+
14
+ // Custom node types
15
+ const NodeType = {
16
+ ACTION: 'action',
17
+ TRIGGER: 'trigger',
18
+ CONDITION: 'condition',
19
+ };
20
+
21
+ // Export them for use in components
22
+ window.FlowConstants = {
23
+ INITIAL_NODES,
24
+ INITIAL_EDGES,
25
+ NodeType
26
+ };
style.css CHANGED
@@ -1,28 +1,50 @@
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
 
 
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
28
  }
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
2
+
3
  body {
4
+ font-family: 'Inter', sans-serif;
5
+ overflow: hidden;
6
+ }
7
+
8
+ .react-flow__node-custom {
9
+ background: rgba(15, 23, 42, 0.8);
10
+ border: 1px solid rgba(71, 85, 105, 0.5);
11
+ border-radius: 0.5rem;
12
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
13
+ transition: all 0.2s ease;
14
+ }
15
+
16
+ .react-flow__node-custom:hover {
17
+ border-color: #fb923c;
18
+ box-shadow: 0 10px 15px -3px rgba(251, 146, 60, 0.1), 0 4px 6px -2px rgba(251, 146, 60, 0.05);
19
+ }
20
+
21
+ .react-flow__edge-path {
22
+ stroke: #64748b;
23
+ stroke-width: 2;
24
  }
25
 
26
+ .react-flow__controls {
27
+ background: rgba(15, 23, 42, 0.8) !important;
28
+ border: 1px solid rgba(71, 85, 105, 0.5) !important;
29
+ border-radius: 0.5rem !important;
30
+ box-shadow: none !important;
31
  }
32
 
33
+ .react-flow__controls-button {
34
+ background: transparent !important;
35
+ border-bottom: 1px solid rgba(71, 85, 105, 0.5) !important;
36
+ color: #94a3b8 !important;
 
37
  }
38
 
39
+ .react-flow__controls-button:hover {
40
+ background: rgba(251, 146, 60, 0.1) !important;
41
+ color: #fb923c !important;
 
 
 
42
  }
43
 
44
+ .react-flow__controls-button svg {
45
+ fill: currentColor !important;
46
  }
47
+
48
+ .react-flow__background {
49
+ background-color: #0f172a !important;
50
+ }