meta_workflows 0.9.26 → 0.9.28

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: fd258997686745d11bbd71e0e28f56ac2a886920fab2b3e2bd2cb6f51bb81707
4
- data.tar.gz: 35ba8f541f70f44f393acdb38b3befd358a49a0075e9b693bf4817683d4857bc
3
+ metadata.gz: f07cda61b8fdd4e757dc33dd1115ce19949fce1f08547ab86b1fe1b16d9a6940
4
+ data.tar.gz: b3d24c6d2b7dffbc6b47efb2dcb673ef07701446fefb4da9279b50041aa9cfb0
5
5
  SHA512:
6
- metadata.gz: 7b5c3e6290d74842f123d72600ea5b92826086b29085dcc568009d442aca91e1e580883a8cab87fc3c9b4a33757d830944373173fc8da866b69ec82cb0616e80
7
- data.tar.gz: 28e92c1cc8c907aedd41325b04093b5188800ca55b9198a6c38beb294f081f268a7aed50830662815787dca7ff096b5891fe34168c5a160aebd1b0a5de124fb7
6
+ metadata.gz: cbd3d21ca6163e39df0808847c4bf2f7fc11a0de8bf7aa9d59cc9226f01176c0837babc6feedd9d19c2117dafe62e8aff12f85723e27b95f84247a515ff0eed8
7
+ data.tar.gz: 99858cf6ed8c38f0fe1d7bc9149787036c18b40db0c04738e495ba902b95c02d1fc3c7978c0b8a1ce0f7e1ed573a47f7d1bd91cd29394809954f0f82246e6f95
data/README.md CHANGED
@@ -347,6 +347,39 @@ steps:
347
347
  redirect_path: "/your/completion/path"
348
348
  ```
349
349
 
350
+ ### Workflow Parameters vs Inputs
351
+
352
+ MetaWorkflows supports two ways to pass data into workflows: `inputs` and `workflow_params`. Understanding the difference is crucial for proper workflow design.
353
+
354
+ #### `inputs` Hash from Service Instantiation
355
+ - **Scope**: Only available to the **very next step** of the workflow
356
+ - **Use Case**: Initial data needed to start the workflow
357
+ - **Lifetime**: Single-step only - not passed to subsequent steps
358
+
359
+ #### `workflow_params` Hash
360
+ - **Scope**: Available to **all steps** throughout the entire workflow execution
361
+ - **Use Case**: Data that needs to persist and be accessible across multiple workflow steps
362
+ - **Lifetime**: Entire workflow execution - accessible in every step
363
+
364
+ #### Priority and Conflicts
365
+
366
+ When both `inputs` and `workflow_params` contain the same attribute name:
367
+ - **`inputs` takes precedence** for the first step, but workflow params are still passed
368
+ - **This is not ideal** - avoid having duplicate keys in both hashes
369
+ - Choose one approach based on whether the data needs to persist throughout the workflow
370
+
371
+ #### When to Use Each
372
+
373
+ **Use `inputs` when:**
374
+ - Data is only needed for workflow initialization
375
+ - You want to pass temporary parameters to start the process
376
+ - The data doesn't need to be available in later steps
377
+
378
+ **Use `workflow_params` when:**
379
+ - Data needs to be available across multiple workflow steps
380
+ - You want to maintain state throughout the entire execution
381
+ - The information is core to the workflow's purpose
382
+
350
383
  ### Executing Workflows
351
384
 
352
385
  #### From Controller Actions
@@ -362,8 +395,13 @@ class YourController < ApplicationController
362
395
  record_id: @record.id,
363
396
  user_id: current_user.id,
364
397
  inputs: {
365
- title: @record.title,
366
- # other initial parameters
398
+ initial_title: @record.title, # Only available to first step
399
+ # other initial parameters for first step only
400
+ },
401
+ workflow_params: {
402
+ organization_id: @record.organization_id, # Available to all steps
403
+ user_role: current_user.role, # Available to all steps
404
+ # other parameters needed throughout workflow
367
405
  }
368
406
  )
369
407
 
@@ -372,6 +410,40 @@ class YourController < ApplicationController
372
410
  end
373
411
  ```
374
412
 
