Spaces:
Running
Running
owenkaplinsky
commited on
Commit
·
6eec7f7
1
Parent(s):
fc2c1ea
Fixes; prompt improvements
Browse files- project/blocks.txt +43 -43
- project/chat.py +66 -35
- project/src/generators/chat.js +8 -8
- project/src/index.js +146 -3
project/blocks.txt
CHANGED
|
@@ -1,60 +1,60 @@
|
|
| 1 |
# AI
|
| 2 |
-
llm_call(inputs(MODEL: "gpt-3.5-turbo-0125/gpt-4o-2024-08-06/gpt-5-mini-2025-08-07/gpt-5-2025-08-07/gpt-4o-search-preview-2025-03-11", PROMPT: value))
|
| 3 |
|
| 4 |
# JSON and API
|
| 5 |
-
in_json(inputs(NAME: value, JSON: value)) // NAME is the value you want to extract from the JSON
|
| 6 |
-
make_json(inputs(KEYN: value, FIELDN: value)) // N starts at 0; you can make as many N as you want. Each key goes with one field
|
| 7 |
-
call_api(inputs(METHOD: "GET/POST/PUT/DELETE", URL: value, HEADERS: value)) // HEADERS is optional
|
| 8 |
|
| 9 |
# Logic
|
| 10 |
-
controls_if(inputs(IF: value, IFELSEN0: value, ELSE)) // IF is REQUIRED (the condition). IFELSEN0, IFELSEN1, etc are OPTIONAL (additional else-if conditions). ELSE is OPTIONAL (no value needed, just include the word). DO NOT use input_name with controls_if creation; specify all conditions in the inputs. After creating, use input_name to place statements: "DO0" (IF then-statements), "DO1" (first ELSE-IF then-statements), "ELSE" (final else statements)
|
| 11 |
-
logic_negate(inputs())
|
| 12 |
-
logic_boolean(inputs(BOOL: "TRUE/FALSE"))
|
| 13 |
-
logic_null(inputs())
|
| 14 |
-
logic_compare(inputs(OP: "EQ/NEQ/LT/LTE/GT/GTE", A: value, B: value))
|
| 15 |
-
logic_operation(inputs(OP: "AND/OR", A: value, B: value))
|
| 16 |
|
| 17 |
# Loops
|
| 18 |
-
controls_repeat_ext(inputs(TIMES: value))
|
| 19 |
-
controls_whileUntil(inputs(MODE: "WHILE/UNTIL", BOOL: value))
|
| 20 |
-
controls_for(inputs(VAR: value, FROM: value, TO: value, BY: value)) // VAR is a variable ID. BY is the increment amount
|
| 21 |
-
controls_forEach(inputs(VAR: value), LIST: value) // VAR is a variable ID
|
| 22 |
-
controls_flow_statements(inputs(FLOW: "CONTINUE/BREAK")) // Must go **inside** of a loop
|
| 23 |
|
| 24 |
# Math
|
| 25 |
-
math_number(inputs(NUM: value))
|
| 26 |
-
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
|
| 27 |
-
math_single(inputs(OP: "ROOT/ABS/NEG/LN/LOG10/EXP/POW10", NUM: value)) // EXP is e^num
|
| 28 |
-
math_number_property(inputs(PROPERTY: "EVEN/ODD/PRIME/WHOLE/POSITIVE/NEGATIVE", NUMBER_TO_CHECK: value)) // Boolean output
|
| 29 |
-
math_round(inputs(OP: "ROUND/ROUNDUP/ROUNDDOWN", NUM: value))
|
| 30 |
-
math_modulo(inputs(DIVIDEND: value, DIVISOR: value))
|
| 31 |
-
math_constrain(VALUE: value, LOW: value, HIGH: value)
|
| 32 |
-
math_random_int(inputs(FROM: value, TO: value))
|
| 33 |
|
| 34 |
# Text
|
| 35 |
-
text(inputs(TEXT: value))
|
| 36 |
-
text_length(VALUE: value)
|
| 37 |
-
text_join(inputs(ADDN: value)) // N starts at 0; you can make as many N as you want
|
| 38 |
-
text_isEmpty(inputs(VALUE: value)) // Boolean output
|
| 39 |
-
text_changeCase(inputs(CASE: "UPPERCASE/LOWERCASE/TITLECASE", TEXT: value))
|
| 40 |
-
text_getSubstring(inputs(WHERE1: "FROM_START/FROM_END/FIRST", WHERE2: "FROM_START/FROM_END/FIRST", STRING: value, AT1: value, AT2: value)) // For AT1 and AT2, only use them if the associated WHEREN is not "FROM_START"
|
| 41 |
-
text_reverse(inputs(TEXT: value))
|
| 42 |
-
text_count(inputs(SUB: value, TEXT: value)) // Integer output
|
| 43 |
-
text_replace(inputs(FROM: value, TO: value, TEXT: value))
|
| 44 |
-
text_trim(inputs(MODE: "BOTH/LEFT/RIGHT", TEXT: value)) // Trim spaces from text
|
| 45 |
|
| 46 |
# Lists
|
| 47 |
-
lists_length(inputs(VALUE: value))
|
| 48 |
-
lists_isEmpty(inputs(VALUE: value)) // Boolean output
|
| 49 |
-
lists_indexOf(inputs(END: "FIRST/LAST", VALUE: value, FIND: value))
|
| 50 |
-
lists_reverse(inputs(LIST: value))
|
| 51 |
-
lists_create_with(inputs(ADDN: value)) // N starts at 0; you can make as many N as you want
|
| 52 |
-
lists_sort(inputs(TYPE: "NUMERIC/TEXT/IGNORE_CASE", DIRECTION: "1/-1")) // For direction, 1 is ascending, -1 is descending
|
| 53 |
|
| 54 |
# Variables
|
| 55 |
-
variables_get(inputs(VAR: value)) // VAR is a variable ID
|
| 56 |
-
variables_set(inputs(VAR: value, VALUE: value)) // VAR is a variable ID
|
| 57 |
-
math_change(inputs(VAR: value, DELTA: value)) // VAR is a variable ID. To use this, you MUST have used `variables_set` before this
|
| 58 |
|
| 59 |
# MCP Block inputs
|
| 60 |
-
input_reference_NAME(inputs()) // Replace "NAME" with the name of the MCP block input. Any inputs for the one MCP block can have their blocks made with this. You may ONLY use this if the MCP has an input with this name. If needed you must create inputs and outputs first for the MCP block
|
|
|
|
| 1 |
# AI
|
| 2 |
+
VALUE: llm_call(inputs(MODEL: "gpt-3.5-turbo-0125/gpt-4o-2024-08-06/gpt-5-mini-2025-08-07/gpt-5-2025-08-07/gpt-4o-search-preview-2025-03-11", PROMPT: value))
|
| 3 |
|
| 4 |
# JSON and API
|
| 5 |
+
VALUE: in_json(inputs(NAME: value, JSON: value)) // NAME is the value you want to extract from the JSON
|
| 6 |
+
VALUE: make_json(inputs(KEYN: value, FIELDN: value)) // N starts at 0; you can make as many N as you want. Each key goes with one field
|
| 7 |
+
VALUE: call_api(inputs(METHOD: "GET/POST/PUT/DELETE", URL: value, HEADERS: value)) // HEADERS is optional
|
| 8 |
|
| 9 |
# Logic
|
| 10 |
+
STATEMENT: controls_if(inputs(IF: value, IFELSEN0: value, ELSE)) // IF is REQUIRED (the condition). IFELSEN0, IFELSEN1, etc are OPTIONAL (additional else-if conditions). ELSE is OPTIONAL (no value needed, just include the word). DO NOT use input_name with controls_if creation; specify all conditions in the inputs. After creating, use input_name to place statements: "DO0" (IF then-statements), "DO1" (first ELSE-IF then-statements), "ELSE" (final else statements)
|
| 11 |
+
VALUE: logic_negate(inputs())
|
| 12 |
+
VALUE: logic_boolean(inputs(BOOL: "TRUE/FALSE"))
|
| 13 |
+
VALUE: logic_null(inputs())
|
| 14 |
+
VALUE: logic_compare(inputs(OP: "EQ/NEQ/LT/LTE/GT/GTE", A: value, B: value))
|
| 15 |
+
VALUE: logic_operation(inputs(OP: "AND/OR", A: value, B: value))
|
| 16 |
|
| 17 |
# Loops
|
| 18 |
+
STATEMENT: controls_repeat_ext(inputs(TIMES: value))
|
| 19 |
+
STATEMENT: controls_whileUntil(inputs(MODE: "WHILE/UNTIL", BOOL: value))
|
| 20 |
+
STATEMENT: controls_for(inputs(VAR: value, FROM: value, TO: value, BY: value)) // VAR is a variable ID. BY is the increment amount
|
| 21 |
+
STATEMENT: controls_forEach(inputs(VAR: value), LIST: value) // VAR is a variable ID
|
| 22 |
+
STATEMENT: controls_flow_statements(inputs(FLOW: "CONTINUE/BREAK")) // Must go **inside** of a loop
|
| 23 |
|
| 24 |
# Math
|
| 25 |
+
VALUE: math_number(inputs(NUM: value))
|
| 26 |
+
VALUE: 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
|
| 27 |
+
VALUE: math_single(inputs(OP: "ROOT/ABS/NEG/LN/LOG10/EXP/POW10", NUM: value)) // EXP is e^num
|
| 28 |
+
VALUE: math_number_property(inputs(PROPERTY: "EVEN/ODD/PRIME/WHOLE/POSITIVE/NEGATIVE", NUMBER_TO_CHECK: value)) // Boolean output
|
| 29 |
+
VALUE: math_round(inputs(OP: "ROUND/ROUNDUP/ROUNDDOWN", NUM: value))
|
| 30 |
+
VALUE: math_modulo(inputs(DIVIDEND: value, DIVISOR: value))
|
| 31 |
+
VALUE: math_constrain(VALUE: value, LOW: value, HIGH: value)
|
| 32 |
+
VALUE: math_random_int(inputs(FROM: value, TO: value))
|
| 33 |
|
| 34 |
# Text
|
| 35 |
+
VALUE: text(inputs(TEXT: value))
|
| 36 |
+
VALUE: text_length(VALUE: value)
|
| 37 |
+
VALUE: text_join(inputs(ADDN: value)) // N starts at 0; you can make as many N as you want
|
| 38 |
+
VALUE: text_isEmpty(inputs(VALUE: value)) // Boolean output
|
| 39 |
+
VALUE: text_changeCase(inputs(CASE: "UPPERCASE/LOWERCASE/TITLECASE", TEXT: value))
|
| 40 |
+
VALUE: text_getSubstring(inputs(WHERE1: "FROM_START/FROM_END/FIRST", WHERE2: "FROM_START/FROM_END/FIRST", STRING: value, AT1: value, AT2: value)) // For AT1 and AT2, only use them if the associated WHEREN is not "FROM_START"
|
| 41 |
+
VALUE: text_reverse(inputs(TEXT: value))
|
| 42 |
+
VALUE: text_count(inputs(SUB: value, TEXT: value)) // Integer output
|
| 43 |
+
VALUE: text_replace(inputs(FROM: value, TO: value, TEXT: value))
|
| 44 |
+
VALUE: text_trim(inputs(MODE: "BOTH/LEFT/RIGHT", TEXT: value)) // Trim spaces from text
|
| 45 |
|
| 46 |
# Lists
|
| 47 |
+
VALUE: lists_length(inputs(VALUE: value))
|
| 48 |
+
VALUE: lists_isEmpty(inputs(VALUE: value)) // Boolean output
|
| 49 |
+
VALUE: lists_indexOf(inputs(END: "FIRST/LAST", VALUE: value, FIND: value))
|
| 50 |
+
VALUE: lists_reverse(inputs(LIST: value))
|
| 51 |
+
VALUE: lists_create_with(inputs(ADDN: value)) // N starts at 0; you can make as many N as you want
|
| 52 |
+
VALUE: lists_sort(inputs(TYPE: "NUMERIC/TEXT/IGNORE_CASE", DIRECTION: "1/-1")) // For direction, 1 is ascending, -1 is descending
|
| 53 |
|
| 54 |
# Variables
|
| 55 |
+
VALUE: variables_get(inputs(VAR: value)) // VAR is a variable ID
|
| 56 |
+
VALUE: variables_set(inputs(VAR: value, VALUE: value)) // VAR is a variable ID
|
| 57 |
+
VALUE: math_change(inputs(VAR: value, DELTA: value)) // VAR is a variable ID. To use this, you MUST have used `variables_set` before this
|
| 58 |
|
| 59 |
# MCP Block inputs
|
| 60 |
+
VALUE: input_reference_NAME(inputs()) // Replace "NAME" with the name of the MCP block input. Any inputs for the one MCP block can have their blocks made with this. You may ONLY use this if the MCP has an input with this name. If needed you must create inputs and outputs first for the MCP block
|
project/chat.py
CHANGED
|
@@ -548,13 +548,13 @@ def create_gradio_interface():
|
|
| 548 |
SYSTEM_PROMPT = f"""You are an AI assistant that helps users build **MCP servers** using Blockly blocks.
|
| 549 |
|
| 550 |
You'll receive the workspace state in this format:
|
| 551 |
-
`blockId
|
| 552 |
|
| 553 |
-
Block ID parsing: Block IDs are everything before `
|
| 554 |
-
Example: `?fHZRh^|us|9bECO![$=
|
| 555 |
|
| 556 |
Special cases:
|
| 557 |
-
- `create_mcp` and `func_def` use `blockId
|
| 558 |
- Indentation or nesting shows logic hierarchy (like loops or conditionals).
|
| 559 |
|
| 560 |
---
|
|
@@ -579,11 +579,11 @@ def create_gradio_interface():
|
|
| 579 |
---
|
| 580 |
|
| 581 |
### Deleting Blocks
|
| 582 |
-
- Each block starts with its ID, like `blockId
|
| 583 |
- To delete a block, specify its block ID.
|
| 584 |
- Any block except the main `create_mcp` block can be deleted.
|
| 585 |
|
| 586 |
-
`blockId
|
| 587 |
|
| 588 |
Use the exact ID from the workspace.
|
| 589 |
|
|
@@ -597,16 +597,9 @@ def create_gradio_interface():
|
|
| 597 |
You cannot create a `create_mcp` block, but you may edit its inputs using the `edit_mcp` tool.
|
| 598 |
|
| 599 |
### Block Types: Statement vs Value
|
| 600 |
-
**Statement blocks**
|
| 601 |
-
- Loops (repeat, while, for)
|
| 602 |
-
- Conditionals (if/else)
|
| 603 |
-
- Any block that wraps other blocks
|
| 604 |
|
| 605 |
-
**Value blocks** produce
|
| 606 |
-
- Math blocks (math_number, math_arithmetic)
|
| 607 |
-
- Text blocks (text, text_join)
|
| 608 |
-
- Logic blocks (logic_compare, logic_operation)
|
| 609 |
-
- Any block that outputs a result for something else to use
|
| 610 |
|
| 611 |
### How to Place Blocks
|
| 612 |
|
|
@@ -636,21 +629,20 @@ def create_gradio_interface():
|
|
| 636 |
- `input_name: "DO1"`: first ELSE-IF branch
|
| 637 |
- `input_name: "ELSE"`: ELSE branch
|
| 638 |
|
|
|
|
|
|
|
| 639 |
**Placement types** - use `blockID` and `type` parameters:
|
| 640 |
|
| 641 |
- `type: "under"` - For statement blocks inside containers. Create the container first, then create statement blocks using the container's ID.
|
| 642 |
Example: Create a loop first, then use `blockID: loopID, type: "under"` to place code inside it.
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
Example: `create_block(text_append(...), blockID: ifBlockID, type: "under", input_name: "DO0")` places the block in the first THEN branch of an IF block.
|
| 646 |
-
**CRITICAL: When using `type: "under"`, `input_name` must ONLY contain "DO0", "DO1", "ELSE", or other statement input names. NEVER use R0, R1, R2, etc. with `type: "under"`.**
|
| 647 |
|
| 648 |
-
- `type: "input"` - ONLY for value blocks placed in MCP output slots
|
| 649 |
Example: `text(inputs(TEXT: "hello"))` with `type: "input", input_name: "R0"` places the text block in the MCP's first output slot.
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
**CRITICAL: NEVER combine `type: "under"` with `input_name: "R0"`, `"R1"`, `"R2"`, etc. These R-numbered slots are only for `type: "input"` when placing into MCP outputs.**
|
| 654 |
|
| 655 |
**Value block nesting** - For value blocks inside other blocks: nest them directly in the create_block command (do not use `blockID` or `type`).
|
| 656 |
Example: `math_arithmetic(inputs(A: math_number(inputs(NUM: 5)), B: math_number(inputs(NUM: 3))))`
|
|
@@ -671,6 +663,10 @@ def create_gradio_interface():
|
|
| 671 |
- WRONG: `math_single(inputs(OP: ROOT, NUM: value)`
|
| 672 |
- CORRECT: `math_single(inputs(OP: "ROOT", NUM: value)`
|
| 673 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 674 |
For value inputs, never use raw values. Always wrap in a block:
|
| 675 |
- WRONG: `math_arithmetic(inputs(A: 5, B: "hi"))`
|
| 676 |
- RIGHT: `math_arithmetic(inputs(A: math_number(inputs(NUM: 5)), B: text(inputs(TEXT: "hi"))))`
|
|
@@ -682,7 +678,7 @@ def create_gradio_interface():
|
|
| 682 |
### Variables
|
| 683 |
Variables appear as:
|
| 684 |
|
| 685 |
-
`varId
|
| 686 |
|
| 687 |
---
|
| 688 |
|
|
@@ -721,6 +717,29 @@ def create_gradio_interface():
|
|
| 721 |
|
| 722 |
---
|
| 723 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
Tool responses appear under the assistant role, but they are not part of your own words. Never treat tool output as
|
| 725 |
something you said, and never fabricate or echo fake tool outputs.
|
| 726 |
|
|
@@ -738,20 +757,27 @@ def create_gradio_interface():
|
|
| 738 |
This is for *your own* understanding: work out the logic *before* translating to blocks.
|
| 739 |
Example: `for item in items: result = process(item); output = combine(result)`
|
| 740 |
|
| 741 |
-
3. **Create a
|
|
|
|
| 742 |
|
| 743 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 744 |
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
- If you're defining what the MCP returns, you must either create output blocks inside the MCP, or edit the MCP block to add outputs first
|
| 750 |
|
| 751 |
-
|
|
|
|
| 752 |
|
| 753 |
-
"""
|
| 754 |
|
|
|
|
| 755 |
tools = [
|
| 756 |
{
|
| 757 |
"type": "function",
|
|
@@ -775,13 +801,18 @@ def create_gradio_interface():
|
|
| 775 |
"parameters": {
|
| 776 |
"type": "object",
|
| 777 |
"properties": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 778 |
"command": {
|
| 779 |
"type": "string",
|
| 780 |
"description": "The create block command using the custom DSL format.",
|
| 781 |
},
|
| 782 |
"blockID": {
|
| 783 |
"type": "string",
|
| 784 |
-
"description": "The ID of the target block for placement.",
|
| 785 |
},
|
| 786 |
"type": {
|
| 787 |
"type": "string",
|
|
|
|
| 548 |
SYSTEM_PROMPT = f"""You are an AI assistant that helps users build **MCP servers** using Blockly blocks.
|
| 549 |
|
| 550 |
You'll receive the workspace state in this format:
|
| 551 |
+
`blockId → block_name(inputs(input_name: value))`
|
| 552 |
|
| 553 |
+
Block ID parsing: Block IDs are everything before ` → ` (space-arrow-space). IDs are always complex/long strings.
|
| 554 |
+
Example: `?fHZRh^|us|9bECO![$= → text(inputs(TEXT: "hello"))`, ID is `?fHZRh^|us|9bECO![$=`
|
| 555 |
|
| 556 |
Special cases:
|
| 557 |
+
- `create_mcp` and `func_def` use `blockId → block_name(inputs(input_name: type), outputs(output_name: value))`
|
| 558 |
- Indentation or nesting shows logic hierarchy (like loops or conditionals).
|
| 559 |
|
| 560 |
---
|
|
|
|
| 579 |
---
|
| 580 |
|
| 581 |
### Deleting Blocks
|
| 582 |
+
- Each block starts with its ID, like `blockId → block_name(...)`.
|
| 583 |
- To delete a block, specify its block ID.
|
| 584 |
- Any block except the main `create_mcp` block can be deleted.
|
| 585 |
|
| 586 |
+
`blockId → code`
|
| 587 |
|
| 588 |
Use the exact ID from the workspace.
|
| 589 |
|
|
|
|
| 597 |
You cannot create a `create_mcp` block, but you may edit its inputs using the `edit_mcp` tool.
|
| 598 |
|
| 599 |
### Block Types: Statement vs Value
|
| 600 |
+
**Statement blocks** (loops, conditionals, any container) hold other blocks inside them and require sequential creation to get the container's ID first.
|
|
|
|
|
|
|
|
|
|
| 601 |
|
| 602 |
+
**Value blocks** (math, text, logic, comparison) produce values that plug into inputs and must be built entirely in one nested create_block call.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 603 |
|
| 604 |
### How to Place Blocks
|
| 605 |
|
|
|
|
| 629 |
- `input_name: "DO1"`: first ELSE-IF branch
|
| 630 |
- `input_name: "ELSE"`: ELSE branch
|
| 631 |
|
| 632 |
+
YOU CANNOT EDIT THE IF BLOCK LATER. IF YOU WILL NEED TO HAVE AN ELSE OR IFELSE LATER, YOU MUST CREATE IT WITH ALL BRANCHES FROM THE START.
|
| 633 |
+
|
| 634 |
**Placement types** - use `blockID` and `type` parameters:
|
| 635 |
|
| 636 |
- `type: "under"` - For statement blocks inside containers. Create the container first, then create statement blocks using the container's ID.
|
| 637 |
Example: Create a loop first, then use `blockID: loopID, type: "under"` to place code inside it.
|
| 638 |
+
Optional: use `input_name` for statement input names only (e.g., "DO0", "DO1", "ELSE" for IF blocks).
|
| 639 |
+
Example: `create_block(text_append(...), blockID: ifBlockID, type: "under", input_name: "DO0")` places the block in the IF branch.
|
|
|
|
|
|
|
| 640 |
|
| 641 |
+
- `type: "input"` - ONLY for value blocks placed in MCP output slots (R0, R1, R2, etc).
|
| 642 |
Example: `text(inputs(TEXT: "hello"))` with `type: "input", input_name: "R0"` places the text block in the MCP's first output slot.
|
| 643 |
+
Requirement: The create_mcp block must have explicit outputs defined (you will see `outputs(...)` in the workspace state). Do not use this if outputs are not visible.
|
| 644 |
+
|
| 645 |
+
Key rule: Statement input names (DO0, DO1, ELSE) are for `type: "under"`. Output slot names (R0, R1, R2) are for `type: "input"`. Never mix them.
|
|
|
|
| 646 |
|
| 647 |
**Value block nesting** - For value blocks inside other blocks: nest them directly in the create_block command (do not use `blockID` or `type`).
|
| 648 |
Example: `math_arithmetic(inputs(A: math_number(inputs(NUM: 5)), B: math_number(inputs(NUM: 3))))`
|
|
|
|
| 663 |
- WRONG: `math_single(inputs(OP: ROOT, NUM: value)`
|
| 664 |
- CORRECT: `math_single(inputs(OP: "ROOT", NUM: value)`
|
| 665 |
|
| 666 |
+
**Variable IDs must always be quoted with double quotes**, even if they look like identifiers. Variable IDs often contain special characters like parentheses, pipes, and semicolons. ALWAYS quote the variable ID:
|
| 667 |
+
- WRONG: variables_set(inputs(VAR: ..., VALUE: ...))
|
| 668 |
+
- CORRECT: variables_set(inputs(VAR: "...", VALUE: ...))
|
| 669 |
+
|
| 670 |
For value inputs, never use raw values. Always wrap in a block:
|
| 671 |
- WRONG: `math_arithmetic(inputs(A: 5, B: "hi"))`
|
| 672 |
- RIGHT: `math_arithmetic(inputs(A: math_number(inputs(NUM: 5)), B: text(inputs(TEXT: "hi"))))`
|
|
|
|
| 678 |
### Variables
|
| 679 |
Variables appear as:
|
| 680 |
|
| 681 |
+
`varId → varName`
|
| 682 |
|
| 683 |
---
|
| 684 |
|
|
|
|
| 717 |
|
| 718 |
---
|
| 719 |
|
| 720 |
+
## EVERY VALUE MUST HAVE A DESTINATION
|
| 721 |
+
|
| 722 |
+
Before constructing ANY expression block (text_join, math operations, etc.), identify where it goes:
|
| 723 |
+
- Assigned to a variable (via set_var block)
|
| 724 |
+
- Passed as input to another block (nested in the create_block call)
|
| 725 |
+
- Placed in an MCP output slot (using type: "input" and input_name: "R#")
|
| 726 |
+
|
| 727 |
+
Do NOT create orphaned expression blocks with no destination. They serve no purpose.
|
| 728 |
+
|
| 729 |
+
Always build the container/assignment block FIRST, then construct the value expression INSIDE it, both in a single call.
|
| 730 |
+
|
| 731 |
+
### No Early Returns in Conditionals
|
| 732 |
+
|
| 733 |
+
Blockly does not support early returns from within conditional branches. You MUST use a variable to store the result and return that variable in the MCP output:
|
| 734 |
+
|
| 735 |
+
1. Create a variable
|
| 736 |
+
2. In each branch, use a `set_var` block to assign the value to that variable. The VALUE input of set_var must contain your text_join or expression.
|
| 737 |
+
3. Return the variable as an MCP output
|
| 738 |
+
|
| 739 |
+
Always collect results in a variable first; never create expression blocks that exist outside a set_var or MCP output.
|
| 740 |
+
|
| 741 |
+
---
|
| 742 |
+
|
| 743 |
Tool responses appear under the assistant role, but they are not part of your own words. Never treat tool output as
|
| 744 |
something you said, and never fabricate or echo fake tool outputs.
|
| 745 |
|
|
|
|
| 757 |
This is for *your own* understanding: work out the logic *before* translating to blocks.
|
| 758 |
Example: `for item in items: result = process(item); output = combine(result)`
|
| 759 |
|
| 760 |
+
3. **Create a detailed, step-by-step to-do list**:
|
| 761 |
+
List every action you will take, in exact order, with no omissions. Build from the outside in: create outer containers first, then add inner blocks inside them.
|
| 762 |
|
| 763 |
+
For every step, explicitly specify:
|
| 764 |
+
- What action you will perform
|
| 765 |
+
- Which block you are acting on
|
| 766 |
+
- Which input or output slot you are using
|
| 767 |
+
- All parameters involved (including type and input_name)
|
| 768 |
+
- Every variable used and where it is used
|
| 769 |
|
| 770 |
+
For every IF statement you create, you must explicitly state:
|
| 771 |
+
- The number of IFELSE branches (0, 1, 2, etc.)
|
| 772 |
+
- The number of ELSE branches (0 or 1)
|
| 773 |
+
- The exact condition that causes each branch to activate
|
|
|
|
| 774 |
|
| 775 |
+
THESE DECLARATIONS ARE REQUIRED EVERY TIME AN IF STATEMENT IS MENTIONED, AND YOU MUST ALWAYS PROVIDE EXACTLY THREE INTEGERS WITH NO EXCEPTIONS OR SUBSTITUTIONS. FAILURE TO DO SO IMMEDIATELY INVALIDATES THE RESPONSE IN ITS ENTIRETY.
|
| 776 |
+
YOU MUST HAVE EXPLICITLY SAID THESE THREE VALUES NO MATTER WHAT. THIS IS NON-NEGOTIABLE. THIS IS A HARD REQUIREMENT. ALWAYS SAY THIS, EVERY SINGLE TIME, NO MATTER WHAT.
|
| 777 |
|
| 778 |
+
4. **Check the create_mcp block state:** Before using `type: "input"` and `input_name: "R0"`, verify that the create_mcp block has outputs defined in the workspace state. If you do not see `outputs(...)` in the create_mcp block, do NOT use these parameters.
|
| 779 |
|
| 780 |
+
5. Perform the actions in order without asking for approval or asking to wait for intermediate results."""
|
| 781 |
tools = [
|
| 782 |
{
|
| 783 |
"type": "function",
|
|
|
|
| 801 |
"parameters": {
|
| 802 |
"type": "object",
|
| 803 |
"properties": {
|
| 804 |
+
# Throwaway parameter to get the agent to think about IF rules.
|
| 805 |
+
"if_notes": {
|
| 806 |
+
"type": "string",
|
| 807 |
+
"description": "If you are going to make an if statement, YOU ARE REQUIRED TO USE THIS. Write the amount if IF/IFELSE/ELSE you will need.",
|
| 808 |
+
},
|
| 809 |
"command": {
|
| 810 |
"type": "string",
|
| 811 |
"description": "The create block command using the custom DSL format.",
|
| 812 |
},
|
| 813 |
"blockID": {
|
| 814 |
"type": "string",
|
| 815 |
+
"description": "The ID of the target block for placement. Do not use this if your goal is to place a block detached in the workspace.",
|
| 816 |
},
|
| 817 |
"type": {
|
| 818 |
"type": "string",
|
project/src/generators/chat.js
CHANGED
|
@@ -70,8 +70,8 @@ forBlock['create_mcp'] = function (block, generator) {
|
|
| 70 |
let body = generator.statementToCode(block, 'BODY');
|
| 71 |
|
| 72 |
// Construct the create_mcp call with inputs and outputs
|
| 73 |
-
// Include block ID for deletion tracking with
|
| 74 |
-
let code = `${block.id}
|
| 75 |
|
| 76 |
// Add the function body
|
| 77 |
if (body) {
|
|
@@ -120,8 +120,8 @@ forBlock['func_def'] = function (block, generator) {
|
|
| 120 |
let body = generator.statementToCode(block, 'BODY');
|
| 121 |
|
| 122 |
// Construct the func_def call with inputs and outputs
|
| 123 |
-
// Include block ID for deletion tracking with
|
| 124 |
-
let code = `${block.id}
|
| 125 |
|
| 126 |
// Add the function body
|
| 127 |
if (body) {
|
|
@@ -257,7 +257,7 @@ chatGenerator.blockToCode = function (block, opt_thisOnly) {
|
|
| 257 |
}
|
| 258 |
|
| 259 |
// Generate the controls_if call with conditions
|
| 260 |
-
let code = `${block.id}
|
| 261 |
|
| 262 |
// Now get all the statement blocks with proper formatting
|
| 263 |
// DO0, DO1, etc. are indented under the if
|
|
@@ -326,8 +326,8 @@ chatGenerator.blockToCode = function (block, opt_thisOnly) {
|
|
| 326 |
}
|
| 327 |
}
|
| 328 |
|
| 329 |
-
// Generate the standard format: name(inputs(...)) with block ID and
|
| 330 |
-
const code = `${block.id}
|
| 331 |
|
| 332 |
// Handle statement inputs (for blocks that have a body)
|
| 333 |
let statements = '';
|
|
@@ -359,7 +359,7 @@ chatGenerator.blockToCode = function (block, opt_thisOnly) {
|
|
| 359 |
return [valueCode, this.ORDER_ATOMIC];
|
| 360 |
} else {
|
| 361 |
// When standalone (not connected), include the ID
|
| 362 |
-
const standaloneCode = `${block.id}
|
| 363 |
// For standalone value blocks, we need to return them as statement-like
|
| 364 |
// but still maintain the value block return format for Blockly
|
| 365 |
return [standaloneCode, this.ORDER_ATOMIC];
|
|
|
|
| 70 |
let body = generator.statementToCode(block, 'BODY');
|
| 71 |
|
| 72 |
// Construct the create_mcp call with inputs and outputs
|
| 73 |
+
// Include block ID for deletion tracking with arrow separator
|
| 74 |
+
let code = `${block.id} → create_mcp(inputs(${inputParams.join(', ')}), outputs(${outputParams.join(', ')}))`
|
| 75 |
|
| 76 |
// Add the function body
|
| 77 |
if (body) {
|
|
|
|
| 120 |
let body = generator.statementToCode(block, 'BODY');
|
| 121 |
|
| 122 |
// Construct the func_def call with inputs and outputs
|
| 123 |
+
// Include block ID for deletion tracking with arrow separator
|
| 124 |
+
let code = `${block.id} → ${name}(inputs(${inputParams.join(', ')}), outputs(${outputParams.join(', ')}))`
|
| 125 |
|
| 126 |
// Add the function body
|
| 127 |
if (body) {
|
|
|
|
| 257 |
}
|
| 258 |
|
| 259 |
// Generate the controls_if call with conditions
|
| 260 |
+
let code = `${block.id} → ${blockType}(inputs(${inputs.join(', ')}))`;
|
| 261 |
|
| 262 |
// Now get all the statement blocks with proper formatting
|
| 263 |
// DO0, DO1, etc. are indented under the if
|
|
|
|
| 326 |
}
|
| 327 |
}
|
| 328 |
|
| 329 |
+
// Generate the standard format: name(inputs(...)) with block ID and arrow separator
|
| 330 |
+
const code = `${block.id} → ${blockType}(inputs(${inputs.join(', ')}))`;
|
| 331 |
|
| 332 |
// Handle statement inputs (for blocks that have a body)
|
| 333 |
let statements = '';
|
|
|
|
| 359 |
return [valueCode, this.ORDER_ATOMIC];
|
| 360 |
} else {
|
| 361 |
// When standalone (not connected), include the ID
|
| 362 |
+
const standaloneCode = `${block.id} → ${blockType}(inputs(${inputs.join(', ')}))`;
|
| 363 |
// For standalone value blocks, we need to return them as statement-like
|
| 364 |
// but still maintain the value block return format for Blockly
|
| 365 |
return [standaloneCode, this.ORDER_ATOMIC];
|
project/src/index.js
CHANGED
|
@@ -397,7 +397,7 @@ const setupUnifiedStream = () => {
|
|
| 397 |
|
| 398 |
try {
|
| 399 |
// Parse and create blocks recursively
|
| 400 |
-
function parseAndCreateBlock(spec, shouldPosition = false) {
|
| 401 |
// Match block_name(inputs(...)) with proper parenthesis matching
|
| 402 |
const blockMatch = spec.match(/^(\w+)\s*\((.*)$/s);
|
| 403 |
|
|
@@ -503,6 +503,93 @@ const setupUnifiedStream = () => {
|
|
| 503 |
|
| 504 |
console.log('[SSE CREATE] inputsContent to parse:', inputsContent);
|
| 505 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
// Create the block
|
| 507 |
const newBlock = ws.newBlock(blockType);
|
| 508 |
|
|
@@ -573,6 +660,28 @@ const setupUnifiedStream = () => {
|
|
| 573 |
}
|
| 574 |
}
|
| 575 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
} else if (blockType === 'controls_if') {
|
| 577 |
// Special handling for if/else blocks
|
| 578 |
// IF is required, IFELSEN0, IFELSEN1, etc are optional, ELSE is optional
|
|
@@ -723,6 +832,40 @@ const setupUnifiedStream = () => {
|
|
| 723 |
}
|
| 724 |
}
|
| 725 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 726 |
// Only position the top-level block
|
| 727 |
if (shouldPosition) {
|
| 728 |
// Find a good position that doesn't overlap existing blocks
|
|
@@ -849,7 +992,7 @@ const setupUnifiedStream = () => {
|
|
| 849 |
}
|
| 850 |
|
| 851 |
// Create the block and all its nested children
|
| 852 |
-
const newBlock = parseAndCreateBlock(data.block_spec, true);
|
| 853 |
|
| 854 |
if (newBlock) {
|
| 855 |
blockId = newBlock.id;
|
|
@@ -1131,7 +1274,7 @@ const updateCode = () => {
|
|
| 1131 |
}
|
| 1132 |
|
| 1133 |
const vars = ws.getVariableMap().getAllVariables();
|
| 1134 |
-
globalVarString = vars.map(v => `${v.id}
|
| 1135 |
|
| 1136 |
const codeEl = document.querySelector('#generatedCode code');
|
| 1137 |
|
|
|
|
| 397 |
|
| 398 |
try {
|
| 399 |
// Parse and create blocks recursively
|
| 400 |
+
function parseAndCreateBlock(spec, shouldPosition = false, placementType = null, placementBlockID = null) {
|
| 401 |
// Match block_name(inputs(...)) with proper parenthesis matching
|
| 402 |
const blockMatch = spec.match(/^(\w+)\s*\((.*)$/s);
|
| 403 |
|
|
|
|
| 503 |
|
| 504 |
console.log('[SSE CREATE] inputsContent to parse:', inputsContent);
|
| 505 |
|
| 506 |
+
// VALIDATION: Check for malformed input - if inputsContent contains unmatched parentheses
|
| 507 |
+
// If there are missing closing parentheses (1-2), automatically add them
|
| 508 |
+
let validateParenCount = 0;
|
| 509 |
+
let validateQuotes = false;
|
| 510 |
+
let validateQuoteChar = '';
|
| 511 |
+
for (let i = 0; i < inputsContent.length; i++) {
|
| 512 |
+
const char = inputsContent[i];
|
| 513 |
+
if ((char === '"' || char === "'") && (i === 0 || inputsContent[i - 1] !== '\\')) {
|
| 514 |
+
if (!validateQuotes) {
|
| 515 |
+
validateQuotes = true;
|
| 516 |
+
validateQuoteChar = char;
|
| 517 |
+
} else if (char === validateQuoteChar) {
|
| 518 |
+
validateQuotes = false;
|
| 519 |
+
}
|
| 520 |
+
}
|
| 521 |
+
if (!validateQuotes) {
|
| 522 |
+
if (char === '(') validateParenCount++;
|
| 523 |
+
else if (char === ')') validateParenCount--;
|
| 524 |
+
}
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
// If parentheses are unbalanced, try to fix by adding closing parentheses
|
| 528 |
+
if (validateParenCount > 0) {
|
| 529 |
+
if (validateParenCount <= 2) {
|
| 530 |
+
console.log(`[SSE CREATE] Auto-fixing ${validateParenCount} missing closing parentheses in block '${blockType}'`);
|
| 531 |
+
|
| 532 |
+
// Smart insertion: find the best place to insert closing parentheses
|
| 533 |
+
// Look for the last comma at depth 0 (which separates key-value pairs)
|
| 534 |
+
let bestInsertPos = inputsContent.length; // Default to end
|
| 535 |
+
let depth = 0;
|
| 536 |
+
let inQuotes = false;
|
| 537 |
+
let quoteChar = '';
|
| 538 |
+
|
| 539 |
+
for (let i = inputsContent.length - 1; i >= 0; i--) {
|
| 540 |
+
const char = inputsContent[i];
|
| 541 |
+
|
| 542 |
+
// Handle quotes (from right to left)
|
| 543 |
+
if ((char === '"' || char === "'") && (i === 0 || inputsContent[i - 1] !== '\\')) {
|
| 544 |
+
if (!inQuotes) {
|
| 545 |
+
inQuotes = true;
|
| 546 |
+
quoteChar = char;
|
| 547 |
+
} else if (char === quoteChar) {
|
| 548 |
+
inQuotes = false;
|
| 549 |
+
}
|
| 550 |
+
}
|
| 551 |
+
|
| 552 |
+
// Handle parentheses (from right to left, so reverse the logic)
|
| 553 |
+
if (!inQuotes) {
|
| 554 |
+
if (char === ')') depth++;
|
| 555 |
+
else if (char === '(') depth--;
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
// Found the last comma at depth 0 - this separates the last nested block from following keys
|
| 559 |
+
if (char === ',' && depth === 0 && !inQuotes) {
|
| 560 |
+
bestInsertPos = i;
|
| 561 |
+
break;
|
| 562 |
+
}
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
// Insert the closing parentheses at the best position
|
| 566 |
+
if (bestInsertPos < inputsContent.length && inputsContent[bestInsertPos] === ',') {
|
| 567 |
+
// Insert right before the comma
|
| 568 |
+
inputsContent = inputsContent.slice(0, bestInsertPos) + ')'.repeat(validateParenCount) + inputsContent.slice(bestInsertPos);
|
| 569 |
+
} else {
|
| 570 |
+
// Fallback: insert at end
|
| 571 |
+
inputsContent += ')'.repeat(validateParenCount);
|
| 572 |
+
}
|
| 573 |
+
} else {
|
| 574 |
+
throw new Error(`Malformed block specification for '${blockType}': too many unmatched opening parentheses (${validateParenCount}). This usually means a nested block specification is malformed. Received: ${inputsContent}`);
|
| 575 |
+
}
|
| 576 |
+
} else if (validateParenCount < 0) {
|
| 577 |
+
throw new Error(`Malformed block specification for '${blockType}': more closing parentheses than opening. Received: ${inputsContent}`);
|
| 578 |
+
}
|
| 579 |
+
|
| 580 |
+
// VALIDATION: Check if trying to place a value block under a statement block
|
| 581 |
+
// Value blocks have an output connection but no previous connection
|
| 582 |
+
if (placementType === 'under') {
|
| 583 |
+
// Check if this block type is a value block by temporarily creating it
|
| 584 |
+
const testBlock = ws.newBlock(blockType);
|
| 585 |
+
const isValueBlock = testBlock.outputConnection && !testBlock.previousConnection;
|
| 586 |
+
testBlock.dispose(true); // Remove the test block
|
| 587 |
+
|
| 588 |
+
if (isValueBlock) {
|
| 589 |
+
throw new Error(`Cannot place value block '${blockType}' under a statement block. Value blocks must be nested inside inputs of other blocks or placed in MCP outputs using type: "input".`);
|
| 590 |
+
}
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
// Create the block
|
| 594 |
const newBlock = ws.newBlock(blockType);
|
| 595 |
|
|
|
|
| 660 |
}
|
| 661 |
}
|
| 662 |
}
|
| 663 |
+
} else if (blockType === 'text_join') {
|
| 664 |
+
// Special handling for text_join block (and similar blocks with ADD0, ADD1, ADD2...)
|
| 665 |
+
// Count ADD entries to determine how many items we need
|
| 666 |
+
let addCount = 0;
|
| 667 |
+
const addValues = {};
|
| 668 |
+
|
| 669 |
+
for (const [key, value] of Object.entries(inputs)) {
|
| 670 |
+
const addMatch = key.match(/^ADD(\d+)$/);
|
| 671 |
+
if (addMatch) {
|
| 672 |
+
const index = parseInt(addMatch[1]);
|
| 673 |
+
addCount = Math.max(addCount, index + 1);
|
| 674 |
+
addValues[index] = value;
|
| 675 |
+
}
|
| 676 |
+
}
|
| 677 |
+
|
| 678 |
+
console.log('[SSE CREATE] text_join detected with', addCount, 'items');
|
| 679 |
+
|
| 680 |
+
// Store pending text_join state to apply after initSvg()
|
| 681 |
+
if (addCount > 0) {
|
| 682 |
+
newBlock.pendingAddCount_ = addCount;
|
| 683 |
+
newBlock.pendingAddValues_ = addValues;
|
| 684 |
+
}
|
| 685 |
} else if (blockType === 'controls_if') {
|
| 686 |
// Special handling for if/else blocks
|
| 687 |
// IF is required, IFELSEN0, IFELSEN1, etc are optional, ELSE is optional
|
|
|
|
| 832 |
}
|
| 833 |
}
|
| 834 |
|
| 835 |
+
// Apply pending text_join mutations (must be after initSvg)
|
| 836 |
+
if (newBlock.type === 'text_join' && newBlock.pendingAddCount_ && newBlock.pendingAddCount_ > 0) {
|
| 837 |
+
console.log('[SSE CREATE] Applying text_join mutation with', newBlock.pendingAddCount_, 'items');
|
| 838 |
+
|
| 839 |
+
const addCount = newBlock.pendingAddCount_;
|
| 840 |
+
const addValues = newBlock.pendingAddValues_;
|
| 841 |
+
|
| 842 |
+
// Use loadExtraState if available to set the item count
|
| 843 |
+
if (typeof newBlock.loadExtraState === 'function') {
|
| 844 |
+
newBlock.loadExtraState({ itemCount: addCount });
|
| 845 |
+
} else {
|
| 846 |
+
// Fallback: set internal state
|
| 847 |
+
newBlock.itemCount_ = addCount;
|
| 848 |
+
}
|
| 849 |
+
|
| 850 |
+
// Now connect the ADD values
|
| 851 |
+
for (let i = 0; i < addCount; i++) {
|
| 852 |
+
const value = addValues[i];
|
| 853 |
+
if (value && typeof value === 'string' && value.match(/^\w+\s*\(inputs\(/)) {
|
| 854 |
+
// This is a nested block, create it recursively
|
| 855 |
+
const childBlock = parseAndCreateBlock(value);
|
| 856 |
+
|
| 857 |
+
// Connect the child block to the ADD input
|
| 858 |
+
const input = newBlock.getInput('ADD' + i);
|
| 859 |
+
if (input && input.connection && childBlock.outputConnection) {
|
| 860 |
+
childBlock.outputConnection.connect(input.connection);
|
| 861 |
+
console.log('[SSE CREATE] Connected ADD' + i + ' input');
|
| 862 |
+
} else {
|
| 863 |
+
console.warn('[SSE CREATE] Could not connect ADD' + i + ' input');
|
| 864 |
+
}
|
| 865 |
+
}
|
| 866 |
+
}
|
| 867 |
+
}
|
| 868 |
+
|
| 869 |
// Only position the top-level block
|
| 870 |
if (shouldPosition) {
|
| 871 |
// Find a good position that doesn't overlap existing blocks
|
|
|
|
| 992 |
}
|
| 993 |
|
| 994 |
// Create the block and all its nested children
|
| 995 |
+
const newBlock = parseAndCreateBlock(data.block_spec, true, data.placement_type, data.blockID);
|
| 996 |
|
| 997 |
if (newBlock) {
|
| 998 |
blockId = newBlock.id;
|
|
|
|
| 1274 |
}
|
| 1275 |
|
| 1276 |
const vars = ws.getVariableMap().getAllVariables();
|
| 1277 |
+
globalVarString = vars.map(v => `${v.id} → ${v.name}`).join("\n");
|
| 1278 |
|
| 1279 |
const codeEl = document.querySelector('#generatedCode code');
|
| 1280 |
|