owenkaplinsky commited on
Commit
f937d73
·
1 Parent(s): 6eec7f7

Fix ifelse conditions; add tool warnings

Browse files
Files changed (3) hide show
  1. project/blocks.txt +2 -2
  2. project/chat.py +32 -8
  3. project/src/index.js +112 -79
project/blocks.txt CHANGED
@@ -53,8 +53,8 @@ VALUE: lists_sort(inputs(TYPE: "NUMERIC/TEXT/IGNORE_CASE", DIRECTION: "1/-1")) /
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
 
53
 
54
  # Variables
55
  VALUE: variables_get(inputs(VAR: value)) // VAR is a variable ID
56
+ STATEMENT: variables_set(inputs(VAR: value, VALUE: value)) // VAR is a variable ID
57
+ STATEMENT: 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
@@ -50,6 +50,9 @@ current_mcp_server_url = None
50
  deployment_just_happened = False
51
  deployment_message = ""
52
 
 
 
 
53
  blocks_context = ""
54
  try:
55
  file_path = os.path.join(os.path.dirname(__file__), "blocks.txt")
@@ -893,7 +896,10 @@ def create_gradio_interface():
893
 
894
  def chat_with_context(message, history):
895
  # Check if API key is set and create/update client
896
- global client, stored_api_key
 
 
 
897
 
898
  # Use stored key or environment key
899
  api_key = stored_api_key or os.environ.get("OPENAI_API_KEY")
@@ -1081,14 +1087,32 @@ def create_gradio_interface():
1081
  blockID = function_args.get("blockID", None)
1082
  placement_type = function_args.get("type", None)
1083
  input_name = function_args.get("input_name", None)
1084
- if blockID is None:
1085
- print(Fore.YELLOW + f"Agent created block with command `{command}`." + Style.RESET_ALL)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1086
  else:
1087
- print(Fore.YELLOW + f"Agent created block with command `{command}`, type: {placement_type}, blockID: `{blockID}`." + Style.RESET_ALL)
1088
- if input_name:
1089
- print(Fore.YELLOW + f" Input name: {input_name}" + Style.RESET_ALL)
1090
- tool_result = create_block(command, blockID, placement_type, input_name)
1091
- result_label = "Create Operation"
 
 
 
 
1092
 
1093
  elif function_name == "create_variable":
1094
  name = function_args.get("name", "")
 
50
  deployment_just_happened = False
51
  deployment_message = ""
52
 
53
+ # Track if first MCP output block creation attempt has happened in this conversation
54
+ first_output_block_attempted = False
55
+
56
  blocks_context = ""
57
  try:
58
  file_path = os.path.join(os.path.dirname(__file__), "blocks.txt")
 
896
 
897
  def chat_with_context(message, history):
898
  # Check if API key is set and create/update client
899
+ global client, stored_api_key, first_output_block_attempted
900
+
901
+ # Reset output block tracking for this conversation turn
902
+ first_output_block_attempted = False
903
 
904
  # Use stored key or environment key
905
  api_key = stored_api_key or os.environ.get("OPENAI_API_KEY")
 
1087
  blockID = function_args.get("blockID", None)
1088
  placement_type = function_args.get("type", None)
1089
  input_name = function_args.get("input_name", None)
1090
+
1091
+ # Check if this is the first MCP output block creation attempt
1092
+ is_first_output_attempt = (
1093
+ not first_output_block_attempted and
1094
+ placement_type == "input" and
1095
+ input_name and
1096
+ input_name.startswith("R")
1097
+ )
1098
+
1099
+ if is_first_output_attempt:
1100
+ # Mark that we've attempted an output block in this conversation
1101
+ first_output_block_attempted = True
1102
+ # Return warning instead of creating the block
1103
+ tool_result = "[TOOL] Automated warning: Make sure your output block contains the full and entire value needed. Block placement was **not** executed. Retry with the full command needed in one go."
1104
+ result_label = "Output Block Warning"
1105
+ print(Fore.YELLOW + f"[FIRST OUTPUT BLOCK] Intercepted first output block attempt with command `{command}`." + Style.RESET_ALL)
1106
  else:
1107
+ # Normal block creation
1108
+ if blockID is None:
1109
+ print(Fore.YELLOW + f"Agent created block with command `{command}`." + Style.RESET_ALL)
1110
+ else:
1111
+ print(Fore.YELLOW + f"Agent created block with command `{command}`, type: {placement_type}, blockID: `{blockID}`." + Style.RESET_ALL)
1112
+ if input_name:
1113
+ print(Fore.YELLOW + f" Input name: {input_name}" + Style.RESET_ALL)
1114
+ tool_result = create_block(command, blockID, placement_type, input_name)
1115
+ result_label = "Create Operation"
1116
 
