meta_workflows 0.9.15 → 0.9.17

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: a869293f98a7a997feb4c8c20173c25c85101ed225713e0a7080b40b45048771
4
- data.tar.gz: 7edc0a1e0bde4bbe02c60b6e0244253e055047684a9a793235ba858886451706
3
+ metadata.gz: 628e1aa6509bd6913af6ecd6c2fb8b9097d5699598624543c30e0e759a835e39
4
+ data.tar.gz: f4388abaaa5e93aeec97c9b00156c87dc93c21348710812f0b068098c90bc9a8
5
5
  SHA512:
6
- metadata.gz: 2b2768f55196fec5a94c8f83810563d7ac46625af7a9953e6aeb6f0fd1c6478bfe402c577f3f9076e93be1bbfc26a38834c27c94a39d937de56dd52b938ee332
7
- data.tar.gz: 6b1f0065875d24435071cbaa15fcda4f3683c1d2e719415949c3001aa6aa33f6c9d841ba870835ae083d2c5df145082f4b70077141690eb8588eb09b793b50b3
6
+ metadata.gz: 4e495070ef645745e8387d21e92dfb4792cdc7dfa8a8cd5680518f1e01454d394772d5a05eeff87e343ec922923b7eea85b4845db866a1ca91017c40b3d81cb1
7
+ data.tar.gz: b8e34fe5aa9a31d19f05f597432859087bb5d5bd2c9de8a2afc4822a29dbbedfb5a7cd2b236d8d805f32bc04b10edd2ed66027e3dfe5964f9fa2a37f8a6dfd5b
data/README.md CHANGED
@@ -233,10 +233,7 @@ steps:
233
233
  action: "agent"
234
234
  prompt_id: "generation_prompt"
235
235
  model: "your-preferred-model-id" # Use appropriate model from RubyLLM documentation
236
- step_progress:
237
- - "Analyzing input parameters..."
238
- - "Generating initial content..."
239
- - "Refining results..."
236
+ step_progress: ["Analyzing input parameters", "Generating initial content", "Refining results"]
240
237
  input:
241
238
  title:
242
239
  type: "string"
@@ -250,8 +247,7 @@ steps:
250
247
  - name: "human_review"
251
248
  action: "human"
252
249
  prompt_id: "review_prompt"
253
- step_progress:
254
- - "Preparing content for review..."
250
+ step_progress: ["Preparing content for review"]
255
251
  input:
256
252
  generated_content:
257
253
  type: "string"
@@ -267,9 +263,7 @@ steps:
267
263
  attributes_to_update:
268
264
  - "title"
269
265
  - "topic"
270
- step_progress:
271
- - "Updating record..."
272
- - "Saving changes..."
266
+ step_progress: ["Updating record", "Saving changes"]
273
267
  workflow_params:
274
268
  - name: "title"
275
269
  type: string
@@ -307,27 +301,87 @@ end
307
301
 
308
302
  ### Using Meta Workflow UI Components
309
303
 
310
- Instead of creating custom views from scratch, use the provided Meta Workflow partials:
304
+ 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:
305
+
306
+ #### Chat-Based UI Components
307
+
308
+ The workflow UI now consists of three main components:
309
+
310
+ 1. **Chat Messages (`meta_response`)**: Displays conversation history and responses
311
+ 2. **Loader Messages (`meta_loader_message`)**: Shows loading states as chat messages
312
+ 3. **Error Messages (`meta_error_message`)**: Displays errors as friendly chat messages
313
+ 4. **Response Form (`meta_response_form`)**: Handles user input during human interaction steps
311
314
 
312
315
  #### Complete View Example
313
316
 
