meta_workflows 0.8.24 → 0.9.1

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: ba1784b378c470dc10b2f863925448fa1990867e7c922e3fcdac70cf5a7225a9
4
- data.tar.gz: d79449ffdd2dd4fa309d4cda8b510f49f10a697729e0491c64097aa087af672d
3
+ metadata.gz: 50ae604291373e74675fbac7838bb9cbbdcef8beb635a07ac40f06c89f784a75
4
+ data.tar.gz: cefff5fe7d2a949941dee57e2a642e7eb54d2eaec220a489e252799fb8888f1c
5
5
  SHA512:
6
- metadata.gz: 43105946578bb6897d6c07762cf4af0ec9456c2d9ac839645c39592fb814d2b7ca44f913b60038c0d43e359d8f5acb876dbaa87f581890d9d0b636d0aec51d05
7
- data.tar.gz: 5bc47a299ef97e9a73a3425fcbde25a6c079a026803b92f706b6bbafe7a8e066cc4c29c8034f6c6af4b40990fa2908019a07ef82ac596c6c0dffc525cc70cc5e
6
+ metadata.gz: ec4974b30bfe8c3c17fbd779228e2bdf469ba86fd808fcfdfa792b4bad5419591e919d23570a42c70aedc06c68021321174a33d91faaf1b935eced1aa5504072
7
+ data.tar.gz: 940604b1494c067ead7d9dbf6da6be88b10538e59bba4551e90a575ac070ff6fe882134a170e0f1d4f2045d2a9cffb75a3116fc010d3591361bbd74666e4efb5
data/README.md CHANGED
@@ -518,13 +518,44 @@ For development contributions:
518
518
 
519
519
  For detailed information about the MetaWorkflows system:
520
520
  - [Setup Guide](https://strongmind.atlassian.net/wiki/spaces/MW/pages/3929833507) - Configuration and workflow setup
521
- - [File Structure Guide](https://strongmind.atlassian.net/wiki/spaces/MW/pages/3930521627) - Complete file organization reference
522
- - [Dummy App Guide](https://strongmind.atlassian.net/wiki/spaces/MW/pages/3941072927) - Test application documentation
523
521
 
524
522
  ## License
525
523
 
526
524
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
527
525
 
526
+ ## Styling Setup
527
+
528
+ To apply or customize the styles for the `meta_workflows` engine, follow these steps:
529
+
530
+ ### 1. Where to Add Your Styles
531
+
532
+ - All styles should be placed in this file:
533
+ `app/assets/stylesheets/meta_workflows/application.css`
534
+
535
+ ### 2. How to Copy the Stylesheet
536
+
537
+ - To bring the stylesheet into your app (host app or dummy app), run this command from your terminal:
538
+
539
+ ```bash
540
+ rails meta_workflows:install:assets
541
+ ```
542
+
543
+ ### 3. Location in Dummy App
544
+
545
+ - If you're using the dummy app (located at `spec/dummy`), the stylesheet will be copied to:
546
+ `spec/dummy/app/assets/stylesheets/meta_workflows/application.css`.
547
+
548
+ ### 4. ⚠️ Important Note
549
+
550
+ - Do not edit the copied stylesheet in the dummy app (spec/dummy/...) directly.
551
+ - Changes made there will not carry over to the host app and will be overwritten if you re-run the install:assets task.
552
+
553
+ ### 5. Avoid Using Tailwind's Custom Properties
554
+
555
+ - **Do not use Tailwind’s custom properties (CSS variables)** in your styles.
556
+ - These properties **won’t be compiled** because the layout files are accessed directly from the engine.
557
+ - As a result, the host app doesn’t recognize that it needs to compile them, and the styles will not work as expected.
558
+
528
559
  ## Tray System (Sidebar, Bottom, and Main Content Trays)
529
560
 
530
561
  MetaWorkflows provides a flexible tray system for building modern, multi-pane layouts. The tray system supports left (Beta), right (Gamma), and bottom (Delta) trays, as well as a main content area (Alpha). Each tray can be shown, hidden, or made collapsible on a per-controller or per-page basis.
@@ -1,165 +1,200 @@
1
- /* AI Generated Content */
1
+ /* Meta Workflows Styles Installed in Main App */
2
2
  .meta-workflows.chat {
3
- display: flex;
4
- flex-direction: column;
5
- gap: 1.5rem;
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 {
6
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;
7
38
  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;
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;
30
73
  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
74
  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;
75
+ line-height: 1.375;
76
+
77
+ &::before {
78
+ display: none;
103
79
  }
104
80
  }
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);
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);
156
94
  }
157
-
158
- em {
159
- font-style: italic;
95
+ }
96
+
97
+ a {
98
+ color: #2563eb;
99
+ text-decoration: underline;
100
+
101
+ &:hover {
102
+ color: #1d4ed8;
160
103
  }
161
104
  }
162
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
+ }
162
+
163
+
164
+ /* Lexi Chat Component Heights */
165
+ .lexi-chat-container {
166
+ height: 95vh;
167
+ }
168
+
169
+ .lexi-chat-container-full {
170
+ height: 100vh;
171
+ }
172
+
173
+ .lexi-avatar-large {
174
+ height: 375px;
175
+ }
176
+
177
+ .lexi-avatar-medium {
178
+ height: 300px;
179
+ }
180
+
181
+ .lexi-input-container {
182
+ height: 335px;
183
+ }
184
+
185
+ /* Lexi Chat Component Constraints */
186
+ .lexi-input-max-height {
187
+ max-height: 200px;
188
+ }
189
+
190
+ .lexi-textarea-min-height {
191
+ min-height: 35px;
192
+ }
193
+
194
+ .lexi-message-max-width {
195
+ max-width: 80%;
196
+ }
197
+
163
198
  /* Tray Animations */
