meta_workflows 0.9.31 → 0.9.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '082bc115fde4da66c333a64ed7f8ab9ae036097687297d12813e604cbd907bbc'
4
- data.tar.gz: 4b8ae8d4f4f3ca60cdd6afb6a658cdcaa9c312bcd26f20d5f600abf3ea6edf79
3
+ metadata.gz: 3496946526cd97d8ed5722ce2aef1c3e5a481e9345f41ee5a3902a001c7a1d9a
4
+ data.tar.gz: 675a6e8c765f6479e7c766e0ae34d458c70b0804aebf720d0584606c734fe956
5
5
  SHA512:
6
- metadata.gz: e5412dbe3df1a2e872de904fdc80569802bedce14f5656cb3192878df024058ccc32c92d52ad7cd90b57ad550f4f69c49401a51e3637dd01b64937aabd918180
7
- data.tar.gz: b49065a8e236c76a4a50655c462ecb02ee54ae65a0275b2bec9e9807a1da8b58d14101fdf0047089e2db2b76bea5b6e9b8d62730f3d47cb83f24aa7a6729320f
6
+ metadata.gz: 66d6d6ab599efe41489eba40a19e2c80d86e4331193981fc45d4deed838ae946da59d41eebc45639a747b9e7c9b8c2c67cf1b18a37387ad8aa310fd054c0eadb
7
+ data.tar.gz: 24998761fb152ca8e78d3ffd4ab1c2b45863a84772e3678127066c37b8188a4e3982a2fdae7d981dc3ae85515668f6fab2bda5fb07bd767cc10844920bef8121
@@ -1,7 +1,7 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ['textarea', 'form', 'structuredInputForm', 'hiddenMessage'];
4
+ static targets = ['textarea', 'form', 'structuredInputForm', 'hiddenMessage', 'structuredInput'];
5
5
 
