meta_workflows 0.9.19 → 0.9.20
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 +4 -4
- data/app/assets/javascripts/meta_workflows/controllers/structured_form_submit_controller.js +18 -0
- data/app/assets/stylesheets/meta_workflows/application.css +221 -0
- data/app/controllers/meta_workflows/humans_controller.rb +3 -2
- data/app/controllers/meta_workflows/structured_humans_controller.rb +74 -0
- data/app/helpers/meta_workflows/meta_workflows_helper.rb +6 -1
- data/app/jobs/meta_workflows/human_input_job.rb +25 -2
- data/app/jobs/meta_workflows/meta_job.rb +12 -5
- data/app/views/meta_workflows/_checkbox_input.html.erb +24 -0
- data/app/views/meta_workflows/_lexi_chat_alpha_tray.html.erb +4 -0
- data/app/views/meta_workflows/_radio_input.html.erb +26 -0
- data/app/views/meta_workflows/_response_form_lexi.html.erb +34 -31
- data/app/views/meta_workflows/_slider_input.html.erb +60 -0
- data/app/views/meta_workflows/_structured_input.html.erb +28 -0
- data/config/routes.rb +1 -0
- data/lib/meta_workflows/version.rb +1 -1
- data/lib/services/meta_workflows/meta_workflow_service.rb +14 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b91afe608b3c5260eebee5a4cc1211621c85171cf2ddc3740f5c695bc6df73f
|
4
|
+
data.tar.gz: cbafaa30a15e992936bdbe66ec444fb15740bbac999c2137e11f67d26870eea0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0886296d806086437b57857c1e509ea76a6db6c784df7bee23ff54428220d819e94265083165a0e33a17715f9ab00a9f0efd0a553d365c21b5b68a93b7c56c4e'
|
7
|
+
data.tar.gz: 76b74c3f7508be1ee96547583adaf43a13f41c8af6ba74023fdcd3cafd0cd59c34ff535eeb2490ee4ba0c03c23c75bfb6ed248630d179938e9dedd724fd50add
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ['form', 'radioInput', 'checkboxInput', 'sliderInput'];
|
5
|
+
|
6
|
+
connect() {
|
7
|
+
this.submitted = false;
|
8
|
+
}
|
9
|
+
|
10
|
+
handleSubmit() {
|
11
|
+
if (this.submitted) {
|
12
|
+
return;
|
13
|
+
}
|
14
|
+
|
15
|
+
this.submitted = true;
|
16
|
+
this.formTarget.requestSubmit();
|
17
|
+
}
|
18
|
+
}
|
@@ -824,3 +824,224 @@
|
|
824
824
|
.lexi-error-content .lexi-loader-ellipses {
|
825
825
|
margin-left: 0.375rem;
|
826
826
|
}
|
827
|
+
|
828
|
+
/* Structured Input Styles */
|
829
|
+
.structured-input-container {
|
830
|
+
padding: 1rem;
|
831
|
+
background-color: rgba(255, 255, 255, 0.9);
|
832
|
+
border-radius: 0.75rem;
|
833
|
+
border: 1px solid var(--gray-300);
|
834
|
+
margin-bottom: 1rem;
|
835
|
+
}
|
836
|
+
|
837
|
+
.structured-input-options {
|
838
|
+
display: flex;
|
839
|
+
flex-direction: column;
|
840
|
+
gap: 0.75rem;
|
841
|
+
}
|
842
|
+
|
843
|
+
/* Radio Button Styles */
|
844
|
+
.structured-radio-option {
|
845
|
+
display: flex;
|
846
|
+
align-items: center;
|
847
|
+
gap: 0.75rem;
|
848
|
+
padding: 0.75rem 1rem;
|
849
|
+
border: 1px solid var(--gray-300);
|
850
|
+
border-radius: 0.5rem;
|
851
|
+
background-color: white;
|
852
|
+
cursor: pointer;
|
853
|
+
transition: all 0.2s ease;
|
854
|
+
}
|
855
|
+
|
856
|
+
.structured-radio-option:hover {
|
857
|
+
background-color: var(--gray-50);
|
858
|
+
border-color: var(--purple-300);
|
859
|
+
}
|
860
|
+
|
861
|
+
.structured-radio-option:has(.structured-radio-input:checked) {
|
862
|
+
background-color: var(--purple-50);
|
863
|
+
border-color: var(--purple-500);
|
864
|
+
}
|
865
|
+
|
866
|
+
.structured-radio-input {
|
867
|
+
width: 1.25rem;
|
868
|
+
height: 1.25rem;
|
869
|
+
accent-color: var(--purple-600);
|
870
|
+
cursor: pointer;
|
871
|
+
}
|
872
|
+
|
873
|
+
.structured-radio-label {
|
874
|
+
font-size: 1rem;
|
875
|
+
font-weight: 500;
|
876
|
+
color: var(--gray-800);
|
877
|
+
cursor: pointer;
|
878
|
+
flex: 1;
|
879
|
+
}
|
880
|
+
|
881
|
+
/* Checkbox Styles */
|
882
|
+
.structured-checkbox-option {
|
883
|
+
display: flex;
|
884
|
+
align-items: center;
|
885
|
+
gap: 0.75rem;
|
886
|
+
padding: 0.75rem 1rem;
|
887
|
+
border: 1px solid var(--gray-300);
|
888
|
+
border-radius: 0.5rem;
|
889
|
+
background-color: white;
|
890
|
+
cursor: pointer;
|
891
|
+
transition: all 0.2s ease;
|
892
|
+
}
|
893
|
+
|
894
|
+
.structured-checkbox-option:hover {
|
895
|
+
background-color: var(--gray-50);
|
896
|
+
border-color: var(--purple-300);
|
897
|
+
}
|
898
|
+
|
899
|
+
.structured-checkbox-option:has(.structured-checkbox-input:checked) {
|
900
|
+
background-color: var(--purple-50);
|
901
|
+
border-color: var(--purple-500);
|
902
|
+
}
|
903
|
+
|
904
|
+
.structured-checkbox-input {
|
905
|
+
width: 1.25rem;
|
906
|
+
height: 1.25rem;
|
907
|
+
accent-color: var(--purple-600);
|
908
|
+
cursor: pointer;
|
909
|
+
}
|
910
|
+
|
911
|
+
.structured-checkbox-label {
|
912
|
+
font-size: 1rem;
|
913
|
+
font-weight: 500;
|
914
|
+
color: var(--gray-800);
|
915
|
+
cursor: pointer;
|
916
|
+
flex: 1;
|
917
|
+
}
|
918
|
+
|
919
|
+
/* Slider Styles */
|
920
|
+
.structured-slider-container {
|
921
|
+
padding: 1rem;
|
922
|
+
}
|
923
|
+
|
924
|
+
.structured-slider-labels {
|
925
|
+
display: flex;
|
926
|
+
justify-content: space-between;
|
927
|
+
margin-bottom: 1rem;
|
928
|
+
font-size: 0.875rem;
|
929
|
+
color: var(--gray-600);
|
930
|
+
font-weight: 500;
|
931
|
+
}
|
932
|
+
|
933
|
+
.structured-slider-wrapper {
|
934
|
+
display: flex;
|
935
|
+
flex-direction: column;
|
936
|
+
gap: 0.5rem;
|
937
|
+
}
|
938
|
+
|
939
|
+
.structured-slider-input {
|
940
|
+
width: 100%;
|
941
|
+
height: 0.5rem;
|
942
|
+
border-radius: 0.25rem;
|
943
|
+
background: var(--gray-200);
|
944
|
+
outline: none;
|
945
|
+
-webkit-appearance: none;
|
946
|
+
appearance: none;
|
947
|
+
cursor: pointer;
|
948
|
+
}
|
949
|
+
|
950
|
+
.structured-slider-input::-webkit-slider-thumb {
|
951
|
+
-webkit-appearance: none;
|
952
|
+
appearance: none;
|
953
|
+
width: 1.5rem;
|
954
|
+
height: 1.5rem;
|
955
|
+
border-radius: 50%;
|
956
|
+
background: var(--purple-600);
|
957
|
+
cursor: pointer;
|
958
|
+
border: 2px solid white;
|
959
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
960
|
+
transition: all 0.2s ease;
|
961
|
+
}
|
962
|
+
|
963
|
+
.structured-slider-input::-webkit-slider-thumb:hover {
|
964
|
+
background: var(--purple-700);
|
965
|
+
transform: scale(1.1);
|
966
|
+
}
|
967
|
+
|
968
|
+
.structured-slider-input::-moz-range-thumb {
|
969
|
+
width: 1.5rem;
|
970
|
+
height: 1.5rem;
|
971
|
+
border-radius: 50%;
|
972
|
+
background: var(--purple-600);
|
973
|
+
cursor: pointer;
|
974
|
+
border: 2px solid white;
|
975
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
976
|
+
transition: all 0.2s ease;
|
977
|
+
}
|
978
|
+
|
979
|
+
.structured-slider-input::-moz-range-thumb:hover {
|
980
|
+
background: var(--purple-700);
|
981
|
+
transform: scale(1.1);
|
982
|
+
}
|
983
|
+
|
984
|
+
.structured-slider-value {
|
985
|
+
text-align: center;
|
986
|
+
font-size: 1.125rem;
|
987
|
+
font-weight: 600;
|
988
|
+
color: var(--purple-700);
|
989
|
+
padding: 0.5rem;
|
990
|
+
background-color: var(--purple-50);
|
991
|
+
border-radius: 0.375rem;
|
992
|
+
border: 1px solid var(--purple-200);
|
993
|
+
}
|
994
|
+
|
995
|
+
/* Structured Input Submit Button */
|
996
|
+
.structured-submit-button {
|
997
|
+
width: 3rem;
|
998
|
+
height: 3rem;
|
999
|
+
border-radius: 50%;
|
1000
|
+
display: flex;
|
1001
|
+
align-items: center;
|
1002
|
+
justify-content: center;
|
1003
|
+
background-color: var(--purple-600);
|
1004
|
+
color: white;
|
1005
|
+
transition: all 0.2s;
|
1006
|
+
border: none;
|
1007
|
+
cursor: pointer;
|
1008
|
+
margin-top: 1rem;
|
1009
|
+
align-self: flex-end;
|
1010
|
+
}
|
1011
|
+
|
1012
|
+
.structured-submit-button:hover {
|
1013
|
+
background-color: var(--purple-700);
|
1014
|
+
transform: scale(1.05);
|
1015
|
+
}
|
1016
|
+
|
1017
|
+
.structured-submit-button:disabled {
|
1018
|
+
background-color: var(--gray-400);
|
1019
|
+
cursor: not-allowed;
|
1020
|
+
transform: none;
|
1021
|
+
}
|
1022
|
+
|
1023
|
+
/* Structured Input Validation Styles */
|
1024
|
+
.structured-input-container[data-invalid="true"] {
|
1025
|
+
border-color: var(--red-500);
|
1026
|
+
background-color: var(--red-50);
|
1027
|
+
}
|
1028
|
+
|
1029
|
+
.structured-input-error {
|
1030
|
+
color: var(--red-600);
|
1031
|
+
font-size: 0.875rem;
|
1032
|
+
margin-top: 0.5rem;
|
1033
|
+
font-weight: 500;
|
1034
|
+
}
|
1035
|
+
|
1036
|
+
/* Screen reader only class for accessibility */
|
1037
|
+
.sr-only {
|
1038
|
+
position: absolute;
|
1039
|
+
width: 1px;
|
1040
|
+
height: 1px;
|
1041
|
+
padding: 0;
|
1042
|
+
margin: -1px;
|
1043
|
+
overflow: hidden;
|
1044
|
+
clip: rect(0, 0, 0, 0);
|
1045
|
+
white-space: nowrap;
|
1046
|
+
border: 0;
|
1047
|
+
}
|
@@ -9,10 +9,10 @@ module MetaWorkflows
|
|
9
9
|
def update
|
10
10
|
auto_advancing = should_auto_advance?
|
11
11
|
|
12
|
-
if params[:
|
12
|
+
if params[:manual_advance].present? || auto_advancing
|
13
13
|
@workflow_execution.increment_step
|
14
14
|
render_loader_stream
|
15
|
-
process_human_input(auto_advancing:, manual_advancing: params[:
|
15
|
+
process_human_input(auto_advancing:, manual_advancing: params[:manual_advance].present?)
|
16
16
|
else
|
17
17
|
render_response_form_stream
|
18
18
|
process_human_input
|
@@ -23,6 +23,7 @@ module MetaWorkflows
|
|
23
23
|
|
24
24
|
def should_auto_advance?
|
25
25
|
step_repetitions = current_step_repetitions
|
26
|
+
return true if params[:auto_advance].present?
|
26
27
|
return false unless step_repetitions
|
27
28
|
|
28
29
|
new_repetition = @workflow_step.increment_repetition!
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
class StructuredHumansController < MetaController
|
5
|
+
include MetaWorkflows::MetaWorkflowsHelper
|
6
|
+
include MetaWorkflows::Streamable
|
7
|
+
before_action :set_workflow_data, only: [:update]
|
8
|
+
|
9
|
+
def update
|
10
|
+
render_loader_stream
|
11
|
+
process_structured_human_input
|
12
|
+
@workflow_execution.increment_step
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def process_structured_human_input
|
18
|
+
MetaWorkflows::HumanInputJob.perform_later(
|
19
|
+
user_id: current_user.id,
|
20
|
+
record: @record,
|
21
|
+
auto_advancing: true,
|
22
|
+
manual_advancing: false,
|
23
|
+
params: {
|
24
|
+
inputs: extract_structured_inputs,
|
25
|
+
chat_id: params[:chat_id],
|
26
|
+
prompt_id: @prompt_id
|
27
|
+
}
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def extract_structured_inputs
|
32
|
+
step_data = @workflow_execution.step_data(@workflow_execution.current_step)
|
33
|
+
structured_input_config = step_data&.dig('structured_input')
|
34
|
+
return params[:message] unless structured_input_config
|
35
|
+
|
36
|
+
case structured_input_config['type']
|
37
|
+
when 'single_choice'
|
38
|
+
params[:single_choice_selection]
|
39
|
+
when 'multiple_choice'
|
40
|
+
params[:multiple_choice_selections]
|
41
|
+
when 'range'
|
42
|
+
params[:range_value]
|
43
|
+
else
|
44
|
+
params[:message]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def render_loader_stream
|
49
|
+
respond_to do |format|
|
50
|
+
format.turbo_stream do
|
51
|
+
render turbo_stream: [
|
52
|
+
build_loader_stream(record: @record, workflow_execution: @workflow_execution),
|
53
|
+
build_response_form_stream(record: @record, workflow_execution: @workflow_execution,
|
54
|
+
response_enabled: false, responding: false, chat_id: params[:chat_id])
|
55
|
+
]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def render_response_form_stream
|
61
|
+
respond_to do |format|
|
62
|
+
format.turbo_stream do
|
63
|
+
render turbo_stream: build_response_form_stream(
|
64
|
+
record: @record,
|
65
|
+
workflow_execution: @workflow_execution,
|
66
|
+
response_enabled: true,
|
67
|
+
responding: true,
|
68
|
+
chat_id: params[:chat_id]
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
module MetaWorkflows
|
4
4
|
module MetaWorkflowsHelper
|
5
|
-
def target_frame_id(record, form: nil, loader: nil)
|
5
|
+
def target_frame_id(record, form: nil, loader: nil, structured_input: nil)
|
6
6
|
base_id = "#{record.class.name.downcase}_#{record.id}"
|
7
7
|
|
8
8
|
return "#{base_id}_loader" if loader
|
9
9
|
return "#{base_id}_form" if form
|
10
|
+
return "#{base_id}_structured_input" if structured_input
|
10
11
|
|
11
12
|
base_id
|
12
13
|
end
|
@@ -27,6 +28,10 @@ module MetaWorkflows
|
|
27
28
|
'meta_workflows/response_form_lexi'
|
28
29
|
end
|
29
30
|
|
31
|
+
def meta_structured_input
|
32
|
+
'meta_workflows/structured_input'
|
33
|
+
end
|
34
|
+
|
30
35
|
def meta_response
|
31
36
|
'meta_workflows/response_lexi'
|
32
37
|
end
|
@@ -5,8 +5,11 @@ module MetaWorkflows
|
|
5
5
|
include MetaWorkflows::MetaWorkflowsHelper
|
6
6
|
queue_as :default
|
7
7
|
|
8
|
+
attr_accessor :current_step_data, :structured_input_config, :is_structured_input
|
9
|
+
|
8
10
|
def perform(user_id:, record:, params:, auto_advancing: false, manual_advancing: false)
|
9
11
|
setup(user_id, record, auto_advancing, manual_advancing)
|
12
|
+
set_workflow_step_variables
|
10
13
|
conversation = initialize_conversation(params)
|
11
14
|
process_and_broadcast(conversation) unless manual_advancing
|
12
15
|
advance_workflow(params) if advancing?
|
@@ -14,6 +17,12 @@ module MetaWorkflows
|
|
14
17
|
|
15
18
|
private
|
16
19
|
|
20
|
+
def set_workflow_step_variables
|
21
|
+
@current_step_data = workflow_execution&.step_data(workflow_execution.current_step)
|
22
|
+
@structured_input_config = current_step_data&.dig('structured_input')
|
23
|
+
@is_structured_input = structured_input_config.present? && current_step_data&.dig('action') == 'structured_human'
|
24
|
+
end
|
25
|
+
|
17
26
|
def broadcast_response(chat, full_response)
|
18
27
|
user_messages = chat.messages.where(role: 'user').order(:created_at)
|
19
28
|
messages = chat.messages.order(:created_at)
|
@@ -42,7 +51,6 @@ module MetaWorkflows
|
|
42
51
|
end
|
43
52
|
|
44
53
|
def broadcast_form(chat)
|
45
|
-
workflow_execution = active_workflow_execution
|
46
54
|
Turbo::StreamsChannel.broadcast_replace_to(
|
47
55
|
turbo_stream_name(record),
|
48
56
|
target: target_frame_id(record, form: true),
|
@@ -51,7 +59,22 @@ module MetaWorkflows
|
|
51
59
|
workflow_execution_id: workflow_execution&.id,
|
52
60
|
response_enabled: true,
|
53
61
|
chat_id: chat.id,
|
54
|
-
step_has_repetitions: current_step_has_repetitions?(workflow_execution)
|
62
|
+
step_has_repetitions: current_step_has_repetitions?(workflow_execution),
|
63
|
+
is_structured_input: is_structured_input }
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def broadcast_structured_input(chat)
|
68
|
+
Turbo::StreamsChannel.broadcast_replace_to(
|
69
|
+
turbo_stream_name(record),
|
70
|
+
target: target_frame_id(record, structured_input: true),
|
71
|
+
partial: meta_structured_input,
|
72
|
+
locals: {
|
73
|
+
record: record,
|
74
|
+
chat_id: chat.id,
|
75
|
+
structured_input_config: structured_input_config,
|
76
|
+
is_structured_input: is_structured_input
|
77
|
+
}
|
55
78
|
)
|
56
79
|
end
|
57
80
|
|
@@ -5,7 +5,8 @@ module MetaWorkflows
|
|
5
5
|
include MetaWorkflows::Concerns::ErrorHandling
|
6
6
|
include MetaWorkflows::Streamable
|
7
7
|
|
8
|
-
attr_accessor :chat, :inputs, :full_response, :user_id, :record, :auto_advancing, :manual_advancing
|
8
|
+
attr_accessor :chat, :inputs, :full_response, :user_id, :record, :auto_advancing, :manual_advancing,
|
9
|
+
:workflow_execution
|
9
10
|
|
10
11
|
private
|
11
12
|
|
@@ -15,6 +16,7 @@ module MetaWorkflows
|
|
15
16
|
@full_response = +''
|
16
17
|
@auto_advancing = auto_advancing
|
17
18
|
@manual_advancing = manual_advancing
|
19
|
+
@workflow_execution = active_workflow_execution
|
18
20
|
end
|
19
21
|
|
20
22
|
def initialize_conversation(params)
|
@@ -28,7 +30,6 @@ module MetaWorkflows
|
|
28
30
|
def initialize_new_conversation(params)
|
29
31
|
inputs = params.symbolize_keys[:inputs]
|
30
32
|
|
31
|
-
workflow_execution = active_workflow_execution
|
32
33
|
current_step = workflow_execution.workflow_steps&.find_by(step: workflow_execution.current_step)
|
33
34
|
@chat = current_step&.chat
|
34
35
|
|
@@ -45,7 +46,6 @@ module MetaWorkflows
|
|
45
46
|
end
|
46
47
|
|
47
48
|
def process_and_broadcast(conversation)
|
48
|
-
workflow_execution = active_workflow_execution
|
49
49
|
workflow_step = current_workflow_step(workflow_execution)
|
50
50
|
|
51
51
|
begin
|
@@ -72,13 +72,16 @@ module MetaWorkflows
|
|
72
72
|
def finalize_conversation(conversation)
|
73
73
|
chat.update!(conversation_id: conversation.id) if chat.conversation_id.blank?
|
74
74
|
persist_messages_to_history
|
75
|
-
|
75
|
+
return if auto_advancing
|
76
|
+
|
77
|
+
broadcast_form(chat)
|
78
|
+
broadcast_structured_input(chat)
|
76
79
|
end
|
77
80
|
|
78
81
|
def persist_messages_to_history
|
79
82
|
MetaWorkflows::MessageHistoryService.new(
|
80
83
|
chat: chat,
|
81
|
-
workflow_execution:
|
84
|
+
workflow_execution: workflow_execution
|
82
85
|
).persist_messages_to_history
|
83
86
|
end
|
84
87
|
|
@@ -92,6 +95,10 @@ module MetaWorkflows
|
|
92
95
|
raise NotImplementedError
|
93
96
|
end
|
94
97
|
|
98
|
+
def broadcast_structured_input(chat)
|
99
|
+
raise NotImplementedError
|
100
|
+
end
|
101
|
+
|
95
102
|
def broadcast_response(chat, full_response)
|
96
103
|
raise NotImplementedError
|
97
104
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<div class="structured-input-container"
|
2
|
+
role="group"
|
3
|
+
aria-labelledby="structured-checkbox-legend">
|
4
|
+
<legend id="structured-checkbox-legend" class="sr-only">Select one or more options</legend>
|
5
|
+
<div class="structured-input-options">
|
6
|
+
<% options.each_with_index do |option, index| %>
|
7
|
+
<div class="structured-checkbox-option">
|
8
|
+
<input
|
9
|
+
type="checkbox"
|
10
|
+
id="multiple_choice_<%= index %>"
|
11
|
+
name="multiple_choice_selections[]"
|
12
|
+
value="<%= option['value'] %>"
|
13
|
+
class="structured-checkbox-input"
|
14
|
+
data-action="change->meta-workflows--structured-form-submit#handleSubmit"
|
15
|
+
aria-describedby="structured-checkbox-help"
|
16
|
+
>
|
17
|
+
<label for="multiple_choice_<%= index %>" class="structured-checkbox-label">
|
18
|
+
<%= option['label'] %>
|
19
|
+
</label>
|
20
|
+
</div>
|
21
|
+
<% end %>
|
22
|
+
</div>
|
23
|
+
<div id="structured-checkbox-help" class="sr-only">Use space to select/deselect options</div>
|
24
|
+
</div>
|
@@ -43,6 +43,10 @@
|
|
43
43
|
<%# Input area (fixed at bottom) %>
|
44
44
|
<div class="lexi-chat-alpha-input-area">
|
45
45
|
<div class="lexi-chat-alpha-input-container">
|
46
|
+
<!-- Structured Input (always rendered, conditionally displays content) -->
|
47
|
+
<%= render meta_structured_input, record: local_assigns[:record], structured_input_config: false, is_structured_input: false %>
|
48
|
+
|
49
|
+
<!-- Regular Form Input (always present) -->
|
46
50
|
<%= render partial: meta_response_form, locals: {record: local_assigns[:record], response_enabled: true, workflow_execution_id: active_execution&.id, chat_id: active_execution&.workflow_steps&.last&.chat&.id, step_has_repetitions: true } %>
|
47
51
|
</div>
|
48
52
|
</div>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<div class="structured-input-container"
|
2
|
+
role="radiogroup"
|
3
|
+
aria-labelledby="structured-radio-legend"
|
4
|
+
aria-required="true">
|
5
|
+
<legend id="structured-radio-legend" class="sr-only">Select one option</legend>
|
6
|
+
<div class="structured-input-options">
|
7
|
+
<% options.each_with_index do |option, index| %>
|
8
|
+
<div class="structured-radio-option">
|
9
|
+
<input
|
10
|
+
type="radio"
|
11
|
+
id="single_choice_<%= index %>"
|
12
|
+
name="single_choice_selection"
|
13
|
+
value="<%= option['value'] %>"
|
14
|
+
class="structured-radio-input"
|
15
|
+
data-action="change->meta-workflows--structured-form-submit#handleSubmit"
|
16
|
+
aria-describedby="structured-radio-help"
|
17
|
+
required
|
18
|
+
>
|
19
|
+
<label for="single_choice_<%= index %>" class="structured-radio-label">
|
20
|
+
<%= option['label'] %>
|
21
|
+
</label>
|
22
|
+
</div>
|
23
|
+
<% end %>
|
24
|
+
</div>
|
25
|
+
<div id="structured-radio-help" class="sr-only">Use arrow keys to navigate between options</div>
|
26
|
+
</div>
|
@@ -1,44 +1,47 @@
|
|
1
1
|
<%= turbo_frame_tag target_frame_id(record, form: true) do %>
|
2
2
|
<div>
|
3
|
-
<%= form_with url:
|
3
|
+
<%= form_with url: workflow_execution_id.present? ? meta_workflows.human_path(workflow_execution_id) : "#",
|
4
4
|
method: :patch,
|
5
5
|
id: "#{target_frame_id(record, form: true)}_lexi",
|
6
6
|
data: { controller: "meta-workflows--lexi-form-submit", "meta-workflows--lexi-form-submit-target": "form" } do |form| %>
|
7
7
|
<%= form.hidden_field :chat_id, value: chat_id %>
|
8
|
+
<% if local_assigns[:is_structured_input] %>
|
9
|
+
<%= form.hidden_field :auto_advance, value: true %>
|
10
|
+
<% end %>
|
8
11
|
<fieldset>
|
9
12
|
<div class="lexi-form-container">
|
10
13
|
<!-- Input Container -->
|
11
14
|
<div class="lexi-input-container lexi-input-max-height">
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
<!-- Text Input -->
|
16
|
+
<div class="lexi-textarea-wrapper">
|
17
|
+
<%= form.text_area :message, rows: 1, placeholder: random_chat_placeholder, disabled: local_assigns[:responding] || !local_assigns[:response_enabled], class: "lexi-textarea lexi-textarea-min-height", data: { action: "keypress.enter->meta-workflows--lexi-form-submit#handleKeyDown submit->meta-workflows--lexi-form-submit#handleSubmit", "meta-workflows--lexi-form-submit-target": "textarea" } %>
|
18
|
+
</div>
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
20
|
+
<div class="lexi-input-controls">
|
21
|
+
<div class="lexi-input-icons">
|
22
|
+
<button type="button" class="lexi-icon-button" disabled>
|
23
|
+
<i class="fa-solid fa-microphone text-lg"></i>
|
24
|
+
</button>
|
25
|
+
<button type="button" class="lexi-icon-button" disabled>
|
26
|
+
<i class="fa-solid fa-headphones text-lg"></i>
|
27
|
+
</button>
|
28
|
+
<button type="button" class="lexi-icon-button" disabled>
|
29
|
+
<i class="fa-solid fa-waveform-lines text-lg"></i>
|
30
|
+
</button>
|
31
|
+
</div>
|
32
|
+
<div>
|
33
|
+
<!-- Send button -->
|
34
|
+
<% if local_assigns[:responding] %>
|
35
|
+
<button type="button" class="lexi-send-button responding" disabled>
|
36
|
+
<i class="fa-solid fa-arrow-up text-lg"></i>
|
37
|
+
</button>
|
38
|
+
<% else %>
|
39
|
+
<%= form.button type: "submit", class: "lexi-send-button sm-btn sm-btn-primary" do %>
|
40
|
+
<i class="fa-solid fa-arrow-up text-lg"></i>
|
41
|
+
<% end %>
|
42
|
+
<% end %>
|
43
|
+
</div>
|
44
|
+
</div>
|
42
45
|
</div>
|
43
46
|
|
44
47
|
<p class="lexi-recording-notice">This chat is being recorded.</p>
|
@@ -48,7 +51,7 @@
|
|
48
51
|
<!-- Empty placeholder to prevent layout jank -->
|
49
52
|
<div aria-hidden="true"></div>
|
50
53
|
<% else %>
|
51
|
-
<%= form.button type: "submit", name: "
|
54
|
+
<%= form.button type: "submit", name: "manual_advance", value: "true", class: "lexi-advance-button #{'opacity-50 cursor-not-allowed' if local_assigns[:responding]}", disabled: local_assigns[:responding] do %>
|
52
55
|
<i class="fa-light fa-arrow-right"></i> Next
|
53
56
|
<% end %>
|
54
57
|
<% end %>
|
@@ -0,0 +1,60 @@
|
|
1
|
+
<div class="structured-input-container"
|
2
|
+
role="group"
|
3
|
+
aria-labelledby="structured-slider-legend">
|
4
|
+
<legend id="structured-slider-legend" class="sr-only">Select a value using the slider</legend>
|
5
|
+
<div class="structured-slider-container">
|
6
|
+
<div class="structured-slider-labels">
|
7
|
+
<span class="structured-slider-label-min"><%= options['min']['label'] %></span>
|
8
|
+
<span class="structured-slider-label-max"><%= options['max']['label'] %></span>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="structured-slider-wrapper">
|
12
|
+
<input
|
13
|
+
type="range"
|
14
|
+
id="range_value"
|
15
|
+
name="range_value"
|
16
|
+
min="<%= options['min']['value'] %>"
|
17
|
+
max="<%= options['max']['value'] %>"
|
18
|
+
step="<%= options['increment'] %>"
|
19
|
+
value="<%= options['min']['value'] %>"
|
20
|
+
class="structured-slider-input"
|
21
|
+
data-action="change->meta-workflows--structured-form-submit#handleSubmit"
|
22
|
+
aria-describedby="structured-slider-help structured-slider-value-display"
|
23
|
+
aria-valuemin="<%= options['min']['value'] %>"
|
24
|
+
aria-valuemax="<%= options['max']['value'] %>"
|
25
|
+
aria-valuenow="<%= options['min']['value'] %>"
|
26
|
+
aria-valuetext="<%= options['min']['value'] %>"
|
27
|
+
required
|
28
|
+
>
|
29
|
+
</div>
|
30
|
+
|
31
|
+
<div class="structured-slider-value">
|
32
|
+
<span class="structured-slider-current-value"
|
33
|
+
id="structured-slider-value-display"
|
34
|
+
data-range-display="<%= options['min']['value'] %>"
|
35
|
+
aria-live="polite">
|
36
|
+
<%= options['min']['value'] %>
|
37
|
+
</span>
|
38
|
+
</div>
|
39
|
+
<div id="structured-slider-help" class="sr-only">Use arrow keys to adjust the value</div>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<script>
|
44
|
+
// Update the displayed value when slider changes
|
45
|
+
document.addEventListener('DOMContentLoaded', function() {
|
46
|
+
const slider = document.getElementById('range_value');
|
47
|
+
const valueDisplay = document.querySelector('.structured-slider-current-value');
|
48
|
+
|
49
|
+
if (slider && valueDisplay) {
|
50
|
+
slider.addEventListener('input', function() {
|
51
|
+
valueDisplay.textContent = this.value;
|
52
|
+
valueDisplay.setAttribute('data-range-display', this.value);
|
53
|
+
|
54
|
+
// Update ARIA attributes for accessibility
|
55
|
+
slider.setAttribute('aria-valuenow', this.value);
|
56
|
+
slider.setAttribute('aria-valuetext', this.value);
|
57
|
+
});
|
58
|
+
}
|
59
|
+
});
|
60
|
+
</script>
|
@@ -0,0 +1,28 @@
|
|
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
|
+
%>
|
9
|
+
|
10
|
+
<%= form_with url: form_url,
|
11
|
+
method: :patch,
|
12
|
+
id: "#{target_frame_id(record, structured_input: true)}_form",
|
13
|
+
data: { controller: "meta-workflows--structured-form-submit", "meta-workflows--structured-form-submit-target": "form" } do |form| %>
|
14
|
+
<%= form.hidden_field :chat_id, value: chat_id %>
|
15
|
+
|
16
|
+
<div class="structured-input-wrapper">
|
17
|
+
<% case structured_input_config['type'] %>
|
18
|
+
<% when 'single_choice' %>
|
19
|
+
<%= render 'meta_workflows/radio_input', options: structured_input_config['options'] %>
|
20
|
+
<% when 'multiple_choice' %>
|
21
|
+
<%= render 'meta_workflows/checkbox_input', options: structured_input_config['options'] %>
|
22
|
+
<% when 'range' %>
|
23
|
+
<%= render 'meta_workflows/slider_input', options: structured_input_config['options'] %>
|
24
|
+
<% end %>
|
25
|
+
</div>
|
26
|
+
<% end %>
|
27
|
+
<% end %>
|
28
|
+
<% end %>
|
data/config/routes.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module MetaWorkflows
|
4
4
|
MAJOR = 0
|
5
5
|
MINOR = 9
|
6
|
-
PATCH =
|
6
|
+
PATCH = 20 # this is automatically incremented by the build process
|
7
7
|
|
8
8
|
VERSION = "#{MetaWorkflows::MAJOR}.#{MetaWorkflows::MINOR}.#{MetaWorkflows::PATCH}".freeze
|
9
9
|
end
|
@@ -45,6 +45,8 @@ module Services
|
|
45
45
|
process_agent_action(conversation, workflow_execution, workflow)
|
46
46
|
when 'human'
|
47
47
|
process_human_action(execution_step, workflow_execution)
|
48
|
+
when 'structured_human'
|
49
|
+
process_structured_human_action(execution_step, workflow_execution)
|
48
50
|
when 'record_redirect'
|
49
51
|
process_record_redirect_action(workflow_execution)
|
50
52
|
when 'collection_create'
|
@@ -133,6 +135,18 @@ module Services
|
|
133
135
|
# the step advancement is handled in the human input job
|
134
136
|
end
|
135
137
|
|
138
|
+
def process_structured_human_action(execution_step, workflow_execution)
|
139
|
+
::MetaWorkflows::HumanInputJob.perform_later(
|
140
|
+
user_id: user&.id,
|
141
|
+
record: workflow_execution.record,
|
142
|
+
params: {
|
143
|
+
inputs: inputs,
|
144
|
+
prompt_id: execution_step['prompt_id']
|
145
|
+
}
|
146
|
+
)
|
147
|
+
# the step advancement is handled in the human input job
|
148
|
+
end
|
149
|
+
|
136
150
|
def process_record_redirect_action(workflow_execution)
|
137
151
|
::MetaWorkflows::RecordRedirectJob.perform_later(workflow_execution: workflow_execution)
|
138
152
|
workflow_execution.increment_step
|
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.
|
4
|
+
version: 0.9.20
|
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-
|
12
|
+
date: 2025-07-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -125,6 +125,7 @@ files:
|
|
125
125
|
- app/assets/javascripts/meta_workflows/controllers/onboarding_controller.js
|
126
126
|
- app/assets/javascripts/meta_workflows/controllers/redirect_controller.js
|
127
127
|
- app/assets/javascripts/meta_workflows/controllers/response_scroll_controller.js
|
128
|
+
- app/assets/javascripts/meta_workflows/controllers/structured_form_submit_controller.js
|
128
129
|
- app/assets/javascripts/meta_workflows/controllers/tray_controller.js
|
129
130
|
- app/assets/javascripts/meta_workflows_manifest.js
|
130
131
|
- app/assets/stylesheets/meta_workflows/application.css
|
@@ -135,6 +136,7 @@ files:
|
|
135
136
|
- app/controllers/meta_workflows/debug_controller.rb
|
136
137
|
- app/controllers/meta_workflows/humans_controller.rb
|
137
138
|
- app/controllers/meta_workflows/meta_controller.rb
|
139
|
+
- app/controllers/meta_workflows/structured_humans_controller.rb
|
138
140
|
- app/controllers/meta_workflows/workflow_imports_controller.rb
|
139
141
|
- app/helpers/meta_workflows/application_helper.rb
|
140
142
|
- app/helpers/meta_workflows/debug_helper.rb
|
@@ -166,13 +168,17 @@ files:
|
|
166
168
|
- app/sidekiq/meta_workflows/tools/meta_workflow_tool.rb
|
167
169
|
- app/views/layouts/meta_workflows/application.html.erb
|
168
170
|
- app/views/meta_workflows/_assistant_message_bubble.html.erb
|
171
|
+
- app/views/meta_workflows/_checkbox_input.html.erb
|
169
172
|
- app/views/meta_workflows/_error_message.html.erb
|
170
173
|
- app/views/meta_workflows/_lexi_chat_alpha_tray.html.erb
|
171
174
|
- app/views/meta_workflows/_lexi_chat_right_tray.html.erb
|
172
175
|
- app/views/meta_workflows/_loader_message.html.erb
|
176
|
+
- app/views/meta_workflows/_radio_input.html.erb
|
173
177
|
- app/views/meta_workflows/_redirect.html.erb
|
174
178
|
- app/views/meta_workflows/_response_form_lexi.html.erb
|
175
179
|
- app/views/meta_workflows/_response_lexi.html.erb
|
180
|
+
- app/views/meta_workflows/_slider_input.html.erb
|
181
|
+
- app/views/meta_workflows/_structured_input.html.erb
|
176
182
|
- app/views/meta_workflows/_user_message_bubble.html.erb
|
177
183
|
- app/views/meta_workflows/debug/executions.html.erb
|
178
184
|
- app/views/meta_workflows/debug/show_execution.html.erb
|