164
199
  .tray-container {
165
200
  transition: all 500ms ease-in-out;
@@ -212,7 +247,58 @@
212
247
 
213
248
  /* Common content transitions */
214
249
  .tray-content {
215
- transition: opacity 500ms ease-in-out, transform 500ms ease-in-out;
250
+ transition: opacity 300ms ease-in-out, transform 500ms ease-in-out;
216
251
  opacity: 1;
217
252
  transform: translate(0);
218
- }
253
+ }
254
+
255
+ /* Lexi Tray Overrides */
256
+ .gamma-tray.lexi-tray.collapsed {
257
+ width: 0px; /* Completely hide the tray */
258
+ border: none; /* Remove border when collapsed */
259
+ overflow: visible; /* Allow avatar to show outside tray bounds */
260
+ }
261
+
262
+ .gamma-tray.lexi-tray.collapsed .tray-content {
263
+ opacity: 0;
264
+ transform: translateX(-1rem);
265
+ pointer-events: none;
266
+ }
267
+
268
+ /* Fast fade out when collapsing */
269
+ .gamma-tray.lexi-tray.collapsed .tray-content {
270
+ transition: opacity 200ms ease-in-out, transform 500ms ease-in-out;
271
+ }
272
+
273
+ /* Delayed fade in when expanding - wait for width animation to mostly complete */
274
+ .gamma-tray.lexi-tray:not(.collapsed) .tray-content {
275
+ transition: opacity 200ms ease-in-out 300ms, transform 500ms ease-in-out;
276
+ }
277
+
278
+ /* Hide standard collapse button when Lexi tray is collapsed */
279
+ .gamma-tray.lexi-tray.collapsed > button[data-action="click->tray#toggle"]:not(.lexi-floating-avatar) {
280
+ display: none;
281
+ }
282
+
283
+ /* Show floating Lexi avatar button when collapsed - with delay to avoid overlap */
284
+ .gamma-tray.lexi-tray.collapsed .lexi-floating-avatar {
285
+ display: block !important;
286
+ opacity: 0;
287
+ animation: lexi-avatar-appear 200ms ease-in-out 400ms forwards;
288
+ }
289
+
290
+ @keyframes lexi-avatar-appear {
291
+ from {
292
+ opacity: 0;
293
+ transform: scale(0.8);
294
+ }
295
+ to {
296
+ opacity: 1;
297
+ transform: scale(1);
298
+ }
299
+ }
300
+
301
+ /* Hide floating avatar when tray is expanded */
302
+ .gamma-tray.lexi-tray:not(.collapsed) .lexi-floating-avatar {
303
+ display: none !important;
304
+ }
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class BaseDebugController < MetaWorkflows::ApplicationController
5
+ include MetaWorkflows::DebugHelper
6
+
7
+ layout 'meta_workflows/application'
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
+ protected
16
+
17
+ def authenticate_with_basic_auth
18
+ return unless production_environment?
19
+ return unless authentication_required?
20
+
21
+ authenticate_or_request_with_http_basic do |username, password|
22
+ username == ENV.fetch('SIDEKIQ_USER', nil) &&
23
+ password == ENV.fetch('SIDEKIQ_PASSWORD', nil)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def production_environment?
30
+ Rails.env.production?
31
+ end
32
+
33
+ def authentication_required?
34
+ ENV['SIDEKIQ_USER'].present? && ENV['SIDEKIQ_PASSWORD'].present?
35
+ end
36
+
37
+ def paginate_collection(collection)
38
+ # Use Kaminari if available, otherwise limit to 25 records
39
+ if defined?(Kaminari) && collection.respond_to?(:page)
40
+ collection.page(params[:page]).per(25)
41
+ else
42
+ collection.limit(25).offset(((params[:page]&.to_i || 1) - 1) * 25)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,17 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
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
-
4
+ class DebugController < MetaWorkflows::BaseDebugController
15
5
  def index
