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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +36 -0
- data/README.md +498 -0
- data/Rakefile +10 -0
- data/app/assets/javascripts/meta_workflows/controllers/loading_phrases_controller.js +31 -0
- data/app/assets/javascripts/meta_workflows/controllers/redirect_controller.js +15 -0
- data/app/assets/javascripts/meta_workflows/controllers/response_scroll_controller.js +67 -0
- data/app/assets/javascripts/meta_workflows_manifest.js +13 -0
- data/app/assets/stylesheets/meta_workflows/application.css +161 -0
- data/app/controllers/meta_workflows/application_controller.rb +10 -0
- data/app/controllers/meta_workflows/debug_controller.rb +101 -0
- data/app/controllers/meta_workflows/humans_controller.rb +96 -0
- data/app/controllers/meta_workflows/meta_controller.rb +21 -0
- data/app/helpers/meta_workflows/application_helper.rb +7 -0
- data/app/helpers/meta_workflows/debug_helper.rb +54 -0
- data/app/helpers/meta_workflows/execution_helper.rb +77 -0
- data/app/helpers/meta_workflows/formatting_helper.rb +30 -0
- data/app/helpers/meta_workflows/meta_workflows_helper.rb +41 -0
- data/app/helpers/meta_workflows/status_badge_helper.rb +66 -0
- data/app/jobs/meta_workflows/application_job.rb +14 -0
- data/app/jobs/meta_workflows/human_input_job.rb +35 -0
- data/app/jobs/meta_workflows/meta_job.rb +121 -0
- data/app/jobs/meta_workflows/meta_workflow_job.rb +20 -0
- data/app/jobs/meta_workflows/record_redirect_job.rb +36 -0
- data/app/mailers/meta_workflows/application_mailer.rb +8 -0
- data/app/models/meta_workflows/application_record.rb +7 -0
- data/app/models/meta_workflows/chat.rb +20 -0
- data/app/models/meta_workflows/message.rb +11 -0
- data/app/models/meta_workflows/tool_call.rb +7 -0
- data/app/models/meta_workflows/workflow.rb +32 -0
- data/app/models/meta_workflows/workflow_execution.rb +23 -0
- data/app/models/meta_workflows/workflow_step.rb +49 -0
- data/app/models/meta_workflows.rb +7 -0
- data/app/services/meta_workflows/execution_filter_service.rb +80 -0
- data/app/sidekiq/meta_workflows/tools/meta_workflow_tool.rb +86 -0
- data/app/views/layouts/meta_workflows/application.html.erb +17 -0
- data/app/views/layouts/meta_workflows/debug.html.erb +47 -0
- data/app/views/meta_workflows/_loader.html.erb +22 -0
- data/app/views/meta_workflows/_redirect.html.erb +1 -0
- data/app/views/meta_workflows/_response.html.erb +5 -0
- data/app/views/meta_workflows/_response_form.html.erb +36 -0
- data/app/views/meta_workflows/debug/executions.html.erb +187 -0
- data/app/views/meta_workflows/debug/show_execution.html.erb +283 -0
- data/app/views/meta_workflows/debug/show_workflow.html.erb +148 -0
- data/app/views/meta_workflows/debug/workflows.html.erb +110 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20250530220618_create_meta_workflows_workflows.rb +16 -0
- data/db/migrate/20250530220634_create_meta_workflows_workflow_executions.rb +18 -0
- data/db/migrate/20250530220704_create_meta_workflows_chats.rb +16 -0
- data/db/migrate/20250530220722_create_meta_workflows_messages.rb +19 -0
- data/db/migrate/20250530220737_create_meta_workflows_tool_calls.rb +18 -0
- data/db/migrate/20250530220750_create_meta_workflows_workflow_steps.rb +17 -0
- data/db/migrate/20250613213159_add_error_fields_to_workflow_steps.rb +8 -0
- data/lib/meta_workflows/asset_installer.rb +509 -0
- data/lib/meta_workflows/configuration.rb +39 -0
- data/lib/meta_workflows/engine.rb +47 -0
- data/lib/meta_workflows/export_execution_service.rb +56 -0
- data/lib/meta_workflows/version.rb +5 -0
- data/lib/meta_workflows.rb +9 -0
- data/lib/services/meta_workflows/application_service.rb +11 -0
- data/lib/services/meta_workflows/meta_workflow_service.rb +277 -0
- data/lib/services/meta_workflows/updaters/meta_service.rb +39 -0
- data/lib/tasks/meta_workflows_tasks.rake +153 -0
- metadata +219 -0
@@ -0,0 +1,161 @@
|
|
1
|
+
/* AI Generated Content */
|
2
|
+
.meta-workflows.chat {
|
3
|
+
display: flex;
|
4
|
+
flex-direction: column;
|
5
|
+
gap: 1.5rem;
|
6
|
+
color: var(--slate-800);
|
7
|
+
font-size: 1.125rem;
|
8
|
+
line-height: 1.75;
|
9
|
+
|
10
|
+
h1 {
|
11
|
+
color: var(--slate-800);
|
12
|
+
font-weight: 600;
|
13
|
+
margin-bottom: 0.5rem;
|
14
|
+
font-size: 1.875rem;
|
15
|
+
line-height: 1.25;
|
16
|
+
}
|
17
|
+
|
18
|
+
h2 {
|
19
|
+
color: var(--slate-800);
|
20
|
+
font-weight: 600;
|
21
|
+
margin-bottom: 0.5rem;
|
22
|
+
font-size: 1.5rem;
|
23
|
+
line-height: 1.25;
|
24
|
+
}
|
25
|
+
|
26
|
+
h3 {
|
27
|
+
color: var(--slate-800);
|
28
|
+
font-weight: 600;
|
29
|
+
margin-bottom: 0.5rem;
|
30
|
+
font-size: 1.25rem;
|
31
|
+
line-height: 1.25;
|
32
|
+
}
|
33
|
+
|
34
|
+
h4 {
|
35
|
+
color: var(--slate-800);
|
36
|
+
font-weight: 600;
|
37
|
+
margin-bottom: 0.5rem;
|
38
|
+
font-size: 1.125rem;
|
39
|
+
line-height: 1.25;
|
40
|
+
}
|
41
|
+
|
42
|
+
h5 {
|
43
|
+
color: var(--slate-800);
|
44
|
+
font-weight: 600;
|
45
|
+
margin-bottom: 0.5rem;
|
46
|
+
font-size: 1rem;
|
47
|
+
line-height: 1.25;
|
48
|
+
}
|
49
|
+
|
50
|
+
h6 {
|
51
|
+
color: var(--slate-800);
|
52
|
+
font-weight: 600;
|
53
|
+
margin-bottom: 0.5rem;
|
54
|
+
font-size: 0.875rem;
|
55
|
+
line-height: 1.25;
|
56
|
+
}
|
57
|
+
|
58
|
+
/* Combined list styles */
|
59
|
+
ul,
|
60
|
+
ol {
|
61
|
+
display: flex;
|
62
|
+
flex-direction: column;
|
63
|
+
gap: .5rem;
|
64
|
+
padding-left: 0;
|
65
|
+
list-style-type: none;
|
66
|
+
|
67
|
+
li {
|
68
|
+
display: flex;
|
69
|
+
padding: 1rem 1.375rem;
|
70
|
+
border: 1px solid var(--slate-300);
|
71
|
+
border-radius: 0.875rem;
|
72
|
+
background-color: white;
|
73
|
+
font-size: 1.25rem;
|
74
|
+
font-weight: 600;
|
75
|
+
line-height: 1.375;
|
76
|
+
|
77
|
+
&::before {
|
78
|
+
display: none;
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
/* Ordered list specific styles */
|
84
|
+
ol {
|
85
|
+
counter-reset: list-counter;
|
86
|
+
|
87
|
+
li::before {
|
88
|
+
content: counter(list-counter) ".";
|
89
|
+
counter-increment: list-counter;
|
90
|
+
display: inline-block;
|
91
|
+
width: 1.5em;
|
92
|
+
margin-right: 0.5em;
|
93
|
+
color: var(--slate-400);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
a {
|
98
|
+
color: #2563eb;
|
99
|
+
text-decoration: underline;
|
100
|
+
|
101
|
+
&:hover {
|
102
|
+
color: #1d4ed8;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
pre {
|
107
|
+
background-color: #f9fafb;
|
108
|
+
padding: 1rem;
|
109
|
+
border-radius: 0.5rem;
|
110
|
+
overflow-x: auto;
|
111
|
+
margin: 1rem 0;
|
112
|
+
}
|
113
|
+
|
114
|
+
code {
|
115
|
+
background-color: #f9fafb;
|
116
|
+
padding: 0.125rem 0.375rem;
|
117
|
+
border-radius: 0.25rem;
|
118
|
+
font-size: 0.875rem;
|
119
|
+
font-family: ui-monospace, monospace;
|
120
|
+
}
|
121
|
+
|
122
|
+
blockquote {
|
123
|
+
border-left: 4px solid #e5e7eb;
|
124
|
+
padding-left: 1rem;
|
125
|
+
font-style: italic;
|
126
|
+
color: #374151;
|
127
|
+
margin: 1rem 0;
|
128
|
+
}
|
129
|
+
|
130
|
+
table {
|
131
|
+
width: 100%;
|
132
|
+
border-collapse: collapse;
|
133
|
+
margin: 1rem 0;
|
134
|
+
}
|
135
|
+
|
136
|
+
th,
|
137
|
+
td {
|
138
|
+
border: 1px solid #e5e7eb;
|
139
|
+
padding: 0.5rem;
|
140
|
+
text-align: left;
|
141
|
+
}
|
142
|
+
|
143
|
+
th {
|
144
|
+
background-color: #f9fafb;
|
145
|
+
font-weight: 600;
|
146
|
+
}
|
147
|
+
|
148
|
+
img {
|
149
|
+
border-radius: 0.5rem;
|
150
|
+
margin: 1rem 0;
|
151
|
+
}
|
152
|
+
|
153
|
+
strong {
|
154
|
+
font-weight: 600;
|
155
|
+
color: var(--slate-800);
|
156
|
+
}
|
157
|
+
|
158
|
+
em {
|
159
|
+
font-style: italic;
|
160
|
+
}
|
161
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
class ApplicationController < ActionController::Base
|
5
|
+
protect_from_forgery with: :exception
|
6
|
+
|
7
|
+
# Add any MetaWorkflows-specific controller functionality here
|
8
|
+
# This ensures proper engine isolation
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
class DebugController < MetaWorkflows::ApplicationController
|
5
|
+
include MetaWorkflows::DebugHelper
|
6
|
+
|
7
|
+
layout 'meta_workflows/debug'
|
8
|
+
|
9
|
+
# Apply authentication only in production environment
|
10
|
+
before_action :authenticate_with_basic_auth, if: :production_environment?
|
11
|
+
|
12
|
+
# Skip CSRF protection for this debug interface
|
13
|
+
skip_before_action :verify_authenticity_token
|
14
|
+
|
15
|
+
def index
|
16
|
+
redirect_to workflows_path
|
17
|
+
end
|
18
|
+
|
19
|
+
def workflows
|
20
|
+
@workflows = MetaWorkflows::Workflow.includes(:workflow_executions)
|
21
|
+
.order(created_at: :desc)
|
22
|
+
|
23
|
+
# Simple search functionality - search by name only
|
24
|
+
@workflows = @workflows.where('name ILIKE ?', "%#{params[:search]}%") if params[:search].present?
|
25
|
+
|
26
|
+
# Apply pagination if available
|
27
|
+
@workflows = paginate_collection(@workflows)
|
28
|
+
end
|
29
|
+
|
30
|
+
def show_workflow
|
31
|
+
@workflow = MetaWorkflows::Workflow.find(params[:id])
|
32
|
+
@executions = @workflow.workflow_executions
|
33
|
+
.includes(workflow_steps: :chat)
|
34
|
+
.order(created_at: :desc)
|
35
|
+
|
36
|
+
@executions = paginate_collection(@executions)
|
37
|
+
end
|
38
|
+
|
39
|
+
def executions
|
40
|
+
@executions = MetaWorkflows::WorkflowExecution.includes(:workflow, workflow_steps: :chat)
|
41
|
+
.order(created_at: :desc)
|
42
|
+
|
43
|
+
@executions = MetaWorkflows::ExecutionFilterService.new(@executions, params).apply_filters
|
44
|
+
|
45
|
+
# Apply pagination
|
46
|
+
@executions = paginate_collection(@executions)
|
47
|
+
end
|
48
|
+
|
49
|
+
def show_execution
|
50
|
+
@execution = MetaWorkflows::WorkflowExecution.includes(
|
51
|
+
:workflow,
|
52
|
+
workflow_steps: { chat: { messages: :tool_calls } }
|
53
|
+
).find(params[:id])
|
54
|
+
end
|
55
|
+
|
56
|
+
def export_execution
|
57
|
+
@execution = MetaWorkflows::WorkflowExecution.includes(
|
58
|
+
:workflow,
|
59
|
+
workflow_steps: { chat: { messages: :tool_calls } }
|
60
|
+
).find(params[:id])
|
61
|
+
|
62
|
+
export_data = MetaWorkflows::ExportExecutionService.new(@execution).export_data
|
63
|
+
|
64
|
+
respond_to do |format|
|
65
|
+
format.json { render json: export_data }
|
66
|
+
format.html { redirect_to execution_path(@execution), alert: t('meta_workflows.export_only_json') }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def authenticate_with_basic_auth
|
73
|
+
return unless production_environment?
|
74
|
+
return unless authentication_required?
|
75
|
+
|
76
|
+
authenticate_or_request_with_http_basic do |username, password|
|
77
|
+
username == ENV.fetch('SIDEKIQ_USER', nil) &&
|
78
|
+
password == ENV.fetch('SIDEKIQ_PASSWORD', nil)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def production_environment?
|
85
|
+
Rails.env.production?
|
86
|
+
end
|
87
|
+
|
88
|
+
def authentication_required?
|
89
|
+
ENV['SIDEKIQ_USER'].present? && ENV['SIDEKIQ_PASSWORD'].present?
|
90
|
+
end
|
91
|
+
|
92
|
+
def paginate_collection(collection)
|
93
|
+
# Use Kaminari if available, otherwise limit to 25 records
|
94
|
+
if defined?(Kaminari) && collection.respond_to?(:page)
|
95
|
+
collection.page(params[:page]).per(25)
|
96
|
+
else
|
97
|
+
collection.limit(25).offset(((params[:page]&.to_i || 1) - 1) * 25)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
class HumansController < MetaController
|
5
|
+
include MetaWorkflows::MetaWorkflowsHelper
|
6
|
+
before_action :set_workflow_data, only: [:update]
|
7
|
+
|
8
|
+
def update
|
9
|
+
if params[:advance].present?
|
10
|
+
advance_workflow
|
11
|
+
render_loader_stream
|
12
|
+
else
|
13
|
+
process_human_input
|
14
|
+
render_response_form_stream
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def render_loader_stream
|
21
|
+
respond_to do |format|
|
22
|
+
format.turbo_stream do
|
23
|
+
render turbo_stream: [
|
24
|
+
turbo_stream.replace(
|
25
|
+
target_frame_id(@record),
|
26
|
+
partial: meta_loader,
|
27
|
+
locals: {
|
28
|
+
record: @record,
|
29
|
+
step_progress: fetch_step_progress
|
30
|
+
}
|
31
|
+
),
|
32
|
+
turbo_stream.replace(
|
33
|
+
target_frame_id(@record, form: true),
|
34
|
+
partial: meta_response_form,
|
35
|
+
locals: response_form_locals(response_enabled: false)
|
36
|
+
)
|
37
|
+
]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def render_response_form_stream
|
43
|
+
respond_to do |format|
|
44
|
+
format.turbo_stream do
|
45
|
+
render turbo_stream: turbo_stream.replace(
|
46
|
+
target_frame_id(@record, form: true),
|
47
|
+
partial: meta_response_form,
|
48
|
+
locals: response_form_locals(response_enabled: true, responding: true)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def advance_workflow
|
55
|
+
dialogue = collect_messages_from_chat
|
56
|
+
@workflow_execution.increment_step
|
57
|
+
|
58
|
+
MetaWorkflows::MetaWorkflowJob.perform_later(
|
59
|
+
record_id: @record.id,
|
60
|
+
record_type: @record.class.name,
|
61
|
+
workflow_name: nil,
|
62
|
+
user_id: current_user.id,
|
63
|
+
inputs: { dialogue: dialogue }
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def process_human_input
|
68
|
+
MetaWorkflows::HumanInputJob.perform_later(
|
69
|
+
user_id: current_user.id,
|
70
|
+
record: @record,
|
71
|
+
params: {
|
72
|
+
inputs: params[:message],
|
73
|
+
chat_id: params[:chat_id],
|
74
|
+
prompt_id: @prompt_id
|
75
|
+
}
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def collect_messages_from_chat
|
80
|
+
messages = MetaWorkflows::Chat.find(params[:chat_id]).messages
|
81
|
+
messages.map do |m|
|
82
|
+
{ role: m.role, content: m.content.strip }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def response_form_locals(response_enabled: true, responding: false)
|
87
|
+
{
|
88
|
+
record: @record,
|
89
|
+
workflow_execution_id: @workflow_execution.id,
|
90
|
+
chat_id: params[:chat_id],
|
91
|
+
response_enabled: response_enabled,
|
92
|
+
responding: responding
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
class MetaController < MetaWorkflows::ApplicationController
|
5
|
+
include MetaWorkflows::MetaWorkflowsHelper
|
6
|
+
|
7
|
+
def set_workflow_data
|
8
|
+
@workflow_execution = MetaWorkflows::WorkflowExecution.find(params[:id])
|
9
|
+
@record = @workflow_execution.record
|
10
|
+
@workflow = @workflow_execution.workflow
|
11
|
+
@prompt_id = @workflow.prompt_id(@workflow_execution.current_step)
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def fetch_step_progress
|
17
|
+
@workflow.step_progress(@workflow_execution.current_step) || ['Processing your request...',
|
18
|
+
'Talking to the LLM...']
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
module DebugHelper
|
5
|
+
include StatusBadgeHelper
|
6
|
+
include ExecutionHelper
|
7
|
+
include FormattingHelper
|
8
|
+
|
9
|
+
# Helper for truncating long text with tooltip
|
10
|
+
def truncate_with_tooltip(text, length: 50)
|
11
|
+
return '' if text.blank?
|
12
|
+
|
13
|
+
if text.length > length
|
14
|
+
content_tag :span, truncate(text, length: length), title: text, class: 'cursor-help'
|
15
|
+
else
|
16
|
+
text
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Helper for relative time with absolute time tooltip
|
21
|
+
def time_with_tooltip(time)
|
22
|
+
return 'N/A' if time.blank?
|
23
|
+
|
24
|
+
content_tag :span, "#{time_ago_in_words(time)} ago",
|
25
|
+
title: time.strftime('%Y-%m-%d %H:%M:%S %Z'),
|
26
|
+
class: 'cursor-help'
|
27
|
+
end
|
28
|
+
|
29
|
+
# Helper to safely get step data from workflow
|
30
|
+
def safe_step_data(workflow, step_number)
|
31
|
+
workflow.step_data(step_number)
|
32
|
+
rescue StandardError => e
|
33
|
+
{ 'error' => e.message }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Helper to safely get workflow name by ID
|
37
|
+
def safe_workflow_name(workflow_id)
|
38
|
+
MetaWorkflows::Workflow.find(workflow_id).name
|
39
|
+
rescue ActiveRecord::RecordNotFound
|
40
|
+
"Workflow ##{workflow_id}"
|
41
|
+
rescue StandardError
|
42
|
+
'Unknown Workflow'
|
43
|
+
end
|
44
|
+
|
45
|
+
# Helper to safely display user information
|
46
|
+
def user_display_name(user)
|
47
|
+
return 'Unknown User' if user.blank?
|
48
|
+
|
49
|
+
user.try(:name) || user.try(:email) || "User ##{user.id}"
|
50
|
+
rescue StandardError
|
51
|
+
'Unknown User'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
module ExecutionHelper
|
5
|
+
# Helper to calculate execution progress percentage
|
6
|
+
def execution_progress_percentage(execution)
|
7
|
+
return 0 if execution.workflow.total_steps.zero?
|
8
|
+
|
9
|
+
((execution.current_step.to_f / execution.workflow.total_steps) * 100).round(1)
|
10
|
+
rescue StandardError
|
11
|
+
0
|
12
|
+
end
|
13
|
+
|
14
|
+
# Helper to format execution progress display
|
15
|
+
def execution_progress_display(execution)
|
16
|
+
total_steps = execution.workflow.total_steps
|
17
|
+
current_step = execution.current_step
|
18
|
+
percentage = execution_progress_percentage(execution)
|
19
|
+
|
20
|
+
content_tag :div do
|
21
|
+
concat content_tag(:div, "#{current_step} / #{total_steps}", class: 'text-sm text-gray-900')
|
22
|
+
concat content_tag(:div, "#{percentage}% complete", class: 'text-xs text-gray-500')
|
23
|
+
end
|
24
|
+
rescue StandardError
|
25
|
+
content_tag :span, 'N/A', class: 'text-sm text-gray-500'
|
26
|
+
end
|
27
|
+
|
28
|
+
# Helper to display record info
|
29
|
+
def record_info_display(execution)
|
30
|
+
content_tag :div do
|
31
|
+
concat content_tag(:div, execution.record_type, class: 'font-medium')
|
32
|
+
concat content_tag(:div, "##{execution.record_id}", class: 'text-xs text-gray-500')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Helper to determine execution status with better logic including errors
|
37
|
+
def execution_status(execution)
|
38
|
+
return 'completed' if execution.completed?
|
39
|
+
return 'error' if execution.workflow_steps.with_errors.any?
|
40
|
+
return 'failed' if execution.updated_at < 1.hour.ago
|
41
|
+
|
42
|
+
'running'
|
43
|
+
end
|
44
|
+
|
45
|
+
# Helper to get error count for execution
|
46
|
+
def execution_error_count(execution)
|
47
|
+
execution.workflow_steps.with_errors.count
|
48
|
+
end
|
49
|
+
|
50
|
+
# Helper to display execution status with error awareness
|
51
|
+
def execution_status_display(execution)
|
52
|
+
status = execution_status(execution)
|
53
|
+
error_count = execution_error_count(execution)
|
54
|
+
|
55
|
+
case status
|
56
|
+
when 'completed'
|
57
|
+
if error_count.positive?
|
58
|
+
content_tag :div do
|
59
|
+
concat status_badge('completed')
|
60
|
+
concat content_tag(:span, " (#{error_count} error#{'s' if error_count > 1})",
|
61
|
+
class: 'text-xs text-red-600 ml-1')
|
62
|
+
end
|
63
|
+
else
|
64
|
+
status_badge('completed')
|
65
|
+
end
|
66
|
+
when 'error'
|
67
|
+
content_tag :div do
|
68
|
+
concat status_badge('error')
|
69
|
+
concat content_tag(:span, " (#{error_count} error#{'s' if error_count > 1})",
|
70
|
+
class: 'text-xs text-red-600 ml-1')
|
71
|
+
end
|
72
|
+
else
|
73
|
+
status_badge(status)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
module FormattingHelper
|
5
|
+
# Format JSON data for readable display
|
6
|
+
def pretty_json(data)
|
7
|
+
return 'No data' if data.blank?
|
8
|
+
|
9
|
+
begin
|
10
|
+
formatted = data.is_a?(String) ? JSON.parse(data) : data
|
11
|
+
JSON.pretty_generate(formatted)
|
12
|
+
rescue JSON::ParserError
|
13
|
+
data.to_s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Generate a formatted JSON display with syntax highlighting
|
18
|
+
def json_display(data, classes: 'bg-gray-100 p-4 rounded-lg text-sm overflow-x-auto')
|
19
|
+
content_tag :pre, pretty_json(data), class: classes
|
20
|
+
end
|
21
|
+
|
22
|
+
# Helper to format token usage
|
23
|
+
def format_token_usage(input_tokens, output_tokens)
|
24
|
+
tokens = [input_tokens, output_tokens].compact
|
25
|
+
return '' if tokens.empty?
|
26
|
+
|
27
|
+
"Tokens: #{tokens.join('/')}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
module MetaWorkflowsHelper
|
5
|
+
def target_frame_id(record, form: nil)
|
6
|
+
base_id = "#{record.class.name.downcase}_#{record.id}"
|
7
|
+
form ? "#{base_id}_form" : base_id
|
8
|
+
end
|
9
|
+
|
10
|
+
def turbo_stream_name(record)
|
11
|
+
[record.class.name.downcase, record.id]
|
12
|
+
end
|
13
|
+
|
14
|
+
def meta_loader
|
15
|
+
'meta_workflows/loader'
|
16
|
+
end
|
17
|
+
|
18
|
+
def meta_response_form
|
19
|
+
'meta_workflows/response_form'
|
20
|
+
end
|
21
|
+
|
22
|
+
def meta_response
|
23
|
+
'meta_workflows/response'
|
24
|
+
end
|
25
|
+
|
26
|
+
def meta_redirect
|
27
|
+
'meta_workflows/redirect'
|
28
|
+
end
|
29
|
+
|
30
|
+
def random_chat_placeholder
|
31
|
+
[
|
32
|
+
'What would you like to improve?',
|
33
|
+
'Suggest an edit or ask for changes...',
|
34
|
+
'Let me know how I can improve this.',
|
35
|
+
'Not quite right? Let me know.',
|
36
|
+
'Edit, clarify, or request a change.',
|
37
|
+
'Happy with this, or want changes?'
|
38
|
+
].sample
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
module StatusBadgeHelper
|
5
|
+
# Helper for status badges
|
6
|
+
def status_badge(status)
|
7
|
+
case status&.to_s&.downcase
|
8
|
+
when 'completed', 'success'
|
9
|
+
content_tag :span, status.capitalize,
|
10
|
+
class: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ' \
|
11
|
+
'bg-green-100 text-green-800'
|
12
|
+
when 'running', 'in_progress', 'processing'
|
13
|
+
content_tag :span, status.capitalize,
|
14
|
+
class: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ' \
|
15
|
+
'bg-blue-100 text-blue-800'
|
16
|
+
when 'failed', 'error'
|
17
|
+
content_tag :span, status.capitalize,
|
18
|
+
class: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ' \
|
19
|
+
'bg-red-100 text-red-800'
|
20
|
+
when 'pending', 'waiting'
|
21
|
+
content_tag :span, status.capitalize,
|
22
|
+
class: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ' \
|
23
|
+
'bg-yellow-100 text-yellow-800'
|
24
|
+
else
|
25
|
+
content_tag :span, status.to_s.capitalize,
|
26
|
+
class: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ' \
|
27
|
+
'bg-gray-100 text-gray-800'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Helper to display workflow step status
|
32
|
+
def workflow_step_status(workflow_step, current_step)
|
33
|
+
if workflow_step.error?
|
34
|
+
content_tag :span, 'Error',
|
35
|
+
class: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ' \
|
36
|
+
'bg-red-100 text-red-800'
|
37
|
+
elsif workflow_step.step == current_step
|
38
|
+
content_tag :span, 'Current',
|
39
|
+
class: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ' \
|
40
|
+
'bg-blue-100 text-blue-800'
|
41
|
+
elsif workflow_step.step < current_step
|
42
|
+
content_tag :span, 'Completed',
|
43
|
+
class: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ' \
|
44
|
+
'bg-green-100 text-green-800'
|
45
|
+
else
|
46
|
+
content_tag :span, 'Pending',
|
47
|
+
class: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ' \
|
48
|
+
'bg-gray-100 text-gray-800'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Helper to format message role with proper styling
|
53
|
+
def message_role_badge(role)
|
54
|
+
case role&.downcase
|
55
|
+
when 'user'
|
56
|
+
'bg-blue-50 border-blue-200'
|
57
|
+
when 'assistant'
|
58
|
+
'bg-green-50 border-green-200'
|
59
|
+
when 'system'
|
60
|
+
'bg-purple-50 border-purple-200'
|
61
|
+
else
|
62
|
+
'bg-gray-50 border-gray-200'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
class ApplicationJob < ActiveJob::Base
|
5
|
+
# Automatically retry jobs that encountered a deadlock
|
6
|
+
# retry_on ActiveRecord::Deadlocked
|
7
|
+
|
8
|
+
# Most jobs are safe to ignore if the underlying records are no longer available
|
9
|
+
# discard_on ActiveJob::DeserializationError
|
10
|
+
|
11
|
+
# Add any MetaWorkflows-specific job functionality here
|
12
|
+
# This ensures proper engine isolation
|
13
|
+
end
|
14
|
+
end
|