ruby_llm-agents 0.4.0 → 1.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +225 -34
  3. data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
  4. data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +214 -0
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
  6. data/app/controllers/ruby_llm/agents/{settings_controller.rb → system_config_controller.rb} +3 -3
  7. data/app/controllers/ruby_llm/agents/tenants_controller.rb +109 -0
  8. data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
  9. data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
  10. data/app/models/ruby_llm/agents/api_configuration.rb +386 -0
  11. data/app/models/ruby_llm/agents/execution.rb +3 -0
  12. data/app/models/ruby_llm/agents/tenant_budget.rb +112 -14
  13. data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
  14. data/app/views/layouts/ruby_llm/agents/application.html.erb +5 -30
  15. data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
  16. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
  17. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
  18. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
  19. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
  20. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
  21. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
  22. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
  23. data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
  24. data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
  25. data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +34 -0
  26. data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +288 -0
  27. data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +95 -0
  28. data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +97 -0
  29. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +211 -0
  30. data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +179 -0
  31. data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +1 -1
  32. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
  33. data/app/views/ruby_llm/agents/executions/show.html.erb +98 -0
  34. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
  35. data/app/views/ruby_llm/agents/{settings → system_config}/show.html.erb +1 -1
  36. data/app/views/ruby_llm/agents/tenants/_form.html.erb +150 -0
  37. data/app/views/ruby_llm/agents/tenants/edit.html.erb +13 -0
  38. data/app/views/ruby_llm/agents/tenants/index.html.erb +129 -0
  39. data/app/views/ruby_llm/agents/tenants/show.html.erb +374 -0
  40. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
  41. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
  42. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
  43. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
  44. data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
  45. data/config/routes.rb +13 -1
  46. data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
  47. data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +100 -0
  48. data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
  49. data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
  50. data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
  51. data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
  52. data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
  53. data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
  54. data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
  55. data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
  56. data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
  57. data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
  58. data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
  59. data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
  60. data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
  61. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
  62. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
  63. data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
  64. data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
  65. data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
  66. data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
  67. data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
  68. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
  69. data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
  70. data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
  71. data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
  72. data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
  73. data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
  74. data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
  75. data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +90 -0
  76. data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
  77. data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
  78. data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
  79. data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
  80. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
  81. data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
  82. data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
  83. data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
  84. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
  85. data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
  86. data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
  87. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
  88. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
  89. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
  90. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
  91. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
  92. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
  93. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
  94. data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
  95. data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
  96. data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
  97. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
  98. data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
  99. data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
  100. data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
  101. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
  102. data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
  103. data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
  104. data/lib/ruby_llm/agents/base_agent.rb +675 -0
  105. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
  106. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
  107. data/lib/ruby_llm/agents/core/base.rb +135 -0
  108. data/lib/ruby_llm/agents/core/configuration.rb +981 -0
  109. data/lib/ruby_llm/agents/core/errors.rb +150 -0
  110. data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +93 -4
  111. data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
  112. data/lib/ruby_llm/agents/core/resolved_config.rb +348 -0
  113. data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
  114. data/lib/ruby_llm/agents/dsl/base.rb +110 -0
  115. data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
  116. data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
  117. data/lib/ruby_llm/agents/dsl.rb +41 -0
  118. data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
  119. data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
  120. data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
  121. data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
  122. data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
  123. data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
  124. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
  125. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
  126. data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
  127. data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
  128. data/lib/ruby_llm/agents/image/editor.rb +92 -0
  129. data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
  130. data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
  131. data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
  132. data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
  133. data/lib/ruby_llm/agents/image/generator.rb +455 -0
  134. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
  135. data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
  136. data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
  137. data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
  138. data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
  139. data/lib/ruby_llm/agents/image/transformer.rb +95 -0
  140. data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
  141. data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
  142. data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
  143. data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
  144. data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
  145. data/lib/ruby_llm/agents/image/variator.rb +80 -0
  146. data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
  147. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
  148. data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
  149. data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
  150. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
  151. data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
  152. data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
  153. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
  154. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
  155. data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
  156. data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
  157. data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
  158. data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
  159. data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
  160. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
  161. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
  162. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
  163. data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
  164. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
  165. data/lib/ruby_llm/agents/pipeline.rb +68 -0
  166. data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -10
  167. data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
  168. data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
  169. data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
  170. data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
  171. data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
  172. data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
  173. data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
  174. data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
  175. data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
  176. data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
  177. data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
  178. data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
  179. data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
  180. data/lib/ruby_llm/agents/text/embedder.rb +444 -0
  181. data/lib/ruby_llm/agents/text/moderator.rb +237 -0
  182. data/lib/ruby_llm/agents/workflow/async.rb +220 -0
  183. data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
  184. data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
  185. data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
  186. data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
  187. data/lib/ruby_llm/agents.rb +86 -20
  188. metadata +189 -35
  189. data/lib/ruby_llm/agents/base/caching.rb +0 -40
  190. data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
  191. data/lib/ruby_llm/agents/base/dsl.rb +0 -324
  192. data/lib/ruby_llm/agents/base/execution.rb +0 -283
  193. data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
  194. data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
  195. data/lib/ruby_llm/agents/base/response_building.rb +0 -86
  196. data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
  197. data/lib/ruby_llm/agents/base.rb +0 -209
  198. data/lib/ruby_llm/agents/budget_tracker.rb +0 -471
  199. data/lib/ruby_llm/agents/configuration.rb +0 -357
  200. /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
  201. /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
  202. /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
  203. /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
  204. /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
  205. /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
  206. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
  207. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
  208. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
@@ -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.settings_path, class: "text-xs text-red-600 dark:text-red-400 hover:underline font-medium ml-5 sm:ml-0" %>
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">