16
6
  redirect_to workflows_path
17
7
  end
@@ -66,36 +56,5 @@ module MetaWorkflows
66
56
  format.html { redirect_to execution_path(@execution), alert: t('meta_workflows.export_only_json') }
67
57
  end
68
58
  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
59
  end
101
60
  end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class WorkflowImportsController < MetaWorkflows::BaseDebugController
5
+ def new
6
+ # Show the import form
7
+ end
8
+
9
+ def create
10
+ result = perform_import
11
+ return unless result
12
+
13
+ handle_import_result(result)
14
+ end
15
+
16
+ def bulk
17
+ import_service = MetaWorkflows::WorkflowImportService.new
18
+ result = import_service.import_from_directory
19
+
20
+ if result.success?
21
+ data = result.data
22
+ flash[:notice] = data[:message]
23
+ else
24
+ flash[:alert] = result.error
25
+ end
26
+
27
+ redirect_to workflows_path
28
+ end
29
+
30
+ private
31
+
32
+ def perform_import
33
+ import_service = MetaWorkflows::WorkflowImportService.new
34
+
35
+ case params[:import_type]
36
+ when 'file'
37
+ handle_file_import(import_service)
38
+ when 'text'
39
+ handle_text_import(import_service)
40
+ when 'directory'
41
+ import_service.import_from_directory
42
+ else
43
+ flash[:alert] = 'Please provide either a file, YAML text, or select directory import'
44
+ render :new
45
+ nil
46
+ end
47
+ end
48
+
49
+ def handle_file_import(import_service)
50
+ if params[:workflow_file].present?
51
+ import_service.import_from_file(params[:workflow_file])
52
+ else
53
+ flash[:alert] = 'Please provide either a file, YAML text, or select directory import'
54
+ render :new
55
+ nil
56
+ end
57
+ end
58
+
59
+ def handle_text_import(import_service)
60
+ if params[:workflow_name].present? && params[:yaml_content].present?
61
+ import_service.import_from_yaml_text(params[:workflow_name], params[:yaml_content])
62
+ else
63
+ flash[:alert] = 'Please provide either a file, YAML text, or select directory import'
64
+ render :new
65
+ nil
66
+ end
67
+ end
68
+
69
+ def handle_import_result(result)
70
+ if result.success?
71
+ import_result = result.data
72
+ flash[:notice] = success_message(import_result)
73
+ redirect_to workflows_path
74
+ else
75
+ flash[:alert] = result.error
76
+ render :new
77
+ end
78
+ end
79
+
80
+ def success_message(import_result)
81
+ action = import_result[:action] == 'created' ? 'imported' : 'updated'
82
+ "Workflow '#{import_result[:workflow_name]}' #{action} successfully!"
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetaWorkflows
4
+ class ApplicationService
5
+ def self.call(...)
6
+ new(...).call
7
+ end
8
+
9
+ protected
10
+
11
+ def success(data = nil)
12
+ ServiceResult.new(success: true, data: data)
13
+ end
14
+
15
+ def failure(error = nil)
16
+ ServiceResult.new(success: false, error: error)
17
+ end
18
+ end
19
+
20
+ # Simple result object for service responses
21
+ class ServiceResult
22
+ attr_reader :data, :error
23
+
24
+ def initialize(success:, data: nil, error: nil)
25
+ @success = success
26
+ @data = data
27
+ @error = error
28
+ end
29
+
30
+ def success?
31
+ @success
32
+ end
33
+
34
+ def failure?
35
+ !@success
36
+ end
37
+ end
38
+ end