ruby_llm-agents 1.0.0 → 1.2.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 +4 -4
- data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +9 -3
- data/app/controllers/concerns/ruby_llm/agents/sortable.rb +58 -0
- data/app/controllers/ruby_llm/agents/agents_controller.rb +59 -16
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +144 -20
- data/app/controllers/ruby_llm/agents/executions_controller.rb +13 -16
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +279 -90
- data/app/helpers/ruby_llm/agents/application_helper.rb +100 -0
- data/app/mailers/ruby_llm/agents/alert_mailer.rb +84 -0
- data/app/mailers/ruby_llm/agents/application_mailer.rb +28 -0
- data/app/models/ruby_llm/agents/execution/analytics.rb +170 -20
- data/app/models/ruby_llm/agents/execution/scopes.rb +0 -31
- data/app/models/ruby_llm/agents/execution/workflow.rb +0 -129
- data/app/models/ruby_llm/agents/execution.rb +50 -14
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +277 -0
- data/app/models/ruby_llm/agents/tenant/configurable.rb +135 -0
- data/app/models/ruby_llm/agents/tenant/trackable.rb +310 -0
- data/app/models/ruby_llm/agents/tenant.rb +146 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +12 -253
- data/app/services/ruby_llm/agents/agent_registry.rb +18 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +72 -76
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -12
- data/app/views/ruby_llm/agents/agents/_sortable_header.html.erb +56 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +5 -15
- data/app/views/ruby_llm/agents/agents/index.html.erb +271 -100
- data/app/views/ruby_llm/agents/agents/show.html.erb +1 -0
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +107 -0
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +18 -0
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +4 -1
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +66 -359
- data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +56 -0
- data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +115 -0
- data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +35 -60
- data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +17 -6
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +373 -72
- data/app/views/ruby_llm/agents/executions/_execution.html.erb +0 -1
- data/app/views/ruby_llm/agents/executions/_filters.html.erb +51 -39
- data/app/views/ruby_llm/agents/executions/_list.html.erb +53 -195
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +5 -20
- data/app/views/ruby_llm/agents/executions/index.html.erb +7 -83
- data/app/views/ruby_llm/agents/executions/show.html.erb +10 -20
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +2 -1
- data/app/views/ruby_llm/agents/shared/_doc_link.html.erb +12 -0
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +3 -15
- data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_sortable_header.html.erb +53 -0
- data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +7 -0
- data/app/views/ruby_llm/agents/shared/_status_dot.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +9 -35
- data/app/views/ruby_llm/agents/system_config/show.html.erb +4 -1
- data/app/views/ruby_llm/agents/tenants/index.html.erb +4 -1
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +7 -15
- data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +539 -0
- data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +920 -0
- data/app/views/ruby_llm/agents/workflows/index.html.erb +179 -0
- data/app/views/ruby_llm/agents/workflows/show.html.erb +164 -139
- data/config/routes.rb +1 -1
- data/lib/generators/ruby_llm_agents/agent_generator.rb +6 -36
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +5 -38
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +8 -41
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +18 -46
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/install_generator.rb +33 -56
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +480 -0
- data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +42 -22
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +2 -2
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +8 -39
- data/lib/generators/ruby_llm_agents/templates/add_tenant_to_executions_migration.rb.tt +13 -2
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +5 -8
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +40 -42
- data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +24 -26
- data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +19 -17
- data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +31 -33
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +125 -127
- data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +20 -18
- data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +19 -17
- data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +19 -17
- data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +38 -40
- data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +42 -44
- data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +48 -0
- data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +19 -21
- data/lib/generators/ruby_llm_agents/templates/create_tenant_budgets_migration.rb.tt +11 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +72 -0
- data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +19 -21
- data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +15 -17
- data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +25 -27
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +19 -21
- data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +17 -19
- data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +15 -17
- data/lib/generators/ruby_llm_agents/templates/rename_tenant_budgets_to_tenants_migration.rb.tt +34 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +87 -24
- data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +21 -27
- data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +46 -54
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +31 -39
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +22 -28
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +53 -63
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +46 -56
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +23 -31
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +22 -30
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +23 -31
- data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +38 -46
- data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +7 -7
- data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +59 -71
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +274 -23
- data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +29 -31
- data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +28 -30
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +10 -43
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +26 -0
- data/lib/ruby_llm/agents/core/configuration.rb +55 -43
- data/lib/ruby_llm/agents/core/llm_tenant.rb +60 -60
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +26 -0
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +4 -2
- data/lib/ruby_llm/agents/pipeline.rb +69 -0
- data/lib/ruby_llm/agents/workflow/approval.rb +205 -0
- data/lib/ruby_llm/agents/workflow/approval_store.rb +179 -0
- data/lib/ruby_llm/agents/workflow/dsl/executor.rb +467 -0
- data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +244 -0
- data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +289 -0
- data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +107 -0
- data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +150 -0
- data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +187 -0
- data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +352 -0
- data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +415 -0
- data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +257 -0
- data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +317 -0
- data/lib/ruby_llm/agents/workflow/dsl.rb +576 -0
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +2 -7
- data/lib/ruby_llm/agents/workflow/notifiers/base.rb +117 -0
- data/lib/ruby_llm/agents/workflow/notifiers/email.rb +117 -0
- data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +180 -0
- data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +121 -0
- data/lib/ruby_llm/agents/workflow/notifiers.rb +70 -0
- data/lib/ruby_llm/agents/workflow/orchestrator.rb +190 -23
- data/lib/ruby_llm/agents/workflow/result.rb +202 -0
- data/lib/ruby_llm/agents/workflow/throttle_manager.rb +206 -0
- data/lib/ruby_llm/agents/workflow/wait_result.rb +213 -0
- metadata +43 -6
- data/app/views/ruby_llm/agents/dashboard/_execution_item.html.erb +0 -66
- data/lib/ruby_llm/agents/workflow/parallel.rb +0 -299
- data/lib/ruby_llm/agents/workflow/pipeline.rb +0 -306
- data/lib/ruby_llm/agents/workflow/router.rb +0 -429
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
<%
|
|
2
|
+
# Vertical workflow diagram visualization
|
|
3
|
+
# Redesigned for clarity: vertical flow, spelled-out indicators, visual hierarchy
|
|
4
|
+
#
|
|
5
|
+
# Required locals:
|
|
6
|
+
# steps: Array of step hashes with full DSL metadata
|
|
7
|
+
#
|
|
8
|
+
# Optional locals:
|
|
9
|
+
# parallel_groups: Array of parallel group hashes
|
|
10
|
+
# input_schema_fields: Hash of input schema field definitions
|
|
11
|
+
# lifecycle_hooks: Hash of lifecycle hook counts
|
|
12
|
+
|
|
13
|
+
steps = local_assigns[:steps] || []
|
|
14
|
+
parallel_groups = local_assigns[:parallel_groups] || []
|
|
15
|
+
input_schema_fields = local_assigns[:input_schema_fields] || {}
|
|
16
|
+
lifecycle_hooks = local_assigns[:lifecycle_hooks] || {}
|
|
17
|
+
|
|
18
|
+
# Group steps into sequential items, parallel groups, and wait steps
|
|
19
|
+
def group_steps(steps, parallel_groups)
|
|
20
|
+
current_parallel_group = nil
|
|
21
|
+
grouped_items = []
|
|
22
|
+
|
|
23
|
+
steps.each do |step|
|
|
24
|
+
# Check if this is a wait step
|
|
25
|
+
if step[:type] == :wait
|
|
26
|
+
current_parallel_group = nil
|
|
27
|
+
grouped_items << { type: :wait, step: step }
|
|
28
|
+
elsif step[:parallel_group].present?
|
|
29
|
+
pg = parallel_groups.find { |g| g[:name].to_s == step[:parallel_group].to_s }
|
|
30
|
+
if current_parallel_group != step[:parallel_group]
|
|
31
|
+
current_parallel_group = step[:parallel_group]
|
|
32
|
+
grouped_items << { type: :parallel_group, group: pg, steps: [step] }
|
|
33
|
+
else
|
|
34
|
+
grouped_items.last[:steps] << step
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
current_parallel_group = nil
|
|
38
|
+
grouped_items << { type: :step, step: step }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
grouped_items
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
grouped_items = group_steps(steps, parallel_groups)
|
|
46
|
+
%>
|
|
47
|
+
|
|
48
|
+
<% if steps.any? %>
|
|
49
|
+
<!-- Lifecycle Hooks Banner -->
|
|
50
|
+
<% if lifecycle_hooks[:before_workflow].to_i > 0 || lifecycle_hooks[:after_workflow].to_i > 0 || lifecycle_hooks[:on_step_error].to_i > 0 %>
|
|
51
|
+
<div class="flex items-center justify-center gap-4 mb-6 pb-4 border-b border-gray-200 dark:border-gray-700">
|
|
52
|
+
<% if lifecycle_hooks[:before_workflow].to_i > 0 %>
|
|
53
|
+
<div class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-emerald-50 dark:bg-emerald-900/30 border border-emerald-200 dark:border-emerald-800">
|
|
54
|
+
<svg class="w-3.5 h-3.5 text-emerald-600 dark:text-emerald-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"/></svg>
|
|
55
|
+
<span class="text-xs font-medium text-emerald-700 dark:text-emerald-300">before_workflow hook</span>
|
|
56
|
+
</div>
|
|
57
|
+
<% end %>
|
|
58
|
+
<% if lifecycle_hooks[:after_workflow].to_i > 0 %>
|
|
59
|
+
<div class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-sky-50 dark:bg-sky-900/30 border border-sky-200 dark:border-sky-800">
|
|
60
|
+
<svg class="w-3.5 h-3.5 text-sky-600 dark:text-sky-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V8a1 1 0 00-1-1H8z" clip-rule="evenodd"/></svg>
|
|
61
|
+
<span class="text-xs font-medium text-sky-700 dark:text-sky-300">after_workflow hook</span>
|
|
62
|
+
</div>
|
|
63
|
+
<% end %>
|
|
64
|
+
<% if lifecycle_hooks[:on_step_error].to_i > 0 %>
|
|
65
|
+
<div class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-rose-50 dark:bg-rose-900/30 border border-rose-200 dark:border-rose-800">
|
|
66
|
+
<svg class="w-3.5 h-3.5 text-rose-600 dark:text-rose-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
|
|
67
|
+
<span class="text-xs font-medium text-rose-700 dark:text-rose-300">on_step_error hook</span>
|
|
68
|
+
</div>
|
|
69
|
+
<% end %>
|
|
70
|
+
</div>
|
|
71
|
+
<% end %>
|
|
72
|
+
|
|
73
|
+
<!-- Vertical Flow Diagram -->
|
|
74
|
+
<div class="flex flex-col items-center py-6">
|
|
75
|
+
<!-- Start Node -->
|
|
76
|
+
<div class="flex flex-col items-center mb-2">
|
|
77
|
+
<div class="relative">
|
|
78
|
+
<div class="absolute inset-0 rounded-full bg-green-400/20 dark:bg-green-500/20 animate-pulse"></div>
|
|
79
|
+
<div class="relative w-16 h-10 flex items-center justify-center rounded-full bg-gradient-to-br from-green-100 to-green-200 dark:from-green-900/50 dark:to-green-800/50 border-2 border-green-500 dark:border-green-600 shadow-md">
|
|
80
|
+
<span class="text-sm font-bold text-green-700 dark:text-green-300 uppercase tracking-wide">Start</span>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<!-- Connecting Line from Start -->
|
|
86
|
+
<div class="w-0.5 h-6 bg-gradient-to-b from-green-400 to-gray-300 dark:from-green-600 dark:to-gray-600"></div>
|
|
87
|
+
|
|
88
|
+
<!-- Steps Flow -->
|
|
89
|
+
<% grouped_items.each_with_index do |item, index| %>
|
|
90
|
+
<% if item[:type] == :wait %>
|
|
91
|
+
<!-- Wait Step -->
|
|
92
|
+
<% step = item[:step] %>
|
|
93
|
+
<%
|
|
94
|
+
wait_type = step[:wait_type]
|
|
95
|
+
case wait_type
|
|
96
|
+
when :delay
|
|
97
|
+
border_class = "border-teal-400 dark:border-teal-500"
|
|
98
|
+
bg_class = "bg-teal-50 dark:bg-teal-900/30"
|
|
99
|
+
text_class = "text-teal-800 dark:text-teal-200"
|
|
100
|
+
type_label = "Delay"
|
|
101
|
+
type_icon = '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>'
|
|
102
|
+
when :until
|
|
103
|
+
border_class = "border-cyan-400 dark:border-cyan-500"
|
|
104
|
+
bg_class = "bg-cyan-50 dark:bg-cyan-900/30"
|
|
105
|
+
text_class = "text-cyan-800 dark:text-cyan-200"
|
|
106
|
+
type_label = "Poll Until"
|
|
107
|
+
type_icon = '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>'
|
|
108
|
+
when :schedule
|
|
109
|
+
border_class = "border-indigo-400 dark:border-indigo-500"
|
|
110
|
+
bg_class = "bg-indigo-50 dark:bg-indigo-900/30"
|
|
111
|
+
text_class = "text-indigo-800 dark:text-indigo-200"
|
|
112
|
+
type_label = "Scheduled"
|
|
113
|
+
type_icon = '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>'
|
|
114
|
+
when :approval
|
|
115
|
+
border_class = "border-orange-400 dark:border-orange-500"
|
|
116
|
+
bg_class = "bg-orange-50 dark:bg-orange-900/30"
|
|
117
|
+
text_class = "text-orange-800 dark:text-orange-200"
|
|
118
|
+
type_label = "Approval"
|
|
119
|
+
type_icon = '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>'
|
|
120
|
+
else
|
|
121
|
+
border_class = "border-gray-400 dark:border-gray-500"
|
|
122
|
+
bg_class = "bg-gray-50 dark:bg-gray-900/30"
|
|
123
|
+
text_class = "text-gray-800 dark:text-gray-200"
|
|
124
|
+
type_label = "Wait"
|
|
125
|
+
type_icon = '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>'
|
|
126
|
+
end
|
|
127
|
+
%>
|
|
128
|
+
|
|
129
|
+
<!-- Wait Step Card -->
|
|
130
|
+
<div class="w-full max-w-lg group/step">
|
|
131
|
+
<div class="relative rounded-xl border-2 border-dashed <%= border_class %> <%= bg_class %> shadow-sm hover:shadow-lg hover:scale-[1.01] transition-all duration-200 p-4">
|
|
132
|
+
<!-- Wait Type Badge -->
|
|
133
|
+
<div class="flex items-center justify-between mb-3">
|
|
134
|
+
<div class="flex items-center gap-2">
|
|
135
|
+
<span class="w-7 h-7 flex items-center justify-center rounded-full <%= bg_class %> border <%= border_class %>">
|
|
136
|
+
<%= raw type_icon %>
|
|
137
|
+
</span>
|
|
138
|
+
<span class="font-bold <%= text_class %> text-lg uppercase tracking-tight">
|
|
139
|
+
<%= step[:ui_label] || step[:name].to_s.titleize %>
|
|
140
|
+
</span>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<span class="inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-medium <%= bg_class %> border <%= border_class %> <%= text_class %>">
|
|
144
|
+
<%= raw type_icon %>
|
|
145
|
+
<%= type_label %>
|
|
146
|
+
</span>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<!-- Wait Details -->
|
|
150
|
+
<div class="space-y-2 text-sm">
|
|
151
|
+
<% if step[:duration] %>
|
|
152
|
+
<div class="flex items-center gap-2 <%= text_class %>">
|
|
153
|
+
<svg class="w-4 h-4 opacity-70" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
154
|
+
<span>Duration: <strong><%= step[:duration].is_a?(Numeric) ? "#{step[:duration]}s" : step[:duration] %></strong></span>
|
|
155
|
+
</div>
|
|
156
|
+
<% end %>
|
|
157
|
+
|
|
158
|
+
<% if step[:poll_interval] %>
|
|
159
|
+
<div class="flex items-center gap-2 <%= text_class %>">
|
|
160
|
+
<svg class="w-4 h-4 opacity-70" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
|
161
|
+
<span>Poll every: <strong><%= step[:poll_interval].is_a?(Numeric) ? "#{step[:poll_interval]}s" : step[:poll_interval] %></strong></span>
|
|
162
|
+
</div>
|
|
163
|
+
<% end %>
|
|
164
|
+
|
|
165
|
+
<% if step[:timeout] %>
|
|
166
|
+
<div class="flex items-center gap-2 <%= text_class %>">
|
|
167
|
+
<svg class="w-4 h-4 opacity-70" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
|
|
168
|
+
<span>Timeout: <strong><%= step[:timeout].is_a?(Numeric) ? "#{step[:timeout]}s" : step[:timeout] %></strong></span>
|
|
169
|
+
</div>
|
|
170
|
+
<% end %>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<!-- Status Indicators -->
|
|
174
|
+
<div class="flex flex-wrap gap-2 text-xs mt-3">
|
|
175
|
+
<% if step[:on_timeout] %>
|
|
176
|
+
<%
|
|
177
|
+
timeout_colors = case step[:on_timeout].to_s
|
|
178
|
+
when "continue" then "bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300 border-green-200 dark:border-green-800"
|
|
179
|
+
when "fail" then "bg-red-100 dark:bg-red-900/50 text-red-700 dark:text-red-300 border-red-200 dark:border-red-800"
|
|
180
|
+
when "skip_next" then "bg-yellow-100 dark:bg-yellow-900/50 text-yellow-700 dark:text-yellow-300 border-yellow-200 dark:border-yellow-800"
|
|
181
|
+
when "escalate" then "bg-purple-100 dark:bg-purple-900/50 text-purple-700 dark:text-purple-300 border-purple-200 dark:border-purple-800"
|
|
182
|
+
else "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-600"
|
|
183
|
+
end
|
|
184
|
+
%>
|
|
185
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-md <%= timeout_colors %> border">
|
|
186
|
+
On timeout: <%= step[:on_timeout] %>
|
|
187
|
+
</span>
|
|
188
|
+
<% end %>
|
|
189
|
+
|
|
190
|
+
<% if step[:notify].present? %>
|
|
191
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-md bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-800">
|
|
192
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
|
|
193
|
+
Notify: <%= Array(step[:notify]).join(", ") %>
|
|
194
|
+
</span>
|
|
195
|
+
<% end %>
|
|
196
|
+
|
|
197
|
+
<% if step[:approvers].present? %>
|
|
198
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-md bg-orange-100 dark:bg-orange-900/50 text-orange-700 dark:text-orange-300 border border-orange-200 dark:border-orange-800">
|
|
199
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
|
200
|
+
<%= Array(step[:approvers]).size %> approver(s)
|
|
201
|
+
</span>
|
|
202
|
+
<% end %>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<% elsif item[:type] == :step %>
|
|
208
|
+
<% step = item[:step] %>
|
|
209
|
+
<%
|
|
210
|
+
# Determine step type and styling
|
|
211
|
+
if step[:workflow]
|
|
212
|
+
step_type = :workflow
|
|
213
|
+
border_class = "border-emerald-400 dark:border-emerald-500"
|
|
214
|
+
bg_class = "bg-emerald-50 dark:bg-emerald-900/30"
|
|
215
|
+
text_class = "text-emerald-800 dark:text-emerald-200"
|
|
216
|
+
type_label = "Sub-workflow"
|
|
217
|
+
type_icon = '<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path d="M10 3.5a1.5 1.5 0 013 0V4a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-.5a1.5 1.5 0 000 3h.5a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-.5a1.5 1.5 0 00-3 0v.5a1 1 0 01-1 1H6a1 1 0 01-1-1v-3a1 1 0 00-1-1h-.5a1.5 1.5 0 010-3H4a1 1 0 001-1V6a1 1 0 011-1h3a1 1 0 001-1v-.5z"/></svg>'
|
|
218
|
+
elsif step[:iteration]
|
|
219
|
+
step_type = :iteration
|
|
220
|
+
border_class = "border-blue-400 dark:border-blue-500"
|
|
221
|
+
bg_class = "bg-blue-50 dark:bg-blue-900/30"
|
|
222
|
+
text_class = "text-blue-800 dark:text-blue-200"
|
|
223
|
+
type_label = "Iteration"
|
|
224
|
+
type_icon = '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>'
|
|
225
|
+
elsif step[:routing]
|
|
226
|
+
step_type = :routing
|
|
227
|
+
border_class = "border-amber-400 dark:border-amber-500"
|
|
228
|
+
bg_class = "bg-amber-50 dark:bg-amber-900/30"
|
|
229
|
+
text_class = "text-amber-800 dark:text-amber-200"
|
|
230
|
+
type_label = "Routing"
|
|
231
|
+
type_icon = '<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M7.707 3.293a1 1 0 010 1.414L5.414 7H11a7 7 0 017 7v2a1 1 0 11-2 0v-2a5 5 0 00-5-5H5.414l2.293 2.293a1 1 0 11-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>'
|
|
232
|
+
elsif step[:custom_block]
|
|
233
|
+
step_type = :block
|
|
234
|
+
border_class = "border-violet-400 dark:border-violet-500"
|
|
235
|
+
bg_class = "bg-violet-50 dark:bg-violet-900/30"
|
|
236
|
+
text_class = "text-violet-800 dark:text-violet-200"
|
|
237
|
+
type_label = "Block"
|
|
238
|
+
type_icon = '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/></svg>'
|
|
239
|
+
else
|
|
240
|
+
step_type = :sequential
|
|
241
|
+
border_class = "border-slate-300 dark:border-slate-600"
|
|
242
|
+
bg_class = "bg-white dark:bg-gray-800"
|
|
243
|
+
text_class = "text-slate-800 dark:text-slate-200"
|
|
244
|
+
type_label = nil
|
|
245
|
+
type_icon = nil
|
|
246
|
+
end
|
|
247
|
+
%>
|
|
248
|
+
|
|
249
|
+
<!-- Step Card -->
|
|
250
|
+
<div class="w-full max-w-lg group/step">
|
|
251
|
+
<div class="relative rounded-xl border-2 <%= border_class %> <%= bg_class %> <%= step[:optional] ? 'border-dashed' : '' %> shadow-sm hover:shadow-lg hover:scale-[1.01] transition-all duration-200 p-4">
|
|
252
|
+
<!-- Step Number & Type Badge -->
|
|
253
|
+
<div class="flex items-center justify-between mb-3">
|
|
254
|
+
<div class="flex items-center gap-2">
|
|
255
|
+
<span class="w-7 h-7 flex items-center justify-center rounded-full bg-gray-100 dark:bg-gray-700 text-sm font-bold text-gray-600 dark:text-gray-300">
|
|
256
|
+
<%= index + 1 %>
|
|
257
|
+
</span>
|
|
258
|
+
<span class="font-bold <%= text_class %> text-lg uppercase tracking-tight">
|
|
259
|
+
<%= step[:name].to_s.titleize %>
|
|
260
|
+
</span>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<% if type_label %>
|
|
264
|
+
<span class="inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-medium <%= bg_class %> border <%= border_class %> <%= text_class %>">
|
|
265
|
+
<%= raw type_icon %>
|
|
266
|
+
<%= type_label %>
|
|
267
|
+
</span>
|
|
268
|
+
<% end %>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<!-- Agent/Workflow Name -->
|
|
272
|
+
<% if step[:agent].present? %>
|
|
273
|
+
<div class="flex items-center gap-2 mb-3 text-sm text-gray-600 dark:text-gray-400">
|
|
274
|
+
<% if step[:workflow] %>
|
|
275
|
+
<svg class="w-4 h-4 text-emerald-500" fill="currentColor" viewBox="0 0 20 20"><path d="M10 3.5a1.5 1.5 0 013 0V4a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-.5a1.5 1.5 0 000 3h.5a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-.5a1.5 1.5 0 00-3 0v.5a1 1 0 01-1 1H6a1 1 0 01-1-1v-3a1 1 0 00-1-1h-.5a1.5 1.5 0 010-3H4a1 1 0 001-1V6a1 1 0 011-1h3a1 1 0 001-1v-.5z"/></svg>
|
|
276
|
+
<% else %>
|
|
277
|
+
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
|
|
278
|
+
<% end %>
|
|
279
|
+
<code class="font-mono bg-gray-100 dark:bg-gray-700 px-2 py-0.5 rounded"><%= step[:agent] %></code>
|
|
280
|
+
</div>
|
|
281
|
+
<% elsif step[:custom_block] %>
|
|
282
|
+
<div class="flex items-center gap-2 mb-3 text-sm text-violet-600 dark:text-violet-400">
|
|
283
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/></svg>
|
|
284
|
+
<span class="italic">Custom Ruby block</span>
|
|
285
|
+
</div>
|
|
286
|
+
<% end %>
|
|
287
|
+
|
|
288
|
+
<!-- Input/Output Data Flow -->
|
|
289
|
+
<% if step[:pick_fields] || step[:has_input_mapper] %>
|
|
290
|
+
<div class="mb-3 p-2 rounded-lg bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 text-xs">
|
|
291
|
+
<div class="flex items-center gap-2">
|
|
292
|
+
<svg class="w-4 h-4 text-green-500 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/></svg>
|
|
293
|
+
<span class="font-medium text-gray-600 dark:text-gray-400">Input:</span>
|
|
294
|
+
<% if step[:pick_fields] %>
|
|
295
|
+
<code class="font-mono text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 px-1.5 py-0.5 rounded">
|
|
296
|
+
<%= step[:pick_fields].join(', ') %>
|
|
297
|
+
</code>
|
|
298
|
+
<% if step[:pick_from] %>
|
|
299
|
+
<span class="text-gray-400 dark:text-gray-500">from</span>
|
|
300
|
+
<span class="font-medium text-indigo-600 dark:text-indigo-400">:<%= step[:pick_from] %></span>
|
|
301
|
+
<% end %>
|
|
302
|
+
<% else %>
|
|
303
|
+
<span class="text-gray-500 dark:text-gray-400 italic">custom mapping (lambda)</span>
|
|
304
|
+
<% end %>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
<% end %>
|
|
308
|
+
|
|
309
|
+
<!-- Description -->
|
|
310
|
+
<% if step[:description].present? %>
|
|
311
|
+
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3 leading-relaxed">
|
|
312
|
+
<%= step[:description] %>
|
|
313
|
+
</p>
|
|
314
|
+
<% end %>
|
|
315
|
+
|
|
316
|
+
<!-- Sub-workflow Preview (expandable) -->
|
|
317
|
+
<% if step[:workflow] && step[:sub_workflow] %>
|
|
318
|
+
<div class="mb-3" x-data="{ expanded: false }">
|
|
319
|
+
<div class="p-3 rounded-lg bg-emerald-100/50 dark:bg-emerald-900/20 border border-emerald-200 dark:border-emerald-800 border-dashed">
|
|
320
|
+
<!-- Header with expand button -->
|
|
321
|
+
<div class="flex items-center justify-between mb-2">
|
|
322
|
+
<div class="flex items-center gap-1.5 text-sm font-medium text-emerald-700 dark:text-emerald-300">
|
|
323
|
+
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path d="M10 3.5a1.5 1.5 0 013 0V4a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-.5a1.5 1.5 0 000 3h.5a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-.5a1.5 1.5 0 00-3 0v.5a1 1 0 01-1 1H6a1 1 0 01-1-1v-3a1 1 0 00-1-1h-.5a1.5 1.5 0 010-3H4a1 1 0 001-1V6a1 1 0 011-1h3a1 1 0 001-1v-.5z"/></svg>
|
|
324
|
+
Nested Workflow (<%= step[:sub_workflow][:steps_count] %> steps)
|
|
325
|
+
</div>
|
|
326
|
+
<button @click="expanded = !expanded" class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded bg-emerald-200 dark:bg-emerald-800 text-emerald-700 dark:text-emerald-300 hover:bg-emerald-300 dark:hover:bg-emerald-700 transition-colors cursor-pointer">
|
|
327
|
+
<span x-text="expanded ? 'Collapse' : 'Expand'">Expand</span>
|
|
328
|
+
<svg class="w-3 h-3 transition-transform" :class="expanded ? 'rotate-180' : ''" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
|
329
|
+
</button>
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
<!-- Budget inheritance indicators -->
|
|
333
|
+
<div class="flex flex-wrap gap-2 text-xs mb-2">
|
|
334
|
+
<% if step[:sub_workflow][:timeout] %>
|
|
335
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-emerald-200/70 dark:bg-emerald-800/50 text-emerald-700 dark:text-emerald-300">
|
|
336
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
337
|
+
Inherits remaining timeout
|
|
338
|
+
</span>
|
|
339
|
+
<% end %>
|
|
340
|
+
<% if step[:sub_workflow][:max_cost] %>
|
|
341
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-emerald-200/70 dark:bg-emerald-800/50 text-emerald-700 dark:text-emerald-300">
|
|
342
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1"/></svg>
|
|
343
|
+
Inherits remaining budget
|
|
344
|
+
</span>
|
|
345
|
+
<% end %>
|
|
346
|
+
<% if step[:sub_workflow][:max_recursion_depth] %>
|
|
347
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-emerald-200/70 dark:bg-emerald-800/50 text-emerald-700 dark:text-emerald-300">
|
|
348
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
|
349
|
+
Max depth: <%= step[:sub_workflow][:max_recursion_depth] %>
|
|
350
|
+
</span>
|
|
351
|
+
<% end %>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
<!-- Collapsed: Mini horizontal flow preview -->
|
|
355
|
+
<div x-show="!expanded" class="flex items-center gap-1 overflow-x-auto py-2">
|
|
356
|
+
<% step[:sub_workflow][:steps_preview]&.each_with_index do |sub_step, sub_idx| %>
|
|
357
|
+
<%
|
|
358
|
+
sub_border = if sub_step[:routing]
|
|
359
|
+
"border-amber-300 dark:border-amber-600"
|
|
360
|
+
elsif sub_step[:workflow]
|
|
361
|
+
"border-emerald-300 dark:border-emerald-600"
|
|
362
|
+
elsif sub_step[:iteration]
|
|
363
|
+
"border-blue-300 dark:border-blue-600"
|
|
364
|
+
else
|
|
365
|
+
"border-gray-300 dark:border-gray-600"
|
|
366
|
+
end
|
|
367
|
+
%>
|
|
368
|
+
<% unless sub_idx == 0 %>
|
|
369
|
+
<svg class="w-3 h-3 text-gray-400 dark:text-gray-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
370
|
+
<% end %>
|
|
371
|
+
<div class="flex-shrink-0 px-2 py-1 text-[10px] rounded border <%= sub_border %> bg-white dark:bg-gray-800">
|
|
372
|
+
<%= sub_step[:name].to_s.titleize.truncate(12) %>
|
|
373
|
+
</div>
|
|
374
|
+
<% end %>
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<!-- Expanded: Full step list -->
|
|
378
|
+
<div x-show="expanded" x-collapse class="space-y-1.5 pt-2">
|
|
379
|
+
<% step[:sub_workflow][:steps_preview]&.each_with_index do |sub_step, sub_idx| %>
|
|
380
|
+
<%
|
|
381
|
+
sub_bg = if sub_step[:routing]
|
|
382
|
+
"bg-amber-50 dark:bg-amber-900/30 border-amber-200 dark:border-amber-700"
|
|
383
|
+
elsif sub_step[:workflow]
|
|
384
|
+
"bg-emerald-50 dark:bg-emerald-900/30 border-emerald-200 dark:border-emerald-700"
|
|
385
|
+
elsif sub_step[:iteration]
|
|
386
|
+
"bg-blue-50 dark:bg-blue-900/30 border-blue-200 dark:border-blue-700"
|
|
387
|
+
elsif sub_step[:parallel]
|
|
388
|
+
"bg-purple-50 dark:bg-purple-900/30 border-purple-200 dark:border-purple-700"
|
|
389
|
+
else
|
|
390
|
+
"bg-gray-50 dark:bg-gray-800 border-gray-200 dark:border-gray-700"
|
|
391
|
+
end
|
|
392
|
+
%>
|
|
393
|
+
<div class="flex items-center gap-2 p-2 rounded-lg border <%= sub_bg %>">
|
|
394
|
+
<span class="w-5 h-5 flex items-center justify-center rounded-full bg-gray-200 dark:bg-gray-700 text-[10px] font-bold text-gray-600 dark:text-gray-400">
|
|
395
|
+
<%= sub_idx + 1 %>
|
|
396
|
+
</span>
|
|
397
|
+
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
398
|
+
<%= sub_step[:name].to_s.titleize %>
|
|
399
|
+
</span>
|
|
400
|
+
<% if sub_step[:agent] %>
|
|
401
|
+
<span class="inline-flex items-center gap-1 text-[10px] text-gray-500 dark:text-gray-400">
|
|
402
|
+
<svg class="w-2.5 h-2.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
403
|
+
<%= sub_step[:agent] %>
|
|
404
|
+
</span>
|
|
405
|
+
<% end %>
|
|
406
|
+
<% if sub_step[:routing] %>
|
|
407
|
+
<span class="text-[10px] px-1.5 py-0.5 rounded bg-amber-100 dark:bg-amber-800 text-amber-600 dark:text-amber-300">routing</span>
|
|
408
|
+
<% end %>
|
|
409
|
+
<% if sub_step[:iteration] %>
|
|
410
|
+
<span class="text-[10px] px-1.5 py-0.5 rounded bg-blue-100 dark:bg-blue-800 text-blue-600 dark:text-blue-300">iteration</span>
|
|
411
|
+
<% end %>
|
|
412
|
+
<% if sub_step[:workflow] %>
|
|
413
|
+
<span class="text-[10px] px-1.5 py-0.5 rounded bg-emerald-100 dark:bg-emerald-800 text-emerald-600 dark:text-emerald-300">workflow</span>
|
|
414
|
+
<% end %>
|
|
415
|
+
<% if sub_step[:parallel] %>
|
|
416
|
+
<span class="text-[10px] px-1.5 py-0.5 rounded bg-purple-100 dark:bg-purple-800 text-purple-600 dark:text-purple-300">parallel</span>
|
|
417
|
+
<% end %>
|
|
418
|
+
</div>
|
|
419
|
+
<% end %>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
<% end %>
|
|
424
|
+
|
|
425
|
+
<!-- Iteration Details -->
|
|
426
|
+
<% if step[:iteration] %>
|
|
427
|
+
<div class="mb-3 p-3 rounded-lg bg-blue-100/50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800">
|
|
428
|
+
<div class="flex items-center gap-2 text-sm font-medium text-blue-700 dark:text-blue-300 mb-2">
|
|
429
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
|
430
|
+
FOR EACH item in collection
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
<!-- Fan-out visualization -->
|
|
434
|
+
<div class="flex items-center justify-center py-3 mb-3 border-b border-blue-200 dark:border-blue-700">
|
|
435
|
+
<div class="flex flex-col items-center">
|
|
436
|
+
<div class="text-xs text-blue-500 dark:text-blue-400 mb-1">Collection</div>
|
|
437
|
+
<div class="w-12 h-6 rounded bg-blue-200 dark:bg-blue-800 flex items-center justify-center text-xs text-blue-700 dark:text-blue-300">[...]</div>
|
|
438
|
+
</div>
|
|
439
|
+
<svg class="w-4 h-4 mx-3 text-blue-400 dark:text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
440
|
+
<div class="flex flex-col items-center">
|
|
441
|
+
<!-- Show concurrent or sequential processing -->
|
|
442
|
+
<% if step[:iteration_concurrency] && step[:iteration_concurrency] > 1 %>
|
|
443
|
+
<div class="text-xs text-blue-500 dark:text-blue-400 mb-1">Process <%= step[:iteration_concurrency] %> at a time</div>
|
|
444
|
+
<div class="flex gap-1">
|
|
445
|
+
<% step[:iteration_concurrency].times do |i| %>
|
|
446
|
+
<div class="w-6 h-6 rounded bg-blue-300 dark:bg-blue-700 flex items-center justify-center text-[10px] text-blue-800 dark:text-blue-200"><%= i + 1 %></div>
|
|
447
|
+
<% end %>
|
|
448
|
+
<div class="w-6 h-6 rounded border-2 border-dashed border-blue-300 dark:border-blue-600 flex items-center justify-center text-[10px] text-blue-400 dark:text-blue-500">...</div>
|
|
449
|
+
</div>
|
|
450
|
+
<% else %>
|
|
451
|
+
<div class="text-xs text-blue-500 dark:text-blue-400 mb-1">Process one at a time</div>
|
|
452
|
+
<div class="flex items-center gap-1">
|
|
453
|
+
<div class="w-6 h-6 rounded bg-blue-300 dark:bg-blue-700 flex items-center justify-center text-[10px] text-blue-800 dark:text-blue-200">1</div>
|
|
454
|
+
<svg class="w-3 h-3 text-blue-400 dark:text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
455
|
+
<div class="w-6 h-6 rounded bg-blue-200 dark:bg-blue-800 flex items-center justify-center text-[10px] text-blue-600 dark:text-blue-400">2</div>
|
|
456
|
+
<svg class="w-3 h-3 text-blue-400 dark:text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
457
|
+
<div class="w-6 h-6 rounded border-2 border-dashed border-blue-300 dark:border-blue-600 flex items-center justify-center text-[10px] text-blue-400 dark:text-blue-500">N</div>
|
|
458
|
+
</div>
|
|
459
|
+
<% end %>
|
|
460
|
+
</div>
|
|
461
|
+
<svg class="w-4 h-4 mx-3 text-blue-400 dark:text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
462
|
+
<div class="flex flex-col items-center">
|
|
463
|
+
<div class="text-xs text-blue-500 dark:text-blue-400 mb-1">Results</div>
|
|
464
|
+
<div class="w-12 h-6 rounded bg-blue-200 dark:bg-blue-800 flex items-center justify-center text-xs text-blue-700 dark:text-blue-300">[...]</div>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
<!-- Iteration settings -->
|
|
469
|
+
<div class="flex flex-wrap gap-2 text-xs">
|
|
470
|
+
<% if step[:iteration_concurrency] %>
|
|
471
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-blue-200 dark:bg-blue-800 text-blue-700 dark:text-blue-300">
|
|
472
|
+
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clip-rule="evenodd"/></svg>
|
|
473
|
+
<%= step[:iteration_concurrency] %> concurrent
|
|
474
|
+
</span>
|
|
475
|
+
<% else %>
|
|
476
|
+
<span class="px-2 py-1 rounded bg-blue-200/50 dark:bg-blue-800/50 text-blue-600 dark:text-blue-400">
|
|
477
|
+
Sequential (1 at a time)
|
|
478
|
+
</span>
|
|
479
|
+
<% end %>
|
|
480
|
+
<% if step[:continue_on_error] %>
|
|
481
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300">
|
|
482
|
+
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
|
|
483
|
+
continue_on_error (collects all results)
|
|
484
|
+
</span>
|
|
485
|
+
<% elsif step[:iteration_fail_fast] %>
|
|
486
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-red-100 dark:bg-red-900/50 text-red-700 dark:text-red-300">
|
|
487
|
+
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>
|
|
488
|
+
fail_fast (stops on first error)
|
|
489
|
+
</span>
|
|
490
|
+
<% else %>
|
|
491
|
+
<span class="px-2 py-1 rounded bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">
|
|
492
|
+
Default error handling
|
|
493
|
+
</span>
|
|
494
|
+
<% end %>
|
|
495
|
+
</div>
|
|
496
|
+
</div>
|
|
497
|
+
<% end %>
|
|
498
|
+
|
|
499
|
+
<!-- Routing Details (Decision Tree) -->
|
|
500
|
+
<% if step[:routing] && step[:routes].present? %>
|
|
501
|
+
<div class="mb-3 p-3 rounded-lg bg-amber-100/50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800">
|
|
502
|
+
<!-- Decision diamond header -->
|
|
503
|
+
<div class="flex items-center justify-center mb-3">
|
|
504
|
+
<div class="flex items-center gap-2">
|
|
505
|
+
<div class="w-8 h-8 bg-amber-400 dark:bg-amber-500 rotate-45 flex items-center justify-center shadow-sm">
|
|
506
|
+
<span class="text-white text-sm font-bold -rotate-45">?</span>
|
|
507
|
+
</div>
|
|
508
|
+
<span class="text-sm font-medium text-amber-700 dark:text-amber-300">
|
|
509
|
+
DECIDE: route based on value
|
|
510
|
+
</span>
|
|
511
|
+
</div>
|
|
512
|
+
</div>
|
|
513
|
+
|
|
514
|
+
<!-- Branching visualization -->
|
|
515
|
+
<div class="relative">
|
|
516
|
+
<!-- Branch lines container -->
|
|
517
|
+
<div class="flex flex-wrap justify-center gap-3">
|
|
518
|
+
<% step[:routes].each_with_index do |route, route_idx| %>
|
|
519
|
+
<%
|
|
520
|
+
is_default = route[:default]
|
|
521
|
+
card_bg = is_default ? "bg-gray-100 dark:bg-gray-800 border-gray-300 dark:border-gray-600" : "bg-white dark:bg-gray-800 border-amber-300 dark:border-amber-600"
|
|
522
|
+
label_color = is_default ? "text-gray-600 dark:text-gray-400" : "text-amber-700 dark:text-amber-300"
|
|
523
|
+
%>
|
|
524
|
+
<div class="flex flex-col items-center">
|
|
525
|
+
<!-- Branch line -->
|
|
526
|
+
<div class="w-0.5 h-4 bg-amber-300 dark:bg-amber-600"></div>
|
|
527
|
+
<!-- Route value pill -->
|
|
528
|
+
<div class="px-2 py-0.5 rounded-full text-xs font-medium <%= is_default ? 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-400' : 'bg-amber-200 dark:bg-amber-800 text-amber-700 dark:text-amber-300' %> mb-1">
|
|
529
|
+
<% if is_default %>
|
|
530
|
+
else
|
|
531
|
+
<% else %>
|
|
532
|
+
:<%= route[:name] %>
|
|
533
|
+
<% end %>
|
|
534
|
+
</div>
|
|
535
|
+
<!-- Arrow -->
|
|
536
|
+
<svg class="w-3 h-3 text-amber-400 dark:text-amber-500" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>
|
|
537
|
+
<!-- Route card -->
|
|
538
|
+
<div class="p-2 rounded-lg border-2 <%= card_bg %> text-center min-w-[100px]">
|
|
539
|
+
<div class="text-xs font-semibold <%= label_color %>">
|
|
540
|
+
<%= route[:agent]&.gsub(/Agent$/, '')&.gsub(/Workflow$/, '') || '?' %>
|
|
541
|
+
</div>
|
|
542
|
+
<% if route[:timeout] %>
|
|
543
|
+
<div class="inline-flex items-center gap-1 text-[10px] text-blue-500 dark:text-blue-400 mt-1">
|
|
544
|
+
<svg class="w-2.5 h-2.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
545
|
+
<%= route[:timeout] %>s
|
|
546
|
+
</div>
|
|
547
|
+
<% end %>
|
|
548
|
+
<% if route[:fallback] %>
|
|
549
|
+
<div class="inline-flex items-center gap-1 text-[10px] text-yellow-600 dark:text-yellow-400 mt-1">
|
|
550
|
+
<svg class="w-2.5 h-2.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/></svg>
|
|
551
|
+
<%= route[:fallback].gsub(/Agent$/, '') %>
|
|
552
|
+
</div>
|
|
553
|
+
<% end %>
|
|
554
|
+
<% if route[:if_condition] %>
|
|
555
|
+
<div class="inline-flex items-center gap-1 text-[10px] text-cyan-600 dark:text-cyan-400 mt-1">
|
|
556
|
+
<svg class="w-2.5 h-2.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clip-rule="evenodd"/></svg>
|
|
557
|
+
conditional
|
|
558
|
+
</div>
|
|
559
|
+
<% end %>
|
|
560
|
+
</div>
|
|
561
|
+
</div>
|
|
562
|
+
<% end %>
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
|
|
566
|
+
<!-- Routes summary count -->
|
|
567
|
+
<div class="text-center mt-3 pt-2 border-t border-amber-200 dark:border-amber-700">
|
|
568
|
+
<span class="text-xs text-amber-600 dark:text-amber-400">
|
|
569
|
+
<%= step[:routes].size %> possible routes
|
|
570
|
+
<% default_route = step[:routes].find { |r| r[:default] } %>
|
|
571
|
+
<% if default_route %>
|
|
572
|
+
(with default fallback)
|
|
573
|
+
<% end %>
|
|
574
|
+
</span>
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
<% end %>
|
|
578
|
+
|
|
579
|
+
<!-- Status Indicators (spelled out) -->
|
|
580
|
+
<div class="flex flex-wrap gap-2 text-xs">
|
|
581
|
+
<% if step[:retry_config] && step[:retry_config][:max].to_i > 0 %>
|
|
582
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-md bg-orange-100 dark:bg-orange-900/50 text-orange-700 dark:text-orange-300 border border-orange-200 dark:border-orange-800">
|
|
583
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
|
584
|
+
Retry <%= step[:retry_config][:max] %>× on failure
|
|
585
|
+
<% if step[:retry_config][:backoff] && step[:retry_config][:backoff] != :none %>
|
|
586
|
+
(<%= step[:retry_config][:backoff] %>)
|
|
587
|
+
<% end %>
|
|
588
|
+
</span>
|
|
589
|
+
<% end %>
|
|
590
|
+
|
|
591
|
+
<% if step[:timeout] %>
|
|
592
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-md bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-800">
|
|
593
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
594
|
+
Timeout: <%= step[:timeout] %>s
|
|
595
|
+
</span>
|
|
596
|
+
<% end %>
|
|
597
|
+
|
|
598
|
+
<% if step[:if_condition] %>
|
|
599
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-md bg-cyan-100 dark:bg-cyan-900/50 text-cyan-700 dark:text-cyan-300 border border-cyan-200 dark:border-cyan-800">
|
|
600
|
+
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clip-rule="evenodd"/></svg>
|
|
601
|
+
if: <%= step[:if_condition] %>
|
|
602
|
+
</span>
|
|
603
|
+
<% end %>
|
|
604
|
+
|
|
605
|
+
<% if step[:unless_condition] %>
|
|
606
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-md bg-pink-100 dark:bg-pink-900/50 text-pink-700 dark:text-pink-300 border border-pink-200 dark:border-pink-800">
|
|
607
|
+
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clip-rule="evenodd"/></svg>
|
|
608
|
+
unless: <%= step[:unless_condition] %>
|
|
609
|
+
</span>
|
|
610
|
+
<% end %>
|
|
611
|
+
|
|
612
|
+
<% if step[:optional] %>
|
|
613
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 border border-gray-200 dark:border-gray-600">
|
|
614
|
+
(optional)
|
|
615
|
+
</span>
|
|
616
|
+
<% end %>
|
|
617
|
+
|
|
618
|
+
<% if step[:fallbacks]&.any? %>
|
|
619
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-md bg-yellow-100 dark:bg-yellow-900/50 text-yellow-700 dark:text-yellow-300 border border-yellow-200 dark:border-yellow-800">
|
|
620
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/></svg>
|
|
621
|
+
Fallback: <%= step[:fallbacks].map { |f| f.gsub(/Agent$/, '') }.join(' → ') %>
|
|
622
|
+
</span>
|
|
623
|
+
<% end %>
|
|
624
|
+
|
|
625
|
+
<% if step[:default_value] %>
|
|
626
|
+
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 border border-gray-200 dark:border-gray-600">
|
|
627
|
+
default: <%= step[:default_value].inspect.truncate(20) %>
|
|
628
|
+
</span>
|
|
629
|
+
<% end %>
|
|
630
|
+
</div>
|
|
631
|
+
</div>
|
|
632
|
+
</div>
|
|
633
|
+
|
|
634
|
+
<% else %>
|
|
635
|
+
<!-- Parallel Group -->
|
|
636
|
+
<div class="w-full max-w-2xl" x-data="{ expanded: true }">
|
|
637
|
+
<!-- Fork visualization -->
|
|
638
|
+
<div class="flex justify-center mb-2">
|
|
639
|
+
<div class="flex items-end gap-1">
|
|
640
|
+
<div class="w-8 h-4 border-l-2 border-b-2 border-purple-400 dark:border-purple-500 rounded-bl"></div>
|
|
641
|
+
<div class="w-0.5 h-6 bg-purple-400 dark:bg-purple-500"></div>
|
|
642
|
+
<div class="w-8 h-4 border-r-2 border-b-2 border-purple-400 dark:border-purple-500 rounded-br"></div>
|
|
643
|
+
</div>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
<div class="relative rounded-2xl border-2 border-purple-400 dark:border-purple-500 bg-purple-50/80 dark:bg-purple-900/20 shadow-md p-4">
|
|
647
|
+
<!-- Parallel Group Header -->
|
|
648
|
+
<div class="flex items-center justify-between mb-4">
|
|
649
|
+
<div class="flex items-center gap-2">
|
|
650
|
+
<span class="w-7 h-7 flex items-center justify-center rounded-full bg-purple-500 dark:bg-purple-600 text-sm font-bold text-white">
|
|
651
|
+
<%= index + 1 %>
|
|
652
|
+
</span>
|
|
653
|
+
<span class="font-bold text-purple-800 dark:text-purple-200 text-lg uppercase tracking-tight">
|
|
654
|
+
<%= item[:group]&.dig(:name) || 'Parallel Group' %>
|
|
655
|
+
</span>
|
|
656
|
+
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-purple-200 dark:bg-purple-800 text-purple-700 dark:text-purple-300">
|
|
657
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg>
|
|
658
|
+
<%= item[:steps].size %> concurrent
|
|
659
|
+
</span>
|
|
660
|
+
</div>
|
|
661
|
+
|
|
662
|
+
<button @click="expanded = !expanded" class="inline-flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-medium bg-purple-100 dark:bg-purple-800 text-purple-700 dark:text-purple-300 hover:bg-purple-200 dark:hover:bg-purple-700 transition-colors cursor-pointer border border-purple-300 dark:border-purple-600">
|
|
663
|
+
<span x-text="expanded ? 'Collapse' : 'Expand'">Collapse</span>
|
|
664
|
+
<svg class="w-3 h-3 transition-transform" :class="expanded ? 'rotate-180' : ''" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
665
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
666
|
+
</svg>
|
|
667
|
+
</button>
|
|
668
|
+
</div>
|
|
669
|
+
|
|
670
|
+
<!-- Expanded: Show all parallel steps with visual fork -->
|
|
671
|
+
<div x-show="expanded" x-collapse>
|
|
672
|
+
<!-- Steps grid -->
|
|
673
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-<%= [item[:steps].size, 3].min %> gap-4 mb-4">
|
|
674
|
+
<% item[:steps].each_with_index do |step, step_idx| %>
|
|
675
|
+
<div class="relative">
|
|
676
|
+
<!-- Connecting line from fork -->
|
|
677
|
+
<div class="absolute -top-4 left-1/2 w-0.5 h-4 bg-purple-300 dark:bg-purple-600 -translate-x-1/2"></div>
|
|
678
|
+
|
|
679
|
+
<div class="rounded-lg border-2 border-purple-300 dark:border-purple-600 bg-white dark:bg-gray-800 p-3 <%= step[:optional] ? 'border-dashed' : '' %> h-full">
|
|
680
|
+
<div class="font-semibold text-purple-700 dark:text-purple-300 text-sm mb-1">
|
|
681
|
+
<%= step[:name].to_s.titleize %>
|
|
682
|
+
</div>
|
|
683
|
+
<% if step[:agent].present? %>
|
|
684
|
+
<code class="text-xs font-mono text-gray-500 dark:text-gray-400">
|
|
685
|
+
<%= step[:agent].to_s.gsub(/Agent$/, '') %>
|
|
686
|
+
</code>
|
|
687
|
+
<% elsif step[:custom_block] %>
|
|
688
|
+
<span class="text-xs text-violet-500 dark:text-violet-400 italic">(block)</span>
|
|
689
|
+
<% end %>
|
|
690
|
+
|
|
691
|
+
<!-- Mini indicators -->
|
|
692
|
+
<div class="flex flex-wrap gap-1 mt-2">
|
|
693
|
+
<% if step[:retry_config] && step[:retry_config][:max].to_i > 0 %>
|
|
694
|
+
<span class="inline-flex items-center gap-0.5 text-[10px] px-1.5 py-0.5 rounded bg-orange-100 dark:bg-orange-900/50 text-orange-600 dark:text-orange-300">
|
|
695
|
+
<svg class="w-2.5 h-2.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
|
696
|
+
<%= step[:retry_config][:max] %>×
|
|
697
|
+
</span>
|
|
698
|
+
<% end %>
|
|
699
|
+
<% if step[:timeout] %>
|
|
700
|
+
<span class="inline-flex items-center gap-0.5 text-[10px] px-1.5 py-0.5 rounded bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-300">
|
|
701
|
+
<svg class="w-2.5 h-2.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
702
|
+
<%= step[:timeout] %>s
|
|
703
|
+
</span>
|
|
704
|
+
<% end %>
|
|
705
|
+
<% if step[:if_condition] || step[:unless_condition] %>
|
|
706
|
+
<span class="inline-flex items-center gap-0.5 text-[10px] px-1.5 py-0.5 rounded bg-cyan-100 dark:bg-cyan-900/50 text-cyan-600 dark:text-cyan-300">
|
|
707
|
+
<svg class="w-2.5 h-2.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clip-rule="evenodd"/></svg>
|
|
708
|
+
conditional
|
|
709
|
+
</span>
|
|
710
|
+
<% end %>
|
|
711
|
+
<% if step[:optional] %>
|
|
712
|
+
<span class="text-[10px] px-1.5 py-0.5 rounded bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400">
|
|
713
|
+
optional
|
|
714
|
+
</span>
|
|
715
|
+
<% end %>
|
|
716
|
+
</div>
|
|
717
|
+
</div>
|
|
718
|
+
|
|
719
|
+
<!-- Connecting line to join -->
|
|
720
|
+
<div class="absolute -bottom-4 left-1/2 w-0.5 h-4 bg-purple-300 dark:bg-purple-600 -translate-x-1/2"></div>
|
|
721
|
+
</div>
|
|
722
|
+
<% end %>
|
|
723
|
+
</div>
|
|
724
|
+
|
|
725
|
+
<!-- WAIT FOR ALL indicator -->
|
|
726
|
+
<div class="flex items-center justify-center py-2 border-t border-purple-200 dark:border-purple-700">
|
|
727
|
+
<span class="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-purple-200 dark:bg-purple-800 text-sm font-semibold text-purple-700 dark:text-purple-300 uppercase tracking-wide">
|
|
728
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
729
|
+
Wait for all
|
|
730
|
+
</span>
|
|
731
|
+
</div>
|
|
732
|
+
</div>
|
|
733
|
+
|
|
734
|
+
<!-- Collapsed: Just show count -->
|
|
735
|
+
<div x-show="!expanded" class="text-center py-4">
|
|
736
|
+
<div class="flex items-center justify-center gap-2 text-purple-600 dark:text-purple-400">
|
|
737
|
+
<% item[:steps].each_with_index do |step, step_idx| %>
|
|
738
|
+
<div class="w-8 h-8 rounded-lg bg-purple-100 dark:bg-purple-800 flex items-center justify-center text-xs font-medium text-purple-600 dark:text-purple-300">
|
|
739
|
+
<%= step_idx + 1 %>
|
|
740
|
+
</div>
|
|
741
|
+
<% unless step_idx == item[:steps].size - 1 %>
|
|
742
|
+
<span class="text-purple-300 dark:text-purple-600">|</span>
|
|
743
|
+
<% end %>
|
|
744
|
+
<% end %>
|
|
745
|
+
</div>
|
|
746
|
+
<p class="text-xs text-purple-500 dark:text-purple-400 mt-2">Click to expand <%= item[:steps].size %> parallel steps</p>
|
|
747
|
+
</div>
|
|
748
|
+
|
|
749
|
+
<!-- Group options -->
|
|
750
|
+
<% if item[:group] && (item[:group][:fail_fast] || item[:group][:concurrency] || item[:group][:timeout]) %>
|
|
751
|
+
<div class="flex items-center justify-center gap-3 mt-3 pt-3 border-t border-purple-200 dark:border-purple-700">
|
|
752
|
+
<% if item[:group][:fail_fast] %>
|
|
753
|
+
<span class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded bg-red-100 dark:bg-red-900/50 text-red-700 dark:text-red-300">
|
|
754
|
+
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>
|
|
755
|
+
fail_fast (stop on first error)
|
|
756
|
+
</span>
|
|
757
|
+
<% end %>
|
|
758
|
+
<% if item[:group][:timeout] %>
|
|
759
|
+
<span class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded bg-purple-200 dark:bg-purple-800 text-purple-700 dark:text-purple-300">
|
|
760
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
761
|
+
Timeout: <%= item[:group][:timeout] %>s
|
|
762
|
+
</span>
|
|
763
|
+
<% end %>
|
|
764
|
+
<% if item[:group][:concurrency] %>
|
|
765
|
+
<span class="text-xs px-2 py-1 rounded bg-purple-200 dark:bg-purple-800 text-purple-700 dark:text-purple-300">
|
|
766
|
+
Max concurrency: <%= item[:group][:concurrency] %>
|
|
767
|
+
</span>
|
|
768
|
+
<% end %>
|
|
769
|
+
</div>
|
|
770
|
+
<% end %>
|
|
771
|
+
</div>
|
|
772
|
+
|
|
773
|
+
<!-- Join visualization -->
|
|
774
|
+
<div class="flex justify-center mt-2">
|
|
775
|
+
<div class="flex items-start gap-1">
|
|
776
|
+
<div class="w-8 h-4 border-l-2 border-t-2 border-purple-400 dark:border-purple-500 rounded-tl"></div>
|
|
777
|
+
<div class="w-0.5 h-6 bg-purple-400 dark:bg-purple-500"></div>
|
|
778
|
+
<div class="w-8 h-4 border-r-2 border-t-2 border-purple-400 dark:border-purple-500 rounded-tr"></div>
|
|
779
|
+
</div>
|
|
780
|
+
</div>
|
|
781
|
+
</div>
|
|
782
|
+
<% end %>
|
|
783
|
+
|
|
784
|
+
<!-- Connecting Line to Next Step -->
|
|
785
|
+
<% unless index == grouped_items.length - 1 %>
|
|
786
|
+
<div class="flex flex-col items-center">
|
|
787
|
+
<div class="w-0.5 h-4 bg-gray-300 dark:bg-gray-600"></div>
|
|
788
|
+
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>
|
|
789
|
+
<div class="w-0.5 h-4 bg-gray-300 dark:bg-gray-600"></div>
|
|
790
|
+
</div>
|
|
791
|
+
<% end %>
|
|
792
|
+
<% end %>
|
|
793
|
+
|
|
794
|
+
<!-- Connecting Line to End -->
|
|
795
|
+
<div class="w-0.5 h-6 bg-gradient-to-b from-gray-300 to-red-400 dark:from-gray-600 dark:to-red-600"></div>
|
|
796
|
+
|
|
797
|
+
<!-- End Node -->
|
|
798
|
+
<div class="flex flex-col items-center mt-2">
|
|
799
|
+
<div class="relative">
|
|
800
|
+
<div class="relative w-16 h-10 flex items-center justify-center rounded-full bg-gradient-to-br from-red-100 to-red-200 dark:from-red-900/50 dark:to-red-800/50 border-2 border-red-500 dark:border-red-600 shadow-md">
|
|
801
|
+
<span class="text-sm font-bold text-red-700 dark:text-red-300 uppercase tracking-wide">End</span>
|
|
802
|
+
</div>
|
|
803
|
+
</div>
|
|
804
|
+
</div>
|
|
805
|
+
</div>
|
|
806
|
+
|
|
807
|
+
<!-- Legend -->
|
|
808
|
+
<div class="border-t border-gray-200 dark:border-gray-700 pt-4 mt-4">
|
|
809
|
+
<div class="flex flex-wrap items-center justify-center gap-x-6 gap-y-2 text-xs text-gray-500 dark:text-gray-400">
|
|
810
|
+
<!-- Step Types -->
|
|
811
|
+
<div class="flex items-center gap-1.5">
|
|
812
|
+
<div class="w-4 h-4 rounded border-2 border-slate-300 dark:border-slate-600 bg-white dark:bg-gray-800"></div>
|
|
813
|
+
<span>Sequential</span>
|
|
814
|
+
</div>
|
|
815
|
+
<% if steps.any? { |s| s[:workflow] } %>
|
|
816
|
+
<div class="flex items-center gap-1.5">
|
|
817
|
+
<div class="w-4 h-4 rounded border-2 border-emerald-400 dark:border-emerald-500 bg-emerald-50 dark:bg-emerald-900/30"></div>
|
|
818
|
+
<span>Sub-workflow</span>
|
|
819
|
+
</div>
|
|
820
|
+
<% end %>
|
|
821
|
+
<% if steps.any? { |s| s[:iteration] } %>
|
|
822
|
+
<div class="flex items-center gap-1.5">
|
|
823
|
+
<div class="w-4 h-4 rounded border-2 border-blue-400 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30"></div>
|
|
824
|
+
<span>Iteration</span>
|
|
825
|
+
</div>
|
|
826
|
+
<% end %>
|
|
827
|
+
<% if parallel_groups.any? %>
|
|
828
|
+
<div class="flex items-center gap-1.5">
|
|
829
|
+
<div class="w-4 h-4 rounded border-2 border-dashed border-purple-400 dark:border-purple-500 bg-purple-50 dark:bg-purple-900/30"></div>
|
|
830
|
+
<span>Parallel</span>
|
|
831
|
+
</div>
|
|
832
|
+
<% end %>
|
|
833
|
+
<% if steps.any? { |s| s[:routing] } %>
|
|
834
|
+
<div class="flex items-center gap-1.5">
|
|
835
|
+
<div class="w-4 h-4 rounded border-2 border-amber-400 dark:border-amber-500 bg-amber-50 dark:bg-amber-900/30"></div>
|
|
836
|
+
<span>Routing</span>
|
|
837
|
+
</div>
|
|
838
|
+
<% end %>
|
|
839
|
+
<% if steps.any? { |s| s[:custom_block] } %>
|
|
840
|
+
<div class="flex items-center gap-1.5">
|
|
841
|
+
<div class="w-4 h-4 rounded border-2 border-violet-400 dark:border-violet-500 bg-violet-50 dark:bg-violet-900/30"></div>
|
|
842
|
+
<span>Block</span>
|
|
843
|
+
</div>
|
|
844
|
+
<% end %>
|
|
845
|
+
<% if steps.any? { |s| s[:type] == :wait && s[:wait_type] == :delay } %>
|
|
846
|
+
<div class="flex items-center gap-1.5">
|
|
847
|
+
<div class="w-4 h-4 rounded border-2 border-dashed border-teal-400 dark:border-teal-500 bg-teal-50 dark:bg-teal-900/30"></div>
|
|
848
|
+
<span>Delay</span>
|
|
849
|
+
</div>
|
|
850
|
+
<% end %>
|
|
851
|
+
<% if steps.any? { |s| s[:type] == :wait && s[:wait_type] == :until } %>
|
|
852
|
+
<div class="flex items-center gap-1.5">
|
|
853
|
+
<div class="w-4 h-4 rounded border-2 border-dashed border-cyan-400 dark:border-cyan-500 bg-cyan-50 dark:bg-cyan-900/30"></div>
|
|
854
|
+
<span>Poll Until</span>
|
|
855
|
+
</div>
|
|
856
|
+
<% end %>
|
|
857
|
+
<% if steps.any? { |s| s[:type] == :wait && s[:wait_type] == :schedule } %>
|
|
858
|
+
<div class="flex items-center gap-1.5">
|
|
859
|
+
<div class="w-4 h-4 rounded border-2 border-dashed border-indigo-400 dark:border-indigo-500 bg-indigo-50 dark:bg-indigo-900/30"></div>
|
|
860
|
+
<span>Scheduled</span>
|
|
861
|
+
</div>
|
|
862
|
+
<% end %>
|
|
863
|
+
<% if steps.any? { |s| s[:type] == :wait && s[:wait_type] == :approval } %>
|
|
864
|
+
<div class="flex items-center gap-1.5">
|
|
865
|
+
<div class="w-4 h-4 rounded border-2 border-dashed border-orange-400 dark:border-orange-500 bg-orange-50 dark:bg-orange-900/30"></div>
|
|
866
|
+
<span>Approval</span>
|
|
867
|
+
</div>
|
|
868
|
+
<% end %>
|
|
869
|
+
<% if steps.any? { |s| s[:optional] } %>
|
|
870
|
+
<div class="flex items-center gap-1.5">
|
|
871
|
+
<div class="w-4 h-4 rounded border-2 border-dashed border-gray-400 dark:border-gray-500 bg-gray-50 dark:bg-gray-800"></div>
|
|
872
|
+
<span>Optional</span>
|
|
873
|
+
</div>
|
|
874
|
+
<% end %>
|
|
875
|
+
</div>
|
|
876
|
+
</div>
|
|
877
|
+
|
|
878
|
+
<!-- Input Schema (if defined) -->
|
|
879
|
+
<% if input_schema_fields.present? %>
|
|
880
|
+
<div class="border-t border-gray-200 dark:border-gray-700 pt-4 mt-4">
|
|
881
|
+
<h4 class="flex items-center gap-2 text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider mb-3">
|
|
882
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/></svg>
|
|
883
|
+
Input Schema
|
|
884
|
+
</h4>
|
|
885
|
+
|
|
886
|
+
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
|
887
|
+
<% input_schema_fields.each do |name, field| %>
|
|
888
|
+
<div class="flex items-start gap-2 py-2 px-3 rounded-lg bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700">
|
|
889
|
+
<span class="w-2 h-2 rounded-full mt-1.5 flex-shrink-0 <%= field[:required] ? 'bg-red-400' : 'bg-gray-300 dark:bg-gray-600' %>"></span>
|
|
890
|
+
<div class="flex-1 min-w-0">
|
|
891
|
+
<div class="flex items-center gap-1.5">
|
|
892
|
+
<span class="font-medium text-gray-700 dark:text-gray-300 text-sm">
|
|
893
|
+
<%= name %>
|
|
894
|
+
</span>
|
|
895
|
+
<span class="text-[10px] text-gray-400 dark:text-gray-500 bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded">
|
|
896
|
+
<%= field[:type] %>
|
|
897
|
+
</span>
|
|
898
|
+
</div>
|
|
899
|
+
<% if field[:default].present? || field[:default] == false %>
|
|
900
|
+
<span class="text-[10px] text-gray-500 dark:text-gray-400 block mt-0.5">
|
|
901
|
+
default: <code class="bg-gray-100 dark:bg-gray-800 px-1 rounded"><%= field[:default].inspect %></code>
|
|
902
|
+
</span>
|
|
903
|
+
<% end %>
|
|
904
|
+
</div>
|
|
905
|
+
</div>
|
|
906
|
+
<% end %>
|
|
907
|
+
</div>
|
|
908
|
+
</div>
|
|
909
|
+
<% end %>
|
|
910
|
+
<% else %>
|
|
911
|
+
<div class="py-12 text-center">
|
|
912
|
+
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
|
|
913
|
+
<svg class="w-8 h-8 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
914
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"/>
|
|
915
|
+
</svg>
|
|
916
|
+
</div>
|
|
917
|
+
<p class="text-gray-500 dark:text-gray-400 text-lg">No workflow structure available</p>
|
|
918
|
+
<p class="text-gray-400 dark:text-gray-500 text-sm mt-1">This workflow has no steps defined</p>
|
|
919
|
+
</div>
|
|
920
|
+
<% end %>
|