owenkaplinsky commited on
Commit
809b5f4
·
1 Parent(s): be7ef2c

Add make_json, weather ex, comment

Browse files
project/src/blocks/text.js CHANGED
@@ -1,5 +1,9 @@
1
  import * as Blockly from 'blockly';
2
  import { pythonGenerator } from 'blockly/python';
 
 
 
 
3
 
4
  // Utility to create a unique input reference block type
5
  function createInputRefBlockType(inputName) {
@@ -540,12 +544,26 @@ const llm_call = {
540
 
541
  const call_api = {
542
  "type": "call_api",
543
- "message0": "call API with url %1",
544
  "args0": [
545
  {
546
- "type": "field_input",
 
 
 
 
 
 
 
 
 
 
547
  "name": "URL",
548
  },
 
 
 
 
549
  ],
550
  "output": ["String", "Integer", "List"],
551
  "colour": 165,
@@ -570,6 +588,38 @@ const in_json = {
570
  "inputsInline": true
571
  }
572
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
573
  // Dynamic function call block
574
  const func_call = {
575
  type: "func_call",
@@ -651,6 +701,142 @@ Blockly.Extensions.register('test_cleanup_extension', function () {
651
  };
652
  });
653
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
  // Extension for dynamic function call blocks
655
  Blockly.Extensions.register('func_call_dynamic', function () {
656
  const block = this;
@@ -911,6 +1097,30 @@ Blockly.Blocks['func_call'] = {
911
  }
912
  };
913
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
914
  export const blocks = Blockly.common.createBlockDefinitionsFromJsonArray([
915
  container,
916
  container_input,
 
1
  import * as Blockly from 'blockly';
2
  import { pythonGenerator } from 'blockly/python';
3
+ import { registerFieldMultilineInput } from '@blockly/field-multilineinput';
4
+
5
+ registerFieldMultilineInput();
6
+
7
 
8
  // Utility to create a unique input reference block type
9
  function createInputRefBlockType(inputName) {
 
544
 
545
  const call_api = {
546
  "type": "call_api",
547
+ "message0": "call API with method %1 url %2 headers %3",
548
  "args0": [
549
  {
550
+ type: "field_dropdown",
551
+ name: "METHOD",
552
+ options: [
553
+ ["GET", "GET"],
554
+ ["POST", "POST"],
555
+ ["PUT", "PUT"],
556
+ ["DELETE", "DELETE"],
557
+ ]
558
+ },
559
+ {
560
+ "type": "input_value",
561
  "name": "URL",
562
  },
563
+ {
564
+ "type": "input_value",
565
+ "name": "HEADERS",
566
+ },
567
  ],
568
  "output": ["String", "Integer", "List"],
569
  "colour": 165,
 
588
  "inputsInline": true
589
  }
590
 
591
+ const json_field = {
592
+ type: "json_field",
593
+ message0: "field",
594
+ args0: [],
595
+ previousStatement: null,
596
+ nextStatement: null,
597
+ colour: 165,
598
+ };
599
+
600
+ const make_json_container = {
601
+ type: "make_json_container",
602
+ message0: "fields %1",
603
+ args0: [
604
+ { type: "input_statement", name: "STACK" },
605
+ ],
606
+ colour: 165,
607
+ inputsInline: false
608
+ };
609
+
610
+ const make_json = {
611
+ type: "make_json",
612
+ message0: "make JSON %1",
613
+ args0: [
614
+ { type: "input_dummy" },
615
+ ],
616
+ colour: 165,
617
+ inputsInline: false,
618
+ output: ["String", "Integer"],
619
+ mutator: "json_mutator",
620
+ fieldCount_: 1,
621
+ };
622
+
623
  // Dynamic function call block
624
  const func_call = {
625
  type: "func_call",
 
701
  };
702
  });
703
 
704
+ // JSON mutator for dynamic field creation
705
+ Blockly.Extensions.registerMutator(
706
+ 'json_mutator',
707
+ {
708
+
709
+ decompose: function (workspace) {
710
+ const containerBlock = workspace.newBlock('make_json_container');
711
+ containerBlock.initSvg();
712
+ let connection = containerBlock.getInput('STACK').connection;
713
+
714
+ // Initialize defaults if not set
715
+ if (this.fieldCount_ === undefined) {
716
+ this.fieldCount_ = 0;
717
+ this.fieldKeys_ = [];
718
+ }
719
+
720
+ // Restore dynamically added field items
721
+ for (let i = 0; i < this.fieldCount_; i++) {
722
+ const itemBlock = workspace.newBlock('json_field');
723
+ itemBlock.initSvg();
724
+ // Store the connection for compose
725
+ const input = this.getInput('FIELD' + i);
726
+ if (input && input.connection && input.connection.targetConnection) {
727
+ itemBlock.valueConnection_ = input.connection.targetConnection;
728
+ }
729
+
730
+ connection.connect(itemBlock.previousConnection);
731
+ connection = itemBlock.nextConnection;
732
+ }
733
+
734
+ return containerBlock;
735
+ },
736
+
737
+ compose: function (containerBlock) {
738
+ Blockly.Events.disable();
739
+ try {
740
+ // Initialize defaults if not set
741
+ if (this.fieldCount_ === undefined) {
742
+ this.fieldCount_ = 0;
743
+ this.fieldKeys_ = [];
744
+ }
745
+
746
+ const connections = [];
747
+ let itemBlock = containerBlock.getInputTargetBlock('STACK');
748
+
749
+ // Collect all child connections from mutator stack
750
+ while (itemBlock) {
751
+ connections.push(itemBlock.valueConnection_);
752
+ itemBlock = itemBlock.nextConnection && itemBlock.nextConnection.targetBlock();
753
+ }
754
+
755
+ const newCount = connections.length;
756
+ const oldCount = this.fieldCount_;
757
+ this.fieldCount_ = newCount;
758
+
759
+ // Preserve old keys and extend array if needed
760
+ if (!this.fieldKeys_) {
761
+ this.fieldKeys_ = [];
762
+ }
763
+
764
+ // Remove all dynamic inputs before reconstruction
765
+ let i = 0;
766
+ while (this.getInput('FIELD' + i)) this.removeInput('FIELD' + i++);
767
+
768
+ // Add each dynamic field input with editable key name
769
+ for (let j = 0; j < newCount; j++) {
770
+ // Use existing key or create new one
771
+ if (!this.fieldKeys_[j]) {
772
+ this.fieldKeys_[j] = 'key' + j;
773
+ }
774
+ const key = this.fieldKeys_[j];
775
+ const input = this.appendValueInput('FIELD' + j);
776
+ const field = new Blockly.FieldTextInput(key);
777
+ field.setValidator((newValue) => {
778
+ // Update the stored key when user edits it
779
+ this.fieldKeys_[j] = newValue || 'key' + j;
780
+ return newValue;
781
+ });
782
+ input.appendField(field, 'KEY' + j);
783
+ input.appendField(':');
784
+ this.moveInputBefore('FIELD' + j, null);
785
+ }
786
+
787
+ // Trim fieldKeys array if fields were removed
788
+ if (newCount < oldCount) {
789
+ this.fieldKeys_.length = newCount;
790
+ }
791
+
792
+ // Reconnect preserved connections to new structure
793
+ for (let k = 0; k < newCount; k++) {
794
+ const conn = connections[k];
795
+ if (conn) {
796
+ try {
797
+ this.getInput('FIELD' + k).connection.connect(conn);
798
+ } catch { }
799
+ }
800
+ }
801
+
802
+ this.workspace.render();
803
+ } finally {
804
+ Blockly.Events.enable();
805
+ }
806
+ },
807
+
808
+ saveExtraState: function () {
809
+ return {
810
+ fieldCount: this.fieldCount_,
811
+ fieldKeys: this.fieldKeys_,
812
+ };
813
+ },
814
+
815
+ loadExtraState: function (state) {
816
+ this.fieldCount_ = state.fieldCount || 0;
817
+ this.fieldKeys_ = state.fieldKeys || [];
818
+
819
+ // Immediately rebuild the inputs structure so they exist when connections are loaded
820
+ if (this.fieldCount_ > 0) {
821
+ for (let j = 0; j < this.fieldCount_; j++) {
822
+ const key = this.fieldKeys_[j] || ('key' + j);
823
+ const input = this.appendValueInput('FIELD' + j);
824
+ const field = new Blockly.FieldTextInput(key);
825
+ field.setValidator((newValue) => {
826
+ // Update the stored key when user edits it
827
+ this.fieldKeys_[j] = newValue || 'key' + j;
828
+ return newValue;
829
+ });
830
+ input.appendField(field, 'KEY' + j);
831
+ input.appendField(':');
832
+ }
833
+ }
834
+ }
835
+ },
836
+ null,
837
+ ['json_field']
838
+ );
839
+
840
  // Extension for dynamic function call blocks
841
  Blockly.Extensions.register('func_call_dynamic', function () {
842
  const block = this;
 
1097
  }
1098
  };
1099
 
1100
+ // Register json_field block (internal mutator block, not user-visible)
1101
+ Blockly.Blocks['json_field'] = {
1102
+ init: function () {
1103
+ this.jsonInit(json_field);
1104
+ }
1105
+ };
1106
+
1107
+ // Register make_json_container block (internal mutator block, not user-visible)
1108
+ Blockly.Blocks['make_json_container'] = {
1109
+ init: function () {
1110
+ this.jsonInit(make_json_container);
1111
+ }
1112
+ };
1113
+
1114
+ // Register make_json block separately to include custom init logic
1115
+ Blockly.Blocks['make_json'] = {
1116
+ init: function () {
1117
+ this.jsonInit(make_json);
1118
+ // Initialize with no fields by default
1119
+ this.fieldCount_ = 0;
1120
+ this.fieldKeys_ = [];
1121
+ }
1122
+ };
1123
+
1124
  export const blocks = Blockly.common.createBlockDefinitionsFromJsonArray([
1125
  container,
1126
  container_input,
project/src/generators/python.js CHANGED
@@ -283,10 +283,12 @@ forBlock['func_call'] = function (block, generator) {
283
  };
284
 
285
  forBlock['call_api'] = function (block, generator) {
286
- const url = block.getFieldValue('URL');
 
 
287
 
288
  // Generate code to call an LLM model with a prompt
289
- const code = `call_api(url="${url}")`;
290
  return [code, Order.NONE];
291
  };
292
 
@@ -297,4 +299,23 @@ forBlock['in_json'] = function (block, generator) {
297
  // Generate code to call an LLM model with a prompt
298
  const code = `${json}["${name}"]`;
299
  return [code, Order.NONE];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  };
 
283
  };
284
 
285
  forBlock['call_api'] = function (block, generator) {
286
+ const url = generator.valueToCode(block, 'URL', Order.NONE) || "''";
287
+ const method = block.getFieldValue('METHOD');
288
+ const headers = generator.valueToCode(block, 'HEADERS', Order.NONE) || "''";
289
 
290
  // Generate code to call an LLM model with a prompt
291
+ const code = `call_api(url=${url}, method="${method}", headers=${headers})`;
292
  return [code, Order.NONE];
293
  };
294
 
 
299
  // Generate code to call an LLM model with a prompt
300
  const code = `${json}["${name}"]`;
301
  return [code, Order.NONE];
302
+ };
303
+
304
+ forBlock['make_json'] = function (block, generator) {
305
+ const pairs = [];
306
+ let i = 0;
307
+
308
+ // Collect all key-value pairs
309
+ while (block.getInput('FIELD' + i)) {
310
+ // Get the key from the text field on the block
311
+ const keyField = block.getField('KEY' + i);
312
+ const key = keyField ? keyField.getValue() : (block.fieldKeys_[i] || ('key' + i));
313
+ const value = generator.valueToCode(block, 'FIELD' + i, Order.NONE) || "''";
314
+ pairs.push(`"${key}": ${value}`);
315
+ i++;
316
+ }
317
+
318
+ // Generate valid Python dict syntax
319
+ const code = pairs.length > 0 ? `{${pairs.join(', ')}}` : '{}';
320
+ return [code, Order.ATOMIC];
321
  };
project/src/index.html CHANGED
@@ -33,7 +33,9 @@
33
 
34
  <div class="menuGroup">
35
  <button class="menuButton">Examples</button>
36
- <div class="dropdown"></div>
 
 
37
  </div>
38
  </div>
39
  <div id="githubLink">
 
33
 
34
  <div class="menuGroup">
35
  <button class="menuButton">Examples</button>
36
+ <div class="dropdown">
37
+ <a href="#" id="weatherButton" class="dropdownItem" data-action="undo">Weather API</a>
38
+ </div>
39
  </div>
40
  </div>
41
  <div id="githubLink">
project/src/index.js CHANGED
@@ -41,6 +41,8 @@ const ws = Blockly.inject(blocklyDiv, {
41
 
42
  window.workspace = ws;
43
 
 
 
44
  const newButton = document.querySelector('#newButton');
45
 
46
  newButton.addEventListener("click", () => {
@@ -89,6 +91,16 @@ saveButton.addEventListener("click", () => {
89
  document.body.removeChild(element);
90
  });
91
 
 
 
 
 
 
 
 
 
 
 
92
  undoButton.addEventListener("click", () => {
93
  ws.undo(false);
94
  });
@@ -124,10 +136,10 @@ const updateCode = () => {
124
 
125
  `;
126
 
127
- const API = `def call_api(url):
128
  import requests
129
 
130
- response = requests.get(url)
131
  data = response.json()
132
  return data
133
 
 
41
 
42
  window.workspace = ws;
43
 
44
+ Blockly.ContextMenuItems.registerCommentOptions();
45
+
46
  const newButton = document.querySelector('#newButton');
47
 
48
  newButton.addEventListener("click", () => {
 
91
  document.body.removeChild(element);
92
  });
93
 
94
+ const weatherText = `{"workspaceComments":[{"height":120,"width":479,"id":"XI5[EHp-Ow+kinXf6n5y","x":51.234375,"y":-83,"text":"Gets temperature of location with a latitude and a longitude.\\n\\nThe API requires a minimum of one decimal point to work."}],"blocks":{"languageVersion":0,"blocks":[{"type":"create_mcp","id":")N.HEG1x]Z/,k#TeWr,S","x":50,"y":50,"deletable":false,"extraState":{"inputCount":2,"inputNames":["latitude","longitude"],"inputTypes":["integer","integer"],"outputCount":1,"outputNames":["output0"],"outputTypes":["string"],"toolCount":0},"inputs":{"X0":{"block":{"type":"input_reference_latitude","id":"]3mj!y}qfRt+!okheU7L","deletable":false,"extraState":{"ownerBlockId":")N.HEG1x]Z/,k#TeWr,S"},"fields":{"VARNAME":"latitude"}}},"X1":{"block":{"type":"input_reference_longitude","id":"Do/{HFNGSd.!;POiKS?D","deletable":false,"extraState":{"ownerBlockId":")N.HEG1x]Z/,k#TeWr,S"},"fields":{"VARNAME":"longitude"}}},"R0":{"block":{"type":"in_json","id":"R|j?_8s^H{l0;UZ-oQt3","fields":{"NAME":"temperature_2m"},"inputs":{"JSON":{"block":{"type":"in_json","id":"X=M,R1@7bRjJVZIPi[qD","fields":{"NAME":"current"},"inputs":{"JSON":{"block":{"type":"call_api","id":"^(.vyM.yni08S~c1EBm=","fields":{"METHOD":"GET"},"inputs":{"URL":{"shadow":{"type":"text","id":"}.T;_U_OsRS)B_y09p % { ","fields":{"TEXT":""}},"block":{"type":"text_replace","id":"OwH9uERJPTGQG!UER#ch","inputs":{"FROM":{"shadow":{"type":"text","id":"ya05#^ 7 % UbUeXX#eDSmH","fields":{"TEXT":"{latitude}"}}},"TO":{"shadow":{"type":"text","id":": _ZloQuh9c-MNf-U]!k5","fields":{"TEXT":""}},"block":{"type":"input_reference_latitude","id":"?%@)3sErZ)}=#4ags#gu","extraState":{"ownerBlockId":")N.HEG1x]Z/,k#TeWr,S"},"fields":{"VARNAME":"latitude"}}},"TEXT":{"shadow":{"type":"text","id":"w@zsP)m6:WjkUp,ln3$x","fields":{"TEXT":""}},"block":{"type":"text_replace","id":"ImNPsvzD7r^+1MJ%IirV","inputs":{"FROM":{"shadow":{"type":"text","id":"%o(3rro?WLIFpmE0#MMM","fields":{"TEXT":"{longitude}"}}},"TO":{"shadow":{"type":"text","id":"Zpql-%oJ_sdSi | r |* er | ","fields":{"TEXT":""}},"block":{"type":"input_reference_longitude","id":"WUgiJP$X + zY#f$5nhnTX","extraState":{"ownerBlockId":") N.HEG1x]Z /, k#TeWr, S"},"fields":{"VARNAME":"longitude"}}},"TEXT":{"shadow":{"type":"text","id":", (vw$o_s7P = b4P; 8]}yj","fields":{"TEXT":"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&current=temperature_2m,wind_speed_10m"}}}}}}}}}}}}}}}}}}}}]}}`;
95
+ weatherButton.addEventListener("click", () => {
96
+ try {
97
+ const fileContent = JSON.parse(weatherText);
98
+ Blockly.serialization.workspaces.load(fileContent, ws);
99
+ } catch (error) {
100
+ console.error("Error loading weather.txt contents:", error);
101
+ }
102
+ });
103
+
104
  undoButton.addEventListener("click", () => {
105
  ws.undo(false);
106
  });
 
136
 
137
  `;
138
 
139
+ const API = `def call_api(url, method="GET", headers={}):
140
  import requests
141
 
142
+ response = requests.request(method, url, headers=headers)
143
  data = response.json()
144
  return data
145
 
project/src/toolbox.js CHANGED
@@ -43,11 +43,25 @@ export const toolbox = {
43
  {
44
  kind: 'block',
45
  type: 'call_api',
 
 
 
 
 
 
 
 
 
 
46
  },
47
  {
48
  kind: 'block',
49
  type: 'in_json',
50
  },
 
 
 
 
51
  ]
52
  },
53
  {
 
43
  {
44
  kind: 'block',
45
  type: 'call_api',
46
+ inputs: {
47
+ URL: {
48
+ shadow: {
49
+ type: 'text',
50
+ fields: {
51
+ text: "10",
52
+ },
53
+ },
54
+ },
55
+ },
56
  },
57
  {
58
  kind: 'block',
59
  type: 'in_json',
60
  },
61
+ {
62
+ kind: 'block',
63
+ type: 'make_json',
64
+ },
65
  ]
66
  },
67
  {