ruby_llm-agents 0.3.3 → 0.3.4
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/ruby_llm/agents/dashboard_controller.rb +68 -4
- data/app/models/ruby_llm/agents/execution/analytics.rb +114 -13
- data/app/models/ruby_llm/agents/execution.rb +19 -58
- data/app/views/layouts/rubyllm/agents/application.html.erb +92 -350
- data/app/views/rubyllm/agents/agents/show.html.erb +331 -385
- data/app/views/rubyllm/agents/dashboard/_agent_comparison.html.erb +46 -0
- data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +0 -90
- data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +79 -5
- data/app/views/rubyllm/agents/dashboard/_top_errors.html.erb +49 -0
- data/app/views/rubyllm/agents/dashboard/index.html.erb +76 -121
- data/app/views/rubyllm/agents/executions/show.html.erb +134 -85
- data/app/views/rubyllm/agents/settings/show.html.erb +1 -1
- data/app/views/rubyllm/agents/shared/_breadcrumbs.html.erb +48 -0
- data/app/views/rubyllm/agents/shared/_nav_link.html.erb +27 -0
- data/config/routes.rb +2 -0
- data/lib/ruby_llm/agents/base/caching.rb +43 -0
- data/lib/ruby_llm/agents/base/cost_calculation.rb +103 -0
- data/lib/ruby_llm/agents/base/dsl.rb +261 -0
- data/lib/ruby_llm/agents/base/execution.rb +206 -0
- data/lib/ruby_llm/agents/base/reliability_execution.rb +131 -0
- data/lib/ruby_llm/agents/base/response_building.rb +86 -0
- data/lib/ruby_llm/agents/base/tool_tracking.rb +57 -0
- data/lib/ruby_llm/agents/base.rb +15 -805
- data/lib/ruby_llm/agents/version.rb +1 -1
- metadata +12 -20
- data/app/channels/ruby_llm/agents/executions_channel.rb +0 -46
- data/app/javascript/ruby_llm/agents/controllers/filter_controller.js +0 -56
- data/app/javascript/ruby_llm/agents/controllers/index.js +0 -12
- data/app/javascript/ruby_llm/agents/controllers/refresh_controller.js +0 -83
- data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +0 -71
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_llm-agents
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- adham90
|
|
@@ -65,20 +65,6 @@ dependencies:
|
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '1.0'
|
|
68
|
-
- !ruby/object:Gem::Dependency
|
|
69
|
-
name: chartkick
|
|
70
|
-
requirement: !ruby/object:Gem::Requirement
|
|
71
|
-
requirements:
|
|
72
|
-
- - ">="
|
|
73
|
-
- !ruby/object:Gem::Version
|
|
74
|
-
version: '5.0'
|
|
75
|
-
type: :runtime
|
|
76
|
-
prerelease: false
|
|
77
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
-
requirements:
|
|
79
|
-
- - ">="
|
|
80
|
-
- !ruby/object:Gem::Version
|
|
81
|
-
version: '5.0'
|
|
82
68
|
- !ruby/object:Gem::Dependency
|
|
83
69
|
name: csv
|
|
84
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -104,7 +90,6 @@ extra_rdoc_files: []
|
|
|
104
90
|
files:
|
|
105
91
|
- LICENSE.txt
|
|
106
92
|
- README.md
|
|
107
|
-
- app/channels/ruby_llm/agents/executions_channel.rb
|
|
108
93
|
- app/controllers/concerns/ruby_llm/agents/filterable.rb
|
|
109
94
|
- app/controllers/concerns/ruby_llm/agents/paginatable.rb
|
|
110
95
|
- app/controllers/ruby_llm/agents/agents_controller.rb
|
|
@@ -112,9 +97,6 @@ files:
|
|
|
112
97
|
- app/controllers/ruby_llm/agents/executions_controller.rb
|
|
113
98
|
- app/controllers/ruby_llm/agents/settings_controller.rb
|
|
114
99
|
- app/helpers/ruby_llm/agents/application_helper.rb
|
|
115
|
-
- app/javascript/ruby_llm/agents/controllers/filter_controller.js
|
|
116
|
-
- app/javascript/ruby_llm/agents/controllers/index.js
|
|
117
|
-
- app/javascript/ruby_llm/agents/controllers/refresh_controller.js
|
|
118
100
|
- app/models/ruby_llm/agents/execution.rb
|
|
119
101
|
- app/models/ruby_llm/agents/execution/analytics.rb
|
|
120
102
|
- app/models/ruby_llm/agents/execution/metrics.rb
|
|
@@ -126,12 +108,13 @@ files:
|
|
|
126
108
|
- app/views/rubyllm/agents/agents/index.html.erb
|
|
127
109
|
- app/views/rubyllm/agents/agents/show.html.erb
|
|
128
110
|
- app/views/rubyllm/agents/dashboard/_action_center.html.erb
|
|
111
|
+
- app/views/rubyllm/agents/dashboard/_agent_comparison.html.erb
|
|
129
112
|
- app/views/rubyllm/agents/dashboard/_alerts_feed.html.erb
|
|
130
113
|
- app/views/rubyllm/agents/dashboard/_breaker_strip.html.erb
|
|
131
114
|
- app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb
|
|
132
115
|
- app/views/rubyllm/agents/dashboard/_execution_item.html.erb
|
|
133
116
|
- app/views/rubyllm/agents/dashboard/_now_strip.html.erb
|
|
134
|
-
- app/views/rubyllm/agents/dashboard/
|
|
117
|
+
- app/views/rubyllm/agents/dashboard/_top_errors.html.erb
|
|
135
118
|
- app/views/rubyllm/agents/dashboard/index.html.erb
|
|
136
119
|
- app/views/rubyllm/agents/executions/_execution.html.erb
|
|
137
120
|
- app/views/rubyllm/agents/executions/_filters.html.erb
|
|
@@ -141,8 +124,10 @@ files:
|
|
|
141
124
|
- app/views/rubyllm/agents/executions/index.turbo_stream.erb
|
|
142
125
|
- app/views/rubyllm/agents/executions/show.html.erb
|
|
143
126
|
- app/views/rubyllm/agents/settings/show.html.erb
|
|
127
|
+
- app/views/rubyllm/agents/shared/_breadcrumbs.html.erb
|
|
144
128
|
- app/views/rubyllm/agents/shared/_executions_table.html.erb
|
|
145
129
|
- app/views/rubyllm/agents/shared/_filter_dropdown.html.erb
|
|
130
|
+
- app/views/rubyllm/agents/shared/_nav_link.html.erb
|
|
146
131
|
- app/views/rubyllm/agents/shared/_select_dropdown.html.erb
|
|
147
132
|
- app/views/rubyllm/agents/shared/_stat_card.html.erb
|
|
148
133
|
- app/views/rubyllm/agents/shared/_status_badge.html.erb
|
|
@@ -168,6 +153,13 @@ files:
|
|
|
168
153
|
- lib/ruby_llm/agents/alert_manager.rb
|
|
169
154
|
- lib/ruby_llm/agents/attempt_tracker.rb
|
|
170
155
|
- lib/ruby_llm/agents/base.rb
|
|
156
|
+
- lib/ruby_llm/agents/base/caching.rb
|
|
157
|
+
- lib/ruby_llm/agents/base/cost_calculation.rb
|
|
158
|
+
- lib/ruby_llm/agents/base/dsl.rb
|
|
159
|
+
- lib/ruby_llm/agents/base/execution.rb
|
|
160
|
+
- lib/ruby_llm/agents/base/reliability_execution.rb
|
|
161
|
+
- lib/ruby_llm/agents/base/response_building.rb
|
|
162
|
+
- lib/ruby_llm/agents/base/tool_tracking.rb
|
|
171
163
|
- lib/ruby_llm/agents/budget_tracker.rb
|
|
172
164
|
- lib/ruby_llm/agents/circuit_breaker.rb
|
|
173
165
|
- lib/ruby_llm/agents/configuration.rb
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module Agents
|
|
5
|
-
# ActionCable channel for real-time execution updates
|
|
6
|
-
#
|
|
7
|
-
# Broadcasts execution create/update events to subscribed clients.
|
|
8
|
-
# Used by the dashboard to show live execution status changes.
|
|
9
|
-
#
|
|
10
|
-
# Inherits from the host app's ApplicationCable::Channel (note the :: prefix
|
|
11
|
-
# to reference the root namespace, not the engine's namespace).
|
|
12
|
-
#
|
|
13
|
-
# @example JavaScript subscription
|
|
14
|
-
# import { createConsumer } from "@rails/actioncable"
|
|
15
|
-
# const consumer = createConsumer()
|
|
16
|
-
# consumer.subscriptions.create("RubyLLM::Agents::ExecutionsChannel", {
|
|
17
|
-
# received(data) {
|
|
18
|
-
# console.log("Execution update:", data)
|
|
19
|
-
# }
|
|
20
|
-
# })
|
|
21
|
-
#
|
|
22
|
-
# @see Execution#broadcast_execution Broadcast trigger
|
|
23
|
-
# @api private
|
|
24
|
-
class ExecutionsChannel < ::ApplicationCable::Channel
|
|
25
|
-
# Subscribes the client to the executions broadcast stream
|
|
26
|
-
#
|
|
27
|
-
# Called automatically when a client connects to this channel.
|
|
28
|
-
# Streams from the "ruby_llm_agents:executions" channel name.
|
|
29
|
-
#
|
|
30
|
-
# @return [void]
|
|
31
|
-
def subscribed
|
|
32
|
-
stream_from "ruby_llm_agents:executions"
|
|
33
|
-
logger.info "[RubyLLM::Agents] Client subscribed to executions channel"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Cleans up when a client disconnects
|
|
37
|
-
#
|
|
38
|
-
# Called automatically when the WebSocket connection is closed.
|
|
39
|
-
#
|
|
40
|
-
# @return [void]
|
|
41
|
-
def unsubscribed
|
|
42
|
-
logger.info "[RubyLLM::Agents] Client unsubscribed from executions channel"
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { Controller } from "@hotwired/stimulus"
|
|
2
|
-
|
|
3
|
-
// Filter controller for live filtering of executions
|
|
4
|
-
//
|
|
5
|
-
// Usage:
|
|
6
|
-
// <form data-controller="filter">
|
|
7
|
-
// <select data-action="change->filter#submit">...</select>
|
|
8
|
-
// </form>
|
|
9
|
-
//
|
|
10
|
-
export default class extends Controller {
|
|
11
|
-
static targets = ["form"]
|
|
12
|
-
static values = {
|
|
13
|
-
debounce: { type: Number, default: 300 }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
connect() {
|
|
17
|
-
this.timeout = null
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
disconnect() {
|
|
21
|
-
if (this.timeout) {
|
|
22
|
-
clearTimeout(this.timeout)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Submit the form with debouncing
|
|
27
|
-
submit(event) {
|
|
28
|
-
if (this.timeout) {
|
|
29
|
-
clearTimeout(this.timeout)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
this.timeout = setTimeout(() => {
|
|
33
|
-
this.element.requestSubmit()
|
|
34
|
-
}, this.debounceValue)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Submit immediately without debounce
|
|
38
|
-
submitNow(event) {
|
|
39
|
-
this.element.requestSubmit()
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Update URL with current filter state
|
|
43
|
-
updateUrl() {
|
|
44
|
-
const formData = new FormData(this.element)
|
|
45
|
-
const params = new URLSearchParams()
|
|
46
|
-
|
|
47
|
-
for (const [key, value] of formData) {
|
|
48
|
-
if (value) {
|
|
49
|
-
params.set(key, value)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const newUrl = `${window.location.pathname}?${params.toString()}`
|
|
54
|
-
window.history.pushState({}, "", newUrl)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Entry point for RubyLLM::Agents Stimulus controllers
|
|
2
|
-
// Import and register controllers with your Stimulus application
|
|
3
|
-
|
|
4
|
-
import FilterController from "./filter_controller"
|
|
5
|
-
import RefreshController from "./refresh_controller"
|
|
6
|
-
|
|
7
|
-
export { FilterController, RefreshController }
|
|
8
|
-
|
|
9
|
-
export function registerControllers(application) {
|
|
10
|
-
application.register("filter", FilterController)
|
|
11
|
-
application.register("refresh", RefreshController)
|
|
12
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Controller } from "@hotwired/stimulus"
|
|
2
|
-
|
|
3
|
-
// Auto-refresh controller for dashboard with toggle support
|
|
4
|
-
//
|
|
5
|
-
// Usage:
|
|
6
|
-
// <div data-controller="refresh"
|
|
7
|
-
// data-refresh-interval-value="30000"
|
|
8
|
-
// data-refresh-enabled-value="false">
|
|
9
|
-
// <button data-action="refresh#toggle" data-refresh-target="button">
|
|
10
|
-
// Live Poll: Off
|
|
11
|
-
// </button>
|
|
12
|
-
// </div>
|
|
13
|
-
//
|
|
14
|
-
export default class extends Controller {
|
|
15
|
-
static values = {
|
|
16
|
-
interval: { type: Number, default: 30000 },
|
|
17
|
-
enabled: { type: Boolean, default: false }
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
static targets = ["button", "indicator"]
|
|
21
|
-
|
|
22
|
-
connect() {
|
|
23
|
-
this.updateUI()
|
|
24
|
-
if (this.enabledValue) {
|
|
25
|
-
this.startRefresh()
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
disconnect() {
|
|
30
|
-
this.stopRefresh()
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
toggle() {
|
|
34
|
-
this.enabledValue = !this.enabledValue
|
|
35
|
-
|
|
36
|
-
if (this.enabledValue) {
|
|
37
|
-
this.startRefresh()
|
|
38
|
-
this.refresh() // Immediate refresh when enabled
|
|
39
|
-
} else {
|
|
40
|
-
this.stopRefresh()
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
this.updateUI()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
startRefresh() {
|
|
47
|
-
if (this.intervalValue > 0 && !this.timer) {
|
|
48
|
-
this.timer = setInterval(() => this.refresh(), this.intervalValue)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
stopRefresh() {
|
|
53
|
-
if (this.timer) {
|
|
54
|
-
clearInterval(this.timer)
|
|
55
|
-
this.timer = null
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
refresh() {
|
|
60
|
-
const frame = this.element.closest("turbo-frame") ||
|
|
61
|
-
this.element.querySelector("turbo-frame")
|
|
62
|
-
|
|
63
|
-
if (frame && typeof frame.reload === "function") {
|
|
64
|
-
frame.reload()
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
updateUI() {
|
|
69
|
-
if (this.hasButtonTarget) {
|
|
70
|
-
if (this.enabledValue) {
|
|
71
|
-
this.buttonTarget.classList.remove("bg-gray-100", "text-gray-600")
|
|
72
|
-
this.buttonTarget.classList.add("bg-green-100", "text-green-700")
|
|
73
|
-
} else {
|
|
74
|
-
this.buttonTarget.classList.remove("bg-green-100", "text-green-700")
|
|
75
|
-
this.buttonTarget.classList.add("bg-gray-100", "text-gray-600")
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (this.hasIndicatorTarget) {
|
|
80
|
-
this.indicatorTarget.textContent = this.enabledValue ? "On" : "Off"
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
|
|
2
|
-
<!-- Running -->
|
|
3
|
-
<div class="flex items-center bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm border border-gray-100 dark:border-gray-700">
|
|
4
|
-
<div class="flex-shrink-0 w-8 h-8 bg-blue-100 dark:bg-blue-900 rounded-full flex items-center justify-center mr-3">
|
|
5
|
-
<span class="w-2.5 h-2.5 bg-blue-500 rounded-full animate-pulse"></span>
|
|
6
|
-
</div>
|
|
7
|
-
<div>
|
|
8
|
-
<p id="now-running" class="text-lg font-bold text-gray-900 dark:text-gray-100"><%= now_strip[:running] %></p>
|
|
9
|
-
<p class="text-xs text-gray-500 dark:text-gray-400">Running</p>
|
|
10
|
-
</div>
|
|
11
|
-
</div>
|
|
12
|
-
|
|
13
|
-
<!-- Success Today -->
|
|
14
|
-
<div class="flex items-center bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm border border-gray-100 dark:border-gray-700">
|
|
15
|
-
<div class="flex-shrink-0 w-8 h-8 bg-green-100 dark:bg-green-900 rounded-full flex items-center justify-center mr-3">
|
|
16
|
-
<span class="w-2.5 h-2.5 bg-green-500 rounded-full"></span>
|
|
17
|
-
</div>
|
|
18
|
-
<div>
|
|
19
|
-
<p id="now-success" class="text-lg font-bold text-green-600 dark:text-green-400"><%= now_strip[:success_today] %></p>
|
|
20
|
-
<p class="text-xs text-gray-500 dark:text-gray-400">Success</p>
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<!-- Errors Today -->
|
|
25
|
-
<div class="flex items-center bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm border border-gray-100 dark:border-gray-700">
|
|
26
|
-
<div class="flex-shrink-0 w-8 h-8 <%= now_strip[:errors_today] > 0 ? 'bg-red-100 dark:bg-red-900' : 'bg-gray-100 dark:bg-gray-700' %> rounded-full flex items-center justify-center mr-3">
|
|
27
|
-
<span class="w-2.5 h-2.5 <%= now_strip[:errors_today] > 0 ? 'bg-red-500' : 'bg-gray-400' %> rounded-full"></span>
|
|
28
|
-
</div>
|
|
29
|
-
<div>
|
|
30
|
-
<p id="now-errors" class="text-lg font-bold <%= now_strip[:errors_today] > 0 ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-gray-100' %>"><%= now_strip[:errors_today] %></p>
|
|
31
|
-
<p class="text-xs text-gray-500 dark:text-gray-400">Errors</p>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
|
|
35
|
-
<!-- Timeouts Today -->
|
|
36
|
-
<div class="flex items-center bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm border border-gray-100 dark:border-gray-700">
|
|
37
|
-
<div class="flex-shrink-0 w-8 h-8 <%= now_strip[:timeouts_today] > 0 ? 'bg-yellow-100 dark:bg-yellow-900' : 'bg-gray-100 dark:bg-gray-700' %> rounded-full flex items-center justify-center mr-3">
|
|
38
|
-
<span class="w-2.5 h-2.5 <%= now_strip[:timeouts_today] > 0 ? 'bg-yellow-500' : 'bg-gray-400' %> rounded-full"></span>
|
|
39
|
-
</div>
|
|
40
|
-
<div>
|
|
41
|
-
<p id="now-timeouts" class="text-lg font-bold <%= now_strip[:timeouts_today] > 0 ? 'text-yellow-600 dark:text-yellow-400' : 'text-gray-900 dark:text-gray-100' %>"><%= now_strip[:timeouts_today] %></p>
|
|
42
|
-
<p class="text-xs text-gray-500 dark:text-gray-400">Timeouts</p>
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
<!-- Cost Today -->
|
|
47
|
-
<div class="flex items-center bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm border border-gray-100 dark:border-gray-700">
|
|
48
|
-
<div class="flex-shrink-0 w-8 h-8 bg-amber-100 dark:bg-amber-900 rounded-full flex items-center justify-center mr-3">
|
|
49
|
-
<svg class="w-4 h-4 text-amber-600 dark:text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
50
|
-
<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-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
51
|
-
</svg>
|
|
52
|
-
</div>
|
|
53
|
-
<div>
|
|
54
|
-
<p id="now-cost-today" class="text-lg font-bold text-gray-900 dark:text-gray-100">$<%= number_with_precision(now_strip[:cost_today], precision: 4) %></p>
|
|
55
|
-
<p class="text-xs text-gray-500 dark:text-gray-400">Cost Today</p>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
|
|
59
|
-
<!-- Success Rate -->
|
|
60
|
-
<div class="flex items-center bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm border border-gray-100 dark:border-gray-700">
|
|
61
|
-
<div class="flex-shrink-0 w-8 h-8 bg-green-100 dark:bg-green-900 rounded-full flex items-center justify-center mr-3">
|
|
62
|
-
<svg class="w-4 h-4 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
63
|
-
<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"/>
|
|
64
|
-
</svg>
|
|
65
|
-
</div>
|
|
66
|
-
<div>
|
|
67
|
-
<p id="now-success-rate" class="text-lg font-bold text-gray-900 dark:text-gray-100"><%= now_strip[:success_rate] %>%</p>
|
|
68
|
-
<p class="text-xs text-gray-500 dark:text-gray-400">Success Rate</p>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|