- Persistence: Added
zustand/persistto save work across reloads. - Network: Added "Proxy Mode" to bypass CORS restrictions during demos.
- Sandbox: Replaced
withsyntax withnew Function(...args)to support React Strict Mode.
Goal: Build project skeleton, implement canvas drag & drop, and ensure data survives page reloads.
-
1.1 Environment Setup (1h)
- Initialize Vite + React + TypeScript.
- Install dependencies:
@xyflow/react,zustand,immer,lucide-react. - Configure Tailwind CSS.
-
1.2 State Management & Persistence (2h)
- Principles: Understand Zustand
set/get. - Implementation: Create
store.ts. Definenodes,edges,onNodesChange,onConnect. -
⚠️ [NEW] Persistence Integration:- Import
persistfromzustand/middleware. - Wrap store config to save to LocalStorage (key:
edgeflow-storage). - Test: Refresh page -> Canvas state should remain.
- Import
- Principles: Understand Zustand
-
1.3 Custom Node UI (3h)
- Create
AgentNode.tsxwith "Left-In, Right-Out" Handle layout. - Style: Icon, Title, Status Indicator (Loading/Success/Error).
- Register in
nodeTypes.
- Create
Goal: "Configuration is UI". Click node -> Open Drawer -> Generate Form based on JSON Schema.
-
2.1 Sidebar Drawer Logic (2h)
- Import
shadcn/uiSheet component. - Logic: Click node -> Set
selectedNodeId-> Open Sheet.
- Import
-
2.2 JSON Schema Registry (2h)
- Create
nodeRegistry.ts. - Define "HTTP Request" Node (inputs: URL, Method, Body, UseProxy).
- Define "Code" Node (inputs: Javascript Code).
- [NEW] Define "AI Agent" Node (inputs: Model, Prompt, API Key).
- Create
-
2.3 Dynamic Form Rendering (4h)
- Use
react-hook-form. - Create
ParameterRendercomponent. - Sync form changes back to Store Node Data (
updateNodeData).
- Use
Goal: Execute logic in Main Thread first, handling real network requests.
-
3.1 Topological Sort (3h)
- Implement
getExecutionSequence(nodes, edges)using BFS/Kahn's Algorithm.
- Implement
-
3.2 Execution Engine with Proxy Strategy (3h)
- Implement
runWorkflowfunction. -
⚠️ [NEW] HTTP Node Implementation:- Add logic: Check
node.data.useProxy. - If true: Prepend
https://cors-anywhere.herokuapp.com/to target URL. - If false: Fetch directly (Direct Mode).
- Add logic: Check
- Store results in
executionResults.
- Implement
-
3.3 Visual Feedback (2h)
- Update Node UI based on status (Yellow -> Green/Red).
Goal: Transform into a professional, non-blocking engine.
Goal: Offload execution to Worker to keep UI at 60FPS.
-
4.1 Web Worker Setup (2h)
- Create
workflow.worker.ts. - Test basic Ping/Pong message.
- Create
-
4.2 Comlink Integration (2h)
- Install
comlink. - Expose Worker functions as RPC Promise objects.
- Install
-
4.3 Engine Migration (4h)
- Move
runWorkflowand Topo Sort logic into Worker. - Main Thread: Send Graph JSON -> Await Result.
- Move
Goal: Support {{ $node["A"].data }} via static analysis.
-
5.1 AST Setup (2h)
- Install
acorn. - Test parsing simple JS expressions in console.
- Install
-
5.2 Dependency Extraction (3h)
- Implement
extractDependencies(code)using Acorn. - Walk the AST tree to find
MemberExpression(e.g.,$node.Http).
- Implement
-
5.3 Data Injection Logic (3h)
- In Worker: Before running a node, parse its code.
- Find dependencies -> Fetch data from
executionResults-> Inject into context.
Goal: Execute user code safely without using with (to support React Strict Mode).
-
6.1 Scope Preparation (2h)
- Define the context object:
const context = { $node: ..., utils: ..., console: ... }. - Extract keys and values:
Object.keys(context),Object.values(context).
- Define the context object:
-
6.2 Safe Execution Implementation (4h)
-
⚠️ [NEW] Replacewithlogic:// Create function with explicit arguments const safeFn = new Function(...scopeKeys, `return ${userCode}`); // Execute with actual values try { const result = safeFn(...scopeValues); } catch (e) { throw new Error(`Execution failed: ${e.message}`); }
-
Verify: Ensure variables defined in
contextare accessible in user code.
-
-
6.3 Security Hardening (Optional)
- Shadow dangerous globals in the Worker scope (e.g.,
self.fetch = nullif needed).
- Shadow dangerous globals in the Worker scope (e.g.,
Goal: High-end UX and Interview Prep.
-
7.1 Monaco Editor Integration (3h)
- Replace standard Textarea with Monaco Editor for Code Nodes.
- (Bonus) Add syntax highlighting.
-
7.2 Data Inspector (2h)
- Hover/Click a node after run to see Input/Output JSON in a nice viewer.
-
7.3 Resume & Demo Assets (3h)
- Record GIF: Drag Node -> Config (Proxy On) -> Connect -> Run -> Result.
- Update README with "Off-Main-Thread", "AST", "Strict Mode Sandbox".
Goal: Implement fully functional AI Agent node with LLM integration, secure storage, and multi-turn conversations.
-
8.1 Type System Extension (30min)
- Extend
ParameterTypewith'ai-model' | 'ai-template' | 'skill-source' - Define
AIAgentNodeDatainterface:- Basic config: model, provider, apiKey, temperature, maxTokens
- Prompt config: systemMessage, prompt
- Advanced toggles: enableStream, enableFunctionCalling, enableMultiTurn
- SKILL config: skillSources array
- Conversation config: conversationId, maxHistoryLength
- Define
LLMMessage,LLMResponse,FunctionCall,SkillSourceinterfaces - Update
WorkflowNodeDataunion type
- Extend
-
8.2 Node Registry Configuration (30min)
- Configure complete
ai-agentparameters innodeRegistry.ts:- Node name, provider selector, model selector
- API Key input (with encryption hint)
- System Message (code type)
- User Prompt (code type, support
{{ $node["XXX"].data }}syntax) - Temperature & Max Tokens parameters
- Advanced feature toggles
- Max history length setting
- Configure multiple handles (main-input/output, context-input, tool-output)
- Configure complete
-
8.3 Encryption Service (1h)
- Create
src/workers/services/crypto.service.ts - Implement
CryptoServiceclass with Web Crypto API:-
encrypt(plaintext, password?): AES-GCM 256-bit, PBKDF2 key derivation -
decrypt(ciphertext, password?): Decrypt and return plaintext -
getDefaultKey(): localStorage-based key persistence
-
- Error handling: return null on decryption failure
- Create
-
8.4 LLM Service Implementation (2h)
- Create
src/workers/services/llm.service.ts - Define
LLMChatParamsandLLMStreamParamsinterfaces - Implement
chat(params): Route to OpenAI or Anthropic - Implement
chatOpenAI(params):- Build request body (model, messages, temperature, max_tokens)
- Fetch
https://api.openai.com/v1/chat/completions - Parse response and return
LLMResponse
- Implement
chatAnthropic(params):- Extract system message
- Fetch
https://api.anthropic.com/v1/messages - Parse Claude format response
- Error handling: unified error format with status code
- Create
-
8.5 Worker Execution Engine Integration (1.5h)
- Import services:
LLMService,CryptoService,ConversationService - Instantiate services in
WorkflowEngineclass - Implement private method
executeAIAgent(...):- Decrypt API Key
- Call
injectVariablesto inject data into Prompt - Build messages array (system + history + user)
- Call
llmService.chat - Save to conversation history if multi-turn enabled
- Return
LLMResponse
- Implement
injectVariables(template, contextNodeData):- Regex replace
{{ $node["XXX"].data }} - Inject data as JSON string
- Regex replace
- Add
ai-agentnode handling inrunWorkflowmethod
- Import services:
-
8.6 Conversation Storage Service (30min)
- Create
src/workers/services/conversation.service.ts - Define
Conversationinterface (id, createdAt, updatedAt, messages) - Implement
ConversationServiceclass (memory + localStorage):-
createConversation(): Generate UUID, init empty messages -
getConversation(id): Retrieve from Map -
addMessage(conversationId, message): Add and update timestamp -
getRecentMessages(conversationId, maxCount): Get last N messages -
deleteConversation(id): Remove conversation -
loadFromStorage()/saveToStorage(): Persistence
-
- Create
Goal: Add streaming output, function calling, SKILL plugin system, and upgrade to IndexedDB.
-
9.1 Streaming Output (1.5h)
- Implement
streamChat(params)inllm.service.ts - Implement
streamOpenAI(params):- Add
stream: trueto request body - Use
response.body?.getReader()to read stream - Parse SSE format (
data: {...}) - Accumulate content and call
onChunk(delta)callback - Return complete
LLMResponse
- Add
- Implement
streamAnthropic(params): Parse Claude SSE format - Error handling: fallback to non-streaming on failure
- Implement
-
9.2 Worker Callback Extension (30min)
- Extend
WorkerCallbacksinterface:- Add
onStreamChunk?(nodeId, chunk)optional callback
- Add
- Update
executeAIAgent:- Pass
onChunkcallback in streaming mode - Call
callbacks.onStreamChunk?.(nodeId, chunk)
- Pass
- Extend
-
9.3 IndexedDB Storage Upgrade (1h)
- Install dependency:
npm install idb - Create
src/utils/indexeddb.ts - Define
EdgeFlowDBschema:-
conversationsstore (id index + updatedAt index) -
apiKeysstore (id index)
-
- Implement
IndexedDBServiceclass:-
init(): Initialize withopenDB -
saveConversation(conversation) -
getConversation(id) -
getAllConversations() -
deleteConversation(id)
-
- Replace localStorage with IndexedDB in
ConversationService
- Install dependency:
-
9.4 Function Calling Implementation (1.5h)
- Create
src/workers/services/function-calling.service.ts - Define
FunctionDefinitionandFunctionExecutionResultinterfaces - Implement
FunctionCallingServiceclass:-
registerFunction(name, fn, definition): Register tool function -
getFunctionDefinitions(): Get all definitions for LLM -
executeFunctionCall(name, arguments_): Execute and return result -
handleLLMFunctionCall(functionCall): Process LLM tool call
-
- Integrate in
LLMService:- Add
functions?optional parameter tochat() - Include
toolsfield in request body - Detect
finish_reason: 'function_call'ortool_calls - Return
functionCallfield in response
- Add
- Update
WorkflowEngine:- Detect
functionCallin response - Call
FunctionCallingServiceto execute - Append result to messages and call LLM again
- Detect
- Create
-
9.5 SKILL Plugin System (1.5h)
- Create
src/workers/services/skill-loader.service.ts - Define
Skillinterface (name, version, description, execute) - Implement
SkillLoaderServiceclass:-
loadFromGitHub(url): Fetch raw code and eval -
loadFromNPM(packageName): Dynamic import via esm.sh CDN -
evalSkill(code): Sandbox withnew Function, validate interface -
getSkill(name): Retrieve loaded skill -
executeSkill(name, input): Execute skill
-
- Integrate in
WorkflowEngine:- Preload configured SKILLS on startup
- Register SKILLS as Function Calling tools
- Unified execution entry point
- Create
-
9.6 Prompt Template Management (30min)
- Create
src/utils/prompt-templates.ts - Define
PromptTemplateinterface - Define
PROMPT_TEMPLATESconstant array:- Text Summarization
- Code Review
- Translation
- JSON Transformation
- Blog Generation
- Implement
PromptTemplateServiceclass:-
getAllTemplates() -
getTemplate(id) -
addTemplate(template) -
removeTemplate(id)
-
- Add template selector in
nodeRegistry.ts
- Create
-
9.7 UI Component Updates (Optional)
- Extend
ParameterRendercomponent:-
ai-templatetype: Dropdown for template selection -
skill-sourcetype: SKILL source configuration form
-
- Add template auto-fill logic
- Add streaming output UI display
- Extend
# Initialize
git init
git add .
git commit -m "Initial: Project scaffold with detailed plan"
# Create Repo on GitHub called 'edgeflow'
git remote add origin https://github.com/YOUR_USER/edgeflow.git
git push -u origin main