6
6
  connect() {
7
7
  this.submitted = false;
@@ -14,7 +14,6 @@ export default class extends Controller {
14
14
  handleKeyDown(event) {
15
15
  if (event.key === 'Enter' && !event.shiftKey) {
16
16
  event.preventDefault();
17
- event.stopPropagation();
18
17
  this.handleSubmit();
19
18
  }
20
19
  }
@@ -24,7 +23,22 @@ export default class extends Controller {
24
23
  return;
25
24
  }
26
25
 
27
- if (!this.hasTextareaTarget || this.textareaTarget.value.trim() === '') {
26
+ // Check if we have structured input selections
27
+ const hasStructuredSelections = this.hasStructuredInputTargets.some(input =>
28
+ input.checked || (input.type === 'range' && input.value !== input.min)
29
+ );
30
+
31
+ if (hasStructuredSelections) {
32
+ // Submit the structured form to StructuredHumansController
33
+ this.submitted = true;
34
+ this.hideStructuredInputForm();
35
+ this.structuredInputFormTarget.requestSubmit();
36
+ return;
37
+ }
38
+
39
+ // Otherwise, handle normal text submission to HumansController
40
+ const textValue = this.hasTextareaTarget ? this.textareaTarget.value.trim() : '';
41
+ if (!textValue) {
28
42
  return;
29
43
  }
30
44
 
@@ -1,42 +1,61 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ['form', 'submitButton', 'checkbox', 'slider'];
4
+ static targets = ['form', 'checkbox', 'slider'];
5
5
 
6
6
  connect() {
7
7
  this.submitted = false;
8
+ this.setupKeyboardListener();
8
9
  }
9
10
 
10
- sliderTargetConnected(slider) {
11
- this.updateSliderAria(slider);
11
+ disconnect() {
12
+ this.removeKeyboardListener();
12
13
  }
13
14
 
14
- updateSliderAria(slider) {
15
- slider.setAttribute('aria-valuenow', slider.value);
16
- slider.setAttribute('aria-valuetext', slider.value);
15
+ setupKeyboardListener() {
16
+ this.keydownHandler = this.handleKeyDown.bind(this);
17
+ if (this.hasFormTarget) {
18
+ this.formTarget.addEventListener('keydown', this.keydownHandler);
19
+ }
17
20
  }
18
21
 
19
- handleSliderChange(event) {
20
- this.updateSliderAria(event.target);
22
+ removeKeyboardListener() {
23
+ if (this.keydownHandler && this.hasFormTarget) {
24
+ this.formTarget.removeEventListener('keydown', this.keydownHandler);
25
+ }
21
26
  }
22
27
 
23
- checkboxTargetConnected() {
24
- this.updateSubmitButtonState();
28
+ handleKeyDown(event) {
29
+ if (event.key === 'Enter' && !event.shiftKey) {
30
+ // Check if we have selected checkboxes
31
+ const selectedCheckboxes = this.checkboxTargets.filter(checkbox => checkbox.checked);
32
+
33
+ if (selectedCheckboxes.length > 0) {
34
+ event.preventDefault();
35
+ event.stopPropagation();
36
+ this.handleSubmit();
37
+ }
38
+ }
25
39
  }
26
40
 
27
- checkboxTargetDisconnected() {
28
- this.updateSubmitButtonState();
41
+ sliderTargetConnected(slider) {
42
+ this.updateSliderAria(slider);
43
+ this.updateSliderFill(slider);
29
44
  }
30
45
 
31
- updateSubmitButtonState() {
32
- if (!this.hasSubmitButtonTarget) return;
46
+ updateSliderAria(slider) {
47
+ slider.setAttribute('aria-valuenow', slider.value);
48
+ slider.setAttribute('aria-valuetext', slider.value);
49
+ }
33
50
 
34
- const hasSelection = this.checkboxTargets.some(checkbox => checkbox.checked);
35
- this.submitButtonTarget.disabled = !hasSelection;
51
+ updateSliderFill(slider) {
52
+ const percentage = ((slider.value - slider.min) / (slider.max - slider.min)) * 100;
53
+ slider.style.setProperty('--slider-fill', `${percentage}%`);
36
54
  }
37
55
 
38
- handleCheckboxChange() {
39
- this.updateSubmitButtonState();
56
+ handleSliderChange(event) {
57
+ this.updateSliderAria(event.target);
58
+ this.updateSliderFill(event.target);
40
59
  }
41
60
 
42
61
  handleSubmit() {
@@ -48,19 +67,15 @@ export default class extends Controller {
48
67
  this.hideFormAndSubmit();
49
68
  }
50
69
 
51
- handleCheckboxSubmit(event) {
52
- event.preventDefault();
70
+ hideFormAndSubmit() {
71
+ // Hide the structured input form
72
+ if (this.hasFormTarget) {
73
+ this.formTarget.style.display = 'none';
74
+ }
53
75
 
54
- if (this.submitted) {
55
- return;
76
+ // Submit the form to StructuredHumansController
77
+ if (this.hasFormTarget) {
78
+ this.formTarget.requestSubmit();
56
79
  }
57
-
58
- this.submitted = true;
59
- this.hideFormAndSubmit();
60
- }
61
-
62
- hideFormAndSubmit() {
63
- this.formTarget.style.display = 'none';
64
- this.formTarget.requestSubmit();
65
80
  }
66
81
  }
@@ -840,8 +840,7 @@
840
840
  padding: 1rem;
841
841
  background-color: rgba(255, 255, 255, 0.9);
842
842
  border-radius: 0.75rem;
843
- border: 1px solid var(--gray-300);
844
- margin-bottom: 1rem;
843
+ margin-bottom: .5rem;
845
844
  }
846
845
 
847
846
  .structured-input-options {
@@ -856,28 +855,28 @@
856
855
  align-items: center;
857
856
  gap: 0.75rem;
858
857
  padding: 0.75rem 1rem;
859
- border: 1px solid var(--gray-300);
858
+ border: 2px solid transparent;
860
859
  border-radius: 0.5rem;
861
860
  background-color: white;
862
861
  cursor: pointer;
863
862
  transition: all 0.2s ease;
863
+ background-image: linear-gradient(white, white), linear-gradient(var(--gray-300), var(--gray-300));
864
+ background-origin: border-box;
865
+ background-clip: padding-box, border-box;
864
866
  }
865
867
 
866
868
  .structured-radio-option:hover {
867
- background-color: var(--gray-50);
868
- border-color: var(--purple-300);
869
+ background-image: linear-gradient(white, white), linear-gradient(45deg, #F3B51C, #E87C66, #F3B51C, #E87C66);
870
+ background-size: auto, 300% 300%;
871
+ animation: animatedgradient 3s ease alternate infinite;
869
872
  }
870
873
 
871
874
  .structured-radio-option:has(.structured-radio-input:checked) {
872
875
  background-color: var(--purple-50);
873
- border-color: var(--purple-500);
874
876
  }
875
877
 
876
878
  .structured-radio-input {
877
- width: 1.25rem;
878
- height: 1.25rem;
879
- accent-color: var(--purple-600);
880
- cursor: pointer;
879
+ display: none;
881
880
  }
882
881
 
883
882
  .structured-radio-label {
@@ -894,27 +893,32 @@
894
893
  align-items: center;
895
894
  gap: 0.75rem;
896
895
  padding: 0.75rem 1rem;
897
- border: 1px solid var(--gray-300);
896
+ border: 2px solid transparent;
898
897
  border-radius: 0.5rem;
899
898
  background-color: white;
900
899
  cursor: pointer;
901
900
  transition: all 0.2s ease;
901
+ background-image: linear-gradient(white, white), linear-gradient(var(--gray-300), var(--gray-300));
902
+ background-origin: border-box;
903
+ background-clip: padding-box, border-box;
902
904
  }
903
905
 
904
906
  .structured-checkbox-option:hover {
905
- background-color: var(--gray-50);
906
- border-color: var(--purple-300);
907
+ background-image: linear-gradient(white, white), linear-gradient(45deg, #F3B51C, #E87C66, #F3B51C, #E87C66);
908
+ background-size: auto, 300% 300%;
909
+ animation: animatedgradient 3s ease alternate infinite;
907
910
  }
908
911
 
909
912
  .structured-checkbox-option:has(.structured-checkbox-input:checked) {
910
- background-color: var(--purple-50);
911
- border-color: var(--purple-500);
913
+ background-image: linear-gradient(white, white), linear-gradient(45deg, #F3B51C, #E87C66);
912
914
  }
913
915
 
916
+
917
+
914
918
  .structured-checkbox-input {
915
919
  width: 1.25rem;
916
920
  height: 1.25rem;
917
- accent-color: var(--purple-600);
921
+ accent-color: var(--purple-700);
918
922
  cursor: pointer;
919
923
  }
920
924
 
@@ -929,12 +933,14 @@
929
933
  /* Slider Styles */
930
934
  .structured-slider-container {
931
935
  padding: 1rem;
936
+ border: 1px solid var(--gray-300);
937
+ border-radius: 0.5rem;
938
+ background-color: var(--gray-50);
932
939
  }
933
940
 
934
941
  .structured-slider-labels {
935
942
  display: flex;
936
943
  justify-content: space-between;
937
- margin-bottom: 1rem;
938
944
  font-size: 0.875rem;
939
945
  color: var(--gray-600);
940
946
  font-weight: 500;
@@ -948,46 +954,90 @@
948
954
 
949
955
  .structured-slider-input {
950
956
  width: 100%;
951
- height: 0.5rem;
957
+ height: 1rem;
952
958
  border-radius: 0.25rem;
953
- background: var(--gray-200);
959
+ background: transparent;
954
960
  outline: none;
955
961
  -webkit-appearance: none;
956
962
  appearance: none;
957
963
  cursor: pointer;
964
+ --slider-fill: 0%;
965
+ border: none;
966
+ }
967
+
968
+ .structured-slider-input::-webkit-slider-runnable-track {
969
+ width: 100%;
970
+ height: 1rem;
971
+ border-radius: 0.25rem;
972
+ background: linear-gradient(90deg,
973
+ #F3B51C 0%,
974
+ #E87C66 var(--slider-fill),
975
+ #e5e7eb var(--slider-fill),
976
+ #e5e7eb 100%
977
+ );
978
+ background-size: 100% 100%;
979
+ background-repeat: no-repeat;
980
+ border: 1px solid var(--gray-300);
981
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
982
+ }
983
+
984
+ .structured-slider-input::-moz-range-track {
985
+ width: 100%;
986
+ height: 1rem;
987
+ border-radius: 0.25rem;
988
+ background: linear-gradient(90deg,
989
+ #F3B51C 0%,
990
+ #E87C66 var(--slider-fill),
991
+ #e5e7eb var(--slider-fill),
992
+ #e5e7eb 100%
993
+ );
994
+ background-size: 100% 100%;
995
+ background-repeat: no-repeat;
996
+ border: 1px solid var(--gray-300);
997
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
958
998
  }
959
999
 
960
1000
  .structured-slider-input::-webkit-slider-thumb {
961
1001
  -webkit-appearance: none;
962
1002
  appearance: none;
963
- width: 1.5rem;
964
- height: 1.5rem;
965
- border-radius: 50%;
966
- background: var(--purple-600);
1003
+ width: 2.25rem;
1004
+ height: 2.25rem;
1005
+ border-radius: 0.5625rem;
1006
+ background: var(--purple-700);
1007
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='white' viewBox='0 0 320 512'%3e%3cpath d='M96 480c-8.188 0-16.38-3.125-22.62-9.375c-12.5-12.5-12.5-32.75 0-45.25L242.8 256L73.38 86.63c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0l192 192c12.5 12.5 12.5 32.75 0 45.25l-192 192C112.4 476.9 104.2 480 96 480z'/%3e%3c/svg%3e");
1008
+ background-size: 12px 12px;
1009
+ background-position: center;
1010
+ background-repeat: no-repeat;
967
1011
  cursor: pointer;
968
1012
  border: 2px solid white;
969
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
1013
+ box-shadow: 0 3px 6px rgba(0, 0, 0, 0.25);
970
1014
  transition: all 0.2s ease;
1015
+ margin-top: -0.65rem;
971
1016
  }
972
1017
 
973
1018
  .structured-slider-input::-webkit-slider-thumb:hover {
974
- background: var(--purple-700);
1019
+ background-color: var(--purple-800);
975
1020
  transform: scale(1.1);
976
1021
  }
977
1022
 
978
1023
  .structured-slider-input::-moz-range-thumb {
979
- width: 1.5rem;
980
- height: 1.5rem;
981
- border-radius: 50%;
982
- background: var(--purple-600);
1024
+ width: 2.25rem;
1025
+ height: 2.25rem;
1026
+ border-radius: 0.5625rem;
1027
+ background: var(--purple-700);
1028
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='white' viewBox='0 0 320 512'%3e%3cpath d='M96 480c-8.188 0-16.38-3.125-22.62-9.375c-12.5-12.5-12.5-32.75 0-45.25L242.8 256L73.38 86.63c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0l192 192c12.5 12.5 12.5 32.75 0 45.25l-192 192C112.4 476.9 104.2 480 96 480z'/%3e%3c/svg%3e");
1029
+ background-size: 12px 12px;
1030
+ background-position: center;
1031
+ background-repeat: no-repeat;
983
1032
  cursor: pointer;
984
1033
  border: 2px solid white;
985
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
1034
+ box-shadow: 0 3px 6px rgba(0, 0, 0, 0.25);
986
1035
  transition: all 0.2s ease;
1036
+ margin-top: -0.65rem;
987
1037
  }
988
1038
 
989
1039
  .structured-slider-input::-moz-range-thumb:hover {
990
- background: var(--purple-700);
1040
+ background-color: var(--purple-800);
991
1041
  transform: scale(1.1);
992
1042
  }
993
1043
 
@@ -1062,3 +1112,16 @@
1062
1112
  white-space: nowrap;
1063
1113
  border: 0;
1064
1114
  }
1115
+
1116
+ /* Animated Gradient Keyframes */
1117
+ @keyframes animatedgradient {
1118
+ 0% {
1119
+ background-position: 0% 50%;
1120
+ }
1121
+ 50% {
1122
+ background-position: 100% 50%;
1123
+ }
1124
+ 100% {
1125
+ background-position: 0% 50%;
1126
+ }
1127
+ }
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ module FrameIdHelper
5
+ def target_frame_id(record, **options)
6
+ base_id = "#{record.class.name.downcase}_#{record.id}"
7
+
8
+ return "#{base_id}_loader" if options[:loader]
9
+ return "#{base_id}_form" if options[:form]
10
+ return "#{base_id}_structured_input" if options[:structured_input]
11
+ return "#{base_id}_chat_history" if options[:chat_history]
12
+ return "#{base_id}_streaming" if options[:streaming]
13
+
14
+ base_id
15
+ end
16
+
17
+ def turbo_stream_name(record)
18
+ [record.class.name.downcase, record.id]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ module PartialPathsHelper
5
+ def meta_loader_message
6
+ 'meta_workflows/loader_message'
7
+ end
8
+
9
+ def meta_error_message
10
+ 'meta_workflows/error_message'
11
+ end
12
+
13
+ def meta_response_form
14
+ 'meta_workflows/response_form_lexi'
15
+ end
16
+
17
+ def meta_structured_input
18
+ 'meta_workflows/structured_input'
19
+ end
20
+
21
+ def meta_response
22
+ 'meta_workflows/response_lexi'
23
+ end
24
+
25
+ def meta_chat_history
26
+ 'meta_workflows/chat_history'
27
+ end
28
+
29
+ def meta_streaming_response
30
+ 'meta_workflows/streaming_response'
31
+ end
32
+
33
+ def meta_redirect
34
+ 'meta_workflows/redirect'
35
+ end
36
+
37
+ def meta_lexi_chat_right_tray
38
+ 'meta_workflows/lexi_chat_right_tray'
39
+ end
40
+
41
+ def meta_lexi_chat_alpha_tray
42
+ 'meta_workflows/lexi_chat_alpha_tray'
43
+ end
44
+
45
+ def meta_lexi_chat_messages
46
+ 'meta_workflows/lexi_chat_messages'
47
+ end
48
+
49
+ def meta_lexi_chat_input_area
50
+ 'meta_workflows/lexi_chat_input_area'
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ module UiStateHelper
5
+ def random_chat_placeholder
6
+ [
7
+ 'What would you like to improve?',
8
+ 'Suggest an edit or ask for changes...',
9
+ 'Let me know how I can improve this.',
10
+ 'Not quite right? Let me know.',
11
+ 'Edit, clarify, or request a change.',
12
+ 'Happy with this, or want changes?'
13
+ ].sample
14
+ end
15
+
16
+ def tray_state(tray)
17
+ (cookies['tray_'] && cookies['tray_'][tray.to_s]) || cookies["tray_#{tray}"] || 'expanded'
18
+ end
19
+
20
+ def chevron_direction(tray)
21
+ state = tray_state(tray)
22
+ case tray.to_s
23
+ when 'beta'
24
+ state == 'collapsed' ? 'fa-chevron-right' : 'fa-chevron-left'
25
+ when 'gamma'
26
+ state == 'collapsed' ? 'fa-chevron-left' : 'fa-chevron-right'
27
+ when 'delta'
28
+ state == 'collapsed' ? 'fa-chevron-up' : 'fa-chevron-down'
29
+ else
30
+ ''
31
+ end
32
+ end
33
+
34
+ def default_step_progress
35
+ ['Thinking']
36
+ end
37
+ end
38
+ end
@@ -2,98 +2,72 @@
2
2
 
3
3
  module MetaWorkflows
4
4
  module MetaWorkflowsHelper
5
- def target_frame_id(record, **options)
6
- base_id = "#{record.class.name.downcase}_#{record.id}"
5
+ include MetaWorkflows::FrameIdHelper
6
+ include MetaWorkflows::PartialPathsHelper
7
+ include MetaWorkflows::UiStateHelper
7
8
 
8
- return "#{base_id}_loader" if options[:loader]
9
- return "#{base_id}_form" if options[:form]
10
- return "#{base_id}_structured_input" if options[:structured_input]
11
- return "#{base_id}_chat_history" if options[:chat_history]
12
- return "#{base_id}_streaming" if options[:streaming]
13
-
14
- base_id
15
- end
16
-
17
- def turbo_stream_name(record)
18
- [record.class.name.downcase, record.id]
19
- end
20
-
21
- def meta_loader_message
22
- 'meta_workflows/loader_message'
23
- end
24
-
25
- def meta_error_message
26
- 'meta_workflows/error_message'
27
- end
28
-
29
- def meta_response_form
30
- 'meta_workflows/response_form_lexi'
31
- end
32
-
33
- def meta_structured_input
34
- 'meta_workflows/structured_input'
9
+ def current_step_has_repetitions?(workflow_execution)
10
+ workflow_execution.step_repetitions(workflow_execution.current_step).present?
35
11
  end
36
12
 
37
- def meta_response
38
- 'meta_workflows/response_lexi'
39
- end
13
+ def show_next_button?(workflow_execution)
14
+ return false if current_step_has_repetitions?(workflow_execution)
40
15
 
41
- def meta_chat_history
42
- 'meta_workflows/chat_history'
16
+ workflow_execution.step_advanceable?(workflow_execution.current_step)
43
17
  end
44
18
 
45
- def meta_streaming_response
46
- 'meta_workflows/streaming_response'
47
- end
19
+ def show_skip_button?(workflow_execution)
20
+ return false if current_step_has_repetitions?(workflow_execution)
48
21
 
49
- def meta_redirect
50
- 'meta_workflows/redirect'
22
+ workflow_execution.step_skippable?(workflow_execution.current_step)
51
23
  end
52
24
 
53
- def meta_lexi_chat_right_tray
54
- 'meta_workflows/lexi_chat_right_tray'
25
+ def default_step_progress
26
+ ['Thinking']
55
27
  end
56
28
 
57
- def meta_lexi_chat_alpha_tray
58
- 'meta_workflows/lexi_chat_alpha_tray'
59
- end
29
+ def show_structured_input?(local_assigns, structured_input_config)
30
+ current_step = local_assigns[:current_step]
60
31
 
61
- def tray_state(tray)
62
- (cookies['tray_'] && cookies['tray_'][tray.to_s]) || cookies["tray_#{tray}"] || 'expanded'
32
+ (structured_input_config.present? && !local_assigns[:initial_load]) ||
33
+ (local_assigns[:initial_load] && !current_step&.initial_execution?)
63
34
  end
64
35
 
65
- def chevron_direction(tray)
66
- state = tray_state(tray)
67
- case tray.to_s
68
- when 'beta'
69
- state == 'collapsed' ? 'fa-chevron-right' : 'fa-chevron-left'
70
- when 'gamma'
71
- state == 'collapsed' ? 'fa-chevron-left' : 'fa-chevron-right'
72
- when 'delta'
73
- state == 'collapsed' ? 'fa-chevron-up' : 'fa-chevron-down'
36
+ def current_step_and_config(active_execution, step_structured_input_config)
37
+ if active_execution.present?
38
+ [
39
+ active_execution.workflow_steps.order(:step).last,
40
+ step_structured_input_config
41
+ ]
74
42
  else
75
- ''
43
+ [nil, {}]
76
44
  end
77
45
  end
78
46
 
79
- def current_step_has_repetitions?(workflow_execution)
80
- workflow_execution.step_repetitions(workflow_execution.current_step).present?
47
+ # Consolidates all data fetching for the Lexi chat tray using service object
48
+ def lexi_chat_data(record)
49
+ LexiChatDataService.new(record).call
81
50
  end
82
51
 
83
- def show_next_button?(workflow_execution)
84
- return false if current_step_has_repetitions?(workflow_execution)
85
-
86
- workflow_execution.step_advanceable?(workflow_execution.current_step)
52
+ def structured_form_url(workflow_execution)
53
+ workflow_execution ? meta_workflows.structured_human_path(workflow_execution.id) : '#'
87
54
  end
88
55
 
89
- def show_skip_button?(workflow_execution)
90
- return false if current_step_has_repetitions?(workflow_execution)
91
-
92
- workflow_execution.step_skippable?(workflow_execution.current_step)
56
+ def structured_chat_id(local_assigns)
57
+ local_assigns[:chat_id] || local_assigns[:current_step]&.chat&.id
93
58
  end
94
59
 
95
- def default_step_progress
96
- ['Thinking']
60
+ # Common locals for lexi chat input area
61
+ def lexi_chat_input_locals(record, chat_data, wrapper_class)
62
+ {
63
+ record: record,
64
+ workflow_execution: chat_data[:active_execution],
65
+ current_step: chat_data[:current_step],
66
+ structured_input_config: chat_data[:structured_input_config],
67
+ chat_id: chat_data[:active_chat]&.id,
68
+ is_structured_input: chat_data[:is_structured_input],
69
+ wrapper_class: wrapper_class
70
+ }
97
71
  end
98
72
  end
99
73
  end
@@ -57,8 +57,9 @@ module MetaWorkflows
57
57
  locals: {
58
58
  record: record,
59
59
  chat_id: chat.id,
60
- structured_input_config: structured_input_config,
61
- is_structured_input: is_structured_input
60
+ workflow_execution: workflow_execution,
61
+ current_step: workflow_execution.workflow_steps&.order(:step)&.last,
62
+ structured_input_config: structured_input_config
62
63
  }
63
64
  )
64
65
  end
@@ -21,5 +21,15 @@ module MetaWorkflows
21
21
  def increment_step
22
22
  update(current_step: current_step + 1)
23
23
  end
24
+
25
+ # Get the latest workflow step with its chat (optimized for preloaded data)
26
+ def latest_workflow_step
27
+ workflow_steps.max_by(&:step)
28
+ end
29
+
30
+ # Get the chat from the latest step (optimized for preloaded data)
31
+ def latest_chat
32
+ latest_workflow_step&.chat
33
+ end
24
34
  end
25
35
  end
@@ -11,10 +11,18 @@ module MetaWorkflows
11
11
 
12
12
  has_one :workflow, through: :workflow_execution, class_name: 'MetaWorkflows::Workflow'
13
13
 
14
+ # Callbacks
15
+ before_save :mark_execution_started_if_chat_assigned
16
+
14
17
  # Scopes for error handling
15
18
  scope :with_errors, -> { where.not(error_message: nil) }
16
19
  scope :without_errors, -> { where(error_message: nil) }
17
20
 
21
+ # Initial execution methods
22
+ def initial_execution?
23
+ initial_execution
24
+ end
25
+
18
26
  # Error handling methods
19
27
  def error?
20
28
  error_message.present?
@@ -57,5 +65,13 @@ module MetaWorkflows
57
65
  update!(repetition: new_repetition)
58
66
  new_repetition
59
67
  end
68
+
69
+ private
70
+
71
+ def mark_execution_started_if_chat_assigned
72
+ return unless chat_id_changed? && chat_id.present? && initial_execution?
73
+
74
+ self.initial_execution = false
75
+ end
60
76
  end
61
77
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class LexiChatDataService < ApplicationService
5
+ def initialize(record)
6
+ super()
7
+ @record = record
8
+ end
9
+
10
+ def call
11
+ executions = @record&.workflow_executions
12
+ active_execution = executions&.order(created_at: :desc)&.first
13
+
14
+ if active_execution
15
+ build_active_execution_data(active_execution)
16
+ else
17
+ build_empty_data
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :record
24
+
25
+ def build_active_execution_data(active_execution)
26
+ current_step = active_execution.latest_workflow_step
27
+ active_chat = active_execution.latest_chat
28
+ chat_history = active_execution.execution_chat_history
29
+ current_step_data = active_execution.step_data(active_execution.current_step)
30
+ step_structured_input_config = current_step_data&.dig('structured_input')
31
+ is_structured_input = step_structured_input_config.present? &&
32
+ current_step_data&.dig('action') == 'structured_human'
33
+
34
+ {
35
+ active_execution: active_execution,
36
+ current_step: current_step,
37
+ active_chat: active_chat,
38
+ chat_history: chat_history,
39
+ current_step_data: current_step_data,
40
+ step_structured_input_config: step_structured_input_config,
41
+ is_structured_input: is_structured_input,
42
+ structured_input_config: step_structured_input_config || {}
43
+ }
44
+ end
45
+
46
+ def build_empty_data
47
+ {
48
+ active_execution: nil,
49
+ current_step: nil,
50
+ active_chat: nil,
51
+ chat_history: nil,
52
+ current_step_data: nil,
53
+ step_structured_input_config: nil,
54
+ is_structured_input: false,
55
+ structured_input_config: {}
56
+ }
57
+ end
58
+ end
59
+ end
@@ -12,6 +12,7 @@
12
12
  value="<%= option['value'] %>"
13
13
  class="structured-checkbox-input"
14
14
  data-meta-workflows--structured-form-submit-target="checkbox"
15
+ data-meta-workflows--lexi-form-submit-target="structuredInput"
15
16
  data-action="change->meta-workflows--structured-form-submit#handleCheckboxChange"
16
17
  aria-describedby="structured-checkbox-help"
17
18
  >
@@ -22,19 +23,4 @@
22
23
  <% end %>
23
24
  </div>
24
25
  <div id="structured-checkbox-help" class="sr-only">Use space to select/deselect options</div>
25
-
26
- <div class="structured-submit-container">
27
- <button
28
- type="submit"
29
- class="structured-submit-button"
30
- data-meta-workflows--structured-form-submit-target="submitButton"
31
- data-action="click->meta-workflows--structured-form-submit#handleCheckboxSubmit"
32
- disabled
33
- aria-describedby="structured-submit-help"
34
- aria-label="Submit your selections"
35
- >
36
- <i class="fa-solid fa-arrow-up"></i>
37
- </button>
38
- <div id="structured-submit-help" class="sr-only">Submit your selections. Button is disabled until at least one option is selected.</div>
39
- </div>
40
26
  </div>
@@ -1,10 +1,5 @@
1
1
  <%# Lexi Chat Alpha Tray (Center Display) %>
2
- <% active_execution = local_assigns[:record]&.workflow_executions&.order(created_at: :desc)&.first %>
3
- <% active_chat = active_execution&.workflow_steps&.last&.chat %>
4
- <% chat_history = active_execution&.execution_chat_history %>
5
- <% current_step_data = active_execution&.step_data(active_execution&.current_step) %>
6
- <% step_structured_input_config = current_step_data&.dig('structured_input') %>
7
- <% is_structured_input = step_structured_input_config.present? && current_step_data&.dig('action') == 'structured_human' %>
2
+ <% chat_data = lexi_chat_data(local_assigns[:record]) %>
8
3
 
9
4
 
10
5
  <div class="lexi-chat-alpha-tray">
@@ -31,30 +26,15 @@
31
26
  <%# Right column - Chat and Input %>
32
27
  <div class="lexi-chat-alpha-right-column">
33
28
  <%# Scrollable chat messages area %>
34
- <div id="main-scroll-container" class="lexi-chat-alpha-messages">
35
- <%= render partial: meta_response, locals: {
36
- record: local_assigns[:record],
37
- chat_history: chat_history,
38
- current_user_messages: [],
39
- show_loader: false,
40
- show_error: false,
41
- step_progress: nil,
42
- full_response: nil,
43
- error_message: nil
44
- } %>
45
- </div>
29
+ <%= render partial: meta_lexi_chat_messages, locals: {
30
+ record: local_assigns[:record],
31
+ chat_history: chat_data[:chat_history],
32
+ container_class: 'lexi-chat-alpha-messages'
33
+ } %>
46
34
 
47
35
  <%# Input area (fixed at bottom) %>
48
36
  <div class="lexi-chat-alpha-input-area">
49
- <div class="lexi-chat-alpha-input-container" data-controller="meta-workflows--lexi-form-submit">
50
- <!-- Structured Input (always rendered, conditionally displays content) -->
51
- <%= render meta_structured_input, record: local_assigns[:record], structured_input_config: false, is_structured_input: false %>
52
-
53
- <!-- Regular Form Input (always present) -->
54
- <%= render partial: meta_response_form, locals: {record: local_assigns[:record], response_enabled: true,
55
- workflow_execution_id: active_execution&.id, chat_id: active_execution&.workflow_steps&.last&.chat&.id,
56
- workflow_execution: active_execution, is_structured_input: is_structured_input, show_skip_button: false, show_next_button: false } %>
57
- </div>
37
+ <%= render partial: meta_lexi_chat_input_area, locals: lexi_chat_input_locals(local_assigns[:record], chat_data, 'lexi-chat-alpha-input-container') %>
58
38
  </div>
59
39
  </div>
60
40
  </div>
@@ -0,0 +1,20 @@
1
+ <%# Shared input area %>
2
+ <div class="<%= local_assigns[:wrapper_class] || 'lexi-input-wrapper' %>" data-controller="meta-workflows--lexi-form-submit">
3
+ <%= render meta_structured_input,
4
+ record: local_assigns[:record],
5
+ workflow_execution: local_assigns[:workflow_execution],
6
+ current_step: local_assigns[:current_step],
7
+ structured_input_config: local_assigns[:structured_input_config],
8
+ initial_load: true %>
9
+
10
+ <%= render partial: meta_response_form, locals: {
11
+ record: local_assigns[:record],
12
+ response_enabled: true,
13
+ workflow_execution_id: local_assigns[:workflow_execution]&.id,
14
+ chat_id: local_assigns[:chat_id],
15
+ step_has_repetitions: true,
16
+ is_structured_input: local_assigns[:is_structured_input],
17
+ show_skip_button: false,
18
+ show_next_button: false
19
+ } %>
20
+ </div>
@@ -0,0 +1,13 @@
1
+ <%# Shared chat messages area %>
2
+ <div id="main-scroll-container" class="<%= local_assigns[:container_class] || 'lexi-chat-messages' %>">
3
+ <%= render partial: meta_response, locals: {
4
+ record: local_assigns[:record],
5
+ chat_history: local_assigns[:chat_history],
6
+ current_user_messages: [],
7
+ show_loader: false,
8
+ show_error: false,
9
+ step_progress: nil,
10
+ full_response: nil,
11
+ error_message: nil
12
+ } %>
13
+ </div>
@@ -1,10 +1,6 @@
1
- <%# Lexi Chat Tray %>
2
- <% active_execution = local_assigns[:record]&.workflow_executions&.order(created_at: :desc)&.first %>
3
- <% active_chat = active_execution&.workflow_steps&.last&.chat %>
4
- <% chat_history = active_execution&.execution_chat_history %>
5
- <% current_step_data = active_execution&.step_data(active_execution&.current_step) %>
6
- <% step_structured_input_config = current_step_data&.dig('structured_input') %>
7
- <% is_structured_input = step_structured_input_config.present? && current_step_data&.dig('action') == 'structured_human' %>
1
+ <%# Lexi Chat Tray (Right Side) %>
2
+ <% chat_data = lexi_chat_data(local_assigns[:record]) %>
3
+
8
4
 
9
5
  <div class="lexi-chat-tray lexi-chat-container-full">
10
6
  <!-- Header with Lexi logo and action icons -->
@@ -23,27 +19,20 @@
23
19
  </div>
24
20
 
25
21
  <!-- Chat messages area -->
26
- <div id="main-scroll-container" class="lexi-chat-messages">
27
- <%= render partial: meta_response, locals: {
28
- record: local_assigns[:record],
29
- chat_history: chat_history,
30
- current_user_messages: [],
31
- show_loader: false,
32
- show_error: false,
33
- step_progress: nil,
34
- full_response: nil,
35
- error_message: nil
36
- } %>
22
+ <%= render partial: meta_lexi_chat_messages, locals: {
23
+ record: local_assigns[:record],
24
+ chat_history: chat_data[:chat_history],
25
+ container_class: 'lexi-chat-messages'
26
+ } %>
27
+
28
+ <!-- Structured inputs area (above avatar) -->
29
+ <div class="structured-input-area" data-controller="meta-workflows--lexi-form-submit">
30
+ <%= render meta_structured_input, record: local_assigns[:record], structured_input_config: false, is_structured_input: false %>
37
31
  </div>
38
32
 
39
33
  <!-- Avatar and input area pinned to bottom -->
40
34
  <div class="lexi-chat-bottom">
41
35
  <%= image_tag("lexi-expanded.png", alt: "Lexi Avatar", class: "lexi-avatar") %>
42
- <div class="lexi-input-wrapper" data-controller="meta-workflows--lexi-form-submit">
43
- <%= render meta_structured_input, record: local_assigns[:record], structured_input_config: false, is_structured_input: false %>
44
-
45
- <%= render partial: meta_response_form, locals: {record: local_assigns[:record], response_enabled: true, workflow_execution_id: active_execution&.id,
46
- chat_id: active_execution&.workflow_steps&.last&.chat&.id, workflow_execution: active_execution, is_structured_input: is_structured_input } %>
47
- </div>
36
+ <%= render partial: meta_lexi_chat_input_area, locals: lexi_chat_input_locals(local_assigns[:record], chat_data, 'lexi-input-wrapper') %>
48
37
  </div>
49
38
  </div>
@@ -12,6 +12,7 @@
12
12
  name="single_choice_selection"
13
13
  value="<%= option['value'] %>"
14
14
  class="structured-radio-input"
15
+ data-meta-workflows--lexi-form-submit-target="structuredInput"
15
16
  data-action="change->meta-workflows--structured-form-submit#handleSubmit"
16
17
  aria-describedby="structured-radio-help"
17
18
  required
@@ -19,6 +19,7 @@
19
19
  value="<%= options['min']['value'] %>"
20
20
  class="structured-slider-input"
21
21
  data-meta-workflows--structured-form-submit-target="slider"
22
+ data-meta-workflows--lexi-form-submit-target="structuredInput"
22
23
  data-action="change->meta-workflows--structured-form-submit#handleSubmit input->meta-workflows--structured-form-submit#handleSliderChange"
23
24
  aria-describedby="structured-slider-help"
24
25
  aria-valuemin="<%= options['min']['value'] %>"
@@ -1,13 +1,8 @@
1
1
  <%= turbo_frame_tag target_frame_id(record, structured_input: true) do %>
2
- <% if local_assigns[:is_structured_input] && structured_input_config.present? %>
3
- <%
4
- # Get workflow execution for form submission
5
- workflow_execution = record.workflow_executions.order(created_at: :desc).first
6
- form_url = workflow_execution ? meta_workflows.structured_human_path(workflow_execution.id) : "#"
7
- chat_id = local_assigns[:chat_id] || workflow_execution&.workflow_steps&.last&.chat&.id
8
- %>
2
+
3
+ <% if show_structured_input?(local_assigns, structured_input_config) %>
9
4
 
10
- <%= form_with url: form_url,
5
+ <%= form_with url: structured_form_url(local_assigns[:workflow_execution]),
11
6
  method: :patch,
12
7
  id: "#{target_frame_id(record, structured_input: true)}_form",
13
8
  data: {
@@ -15,7 +10,7 @@
15
10
  "meta-workflows--structured-form-submit-target": "form",
16
11
  "meta-workflows--lexi-form-submit-target": "structuredInputForm"
17
12
  } do |form| %>
18
- <%= form.hidden_field :chat_id, value: chat_id %>
13
+ <%= form.hidden_field :chat_id, value: structured_chat_id(local_assigns) %>
19
14
 
20
15
  <div class="structured-input-wrapper">
21
16
  <% case structured_input_config['type'] %>
@@ -0,0 +1,5 @@
1
+ class AddInitialExecutionToMetaWorkflowsWorkflowSteps < ActiveRecord::Migration[7.2]
2
+ def change
3
+ add_column :meta_workflows_workflow_steps, :initial_execution, :boolean, default: true, null: false
4
+ end
5
+ end
@@ -3,7 +3,7 @@
3
3
  module MetaWorkflows
4
4
  MAJOR = 0
5
5
  MINOR = 9
6
- PATCH = 31 # this is automatically incremented by the build process
6
+ PATCH = 33 # this is automatically incremented by the build process
7
7
 
8
8
  VERSION = "#{MetaWorkflows::MAJOR}.#{MetaWorkflows::MINOR}.#{MetaWorkflows::PATCH}".freeze
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meta_workflows
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.31
4
+ version: 0.9.33
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leonid Medovyy
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-07-24 00:00:00.000000000 Z
12
+ date: 2025-07-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -138,6 +138,9 @@ files:
138
138
  - app/controllers/meta_workflows/meta_controller.rb
139
139
  - app/controllers/meta_workflows/structured_humans_controller.rb
140
140
  - app/controllers/meta_workflows/workflow_imports_controller.rb
141
+ - app/helpers/concerns/meta_workflows/frame_id_helper.rb
142
+ - app/helpers/concerns/meta_workflows/partial_paths_helper.rb
143
+ - app/helpers/concerns/meta_workflows/ui_state_helper.rb
141
144
  - app/helpers/meta_workflows/application_helper.rb
142
145
  - app/helpers/meta_workflows/debug_helper.rb
143
146
  - app/helpers/meta_workflows/execution_helper.rb
@@ -164,6 +167,7 @@ files:
164
167
  - app/models/meta_workflows/workflow_step.rb
165
168
  - app/services/meta_workflows/application_service.rb
166
169
  - app/services/meta_workflows/execution_filter_service.rb
170
+ - app/services/meta_workflows/lexi_chat_data_service.rb
167
171
  - app/services/meta_workflows/message_history_service.rb
168
172
  - app/services/meta_workflows/workflow_import_service.rb
169
173
  - app/sidekiq/meta_workflows/tools/meta_workflow_tool.rb
@@ -173,6 +177,8 @@ files:
173
177
  - app/views/meta_workflows/_checkbox_input.html.erb
174
178
  - app/views/meta_workflows/_error_message.html.erb
175
179
  - app/views/meta_workflows/_lexi_chat_alpha_tray.html.erb
180
+ - app/views/meta_workflows/_lexi_chat_input_area.html.erb
181
+ - app/views/meta_workflows/_lexi_chat_messages.html.erb
176
182
  - app/views/meta_workflows/_lexi_chat_right_tray.html.erb
177
183
  - app/views/meta_workflows/_loader_message.html.erb
178
184
  - app/views/meta_workflows/_radio_input.html.erb
@@ -201,6 +207,7 @@ files:
201
207
  - db/migrate/20250626211926_add_repetition_to_meta_workflows_workflow_steps.rb
202
208
  - db/migrate/20250709153017_add_recipe_to_meta_workflows_workflow_executions.rb
203
209
  - db/migrate/20250714192853_create_meta_workflows_execution_chat_histories.rb
210
+ - db/migrate/20250721195735_add_initial_execution_to_meta_workflows_workflow_steps.rb
204
211
  - lib/meta_workflows.rb
205
212
  - lib/meta_workflows/asset_installer.rb
206
213
  - lib/meta_workflows/configuration.rb