413
+ #### Example: Workflow Definition Using Both
414
+
415
+ ```yaml
416
+ name: "content_creation_workflow"
417
+ steps:
418
+ - name: "initial_analysis"
419
+ action: "agent"
420
+ prompt_id: "analyze_content"
421
+ input:
422
+ # This step can access both inputs (first step only) and workflow_params
423
+ - initial_title: "Starting title from inputs hash"
424
+ type: "string"
425
+ - organization_id: "Organization context from workflow_params"
426
+ type: "string"
427
+
428
+ - name: "content_generation"
429
+ action: "agent"
430
+ prompt_id: "generate_content"
431
+ input:
432
+ # This step can only access workflow_params (inputs not available)
433
+ - organization_id: "Organization context from workflow_params"
434
+ type: "string"
435
+ - user_role: "User role from workflow_params"
436
+ type: "string"
437
+
438
+ - name: "final_review"
439
+ action: "human"
440
+ prompt_id: "review_content"
441
+ input:
442
+ # This step can also only access workflow_params
443
+ - organization_id: "Organization context from workflow_params"
444
+ type: "string"
445
+ ```
446
+
375
447
  ### Using Meta Workflow UI Components
376
448
 
377
449
  Instead of creating custom views from scratch, use the provided Meta Workflow partials. The system now uses in-chat messaging for loader states and error handling, providing a seamless conversational experience:
