Spaces:
Running
Running
owenkaplinsky
commited on
Commit
·
a7bfb03
1
Parent(s):
c4b5da1
Fixes
Browse files- project/blocks.txt +1 -1
- project/chat.py +29 -22
- project/src/generators/chat.js +58 -3
- project/src/index.js +287 -111
project/blocks.txt
CHANGED
|
@@ -13,7 +13,7 @@ logic_operation(inputs(OP: "AND/OR", A: value, B: value))
|
|
| 13 |
|
| 14 |
# Math
|
| 15 |
math_number(inputs(NUM: value))
|
| 16 |
-
math_arithmetic(inputs(OP: "ADD/MINUS/MULTIPLY/DIVIDE/POWER", A: value, B: value))
|
| 17 |
|
| 18 |
# Text
|
| 19 |
text(inputs(TEXT: value))
|
|
|
|
| 13 |
|
| 14 |
# Math
|
| 15 |
math_number(inputs(NUM: value))
|
| 16 |
+
math_arithmetic(inputs(OP: "ADD/MINUS/MULTIPLY/DIVIDE/POWER", A: value, B: value)) // A and B are required. They must be blocks, so you need to add math_number or an alternative
|
| 17 |
|
| 18 |
# Text
|
| 19 |
text(inputs(TEXT: value))
|
project/chat.py
CHANGED
|
@@ -457,9 +457,8 @@ To call a tool, use this exact format (no newline after the opening backticks):
|
|
| 457 |
|
| 458 |
---
|
| 459 |
|
| 460 |
-
### Running
|
| 461 |
-
You can execute
|
| 462 |
-
End your message (and say nothing after) with:
|
| 463 |
|
| 464 |
```run
|
| 465 |
create_mcp(input_name=value)
|
|
@@ -480,6 +479,15 @@ blockId
|
|
| 480 |
|
| 481 |
You can delete any block except the main `create_mcp` block.
|
| 482 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
---
|
| 484 |
|
| 485 |
### Creating Blocks
|
|
@@ -488,17 +496,16 @@ To create a block, specify its type and parameters (if any).
|
|
| 488 |
End your message (and say nothing after) with:
|
| 489 |
|
| 490 |
```create
|
| 491 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 492 |
```
|
| 493 |
|
| 494 |
-
|
| 495 |
-
- `print_text("Hello World")` - creates a print block with text
|
| 496 |
-
- `text("some text")` - creates a text block
|
| 497 |
-
- `math_number(42)` - creates a number block
|
| 498 |
-
- `logic_boolean(true)` - creates a boolean block
|
| 499 |
-
- `mcp_tool("tool_name")` - creates an MCP tool block
|
| 500 |
-
- `controls_if()` - creates an if block
|
| 501 |
-
- `lists_create_empty()` - creates an empty list block
|
| 502 |
|
| 503 |
List of blocks:
|
| 504 |
|
|
@@ -506,15 +513,15 @@ List of blocks:
|
|
| 506 |
|
| 507 |
---
|
| 508 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
Additionally, if you ever send a message without a tool call, your response will end. So, if you want to
|
| 510 |
call more tools after something you have to keep calling them. Any pause in tool callings ends the loop.
|
| 511 |
|
| 512 |
-
|
| 513 |
-
THIS IS A REQUIREMENT. NAME MUST BE ON THE EXACT SAME LINE AS THE BACKTICKS.
|
| 514 |
-
|
| 515 |
-
```name
|
| 516 |
-
(arguments_here)
|
| 517 |
-
```
|
| 518 |
"""
|
| 519 |
|
| 520 |
def chat_with_context(message, history):
|
|
@@ -569,7 +576,7 @@ THIS IS A REQUIREMENT. NAME MUST BE ON THE EXACT SAME LINE AS THE BACKTICKS.
|
|
| 569 |
|
| 570 |
try:
|
| 571 |
response = client.chat.completions.create(
|
| 572 |
-
model="gpt-
|
| 573 |
messages=[
|
| 574 |
{"role": "system", "content": full_system_prompt},
|
| 575 |
*temp_history,
|
|
@@ -582,21 +589,21 @@ THIS IS A REQUIREMENT. NAME MUST BE ON THE EXACT SAME LINE AS THE BACKTICKS.
|
|
| 582 |
# Define action patterns and their handlers
|
| 583 |
action_patterns = {
|
| 584 |
'run': {
|
| 585 |
-
'pattern': r'```run\n(.+?)\n```',
|
| 586 |
'label': 'MCP',
|
| 587 |
'result_label': 'MCP Execution Result',
|
| 588 |
'handler': lambda content: execute_mcp(content),
|
| 589 |
'next_prompt': "Please respond to the MCP execution result above and provide any relevant information to the user. If you need to run another MCP, delete, or create blocks, you can do so."
|
| 590 |
},
|
| 591 |
'delete': {
|
| 592 |
-
'pattern': r'```delete\n(.+?)\n```',
|
| 593 |
'label': 'DELETE',
|
| 594 |
'result_label': 'Delete Operation',
|
| 595 |
'handler': lambda content: delete_block(content.strip()),
|
| 596 |
'next_prompt': "Please respond to the delete operation result above. If you need to run an MCP, delete more code, or create blocks, you can do so."
|
| 597 |
},
|
| 598 |
'create': {
|
| 599 |
-
'pattern': r'```create\n(.+?)\n```',
|
| 600 |
'label': 'CREATE',
|
| 601 |
'result_label': 'Create Operation',
|
| 602 |
'handler': lambda content: create_block(content.strip()),
|
|
|
|
| 457 |
|
| 458 |
---
|
| 459 |
|
| 460 |
+
### Running MCP
|
| 461 |
+
You can execute the MCP directly:
|
|
|
|
| 462 |
|
| 463 |
```run
|
| 464 |
create_mcp(input_name=value)
|
|
|
|
| 479 |
|
| 480 |
You can delete any block except the main `create_mcp` block.
|
| 481 |
|
| 482 |
+
Remember, you give the blockId. Never put the code for it!
|
| 483 |
+
You can see the ID to the left of each block it will be a jarble of characters
|
| 484 |
+
looking something like:
|
| 485 |
+
|
| 486 |
+
`blockId | code`
|
| 487 |
+
|
| 488 |
+
Each block has its own ID, and you need to use the ID specifically from
|
| 489 |
+
the correct block.
|
| 490 |
+
|
| 491 |
---
|
| 492 |
|
| 493 |
### Creating Blocks
|
|
|
|
| 496 |
End your message (and say nothing after) with:
|
| 497 |
|
| 498 |
```create
|
| 499 |
+
block_name(inputs(value_name: value))
|
| 500 |
+
```
|
| 501 |
+
|
| 502 |
+
If you want to create a block inside of a block, do it like this:
|
| 503 |
+
|
| 504 |
+
```create
|
| 505 |
+
block_name(inputs(value_name: block_name2(inputs(value_name2: value))))
|
| 506 |
```
|
| 507 |
|
| 508 |
+
Where you specify inputs() per block, even if it's inside of another block.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
|
| 510 |
List of blocks:
|
| 511 |
|
|
|
|
| 513 |
|
| 514 |
---
|
| 515 |
|
| 516 |
+
Remember, this is not the standard tool format. You must follow the one outlined above.
|
| 517 |
+
|
| 518 |
+
Never say that you are going to run a tool and don't run it. You need to put tool calls in the same
|
| 519 |
+
message or your messages will end (see below).
|
| 520 |
+
|
| 521 |
Additionally, if you ever send a message without a tool call, your response will end. So, if you want to
|
| 522 |
call more tools after something you have to keep calling them. Any pause in tool callings ends the loop.
|
| 523 |
|
| 524 |
+
For deleting blocks, don't forget that you use the **blockId** and **not** the code for it.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 525 |
"""
|
| 526 |
|
| 527 |
def chat_with_context(message, history):
|
|
|
|
| 576 |
|
| 577 |
try:
|
| 578 |
response = client.chat.completions.create(
|
| 579 |
+
model="gpt-4o-2024-08-06",
|
| 580 |
messages=[
|
| 581 |
{"role": "system", "content": full_system_prompt},
|
| 582 |
*temp_history,
|
|
|
|
| 589 |
# Define action patterns and their handlers
|
| 590 |
action_patterns = {
|
| 591 |
'run': {
|
| 592 |
+
'pattern': r'```(?:\n)?run\n(.+?)\n```',
|
| 593 |
'label': 'MCP',
|
| 594 |
'result_label': 'MCP Execution Result',
|
| 595 |
'handler': lambda content: execute_mcp(content),
|
| 596 |
'next_prompt': "Please respond to the MCP execution result above and provide any relevant information to the user. If you need to run another MCP, delete, or create blocks, you can do so."
|
| 597 |
},
|
| 598 |
'delete': {
|
| 599 |
+
'pattern': r'```(?:\n)?delete\n(.+?)\n```',
|
| 600 |
'label': 'DELETE',
|
| 601 |
'result_label': 'Delete Operation',
|
| 602 |
'handler': lambda content: delete_block(content.strip()),
|
| 603 |
'next_prompt': "Please respond to the delete operation result above. If you need to run an MCP, delete more code, or create blocks, you can do so."
|
| 604 |
},
|
| 605 |
'create': {
|
| 606 |
+
'pattern': r'```(?:\n)?create\n(.+?)\n```',
|
| 607 |
'label': 'CREATE',
|
| 608 |
'result_label': 'Create Operation',
|
| 609 |
'handler': lambda content: create_block(content.strip()),
|
project/src/generators/chat.js
CHANGED
|
@@ -143,6 +143,50 @@ forBlock['input_reference'] = function(block, generator) {
|
|
| 143 |
// Register the forBlock definitions with the chat generator
|
| 144 |
Object.assign(chatGenerator.forBlock, forBlock);
|
| 145 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
// Override blockToCode to provide a catch-all handler
|
| 147 |
const originalBlockToCode = chatGenerator.blockToCode.bind(chatGenerator);
|
| 148 |
chatGenerator.blockToCode = function(block, opt_thisOnly) {
|
|
@@ -230,9 +274,20 @@ chatGenerator.blockToCode = function(block, opt_thisOnly) {
|
|
| 230 |
// Return appropriate format based on whether it's a value or statement block
|
| 231 |
if (block.outputConnection) {
|
| 232 |
// This is a value block (can be plugged into inputs)
|
| 233 |
-
//
|
| 234 |
-
const
|
| 235 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
} else {
|
| 237 |
// This is a statement block (has prev/next connections)
|
| 238 |
const fullCode = code + (statements ? '\n' + statements : '');
|
|
|
|
| 143 |
// Register the forBlock definitions with the chat 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.');
|
| 151 |
+
workspace = Blockly.getMainWorkspace();
|
| 152 |
+
}
|
| 153 |
+
let code = [];
|
| 154 |
+
const blocks = workspace.getTopBlocks(true);
|
| 155 |
+
for (let i = 0; i < blocks.length; i++) {
|
| 156 |
+
const block = blocks[i];
|
| 157 |
+
// Process ALL top-level blocks, including value blocks
|
| 158 |
+
if (block.outputConnection) {
|
| 159 |
+
// This is a value block - check if it's connected
|
| 160 |
+
if (!block.outputConnection.isConnected()) {
|
| 161 |
+
// Standalone value block - get its code
|
| 162 |
+
const line = this.blockToCode(block, true);
|
| 163 |
+
if (Array.isArray(line)) {
|
| 164 |
+
// Value blocks return [code, order], extract just the code
|
| 165 |
+
const blockCode = line[0];
|
| 166 |
+
if (blockCode) {
|
| 167 |
+
code.push(blockCode);
|
| 168 |
+
}
|
| 169 |
+
} else if (line) {
|
| 170 |
+
code.push(line);
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
} else {
|
| 174 |
+
// Regular statement block
|
| 175 |
+
const line = this.blockToCode(block);
|
| 176 |
+
if (Array.isArray(line)) {
|
| 177 |
+
// Shouldn't happen for statement blocks, but handle it anyway
|
| 178 |
+
code.push(line[0]);
|
| 179 |
+
} else if (line) {
|
| 180 |
+
code.push(line);
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
code = code.join('\n'); // Blank line between each section.
|
| 185 |
+
// Strip trailing whitespace
|
| 186 |
+
code = code.replace(/\n\s*$/g, '\n');
|
| 187 |
+
return code;
|
| 188 |
+
};
|
| 189 |
+
|
| 190 |
// Override blockToCode to provide a catch-all handler
|
| 191 |
const originalBlockToCode = chatGenerator.blockToCode.bind(chatGenerator);
|
| 192 |
chatGenerator.blockToCode = function(block, opt_thisOnly) {
|
|
|
|
| 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(', ')}))`;
|
| 283 |
+
return [valueCode, this.ORDER_ATOMIC];
|
| 284 |
+
} else {
|
| 285 |
+
// When standalone (not connected), include the ID
|
| 286 |
+
const standaloneCode = `${block.id} | ${blockType}(inputs(${inputs.join(', ')}))`;
|
| 287 |
+
// For standalone value blocks, we need to return them as statement-like
|
| 288 |
+
// but still maintain the value block return format for Blockly
|
| 289 |
+
return [standaloneCode, this.ORDER_ATOMIC];
|
| 290 |
+
}
|
| 291 |
} else {
|
| 292 |
// This is a statement block (has prev/next connections)
|
| 293 |
const fullCode = code + (statements ? '\n' + statements : '');
|
project/src/index.js
CHANGED
|
@@ -330,125 +330,208 @@ const setupCreationStream = () => {
|
|
| 330 |
let blockId = null;
|
| 331 |
|
| 332 |
try {
|
| 333 |
-
// Parse
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
|
|
|
|
|
|
|
|
|
| 357 |
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
}
|
| 365 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
const
|
| 371 |
-
|
| 372 |
}
|
| 373 |
-
break;
|
| 374 |
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
}
|
| 382 |
}
|
| 383 |
-
break;
|
| 384 |
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
newBlock.setFieldValue(bool, 'BOOL');
|
| 390 |
}
|
| 391 |
-
break;
|
| 392 |
-
|
| 393 |
-
case 'logic_compare':
|
| 394 |
-
newBlock = ws.newBlock('logic_compare');
|
| 395 |
-
// Could parse operator if provided
|
| 396 |
-
break;
|
| 397 |
-
|
| 398 |
-
case 'logic_operation':
|
| 399 |
-
newBlock = ws.newBlock('logic_operation');
|
| 400 |
-
// Could parse operator if provided
|
| 401 |
-
break;
|
| 402 |
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
break;
|
| 410 |
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
}
|
| 424 |
|
|
|
|
|
|
|
|
|
|
| 425 |
if (newBlock) {
|
| 426 |
-
// Initialize the block (renders it)
|
| 427 |
-
newBlock.initSvg();
|
| 428 |
-
|
| 429 |
-
// Position it in a visible area
|
| 430 |
-
// Find a good position that doesn't overlap existing blocks
|
| 431 |
-
const existingBlocks = ws.getAllBlocks();
|
| 432 |
-
let x = 50;
|
| 433 |
-
let y = 50;
|
| 434 |
-
|
| 435 |
-
// Simple positioning: stack new blocks vertically
|
| 436 |
-
if (existingBlocks.length > 0) {
|
| 437 |
-
const lastBlock = existingBlocks[existingBlocks.length - 1];
|
| 438 |
-
const lastPos = lastBlock.getRelativeToSurfaceXY();
|
| 439 |
-
y = lastPos.y + lastBlock.height + 20;
|
| 440 |
-
}
|
| 441 |
-
|
| 442 |
-
newBlock.moveBy(x, y);
|
| 443 |
-
|
| 444 |
-
// Render the block
|
| 445 |
-
newBlock.render();
|
| 446 |
-
|
| 447 |
blockId = newBlock.id;
|
| 448 |
success = true;
|
| 449 |
-
console.log('[SSE CREATE] Successfully created block:', blockId,
|
| 450 |
} else {
|
| 451 |
-
throw new Error(`Failed to create block
|
| 452 |
}
|
| 453 |
|
| 454 |
} catch (e) {
|
|
@@ -570,6 +653,78 @@ const updateCode = () => {
|
|
| 570 |
});
|
| 571 |
};
|
| 572 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 573 |
// Update function for the Chat generator (AI Chat tab)
|
| 574 |
const updateChatCode = () => {
|
| 575 |
let code = chatGenerator.workspaceToCode(ws);
|
|
@@ -582,16 +737,26 @@ const updateChatCode = () => {
|
|
| 582 |
codeEl.textContent = code;
|
| 583 |
}
|
| 584 |
|
| 585 |
-
//
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 595 |
};
|
| 596 |
|
| 597 |
try {
|
|
@@ -657,7 +822,18 @@ if (existingMcpBlocks.length === 0) {
|
|
| 657 |
}
|
| 658 |
|
| 659 |
updateCode();
|
| 660 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 661 |
|
| 662 |
ws.addChangeListener((e) => {
|
| 663 |
if (e.isUiEvent) return;
|
|
|
|
| 330 |
let blockId = null;
|
| 331 |
|
| 332 |
try {
|
| 333 |
+
// Parse and create blocks recursively
|
| 334 |
+
function parseAndCreateBlock(spec, shouldPosition = false) {
|
| 335 |
+
// Match block_name(inputs(...))
|
| 336 |
+
const blockMatch = spec.match(/^(\w+)\s*\((.+)\)$/s);
|
| 337 |
+
|
| 338 |
+
if (!blockMatch) {
|
| 339 |
+
throw new Error(`Invalid block specification format: ${spec}`);
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
const blockType = blockMatch[1];
|
| 343 |
+
const content = blockMatch[2].trim();
|
| 344 |
+
|
| 345 |
+
console.log('[SSE CREATE] Parsing block:', blockType, 'with content:', content);
|
| 346 |
+
|
| 347 |
+
// Check if this has inputs() wrapper
|
| 348 |
+
let inputsContent = content;
|
| 349 |
+
if (content.startsWith('inputs(') && content.endsWith(')')) {
|
| 350 |
+
inputsContent = content.slice(7, -1); // Remove 'inputs(' and ')'
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
// Create the block
|
| 354 |
+
const newBlock = ws.newBlock(blockType);
|
| 355 |
+
|
| 356 |
+
if (inputsContent) {
|
| 357 |
+
// Parse the inputs content
|
| 358 |
+
const inputs = parseInputs(inputsContent);
|
| 359 |
+
console.log('[SSE CREATE] Parsed inputs:', inputs);
|
| 360 |
|
| 361 |
+
// Set field values and connect child blocks
|
| 362 |
+
for (const [key, value] of Object.entries(inputs)) {
|
| 363 |
+
if (typeof value === 'string') {
|
| 364 |
+
// Check if this is a nested block specification
|
| 365 |
+
if (value.match(/^\w+\s*\(inputs\(/)) {
|
| 366 |
+
// This is a nested block, create it recursively
|
| 367 |
+
const childBlock = parseAndCreateBlock(value);
|
| 368 |
+
|
| 369 |
+
// Connect the child block to the appropriate input
|
| 370 |
+
const input = newBlock.getInput(key);
|
| 371 |
+
if (input && input.connection) {
|
| 372 |
+
childBlock.outputConnection.connect(input.connection);
|
| 373 |
+
}
|
| 374 |
+
} else {
|
| 375 |
+
// This is a simple value, set it as a field
|
| 376 |
+
// Remove quotes if present
|
| 377 |
+
const cleanValue = value.replace(/^["']|["']$/g, '');
|
| 378 |
+
|
| 379 |
+
// Try to set as a field value
|
| 380 |
+
try {
|
| 381 |
+
newBlock.setFieldValue(cleanValue, key);
|
| 382 |
+
} catch (e) {
|
| 383 |
+
console.log(`[SSE CREATE] Could not set field ${key} to ${cleanValue}:`, e);
|
| 384 |
+
}
|
| 385 |
+
}
|
| 386 |
+
} else if (typeof value === 'number') {
|
| 387 |
+
// Set numeric field value
|
| 388 |
+
try {
|
| 389 |
+
newBlock.setFieldValue(value, key);
|
| 390 |
+
} catch (e) {
|
| 391 |
+
console.log(`[SSE CREATE] Could not set field ${key} to ${value}:`, e);
|
| 392 |
+
}
|
| 393 |
+
} else if (typeof value === 'boolean') {
|
| 394 |
+
// Set boolean field value
|
| 395 |
+
try {
|
| 396 |
+
newBlock.setFieldValue(value ? 'TRUE' : 'FALSE', key);
|
| 397 |
+
} catch (e) {
|
| 398 |
+
console.log(`[SSE CREATE] Could not set field ${key} to ${value}:`, e);
|
| 399 |
+
}
|
| 400 |
+
}
|
| 401 |
}
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
// Initialize the block (renders it)
|
| 405 |
+
newBlock.initSvg();
|
| 406 |
+
|
| 407 |
+
// Only position the top-level block
|
| 408 |
+
if (shouldPosition) {
|
| 409 |
+
// Find a good position that doesn't overlap existing blocks
|
| 410 |
+
const existingBlocks = ws.getAllBlocks();
|
| 411 |
+
let x = 50;
|
| 412 |
+
let y = 50;
|
| 413 |
|
| 414 |
+
// Simple positioning: stack new blocks vertically
|
| 415 |
+
if (existingBlocks.length > 0) {
|
| 416 |
+
const lastBlock = existingBlocks[existingBlocks.length - 1];
|
| 417 |
+
const lastPos = lastBlock.getRelativeToSurfaceXY();
|
| 418 |
+
y = lastPos.y + lastBlock.height + 20;
|
| 419 |
}
|
|
|
|
| 420 |
|
| 421 |
+
newBlock.moveBy(x, y);
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
// Render the block
|
| 425 |
+
newBlock.render();
|
| 426 |
+
|
| 427 |
+
return newBlock;
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
// Helper function to parse inputs(key: value, key2: value2, ...)
|
| 431 |
+
function parseInputs(inputStr) {
|
| 432 |
+
const result = {};
|
| 433 |
+
let currentKey = '';
|
| 434 |
+
let currentValue = '';
|
| 435 |
+
let depth = 0;
|
| 436 |
+
let inQuotes = false;
|
| 437 |
+
let quoteChar = '';
|
| 438 |
+
let readingKey = true;
|
| 439 |
+
|
| 440 |
+
for (let i = 0; i < inputStr.length; i++) {
|
| 441 |
+
const char = inputStr[i];
|
| 442 |
+
|
| 443 |
+
// Handle quotes
|
| 444 |
+
if ((char === '"' || char === "'") && (i === 0 || inputStr[i-1] !== '\\')) {
|
| 445 |
+
if (!inQuotes) {
|
| 446 |
+
inQuotes = true;
|
| 447 |
+
quoteChar = char;
|
| 448 |
+
} else if (char === quoteChar) {
|
| 449 |
+
inQuotes = false;
|
| 450 |
+
quoteChar = '';
|
| 451 |
}
|
| 452 |
}
|
|
|
|
| 453 |
|
| 454 |
+
// Handle parentheses depth (for nested blocks)
|
| 455 |
+
if (!inQuotes) {
|
| 456 |
+
if (char === '(') depth++;
|
| 457 |
+
else if (char === ')') depth--;
|
|
|
|
| 458 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
+
// Handle key-value separation
|
| 461 |
+
if (char === ':' && depth === 0 && !inQuotes && readingKey) {
|
| 462 |
+
readingKey = false;
|
| 463 |
+
currentKey = currentKey.trim();
|
| 464 |
+
continue;
|
| 465 |
+
}
|
|
|
|
| 466 |
|
| 467 |
+
// Handle comma separation
|
| 468 |
+
if (char === ',' && depth === 0 && !inQuotes && !readingKey) {
|
| 469 |
+
// Store the key-value pair
|
| 470 |
+
currentValue = currentValue.trim();
|
| 471 |
+
|
| 472 |
+
// Parse the value
|
| 473 |
+
if (currentValue.match(/^\w+\s*\(inputs\(/)) {
|
| 474 |
+
// This is a nested block
|
| 475 |
+
result[currentKey] = currentValue;
|
| 476 |
+
} else if (currentValue.match(/^-?\d+(\.\d+)?$/)) {
|
| 477 |
+
// This is a number
|
| 478 |
+
result[currentKey] = parseFloat(currentValue);
|
| 479 |
+
} else if (currentValue === 'true' || currentValue === 'false') {
|
| 480 |
+
// This is a boolean
|
| 481 |
+
result[currentKey] = currentValue === 'true';
|
| 482 |
+
} else {
|
| 483 |
+
// This is a string (remove quotes if present)
|
| 484 |
+
result[currentKey] = currentValue.replace(/^["']|["']$/g, '');
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
// Reset for next key-value pair
|
| 488 |
+
currentKey = '';
|
| 489 |
+
currentValue = '';
|
| 490 |
+
readingKey = true;
|
| 491 |
+
continue;
|
| 492 |
+
}
|
| 493 |
|
| 494 |
+
// Accumulate characters
|
| 495 |
+
if (readingKey) {
|
| 496 |
+
currentKey += char;
|
| 497 |
+
} else {
|
| 498 |
+
currentValue += char;
|
| 499 |
+
}
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
// Handle the last key-value pair
|
| 503 |
+
if (currentKey && currentValue) {
|
| 504 |
+
currentKey = currentKey.trim();
|
| 505 |
+
currentValue = currentValue.trim();
|
| 506 |
|
| 507 |
+
// Parse the value
|
| 508 |
+
if (currentValue.match(/^\w+\s*\(inputs\(/)) {
|
| 509 |
+
// This is a nested block
|
| 510 |
+
result[currentKey] = currentValue;
|
| 511 |
+
} else if (currentValue.match(/^-?\d+(\.\d+)?$/)) {
|
| 512 |
+
// This is a number
|
| 513 |
+
result[currentKey] = parseFloat(currentValue);
|
| 514 |
+
} else if (currentValue === 'true' || currentValue === 'false') {
|
| 515 |
+
// This is a boolean
|
| 516 |
+
result[currentKey] = currentValue === 'true';
|
| 517 |
+
} else {
|
| 518 |
+
// This is a string (remove quotes if present)
|
| 519 |
+
result[currentKey] = currentValue.replace(/^["']|["']$/g, '');
|
| 520 |
+
}
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
return result;
|
| 524 |
}
|
| 525 |
|
| 526 |
+
// Create the block and all its nested children
|
| 527 |
+
const newBlock = parseAndCreateBlock(data.block_spec, true);
|
| 528 |
+
|
| 529 |
if (newBlock) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 530 |
blockId = newBlock.id;
|
| 531 |
success = true;
|
| 532 |
+
console.log('[SSE CREATE] Successfully created block with children:', blockId, newBlock.type);
|
| 533 |
} else {
|
| 534 |
+
throw new Error(`Failed to create block from specification`);
|
| 535 |
}
|
| 536 |
|
| 537 |
} catch (e) {
|
|
|
|
| 653 |
});
|
| 654 |
};
|
| 655 |
|
| 656 |
+
// Track if chat backend is available
|
| 657 |
+
let chatBackendAvailable = false;
|
| 658 |
+
let chatUpdateQueue = [];
|
| 659 |
+
let chatRetryTimeout = null;
|
| 660 |
+
|
| 661 |
+
// Function to check if chat backend is available
|
| 662 |
+
const checkChatBackend = async () => {
|
| 663 |
+
try {
|
| 664 |
+
const response = await fetch("http://127.0.0.1:7861/update_chat", {
|
| 665 |
+
method: "POST",
|
| 666 |
+
headers: { "Content-Type": "application/json" },
|
| 667 |
+
body: JSON.stringify({ code: "" }),
|
| 668 |
+
});
|
| 669 |
+
if (response.ok) {
|
| 670 |
+
chatBackendAvailable = true;
|
| 671 |
+
console.log("[Blockly] Chat backend is available");
|
| 672 |
+
// Process any queued updates
|
| 673 |
+
processChatUpdateQueue();
|
| 674 |
+
return true;
|
| 675 |
+
}
|
| 676 |
+
} catch (err) {
|
| 677 |
+
chatBackendAvailable = false;
|
| 678 |
+
}
|
| 679 |
+
return false;
|
| 680 |
+
};
|
| 681 |
+
|
| 682 |
+
// Process queued chat updates
|
| 683 |
+
const processChatUpdateQueue = () => {
|
| 684 |
+
if (chatBackendAvailable && chatUpdateQueue.length > 0) {
|
| 685 |
+
const code = chatUpdateQueue.pop(); // Get the latest update
|
| 686 |
+
chatUpdateQueue = []; // Clear the queue
|
| 687 |
+
sendChatUpdate(code);
|
| 688 |
+
}
|
| 689 |
+
};
|
| 690 |
+
|
| 691 |
+
// Send chat update with retry logic
|
| 692 |
+
const sendChatUpdate = async (code, retryCount = 0) => {
|
| 693 |
+
try {
|
| 694 |
+
const response = await fetch("http://127.0.0.1:7861/update_chat", {
|
| 695 |
+
method: "POST",
|
| 696 |
+
headers: { "Content-Type": "application/json" },
|
| 697 |
+
body: JSON.stringify({ code }),
|
| 698 |
+
});
|
| 699 |
+
|
| 700 |
+
if (response.ok) {
|
| 701 |
+
chatBackendAvailable = true;
|
| 702 |
+
console.log("[Blockly] Sent updated Chat code to backend");
|
| 703 |
+
} else {
|
| 704 |
+
throw new Error(`Server responded with status ${response.status}`);
|
| 705 |
+
}
|
| 706 |
+
} catch (err) {
|
| 707 |
+
console.warn(`[Blockly] Chat backend not ready (attempt ${retryCount + 1}):`, err.message);
|
| 708 |
+
chatBackendAvailable = false;
|
| 709 |
+
|
| 710 |
+
// Queue this update for retry
|
| 711 |
+
if (retryCount < 5) {
|
| 712 |
+
const delay = Math.min(1000 * Math.pow(2, retryCount), 10000); // Exponential backoff, max 10s
|
| 713 |
+
setTimeout(() => {
|
| 714 |
+
if (!chatBackendAvailable) {
|
| 715 |
+
checkChatBackend().then(available => {
|
| 716 |
+
if (available) {
|
| 717 |
+
sendChatUpdate(code, retryCount + 1);
|
| 718 |
+
} else if (retryCount < 4) {
|
| 719 |
+
sendChatUpdate(code, retryCount + 1);
|
| 720 |
+
}
|
| 721 |
+
});
|
| 722 |
+
}
|
| 723 |
+
}, delay);
|
| 724 |
+
}
|
| 725 |
+
}
|
| 726 |
+
};
|
| 727 |
+
|
| 728 |
// Update function for the Chat generator (AI Chat tab)
|
| 729 |
const updateChatCode = () => {
|
| 730 |
let code = chatGenerator.workspaceToCode(ws);
|
|
|
|
| 737 |
codeEl.textContent = code;
|
| 738 |
}
|
| 739 |
|
| 740 |
+
// If backend is available, send immediately
|
| 741 |
+
if (chatBackendAvailable) {
|
| 742 |
+
sendChatUpdate(code);
|
| 743 |
+
} else {
|
| 744 |
+
// Queue the update and try to establish connection
|
| 745 |
+
chatUpdateQueue.push(code);
|
| 746 |
+
|
| 747 |
+
// Clear any existing retry timeout
|
| 748 |
+
if (chatRetryTimeout) {
|
| 749 |
+
clearTimeout(chatRetryTimeout);
|
| 750 |
+
}
|
| 751 |
+
|
| 752 |
+
// Try to connect to backend
|
| 753 |
+
checkChatBackend();
|
| 754 |
+
|
| 755 |
+
// Set up periodic retry
|
| 756 |
+
chatRetryTimeout = setTimeout(() => {
|
| 757 |
+
checkChatBackend();
|
| 758 |
+
}, 2000);
|
| 759 |
+
}
|
| 760 |
};
|
| 761 |
|
| 762 |
try {
|
|
|
|
| 822 |
}
|
| 823 |
|
| 824 |
updateCode();
|
| 825 |
+
|
| 826 |
+
// Check if chat backend is available before first update
|
| 827 |
+
checkChatBackend().then(() => {
|
| 828 |
+
updateChatCode();
|
| 829 |
+
});
|
| 830 |
+
|
| 831 |
+
// Also set up periodic health checks for the chat backend
|
| 832 |
+
setInterval(() => {
|
| 833 |
+
if (!chatBackendAvailable) {
|
| 834 |
+
checkChatBackend();
|
| 835 |
+
}
|
| 836 |
+
}, 5000); // Check every 5 seconds if not connected
|
| 837 |
|
| 838 |
ws.addChangeListener((e) => {
|
| 839 |
if (e.isUiEvent) return;
|