314
317
  ```erb
315
318
  <%= turbo_stream_from turbo_stream_name(@record) %>
316
319
  <div class="max-w-3xl m-auto flex flex-col p-10 gap-6">
317
- <!-- Workflow progress and chat messages area -->
318
- <%= render partial: 'meta_workflows/loader', locals: {
320
+ <!-- Chat messages area with existing conversation -->
321
+ <%= render partial: 'meta_workflows/response_lexi', locals: {
319
322
  record: @record,
320
- step_progress: ["Generating draft content...", "Processing with AI..."]
323
+ chat: @active_chat,
324
+ messages: @active_chat&.messages&.order(:created_at) || [],
325
+ user_messages: @active_chat&.messages&.where(role: 'user')&.order(:created_at) || [],
326
+ last_message: @active_chat&.messages&.last,
327
+ full_response: nil,
328
+ is_streaming: false
321
329
  } %>
322
330
 
323
331
  <!-- User input area for human interaction steps -->
324
332
  <%= render partial: 'meta_workflows/response_form', locals: {
325
333
  record: @record,
326
- response_enabled: false
334
+ response_enabled: true,
335
+ workflow_execution_id: @workflow_execution&.id,
336
+ chat_id: @active_chat&.id,
337
+ step_has_repetitions: true
327
338
  } %>
328
339
  </div>
329
340
  ```
330
341
 
342
+ #### Built-in Chat Tray Components
343
+
344
+ For a complete chat experience, use the pre-built chat tray components:
345
+
346
+ **Right Tray (Gamma) - Sidebar Chat:**
347
+ ```erb
348
+ <%= render partial: 'meta_workflows/lexi_chat_right_tray', locals: {
349
+ record: @record
350
+ } %>
351
+ ```
352
+
353
+ **Alpha Tray (Main) - Full-Screen Chat:**
354
+ ```erb
355
+ <%= render partial: 'meta_workflows/lexi_chat_alpha_tray', locals: {
356
+ record: @record
357
+ } %>
358
+ ```
359
+
360
+ #### Individual Component Usage
361
+
362
+ **Loader Message (appears as chat message):**
363
+ ```erb
364
+ <%= render partial: 'meta_workflows/loader_message', locals: {
365
+ record: @record,
366
+ step_progress: ["Generating content", "Processing with AI", "Finalizing results"]
367
+ } %>
368
+ ```
369
+
370
+ **Error Message (appears as chat message):**
371
+ ```erb
372
+ <%= render partial: 'meta_workflows/error_message', locals: {
373
+ record: @record
374
+ } %>
375
+ ```
376
+
377
+ #### Key Features
378
+
379
+ - **In-Chat Loading**: Loader messages appear as animated chat bubbles from the assistant
380
+ - **Friendly Error Messages**: Errors display as conversational messages with friendly styling
381
+ - **Seamless Integration**: Chat history persists while new interactions are processed
382
+ - **Responsive Design**: Components adapt to different tray layouts (sidebar, main, bottom)
383
+ - **Turbo Stream Updates**: Real-time updates without page refreshes using dedicated frame targeting
384
+
331
385
  ## File Structure Overview
332
386
 
333
387
  The MetaWorkflows gem follows a structured organization:
@@ -478,7 +532,7 @@ To add a new action type:
478
532
  ### Workflow Design
479
533
  - Keep steps focused and single-purpose
480
534
  - Design clear input/output schemas
481
- - Provide meaningful progress messages
535
+ - Provide meaningful progress messages without ellipsis (the loader UI handles animated dots automatically)
482
536
  - Handle error cases gracefully
483
537
  - Use `record_update` steps to persist intermediate workflow results
484
538
  - Choose appropriate AI models for each step based on task complexity and cost considerations
