ruby_llm-agents 0.3.6 → 0.5.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/README.md +46 -13
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +214 -0
- data/app/controllers/ruby_llm/agents/{settings_controller.rb → system_config_controller.rb} +3 -3
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +109 -0
- data/app/models/ruby_llm/agents/api_configuration.rb +386 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +62 -7
- data/app/views/layouts/ruby_llm/agents/application.html.erb +3 -1
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +34 -0
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +288 -0
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +95 -0
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +97 -0
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +211 -0
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +179 -0
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +1 -1
- data/app/views/ruby_llm/agents/executions/show.html.erb +82 -0
- data/app/views/ruby_llm/agents/{settings → system_config}/show.html.erb +1 -1
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +150 -0
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +13 -0
- data/app/views/ruby_llm/agents/tenants/index.html.erb +129 -0
- data/app/views/ruby_llm/agents/tenants/show.html.erb +374 -0
- data/config/routes.rb +12 -1
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +100 -0
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +90 -0
- data/lib/ruby_llm/agents/base/dsl.rb +65 -13
- data/lib/ruby_llm/agents/base/execution.rb +113 -6
- data/lib/ruby_llm/agents/base/reliability_dsl.rb +82 -0
- data/lib/ruby_llm/agents/base.rb +28 -0
- data/lib/ruby_llm/agents/budget_tracker.rb +285 -23
- data/lib/ruby_llm/agents/configuration.rb +38 -1
- data/lib/ruby_llm/agents/deprecations.rb +77 -0
- data/lib/ruby_llm/agents/engine.rb +1 -0
- data/lib/ruby_llm/agents/instrumentation.rb +71 -3
- data/lib/ruby_llm/agents/reliability/breaker_manager.rb +80 -0
- data/lib/ruby_llm/agents/reliability/execution_constraints.rb +69 -0
- data/lib/ruby_llm/agents/reliability/executor.rb +124 -0
- data/lib/ruby_llm/agents/reliability/fallback_routing.rb +72 -0
- data/lib/ruby_llm/agents/reliability/retry_strategy.rb +76 -0
- data/lib/ruby_llm/agents/resolved_config.rb +348 -0
- data/lib/ruby_llm/agents/result.rb +72 -2
- data/lib/ruby_llm/agents/version.rb +1 -1
- data/lib/ruby_llm/agents.rb +6 -0
- metadata +26 -3
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<%= render "ruby_llm/agents/shared/breadcrumbs", items: [
|
|
2
|
+
{ label: "Dashboard", path: ruby_llm_agents.root_path },
|
|
3
|
+
{ label: "API Configuration" }
|
|
4
|
+
] %>
|
|
5
|
+
|
|
6
|
+
<!-- Header -->
|
|
7
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
|
8
|
+
<div class="flex items-start justify-between">
|
|
9
|
+
<div class="flex items-center space-x-3">
|
|
10
|
+
<div class="flex-shrink-0 h-10 w-10 rounded-full bg-indigo-100 dark:bg-indigo-900/50 flex items-center justify-center">
|
|
11
|
+
<svg class="w-5 h-5 text-indigo-600 dark:text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
|
13
|
+
</svg>
|
|
14
|
+
</div>
|
|
15
|
+
<div>
|
|
16
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">API Configuration</h1>
|
|
17
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
18
|
+
Manage API keys and connection settings for LLM providers
|
|
19
|
+
</p>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div>
|
|
24
|
+
<%= link_to edit_api_configuration_path, class: "inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors" do %>
|
|
25
|
+
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
26
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
27
|
+
</svg>
|
|
28
|
+
Edit Configuration
|
|
29
|
+
<% end %>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<!-- Provider Status Grid -->
|
|
35
|
+
<div class="mb-6">
|
|
36
|
+
<h3 class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3">Provider Status</h3>
|
|
37
|
+
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
38
|
+
<% @provider_statuses.each do |provider| %>
|
|
39
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4">
|
|
40
|
+
<div class="flex items-center justify-between mb-2">
|
|
41
|
+
<h4 class="font-medium text-gray-900 dark:text-gray-100"><%= provider[:name] %></h4>
|
|
42
|
+
<% if provider[:configured] %>
|
|
43
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-200">
|
|
44
|
+
Active
|
|
45
|
+
</span>
|
|
46
|
+
<% else %>
|
|
47
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">
|
|
48
|
+
Not Set
|
|
49
|
+
</span>
|
|
50
|
+
<% end %>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<% if provider[:configured] %>
|
|
54
|
+
<div class="mb-2">
|
|
55
|
+
<code class="text-xs text-gray-600 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded font-mono">
|
|
56
|
+
<%= provider[:masked_key] %>
|
|
57
|
+
</code>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="flex items-center text-xs text-gray-500 dark:text-gray-400">
|
|
60
|
+
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
61
|
+
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd" />
|
|
62
|
+
</svg>
|
|
63
|
+
Source: <%= provider[:source].gsub("_", " ").titleize %>
|
|
64
|
+
</div>
|
|
65
|
+
<% else %>
|
|
66
|
+
<p class="text-xs text-gray-400 dark:text-gray-500">
|
|
67
|
+
Configure in settings or environment
|
|
68
|
+
</p>
|
|
69
|
+
<% end %>
|
|
70
|
+
|
|
71
|
+
<% if provider[:capabilities].present? %>
|
|
72
|
+
<div class="mt-2 flex flex-wrap gap-1">
|
|
73
|
+
<% provider[:capabilities].each do |cap| %>
|
|
74
|
+
<span class="text-xs px-1.5 py-0.5 bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded">
|
|
75
|
+
<%= cap %>
|
|
76
|
+
</span>
|
|
77
|
+
<% end %>
|
|
78
|
+
</div>
|
|
79
|
+
<% end %>
|
|
80
|
+
</div>
|
|
81
|
+
<% end %>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<!-- Configuration Details -->
|
|
86
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
87
|
+
<!-- Default Models -->
|
|
88
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
89
|
+
<h3 class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-4">Default Models</h3>
|
|
90
|
+
<div class="space-y-4">
|
|
91
|
+
<div class="flex justify-between items-center">
|
|
92
|
+
<div>
|
|
93
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">Chat Model</p>
|
|
94
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Default for chat completions</p>
|
|
95
|
+
</div>
|
|
96
|
+
<% if @resolved.default_model.present? %>
|
|
97
|
+
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-sm"><%= @resolved.default_model %></code>
|
|
98
|
+
<% else %>
|
|
99
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">Not set</span>
|
|
100
|
+
<% end %>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div class="flex justify-between items-center">
|
|
104
|
+
<div>
|
|
105
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">Embedding Model</p>
|
|
106
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Default for embeddings</p>
|
|
107
|
+
</div>
|
|
108
|
+
<% if @resolved.default_embedding_model.present? %>
|
|
109
|
+
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-sm"><%= @resolved.default_embedding_model %></code>
|
|
110
|
+
<% else %>
|
|
111
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">Not set</span>
|
|
112
|
+
<% end %>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div class="flex justify-between items-center">
|
|
116
|
+
<div>
|
|
117
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">Image Model</p>
|
|
118
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Default for image generation</p>
|
|
119
|
+
</div>
|
|
120
|
+
<% if @resolved.default_image_model.present? %>
|
|
121
|
+
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-sm"><%= @resolved.default_image_model %></code>
|
|
122
|
+
<% else %>
|
|
123
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">Not set</span>
|
|
124
|
+
<% end %>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<!-- Connection Settings -->
|
|
130
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
131
|
+
<h3 class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-4">Connection Settings</h3>
|
|
132
|
+
<div class="space-y-4">
|
|
133
|
+
<div class="flex justify-between items-center">
|
|
134
|
+
<div>
|
|
135
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">Request Timeout</p>
|
|
136
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Seconds before timing out</p>
|
|
137
|
+
</div>
|
|
138
|
+
<% if @resolved.request_timeout.present? %>
|
|
139
|
+
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-sm"><%= @resolved.request_timeout %>s</code>
|
|
140
|
+
<% else %>
|
|
141
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">Default</span>
|
|
142
|
+
<% end %>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div class="flex justify-between items-center">
|
|
146
|
+
<div>
|
|
147
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">Max Retries</p>
|
|
148
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Retry attempts on failure</p>
|
|
149
|
+
</div>
|
|
150
|
+
<% if @resolved.max_retries.present? %>
|
|
151
|
+
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-sm"><%= @resolved.max_retries %></code>
|
|
152
|
+
<% else %>
|
|
153
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">Default</span>
|
|
154
|
+
<% end %>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div class="flex justify-between items-center">
|
|
158
|
+
<div>
|
|
159
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">HTTP Proxy</p>
|
|
160
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Proxy for API requests</p>
|
|
161
|
+
</div>
|
|
162
|
+
<% if @resolved.http_proxy.present? %>
|
|
163
|
+
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-sm truncate max-w-[200px]"><%= @resolved.http_proxy %></code>
|
|
164
|
+
<% else %>
|
|
165
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">None</span>
|
|
166
|
+
<% end %>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<!-- Custom Endpoints -->
|
|
173
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
|
174
|
+
<h3 class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-4">Custom Endpoints</h3>
|
|
175
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
176
|
+
<% [
|
|
177
|
+
{ attr: :openai_api_base, label: "OpenAI Base URL" },
|
|
178
|
+
{ attr: :gemini_api_base, label: "Gemini Base URL" },
|
|
179
|
+
{ attr: :ollama_api_base, label: "Ollama Base URL" },
|
|
180
|
+
{ attr: :gpustack_api_base, label: "GPUStack Base URL" },
|
|
181
|
+
{ attr: :xai_api_base, label: "xAI Base URL" }
|
|
182
|
+
].each do |endpoint| %>
|
|
183
|
+
<div class="flex justify-between items-center">
|
|
184
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= endpoint[:label] %></p>
|
|
185
|
+
<% value = @resolved.send(endpoint[:attr]) %>
|
|
186
|
+
<% if value.present? %>
|
|
187
|
+
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-xs truncate max-w-[250px]"><%= value %></code>
|
|
188
|
+
<% else %>
|
|
189
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">Default</span>
|
|
190
|
+
<% end %>
|
|
191
|
+
</div>
|
|
192
|
+
<% end %>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<!-- Security Notice -->
|
|
197
|
+
<div class="bg-amber-50 dark:bg-amber-900/20 rounded-lg p-4 border border-amber-200 dark:border-amber-800">
|
|
198
|
+
<div class="flex">
|
|
199
|
+
<svg class="w-5 h-5 text-amber-400 mr-3 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
|
200
|
+
<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"/>
|
|
201
|
+
</svg>
|
|
202
|
+
<div>
|
|
203
|
+
<h4 class="text-sm font-medium text-amber-800 dark:text-amber-200">Security Note</h4>
|
|
204
|
+
<p class="text-sm text-amber-700 dark:text-amber-300 mt-1">
|
|
205
|
+
API keys stored in the database are encrypted at rest using Rails encrypted attributes.
|
|
206
|
+
For highest security, consider using environment variables for production deployments.
|
|
207
|
+
Database-stored keys are useful for multi-tenant configurations or dynamic key management.
|
|
208
|
+
</p>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
<%= render "ruby_llm/agents/shared/breadcrumbs", items: [
|
|
2
|
+
{ label: "Dashboard", path: ruby_llm_agents.root_path },
|
|
3
|
+
{ label: "Tenants", path: tenants_path },
|
|
4
|
+
{ label: @tenant_budget&.display_name || @tenant_id, path: @tenant_budget ? tenant_path(@tenant_budget) : nil },
|
|
5
|
+
{ label: "API Configuration" }
|
|
6
|
+
] %>
|
|
7
|
+
|
|
8
|
+
<!-- Header -->
|
|
9
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
|
10
|
+
<div class="flex items-start justify-between">
|
|
11
|
+
<div class="flex items-center space-x-3">
|
|
12
|
+
<div class="flex-shrink-0 h-10 w-10 rounded-full bg-indigo-100 dark:bg-indigo-900/50 flex items-center justify-center">
|
|
13
|
+
<svg class="w-5 h-5 text-indigo-600 dark:text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
14
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
|
15
|
+
</svg>
|
|
16
|
+
</div>
|
|
17
|
+
<div>
|
|
18
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
|
19
|
+
API Configuration
|
|
20
|
+
</h1>
|
|
21
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
22
|
+
Tenant: <span class="font-medium"><%= @tenant_budget&.display_name || @tenant_id %></span>
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
<% if @config.inherit_global_defaults %>
|
|
26
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-200">
|
|
27
|
+
Inheriting Global
|
|
28
|
+
</span>
|
|
29
|
+
<% end %>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="flex items-center space-x-3">
|
|
33
|
+
<% if @tenant_budget %>
|
|
34
|
+
<%= link_to tenant_path(@tenant_budget), class: "inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors" do %>
|
|
35
|
+
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
36
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
37
|
+
</svg>
|
|
38
|
+
Tenant Details
|
|
39
|
+
<% end %>
|
|
40
|
+
<% end %>
|
|
41
|
+
|
|
42
|
+
<%= link_to edit_tenant_api_configuration_path(@tenant_id), class: "inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors" do %>
|
|
43
|
+
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
44
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
45
|
+
</svg>
|
|
46
|
+
Edit Configuration
|
|
47
|
+
<% end %>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<!-- Provider Status Grid -->
|
|
53
|
+
<div class="mb-6">
|
|
54
|
+
<h3 class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3">Provider Status</h3>
|
|
55
|
+
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
56
|
+
<% @provider_statuses.each do |provider| %>
|
|
57
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4">
|
|
58
|
+
<div class="flex items-center justify-between mb-2">
|
|
59
|
+
<h4 class="font-medium text-gray-900 dark:text-gray-100"><%= provider[:name] %></h4>
|
|
60
|
+
<% if provider[:configured] %>
|
|
61
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-200">
|
|
62
|
+
Active
|
|
63
|
+
</span>
|
|
64
|
+
<% else %>
|
|
65
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">
|
|
66
|
+
Not Set
|
|
67
|
+
</span>
|
|
68
|
+
<% end %>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<% if provider[:configured] %>
|
|
72
|
+
<div class="mb-2">
|
|
73
|
+
<code class="text-xs text-gray-600 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded font-mono">
|
|
74
|
+
<%= provider[:masked_key] %>
|
|
75
|
+
</code>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="flex items-center text-xs text-gray-500 dark:text-gray-400">
|
|
78
|
+
<% source = provider[:source] %>
|
|
79
|
+
<% source_class = case source
|
|
80
|
+
when /^tenant:/ then "text-indigo-600 dark:text-indigo-400"
|
|
81
|
+
when "global_db" then "text-blue-600 dark:text-blue-400"
|
|
82
|
+
else "text-gray-500 dark:text-gray-400"
|
|
83
|
+
end %>
|
|
84
|
+
<svg class="w-3 h-3 mr-1 <%= source_class %>" fill="currentColor" viewBox="0 0 20 20">
|
|
85
|
+
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd" />
|
|
86
|
+
</svg>
|
|
87
|
+
<span class="<%= source_class %>">
|
|
88
|
+
<%= case source
|
|
89
|
+
when /^tenant:/ then "Tenant Override"
|
|
90
|
+
when "global_db" then "Global DB"
|
|
91
|
+
when "ruby_llm_config" then "Config File"
|
|
92
|
+
else "Unknown"
|
|
93
|
+
end %>
|
|
94
|
+
</span>
|
|
95
|
+
</div>
|
|
96
|
+
<% else %>
|
|
97
|
+
<p class="text-xs text-gray-400 dark:text-gray-500">
|
|
98
|
+
Not configured
|
|
99
|
+
</p>
|
|
100
|
+
<% end %>
|
|
101
|
+
</div>
|
|
102
|
+
<% end %>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<!-- Configuration Details -->
|
|
107
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
108
|
+
<!-- Default Models -->
|
|
109
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
110
|
+
<h3 class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-4">Default Models</h3>
|
|
111
|
+
<div class="space-y-4">
|
|
112
|
+
<% [
|
|
113
|
+
{ attr: :default_model, label: "Chat Model", desc: "Default for chat completions" },
|
|
114
|
+
{ attr: :default_embedding_model, label: "Embedding Model", desc: "Default for embeddings" },
|
|
115
|
+
{ attr: :default_image_model, label: "Image Model", desc: "Default for image generation" }
|
|
116
|
+
].each do |model_info| %>
|
|
117
|
+
<div class="flex justify-between items-center">
|
|
118
|
+
<div>
|
|
119
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= model_info[:label] %></p>
|
|
120
|
+
<p class="text-xs text-gray-500 dark:text-gray-400"><%= model_info[:desc] %></p>
|
|
121
|
+
</div>
|
|
122
|
+
<% value = @resolved.send(model_info[:attr]) %>
|
|
123
|
+
<% source = @resolved.source_for(model_info[:attr]) %>
|
|
124
|
+
<% if value.present? %>
|
|
125
|
+
<div class="text-right">
|
|
126
|
+
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-sm"><%= value %></code>
|
|
127
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1"><%= source.gsub("_", " ").titleize %></p>
|
|
128
|
+
</div>
|
|
129
|
+
<% else %>
|
|
130
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">Not set</span>
|
|
131
|
+
<% end %>
|
|
132
|
+
</div>
|
|
133
|
+
<% end %>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<!-- Connection Settings -->
|
|
138
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
139
|
+
<h3 class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-4">Connection Settings</h3>
|
|
140
|
+
<div class="space-y-4">
|
|
141
|
+
<% [
|
|
142
|
+
{ attr: :request_timeout, label: "Request Timeout", suffix: "s" },
|
|
143
|
+
{ attr: :max_retries, label: "Max Retries" },
|
|
144
|
+
{ attr: :http_proxy, label: "HTTP Proxy" }
|
|
145
|
+
].each do |setting| %>
|
|
146
|
+
<div class="flex justify-between items-center">
|
|
147
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= setting[:label] %></p>
|
|
148
|
+
<% value = @resolved.send(setting[:attr]) %>
|
|
149
|
+
<% if value.present? %>
|
|
150
|
+
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-sm"><%= value %><%= setting[:suffix] %></code>
|
|
151
|
+
<% else %>
|
|
152
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">Default</span>
|
|
153
|
+
<% end %>
|
|
154
|
+
</div>
|
|
155
|
+
<% end %>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<!-- Inheritance Info -->
|
|
161
|
+
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4 border border-blue-200 dark:border-blue-800">
|
|
162
|
+
<div class="flex">
|
|
163
|
+
<svg class="w-5 h-5 text-blue-400 mr-3 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
|
164
|
+
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
|
|
165
|
+
</svg>
|
|
166
|
+
<div>
|
|
167
|
+
<h4 class="text-sm font-medium text-blue-800 dark:text-blue-200">Configuration Resolution</h4>
|
|
168
|
+
<p class="text-sm text-blue-700 dark:text-blue-300 mt-1">
|
|
169
|
+
<% if @config.inherit_global_defaults %>
|
|
170
|
+
This tenant inherits unset values from the global configuration.
|
|
171
|
+
Values shown with "Global DB" or "Config File" source will be used unless overridden.
|
|
172
|
+
<% else %>
|
|
173
|
+
This tenant does <strong>not</strong> inherit from global configuration.
|
|
174
|
+
Only values explicitly set for this tenant will be used.
|
|
175
|
+
<% end %>
|
|
176
|
+
</p>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
</p>
|
|
40
40
|
</div>
|
|
41
41
|
</div>
|
|
42
|
-
<%= link_to "Adjust", ruby_llm_agents.
|
|
42
|
+
<%= link_to "Adjust", ruby_llm_agents.system_config_path, class: "text-xs text-red-600 dark:text-red-400 hover:underline font-medium ml-5 sm:ml-0" %>
|
|
43
43
|
|
|
44
44
|
<% when :error_spike %>
|
|
45
45
|
<div class="flex items-center">
|
|
@@ -743,6 +743,88 @@
|
|
|
743
743
|
</div>
|
|
744
744
|
<% end %>
|
|
745
745
|
|
|
746
|
+
<!-- Conversation Context -->
|
|
747
|
+
<% if @execution.respond_to?(:messages_count) && @execution.messages_count.to_i > 0 %>
|
|
748
|
+
<%
|
|
749
|
+
messages_summary = @execution.messages_summary || {}
|
|
750
|
+
# Handle both string and symbol keys
|
|
751
|
+
first_message = messages_summary["first"] || messages_summary[:first]
|
|
752
|
+
last_message = messages_summary["last"] || messages_summary[:last]
|
|
753
|
+
max_len = RubyLLM::Agents.configuration.messages_summary_max_length || 500
|
|
754
|
+
%>
|
|
755
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
|
|
756
|
+
<div class="flex items-center gap-2 mb-4">
|
|
757
|
+
<svg class="w-5 h-5 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
758
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
|
|
759
|
+
</svg>
|
|
760
|
+
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Conversation Context</h3>
|
|
761
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-indigo-100 dark:bg-indigo-900/50 text-indigo-800 dark:text-indigo-300">
|
|
762
|
+
<%= @execution.messages_count %> message<%= @execution.messages_count == 1 ? '' : 's' %>
|
|
763
|
+
</span>
|
|
764
|
+
</div>
|
|
765
|
+
|
|
766
|
+
<div class="space-y-4">
|
|
767
|
+
<% if first_message %>
|
|
768
|
+
<%
|
|
769
|
+
first_role = first_message["role"] || first_message[:role] || "unknown"
|
|
770
|
+
first_content = first_message["content"] || first_message[:content] || ""
|
|
771
|
+
first_truncated = first_content.length >= max_len
|
|
772
|
+
role_badge_class = case first_role.to_s
|
|
773
|
+
when "user" then "bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300"
|
|
774
|
+
when "assistant" then "bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300"
|
|
775
|
+
when "system" then "bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300"
|
|
776
|
+
else "bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300"
|
|
777
|
+
end
|
|
778
|
+
%>
|
|
779
|
+
<div class="border border-gray-100 dark:border-gray-700 rounded-lg p-4">
|
|
780
|
+
<div class="flex items-center gap-2 mb-2">
|
|
781
|
+
<span class="text-xs text-gray-500 dark:text-gray-400 font-medium">First Message</span>
|
|
782
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium <%= role_badge_class %>">
|
|
783
|
+
<%= first_role %>
|
|
784
|
+
</span>
|
|
785
|
+
<% if first_truncated %>
|
|
786
|
+
<span class="text-xs text-gray-400 dark:text-gray-500 italic">(truncated)</span>
|
|
787
|
+
<% end %>
|
|
788
|
+
</div>
|
|
789
|
+
<p class="text-sm text-gray-700 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded-lg p-3 whitespace-pre-wrap break-words"><%= first_content %></p>
|
|
790
|
+
</div>
|
|
791
|
+
<% end %>
|
|
792
|
+
|
|
793
|
+
<% if last_message %>
|
|
794
|
+
<%
|
|
795
|
+
last_role = last_message["role"] || last_message[:role] || "unknown"
|
|
796
|
+
last_content = last_message["content"] || last_message[:content] || ""
|
|
797
|
+
last_truncated = last_content.length >= max_len
|
|
798
|
+
role_badge_class = case last_role.to_s
|
|
799
|
+
when "user" then "bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300"
|
|
800
|
+
when "assistant" then "bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300"
|
|
801
|
+
when "system" then "bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300"
|
|
802
|
+
else "bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300"
|
|
803
|
+
end
|
|
804
|
+
%>
|
|
805
|
+
<div class="border border-gray-100 dark:border-gray-700 rounded-lg p-4">
|
|
806
|
+
<div class="flex items-center gap-2 mb-2">
|
|
807
|
+
<span class="text-xs text-gray-500 dark:text-gray-400 font-medium">Last Message</span>
|
|
808
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium <%= role_badge_class %>">
|
|
809
|
+
<%= last_role %>
|
|
810
|
+
</span>
|
|
811
|
+
<% if last_truncated %>
|
|
812
|
+
<span class="text-xs text-gray-400 dark:text-gray-500 italic">(truncated)</span>
|
|
813
|
+
<% end %>
|
|
814
|
+
</div>
|
|
815
|
+
<p class="text-sm text-gray-700 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded-lg p-3 whitespace-pre-wrap break-words"><%= last_content %></p>
|
|
816
|
+
</div>
|
|
817
|
+
<% end %>
|
|
818
|
+
|
|
819
|
+
<% if @execution.messages_count > 2 %>
|
|
820
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 text-center">
|
|
821
|
+
+ <%= @execution.messages_count - 2 %> more message<%= @execution.messages_count - 2 == 1 ? '' : 's' %> in between
|
|
822
|
+
</p>
|
|
823
|
+
<% end %>
|
|
824
|
+
</div>
|
|
825
|
+
</div>
|
|
826
|
+
<% end %>
|
|
827
|
+
|
|
746
828
|
<!-- Diagnostics Panel -->
|
|
747
829
|
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
|
|
748
830
|
<div class="flex items-center justify-between mb-4">
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
8
8
|
</svg>
|
|
9
9
|
<div>
|
|
10
|
-
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
|
10
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">System Configuration</h1>
|
|
11
11
|
<p class="text-sm text-gray-500 dark:text-gray-400">Settings configured in <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded">config/initializers/ruby_llm_agents.rb</code></p>
|
|
12
12
|
</div>
|
|
13
13
|
</div>
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<%# Form partial for tenant budget editing %>
|
|
2
|
+
<%= form_with model: tenant, url: tenant_path(tenant), method: :patch, class: "space-y-6" do |f| %>
|
|
3
|
+
<% if tenant.errors.any? %>
|
|
4
|
+
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
|
|
5
|
+
<div class="flex items-center">
|
|
6
|
+
<svg class="w-5 h-5 text-red-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
7
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
8
|
+
</svg>
|
|
9
|
+
<h3 class="text-sm font-medium text-red-800 dark:text-red-200">
|
|
10
|
+
Please fix the following errors:
|
|
11
|
+
</h3>
|
|
12
|
+
</div>
|
|
13
|
+
<ul class="mt-2 ml-7 list-disc list-inside text-sm text-red-700 dark:text-red-300">
|
|
14
|
+
<% tenant.errors.full_messages.each do |message| %>
|
|
15
|
+
<li><%= message %></li>
|
|
16
|
+
<% end %>
|
|
17
|
+
</ul>
|
|
18
|
+
</div>
|
|
19
|
+
<% end %>
|
|
20
|
+
|
|
21
|
+
<!-- Basic Information -->
|
|
22
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
23
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Basic Information</h3>
|
|
24
|
+
|
|
25
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
26
|
+
<div>
|
|
27
|
+
<%= f.label :name, "Display Name", class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
|
|
28
|
+
<%= f.text_field :name,
|
|
29
|
+
class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100",
|
|
30
|
+
placeholder: "e.g., Acme Corporation" %>
|
|
31
|
+
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
32
|
+
Human-readable name for this tenant. Falls back to tenant ID if not set.
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div>
|
|
37
|
+
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Tenant ID</label>
|
|
38
|
+
<p class="w-full px-3 py-2 bg-gray-50 dark:bg-gray-700/50 border border-gray-200 dark:border-gray-600 rounded-md text-gray-900 dark:text-gray-100 font-mono text-sm">
|
|
39
|
+
<%= tenant.tenant_id %>
|
|
40
|
+
</p>
|
|
41
|
+
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
42
|
+
System identifier. Cannot be changed.
|
|
43
|
+
</p>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<!-- Budget Limits (USD) -->
|
|
49
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
50
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Budget Limits (USD)</h3>
|
|
51
|
+
|
|
52
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
53
|
+
<div>
|
|
54
|
+
<%= f.label :daily_limit, "Daily Limit", class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
|
|
55
|
+
<div class="relative">
|
|
56
|
+
<span class="absolute inset-y-0 left-0 pl-3 flex items-center text-gray-500 dark:text-gray-400">$</span>
|
|
57
|
+
<%= f.number_field :daily_limit,
|
|
58
|
+
class: "w-full pl-7 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100",
|
|
59
|
+
step: "0.01",
|
|
60
|
+
min: "0",
|
|
61
|
+
placeholder: "e.g., 50.00" %>
|
|
62
|
+
</div>
|
|
63
|
+
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
64
|
+
Maximum daily spend in USD. Leave blank for no limit.
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div>
|
|
69
|
+
<%= f.label :monthly_limit, "Monthly Limit", class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
|
|
70
|
+
<div class="relative">
|
|
71
|
+
<span class="absolute inset-y-0 left-0 pl-3 flex items-center text-gray-500 dark:text-gray-400">$</span>
|
|
72
|
+
<%= f.number_field :monthly_limit,
|
|
73
|
+
class: "w-full pl-7 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100",
|
|
74
|
+
step: "0.01",
|
|
75
|
+
min: "0",
|
|
76
|
+
placeholder: "e.g., 500.00" %>
|
|
77
|
+
</div>
|
|
78
|
+
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
79
|
+
Maximum monthly spend in USD. Leave blank for no limit.
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<!-- Token Limits -->
|
|
86
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
87
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Token Limits</h3>
|
|
88
|
+
|
|
89
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
90
|
+
<div>
|
|
91
|
+
<%= f.label :daily_token_limit, "Daily Token Limit", class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
|
|
92
|
+
<%= f.number_field :daily_token_limit,
|
|
93
|
+
class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100",
|
|
94
|
+
step: "1",
|
|
95
|
+
min: "0",
|
|
96
|
+
placeholder: "e.g., 1000000" %>
|
|
97
|
+
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
98
|
+
Maximum daily token usage. Leave blank for no limit.
|
|
99
|
+
</p>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div>
|
|
103
|
+
<%= f.label :monthly_token_limit, "Monthly Token Limit", class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
|
|
104
|
+
<%= f.number_field :monthly_token_limit,
|
|
105
|
+
class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100",
|
|
106
|
+
step: "1",
|
|
107
|
+
min: "0",
|
|
108
|
+
placeholder: "e.g., 10000000" %>
|
|
109
|
+
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
110
|
+
Maximum monthly token usage. Leave blank for no limit.
|
|
111
|
+
</p>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<!-- Enforcement -->
|
|
117
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
118
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Enforcement</h3>
|
|
119
|
+
|
|
120
|
+
<div>
|
|
121
|
+
<%= f.label :enforcement, "Enforcement Mode", class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
|
|
122
|
+
<%= f.select :enforcement,
|
|
123
|
+
options_for_select([
|
|
124
|
+
["None - No enforcement", "none"],
|
|
125
|
+
["Soft - Log warnings but allow requests", "soft"],
|
|
126
|
+
["Hard - Block requests when limits exceeded", "hard"]
|
|
127
|
+
], tenant.enforcement || "soft"),
|
|
128
|
+
{},
|
|
129
|
+
class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100" %>
|
|
130
|
+
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
131
|
+
Controls what happens when budget limits are exceeded.
|
|
132
|
+
</p>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<div class="mt-4 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
|
|
136
|
+
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Enforcement Mode Details</h4>
|
|
137
|
+
<ul class="text-xs text-gray-600 dark:text-gray-400 space-y-1">
|
|
138
|
+
<li><strong>None:</strong> Budget tracking only, no enforcement actions taken.</li>
|
|
139
|
+
<li><strong>Soft:</strong> Logs warnings and triggers alerts when limits are exceeded, but allows requests to proceed.</li>
|
|
140
|
+
<li><strong>Hard:</strong> Blocks agent executions when budget limits are exceeded. Use with caution.</li>
|
|
141
|
+
</ul>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<!-- Actions -->
|
|
146
|
+
<div class="flex items-center justify-end space-x-4">
|
|
147
|
+
<%= link_to "Cancel", tenant_path(tenant), class: "px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors" %>
|
|
148
|
+
<%= f.submit "Save Changes", class: "px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer transition-colors" %>
|
|
149
|
+
</div>
|
|
150
|
+
<% end %>
|