1117
  elif function_name == "create_variable":
1118
  name = function_args.get("name", "")
project/src/index.js CHANGED
@@ -528,17 +528,17 @@ const setupUnifiedStream = () => {
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) {
@@ -548,20 +548,20 @@ const setupUnifiedStream = () => {
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
@@ -683,22 +683,25 @@ const setupUnifiedStream = () => {
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
688
- // Each condition block goes into IF, IFELSEN0, IFELSEN1, etc inputs
689
-
690
- // Count how many IF/IFELSEN conditions we have
691
- let conditionCount = 0;
692
  const conditionBlocks = {};
 
693
  let hasElse = false;
694
 
695
  console.log('[SSE CREATE] controls_if inputs:', inputs);
696
 
 
697
  for (const [key, value] of Object.entries(inputs)) {
698
- if (key === 'IF') {
699
- conditionBlocks['IF'] = value;
700
- } else if (key.match(/^IFELSEN\d+$/)) {
701
  conditionBlocks[key] = value;
 
 
 
 
 
 
 
702
  } else if (key === 'ELSE' && value === true) {
703
  // ELSE is a marker with no value (set to true by parseInputs)
704
  console.log('[SSE CREATE] Detected ELSE marker');
@@ -706,47 +709,24 @@ const setupUnifiedStream = () => {
706
  }
707
  }
708
 
709
- console.log('[SSE CREATE] controls_if parsed: hasElse =', hasElse);
710
-
711
- // Create inputs for each condition
712
- if (conditionBlocks['IF']) {
713
- const ifValue = conditionBlocks['IF'];
714
- if (typeof ifValue === 'string' && ifValue.match(/^\w+\s*\(inputs\(/)) {
715
- const childBlock = parseAndCreateBlock(ifValue);
716
- const input = newBlock.getInput('IF0');
717
- if (input && input.connection && childBlock.outputConnection) {
718
- childBlock.outputConnection.connect(input.connection);
719
- }
720
- }
721
- }
722
-
723
- // Handle IFELSEN conditions
724
  let elseIfCount = 0;
725
- for (const [key, value] of Object.entries(conditionBlocks)) {
726
- const elseIfMatch = key.match(/^IFELSEN(\d+)$/);
727
- if (elseIfMatch) {
728
- const elseIfValue = value;
729
- if (typeof elseIfValue === 'string' && elseIfValue.match(/^\w+\s*\(inputs\(/)) {
730
- // Blockly uses a mutator to add IF/ELSE IF blocks, so we need to configure that
731
- // For now, just try to create the structure
732
- const childBlock = parseAndCreateBlock(elseIfValue);
733
-
734
- // The inputs will be IF1, IF2, etc. for additional conditions
735
- const ifInputName = 'IF' + (elseIfCount + 1);
736
- const input = newBlock.getInput(ifInputName);
737
- if (input && input.connection && childBlock.outputConnection) {
738
- childBlock.outputConnection.connect(input.connection);
739
- }
740
- elseIfCount++;
741
- }
742
  }
743
  }
744
 
745
- // Store these for later - we'll apply them after initSvg()
 
 
 
746
  newBlock.pendingElseifCount_ = elseIfCount;
747
  newBlock.pendingElseCount_ = hasElse ? 1 : 0;
748
- } else {
749
- // Normal block handling
 
 
750
  for (const [key, value] of Object.entries(inputs)) {
751
  if (typeof value === 'string') {
752
  // Check if this is a nested block specification
@@ -792,44 +772,97 @@ const setupUnifiedStream = () => {
792
 
793
  // Initialize the block (renders it)
794
  newBlock.initSvg();
 
795
 
796
  // Apply pending controls_if mutations (must be after initSvg)
797
- if (newBlock.type === 'controls_if' && (newBlock.pendingElseifCount_ > 0 || newBlock.pendingElseCount_ > 0)) {
798
- console.log('[SSE CREATE] Applying controls_if mutation:', {
799
- elseifCount: newBlock.pendingElseifCount_,
800
- elseCount: newBlock.pendingElseCount_
801
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
802
 
803
- // Use the loadExtraState method if available (Blockly's preferred way)
804
- if (typeof newBlock.loadExtraState === 'function') {
805
- const state = {};
806
- if (newBlock.pendingElseifCount_ > 0) {
807
- state.elseIfCount = newBlock.pendingElseifCount_;
808
- }
809
- if (newBlock.pendingElseCount_ > 0) {
810
- state.hasElse = true;
811
  }
812
- console.log('[SSE CREATE] Using loadExtraState with:', state);
813
- newBlock.loadExtraState(state);
814
- } else {
815
- // Fallback: Set the internal state variables and call updateShape_
816
- newBlock.elseifCount_ = newBlock.pendingElseifCount_;
817
- newBlock.elseCount_ = newBlock.pendingElseCount_;
818
 
819
- if (typeof newBlock.updateShape_ === 'function') {
820
- console.log('[SSE CREATE] Calling updateShape_ on controls_if');
821
- newBlock.updateShape_();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
822
  }
823
- }
824
 
825
- // Verify the ELSE input was created
826
- if (newBlock.pendingElseCount_ > 0) {
827
- const elseInput = newBlock.getInput('ELSE');
828
- console.log('[SSE CREATE] ELSE input after mutation:', elseInput);
829
- if (!elseInput) {
830
- console.error('[SSE CREATE] ELSE input was NOT created!');
 
831
  }
 
 
 
832
  }
 
 
833
  }
834
 
835
  // Apply pending text_join mutations (must be after initSvg)
 
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) {
 
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
 
683
  newBlock.pendingAddValues_ = addValues;
684
  }
685
  } else if (blockType === 'controls_if') {
686
+ // Special handling for if/else blocks - create condition blocks now and store references
 
 
 
 
 
687
  const conditionBlocks = {};
688
+ const conditionBlockObjects = {};
689
  let hasElse = false;
690
 
691
  console.log('[SSE CREATE] controls_if inputs:', inputs);
692
 
693
+ // Process condition inputs and store block objects
694
  for (const [key, value] of Object.entries(inputs)) {
695
+ if (key === 'IF' || key.match(/^IFELSEN\d+$/)) {
696
+ // This is a condition block specification (not a value for a field)
 
697
  conditionBlocks[key] = value;
698
+
699
+ if (typeof value === 'string' && value.match(/^\w+\s*\(inputs\(/)) {
700
+ // Create the condition block now
701
+ const conditionBlock = parseAndCreateBlock(value);
702
+ conditionBlockObjects[key] = conditionBlock;
703
+ console.log('[SSE CREATE] Created condition block for', key);
704
+ }
705
  } else if (key === 'ELSE' && value === true) {
706
  // ELSE is a marker with no value (set to true by parseInputs)
707
  console.log('[SSE CREATE] Detected ELSE marker');
 
709
  }
710
  }
711
 
712
+ // Count IFELSEN blocks
 
 
 
 
 
 
 
 
 
 
 
 
 
 
713
  let elseIfCount = 0;
714
+ for (const key of Object.keys(conditionBlocks)) {
715
+ if (key.match(/^IFELSEN\d+$/)) {
716
+ elseIfCount++;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
717
  }
718
  }
719
 
720
+ console.log('[SSE CREATE] controls_if parsed: elseIfCount =', elseIfCount, 'hasElse =', hasElse);
721
+
722
+ // Store condition block OBJECTS for later - we'll connect them after mutator creates inputs
723
+ newBlock.pendingConditionBlockObjects_ = conditionBlockObjects;
724
  newBlock.pendingElseifCount_ = elseIfCount;
725
  newBlock.pendingElseCount_ = hasElse ? 1 : 0;
726
+ console.log('[SSE CREATE] Stored pending condition block objects:', Object.keys(conditionBlockObjects));
727
+ // Skip normal input processing for controls_if - we handle conditions after mutator
728
+ } else if (blockType !== 'controls_if') {
729
+ // Normal block handling (skip for controls_if which is handled specially)
730
  for (const [key, value] of Object.entries(inputs)) {
731
  if (typeof value === 'string') {
732
  // Check if this is a nested block specification
 
772
 
773
  // Initialize the block (renders it)
774
  newBlock.initSvg();
775
+ console.log('[SSE CREATE] === VERSION 2.0 - After initSvg, block type:', newBlock.type);
776
 
777
  // Apply pending controls_if mutations (must be after initSvg)
778
+ try {
779
+ console.log('[SSE CREATE] Checking for controls_if mutations: type =', newBlock.type, 'pendingElseifCount_ =', newBlock.pendingElseifCount_, 'pendingConditionBlockObjects_ =', !!newBlock.pendingConditionBlockObjects_);
780
+ if (newBlock.type === 'controls_if' && (newBlock.pendingElseifCount_ > 0 || newBlock.pendingElseCount_ > 0 || newBlock.pendingConditionBlockObjects_)) {
781
+ console.log('[SSE CREATE] ENTERING controls_if mutation block');
782
+ console.log('[SSE CREATE] Applying controls_if mutation:', {
783
+ elseifCount: newBlock.pendingElseifCount_,
784
+ elseCount: newBlock.pendingElseCount_
785
+ });
786
+
787
+ // Use the loadExtraState method if available (Blockly's preferred way)
788
+ if (typeof newBlock.loadExtraState === 'function') {
789
+ const state = {};
790
+ if (newBlock.pendingElseifCount_ > 0) {
791
+ state.elseIfCount = newBlock.pendingElseifCount_;
792
+ }
793
+ if (newBlock.pendingElseCount_ > 0) {
794
+ state.hasElse = true;
795
+ }
796
+ console.log('[SSE CREATE] Using loadExtraState with:', state);
797
+ newBlock.loadExtraState(state);
798
+ console.log('[SSE CREATE] After loadExtraState');
799
+ } else {
800
+ // Fallback: Set the internal state variables and call updateShape_
801
+ newBlock.elseifCount_ = newBlock.pendingElseifCount_;
802
+ newBlock.elseCount_ = newBlock.pendingElseCount_;
803
 
804
+ if (typeof newBlock.updateShape_ === 'function') {
805
+ console.log('[SSE CREATE] Calling updateShape_ on controls_if');
806
+ newBlock.updateShape_();
807
+ }
 
 
 
 
808
  }
 
 
 
 
 
 
809
 
810
+ // Now that the mutator has created all the inputs, connect the stored condition block objects
811
+ console.log('[SSE CREATE] pendingConditionBlockObjects_ exists?', !!newBlock.pendingConditionBlockObjects_);
812
+ if (newBlock.pendingConditionBlockObjects_) {
813
+ const conditionBlockObjects = newBlock.pendingConditionBlockObjects_;
814
+ console.log('[SSE CREATE] Connecting condition blocks:', Object.keys(conditionBlockObjects));
815
+
816
+ // Connect the IF condition
817
+ if (conditionBlockObjects['IF']) {
818
+ const ifBlock = conditionBlockObjects['IF'];
819
+ const input = newBlock.getInput('IF0');
820
+ console.log('[SSE CREATE] IF0 input exists?', !!input);
821
+ if (input && input.connection && ifBlock.outputConnection) {
822
+ ifBlock.outputConnection.connect(input.connection);
823
+ console.log('[SSE CREATE] Connected IF condition');
824
+ } else {
825
+ console.warn('[SSE CREATE] Could not connect IF - input:', !!input, 'childConnection:', !!ifBlock.outputConnection);
826
+ }
827
+ }
828
+
829
+ // Connect IFELSEN conditions
830
+ console.log('[SSE CREATE] Processing', newBlock.pendingElseifCount_, 'IFELSEN conditions');
831
+ for (let i = 0; i < newBlock.pendingElseifCount_; i++) {
832
+ const key = 'IFELSEN' + i;
833
+ console.log('[SSE CREATE] Looking for key:', key, 'exists?', !!conditionBlockObjects[key]);
834
+ if (conditionBlockObjects[key]) {
835
+ const ifElseBlock = conditionBlockObjects[key];
836
+ // IFELSEN blocks connect to IF1, IF2, etc.
837
+ const inputName = 'IF' + (i + 1);
838
+ const input = newBlock.getInput(inputName);
839
+ console.log('[SSE CREATE] Input', inputName, 'exists?', !!input);
840
+ if (input && input.connection && ifElseBlock.outputConnection) {
841
+ ifElseBlock.outputConnection.connect(input.connection);
842
+ console.log('[SSE CREATE] Connected IFELSEN' + i + ' condition to ' + inputName);
843
+ } else {
844
+ console.warn('[SSE CREATE] Could not connect IFELSEN' + i + ' - input ' + inputName + ' exists:', !!input, 'has connection:', input ? !!input.connection : false, 'childHasOutput:', !!ifElseBlock.outputConnection);
845
+ }
846
+ }
847
+ }
848
+ } else {
849
+ console.warn('[SSE CREATE] No pendingConditionBlockObjects_ found');
850
  }
 
851
 
852
+ // Verify the ELSE input was created
853
+ if (newBlock.pendingElseCount_ > 0) {
854
+ const elseInput = newBlock.getInput('ELSE');
855
+ console.log('[SSE CREATE] ELSE input after mutation:', elseInput);
856
+ if (!elseInput) {
857
+ console.error('[SSE CREATE] ELSE input was NOT created!');
858
+ }
859
  }
860
+
861
+ // Re-render after connecting condition blocks
862
+ newBlock.render();
863
  }
864
+ } catch (err) {
865
+ console.error('[SSE CREATE] Error in controls_if mutations:', err);
866
  }
867
 
868
  // Apply pending text_join mutations (must be after initSvg)