@@ -60,7 +60,7 @@
60
60
  ol {
61
61
  display: flex;
62
62
  flex-direction: column;
63
- gap: .5rem;
63
+ gap: 0.5rem;
64
64
  padding-left: 0;
65
65
  list-style-type: none;
66
66
 
@@ -85,7 +85,7 @@
85
85
  counter-reset: list-counter;
86
86
 
87
87
  li::before {
88
- content: counter(list-counter) ".";
88
+ content: counter(list-counter) '.';
89
89
  counter-increment: list-counter;
90
90
  display: inline-block;
91
91
  width: 1.5em;
@@ -180,7 +180,9 @@
180
180
  }
181
181
 
182
182
  @keyframes dot-bounce {
183
- 0%, 80%, 100% {
183
+ 0%,
184
+ 80%,
185
+ 100% {
184
186
  transform: translateY(0);
185
187
  opacity: 0.7;
186
188
  }
@@ -192,7 +194,7 @@
192
194
 
193
195
  .lexi-loader-text {
194
196
  font-size: 1.25rem;
195
- background: linear-gradient(135deg, #7B5BA3 0%, #3A9999 50%, #7B5BA3 100%);
197
+ background: linear-gradient(135deg, #7b5ba3 0%, #3a9999 50%, #7b5ba3 100%);
196
198
  background-size: 200% 200%;
197
199
  background-clip: text;
198
200
  -webkit-background-clip: text;
@@ -211,13 +213,14 @@
211
213
  .lexi-dot {
212
214
  display: inline-block;
213
215
  font-size: 1.25rem;
214
- background: linear-gradient(135deg, #7B5BA3 0%, #3A9999 50%, #7B5BA3 100%);
216
+ background: linear-gradient(135deg, #7b5ba3 0%, #3a9999 50%, #7b5ba3 100%);
215
217
  background-size: 200% 200%;
216
218
  background-clip: text;
217
219
  -webkit-background-clip: text;
218
220
  -webkit-text-fill-color: transparent;
219
221
  color: transparent;
220
- animation: gradient-shift 2s ease-in-out infinite, dot-bounce 1.4s ease-in-out infinite;
222
+ animation: gradient-shift 2s ease-in-out infinite,
223
+ dot-bounce 1.4s ease-in-out infinite;
221
224
  -webkit-text-stroke: 0.75px rgba(0, 0, 0, 0.15);
222
225
  text-stroke: 0.75px rgba(0, 0, 0, 0.15);
223
226
  margin-left: -0.125rem;
@@ -339,7 +342,8 @@
339
342
 
340
343
  .lexi-input-container {
341
344
  display: flex;
342
- flex-direction: column;
345
+ flex-direction: row;
346
+ gap: 0.5rem;
343
347
  border: 1px solid var(--gray-300);
344
348
  border-radius: 0.5rem;
345
349
  background-color: rgba(255, 255, 255, 0.8);
@@ -353,6 +357,7 @@
353
357
 
354
358
  .lexi-textarea {
355
359
  width: 100%;
360
+ height: 100%;
356
361
  min-height: 35px;
357
362
  border: none;
358
363
  resize: none;
@@ -647,7 +652,8 @@
647
652
  }
648
653
 
649
654
  /* Hide standard collapse button when Lexi tray is collapsed */
650
- .gamma-tray.lexi-tray.collapsed > button[data-action="click->tray#toggle"]:not(.lexi-floating-avatar) {
655
+ .gamma-tray.lexi-tray.collapsed
656
+ > button[data-action='click->tray#toggle']:not(.lexi-floating-avatar) {
651
657
  display: none;
652
658
  }
653
659
 
@@ -683,7 +689,8 @@
683
689
  height: 100vh;
684
690
  }
685
691
 
686
- .welcome-screen, .chat-screen {
692
+ .welcome-screen,
693
+ .chat-screen {
687
694
  grid-column: 1;
688
695
  grid-row: 1;
689
696
  }
@@ -1028,7 +1035,7 @@
1028
1035
  }
1029
1036
 
1030
1037
  /* Structured Input Validation Styles */
1031
- .structured-input-container[data-invalid="true"] {
1038
+ .structured-input-container[data-invalid='true'] {
1032
1039
  border-color: var(--red-500);
1033
1040
  background-color: var(--red-50);
1034
1041
  }
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ module Concerns
5
+ module WorkflowParamsMergeable
6
+ extend ActiveSupport::Concern
7
+
8
+ def merge_matching_workflow_params(workflow_execution, execution_step, inputs = {})
9
+ merged_inputs = inputs.deep_symbolize_keys
10
+ return merged_inputs if workflow_execution.workflow_params.blank?
11
+
12
+ step_input_names = extract_input_variable_names(execution_step['input'] || [])
13
+
14
+ workflow_execution.workflow_params.each do |param_name, param_value|
15
+ param_string = param_name.to_s
16
+ param_symbol = param_name.to_sym
17
+
18
+ next unless step_input_names.include?(param_string) &&
19
+ !merged_inputs.key?(param_symbol) &&
20
+ !merged_inputs.key?(param_string)
21
+
22
+ merged_inputs[param_symbol] = param_value
23
+ end
24
+
25
+ merged_inputs
26
+ end
27
+
28
+ private
29
+
30
+ def extract_input_variable_names(input_definitions)
31
+ input_definitions.filter_map do |input_def|
32
+ input_def.keys.find { |key| key != 'type' }&.to_s if input_def.is_a?(Hash) && input_def.keys.any?
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -4,6 +4,7 @@ module MetaWorkflows
4
4
  class MetaJob < MetaWorkflows::ApplicationJob
5
5
  include MetaWorkflows::Concerns::ErrorHandling
6
6
  include MetaWorkflows::Streamable
7
+ include MetaWorkflows::Concerns::WorkflowParamsMergeable
7
8
 
8
9
  attr_accessor :chat, :inputs, :full_response, :user_id, :record, :auto_advancing, :manual_advancing,
9
10
  :workflow_execution
@@ -33,8 +34,12 @@ module MetaWorkflows
33
34
  current_step = workflow_execution.workflow_steps&.find_by(step: workflow_execution.current_step)
34
35
  @chat = current_step&.chat
35
36
 
37
+ current_step_data = workflow_execution.step_data(workflow_execution.current_step)
38
+ merged_inputs = merge_matching_workflow_params(workflow_execution, current_step_data, inputs)
39
+
36
40
  conversation = RubyConversations::Conversation.new(chat:)
37
- conversation.with_prompt(params[:prompt_id], inputs: inputs, description: "Running #{params[:prompt_id]} process")
41
+ conversation.with_prompt(params[:prompt_id], inputs: merged_inputs,
42
+ description: "Running #{params[:prompt_id]} process")
38
43
  end
39
44
 
40
45
  def continue_existing_conversation(params)
@@ -17,29 +17,16 @@
17
17
  </div>
18
18
 
19
19
  <div class="lexi-input-controls">
20
- <div class="lexi-input-icons">
21
- <button type="button" class="lexi-icon-button" disabled>
22
- <i class="fa-solid fa-microphone text-lg"></i>
20
+ <!-- Send button -->
21
+ <% if local_assigns[:responding] %>
22
+ <button type="button" class="lexi-send-button responding" disabled>
23
+ <i class="fa-solid fa-arrow-up text-lg"></i>
23
24
  </button>
24
- <button type="button" class="lexi-icon-button" disabled>
25
- <i class="fa-solid fa-headphones text-lg"></i>
26
- </button>
27
- <button type="button" class="lexi-icon-button" disabled>
28
- <i class="fa-solid fa-waveform-lines text-lg"></i>
29
- </button>
30
- </div>
31
- <div>
32
- <!-- Send button -->
33
- <% if local_assigns[:responding] %>
34
- <button type="button" class="lexi-send-button responding" disabled>
35
- <i class="fa-solid fa-arrow-up text-lg"></i>
36
- </button>
37
- <% else %>
38
- <%= form.button type: "button", class: "lexi-send-button sm-btn sm-btn-primary", data: { "meta-workflows--lexi-form-submit-target": "submitButton", action: "click->meta-workflows--lexi-form-submit#handleSubmit" } do %>
39
- <i class="fa-solid fa-arrow-up text-lg"></i>
40
- <% end %>
25
+ <% else %>
26
+ <%= form.button type: "button", class: "lexi-send-button sm-btn sm-btn-primary", data: { "meta-workflows--lexi-form-submit-target": "submitButton", action: "click->meta-workflows--lexi-form-submit#handleSubmit" } do %>
27
+ <i class="fa-solid fa-arrow-up text-lg"></i>
41
28
  <% end %>
42
- </div>
29
+ <% end %>
43
30
  </div>
44
31
  </div>
45
32
 
@@ -3,7 +3,7 @@
3
3
  module MetaWorkflows
4
4
  MAJOR = 0
5
5
  MINOR = 9
6
- PATCH = 26 # this is automatically incremented by the build process
6
+ PATCH = 28 # this is automatically incremented by the build process
7
7
 
8
8
  VERSION = "#{MetaWorkflows::MAJOR}.#{MetaWorkflows::MINOR}.#{MetaWorkflows::PATCH}".freeze
9
9
  end
@@ -5,6 +5,7 @@ module Services
5
5
  class MetaWorkflowService
6
6
  include ::MetaWorkflows::MetaWorkflowsHelper
7
7
  include ::MetaWorkflows::Streamable
8
+ include ::MetaWorkflows::Concerns::WorkflowParamsMergeable
8
9
 
9
10
  ACTIONS_WITHOUT_CONVERSATION = %w[record_redirect collection_create record_update].freeze
10
11
 
@@ -124,11 +125,13 @@ module Services
124
125
  end
125
126
 
126
127
  def process_human_action(execution_step, workflow_execution)
128
+ merged_inputs = merge_matching_workflow_params(workflow_execution, execution_step, inputs)
129
+
127
130
  ::MetaWorkflows::HumanInputJob.perform_later(
128
131
  user_id: user&.id,
129
132
  record: workflow_execution.record,
130
133
  params: {
131
- inputs: inputs,
134
+ inputs: merged_inputs,
132
135
  prompt_id: execution_step['prompt_id']
133
136
  }
134
137
  )
@@ -136,11 +139,13 @@ module Services
136
139
  end
137
140
 
138
141
  def process_structured_human_action(execution_step, workflow_execution)
142
+ merged_inputs = merge_matching_workflow_params(workflow_execution, execution_step, inputs)
143
+
139
144
  ::MetaWorkflows::HumanInputJob.perform_later(
140
145
  user_id: user&.id,
141
146
  record: workflow_execution.record,
142
147
  params: {
143
- inputs: inputs,
148
+ inputs: merged_inputs,
144
149
  prompt_id: execution_step['prompt_id']
145
150
  }
146
151
  )
@@ -235,9 +240,12 @@ module Services
235
240
  def create_and_configure_conversation(chat, workflow_execution, execution_output, execution_step)
236
241
  conversation = RubyConversations::Conversation.new(chat: chat)
237
242
  conversation.tool = ::MetaWorkflows::Tools::MetaWorkflowTool.build(workflow_execution, execution_output)
243
+
244
+ merged_inputs = merge_matching_workflow_params(workflow_execution, execution_step, inputs)
245
+
238
246
  conversation.with_prompt(
239
247
  execution_step['prompt_id'],
240
- inputs: inputs,
248
+ inputs: merged_inputs,
241
249
  description: "Executing step #{workflow_execution.current_step} of " \
242
250
  "workflow #{workflow_execution.workflow.name}"
243
251
  )
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.26
4
+ version: 0.9.28
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-21 00:00:00.000000000 Z
12
+ date: 2025-07-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -146,6 +146,7 @@ files:
146
146
  - app/helpers/meta_workflows/status_badge_helper.rb
147
147
  - app/jobs/meta_workflows/application_job.rb
148
148
  - app/jobs/meta_workflows/concerns/error_handling.rb
149
+ - app/jobs/meta_workflows/concerns/workflow_params_mergeable.rb
149
150
  - app/jobs/meta_workflows/human_input_job.rb
150
151
  - app/jobs/meta_workflows/meta_job.rb
151
152
  - app/jobs/meta_workflows/meta_workflow_job.rb