meta_workflows 0.7.0

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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +36 -0
  3. data/README.md +498 -0
  4. data/Rakefile +10 -0
  5. data/app/assets/javascripts/meta_workflows/controllers/loading_phrases_controller.js +31 -0
  6. data/app/assets/javascripts/meta_workflows/controllers/redirect_controller.js +15 -0
  7. data/app/assets/javascripts/meta_workflows/controllers/response_scroll_controller.js +67 -0
  8. data/app/assets/javascripts/meta_workflows_manifest.js +13 -0
  9. data/app/assets/stylesheets/meta_workflows/application.css +161 -0
  10. data/app/controllers/meta_workflows/application_controller.rb +10 -0
  11. data/app/controllers/meta_workflows/debug_controller.rb +101 -0
  12. data/app/controllers/meta_workflows/humans_controller.rb +96 -0
  13. data/app/controllers/meta_workflows/meta_controller.rb +21 -0
  14. data/app/helpers/meta_workflows/application_helper.rb +7 -0
  15. data/app/helpers/meta_workflows/debug_helper.rb +54 -0
  16. data/app/helpers/meta_workflows/execution_helper.rb +77 -0
  17. data/app/helpers/meta_workflows/formatting_helper.rb +30 -0
  18. data/app/helpers/meta_workflows/meta_workflows_helper.rb +41 -0
  19. data/app/helpers/meta_workflows/status_badge_helper.rb +66 -0
  20. data/app/jobs/meta_workflows/application_job.rb +14 -0
  21. data/app/jobs/meta_workflows/human_input_job.rb +35 -0
  22. data/app/jobs/meta_workflows/meta_job.rb +121 -0
  23. data/app/jobs/meta_workflows/meta_workflow_job.rb +20 -0
  24. data/app/jobs/meta_workflows/record_redirect_job.rb +36 -0
  25. data/app/mailers/meta_workflows/application_mailer.rb +8 -0
  26. data/app/models/meta_workflows/application_record.rb +7 -0
  27. data/app/models/meta_workflows/chat.rb +20 -0
  28. data/app/models/meta_workflows/message.rb +11 -0
  29. data/app/models/meta_workflows/tool_call.rb +7 -0
  30. data/app/models/meta_workflows/workflow.rb +32 -0
  31. data/app/models/meta_workflows/workflow_execution.rb +23 -0
  32. data/app/models/meta_workflows/workflow_step.rb +49 -0
  33. data/app/models/meta_workflows.rb +7 -0
  34. data/app/services/meta_workflows/execution_filter_service.rb +80 -0
  35. data/app/sidekiq/meta_workflows/tools/meta_workflow_tool.rb +86 -0
  36. data/app/views/layouts/meta_workflows/application.html.erb +17 -0
  37. data/app/views/layouts/meta_workflows/debug.html.erb +47 -0
  38. data/app/views/meta_workflows/_loader.html.erb +22 -0
  39. data/app/views/meta_workflows/_redirect.html.erb +1 -0
  40. data/app/views/meta_workflows/_response.html.erb +5 -0
  41. data/app/views/meta_workflows/_response_form.html.erb +36 -0
  42. data/app/views/meta_workflows/debug/executions.html.erb +187 -0
  43. data/app/views/meta_workflows/debug/show_execution.html.erb +283 -0
  44. data/app/views/meta_workflows/debug/show_workflow.html.erb +148 -0
  45. data/app/views/meta_workflows/debug/workflows.html.erb +110 -0
  46. data/config/routes.rb +13 -0
  47. data/db/migrate/20250530220618_create_meta_workflows_workflows.rb +16 -0
  48. data/db/migrate/20250530220634_create_meta_workflows_workflow_executions.rb +18 -0
  49. data/db/migrate/20250530220704_create_meta_workflows_chats.rb +16 -0
  50. data/db/migrate/20250530220722_create_meta_workflows_messages.rb +19 -0
  51. data/db/migrate/20250530220737_create_meta_workflows_tool_calls.rb +18 -0
  52. data/db/migrate/20250530220750_create_meta_workflows_workflow_steps.rb +17 -0
  53. data/db/migrate/20250613213159_add_error_fields_to_workflow_steps.rb +8 -0
  54. data/lib/meta_workflows/asset_installer.rb +509 -0
  55. data/lib/meta_workflows/configuration.rb +39 -0
  56. data/lib/meta_workflows/engine.rb +47 -0
  57. data/lib/meta_workflows/export_execution_service.rb +56 -0
  58. data/lib/meta_workflows/version.rb +5 -0
  59. data/lib/meta_workflows.rb +9 -0
  60. data/lib/services/meta_workflows/application_service.rb +11 -0
  61. data/lib/services/meta_workflows/meta_workflow_service.rb +277 -0
  62. data/lib/services/meta_workflows/updaters/meta_service.rb +39 -0
  63. data/lib/tasks/meta_workflows_tasks.rake +153 -0
  64. metadata +219 -0
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class HumanInputJob < MetaJob
5
+ include MetaWorkflows::MetaWorkflowsHelper
6
+ queue_as :default
7
+
8
+ def perform(user_id:, record:, params:)
9
+ setup(user_id, record)
10
+ conversation = initialize_conversation(params)
11
+ process_and_broadcast(conversation)
12
+ end
13
+
14
+ private
15
+
16
+ def broadcast_response(response)
17
+ Turbo::StreamsChannel.broadcast_replace_to(turbo_stream_name(record),
18
+ target: target_frame_id(record),
19
+ locals: { record: record, response: response },
20
+ partial: meta_response)
21
+ end
22
+
23
+ def broadcast_form(chat)
24
+ Turbo::StreamsChannel.broadcast_replace_to(
25
+ turbo_stream_name(record),
26
+ target: target_frame_id(record, form: true),
27
+ partial: meta_response_form,
28
+ locals: { record: record,
29
+ workflow_execution_id: record.workflow_execution.id,
30
+ response_enabled: true,
31
+ chat_id: chat.id }
32
+ )
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class MetaJob < MetaWorkflows::ApplicationJob
5
+ attr_accessor :chat, :inputs, :full_response, :user_id, :record
6
+
7
+ private
8
+
9
+ def setup(user_id, record)
10
+ @user_id = user_id
11
+ @record = record.class.find(record.id)
12
+ end
13
+
14
+ def initialize_conversation(params)
15
+ if params[:chat_id].blank?
16
+ initialize_new_conversation(params)
17
+ else
18
+ continue_existing_conversation(params)
19
+ end
20
+ end
21
+
22
+ def initialize_new_conversation(params)
23
+ inputs = params.symbolize_keys[:inputs]
24
+
25
+ workflow_execution = record.workflow_execution
26
+ current_step = workflow_execution.workflow_steps.find_by(step: workflow_execution.current_step)
27
+ @chat = current_step&.chat
28
+
29
+ @full_response = +''
30
+
31
+ conversation = RubyConversations::Conversation.new(chat:)
32
+ conversation.with_prompt(params[:prompt_id], inputs: inputs, description: "Running #{params[:prompt_id]} process")
33
+ end
34
+
35
+ def continue_existing_conversation(params)
36
+ @chat = MetaWorkflows::Chat.find(params[:chat_id])
37
+ conversation = RubyConversations::Conversation.new(chat:, id: chat.conversation_id)
38
+
39
+ new_message = params[:inputs]
40
+ conversation.with_user_message(new_message, description: "Following up on #{params[:prompt_id]} process")
41
+ @full_response = chat.messages[1..].map(&:content).join + format_user_message(new_message)
42
+
43
+ conversation
44
+ end
45
+
46
+ def process_and_broadcast(conversation)
47
+ workflow_step = current_workflow_step
48
+
49
+ begin
50
+ clear_previous_errors(workflow_step)
51
+ execute_llm_conversation(conversation)
52
+ finalize_conversation(conversation)
53
+ rescue StandardError => e
54
+ handle_llm_error(e, workflow_step, conversation)
55
+ raise
56
+ end
57
+ end
58
+
59
+ def clear_previous_errors(workflow_step)
60
+ workflow_step&.clear_error if workflow_step&.error?
61
+ end
62
+
63
+ def execute_llm_conversation(conversation)
64
+ conversation.call_llm do |chunk|
65
+ full_response << (chunk.content || '')
66
+ broadcast_response(full_response)
67
+ end
68
+ end
69
+
70
+ def finalize_conversation(conversation)
71
+ chat.update!(conversation_id: conversation.id) if chat.conversation_id.blank?
72
+ broadcast_response(full_response)
73
+ broadcast_form(chat)
74
+ end
75
+
76
+ def handle_llm_error(error, workflow_step, conversation)
77
+ log_error(error)
78
+ store_error_in_workflow_step(error, workflow_step, conversation)
79
+ broadcast_error_response(error)
80
+ end
81
+
82
+ def log_error(error)
83
+ Rails.logger.error("LLM Error in MetaJob: #{error.message}")
84
+ Rails.logger.error(error.backtrace.join("\n"))
85
+ end
86
+
87
+ def store_error_in_workflow_step(error, workflow_step, conversation)
88
+ workflow_step&.record_error(error, additional_details: {
89
+ job_class: self.class.name,
90
+ chat_id: chat&.id,
91
+ conversation_id: conversation&.id,
92
+ full_response_length: full_response&.length || 0
93
+ })
94
+ end
95
+
96
+ def current_workflow_step
97
+ return nil unless record&.workflow_execution
98
+
99
+ workflow_execution = record.workflow_execution
100
+ workflow_execution.workflow_steps.find_by(step: workflow_execution.current_step)
101
+ end
102
+
103
+ def broadcast_error_response(error)
104
+ error_message = "Error occurred during LLM processing: #{error.message}"
105
+ broadcast_response(error_message)
106
+ end
107
+
108
+ def broadcast_response(response)
109
+ raise NotImplementedError
110
+ end
111
+
112
+ def broadcast_form(chat)
113
+ raise NotImplementedError
114
+ end
115
+
116
+ def format_user_message(message)
117
+ '<div class="flex p-4 border border-slate-300 rounded-xl bg-white text-xl ' \
118
+ "font-semibold leading-snug\">#{message}</div>\n\n"
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class MetaWorkflowJob < MetaWorkflows::ApplicationJob
5
+ queue_as :default
6
+
7
+ def perform(record_id:, record_type:, workflow_name:, user_id:, inputs: {}, workflow_params: nil)
8
+ record = record_type.constantize.find(record_id)
9
+ user = User.find(user_id)
10
+
11
+ Services::MetaWorkflows::MetaWorkflowService.new(
12
+ record: record,
13
+ workflow_name: workflow_name,
14
+ user: user,
15
+ inputs: inputs,
16
+ workflow_params: workflow_params
17
+ ).call
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class RecordRedirectJob < MetaWorkflows::ApplicationJob
5
+ include MetaWorkflows::MetaWorkflowsHelper
6
+ queue_as :default
7
+
8
+ def perform(workflow_execution:)
9
+ @workflow_execution = workflow_execution
10
+ params = @workflow_execution.workflow_params || {}
11
+ broadcast_redirect(params)
12
+ end
13
+
14
+ private
15
+
16
+ def broadcast_redirect(params)
17
+ record_id = params['record_id']
18
+ record_class = params['record_class']
19
+ redirect_method = params['redirect_method']
20
+
21
+ record_class_constant = record_class.to_s.safe_constantize
22
+ record = record_class_constant&.find_by(id: record_id)
23
+
24
+ redirect_path = Rails.application.routes.url_helpers.send(redirect_method.to_s, record)
25
+
26
+ Turbo::StreamsChannel.broadcast_append_to(
27
+ turbo_stream_name(@workflow_execution.record),
28
+ target: target_frame_id(@workflow_execution.record, form: true),
29
+ partial: meta_redirect,
30
+ locals: {
31
+ redirect_path: redirect_path
32
+ }
33
+ )
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class ApplicationMailer < ActionMailer::Base
5
+ default from: 'from@example.com'
6
+ layout 'mailer'
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class Chat < ApplicationRecord
5
+ acts_as_chat(message_class: 'MetaWorkflows::Message', tool_call_class: 'MetaWorkflows::ToolCall')
6
+
7
+ belongs_to :user, optional: true
8
+ has_many :workflow_steps, dependent: :nullify, class_name: 'MetaWorkflows::WorkflowStep'
9
+
10
+ validates :model_id, presence: true
11
+ validates :provider, presence: true
12
+
13
+ after_initialize :set_chat
14
+
15
+ def set_chat
16
+ RubyConversations::Conversation.configure_llm_credentials
17
+ @chat = RubyLLM::Chat.new(model: model_id, provider:)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class Message < ApplicationRecord
5
+ acts_as_message(chat_class: 'MetaWorkflows::Chat', tool_call_class: 'MetaWorkflows::ToolCall')
6
+
7
+ def to_h
8
+ attributes
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class ToolCall < ApplicationRecord
5
+ acts_as_tool_call(message_class: 'MetaWorkflows::Message')
6
+ end
7
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class Workflow < ApplicationRecord
5
+ validates :name, presence: true, uniqueness: true
6
+ validates :recipe, presence: true
7
+
8
+ has_many :workflow_executions, dependent: :destroy, class_name: 'MetaWorkflows::WorkflowExecution'
9
+ has_many :courses, through: :workflow_executions
10
+ has_many :workflow_steps, through: :workflow_executions, class_name: 'MetaWorkflows::WorkflowStep'
11
+
12
+ def step_progress(step)
13
+ recipe.dig('steps', step, 'step_progress')
14
+ end
15
+
16
+ def prompt_id(step)
17
+ recipe.dig('steps', step, 'prompt_id')
18
+ end
19
+
20
+ def step_data(step)
21
+ recipe.dig('steps', step)
22
+ end
23
+
24
+ def step_output(step)
25
+ recipe.dig('steps', step, 'output')
26
+ end
27
+
28
+ def total_steps
29
+ recipe['steps'].size
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class WorkflowExecution < ApplicationRecord
5
+ belongs_to :workflow, class_name: 'MetaWorkflows::Workflow'
6
+ belongs_to :record, polymorphic: true
7
+ has_many :workflow_steps, dependent: :destroy, class_name: 'MetaWorkflows::WorkflowStep'
8
+
9
+ # Default values for fields
10
+ attribute :current_step, :integer, default: 0
11
+ attribute :repetition, :integer, default: 0
12
+ attribute :completed, :boolean, default: false
13
+ attribute :workflow_params, :json, default: -> { {} }
14
+
15
+ validates :current_step, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
16
+ validates :repetition, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
17
+ validates :completed, inclusion: { in: [true, false] }
18
+
19
+ def increment_step
20
+ update(current_step: current_step + 1)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class WorkflowStep < ApplicationRecord
5
+ belongs_to :workflow_execution, class_name: 'MetaWorkflows::WorkflowExecution'
6
+ belongs_to :chat, class_name: 'MetaWorkflows::Chat', optional: true
7
+
8
+ validates :step, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
9
+ validates :workflow_execution_id, uniqueness: { scope: :step }
10
+
11
+ has_one :workflow, through: :workflow_execution, class_name: 'MetaWorkflows::Workflow'
12
+
13
+ # Scopes for error handling
14
+ scope :with_errors, -> { where.not(error_message: nil) }
15
+ scope :without_errors, -> { where(error_message: nil) }
16
+
17
+ # Error handling methods
18
+ def error?
19
+ error_message.present?
20
+ end
21
+
22
+ def record_error(error, additional_details: {})
23
+ update!(
24
+ error_message: error.message,
25
+ error_type: error.class.name,
26
+ error_details: {
27
+ backtrace: error.backtrace&.first(10),
28
+ additional_details: additional_details
29
+ }.compact,
30
+ error_occurred_at: Time.current
31
+ )
32
+ end
33
+
34
+ def clear_error
35
+ update!(
36
+ error_message: nil,
37
+ error_type: nil,
38
+ error_details: nil,
39
+ error_occurred_at: nil
40
+ )
41
+ end
42
+
43
+ def error_summary
44
+ return nil unless error?
45
+
46
+ "#{error_type}: #{error_message}"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ def self.table_name_prefix
5
+ 'meta_workflows_'
6
+ end
7
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class ExecutionFilterService
5
+ def initialize(executions, params)
6
+ @executions = executions
7
+ @params = params
8
+ end
9
+
10
+ def apply_filters
11
+ executions = @executions
12
+ executions = filter_by_workflow(executions)
13
+ executions = filter_by_status(executions)
14
+ executions = filter_by_date_range(executions)
15
+ search_executions(executions)
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :params
21
+
22
+ def filter_by_workflow(executions)
23
+ return executions if params[:workflow_id].blank?
24
+
25
+ executions.where(workflow_id: params[:workflow_id])
26
+ end
27
+
28
+ def filter_by_status(executions)
29
+ return executions if params[:status].blank?
30
+
31
+ case params[:status]
32
+ when 'completed'
33
+ executions.where(completed: true)
34
+ when 'running'
35
+ executions.where(completed: false).where(updated_at: 1.hour.ago..)
36
+ when 'failed'
37
+ executions.where(completed: false).where(updated_at: ...1.hour.ago)
38
+ else
39
+ executions
40
+ end
41
+ end
42
+
43
+ def filter_by_date_range(executions)
44
+ executions = filter_by_date_from(executions)
45
+ filter_by_date_to(executions)
46
+ end
47
+
48
+ def filter_by_date_from(executions)
49
+ return executions if params[:date_from].blank?
50
+
51
+ begin
52
+ date_from = Date.parse(params[:date_from])
53
+ executions.where(created_at: date_from.beginning_of_day..)
54
+ rescue ArgumentError
55
+ executions
56
+ end
57
+ end
58
+
59
+ def filter_by_date_to(executions)
60
+ return executions if params[:date_to].blank?
61
+
62
+ begin
63
+ date_to = Date.parse(params[:date_to])
64
+ executions.where(created_at: ..date_to.end_of_day)
65
+ rescue ArgumentError
66
+ executions
67
+ end
68
+ end
69
+
70
+ def search_executions(executions)
71
+ return executions if params[:search].blank?
72
+
73
+ executions.joins(:workflow)
74
+ .where(
75
+ 'meta_workflows_workflows.name ILIKE ? OR meta_workflows_workflow_executions.id::text ILIKE ?',
76
+ "%#{params[:search]}%", "%#{params[:search]}%"
77
+ )
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ module Tools
5
+ class MetaWorkflowTool < RubyLLM::Tool
6
+ ALLOWED_PARAM_TYPES = %w[array string integer number boolean object].freeze
7
+
8
+ attr_reader :workflow_execution, :step_output
9
+
10
+ def initialize(workflow_execution:, step_output:)
11
+ super()
12
+ @workflow_execution = workflow_execution
13
+ @step_output = step_output
14
+
15
+ self.class.description("Workflow step output for #{workflow_execution.workflow.name}")
16
+ self.class.setup_params(step_output) if step_output.present?
17
+ end
18
+
19
+ def self.setup_params(step_output)
20
+ step_output.each do |output_param|
21
+ process_param(output_param)
22
+ end
23
+ end
24
+
25
+ def self.process_param(param_def)
26
+ common_args = process_param_definition(param_def)
27
+ param param_def['name'].to_sym, **common_args
28
+ end
29
+
30
+ def self.process_array_items(items_array)
31
+ items_hash = {}
32
+
33
+ items_array.each do |param_def|
34
+ common_args = process_param_definition(param_def)
35
+ items_hash[param_def['name'].to_sym] = common_args
36
+ end
37
+
38
+ items_hash
39
+ end
40
+
41
+ def self.process_object_properties(properties_hash)
42
+ properties = {}
43
+
44
+ properties_hash.each do |key, property_def|
45
+ common_args = process_param_definition(property_def)
46
+ properties[key.to_sym] = common_args
47
+ end
48
+
49
+ properties
50
+ end
51
+
52
+ def self.process_param_definition(param_def)
53
+ type = param_def['type']
54
+
55
+ validate_type!(type)
56
+
57
+ common_args = {
58
+ type: type,
59
+ desc: param_def['description']
60
+ }
61
+
62
+ if type == 'array'
63
+ common_args[:items] = process_array_items(param_def['items'])
64
+ elsif type == 'object' && param_def['properties'].present?
65
+ common_args[:properties] = process_object_properties(param_def['properties'])
66
+ end
67
+
68
+ common_args
69
+ end
70
+
71
+ def self.validate_type!(type)
72
+ return if ALLOWED_PARAM_TYPES.include?(type)
73
+
74
+ raise ArgumentError, "Invalid type: #{type}. Allowed types are: #{ALLOWED_PARAM_TYPES.join(', ')}"
75
+ end
76
+
77
+ def self.build(workflow_execution, step_output)
78
+ new(workflow_execution: workflow_execution, step_output: step_output)
79
+ end
80
+
81
+ def execute(**_params)
82
+ { success: true, message: 'Output parameters saved successfully' }.to_json
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Meta workflows</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= yield :head %>
9
+
10
+ <%= stylesheet_link_tag "meta_workflows/application", media: "all" %>
11
+ </head>
12
+ <body>
13
+
14
+ <%= yield %>
15
+
16
+ </body>
17
+ </html>
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>MetaWorkflows Debug Tool</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= yield :head %>
9
+
10
+ <%= stylesheet_link_tag "meta_workflows/application", media: "all" %>
11
+
12
+ <!-- Tailwind CSS -->
13
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
+ <script src="https://cdn.tailwindcss.com"></script>
15
+ </head>
16
+ <body class="bg-gray-50 min-h-screen">
17
+ <!-- Header Navigation -->
18
+ <nav class="bg-white shadow-sm border-b border-gray-200">
19
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
20
+ <div class="flex justify-between items-center h-16">
21
+ <!-- Logo/Title -->
22
+ <div class="flex items-center">
23
+ <h1 class="text-xl font-semibold text-gray-900">
24
+ MetaWorkflows Debug Tool
25
+ </h1>
26
+ </div>
27
+
28
+ <!-- Navigation Links -->
29
+ <div class="flex space-x-8">
30
+ <%= link_to "Workflows", workflows_path,
31
+ class: "#{current_page?(workflows_path) || params[:controller].include?('workflow') ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'} px-3 py-2 text-sm font-medium" %>
32
+ <%= link_to "Executions", executions_path,
33
+ class: "#{current_page?(executions_path) || params[:controller].include?('execution') ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'} px-3 py-2 text-sm font-medium" %>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </nav>
38
+
39
+ <!-- Main Content -->
40
+ <main class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
41
+ <%= yield %>
42
+ </main>
43
+
44
+ <!-- Optional JavaScript -->
45
+ <%= yield :javascripts %>
46
+ </body>
47
+ </html>
@@ -0,0 +1,22 @@
1
+ <%= turbo_frame_tag target_frame_id(record) do %>
2
+ <div class="flex flex-col items-center justify-center gap-2"
3
+ data-controller="loading-phrases"
4
+ data-loading-phrases-phrases-value="<%= (local_assigns[:step_progress] || ["Processing your request...", "Talking to the LLM..."]).to_json %>">
5
+ <h2
6
+ class="text-center font-semibold
7
+ bg-gradient-to-r
8
+ bg-[length:300%_100%] bg-right-bottom
9
+ bg-clip-text [-webkit-text-fill-color:transparent]
10
+ animate-gradient-shift"
11
+ style="--tw-gradient-stops: #3182ce, #805ad5, #d53f8c, #3182ce;"
12
+ data-loading-phrases-target="message"
13
+ >
14
+ Processing your request...
15
+ </h2>
16
+ <div class="sm-sprite loader">
17
+ <span class="image-container">
18
+ <img class="" src="https://cdn.strongmind.com/backpack-ui/latest/assets/images/png/loaders/CB_loader.png" alt="loaders/CB_loader.png">
19
+ </span>
20
+ </div>
21
+ </div>
22
+ <% end %>
@@ -0,0 +1 @@
1
+ <div data-controller="redirect" data-redirect-url-value="<%= redirect_path %>" data-redirect-delay-value="0"></div>
@@ -0,0 +1,5 @@
1
+ <%= turbo_frame_tag target_frame_id(record) do %>
2
+ <div id="response-content-container" class="meta-workflows chat" data-controller="response-scroll">
3
+ <%= markdown(response) %>
4
+ </div>
5
+ <% end %>