meta_workflows 0.9.19 → 0.9.21
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/concerns/meta_workflows/streamable.rb +59 -19
- data/app/controllers/meta_workflows/humans_controller.rb +7 -32
- data/app/controllers/meta_workflows/meta_controller.rb +33 -0
- data/app/controllers/meta_workflows/structured_humans_controller.rb +50 -0
- data/app/helpers/meta_workflows/application_helper.rb +1 -0
- data/app/helpers/meta_workflows/meta_workflows_helper.rb +18 -3
- data/app/jobs/meta_workflows/human_input_job.rb +31 -25
- data/app/jobs/meta_workflows/meta_job.rb +14 -7
- data/app/views/meta_workflows/_chat_history.html.erb +23 -0
- data/app/views/meta_workflows/_checkbox_input.html.erb +24 -0
- data/app/views/meta_workflows/_lexi_chat_alpha_tray.html.erb +10 -6
- data/app/views/meta_workflows/_lexi_chat_right_tray.html.erb +7 -5
- 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/_response_lexi.html.erb +15 -42
- data/app/views/meta_workflows/_slider_input.html.erb +60 -0
- data/app/views/meta_workflows/_streaming_response.html.erb +17 -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 +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ece4bc1842d69733d2b951c9406fda3e83f070ca2f6603e97a826d8fd0cd87d
|
4
|
+
data.tar.gz: 8208fa70b0e19259a21eaad10aa915d111b4cdc4135c16d9bc7e7929186fa7c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be5e764d6188da7ea34398d3ab7649ee0c8bf3964deab167ee1c778b21ea2b3e8f4f86018bfdf2a3a6aea549f44187549ddc6803c76516c886c7f06184ba16d8
|
7
|
+
data.tar.gz: '028a513a4c38ea854f5f15798f3c6d8546653b94edf5bb41a14938f3dddf1c1ba6e40421956fd9f0a8c4e9982fea17879f53557911caefd1928012c72c36b97c'
|
@@ -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
|
+
}
|
@@ -17,9 +17,14 @@ module MetaWorkflows
|
|
17
17
|
|
18
18
|
Turbo::StreamsChannel.broadcast_replace_to(
|
19
19
|
turbo_stream_name(record),
|
20
|
-
target: target_frame_id(record,
|
21
|
-
partial:
|
22
|
-
locals: {
|
20
|
+
target: target_frame_id(record, streaming: true),
|
21
|
+
partial: meta_streaming_response,
|
22
|
+
locals: {
|
23
|
+
record: record,
|
24
|
+
show_loader: true,
|
25
|
+
step_progress: step_progress,
|
26
|
+
full_response: nil
|
27
|
+
}
|
23
28
|
)
|
24
29
|
end
|
25
30
|
|
@@ -27,22 +32,11 @@ module MetaWorkflows
|
|
27
32
|
def broadcast_error_response(workflow_execution)
|
28
33
|
record = workflow_execution.record
|
29
34
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
)
|
36
|
-
end
|
37
|
-
|
38
|
-
# Builds loader stream for direct response
|
39
|
-
def build_loader_stream(record:, workflow_execution:)
|
40
|
-
step_progress = workflow_execution.step_progress(workflow_execution.current_step) || default_step_progress
|
41
|
-
|
42
|
-
turbo_stream.replace(
|
43
|
-
target_frame_id(record, loader: true),
|
44
|
-
partial: meta_loader_message,
|
45
|
-
locals: { record: record, step_progress: step_progress }
|
35
|
+
broadcast_streaming_response(
|
36
|
+
record: record,
|
37
|
+
full_response: nil,
|
38
|
+
show_loader: false,
|
39
|
+
show_error: true
|
46
40
|
)
|
47
41
|
end
|
48
42
|
|
@@ -66,5 +60,51 @@ module MetaWorkflows
|
|
66
60
|
step_has_repetitions: current_step_has_repetitions?(workflow_execution)
|
67
61
|
}
|
68
62
|
end
|
63
|
+
|
64
|
+
# Builds chat history stream for instant user message display
|
65
|
+
def build_chat_history_stream(record:, workflow_execution:)
|
66
|
+
chat_history = workflow_execution.execution_chat_history
|
67
|
+
|
68
|
+
turbo_stream.replace(
|
69
|
+
target_frame_id(record, chat_history: true),
|
70
|
+
partial: meta_chat_history,
|
71
|
+
locals: {
|
72
|
+
record: record,
|
73
|
+
chat_history: chat_history,
|
74
|
+
current_user_messages: []
|
75
|
+
}
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Builds streaming response stream for loader display
|
80
|
+
def build_streaming_response_stream(record:, show_loader: false, step_progress: nil)
|
81
|
+
turbo_stream.replace(
|
82
|
+
target_frame_id(record, streaming: true),
|
83
|
+
partial: meta_streaming_response,
|
84
|
+
locals: {
|
85
|
+
record: record,
|
86
|
+
show_loader: show_loader,
|
87
|
+
step_progress: step_progress || default_step_progress,
|
88
|
+
full_response: nil
|
89
|
+
}
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Broadcasts streaming response to the streaming frame
|
94
|
+
def broadcast_streaming_response(record:, full_response: nil, show_loader: false,
|
95
|
+
show_error: false, error_message: nil)
|
96
|
+
Turbo::StreamsChannel.broadcast_replace_to(
|
97
|
+
turbo_stream_name(record),
|
98
|
+
target: target_frame_id(record, streaming: true),
|
99
|
+
partial: meta_streaming_response,
|
100
|
+
locals: {
|
101
|
+
record: record,
|
102
|
+
full_response: full_response,
|
103
|
+
show_loader: show_loader,
|
104
|
+
show_error: show_error,
|
105
|
+
error_message: error_message
|
106
|
+
}
|
107
|
+
)
|
108
|
+
end
|
69
109
|
end
|
70
110
|
end
|
@@ -2,19 +2,19 @@
|
|
2
2
|
|
3
3
|
module MetaWorkflows
|
4
4
|
class HumansController < MetaController
|
5
|
-
include MetaWorkflows::MetaWorkflowsHelper
|
6
|
-
include MetaWorkflows::Streamable
|
7
5
|
before_action :set_workflow_data, only: [:update]
|
8
6
|
|
9
7
|
def update
|
8
|
+
append_user_message_to_history(params[:message]) if params[:message].present?
|
9
|
+
|
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
|
-
|
15
|
-
process_human_input(auto_advancing:, manual_advancing: params[:
|
14
|
+
render_processing_state_stream
|
15
|
+
process_human_input(auto_advancing:, manual_advancing: params[:manual_advance].present?)
|
16
16
|
else
|
17
|
-
|
17
|
+
render_processing_state_stream
|
18
18
|
process_human_input
|
19
19
|
end
|
20
20
|
end
|
@@ -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!
|
@@ -43,31 +44,5 @@ module MetaWorkflows
|
|
43
44
|
}
|
44
45
|
)
|
45
46
|
end
|
46
|
-
|
47
|
-
def render_loader_stream
|
48
|
-
respond_to do |format|
|
49
|
-
format.turbo_stream do
|
50
|
-
render turbo_stream: [
|
51
|
-
build_loader_stream(record: @record, workflow_execution: @workflow_execution),
|
52
|
-
build_response_form_stream(record: @record, workflow_execution: @workflow_execution,
|
53
|
-
response_enabled: false, responding: false, chat_id: params[:chat_id])
|
54
|
-
]
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def render_response_form_stream
|
60
|
-
respond_to do |format|
|
61
|
-
format.turbo_stream do
|
62
|
-
render turbo_stream: build_response_form_stream(
|
63
|
-
record: @record,
|
64
|
-
workflow_execution: @workflow_execution,
|
65
|
-
response_enabled: true,
|
66
|
-
responding: true,
|
67
|
-
chat_id: params[:chat_id]
|
68
|
-
)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
47
|
end
|
73
48
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module MetaWorkflows
|
4
4
|
class MetaController < MetaWorkflows::ApplicationController
|
5
5
|
include MetaWorkflows::MetaWorkflowsHelper
|
6
|
+
include MetaWorkflows::Streamable
|
6
7
|
|
7
8
|
def set_workflow_data
|
8
9
|
@workflow_execution = MetaWorkflows::WorkflowExecution.includes(:record, :workflow,
|
@@ -21,5 +22,37 @@ module MetaWorkflows
|
|
21
22
|
def current_step_repetitions
|
22
23
|
@workflow_execution.step_repetitions(@workflow_execution.current_step)
|
23
24
|
end
|
25
|
+
|
26
|
+
def append_user_message_to_history(user_message)
|
27
|
+
message_data = {
|
28
|
+
role: 'user',
|
29
|
+
content: user_message,
|
30
|
+
model_id: nil,
|
31
|
+
input_tokens: nil,
|
32
|
+
output_tokens: nil,
|
33
|
+
created_at: Time.current.iso8601,
|
34
|
+
step: @workflow_execution.current_step
|
35
|
+
}
|
36
|
+
|
37
|
+
# Ensure ExecutionChatHistory exists
|
38
|
+
chat_history = @workflow_execution.execution_chat_history ||
|
39
|
+
@workflow_execution.create_execution_chat_history!
|
40
|
+
|
41
|
+
# Append the user message to the history
|
42
|
+
chat_history.append_message(message_data)
|
43
|
+
end
|
44
|
+
|
45
|
+
def render_processing_state_stream
|
46
|
+
respond_to do |format|
|
47
|
+
format.turbo_stream do
|
48
|
+
render turbo_stream: [
|
49
|
+
build_chat_history_stream(record: @record, workflow_execution: @workflow_execution),
|
50
|
+
build_streaming_response_stream(record: @record, show_loader: true),
|
51
|
+
build_response_form_stream(record: @record, workflow_execution: @workflow_execution,
|
52
|
+
response_enabled: false, responding: false, chat_id: params[:chat_id])
|
53
|
+
]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
24
57
|
end
|
25
58
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
class StructuredHumansController < MetaController
|
5
|
+
before_action :set_workflow_data, only: [:update]
|
6
|
+
|
7
|
+
def update
|
8
|
+
formatted_input = extract_and_format_structured_input
|
9
|
+
|
10
|
+
append_user_message_to_history(formatted_input) if formatted_input.present?
|
11
|
+
render_processing_state_stream
|
12
|
+
process_structured_human_input(formatted_input)
|
13
|
+
@workflow_execution.increment_step
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def process_structured_human_input(formatted_input)
|
19
|
+
MetaWorkflows::HumanInputJob.perform_later(
|
20
|
+
user_id: current_user.id,
|
21
|
+
record: @record,
|
22
|
+
auto_advancing: true,
|
23
|
+
manual_advancing: false,
|
24
|
+
params: {
|
25
|
+
inputs: formatted_input,
|
26
|
+
chat_id: params[:chat_id],
|
27
|
+
prompt_id: @prompt_id
|
28
|
+
}
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def extract_and_format_structured_input
|
33
|
+
step_data = @workflow_execution.step_data(@workflow_execution.current_step)
|
34
|
+
structured_input_config = step_data&.dig('structured_input')
|
35
|
+
|
36
|
+
return params[:message].to_s unless structured_input_config
|
37
|
+
|
38
|
+
case structured_input_config['type']
|
39
|
+
when 'single_choice'
|
40
|
+
params[:single_choice_selection].to_s
|
41
|
+
when 'multiple_choice'
|
42
|
+
Array(params[:multiple_choice_selections]).join(', ')
|
43
|
+
when 'range'
|
44
|
+
params[:range_value].to_s
|
45
|
+
else
|
46
|
+
params[:message].to_s
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -2,11 +2,14 @@
|
|
2
2
|
|
3
3
|
module MetaWorkflows
|
4
4
|
module MetaWorkflowsHelper
|
5
|
-
def target_frame_id(record,
|
5
|
+
def target_frame_id(record, **options)
|
6
6
|
base_id = "#{record.class.name.downcase}_#{record.id}"
|
7
7
|
|
8
|
-
return "#{base_id}_loader" if loader
|
9
|
-
return "#{base_id}_form" if form
|
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]
|
10
13
|
|
11
14
|
base_id
|
12
15
|
end
|
@@ -27,10 +30,22 @@ module MetaWorkflows
|
|
27
30
|
'meta_workflows/response_form_lexi'
|
28
31
|
end
|
29
32
|
|
33
|
+
def meta_structured_input
|
34
|
+
'meta_workflows/structured_input'
|
35
|
+
end
|
36
|
+
|
30
37
|
def meta_response
|
31
38
|
'meta_workflows/response_lexi'
|
32
39
|
end
|
33
40
|
|
41
|
+
def meta_chat_history
|
42
|
+
'meta_workflows/chat_history'
|
43
|
+
end
|
44
|
+
|
45
|
+
def meta_streaming_response
|
46
|
+
'meta_workflows/streaming_response'
|
47
|
+
end
|
48
|
+
|
34
49
|
def meta_redirect
|
35
50
|
'meta_workflows/redirect'
|
36
51
|
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,35 +17,23 @@ module MetaWorkflows
|
|
14
17
|
|
15
18
|
private
|
16
19
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
def broadcast_response(full_response)
|
27
|
+
# Update streaming response frame with LLM content
|
28
|
+
broadcast_streaming_response(
|
29
|
+
record: record,
|
30
|
+
full_response: full_response,
|
31
|
+
show_loader: false,
|
32
|
+
show_error: false
|
27
33
|
)
|
28
|
-
|
29
|
-
# Update main chat content
|
30
|
-
Turbo::StreamsChannel.broadcast_replace_to(turbo_stream_name(record),
|
31
|
-
target: target_frame_id(record),
|
32
|
-
locals: {
|
33
|
-
record: record,
|
34
|
-
chat: chat,
|
35
|
-
full_response: full_response,
|
36
|
-
user_messages: user_messages,
|
37
|
-
messages: messages,
|
38
|
-
last_message: last_message,
|
39
|
-
is_streaming: is_streaming
|
40
|
-
},
|
41
|
-
partial: meta_response)
|
42
34
|
end
|
43
35
|
|
44
36
|
def broadcast_form(chat)
|
45
|
-
workflow_execution = active_workflow_execution
|
46
37
|
Turbo::StreamsChannel.broadcast_replace_to(
|
47
38
|
turbo_stream_name(record),
|
48
39
|
target: target_frame_id(record, form: true),
|
@@ -51,7 +42,22 @@ module MetaWorkflows
|
|
51
42
|
workflow_execution_id: workflow_execution&.id,
|
52
43
|
response_enabled: true,
|
53
44
|
chat_id: chat.id,
|
54
|
-
step_has_repetitions: current_step_has_repetitions?(workflow_execution)
|
45
|
+
step_has_repetitions: current_step_has_repetitions?(workflow_execution),
|
46
|
+
is_structured_input: is_structured_input }
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def broadcast_structured_input(chat)
|
51
|
+
Turbo::StreamsChannel.broadcast_replace_to(
|
52
|
+
turbo_stream_name(record),
|
53
|
+
target: target_frame_id(record, structured_input: true),
|
54
|
+
partial: meta_structured_input,
|
55
|
+
locals: {
|
56
|
+
record: record,
|
57
|
+
chat_id: chat.id,
|
58
|
+
structured_input_config: structured_input_config,
|
59
|
+
is_structured_input: is_structured_input
|
60
|
+
}
|
55
61
|
)
|
56
62
|
end
|
57
63
|
|
@@ -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
|
@@ -65,20 +65,23 @@ module MetaWorkflows
|
|
65
65
|
def execute_llm_conversation(conversation)
|
66
66
|
conversation.call_llm do |chunk|
|
67
67
|
@full_response << (chunk.content || '')
|
68
|
-
broadcast_response(
|
68
|
+
broadcast_response(full_response) unless auto_advancing
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
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,7 +95,11 @@ module MetaWorkflows
|
|
92
95
|
raise NotImplementedError
|
93
96
|
end
|
94
97
|
|
95
|
-
def
|
98
|
+
def broadcast_structured_input(chat)
|
99
|
+
raise NotImplementedError
|
100
|
+
end
|
101
|
+
|
102
|
+
def broadcast_response(full_response)
|
96
103
|
raise NotImplementedError
|
97
104
|
end
|
98
105
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<%= turbo_frame_tag target_frame_id(record, chat_history: true) do %>
|
2
|
+
<div class="lexi-chat-history">
|
3
|
+
<!-- Show historical messages from ExecutionChatHistory -->
|
4
|
+
<% if local_assigns[:chat_history]&.history&.any? %>
|
5
|
+
<% local_assigns[:chat_history].history.each do |historical_message| %>
|
6
|
+
<% next if historical_message['role'] == 'system' %>
|
7
|
+
|
8
|
+
<% if historical_message['role'] == 'user' %>
|
9
|
+
<%= render 'meta_workflows/user_message_bubble', content: historical_message['content'] %>
|
10
|
+
<% elsif historical_message['role'] == 'assistant' %>
|
11
|
+
<%= render 'meta_workflows/assistant_message_bubble', content: historical_message['content'] %>
|
12
|
+
<% end %>
|
13
|
+
<% end %>
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
<!-- Show current user messages (for instant display) -->
|
17
|
+
<% if local_assigns[:current_user_messages]&.any? %>
|
18
|
+
<% local_assigns[:current_user_messages].each do |user_message| %>
|
19
|
+
<%= render 'meta_workflows/user_message_bubble', content: user_message %>
|
20
|
+
<% end %>
|
21
|
+
<% end %>
|
22
|
+
</div>
|
23
|
+
<% 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>
|
@@ -30,19 +30,23 @@
|
|
30
30
|
<div id="main-scroll-container" class="lexi-chat-alpha-messages">
|
31
31
|
<%= render partial: meta_response, locals: {
|
32
32
|
record: local_assigns[:record],
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
chat_history: chat_history,
|
34
|
+
current_user_messages: [],
|
35
|
+
show_loader: false,
|
36
|
+
show_error: false,
|
37
|
+
step_progress: nil,
|
37
38
|
full_response: nil,
|
38
|
-
|
39
|
-
chat_history: chat_history
|
39
|
+
error_message: nil
|
40
40
|
} %>
|
41
41
|
</div>
|
42
42
|
|
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>
|
@@ -1,6 +1,7 @@
|
|
1
1
|
<%# Lexi Chat Tray %>
|
2
2
|
<% active_execution = local_assigns[:record]&.workflow_executions&.order(created_at: :desc)&.first %>
|
3
3
|
<% active_chat = active_execution&.workflow_steps&.last&.chat %>
|
4
|
+
<% chat_history = active_execution&.execution_chat_history %>
|
4
5
|
|
5
6
|
<div class="lexi-chat-tray lexi-chat-container-full">
|
6
7
|
<!-- Header with Lexi logo and action icons -->
|
@@ -22,12 +23,13 @@
|
|
22
23
|
<div id="main-scroll-container" class="lexi-chat-messages">
|
23
24
|
<%= render partial: meta_response, locals: {
|
24
25
|
record: local_assigns[:record],
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
chat_history: chat_history,
|
27
|
+
current_user_messages: [],
|
28
|
+
show_loader: false,
|
29
|
+
show_error: false,
|
30
|
+
step_progress: nil,
|
29
31
|
full_response: nil,
|
30
|
-
|
32
|
+
error_message: nil
|
31
33
|
} %>
|
32
34
|
</div>
|
33
35
|
|
@@ -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 %>
|
@@ -1,49 +1,22 @@
|
|
1
1
|
<%= turbo_frame_tag target_frame_id(record) do %>
|
2
2
|
<div id="response-content-container" class="lexi-response-container" data-controller="response-scroll">
|
3
3
|
|
4
|
-
<!--
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
<%= render 'meta_workflows/user_message_bubble', content: historical_message['content'] %>
|
11
|
-
<% elsif historical_message['role'] == 'assistant' %>
|
12
|
-
<%= render 'meta_workflows/assistant_message_bubble', content: historical_message['content'] %>
|
13
|
-
<% end %>
|
14
|
-
<% end %>
|
15
|
-
<% end %>
|
4
|
+
<!-- Persistent Chat History Frame -->
|
5
|
+
<%= render partial: meta_chat_history, locals: {
|
6
|
+
record: record,
|
7
|
+
chat_history: local_assigns[:chat_history],
|
8
|
+
current_user_messages: local_assigns[:current_user_messages] || []
|
9
|
+
} %>
|
16
10
|
|
17
|
-
|
18
|
-
|
19
|
-
|
11
|
+
<!-- Streaming Response Frame -->
|
12
|
+
<%= render partial: meta_streaming_response, locals: {
|
13
|
+
record: record,
|
14
|
+
show_loader: local_assigns[:show_loader] || true,
|
15
|
+
show_error: local_assigns[:show_error] || false,
|
16
|
+
step_progress: local_assigns[:step_progress],
|
17
|
+
full_response: local_assigns[:full_response],
|
18
|
+
error_message: local_assigns[:error_message]
|
19
|
+
} %>
|
20
20
|
|
21
|
-
<!-- Show all saved messages except the first user message -->
|
22
|
-
<% messages.each_with_index do |message, index| %>
|
23
|
-
<% next if message.role == 'system' %>
|
24
|
-
<% next if message.role == 'user' && message == user_messages.first %>
|
25
|
-
<% next if is_streaming && message.role == 'assistant' && message == last_message %>
|
26
|
-
|
27
|
-
<% if message.role == 'user' %>
|
28
|
-
<%= render 'meta_workflows/user_message_bubble', content: message.content %>
|
29
|
-
<% elsif message.role == 'assistant' %>
|
30
|
-
<%= render 'meta_workflows/assistant_message_bubble', content: message.content %>
|
31
|
-
<% end %>
|
32
|
-
<% end %>
|
33
|
-
|
34
|
-
<%= render 'meta_workflows/assistant_message_bubble', content: full_response %>
|
35
|
-
<% end %>
|
36
|
-
<% end %>
|
37
|
-
|
38
|
-
<div class="lexi-loader-container">
|
39
|
-
<%= turbo_frame_tag target_frame_id(record, loader: true) do %>
|
40
|
-
<% if chat.blank? || chat.messages.empty? %>
|
41
|
-
<%= render partial: meta_loader_message, locals: {
|
42
|
-
record: local_assigns[:record],
|
43
|
-
step_progress: default_step_progress
|
44
|
-
} %>
|
45
|
-
<% end %>
|
46
|
-
<% end %>
|
47
|
-
</div>
|
48
21
|
</div>
|
49
22
|
<% 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,17 @@
|
|
1
|
+
<%= turbo_frame_tag target_frame_id(record, streaming: true) do %>
|
2
|
+
<div class="lexi-streaming-response">
|
3
|
+
<% if local_assigns[:show_loader] %>
|
4
|
+
<!-- Show loader message -->
|
5
|
+
<%= render partial: meta_loader_message, locals: {
|
6
|
+
record: record,
|
7
|
+
step_progress: local_assigns[:step_progress] || default_step_progress
|
8
|
+
} %>
|
9
|
+
<% elsif local_assigns[:show_error] %>
|
10
|
+
<!-- Show error message -->
|
11
|
+
<%= render partial: meta_error_message, locals: { record: record } %>
|
12
|
+
<% elsif local_assigns[:full_response].present? %>
|
13
|
+
<!-- Show streaming LLM response -->
|
14
|
+
<%= render 'meta_workflows/assistant_message_bubble', content: local_assigns[:full_response] %>
|
15
|
+
<% end %>
|
16
|
+
</div>
|
17
|
+
<% end %>
|
@@ -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 = 21 # 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.21
|
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-18 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,19 @@ 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/_chat_history.html.erb
|
172
|
+
- app/views/meta_workflows/_checkbox_input.html.erb
|
169
173
|
- app/views/meta_workflows/_error_message.html.erb
|
170
174
|
- app/views/meta_workflows/_lexi_chat_alpha_tray.html.erb
|
171
175
|
- app/views/meta_workflows/_lexi_chat_right_tray.html.erb
|
172
176
|
- app/views/meta_workflows/_loader_message.html.erb
|
177
|
+
- app/views/meta_workflows/_radio_input.html.erb
|
173
178
|
- app/views/meta_workflows/_redirect.html.erb
|
174
179
|
- app/views/meta_workflows/_response_form_lexi.html.erb
|
175
180
|
- app/views/meta_workflows/_response_lexi.html.erb
|
181
|
+
- app/views/meta_workflows/_slider_input.html.erb
|
182
|
+
- app/views/meta_workflows/_streaming_response.html.erb
|
183
|
+
- app/views/meta_workflows/_structured_input.html.erb
|
176
184
|
- app/views/meta_workflows/_user_message_bubble.html.erb
|
177
185
|
- app/views/meta_workflows/debug/executions.html.erb
|
178
186
|
- app/views/meta_workflows/debug/show_execution.html.erb
|