Spaces:
Running
Running
owenkaplinsky
commited on
Commit
·
ab1ed73
1
Parent(s):
c8dc711
Fix variable errors
Browse files- project/src/generators/chat.js +24 -24
- project/src/generators/python.js +6 -6
- project/src/index.html +25 -13
- project/src/index.js +125 -156
project/src/generators/chat.js
CHANGED
|
@@ -53,7 +53,7 @@ forBlock['create_mcp'] = function (block, generator) {
|
|
| 53 |
for (let r = 0; r < block.outputCount_; r++) {
|
| 54 |
const outputName = (block.outputNames_ && block.outputNames_[r]) || `result${r}`;
|
| 55 |
let returnValue = generator.valueToCode(block, 'R' + r, chatGenerator.ORDER_ATOMIC) || 'None';
|
| 56 |
-
|
| 57 |
// Replace placeholder args with actual names
|
| 58 |
if (returnValue && block.inputNames_) {
|
| 59 |
for (let j = 0; j < block.inputNames_.length; j++) {
|
|
@@ -61,7 +61,7 @@ forBlock['create_mcp'] = function (block, generator) {
|
|
| 61 |
returnValue = returnValue.replace(new RegExp(`arg${j}\\b`, 'g'), paramName);
|
| 62 |
}
|
| 63 |
}
|
| 64 |
-
|
| 65 |
outputParams.push(`${outputName}: ${returnValue}`);
|
| 66 |
}
|
| 67 |
}
|
|
@@ -103,7 +103,7 @@ forBlock['func_def'] = function (block, generator) {
|
|
| 103 |
for (let r = 0; r < block.outputCount_; r++) {
|
| 104 |
const outputName = (block.outputNames_ && block.outputNames_[r]) || `output${r}`;
|
| 105 |
let returnValue = generator.valueToCode(block, 'R' + r, chatGenerator.ORDER_ATOMIC) || 'None';
|
| 106 |
-
|
| 107 |
// Replace placeholder args with actual names
|
| 108 |
if (returnValue && block.inputNames_) {
|
| 109 |
for (let j = 0; j < block.inputNames_.length; j++) {
|
|
@@ -111,7 +111,7 @@ forBlock['func_def'] = function (block, generator) {
|
|
| 111 |
returnValue = returnValue.replace(new RegExp(`arg${j}\\b`, 'g'), paramName);
|
| 112 |
}
|
| 113 |
}
|
| 114 |
-
|
| 115 |
outputParams.push(`${outputName}: ${returnValue}`);
|
| 116 |
}
|
| 117 |
}
|
|
@@ -132,10 +132,10 @@ forBlock['func_def'] = function (block, generator) {
|
|
| 132 |
};
|
| 133 |
|
| 134 |
// Handler for input reference blocks
|
| 135 |
-
forBlock['input_reference'] = function(block, generator) {
|
| 136 |
-
const varName = block.getFieldValue('VARNAME') ||
|
| 137 |
-
|
| 138 |
-
|
| 139 |
// Value blocks must return a tuple: [code, order]
|
| 140 |
return [varName, chatGenerator.ORDER_ATOMIC];
|
| 141 |
};
|
|
@@ -144,7 +144,7 @@ forBlock['input_reference'] = function(block, generator) {
|
|
| 144 |
Object.assign(chatGenerator.forBlock, forBlock);
|
| 145 |
|
| 146 |
// Override workspaceToCode to include standalone value blocks
|
| 147 |
-
chatGenerator.workspaceToCode = function(workspace) {
|
| 148 |
if (!workspace) {
|
| 149 |
// Backwards compatibility from before there could be multiple workspaces.
|
| 150 |
console.warn('No workspace specified in workspaceToCode call. Guessing.');
|
|
@@ -189,21 +189,21 @@ chatGenerator.workspaceToCode = function(workspace) {
|
|
| 189 |
|
| 190 |
// Override blockToCode to provide a catch-all handler
|
| 191 |
const originalBlockToCode = chatGenerator.blockToCode.bind(chatGenerator);
|
| 192 |
-
chatGenerator.blockToCode = function(block, opt_thisOnly) {
|
| 193 |
// Null check
|
| 194 |
if (!block) {
|
| 195 |
return '';
|
| 196 |
}
|
| 197 |
-
|
| 198 |
// Check if it's an input reference block type
|
| 199 |
if (block.type.startsWith('input_reference_')) {
|
| 200 |
-
const varName = block.getFieldValue('VARNAME') ||
|
| 201 |
-
|
| 202 |
-
|
| 203 |
// Value blocks must return a tuple: [code, order]
|
| 204 |
return [varName, this.ORDER_ATOMIC];
|
| 205 |
}
|
| 206 |
-
|
| 207 |
// Try the normal generation first
|
| 208 |
try {
|
| 209 |
return originalBlockToCode(block, opt_thisOnly);
|
|
@@ -211,7 +211,7 @@ chatGenerator.blockToCode = function(block, opt_thisOnly) {
|
|
| 211 |
// Catch-all handler for blocks without specific generators
|
| 212 |
const blockType = block.type;
|
| 213 |
const inputs = [];
|
| 214 |
-
|
| 215 |
// Special handling for common blocks with field values
|
| 216 |
if (blockType === 'text') {
|
| 217 |
const text = block.getFieldValue('TEXT');
|
|
@@ -243,23 +243,23 @@ chatGenerator.blockToCode = function(block, opt_thisOnly) {
|
|
| 243 |
}
|
| 244 |
}
|
| 245 |
}
|
| 246 |
-
|
| 247 |
// Then get all value inputs (connected blocks)
|
| 248 |
const inputList = block.inputList || [];
|
| 249 |
for (const input of inputList) {
|
| 250 |
if (input.type === Blockly.INPUT_VALUE && input.connection) {
|
| 251 |
const inputName = input.name;
|
| 252 |
const inputValue = this.valueToCode(block, inputName, this.ORDER_ATOMIC);
|
| 253 |
-
|
| 254 |
if (inputValue) {
|
| 255 |
inputs.push(`${inputName}: ${inputValue}`);
|
| 256 |
}
|
| 257 |
}
|
| 258 |
}
|
| 259 |
-
|
| 260 |
// Generate the standard format: name(inputs(...)) with block ID and pipe separator
|
| 261 |
const code = `${block.id} | ${blockType}(inputs(${inputs.join(', ')}))`;
|
| 262 |
-
|
| 263 |
// Handle statement inputs (for blocks that have a body)
|
| 264 |
let statements = '';
|
| 265 |
for (const input of inputList) {
|
|
@@ -270,13 +270,13 @@ chatGenerator.blockToCode = function(block, opt_thisOnly) {
|
|
| 270 |
}
|
| 271 |
}
|
| 272 |
}
|
| 273 |
-
|
| 274 |
// Return appropriate format based on whether it's a value or statement block
|
| 275 |
if (block.outputConnection) {
|
| 276 |
// This is a value block (can be plugged into inputs)
|
| 277 |
// Check if this block is connected to another block's input
|
| 278 |
const isConnectedToInput = block.outputConnection && block.outputConnection.isConnected();
|
| 279 |
-
|
| 280 |
if (isConnectedToInput) {
|
| 281 |
// When used as input to another block, don't include the ID
|
| 282 |
const valueCode = `${blockType}(inputs(${inputs.join(', ')}))`;
|
|
@@ -291,13 +291,13 @@ chatGenerator.blockToCode = function(block, opt_thisOnly) {
|
|
| 291 |
} else {
|
| 292 |
// This is a statement block (has prev/next connections)
|
| 293 |
const fullCode = code + (statements ? '\n' + statements : '');
|
| 294 |
-
|
| 295 |
// Handle the next block in the sequence if not opt_thisOnly
|
| 296 |
if (!opt_thisOnly) {
|
| 297 |
const nextCode = this.scrub_(block, fullCode, opt_thisOnly);
|
| 298 |
return nextCode;
|
| 299 |
}
|
| 300 |
-
|
| 301 |
return fullCode + '\n';
|
| 302 |
}
|
| 303 |
}
|
|
|
|
| 53 |
for (let r = 0; r < block.outputCount_; r++) {
|
| 54 |
const outputName = (block.outputNames_ && block.outputNames_[r]) || `result${r}`;
|
| 55 |
let returnValue = generator.valueToCode(block, 'R' + r, chatGenerator.ORDER_ATOMIC) || 'None';
|
| 56 |
+
|
| 57 |
// Replace placeholder args with actual names
|
| 58 |
if (returnValue && block.inputNames_) {
|
| 59 |
for (let j = 0; j < block.inputNames_.length; j++) {
|
|
|
|
| 61 |
returnValue = returnValue.replace(new RegExp(`arg${j}\\b`, 'g'), paramName);
|
| 62 |
}
|
| 63 |
}
|
| 64 |
+
|
| 65 |
outputParams.push(`${outputName}: ${returnValue}`);
|
| 66 |
}
|
| 67 |
}
|
|
|
|
| 103 |
for (let r = 0; r < block.outputCount_; r++) {
|
| 104 |
const outputName = (block.outputNames_ && block.outputNames_[r]) || `output${r}`;
|
| 105 |
let returnValue = generator.valueToCode(block, 'R' + r, chatGenerator.ORDER_ATOMIC) || 'None';
|
| 106 |
+
|
| 107 |
// Replace placeholder args with actual names
|
| 108 |
if (returnValue && block.inputNames_) {
|
| 109 |
for (let j = 0; j < block.inputNames_.length; j++) {
|
|
|
|
| 111 |
returnValue = returnValue.replace(new RegExp(`arg${j}\\b`, 'g'), paramName);
|
| 112 |
}
|
| 113 |
}
|
| 114 |
+
|
| 115 |
outputParams.push(`${outputName}: ${returnValue}`);
|
| 116 |
}
|
| 117 |
}
|
|
|
|
| 132 |
};
|
| 133 |
|
| 134 |
// Handler for input reference blocks
|
| 135 |
+
forBlock['input_reference'] = function (block, generator) {
|
| 136 |
+
const varName = block.getFieldValue('VARNAME') ||
|
| 137 |
+
block.type.replace('input_reference_', '') ||
|
| 138 |
+
'unnamed_arg';
|
| 139 |
// Value blocks must return a tuple: [code, order]
|
| 140 |
return [varName, chatGenerator.ORDER_ATOMIC];
|
| 141 |
};
|
|
|
|
| 144 |
Object.assign(chatGenerator.forBlock, forBlock);
|
| 145 |
|
| 146 |
// Override workspaceToCode to include standalone value blocks
|
| 147 |
+
chatGenerator.workspaceToCode = function (workspace) {
|
| 148 |
if (!workspace) {
|
| 149 |
// Backwards compatibility from before there could be multiple workspaces.
|
| 150 |
console.warn('No workspace specified in workspaceToCode call. Guessing.');
|
|
|
|
| 189 |
|
| 190 |
// Override blockToCode to provide a catch-all handler
|
| 191 |
const originalBlockToCode = chatGenerator.blockToCode.bind(chatGenerator);
|
| 192 |
+
chatGenerator.blockToCode = function (block, opt_thisOnly) {
|
| 193 |
// Null check
|
| 194 |
if (!block) {
|
| 195 |
return '';
|
| 196 |
}
|
| 197 |
+
|
| 198 |
// Check if it's an input reference block type
|
| 199 |
if (block.type.startsWith('input_reference_')) {
|
| 200 |
+
const varName = block.getFieldValue('VARNAME') ||
|
| 201 |
+
block.type.replace('input_reference_', '') ||
|
| 202 |
+
'unnamed_arg';
|
| 203 |
// Value blocks must return a tuple: [code, order]
|
| 204 |
return [varName, this.ORDER_ATOMIC];
|
| 205 |
}
|
| 206 |
+
|
| 207 |
// Try the normal generation first
|
| 208 |
try {
|
| 209 |
return originalBlockToCode(block, opt_thisOnly);
|
|
|
|
| 211 |
// Catch-all handler for blocks without specific generators
|
| 212 |
const blockType = block.type;
|
| 213 |
const inputs = [];
|
| 214 |
+
|
| 215 |
// Special handling for common blocks with field values
|
| 216 |
if (blockType === 'text') {
|
| 217 |
const text = block.getFieldValue('TEXT');
|
|
|
|
| 243 |
}
|
| 244 |
}
|
| 245 |
}
|
| 246 |
+
|
| 247 |
// Then get all value inputs (connected blocks)
|
| 248 |
const inputList = block.inputList || [];
|
| 249 |
for (const input of inputList) {
|
| 250 |
if (input.type === Blockly.INPUT_VALUE && input.connection) {
|
| 251 |
const inputName = input.name;
|
| 252 |
const inputValue = this.valueToCode(block, inputName, this.ORDER_ATOMIC);
|
| 253 |
+
|
| 254 |
if (inputValue) {
|
| 255 |
inputs.push(`${inputName}: ${inputValue}`);
|
| 256 |
}
|
| 257 |
}
|
| 258 |
}
|
| 259 |
+
|
| 260 |
// Generate the standard format: name(inputs(...)) with block ID and pipe separator
|
| 261 |
const code = `${block.id} | ${blockType}(inputs(${inputs.join(', ')}))`;
|
| 262 |
+
|
| 263 |
// Handle statement inputs (for blocks that have a body)
|
| 264 |
let statements = '';
|
| 265 |
for (const input of inputList) {
|
|
|
|
| 270 |
}
|
| 271 |
}
|
| 272 |
}
|
| 273 |
+
|
| 274 |
// Return appropriate format based on whether it's a value or statement block
|
| 275 |
if (block.outputConnection) {
|
| 276 |
// This is a value block (can be plugged into inputs)
|
| 277 |
// Check if this block is connected to another block's input
|
| 278 |
const isConnectedToInput = block.outputConnection && block.outputConnection.isConnected();
|
| 279 |
+
|
| 280 |
if (isConnectedToInput) {
|
| 281 |
// When used as input to another block, don't include the ID
|
| 282 |
const valueCode = `${blockType}(inputs(${inputs.join(', ')}))`;
|
|
|
|
| 291 |
} else {
|
| 292 |
// This is a statement block (has prev/next connections)
|
| 293 |
const fullCode = code + (statements ? '\n' + statements : '');
|
| 294 |
+
|
| 295 |
// Handle the next block in the sequence if not opt_thisOnly
|
| 296 |
if (!opt_thisOnly) {
|
| 297 |
const nextCode = this.scrub_(block, fullCode, opt_thisOnly);
|
| 298 |
return nextCode;
|
| 299 |
}
|
| 300 |
+
|
| 301 |
return fullCode + '\n';
|
| 302 |
}
|
| 303 |
}
|
project/src/generators/python.js
CHANGED
|
@@ -8,14 +8,14 @@ forBlock['create_mcp'] = function (block, generator) {
|
|
| 8 |
if (!generator.nameDB_) {
|
| 9 |
generator.nameDB_ = new Blockly.Names(generator.RESERVED_WORDS_ || []);
|
| 10 |
}
|
| 11 |
-
|
| 12 |
// Ensure getDistinctName is available for control flow blocks
|
| 13 |
if (!generator.getDistinctName) {
|
| 14 |
-
generator.getDistinctName = function(name, type) {
|
| 15 |
return this.nameDB_.getDistinctName(name, type);
|
| 16 |
};
|
| 17 |
}
|
| 18 |
-
|
| 19 |
const typedInputs = [];
|
| 20 |
let i = 0;
|
| 21 |
|
|
@@ -155,14 +155,14 @@ forBlock['func_def'] = function (block, generator) {
|
|
| 155 |
if (!generator.nameDB_) {
|
| 156 |
generator.nameDB_ = new Blockly.Names(generator.RESERVED_WORDS_ || []);
|
| 157 |
}
|
| 158 |
-
|
| 159 |
// Ensure getDistinctName is available for control flow blocks
|
| 160 |
if (!generator.getDistinctName) {
|
| 161 |
-
generator.getDistinctName = function(name, type) {
|
| 162 |
return this.nameDB_.getDistinctName(name, type);
|
| 163 |
};
|
| 164 |
}
|
| 165 |
-
|
| 166 |
const name = block.getFieldValue('NAME');
|
| 167 |
const typedInputs = [];
|
| 168 |
let i = 0;
|
|
|
|
| 8 |
if (!generator.nameDB_) {
|
| 9 |
generator.nameDB_ = new Blockly.Names(generator.RESERVED_WORDS_ || []);
|
| 10 |
}
|
| 11 |
+
|
| 12 |
// Ensure getDistinctName is available for control flow blocks
|
| 13 |
if (!generator.getDistinctName) {
|
| 14 |
+
generator.getDistinctName = function (name, type) {
|
| 15 |
return this.nameDB_.getDistinctName(name, type);
|
| 16 |
};
|
| 17 |
}
|
| 18 |
+
|
| 19 |
const typedInputs = [];
|
| 20 |
let i = 0;
|
| 21 |
|
|
|
|
| 155 |
if (!generator.nameDB_) {
|
| 156 |
generator.nameDB_ = new Blockly.Names(generator.RESERVED_WORDS_ || []);
|
| 157 |
}
|
| 158 |
+
|
| 159 |
// Ensure getDistinctName is available for control flow blocks
|
| 160 |
if (!generator.getDistinctName) {
|
| 161 |
+
generator.getDistinctName = function (name, type) {
|
| 162 |
return this.nameDB_.getDistinctName(name, type);
|
| 163 |
};
|
| 164 |
}
|
| 165 |
+
|
| 166 |
const name = block.getFieldValue('NAME');
|
| 167 |
const typedInputs = [];
|
| 168 |
let i = 0;
|
project/src/index.html
CHANGED
|
@@ -3,13 +3,13 @@
|
|
| 3 |
|
| 4 |
<head>
|
| 5 |
<meta charset="utf-8" />
|
| 6 |
-
<title>
|
| 7 |
</head>
|
| 8 |
|
| 9 |
<body>
|
| 10 |
<div id="topBar">
|
| 11 |
<div id="titleSection">
|
| 12 |
-
<h1>
|
| 13 |
</div>
|
| 14 |
<div id="divider"></div>
|
| 15 |
<div id="menuSection">
|
|
@@ -193,22 +193,34 @@
|
|
| 193 |
</script>
|
| 194 |
|
| 195 |
<!-- Keys Modal -->
|
| 196 |
-
<div id="apiKeyModal"
|
| 197 |
-
|
|
|
|
|
|
|
| 198 |
<h2 style="margin-top: 0; margin-bottom: 20px; color: #333;">API Keys</h2>
|
| 199 |
-
|
| 200 |
-
<label for="apiKeyInput"
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
<p style="margin: 5px 0 15px 0; color: #999; font-size: 12px;">For the AI assistant and blocks' model calls.</p>
|
| 203 |
-
|
| 204 |
-
<label for="hfKeyInput"
|
| 205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
<p style="margin: 5px 0 20px 0; color: #999; font-size: 12px;">For deploying your MCP server.</p>
|
| 207 |
-
|
| 208 |
<p style="color: #999; font-size: 12px;">Your API keys will be stored securely for this session.</p>
|
| 209 |
<div style="margin-top: 20px; display: flex; justify-content: flex-end; gap: 10px;">
|
| 210 |
-
<button id="cancelApiKey"
|
| 211 |
-
|
|
|
|
|
|
|
| 212 |
</div>
|
| 213 |
</div>
|
| 214 |
</div>
|
|
|
|
| 3 |
|
| 4 |
<head>
|
| 5 |
<meta charset="utf-8" />
|
| 6 |
+
<title>MCP Blockly</title>
|
| 7 |
</head>
|
| 8 |
|
| 9 |
<body>
|
| 10 |
<div id="topBar">
|
| 11 |
<div id="titleSection">
|
| 12 |
+
<h1>MCP Blockly</h1>
|
| 13 |
</div>
|
| 14 |
<div id="divider"></div>
|
| 15 |
<div id="menuSection">
|
|
|
|
| 193 |
</script>
|
| 194 |
|
| 195 |
<!-- Keys Modal -->
|
| 196 |
+
<div id="apiKeyModal"
|
| 197 |
+
style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 9999; align-items: center; justify-content: center;">
|
| 198 |
+
<div
|
| 199 |
+
style="background: white; padding: 30px; border-radius: 10px; width: 90%; max-width: 500px; box-shadow: 0 10px 30px rgba(0,0,0,0.2);">
|
| 200 |
<h2 style="margin-top: 0; margin-bottom: 20px; color: #333;">API Keys</h2>
|
| 201 |
+
|
| 202 |
+
<label for="apiKeyInput"
|
| 203 |
+
style="display: block; margin-bottom: 10px; color: #666; font-size: 14px; font-weight: 500;">OpenAI API
|
| 204 |
+
Key:</label>
|
| 205 |
+
<input type="password" id="apiKeyInput"
|
| 206 |
+
style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; box-sizing: border-box; margin-bottom: 5px;"
|
| 207 |
+
placeholder="sk-...">
|
| 208 |
<p style="margin: 5px 0 15px 0; color: #999; font-size: 12px;">For the AI assistant and blocks' model calls.</p>
|
| 209 |
+
|
| 210 |
+
<label for="hfKeyInput"
|
| 211 |
+
style="display: block; margin-bottom: 10px; color: #666; font-size: 14px; font-weight: 500;">Hugging Face API
|
| 212 |
+
Key:</label>
|
| 213 |
+
<input type="password" id="hfKeyInput"
|
| 214 |
+
style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; box-sizing: border-box; margin-bottom: 5px;"
|
| 215 |
+
placeholder="hf_...">
|
| 216 |
<p style="margin: 5px 0 20px 0; color: #999; font-size: 12px;">For deploying your MCP server.</p>
|
| 217 |
+
|
| 218 |
<p style="color: #999; font-size: 12px;">Your API keys will be stored securely for this session.</p>
|
| 219 |
<div style="margin-top: 20px; display: flex; justify-content: flex-end; gap: 10px;">
|
| 220 |
+
<button id="cancelApiKey"
|
| 221 |
+
style="padding: 10px 20px; background: #e5e7eb; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;">Cancel</button>
|
| 222 |
+
<button id="saveApiKey"
|
| 223 |
+
style="padding: 10px 20px; background: #6366f1; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;">Save</button>
|
| 224 |
</div>
|
| 225 |
</div>
|
| 226 |
</div>
|
project/src/index.js
CHANGED
|
@@ -9,37 +9,6 @@ import '@blockly/toolbox-search';
|
|
| 9 |
import DarkTheme from '@blockly/theme-dark';
|
| 10 |
import './index.css';
|
| 11 |
|
| 12 |
-
// Initialize the Python generator's Names database if it doesn't exist
|
| 13 |
-
if (!pythonGenerator.nameDB_) {
|
| 14 |
-
pythonGenerator.init = function(workspace) {
|
| 15 |
-
// Call parent init if it exists
|
| 16 |
-
if (Blockly.Generator.prototype.init) {
|
| 17 |
-
Blockly.Generator.prototype.init.call(this, workspace);
|
| 18 |
-
}
|
| 19 |
-
|
| 20 |
-
// Initialize the Names database for variable name generation
|
| 21 |
-
if (!this.nameDB_) {
|
| 22 |
-
this.nameDB_ = new Blockly.Names(this.RESERVED_WORDS_);
|
| 23 |
-
} else {
|
| 24 |
-
this.nameDB_.reset();
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
// Add reserved Python keywords
|
| 28 |
-
const reservedWords = ['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
|
| 29 |
-
'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
|
| 30 |
-
'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
|
| 31 |
-
'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return',
|
| 32 |
-
'try', 'while', 'with', 'yield'];
|
| 33 |
-
|
| 34 |
-
for (const word of reservedWords) {
|
| 35 |
-
this.nameDB_.setVariableMap(word, word);
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
// Initialize function name database
|
| 39 |
-
this.functionNames_ = this.nameDB_;
|
| 40 |
-
};
|
| 41 |
-
}
|
| 42 |
-
|
| 43 |
// Register the blocks and generator with Blockly
|
| 44 |
Blockly.common.defineBlocks(blocks);
|
| 45 |
Object.assign(pythonGenerator.forBlock, forBlock);
|
|
@@ -132,7 +101,7 @@ downloadCodeButton.addEventListener("click", () => {
|
|
| 132 |
// Get the current generated code
|
| 133 |
const codeEl = document.querySelector('#generatedCode code');
|
| 134 |
const code = codeEl ? codeEl.textContent : '';
|
| 135 |
-
|
| 136 |
if (!code) {
|
| 137 |
alert('No code to download');
|
| 138 |
return;
|
|
@@ -140,7 +109,7 @@ downloadCodeButton.addEventListener("click", () => {
|
|
| 140 |
|
| 141 |
var filename = "app.py";
|
| 142 |
var element = document.createElement('a');
|
| 143 |
-
|
| 144 |
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(code));
|
| 145 |
element.setAttribute('download', filename);
|
| 146 |
element.style.display = 'none';
|
|
@@ -159,19 +128,19 @@ const cancelApiKeyButton = document.querySelector('#cancelApiKey');
|
|
| 159 |
|
| 160 |
settingsButton.addEventListener("click", () => {
|
| 161 |
apiKeyModal.style.display = 'flex';
|
| 162 |
-
|
| 163 |
// Load current API keys from backend
|
| 164 |
fetch("/get_api_key", {
|
| 165 |
method: "GET",
|
| 166 |
})
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
});
|
| 176 |
|
| 177 |
saveApiKeyButton.addEventListener("click", () => {
|
|
@@ -203,19 +172,19 @@ saveApiKeyButton.addEventListener("click", () => {
|
|
| 203 |
body: JSON.stringify({ api_key: apiKey, hf_key: hfKey }),
|
| 204 |
})
|
| 205 |
])
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
});
|
| 220 |
|
| 221 |
cancelApiKeyButton.addEventListener("click", () => {
|
|
@@ -258,14 +227,14 @@ cleanWorkspace.addEventListener("click", () => {
|
|
| 258 |
const setupDeletionStream = () => {
|
| 259 |
const eventSource = new EventSource('/delete_stream');
|
| 260 |
const processedRequests = new Set(); // Track processed deletion requests
|
| 261 |
-
|
| 262 |
eventSource.onmessage = (event) => {
|
| 263 |
try {
|
| 264 |
const data = JSON.parse(event.data);
|
| 265 |
-
|
| 266 |
// Skip heartbeat messages
|
| 267 |
if (data.heartbeat) return;
|
| 268 |
-
|
| 269 |
// Skip if we've already processed this exact request
|
| 270 |
const requestKey = `${data.block_id}_${Date.now()}`;
|
| 271 |
if (data.block_id && processedRequests.has(data.block_id)) {
|
|
@@ -277,15 +246,15 @@ const setupDeletionStream = () => {
|
|
| 277 |
// Clear after 10 seconds to allow retries if needed
|
| 278 |
setTimeout(() => processedRequests.delete(data.block_id), 10000);
|
| 279 |
}
|
| 280 |
-
|
| 281 |
if (data.block_id) {
|
| 282 |
console.log('[SSE] Received deletion request for block:', data.block_id);
|
| 283 |
-
|
| 284 |
// Try to delete the block
|
| 285 |
const block = ws.getBlockById(data.block_id);
|
| 286 |
let success = false;
|
| 287 |
let error = null;
|
| 288 |
-
|
| 289 |
if (block) {
|
| 290 |
console.log('[SSE] Found block to delete:', block.type, block.id);
|
| 291 |
// Check if it's the main create_mcp block (which shouldn't be deleted)
|
|
@@ -306,7 +275,7 @@ const setupDeletionStream = () => {
|
|
| 306 |
error = 'Block not found';
|
| 307 |
console.log('[SSE] Block not found:', data.block_id);
|
| 308 |
}
|
| 309 |
-
|
| 310 |
// Send result back to backend immediately
|
| 311 |
console.log('[SSE] Sending deletion result:', { block_id: data.block_id, success, error });
|
| 312 |
fetch('/deletion_result', {
|
|
@@ -327,7 +296,7 @@ const setupDeletionStream = () => {
|
|
| 327 |
console.error('[SSE] Error processing message:', err);
|
| 328 |
}
|
| 329 |
};
|
| 330 |
-
|
| 331 |
eventSource.onerror = (error) => {
|
| 332 |
console.error('[SSE] Connection error:', error);
|
| 333 |
// Reconnect after 5 seconds
|
|
@@ -336,7 +305,7 @@ const setupDeletionStream = () => {
|
|
| 336 |
setupDeletionStream();
|
| 337 |
}, 5000);
|
| 338 |
};
|
| 339 |
-
|
| 340 |
eventSource.onopen = () => {
|
| 341 |
console.log('[SSE] Connected to deletion stream');
|
| 342 |
};
|
|
@@ -349,14 +318,14 @@ setupDeletionStream();
|
|
| 349 |
const setupCreationStream = () => {
|
| 350 |
const eventSource = new EventSource('/create_stream');
|
| 351 |
const processedRequests = new Set(); // Track processed creation requests
|
| 352 |
-
|
| 353 |
eventSource.onmessage = (event) => {
|
| 354 |
try {
|
| 355 |
const data = JSON.parse(event.data);
|
| 356 |
-
|
| 357 |
// Skip heartbeat messages
|
| 358 |
if (data.heartbeat) return;
|
| 359 |
-
|
| 360 |
// Skip if we've already processed this request
|
| 361 |
if (data.request_id && processedRequests.has(data.request_id)) {
|
| 362 |
console.log('[SSE CREATE] Skipping duplicate creation request:', data.request_id);
|
|
@@ -367,54 +336,54 @@ const setupCreationStream = () => {
|
|
| 367 |
// Clear after 10 seconds to allow retries if needed
|
| 368 |
setTimeout(() => processedRequests.delete(data.request_id), 10000);
|
| 369 |
}
|
| 370 |
-
|
| 371 |
if (data.block_spec && data.request_id) {
|
| 372 |
console.log('[SSE CREATE] Received creation request:', data.request_id, data.block_spec);
|
| 373 |
-
|
| 374 |
let success = false;
|
| 375 |
let error = null;
|
| 376 |
let blockId = null;
|
| 377 |
-
|
| 378 |
try {
|
| 379 |
// Parse and create blocks recursively
|
| 380 |
function parseAndCreateBlock(spec, shouldPosition = false) {
|
| 381 |
// Match block_name(inputs(...))
|
| 382 |
const blockMatch = spec.match(/^(\w+)\s*\((.+)\)$/s);
|
| 383 |
-
|
| 384 |
if (!blockMatch) {
|
| 385 |
throw new Error(`Invalid block specification format: ${spec}`);
|
| 386 |
}
|
| 387 |
-
|
| 388 |
const blockType = blockMatch[1];
|
| 389 |
const content = blockMatch[2].trim();
|
| 390 |
-
|
| 391 |
console.log('[SSE CREATE] Parsing block:', blockType, 'with content:', content);
|
| 392 |
-
|
| 393 |
// Check if this has inputs() wrapper
|
| 394 |
let inputsContent = content;
|
| 395 |
if (content.startsWith('inputs(') && content.endsWith(')')) {
|
| 396 |
inputsContent = content.slice(7, -1); // Remove 'inputs(' and ')'
|
| 397 |
}
|
| 398 |
-
|
| 399 |
// Create the block
|
| 400 |
const newBlock = ws.newBlock(blockType);
|
| 401 |
-
|
| 402 |
if (inputsContent) {
|
| 403 |
// Parse the inputs content
|
| 404 |
const inputs = parseInputs(inputsContent);
|
| 405 |
console.log('[SSE CREATE] Parsed inputs:', inputs);
|
| 406 |
-
|
| 407 |
// Special handling for make_json block
|
| 408 |
if (blockType === 'make_json') {
|
| 409 |
// Count FIELD entries to determine how many fields we need
|
| 410 |
let fieldCount = 0;
|
| 411 |
const fieldValues = {};
|
| 412 |
const keyValues = {};
|
| 413 |
-
|
| 414 |
for (const [key, value] of Object.entries(inputs)) {
|
| 415 |
const fieldMatch = key.match(/^FIELD(\d+)$/);
|
| 416 |
const keyMatch = key.match(/^KEY(\d+)$/);
|
| 417 |
-
|
| 418 |
if (fieldMatch) {
|
| 419 |
const index = parseInt(fieldMatch[1]);
|
| 420 |
fieldCount = Math.max(fieldCount, index + 1);
|
|
@@ -424,21 +393,21 @@ const setupCreationStream = () => {
|
|
| 424 |
keyValues[index] = value;
|
| 425 |
}
|
| 426 |
}
|
| 427 |
-
|
| 428 |
// Set up the mutator state
|
| 429 |
if (fieldCount > 0) {
|
| 430 |
newBlock.fieldCount_ = fieldCount;
|
| 431 |
newBlock.fieldKeys_ = [];
|
| 432 |
-
|
| 433 |
// Create the inputs through the mutator
|
| 434 |
for (let i = 0; i < fieldCount; i++) {
|
| 435 |
const keyValue = keyValues[i];
|
| 436 |
-
const key = (typeof keyValue === 'string' && !keyValue.match(/^\w+\s*\(inputs\(/))
|
| 437 |
-
? keyValue.replace(/^["']|["']$/g, '')
|
| 438 |
: `key${i}`;
|
| 439 |
-
|
| 440 |
newBlock.fieldKeys_[i] = key;
|
| 441 |
-
|
| 442 |
// Create the input
|
| 443 |
const input = newBlock.appendValueInput('FIELD' + i);
|
| 444 |
const field = new Blockly.FieldTextInput(key);
|
|
@@ -449,14 +418,14 @@ const setupCreationStream = () => {
|
|
| 449 |
input.appendField(field, 'KEY' + i);
|
| 450 |
input.appendField(':');
|
| 451 |
}
|
| 452 |
-
|
| 453 |
// Now connect the field values
|
| 454 |
for (let i = 0; i < fieldCount; i++) {
|
| 455 |
const value = fieldValues[i];
|
| 456 |
if (value && typeof value === 'string' && value.match(/^\w+\s*\(inputs\(/)) {
|
| 457 |
// This is a nested block, create it recursively
|
| 458 |
const childBlock = parseAndCreateBlock(value);
|
| 459 |
-
|
| 460 |
// Connect the child block to the FIELD input
|
| 461 |
const input = newBlock.getInput('FIELD' + i);
|
| 462 |
if (input && input.connection && childBlock.outputConnection) {
|
|
@@ -473,7 +442,7 @@ const setupCreationStream = () => {
|
|
| 473 |
if (value.match(/^\w+\s*\(inputs\(/)) {
|
| 474 |
// This is a nested block, create it recursively
|
| 475 |
const childBlock = parseAndCreateBlock(value);
|
| 476 |
-
|
| 477 |
// Connect the child block to the appropriate input
|
| 478 |
const input = newBlock.getInput(key);
|
| 479 |
if (input && input.connection && childBlock.outputConnection) {
|
|
@@ -483,7 +452,7 @@ const setupCreationStream = () => {
|
|
| 483 |
// This is a simple value, set it as a field
|
| 484 |
// Remove quotes if present
|
| 485 |
const cleanValue = value.replace(/^["']|["']$/g, '');
|
| 486 |
-
|
| 487 |
// Try to set as a field value
|
| 488 |
try {
|
| 489 |
newBlock.setFieldValue(cleanValue, key);
|
|
@@ -509,33 +478,33 @@ const setupCreationStream = () => {
|
|
| 509 |
}
|
| 510 |
}
|
| 511 |
}
|
| 512 |
-
|
| 513 |
// Initialize the block (renders it)
|
| 514 |
newBlock.initSvg();
|
| 515 |
-
|
| 516 |
// Only position the top-level block
|
| 517 |
if (shouldPosition) {
|
| 518 |
// Find a good position that doesn't overlap existing blocks
|
| 519 |
const existingBlocks = ws.getAllBlocks();
|
| 520 |
let x = 50;
|
| 521 |
let y = 50;
|
| 522 |
-
|
| 523 |
// Simple positioning: stack new blocks vertically
|
| 524 |
if (existingBlocks.length > 0) {
|
| 525 |
const lastBlock = existingBlocks[existingBlocks.length - 1];
|
| 526 |
const lastPos = lastBlock.getRelativeToSurfaceXY();
|
| 527 |
y = lastPos.y + lastBlock.height + 20;
|
| 528 |
}
|
| 529 |
-
|
| 530 |
newBlock.moveBy(x, y);
|
| 531 |
}
|
| 532 |
-
|
| 533 |
// Render the block
|
| 534 |
newBlock.render();
|
| 535 |
-
|
| 536 |
return newBlock;
|
| 537 |
}
|
| 538 |
-
|
| 539 |
// Helper function to parse inputs(key: value, key2: value2, ...)
|
| 540 |
function parseInputs(inputStr) {
|
| 541 |
const result = {};
|
|
@@ -545,12 +514,12 @@ const setupCreationStream = () => {
|
|
| 545 |
let inQuotes = false;
|
| 546 |
let quoteChar = '';
|
| 547 |
let readingKey = true;
|
| 548 |
-
|
| 549 |
for (let i = 0; i < inputStr.length; i++) {
|
| 550 |
const char = inputStr[i];
|
| 551 |
-
|
| 552 |
// Handle quotes
|
| 553 |
-
if ((char === '"' || char === "'") && (i === 0 || inputStr[i-1] !== '\\')) {
|
| 554 |
if (!inQuotes) {
|
| 555 |
inQuotes = true;
|
| 556 |
quoteChar = char;
|
|
@@ -559,25 +528,25 @@ const setupCreationStream = () => {
|
|
| 559 |
quoteChar = '';
|
| 560 |
}
|
| 561 |
}
|
| 562 |
-
|
| 563 |
// Handle parentheses depth (for nested blocks)
|
| 564 |
if (!inQuotes) {
|
| 565 |
if (char === '(') depth++;
|
| 566 |
else if (char === ')') depth--;
|
| 567 |
}
|
| 568 |
-
|
| 569 |
// Handle key-value separation
|
| 570 |
if (char === ':' && depth === 0 && !inQuotes && readingKey) {
|
| 571 |
readingKey = false;
|
| 572 |
currentKey = currentKey.trim();
|
| 573 |
continue;
|
| 574 |
}
|
| 575 |
-
|
| 576 |
// Handle comma separation
|
| 577 |
if (char === ',' && depth === 0 && !inQuotes && !readingKey) {
|
| 578 |
// Store the key-value pair
|
| 579 |
currentValue = currentValue.trim();
|
| 580 |
-
|
| 581 |
// Parse the value
|
| 582 |
if (currentValue.match(/^\w+\s*\(inputs\(/)) {
|
| 583 |
// This is a nested block
|
|
@@ -592,14 +561,14 @@ const setupCreationStream = () => {
|
|
| 592 |
// This is a string (remove quotes if present)
|
| 593 |
result[currentKey] = currentValue.replace(/^["']|["']$/g, '');
|
| 594 |
}
|
| 595 |
-
|
| 596 |
// Reset for next key-value pair
|
| 597 |
currentKey = '';
|
| 598 |
currentValue = '';
|
| 599 |
readingKey = true;
|
| 600 |
continue;
|
| 601 |
}
|
| 602 |
-
|
| 603 |
// Accumulate characters
|
| 604 |
if (readingKey) {
|
| 605 |
currentKey += char;
|
|
@@ -607,12 +576,12 @@ const setupCreationStream = () => {
|
|
| 607 |
currentValue += char;
|
| 608 |
}
|
| 609 |
}
|
| 610 |
-
|
| 611 |
// Handle the last key-value pair
|
| 612 |
if (currentKey && currentValue) {
|
| 613 |
currentKey = currentKey.trim();
|
| 614 |
currentValue = currentValue.trim();
|
| 615 |
-
|
| 616 |
// Parse the value
|
| 617 |
if (currentValue.match(/^\w+\s*\(inputs\(/)) {
|
| 618 |
// This is a nested block
|
|
@@ -628,27 +597,27 @@ const setupCreationStream = () => {
|
|
| 628 |
result[currentKey] = currentValue.replace(/^["']|["']$/g, '');
|
| 629 |
}
|
| 630 |
}
|
| 631 |
-
|
| 632 |
return result;
|
| 633 |
}
|
| 634 |
-
|
| 635 |
// Create the block and all its nested children
|
| 636 |
const newBlock = parseAndCreateBlock(data.block_spec, true);
|
| 637 |
-
|
| 638 |
if (newBlock) {
|
| 639 |
blockId = newBlock.id;
|
| 640 |
-
|
| 641 |
// If under_block_id is specified, attach the new block under the parent
|
| 642 |
if (data.under_block_id) {
|
| 643 |
const parentBlock = ws.getBlockById(data.under_block_id);
|
| 644 |
if (parentBlock) {
|
| 645 |
console.log('[SSE CREATE] Attaching to parent block:', data.under_block_id);
|
| 646 |
-
|
| 647 |
// Find an appropriate input to connect to
|
| 648 |
// Try common statement inputs first
|
| 649 |
const statementInputs = ['BODY', 'DO', 'THEN', 'ELSE', 'STACK'];
|
| 650 |
let connected = false;
|
| 651 |
-
|
| 652 |
for (const inputName of statementInputs) {
|
| 653 |
const input = parentBlock.getInput(inputName);
|
| 654 |
if (input && input.type === Blockly.NEXT_STATEMENT) {
|
|
@@ -677,7 +646,7 @@ const setupCreationStream = () => {
|
|
| 677 |
}
|
| 678 |
}
|
| 679 |
}
|
| 680 |
-
|
| 681 |
// If not connected to statement input, try value inputs
|
| 682 |
if (!connected) {
|
| 683 |
// Try all inputs
|
|
@@ -693,7 +662,7 @@ const setupCreationStream = () => {
|
|
| 693 |
}
|
| 694 |
}
|
| 695 |
}
|
| 696 |
-
|
| 697 |
if (!connected) {
|
| 698 |
console.warn('[SSE CREATE] Could not find suitable connection point on parent block');
|
| 699 |
}
|
|
@@ -701,26 +670,26 @@ const setupCreationStream = () => {
|
|
| 701 |
console.warn('[SSE CREATE] Parent block not found:', data.under_block_id);
|
| 702 |
}
|
| 703 |
}
|
| 704 |
-
|
| 705 |
success = true;
|
| 706 |
console.log('[SSE CREATE] Successfully created block with children:', blockId, newBlock.type);
|
| 707 |
} else {
|
| 708 |
throw new Error(`Failed to create block from specification`);
|
| 709 |
}
|
| 710 |
-
|
| 711 |
} catch (e) {
|
| 712 |
error = e.toString();
|
| 713 |
console.error('[SSE CREATE] Error creating block:', e);
|
| 714 |
}
|
| 715 |
-
|
| 716 |
// Send result back to backend immediately
|
| 717 |
-
console.log('[SSE CREATE] Sending creation result:', {
|
| 718 |
-
request_id: data.request_id,
|
| 719 |
-
success,
|
| 720 |
error,
|
| 721 |
-
block_id: blockId
|
| 722 |
});
|
| 723 |
-
|
| 724 |
fetch('/creation_result', {
|
| 725 |
method: 'POST',
|
| 726 |
headers: { 'Content-Type': 'application/json' },
|
|
@@ -740,7 +709,7 @@ const setupCreationStream = () => {
|
|
| 740 |
console.error('[SSE CREATE] Error processing message:', err);
|
| 741 |
}
|
| 742 |
};
|
| 743 |
-
|
| 744 |
eventSource.onerror = (error) => {
|
| 745 |
console.error('[SSE CREATE] Connection error:', error);
|
| 746 |
// Reconnect after 5 seconds
|
|
@@ -749,7 +718,7 @@ const setupCreationStream = () => {
|
|
| 749 |
setupCreationStream();
|
| 750 |
}, 5000);
|
| 751 |
};
|
| 752 |
-
|
| 753 |
eventSource.onopen = () => {
|
| 754 |
console.log('[SSE CREATE] Connected to creation stream');
|
| 755 |
};
|
|
@@ -761,14 +730,14 @@ setupCreationStream();
|
|
| 761 |
const setupVariableStream = () => {
|
| 762 |
const eventSource = new EventSource('/variable_stream');
|
| 763 |
const processedRequests = new Set(); // Track processed variable requests
|
| 764 |
-
|
| 765 |
eventSource.onmessage = (event) => {
|
| 766 |
try {
|
| 767 |
const data = JSON.parse(event.data);
|
| 768 |
-
|
| 769 |
// Skip heartbeat messages
|
| 770 |
if (data.heartbeat) return;
|
| 771 |
-
|
| 772 |
// Skip if we've already processed this request
|
| 773 |
if (data.request_id && processedRequests.has(data.request_id)) {
|
| 774 |
console.log('[SSE VARIABLE] Skipping duplicate variable request:', data.request_id);
|
|
@@ -779,21 +748,21 @@ const setupVariableStream = () => {
|
|
| 779 |
// Clear after 10 seconds to allow retries if needed
|
| 780 |
setTimeout(() => processedRequests.delete(data.request_id), 10000);
|
| 781 |
}
|
| 782 |
-
|
| 783 |
if (data.variable_name && data.request_id) {
|
| 784 |
console.log('[SSE VARIABLE] Received variable creation request:', data.request_id, data.variable_name);
|
| 785 |
-
|
| 786 |
let success = false;
|
| 787 |
let error = null;
|
| 788 |
let variableId = null;
|
| 789 |
-
|
| 790 |
try {
|
| 791 |
// Create the variable using Blockly's variable map
|
| 792 |
const variableName = data.variable_name;
|
| 793 |
-
|
| 794 |
// Use the workspace's variable map to create a new variable
|
| 795 |
const variableModel = ws.getVariableMap().createVariable(variableName);
|
| 796 |
-
|
| 797 |
if (variableModel) {
|
| 798 |
variableId = variableModel.getId();
|
| 799 |
success = true;
|
|
@@ -801,20 +770,20 @@ const setupVariableStream = () => {
|
|
| 801 |
} else {
|
| 802 |
throw new Error('Failed to create variable model');
|
| 803 |
}
|
| 804 |
-
|
| 805 |
} catch (e) {
|
| 806 |
error = e.toString();
|
| 807 |
console.error('[SSE VARIABLE] Error creating variable:', e);
|
| 808 |
}
|
| 809 |
-
|
| 810 |
// Send result back to backend immediately
|
| 811 |
-
console.log('[SSE VARIABLE] Sending variable creation result:', {
|
| 812 |
-
request_id: data.request_id,
|
| 813 |
-
success,
|
| 814 |
error,
|
| 815 |
-
variable_id: variableId
|
| 816 |
});
|
| 817 |
-
|
| 818 |
fetch('/variable_result', {
|
| 819 |
method: 'POST',
|
| 820 |
headers: { 'Content-Type': 'application/json' },
|
|
@@ -834,7 +803,7 @@ const setupVariableStream = () => {
|
|
| 834 |
console.error('[SSE VARIABLE] Error processing message:', err);
|
| 835 |
}
|
| 836 |
};
|
| 837 |
-
|
| 838 |
eventSource.onerror = (error) => {
|
| 839 |
console.error('[SSE VARIABLE] Connection error:', error);
|
| 840 |
// Reconnect after 5 seconds
|
|
@@ -843,7 +812,7 @@ const setupVariableStream = () => {
|
|
| 843 |
setupVariableStream();
|
| 844 |
}, 5000);
|
| 845 |
};
|
| 846 |
-
|
| 847 |
eventSource.onopen = () => {
|
| 848 |
console.log('[SSE VARIABLE] Connected to variable stream');
|
| 849 |
};
|
|
@@ -862,14 +831,14 @@ const updateCode = () => {
|
|
| 862 |
// Initialize the Python generator with the workspace before generating code
|
| 863 |
// This ensures the Names database is properly set up for control flow blocks
|
| 864 |
pythonGenerator.init(ws);
|
| 865 |
-
|
| 866 |
// Instead of using workspaceToCode which processes ALL blocks,
|
| 867 |
// manually process only blocks connected to create_mcp or func_def
|
| 868 |
let code = '';
|
| 869 |
-
|
| 870 |
// Get all top-level blocks (not connected to other blocks)
|
| 871 |
const topBlocks = ws.getTopBlocks(false);
|
| 872 |
-
|
| 873 |
// Process only create_mcp and func_def blocks
|
| 874 |
for (const block of topBlocks) {
|
| 875 |
if (block.type === 'create_mcp' || block.type === 'func_def') {
|
|
@@ -888,7 +857,7 @@ const updateCode = () => {
|
|
| 888 |
|
| 889 |
const vars = ws.getVariableMap().getAllVariables();
|
| 890 |
globalVarString = vars.map(v => `${v.id} | ${v.name}`).join("\n");
|
| 891 |
-
|
| 892 |
const codeEl = document.querySelector('#generatedCode code');
|
| 893 |
|
| 894 |
const call = `def llm_call(prompt, model):
|
|
@@ -998,12 +967,12 @@ const sendChatUpdate = async (chatCode, retryCount = 0) => {
|
|
| 998 |
const response = await fetch("/update_chat", {
|
| 999 |
method: "POST",
|
| 1000 |
headers: { "Content-Type": "application/json" },
|
| 1001 |
-
body: JSON.stringify({
|
| 1002 |
code: chatCode,
|
| 1003 |
varString: globalVarString
|
| 1004 |
}),
|
| 1005 |
});
|
| 1006 |
-
|
| 1007 |
if (response.ok) {
|
| 1008 |
chatBackendAvailable = true;
|
| 1009 |
console.log("[Blockly] Sent updated Chat code to backend");
|
|
@@ -1013,7 +982,7 @@ const sendChatUpdate = async (chatCode, retryCount = 0) => {
|
|
| 1013 |
} catch (err) {
|
| 1014 |
console.warn(`[Blockly] Chat backend not ready (attempt ${retryCount + 1}):`, err.message);
|
| 1015 |
chatBackendAvailable = false;
|
| 1016 |
-
|
| 1017 |
// Queue this update for retry
|
| 1018 |
if (retryCount < 5) {
|
| 1019 |
const delay = Math.min(1000 * Math.pow(2, retryCount), 10000); // Exponential backoff, max 10s
|
|
@@ -1039,7 +1008,7 @@ const updateChatCode = () => {
|
|
| 1039 |
|
| 1040 |
// You can add any chat-specific preprocessing here
|
| 1041 |
// For example, adding headers or formatting
|
| 1042 |
-
|
| 1043 |
if (codeEl) {
|
| 1044 |
codeEl.textContent = globalChatCode;
|
| 1045 |
}
|
|
@@ -1050,15 +1019,15 @@ const updateChatCode = () => {
|
|
| 1050 |
} else {
|
| 1051 |
// Queue the update and try to establish connection
|
| 1052 |
chatUpdateQueue.push(globalChatCode);
|
| 1053 |
-
|
| 1054 |
// Clear any existing retry timeout
|
| 1055 |
if (chatRetryTimeout) {
|
| 1056 |
clearTimeout(chatRetryTimeout);
|
| 1057 |
}
|
| 1058 |
-
|
| 1059 |
// Try to connect to backend
|
| 1060 |
checkChatBackend();
|
| 1061 |
-
|
| 1062 |
// Set up periodic retry
|
| 1063 |
chatRetryTimeout = setTimeout(() => {
|
| 1064 |
checkChatBackend();
|
|
@@ -1080,7 +1049,7 @@ try {
|
|
| 1080 |
if (!block.inputRefBlocks_) {
|
| 1081 |
block.inputRefBlocks_ = new Map();
|
| 1082 |
}
|
| 1083 |
-
|
| 1084 |
// Create reference blocks for each input if they don't exist
|
| 1085 |
if (block.inputNames_ && block.inputNames_.length > 0) {
|
| 1086 |
for (let i = 0; i < block.inputNames_.length; i++) {
|
|
@@ -1090,13 +1059,13 @@ try {
|
|
| 1090 |
if (input && input.connection) {
|
| 1091 |
const connectedBlock = input.connection.targetBlock();
|
| 1092 |
const expectedType = `input_reference_${name}`;
|
| 1093 |
-
|
| 1094 |
// If there's already the correct block connected, just track it
|
| 1095 |
if (connectedBlock && connectedBlock.type === expectedType) {
|
| 1096 |
connectedBlock._ownerBlockId = block.id;
|
| 1097 |
connectedBlock.setDeletable(false);
|
| 1098 |
block.inputRefBlocks_.set(name, connectedBlock);
|
| 1099 |
-
}
|
| 1100 |
// Only create if input exists AND has no connected block yet
|
| 1101 |
else if (!connectedBlock) {
|
| 1102 |
// Create the reference block
|
|
|
|
| 9 |
import DarkTheme from '@blockly/theme-dark';
|
| 10 |
import './index.css';
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
// Register the blocks and generator with Blockly
|
| 13 |
Blockly.common.defineBlocks(blocks);
|
| 14 |
Object.assign(pythonGenerator.forBlock, forBlock);
|
|
|
|
| 101 |
// Get the current generated code
|
| 102 |
const codeEl = document.querySelector('#generatedCode code');
|
| 103 |
const code = codeEl ? codeEl.textContent : '';
|
| 104 |
+
|
| 105 |
if (!code) {
|
| 106 |
alert('No code to download');
|
| 107 |
return;
|
|
|
|
| 109 |
|
| 110 |
var filename = "app.py";
|
| 111 |
var element = document.createElement('a');
|
| 112 |
+
|
| 113 |
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(code));
|
| 114 |
element.setAttribute('download', filename);
|
| 115 |
element.style.display = 'none';
|
|
|
|
| 128 |
|
| 129 |
settingsButton.addEventListener("click", () => {
|
| 130 |
apiKeyModal.style.display = 'flex';
|
| 131 |
+
|
| 132 |
// Load current API keys from backend
|
| 133 |
fetch("/get_api_key", {
|
| 134 |
method: "GET",
|
| 135 |
})
|
| 136 |
+
.then(response => response.json())
|
| 137 |
+
.then(data => {
|
| 138 |
+
apiKeyInput.value = data.api_key || '';
|
| 139 |
+
hfKeyInput.value = data.hf_key || '';
|
| 140 |
+
})
|
| 141 |
+
.catch(err => {
|
| 142 |
+
console.error("Error loading API keys:", err);
|
| 143 |
+
});
|
| 144 |
});
|
| 145 |
|
| 146 |
saveApiKeyButton.addEventListener("click", () => {
|
|
|
|
| 172 |
body: JSON.stringify({ api_key: apiKey, hf_key: hfKey }),
|
| 173 |
})
|
| 174 |
])
|
| 175 |
+
.then(async (responses) => {
|
| 176 |
+
const results = await Promise.all(responses.map(r => r.json()));
|
| 177 |
+
if (results.every(r => r.success)) {
|
| 178 |
+
alert('API keys saved successfully');
|
| 179 |
+
apiKeyModal.style.display = 'none';
|
| 180 |
+
} else {
|
| 181 |
+
alert('Failed to save API keys to all services');
|
| 182 |
+
}
|
| 183 |
+
})
|
| 184 |
+
.catch(err => {
|
| 185 |
+
console.error("Error saving API keys:", err);
|
| 186 |
+
alert('Failed to save API keys');
|
| 187 |
+
});
|
| 188 |
});
|
| 189 |
|
| 190 |
cancelApiKeyButton.addEventListener("click", () => {
|
|
|
|
| 227 |
const setupDeletionStream = () => {
|
| 228 |
const eventSource = new EventSource('/delete_stream');
|
| 229 |
const processedRequests = new Set(); // Track processed deletion requests
|
| 230 |
+
|
| 231 |
eventSource.onmessage = (event) => {
|
| 232 |
try {
|
| 233 |
const data = JSON.parse(event.data);
|
| 234 |
+
|
| 235 |
// Skip heartbeat messages
|
| 236 |
if (data.heartbeat) return;
|
| 237 |
+
|
| 238 |
// Skip if we've already processed this exact request
|
| 239 |
const requestKey = `${data.block_id}_${Date.now()}`;
|
| 240 |
if (data.block_id && processedRequests.has(data.block_id)) {
|
|
|
|
| 246 |
// Clear after 10 seconds to allow retries if needed
|
| 247 |
setTimeout(() => processedRequests.delete(data.block_id), 10000);
|
| 248 |
}
|
| 249 |
+
|
| 250 |
if (data.block_id) {
|
| 251 |
console.log('[SSE] Received deletion request for block:', data.block_id);
|
| 252 |
+
|
| 253 |
// Try to delete the block
|
| 254 |
const block = ws.getBlockById(data.block_id);
|
| 255 |
let success = false;
|
| 256 |
let error = null;
|
| 257 |
+
|
| 258 |
if (block) {
|
| 259 |
console.log('[SSE] Found block to delete:', block.type, block.id);
|
| 260 |
// Check if it's the main create_mcp block (which shouldn't be deleted)
|
|
|
|
| 275 |
error = 'Block not found';
|
| 276 |
console.log('[SSE] Block not found:', data.block_id);
|
| 277 |
}
|
| 278 |
+
|
| 279 |
// Send result back to backend immediately
|
| 280 |
console.log('[SSE] Sending deletion result:', { block_id: data.block_id, success, error });
|
| 281 |
fetch('/deletion_result', {
|
|
|
|
| 296 |
console.error('[SSE] Error processing message:', err);
|
| 297 |
}
|
| 298 |
};
|
| 299 |
+
|
| 300 |
eventSource.onerror = (error) => {
|
| 301 |
console.error('[SSE] Connection error:', error);
|
| 302 |
// Reconnect after 5 seconds
|
|
|
|
| 305 |
setupDeletionStream();
|
| 306 |
}, 5000);
|
| 307 |
};
|
| 308 |
+
|
| 309 |
eventSource.onopen = () => {
|
| 310 |
console.log('[SSE] Connected to deletion stream');
|
| 311 |
};
|
|
|
|
| 318 |
const setupCreationStream = () => {
|
| 319 |
const eventSource = new EventSource('/create_stream');
|
| 320 |
const processedRequests = new Set(); // Track processed creation requests
|
| 321 |
+
|
| 322 |
eventSource.onmessage = (event) => {
|
| 323 |
try {
|
| 324 |
const data = JSON.parse(event.data);
|
| 325 |
+
|
| 326 |
// Skip heartbeat messages
|
| 327 |
if (data.heartbeat) return;
|
| 328 |
+
|
| 329 |
// Skip if we've already processed this request
|
| 330 |
if (data.request_id && processedRequests.has(data.request_id)) {
|
| 331 |
console.log('[SSE CREATE] Skipping duplicate creation request:', data.request_id);
|
|
|
|
| 336 |
// Clear after 10 seconds to allow retries if needed
|
| 337 |
setTimeout(() => processedRequests.delete(data.request_id), 10000);
|
| 338 |
}
|
| 339 |
+
|
| 340 |
if (data.block_spec && data.request_id) {
|
| 341 |
console.log('[SSE CREATE] Received creation request:', data.request_id, data.block_spec);
|
| 342 |
+
|
| 343 |
let success = false;
|
| 344 |
let error = null;
|
| 345 |
let blockId = null;
|
| 346 |
+
|
| 347 |
try {
|
| 348 |
// Parse and create blocks recursively
|
| 349 |
function parseAndCreateBlock(spec, shouldPosition = false) {
|
| 350 |
// Match block_name(inputs(...))
|
| 351 |
const blockMatch = spec.match(/^(\w+)\s*\((.+)\)$/s);
|
| 352 |
+
|
| 353 |
if (!blockMatch) {
|
| 354 |
throw new Error(`Invalid block specification format: ${spec}`);
|
| 355 |
}
|
| 356 |
+
|
| 357 |
const blockType = blockMatch[1];
|
| 358 |
const content = blockMatch[2].trim();
|
| 359 |
+
|
| 360 |
console.log('[SSE CREATE] Parsing block:', blockType, 'with content:', content);
|
| 361 |
+
|
| 362 |
// Check if this has inputs() wrapper
|
| 363 |
let inputsContent = content;
|
| 364 |
if (content.startsWith('inputs(') && content.endsWith(')')) {
|
| 365 |
inputsContent = content.slice(7, -1); // Remove 'inputs(' and ')'
|
| 366 |
}
|
| 367 |
+
|
| 368 |
// Create the block
|
| 369 |
const newBlock = ws.newBlock(blockType);
|
| 370 |
+
|
| 371 |
if (inputsContent) {
|
| 372 |
// Parse the inputs content
|
| 373 |
const inputs = parseInputs(inputsContent);
|
| 374 |
console.log('[SSE CREATE] Parsed inputs:', inputs);
|
| 375 |
+
|
| 376 |
// Special handling for make_json block
|
| 377 |
if (blockType === 'make_json') {
|
| 378 |
// Count FIELD entries to determine how many fields we need
|
| 379 |
let fieldCount = 0;
|
| 380 |
const fieldValues = {};
|
| 381 |
const keyValues = {};
|
| 382 |
+
|
| 383 |
for (const [key, value] of Object.entries(inputs)) {
|
| 384 |
const fieldMatch = key.match(/^FIELD(\d+)$/);
|
| 385 |
const keyMatch = key.match(/^KEY(\d+)$/);
|
| 386 |
+
|
| 387 |
if (fieldMatch) {
|
| 388 |
const index = parseInt(fieldMatch[1]);
|
| 389 |
fieldCount = Math.max(fieldCount, index + 1);
|
|
|
|
| 393 |
keyValues[index] = value;
|
| 394 |
}
|
| 395 |
}
|
| 396 |
+
|
| 397 |
// Set up the mutator state
|
| 398 |
if (fieldCount > 0) {
|
| 399 |
newBlock.fieldCount_ = fieldCount;
|
| 400 |
newBlock.fieldKeys_ = [];
|
| 401 |
+
|
| 402 |
// Create the inputs through the mutator
|
| 403 |
for (let i = 0; i < fieldCount; i++) {
|
| 404 |
const keyValue = keyValues[i];
|
| 405 |
+
const key = (typeof keyValue === 'string' && !keyValue.match(/^\w+\s*\(inputs\(/))
|
| 406 |
+
? keyValue.replace(/^["']|["']$/g, '')
|
| 407 |
: `key${i}`;
|
| 408 |
+
|
| 409 |
newBlock.fieldKeys_[i] = key;
|
| 410 |
+
|
| 411 |
// Create the input
|
| 412 |
const input = newBlock.appendValueInput('FIELD' + i);
|
| 413 |
const field = new Blockly.FieldTextInput(key);
|
|
|
|
| 418 |
input.appendField(field, 'KEY' + i);
|
| 419 |
input.appendField(':');
|
| 420 |
}
|
| 421 |
+
|
| 422 |
// Now connect the field values
|
| 423 |
for (let i = 0; i < fieldCount; i++) {
|
| 424 |
const value = fieldValues[i];
|
| 425 |
if (value && typeof value === 'string' && value.match(/^\w+\s*\(inputs\(/)) {
|
| 426 |
// This is a nested block, create it recursively
|
| 427 |
const childBlock = parseAndCreateBlock(value);
|
| 428 |
+
|
| 429 |
// Connect the child block to the FIELD input
|
| 430 |
const input = newBlock.getInput('FIELD' + i);
|
| 431 |
if (input && input.connection && childBlock.outputConnection) {
|
|
|
|
| 442 |
if (value.match(/^\w+\s*\(inputs\(/)) {
|
| 443 |
// This is a nested block, create it recursively
|
| 444 |
const childBlock = parseAndCreateBlock(value);
|
| 445 |
+
|
| 446 |
// Connect the child block to the appropriate input
|
| 447 |
const input = newBlock.getInput(key);
|
| 448 |
if (input && input.connection && childBlock.outputConnection) {
|
|
|
|
| 452 |
// This is a simple value, set it as a field
|
| 453 |
// Remove quotes if present
|
| 454 |
const cleanValue = value.replace(/^["']|["']$/g, '');
|
| 455 |
+
|
| 456 |
// Try to set as a field value
|
| 457 |
try {
|
| 458 |
newBlock.setFieldValue(cleanValue, key);
|
|
|
|
| 478 |
}
|
| 479 |
}
|
| 480 |
}
|
| 481 |
+
|
| 482 |
// Initialize the block (renders it)
|
| 483 |
newBlock.initSvg();
|
| 484 |
+
|
| 485 |
// Only position the top-level block
|
| 486 |
if (shouldPosition) {
|
| 487 |
// Find a good position that doesn't overlap existing blocks
|
| 488 |
const existingBlocks = ws.getAllBlocks();
|
| 489 |
let x = 50;
|
| 490 |
let y = 50;
|
| 491 |
+
|
| 492 |
// Simple positioning: stack new blocks vertically
|
| 493 |
if (existingBlocks.length > 0) {
|
| 494 |
const lastBlock = existingBlocks[existingBlocks.length - 1];
|
| 495 |
const lastPos = lastBlock.getRelativeToSurfaceXY();
|
| 496 |
y = lastPos.y + lastBlock.height + 20;
|
| 497 |
}
|
| 498 |
+
|
| 499 |
newBlock.moveBy(x, y);
|
| 500 |
}
|
| 501 |
+
|
| 502 |
// Render the block
|
| 503 |
newBlock.render();
|
| 504 |
+
|
| 505 |
return newBlock;
|
| 506 |
}
|
| 507 |
+
|
| 508 |
// Helper function to parse inputs(key: value, key2: value2, ...)
|
| 509 |
function parseInputs(inputStr) {
|
| 510 |
const result = {};
|
|
|
|
| 514 |
let inQuotes = false;
|
| 515 |
let quoteChar = '';
|
| 516 |
let readingKey = true;
|
| 517 |
+
|
| 518 |
for (let i = 0; i < inputStr.length; i++) {
|
| 519 |
const char = inputStr[i];
|
| 520 |
+
|
| 521 |
// Handle quotes
|
| 522 |
+
if ((char === '"' || char === "'") && (i === 0 || inputStr[i - 1] !== '\\')) {
|
| 523 |
if (!inQuotes) {
|
| 524 |
inQuotes = true;
|
| 525 |
quoteChar = char;
|
|
|
|
| 528 |
quoteChar = '';
|
| 529 |
}
|
| 530 |
}
|
| 531 |
+
|
| 532 |
// Handle parentheses depth (for nested blocks)
|
| 533 |
if (!inQuotes) {
|
| 534 |
if (char === '(') depth++;
|
| 535 |
else if (char === ')') depth--;
|
| 536 |
}
|
| 537 |
+
|
| 538 |
// Handle key-value separation
|
| 539 |
if (char === ':' && depth === 0 && !inQuotes && readingKey) {
|
| 540 |
readingKey = false;
|
| 541 |
currentKey = currentKey.trim();
|
| 542 |
continue;
|
| 543 |
}
|
| 544 |
+
|
| 545 |
// Handle comma separation
|
| 546 |
if (char === ',' && depth === 0 && !inQuotes && !readingKey) {
|
| 547 |
// Store the key-value pair
|
| 548 |
currentValue = currentValue.trim();
|
| 549 |
+
|
| 550 |
// Parse the value
|
| 551 |
if (currentValue.match(/^\w+\s*\(inputs\(/)) {
|
| 552 |
// This is a nested block
|
|
|
|
| 561 |
// This is a string (remove quotes if present)
|
| 562 |
result[currentKey] = currentValue.replace(/^["']|["']$/g, '');
|
| 563 |
}
|
| 564 |
+
|
| 565 |
// Reset for next key-value pair
|
| 566 |
currentKey = '';
|
| 567 |
currentValue = '';
|
| 568 |
readingKey = true;
|
| 569 |
continue;
|
| 570 |
}
|
| 571 |
+
|
| 572 |
// Accumulate characters
|
| 573 |
if (readingKey) {
|
| 574 |
currentKey += char;
|
|
|
|
| 576 |
currentValue += char;
|
| 577 |
}
|
| 578 |
}
|
| 579 |
+
|
| 580 |
// Handle the last key-value pair
|
| 581 |
if (currentKey && currentValue) {
|
| 582 |
currentKey = currentKey.trim();
|
| 583 |
currentValue = currentValue.trim();
|
| 584 |
+
|
| 585 |
// Parse the value
|
| 586 |
if (currentValue.match(/^\w+\s*\(inputs\(/)) {
|
| 587 |
// This is a nested block
|
|
|
|
| 597 |
result[currentKey] = currentValue.replace(/^["']|["']$/g, '');
|
| 598 |
}
|
| 599 |
}
|
| 600 |
+
|
| 601 |
return result;
|
| 602 |
}
|
| 603 |
+
|
| 604 |
// Create the block and all its nested children
|
| 605 |
const newBlock = parseAndCreateBlock(data.block_spec, true);
|
| 606 |
+
|
| 607 |
if (newBlock) {
|
| 608 |
blockId = newBlock.id;
|
| 609 |
+
|
| 610 |
// If under_block_id is specified, attach the new block under the parent
|
| 611 |
if (data.under_block_id) {
|
| 612 |
const parentBlock = ws.getBlockById(data.under_block_id);
|
| 613 |
if (parentBlock) {
|
| 614 |
console.log('[SSE CREATE] Attaching to parent block:', data.under_block_id);
|
| 615 |
+
|
| 616 |
// Find an appropriate input to connect to
|
| 617 |
// Try common statement inputs first
|
| 618 |
const statementInputs = ['BODY', 'DO', 'THEN', 'ELSE', 'STACK'];
|
| 619 |
let connected = false;
|
| 620 |
+
|
| 621 |
for (const inputName of statementInputs) {
|
| 622 |
const input = parentBlock.getInput(inputName);
|
| 623 |
if (input && input.type === Blockly.NEXT_STATEMENT) {
|
|
|
|
| 646 |
}
|
| 647 |
}
|
| 648 |
}
|
| 649 |
+
|
| 650 |
// If not connected to statement input, try value inputs
|
| 651 |
if (!connected) {
|
| 652 |
// Try all inputs
|
|
|
|
| 662 |
}
|
| 663 |
}
|
| 664 |
}
|
| 665 |
+
|
| 666 |
if (!connected) {
|
| 667 |
console.warn('[SSE CREATE] Could not find suitable connection point on parent block');
|
| 668 |
}
|
|
|
|
| 670 |
console.warn('[SSE CREATE] Parent block not found:', data.under_block_id);
|
| 671 |
}
|
| 672 |
}
|
| 673 |
+
|
| 674 |
success = true;
|
| 675 |
console.log('[SSE CREATE] Successfully created block with children:', blockId, newBlock.type);
|
| 676 |
} else {
|
| 677 |
throw new Error(`Failed to create block from specification`);
|
| 678 |
}
|
| 679 |
+
|
| 680 |
} catch (e) {
|
| 681 |
error = e.toString();
|
| 682 |
console.error('[SSE CREATE] Error creating block:', e);
|
| 683 |
}
|
| 684 |
+
|
| 685 |
// Send result back to backend immediately
|
| 686 |
+
console.log('[SSE CREATE] Sending creation result:', {
|
| 687 |
+
request_id: data.request_id,
|
| 688 |
+
success,
|
| 689 |
error,
|
| 690 |
+
block_id: blockId
|
| 691 |
});
|
| 692 |
+
|
| 693 |
fetch('/creation_result', {
|
| 694 |
method: 'POST',
|
| 695 |
headers: { 'Content-Type': 'application/json' },
|
|
|
|
| 709 |
console.error('[SSE CREATE] Error processing message:', err);
|
| 710 |
}
|
| 711 |
};
|
| 712 |
+
|
| 713 |
eventSource.onerror = (error) => {
|
| 714 |
console.error('[SSE CREATE] Connection error:', error);
|
| 715 |
// Reconnect after 5 seconds
|
|
|
|
| 718 |
setupCreationStream();
|
| 719 |
}, 5000);
|
| 720 |
};
|
| 721 |
+
|
| 722 |
eventSource.onopen = () => {
|
| 723 |
console.log('[SSE CREATE] Connected to creation stream');
|
| 724 |
};
|
|
|
|
| 730 |
const setupVariableStream = () => {
|
| 731 |
const eventSource = new EventSource('/variable_stream');
|
| 732 |
const processedRequests = new Set(); // Track processed variable requests
|
| 733 |
+
|
| 734 |
eventSource.onmessage = (event) => {
|
| 735 |
try {
|
| 736 |
const data = JSON.parse(event.data);
|
| 737 |
+
|
| 738 |
// Skip heartbeat messages
|
| 739 |
if (data.heartbeat) return;
|
| 740 |
+
|
| 741 |
// Skip if we've already processed this request
|
| 742 |
if (data.request_id && processedRequests.has(data.request_id)) {
|
| 743 |
console.log('[SSE VARIABLE] Skipping duplicate variable request:', data.request_id);
|
|
|
|
| 748 |
// Clear after 10 seconds to allow retries if needed
|
| 749 |
setTimeout(() => processedRequests.delete(data.request_id), 10000);
|
| 750 |
}
|
| 751 |
+
|
| 752 |
if (data.variable_name && data.request_id) {
|
| 753 |
console.log('[SSE VARIABLE] Received variable creation request:', data.request_id, data.variable_name);
|
| 754 |
+
|
| 755 |
let success = false;
|
| 756 |
let error = null;
|
| 757 |
let variableId = null;
|
| 758 |
+
|
| 759 |
try {
|
| 760 |
// Create the variable using Blockly's variable map
|
| 761 |
const variableName = data.variable_name;
|
| 762 |
+
|
| 763 |
// Use the workspace's variable map to create a new variable
|
| 764 |
const variableModel = ws.getVariableMap().createVariable(variableName);
|
| 765 |
+
|
| 766 |
if (variableModel) {
|
| 767 |
variableId = variableModel.getId();
|
| 768 |
success = true;
|
|
|
|
| 770 |
} else {
|
| 771 |
throw new Error('Failed to create variable model');
|
| 772 |
}
|
| 773 |
+
|
| 774 |
} catch (e) {
|
| 775 |
error = e.toString();
|
| 776 |
console.error('[SSE VARIABLE] Error creating variable:', e);
|
| 777 |
}
|
| 778 |
+
|
| 779 |
// Send result back to backend immediately
|
| 780 |
+
console.log('[SSE VARIABLE] Sending variable creation result:', {
|
| 781 |
+
request_id: data.request_id,
|
| 782 |
+
success,
|
| 783 |
error,
|
| 784 |
+
variable_id: variableId
|
| 785 |
});
|
| 786 |
+
|
| 787 |
fetch('/variable_result', {
|
| 788 |
method: 'POST',
|
| 789 |
headers: { 'Content-Type': 'application/json' },
|
|
|
|
| 803 |
console.error('[SSE VARIABLE] Error processing message:', err);
|
| 804 |
}
|
| 805 |
};
|
| 806 |
+
|
| 807 |
eventSource.onerror = (error) => {
|
| 808 |
console.error('[SSE VARIABLE] Connection error:', error);
|
| 809 |
// Reconnect after 5 seconds
|
|
|
|
| 812 |
setupVariableStream();
|
| 813 |
}, 5000);
|
| 814 |
};
|
| 815 |
+
|
| 816 |
eventSource.onopen = () => {
|
| 817 |
console.log('[SSE VARIABLE] Connected to variable stream');
|
| 818 |
};
|
|
|
|
| 831 |
// Initialize the Python generator with the workspace before generating code
|
| 832 |
// This ensures the Names database is properly set up for control flow blocks
|
| 833 |
pythonGenerator.init(ws);
|
| 834 |
+
|
| 835 |
// Instead of using workspaceToCode which processes ALL blocks,
|
| 836 |
// manually process only blocks connected to create_mcp or func_def
|
| 837 |
let code = '';
|
| 838 |
+
|
| 839 |
// Get all top-level blocks (not connected to other blocks)
|
| 840 |
const topBlocks = ws.getTopBlocks(false);
|
| 841 |
+
|
| 842 |
// Process only create_mcp and func_def blocks
|
| 843 |
for (const block of topBlocks) {
|
| 844 |
if (block.type === 'create_mcp' || block.type === 'func_def') {
|
|
|
|
| 857 |
|
| 858 |
const vars = ws.getVariableMap().getAllVariables();
|
| 859 |
globalVarString = vars.map(v => `${v.id} | ${v.name}`).join("\n");
|
| 860 |
+
|
| 861 |
const codeEl = document.querySelector('#generatedCode code');
|
| 862 |
|
| 863 |
const call = `def llm_call(prompt, model):
|
|
|
|
| 967 |
const response = await fetch("/update_chat", {
|
| 968 |
method: "POST",
|
| 969 |
headers: { "Content-Type": "application/json" },
|
| 970 |
+
body: JSON.stringify({
|
| 971 |
code: chatCode,
|
| 972 |
varString: globalVarString
|
| 973 |
}),
|
| 974 |
});
|
| 975 |
+
|
| 976 |
if (response.ok) {
|
| 977 |
chatBackendAvailable = true;
|
| 978 |
console.log("[Blockly] Sent updated Chat code to backend");
|
|
|
|
| 982 |
} catch (err) {
|
| 983 |
console.warn(`[Blockly] Chat backend not ready (attempt ${retryCount + 1}):`, err.message);
|
| 984 |
chatBackendAvailable = false;
|
| 985 |
+
|
| 986 |
// Queue this update for retry
|
| 987 |
if (retryCount < 5) {
|
| 988 |
const delay = Math.min(1000 * Math.pow(2, retryCount), 10000); // Exponential backoff, max 10s
|
|
|
|
| 1008 |
|
| 1009 |
// You can add any chat-specific preprocessing here
|
| 1010 |
// For example, adding headers or formatting
|
| 1011 |
+
|
| 1012 |
if (codeEl) {
|
| 1013 |
codeEl.textContent = globalChatCode;
|
| 1014 |
}
|
|
|
|
| 1019 |
} else {
|
| 1020 |
// Queue the update and try to establish connection
|
| 1021 |
chatUpdateQueue.push(globalChatCode);
|
| 1022 |
+
|
| 1023 |
// Clear any existing retry timeout
|
| 1024 |
if (chatRetryTimeout) {
|
| 1025 |
clearTimeout(chatRetryTimeout);
|
| 1026 |
}
|
| 1027 |
+
|
| 1028 |
// Try to connect to backend
|
| 1029 |
checkChatBackend();
|
| 1030 |
+
|
| 1031 |
// Set up periodic retry
|
| 1032 |
chatRetryTimeout = setTimeout(() => {
|
| 1033 |
checkChatBackend();
|
|
|
|
| 1049 |
if (!block.inputRefBlocks_) {
|
| 1050 |
block.inputRefBlocks_ = new Map();
|
| 1051 |
}
|
| 1052 |
+
|
| 1053 |
// Create reference blocks for each input if they don't exist
|
| 1054 |
if (block.inputNames_ && block.inputNames_.length > 0) {
|
| 1055 |
for (let i = 0; i < block.inputNames_.length; i++) {
|
|
|
|
| 1059 |
if (input && input.connection) {
|
| 1060 |
const connectedBlock = input.connection.targetBlock();
|
| 1061 |
const expectedType = `input_reference_${name}`;
|
| 1062 |
+
|
| 1063 |
// If there's already the correct block connected, just track it
|
| 1064 |
if (connectedBlock && connectedBlock.type === expectedType) {
|
| 1065 |
connectedBlock._ownerBlockId = block.id;
|
| 1066 |
connectedBlock.setDeletable(false);
|
| 1067 |
block.inputRefBlocks_.set(name, connectedBlock);
|
| 1068 |
+
}
|
| 1069 |
// Only create if input exists AND has no connected block yet
|
| 1070 |
else if (!connectedBlock) {
|
| 1071 |
// Create the reference block
|