@@ -9,7 +9,7 @@ export default class extends Controller {
9
9
 
10
10
  connect() {
11
11
  if (!this.hasPhrasesValue || this.phrasesValue.length === 0) {
12
- this.phrasesValue = ["Talking to the LLM..."]
12
+ this.phrasesValue = ["Thinking"]
13
13
  }
14
14
 
15
15
  this.currentIndex = 0
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ module Streamable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include MetaWorkflows::MetaWorkflowsHelper
9
+ end
10
+
11
+ private
12
+
13
+ # Broadcasts loader message with step progress
14
+ def broadcast_loader_stream(workflow_execution)
15
+ step_progress = workflow_execution.step_progress(workflow_execution.current_step) || default_step_progress
16
+ record = workflow_execution.record
17
+
18
+ Turbo::StreamsChannel.broadcast_replace_to(
19
+ turbo_stream_name(record),
20
+ target: target_frame_id(record, loader: true),
21
+ partial: meta_loader_message,
22
+ locals: { record: record, step_progress: step_progress }
23
+ )
24
+ end
25
+
26
+ # Broadcasts error response when workflow fails
27
+ def broadcast_error_response(workflow_execution)
28
+ record = workflow_execution.record
29
+
30
+ Turbo::StreamsChannel.broadcast_replace_to(
31
+ turbo_stream_name(record),
32
+ target: target_frame_id(record, loader: true),
33
+ partial: meta_error_message,
34
+ locals: { record: record }
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 }
46
+ )
47
+ end
48
+
49
+ # Builds response form stream for direct response
50
+ def build_response_form_stream(record:, workflow_execution:, response_enabled:, responding:, chat_id:)
51
+ turbo_stream.replace(
52
+ target_frame_id(record, form: true),
53
+ partial: meta_response_form,
54
+ locals: build_response_form_locals(record:, workflow_execution:, response_enabled:, responding:, chat_id:)
55
+ )
56
+ end
57
+
58
+ # Builds locals hash for response form
59
+ def build_response_form_locals(record:, workflow_execution:, response_enabled:, responding:, chat_id:)
60
+ {
61
+ record: record,
62
+ workflow_execution_id: workflow_execution.id,
63
+ chat_id: chat_id,
64
+ response_enabled: response_enabled,
65
+ responding: responding,
66
+ step_has_repetitions: current_step_has_repetitions?(workflow_execution)
67
+ }
68
+ end
69
+ end
70
+ end
@@ -3,10 +3,12 @@
3
3
  module MetaWorkflows
4
4
  class HumansController < MetaController
5
5
  include MetaWorkflows::MetaWorkflowsHelper
6
+ include MetaWorkflows::Streamable
6
7
  before_action :set_workflow_data, only: [:update]
7
8
 
8
9
  def update
9
10
  if params[:advance].present? || should_auto_advance?
11
+ @workflow_execution.increment_step
10
12
  render_loader_stream
11
13
  process_human_input(auto_advancing: should_auto_advance?, manual_advancing: params[:advance].present?)
12
14
  else
@@ -44,19 +46,9 @@ module MetaWorkflows
44
46
  respond_to do |format|
45
47
  format.turbo_stream do
46
48
  render turbo_stream: [
47
- turbo_stream.replace(
48
- target_frame_id(@record, loader: true),
49
- partial: meta_loader_message,
50
- locals: {
51
- record: @record,
52
- step_progress: fetch_step_progress
53
- }
54
- ),
55
- turbo_stream.replace(
56
- target_frame_id(@record, form: true),
57
- partial: meta_response_form,
58
- locals: response_form_locals(response_enabled: false)
59
- )
49
+ build_loader_stream(record: @record, workflow_execution: @workflow_execution),
50
+ build_response_form_stream(record: @record, workflow_execution: @workflow_execution,
51
+ response_enabled: false, responding: false, chat_id: params[:chat_id])
60
52
  ]
61
53
  end
62
54
  end
@@ -65,24 +57,15 @@ module MetaWorkflows
65
57
  def render_response_form_stream
66
58
  respond_to do |format|
67
59
  format.turbo_stream do
68
- render turbo_stream: turbo_stream.replace(
69
- target_frame_id(@record, form: true),
70
- partial: meta_response_form,
71
- locals: response_form_locals(response_enabled: true, responding: true)
60
+ render turbo_stream: build_response_form_stream(
61
+ record: @record,
62
+ workflow_execution: @workflow_execution,
63
+ response_enabled: true,
64
+ responding: true,
65
+ chat_id: params[:chat_id]
72
66
  )
73
67
  end
74
68
  end
75
69
  end
76
-
77
- def response_form_locals(response_enabled: true, responding: false)
78
- {
79
- record: @record,
80
- workflow_execution_id: @workflow_execution.id,
81
- chat_id: params[:chat_id],
82
- response_enabled: response_enabled,
83
- responding: responding,
84
- step_has_repetitions: current_step_has_repetitions?(@workflow_execution)
85
- }
86
- end
87
70
  end
88
71
  end
@@ -57,8 +57,6 @@ module MetaWorkflows
57
57
 
58
58
  def advance_workflow(params)
59
59
  dialogue = collect_messages_from_chat(params)
60
- workflow_execution = active_workflow_execution
61
- workflow_execution.increment_step
62
60
 
63
61
  MetaWorkflows::MetaWorkflowJob.perform_later(
64
62
  record_id: @record.id,
@@ -2,6 +2,8 @@
2
2
 
3
3
  module MetaWorkflows
4
4
  class MetaJob < MetaWorkflows::ApplicationJob
5
+ include MetaWorkflows::Streamable
6
+
5
7
  attr_accessor :chat, :inputs, :full_response, :user_id, :record, :auto_advancing, :manual_advancing
6
8
 
7
9
  private
@@ -42,14 +44,15 @@ module MetaWorkflows
42
44
  end
43
45
 
44
46
  def process_and_broadcast(conversation)
45
- workflow_step = current_workflow_step
47
+ workflow_execution = active_workflow_execution
48
+ workflow_step = current_workflow_step(workflow_execution)
46
49
 
47
50
  begin
48
51
  clear_previous_errors(workflow_step)
49
52
  execute_llm_conversation(conversation)
50
53
  finalize_conversation(conversation)
51
54
  rescue StandardError => e
52
- handle_llm_error(e, workflow_step, conversation)
55
+ handle_llm_error(e, workflow_step, workflow_execution, conversation)
53
56
  raise
54
57
  end
55
58
  end
@@ -70,10 +73,10 @@ module MetaWorkflows
70
73
  broadcast_form(chat) unless auto_advancing
71
74
  end
72
75
 
73
- def handle_llm_error(error, workflow_step, conversation)
76
+ def handle_llm_error(error, workflow_step, workflow_execution, conversation)
74
77
  log_error(error)
75
78
  store_error_in_workflow_step(error, workflow_step, conversation)
76
- broadcast_error_response
79
+ broadcast_error_response(workflow_execution)
77
80
  end
78
81
 
79
82
  def log_error(error)
@@ -90,25 +93,12 @@ module MetaWorkflows
90
93
  })
91
94
  end
92
95
 
93
- def current_workflow_step
94
- workflow_execution = active_workflow_execution
96
+ def current_workflow_step(workflow_execution)
95
97
  return nil unless workflow_execution
96
98
 
97
99
  workflow_execution.workflow_steps.find_by(step: workflow_execution.current_step)
98
100
  end
99
101
 
100
- def broadcast_error_response
101
- # Replace loader with error message
102
- Turbo::StreamsChannel.broadcast_replace_to(
103
- turbo_stream_name(record),
104
- target: target_frame_id(record, loader: true),
105
- partial: 'meta_workflows/error_message',
106
- locals: {
107
- record: record
108
- }
109
- )
110
- end
111
-
112
102
  def broadcast_form(chat)
113
103
  raise NotImplementedError
114
104
  end
@@ -1,3 +1,4 @@
1
+ <%= turbo_frame_tag target_frame_id(record, loader: true) do %>
1
2
  <div class="lexi-message-assistant">
2
3
  <div class="lexi-message-bubble-assistant">
3
4
  <div class="lexi-message-content-assistant">
@@ -11,4 +12,5 @@
11
12
  </div>
12
13
  </div>
13
14
  </div>
14
- </div>
15
+ </div>
16
+ <% end %>
@@ -1,3 +1,4 @@
1
+ <%= turbo_frame_tag target_frame_id(record, loader: true) do %>
1
2
  <div class="lexi-message-assistant">
2
3
  <div class="lexi-loader-bubble">
3
4
  <div class="lexi-message-content-assistant"
@@ -13,4 +14,5 @@
13
14
  </span>
14
15
  </div>
15
16
  </div>
16
- </div>
17
+ </div>
18
+ <% end %>
@@ -3,7 +3,7 @@
3
3
  module MetaWorkflows
4
4
  MAJOR = 0
5
5
  MINOR = 9
6
- PATCH = 15 # this is automatically incremented by the build process
6
+ PATCH = 17 # this is automatically incremented by the build process
7
7
 
8
8
  VERSION = "#{MetaWorkflows::MAJOR}.#{MetaWorkflows::MINOR}.#{MetaWorkflows::PATCH}".freeze
9
9
  end
@@ -3,6 +3,9 @@
3
3
  module Services
4
4
  module MetaWorkflows
5
5
  class MetaWorkflowService
6
+ include ::MetaWorkflows::MetaWorkflowsHelper
7
+ include ::MetaWorkflows::Streamable
8
+
6
9
  ACTIONS_WITHOUT_CONVERSATION = %w[record_redirect collection_create record_update].freeze
7
10
 
8
11
  attr_reader :record, :workflow_name, :user, :inputs, :workflow_params
@@ -35,6 +38,8 @@ module Services
35
38
  private
36
39
 
37
40
  def process_action(execution_step, conversation, workflow_execution, workflow)
41
+ broadcast_loader_stream(workflow_execution)
42
+
38
43
  case execution_step['action']
39
44
  when 'agent'
40
45
  process_agent_action(conversation, workflow_execution, workflow)
@@ -102,6 +107,7 @@ module Services
102
107
  Rails.logger.error(error.backtrace.join("\n"))
103
108
 
104
109
  record_error_details(error, workflow_step, workflow, workflow_execution) if workflow_step
110
+ broadcast_error_response(workflow_execution)
105
111
  end
106
112
 
107
113
  def record_error_details(error, workflow_step, workflow, workflow_execution)
@@ -145,6 +151,7 @@ module Services
145
151
  continue_workflow(workflow_execution)
146
152
  rescue StandardError => e
147
153
  Rails.logger.error("Error creating collection: #{e.message}")
154
+ broadcast_error_response(workflow_execution)
148
155
  # The workflow should halt on error per requirements - do not increment step
149
156
  end
150
157
  end
@@ -158,6 +165,7 @@ module Services
158
165
  continue_workflow(workflow_execution)
159
166
  rescue StandardError => e
160
167
  Rails.logger.error("Error updating record: #{e.message}")
168
+ broadcast_error_response(workflow_execution)
161
169
  # Let Sidekiq handle retry - reraise the error
162
170
  raise
163
171
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meta_workflows
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.15
4
+ version: 0.9.17
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-11 00:00:00.000000000 Z
12
+ date: 2025-07-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -128,6 +128,7 @@ files:
128
128
  - app/assets/javascripts/meta_workflows/controllers/tray_controller.js
129
129
  - app/assets/javascripts/meta_workflows_manifest.js
130
130
  - app/assets/stylesheets/meta_workflows/application.css
131
+ - app/controllers/concerns/meta_workflows/streamable.rb
131
132
  - app/controllers/concerns/tray_configurable.rb
132
133
  - app/controllers/meta_workflows/application_controller.rb
133
134
  - app/controllers/meta_workflows/base_debug_controller.rb