rubyllm-observ 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.
Files changed (209) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +778 -0
  3. data/Rakefile +49 -0
  4. data/app/assets/javascripts/observ/application.js +12 -0
  5. data/app/assets/javascripts/observ/controllers/autoscroll_controller.js +33 -0
  6. data/app/assets/javascripts/observ/controllers/chat_form_controller.js +93 -0
  7. data/app/assets/javascripts/observ/controllers/copy_controller.js +43 -0
  8. data/app/assets/javascripts/observ/controllers/dashboard_controller.js +58 -0
  9. data/app/assets/javascripts/observ/controllers/drawer_controller.js +58 -0
  10. data/app/assets/javascripts/observ/controllers/expandable_controller.js +33 -0
  11. data/app/assets/javascripts/observ/controllers/filter_controller.js +36 -0
  12. data/app/assets/javascripts/observ/controllers/index.js +52 -0
  13. data/app/assets/javascripts/observ/controllers/json_viewer_controller.js +260 -0
  14. data/app/assets/javascripts/observ/controllers/message_form_controller.js +58 -0
  15. data/app/assets/javascripts/observ/controllers/prompt_variables_controller.js +64 -0
  16. data/app/assets/javascripts/observ/controllers/text_select_controller.js +14 -0
  17. data/app/assets/stylesheets/observ/_annotations.scss +127 -0
  18. data/app/assets/stylesheets/observ/_card.scss +52 -0
  19. data/app/assets/stylesheets/observ/_chat.scss +156 -0
  20. data/app/assets/stylesheets/observ/_components.scss +460 -0
  21. data/app/assets/stylesheets/observ/_dashboard.scss +40 -0
  22. data/app/assets/stylesheets/observ/_datasets.scss +697 -0
  23. data/app/assets/stylesheets/observ/_drawer.scss +273 -0
  24. data/app/assets/stylesheets/observ/_json_viewer.scss +120 -0
  25. data/app/assets/stylesheets/observ/_layout.scss +256 -0
  26. data/app/assets/stylesheets/observ/_metrics.scss +99 -0
  27. data/app/assets/stylesheets/observ/_observations.scss +160 -0
  28. data/app/assets/stylesheets/observ/_pagination.scss +143 -0
  29. data/app/assets/stylesheets/observ/_prompts.scss +365 -0
  30. data/app/assets/stylesheets/observ/_table.scss +53 -0
  31. data/app/assets/stylesheets/observ/_variables.scss +53 -0
  32. data/app/assets/stylesheets/observ/application.scss +15 -0
  33. data/app/controllers/observ/annotations_controller.rb +144 -0
  34. data/app/controllers/observ/application_controller.rb +8 -0
  35. data/app/controllers/observ/chats_controller.rb +58 -0
  36. data/app/controllers/observ/dashboard_controller.rb +159 -0
  37. data/app/controllers/observ/dataset_items_controller.rb +85 -0
  38. data/app/controllers/observ/dataset_run_items_controller.rb +84 -0
  39. data/app/controllers/observ/dataset_runs_controller.rb +110 -0
  40. data/app/controllers/observ/datasets_controller.rb +74 -0
  41. data/app/controllers/observ/messages_controller.rb +26 -0
  42. data/app/controllers/observ/observations_controller.rb +59 -0
  43. data/app/controllers/observ/prompt_versions_controller.rb +148 -0
  44. data/app/controllers/observ/prompts_controller.rb +205 -0
  45. data/app/controllers/observ/sessions_controller.rb +45 -0
  46. data/app/controllers/observ/traces_controller.rb +86 -0
  47. data/app/forms/observ/prompt_form.rb +96 -0
  48. data/app/helpers/observ/application_helper.rb +9 -0
  49. data/app/helpers/observ/chats_helper.rb +47 -0
  50. data/app/helpers/observ/dashboard_helper.rb +154 -0
  51. data/app/helpers/observ/datasets_helper.rb +62 -0
  52. data/app/helpers/observ/pagination_helper.rb +38 -0
  53. data/app/jobs/observ/application_job.rb +4 -0
  54. data/app/jobs/observ/dataset_runner_job.rb +49 -0
  55. data/app/mailers/observ/application_mailer.rb +6 -0
  56. data/app/models/concerns/observ/agent_phaseable.rb +124 -0
  57. data/app/models/concerns/observ/agent_selectable.rb +50 -0
  58. data/app/models/concerns/observ/chat_enhancements.rb +109 -0
  59. data/app/models/concerns/observ/message_enhancements.rb +31 -0
  60. data/app/models/concerns/observ/observability_instrumentation.rb +124 -0
  61. data/app/models/concerns/observ/prompt_management.rb +320 -0
  62. data/app/models/concerns/observ/trace_association.rb +9 -0
  63. data/app/models/observ/annotation.rb +23 -0
  64. data/app/models/observ/application_record.rb +5 -0
  65. data/app/models/observ/dataset.rb +51 -0
  66. data/app/models/observ/dataset_item.rb +41 -0
  67. data/app/models/observ/dataset_run.rb +104 -0
  68. data/app/models/observ/dataset_run_item.rb +111 -0
  69. data/app/models/observ/generation.rb +56 -0
  70. data/app/models/observ/null_prompt.rb +59 -0
  71. data/app/models/observ/observation.rb +38 -0
  72. data/app/models/observ/prompt.rb +315 -0
  73. data/app/models/observ/score.rb +51 -0
  74. data/app/models/observ/session.rb +131 -0
  75. data/app/models/observ/span.rb +13 -0
  76. data/app/models/observ/trace.rb +135 -0
  77. data/app/presenters/observ/agent_select_presenter.rb +59 -0
  78. data/app/services/observ/agent_executor_service.rb +174 -0
  79. data/app/services/observ/agent_provider.rb +60 -0
  80. data/app/services/observ/agent_selection_service.rb +53 -0
  81. data/app/services/observ/chat_instrumenter.rb +523 -0
  82. data/app/services/observ/dataset_runner_service.rb +153 -0
  83. data/app/services/observ/evaluator_runner_service.rb +58 -0
  84. data/app/services/observ/evaluators/base_evaluator.rb +51 -0
  85. data/app/services/observ/evaluators/contains_evaluator.rb +53 -0
  86. data/app/services/observ/evaluators/exact_match_evaluator.rb +23 -0
  87. data/app/services/observ/evaluators/json_structure_evaluator.rb +44 -0
  88. data/app/services/observ/prompt_manager/cache_statistics.rb +82 -0
  89. data/app/services/observ/prompt_manager/caching.rb +167 -0
  90. data/app/services/observ/prompt_manager/comparison.rb +49 -0
  91. data/app/services/observ/prompt_manager/version_management.rb +96 -0
  92. data/app/services/observ/prompt_manager.rb +40 -0
  93. data/app/services/observ/trace_text_formatter.rb +349 -0
  94. data/app/validators/observ/prompt_config_validator.rb +187 -0
  95. data/app/views/kaminari/_first_page.html.erb +11 -0
  96. data/app/views/kaminari/_gap.html.erb +8 -0
  97. data/app/views/kaminari/_last_page.html.erb +11 -0
  98. data/app/views/kaminari/_next_page.html.erb +11 -0
  99. data/app/views/kaminari/_page.html.erb +12 -0
  100. data/app/views/kaminari/_paginator.html.erb +25 -0
  101. data/app/views/kaminari/_prev_page.html.erb +11 -0
  102. data/app/views/kaminari/observ/_first_page.html.erb +11 -0
  103. data/app/views/kaminari/observ/_gap.html.erb +8 -0
  104. data/app/views/kaminari/observ/_last_page.html.erb +11 -0
  105. data/app/views/kaminari/observ/_next_page.html.erb +11 -0
  106. data/app/views/kaminari/observ/_page.html.erb +12 -0
  107. data/app/views/kaminari/observ/_paginator.html.erb +25 -0
  108. data/app/views/kaminari/observ/_prev_page.html.erb +11 -0
  109. data/app/views/layouts/observ/application.html.erb +88 -0
  110. data/app/views/observ/annotations/_annotation.html.erb +13 -0
  111. data/app/views/observ/annotations/_form.html.erb +28 -0
  112. data/app/views/observ/annotations/index.html.erb +28 -0
  113. data/app/views/observ/annotations/sessions_index.html.erb +48 -0
  114. data/app/views/observ/annotations/traces_index.html.erb +48 -0
  115. data/app/views/observ/chats/_form.html.erb +45 -0
  116. data/app/views/observ/chats/index.html.erb +67 -0
  117. data/app/views/observ/chats/new.html.erb +17 -0
  118. data/app/views/observ/chats/show.html.erb +34 -0
  119. data/app/views/observ/dashboard/index.html.erb +236 -0
  120. data/app/views/observ/dataset_items/_form.html.erb +49 -0
  121. data/app/views/observ/dataset_items/edit.html.erb +18 -0
  122. data/app/views/observ/dataset_items/index.html.erb +95 -0
  123. data/app/views/observ/dataset_items/new.html.erb +18 -0
  124. data/app/views/observ/dataset_run_items/_score_close_drawer.html.erb +4 -0
  125. data/app/views/observ/dataset_run_items/_score_drawer.html.erb +75 -0
  126. data/app/views/observ/dataset_run_items/_score_success.html.erb +29 -0
  127. data/app/views/observ/dataset_run_items/_scores_cell.html.erb +19 -0
  128. data/app/views/observ/dataset_run_items/details_drawer.turbo_stream.erb +80 -0
  129. data/app/views/observ/dataset_run_items/score_drawer.turbo_stream.erb +7 -0
  130. data/app/views/observ/dataset_runs/index.html.erb +108 -0
  131. data/app/views/observ/dataset_runs/new.html.erb +57 -0
  132. data/app/views/observ/dataset_runs/review.html.erb +155 -0
  133. data/app/views/observ/dataset_runs/show.html.erb +166 -0
  134. data/app/views/observ/datasets/_form.html.erb +62 -0
  135. data/app/views/observ/datasets/_items_tab.html.erb +66 -0
  136. data/app/views/observ/datasets/_runs_tab.html.erb +82 -0
  137. data/app/views/observ/datasets/edit.html.erb +32 -0
  138. data/app/views/observ/datasets/index.html.erb +105 -0
  139. data/app/views/observ/datasets/new.html.erb +18 -0
  140. data/app/views/observ/datasets/show.html.erb +67 -0
  141. data/app/views/observ/messages/_content.html.erb +1 -0
  142. data/app/views/observ/messages/_form.html.erb +33 -0
  143. data/app/views/observ/messages/_message.html.erb +14 -0
  144. data/app/views/observ/messages/_tool_calls.html.erb +10 -0
  145. data/app/views/observ/messages/create.turbo_stream.erb +9 -0
  146. data/app/views/observ/observations/index.html.erb +97 -0
  147. data/app/views/observ/observations/show_generation.html.erb +195 -0
  148. data/app/views/observ/observations/show_span.html.erb +93 -0
  149. data/app/views/observ/prompts/_diff_content.html.erb +16 -0
  150. data/app/views/observ/prompts/_form.html.erb +111 -0
  151. data/app/views/observ/prompts/_new_form.html.erb +102 -0
  152. data/app/views/observ/prompts/_prompt_actions.html.erb +4 -0
  153. data/app/views/observ/prompts/_prompt_content_highlighted.html.erb +4 -0
  154. data/app/views/observ/prompts/_version_actions.html.erb +40 -0
  155. data/app/views/observ/prompts/compare.html.erb +155 -0
  156. data/app/views/observ/prompts/edit.html.erb +17 -0
  157. data/app/views/observ/prompts/index.html.erb +108 -0
  158. data/app/views/observ/prompts/new.html.erb +17 -0
  159. data/app/views/observ/prompts/show.html.erb +138 -0
  160. data/app/views/observ/prompts/versions.html.erb +87 -0
  161. data/app/views/observ/sessions/annotations_drawer.turbo_stream.erb +25 -0
  162. data/app/views/observ/sessions/drawer_test.turbo_stream.erb +49 -0
  163. data/app/views/observ/sessions/index.html.erb +91 -0
  164. data/app/views/observ/sessions/show.html.erb +251 -0
  165. data/app/views/observ/traces/add_to_dataset_drawer.turbo_stream.erb +48 -0
  166. data/app/views/observ/traces/annotations_drawer.turbo_stream.erb +25 -0
  167. data/app/views/observ/traces/index.html.erb +87 -0
  168. data/app/views/observ/traces/show.html.erb +285 -0
  169. data/app/views/observ/traces/text_output_drawer.turbo_stream.erb +48 -0
  170. data/app/views/shared/_drawer.html.erb +26 -0
  171. data/config/routes.rb +80 -0
  172. data/db/migrate/001_create_observ_sessions.rb +21 -0
  173. data/db/migrate/002_create_observ_traces.rb +25 -0
  174. data/db/migrate/003_create_observ_observations.rb +42 -0
  175. data/db/migrate/004_add_message_id_to_observ_traces.rb +7 -0
  176. data/db/migrate/005_create_observ_prompts.rb +21 -0
  177. data/db/migrate/006_fix_prompt_config_strings.rb +23 -0
  178. data/db/migrate/007_create_observ_annotations.rb +12 -0
  179. data/db/migrate/009_add_prompt_fields_to_observ_chats.rb +11 -0
  180. data/db/migrate/010_create_observ_datasets.rb +15 -0
  181. data/db/migrate/011_create_observ_dataset_items.rb +17 -0
  182. data/db/migrate/012_create_observ_dataset_runs.rb +22 -0
  183. data/db/migrate/013_create_observ_dataset_run_items.rb +16 -0
  184. data/db/migrate/014_create_observ_scores.rb +26 -0
  185. data/lib/generators/observ/add_phase_tracking/add_phase_tracking_generator.rb +150 -0
  186. data/lib/generators/observ/add_phase_tracking/templates/migration.rb.tt +6 -0
  187. data/lib/generators/observ/install/USAGE +27 -0
  188. data/lib/generators/observ/install/install_generator.rb +270 -0
  189. data/lib/generators/observ/install_chat/install_chat_generator.rb +313 -0
  190. data/lib/generators/observ/install_chat/templates/agents/base_agent.rb.tt +147 -0
  191. data/lib/generators/observ/install_chat/templates/agents/simple_agent.rb.tt +55 -0
  192. data/lib/generators/observ/install_chat/templates/concerns/observ_chat_enhancements.rb.tt +34 -0
  193. data/lib/generators/observ/install_chat/templates/concerns/observ_message_enhancements.rb.tt +18 -0
  194. data/lib/generators/observ/install_chat/templates/initializers/observability.rb.tt +20 -0
  195. data/lib/generators/observ/install_chat/templates/jobs/chat_response_job.rb.tt +56 -0
  196. data/lib/generators/observ/install_chat/templates/migrations/add_agent_class_name.rb.tt +6 -0
  197. data/lib/generators/observ/install_chat/templates/migrations/add_observability_session_id.rb.tt +6 -0
  198. data/lib/generators/observ/install_chat/templates/tools/think_tool.rb.tt +29 -0
  199. data/lib/generators/observ/install_chat/templates/views/messages/_content.html.erb.tt +1 -0
  200. data/lib/observ/asset_installer.rb +130 -0
  201. data/lib/observ/asset_syncer.rb +104 -0
  202. data/lib/observ/configuration.rb +108 -0
  203. data/lib/observ/engine.rb +50 -0
  204. data/lib/observ/index_file_generator.rb +142 -0
  205. data/lib/observ/instrumenter/ruby_llm.rb +6 -0
  206. data/lib/observ/version.rb +3 -0
  207. data/lib/observ.rb +29 -0
  208. data/lib/tasks/observ_tasks.rake +75 -0
  209. metadata +453 -0
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require "bundler/setup"
2
+ require "bundler/gem_tasks"
3
+
4
+ # Load Rails tasks only when Rails is available
5
+ if defined?(Rails)
6
+ load "rails/tasks/statistics.rake"
7
+ end
8
+
9
+ namespace :gem do
10
+ desc "Build and install gem locally"
11
+ task :install_local do
12
+ Rake::Task["build"].invoke
13
+ gem_file = Dir["pkg/*.gem"].sort_by { |f| File.mtime(f) }.last
14
+ sh "gem install #{gem_file}"
15
+ end
16
+
17
+ desc "Build, tag, and push gem to RubyGems"
18
+ task :publish do
19
+ require_relative "lib/observ/version"
20
+ version = Observ::VERSION
21
+
22
+ puts "Publishing observ v#{version}..."
23
+
24
+ # Build the gem
25
+ puts "\n1. Building gem..."
26
+ Rake::Task["build"].invoke
27
+
28
+ # Tag the release
29
+ puts "\n2. Creating git tag v#{version}..."
30
+ sh "git tag -a v#{version} -m 'Release v#{version}'" rescue puts "Tag already exists"
31
+
32
+ # Push gem to RubyGems
33
+ puts "\n3. Pushing to RubyGems..."
34
+ gem_file = "pkg/observ-#{version}.gem"
35
+ sh "gem push #{gem_file}"
36
+
37
+ # Push tags to remote
38
+ puts "\n4. Pushing tags to remote..."
39
+ sh "git push origin --tags"
40
+
41
+ puts "\n✓ Successfully published observ v#{version}!"
42
+ puts " View at: https://rubygems.org/gems/observ"
43
+ end
44
+
45
+ desc "Clean built gems"
46
+ task :clean do
47
+ sh "rm -f *.gem"
48
+ end
49
+ end
@@ -0,0 +1,12 @@
1
+ // Observ Engine JavaScript
2
+ // This file is automatically included when the engine layout is rendered
3
+
4
+ // Import Stimulus if available
5
+ if (typeof Stimulus !== 'undefined') {
6
+ console.log('Observ: Stimulus detected');
7
+ }
8
+
9
+ // Observ initialization
10
+ document.addEventListener('DOMContentLoaded', function() {
11
+ console.log('Observ engine loaded');
12
+ });
@@ -0,0 +1,33 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["container"]
5
+
6
+ connect() {
7
+ console.log('[Observ] autoscroll controller connected')
8
+ this.scrollToBottom()
9
+ this.observer = new MutationObserver(() => {
10
+ this.scrollToBottom()
11
+ })
12
+
13
+ this.observer.observe(this.containerTarget, {
14
+ childList: true,
15
+ subtree: true
16
+ })
17
+ }
18
+
19
+ disconnect() {
20
+ if (this.observer) {
21
+ this.observer.disconnect()
22
+ }
23
+ }
24
+
25
+ scrollToBottom() {
26
+ requestAnimationFrame(() => {
27
+ const lastMessage = this.containerTarget.lastElementChild
28
+ if (lastMessage) {
29
+ lastMessage.scrollIntoView({ behavior: "smooth", block: "start" })
30
+ }
31
+ })
32
+ }
33
+ }
@@ -0,0 +1,93 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // Connects to data-controller="observ--chat-form"
4
+ export default class extends Controller {
5
+ static targets = ["agentSelect", "promptVersionGroup", "promptVersionSelect"]
6
+ static values = {
7
+ promptsUrl: String,
8
+ agentsWithPrompts: Object // Map of agent_name => prompt_name
9
+ }
10
+
11
+ connect() {
12
+ console.log('[Observ] chat-form controller connected')
13
+ console.log(' - Prompts URL:', this.promptsUrlValue)
14
+ console.log(' - Agents with prompts:', this.agentsWithPromptsValue)
15
+ this.togglePromptVersionField()
16
+ }
17
+
18
+ disconnect() {
19
+ console.log('[Observ] chat-form controller disconnected')
20
+ }
21
+
22
+ agentChanged() {
23
+ console.log('[Observ] Agent selection changed')
24
+ this.togglePromptVersionField()
25
+ }
26
+
27
+ togglePromptVersionField() {
28
+ if (!this.hasAgentSelectTarget) {
29
+ console.warn('[Observ] Agent select target not found')
30
+ return
31
+ }
32
+
33
+ const selectedAgent = this.agentSelectTarget.value
34
+ console.log(' - Selected agent:', selectedAgent)
35
+
36
+ const promptName = this.agentsWithPromptsValue[selectedAgent]
37
+ console.log(' - Prompt name for agent:', promptName)
38
+
39
+ if (promptName) {
40
+ this.loadPromptVersions(promptName)
41
+ this.showPromptVersionField()
42
+ } else {
43
+ this.hidePromptVersionField()
44
+ }
45
+ }
46
+
47
+ loadPromptVersions(promptName) {
48
+ const url = `${this.promptsUrlValue}/${promptName}/versions.json`
49
+ console.log(' - Fetching versions from:', url)
50
+
51
+ fetch(url)
52
+ .then(response => {
53
+ console.log(' - Response status:', response.status)
54
+ if (!response.ok) {
55
+ throw new Error(`Failed to fetch prompt versions: ${response.status}`)
56
+ }
57
+ return response.json()
58
+ })
59
+ .then(data => {
60
+ console.log(' - Versions loaded:', data.length, 'version(s)')
61
+ this.populateVersions(data)
62
+ })
63
+ .catch(error => {
64
+ console.error('[Observ] Error loading prompt versions:', error)
65
+ // On error, hide the field gracefully
66
+ this.hidePromptVersionField()
67
+ })
68
+ }
69
+
70
+ populateVersions(versions) {
71
+ this.promptVersionSelectTarget.innerHTML = this.buildVersionOptions(versions)
72
+ }
73
+
74
+ buildVersionOptions(versions) {
75
+ const defaultOption = '<option value="">Use default (production)</option>'
76
+ const versionOptions = versions.map(v => {
77
+ const commitMsg = v.commit_message ? ` - ${v.commit_message}` : ''
78
+ return `<option value="${v.version}">v${v.version} - ${v.state}${commitMsg}</option>`
79
+ }).join('')
80
+
81
+ return defaultOption + versionOptions
82
+ }
83
+
84
+ showPromptVersionField() {
85
+ this.promptVersionGroupTarget.classList.remove('observ-hidden')
86
+ }
87
+
88
+ hidePromptVersionField() {
89
+ this.promptVersionGroupTarget.classList.add('observ-hidden')
90
+ // Clear the select value when hiding
91
+ this.promptVersionSelectTarget.value = ''
92
+ }
93
+ }
@@ -0,0 +1,43 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["source", "button"]
5
+ static classes = ["success"]
6
+ static values = {
7
+ successDuration: { type: Number, default: 2000 }
8
+ }
9
+
10
+ connect() {
11
+ console.log('[Observ] copy controller connected')
12
+ }
13
+
14
+ copy(event) {
15
+ event.preventDefault()
16
+
17
+ const text = this.sourceTarget.textContent || this.sourceTarget.value
18
+
19
+ navigator.clipboard.writeText(text).then(() => {
20
+ this.showSuccess()
21
+ }).catch(err => {
22
+ console.error('Failed to copy text: ', err)
23
+ })
24
+ }
25
+
26
+ showSuccess() {
27
+ const originalText = this.buttonTarget.textContent
28
+
29
+ this.buttonTarget.textContent = "Copied!"
30
+
31
+ if (this.hasSuccessClass) {
32
+ this.buttonTarget.classList.add(...this.successClasses)
33
+ }
34
+
35
+ setTimeout(() => {
36
+ this.buttonTarget.textContent = originalText
37
+
38
+ if (this.hasSuccessClass) {
39
+ this.buttonTarget.classList.remove(...this.successClasses)
40
+ }
41
+ }, this.successDurationValue)
42
+ }
43
+ }
@@ -0,0 +1,58 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["metrics"]
5
+ static values = {
6
+ refreshInterval: { type: Number, default: 30000 },
7
+ autoRefresh: { type: Boolean, default: false }
8
+ }
9
+
10
+ connect() {
11
+ console.log('[Observ] dashboard controller connected')
12
+ if (this.autoRefreshValue) {
13
+ this.startAutoRefresh()
14
+ }
15
+ }
16
+
17
+ disconnect() {
18
+ this.stopAutoRefresh()
19
+ }
20
+
21
+ startAutoRefresh() {
22
+ this.refreshTimer = setInterval(() => {
23
+ this.refresh()
24
+ }, this.refreshIntervalValue)
25
+ }
26
+
27
+ stopAutoRefresh() {
28
+ if (this.refreshTimer) {
29
+ clearInterval(this.refreshTimer)
30
+ this.refreshTimer = null
31
+ }
32
+ }
33
+
34
+ async refresh() {
35
+ try {
36
+ const response = await fetch('/observ/dashboard/metrics', {
37
+ headers: {
38
+ 'Accept': 'application/json'
39
+ }
40
+ })
41
+
42
+ if (!response.ok) {
43
+ throw new Error('Network response was not ok')
44
+ }
45
+
46
+ const data = await response.json()
47
+ this.updateMetrics(data)
48
+ } catch (error) {
49
+ console.error('Failed to refresh metrics:', error)
50
+ }
51
+ }
52
+
53
+ updateMetrics(data) {
54
+ if (this.hasMetricsTarget) {
55
+ console.log('Metrics updated:', data)
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,58 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["drawer", "content", "headerTitle"]
5
+
6
+ connect() {
7
+ console.log('[Observ] drawer controller connected')
8
+ }
9
+
10
+ open(event) {
11
+ event.preventDefault()
12
+ const url = event.currentTarget.dataset.drawerUrlParam
13
+
14
+ if (!url) {
15
+ console.error("No URL provided for drawer")
16
+ return
17
+ }
18
+
19
+ this.drawerTarget.classList.add("open")
20
+ this.replaceSpinners()
21
+ this.fetchContent(url)
22
+ }
23
+
24
+ close(event) {
25
+ if (event) {
26
+ event.preventDefault()
27
+ }
28
+ this.drawerTarget.classList.remove("open")
29
+ }
30
+
31
+ replaceSpinners() {
32
+ this.headerTitleTarget.innerHTML = '<span class="observ-spinner"></span>'
33
+ this.contentTarget.innerHTML = '<div class="observ-drawer__loading"><span class="observ-spinner"></span></div>'
34
+ }
35
+
36
+ async fetchContent(url) {
37
+ try {
38
+ const response = await fetch(url, {
39
+ headers: {
40
+ Accept: "text/vnd.turbo-stream.html"
41
+ }
42
+ })
43
+
44
+ if (!response.ok) {
45
+ throw new Error(`HTTP error! status: ${response.status}`)
46
+ }
47
+
48
+ const html = await response.text()
49
+ const turboStream = document.createElement("div")
50
+ turboStream.innerHTML = html
51
+ document.body.appendChild(turboStream)
52
+ setTimeout(() => turboStream.remove(), 100)
53
+ } catch (error) {
54
+ console.error("Failed to fetch drawer content:", error)
55
+ this.contentTarget.innerHTML = '<div class="observ-drawer__error">Failed to load content</div>'
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,33 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["content", "toggle"]
5
+ static classes = ["expanded"]
6
+ static values = {
7
+ expanded: { type: Boolean, default: false }
8
+ }
9
+
10
+ connect() {
11
+ console.log('[Observ] expandable controller connected')
12
+ this.updateState()
13
+ }
14
+
15
+ toggle() {
16
+ this.expandedValue = !this.expandedValue
17
+ this.updateState()
18
+ }
19
+
20
+ updateState() {
21
+ if (this.expandedValue) {
22
+ this.contentTarget.classList.remove("hidden")
23
+ if (this.hasToggleTarget) {
24
+ this.toggleTarget.textContent = "Collapse ▲"
25
+ }
26
+ } else {
27
+ this.contentTarget.classList.add("hidden")
28
+ if (this.hasToggleTarget) {
29
+ this.toggleTarget.textContent = "Expand ▼"
30
+ }
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,36 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["form", "input"]
5
+
6
+ connect() {
7
+ console.log('[Observ] filter controller connected')
8
+ }
9
+
10
+ submit(event) {
11
+ event.preventDefault()
12
+ this.formTarget.requestSubmit()
13
+ }
14
+
15
+ clear(event) {
16
+ event.preventDefault()
17
+
18
+ this.inputTargets.forEach(input => {
19
+ if (input.type === "text" || input.type === "date") {
20
+ input.value = ""
21
+ } else if (input.type === "select-one") {
22
+ input.selectedIndex = 0
23
+ }
24
+ })
25
+
26
+ this.formTarget.requestSubmit()
27
+ }
28
+
29
+ autoSubmit() {
30
+ clearTimeout(this.timeout)
31
+
32
+ this.timeout = setTimeout(() => {
33
+ this.formTarget.requestSubmit()
34
+ }, 500)
35
+ }
36
+ }
@@ -0,0 +1,52 @@
1
+ // Auto-generated index file for Observ Stimulus controllers
2
+ // Register all Observ controllers with the observ-- prefix
3
+ //
4
+ // This file is designed to be imported by the host application's Stimulus setup.
5
+ // The host app should import this file in their controllers/index.js:
6
+ // import "./observ"
7
+
8
+ import AutoscrollController from "./autoscroll_controller.js"
9
+ import ChatFormController from "./chat_form_controller.js"
10
+ import CopyController from "./copy_controller.js"
11
+ import DashboardController from "./dashboard_controller.js"
12
+ import DrawerController from "./drawer_controller.js"
13
+ import ExpandableController from "./expandable_controller.js"
14
+ import FilterController from "./filter_controller.js"
15
+ import JsonViewerController from "./json_viewer_controller.js"
16
+ import MessageFormController from "./message_form_controller.js"
17
+ import PromptVariablesController from "./prompt_variables_controller.js"
18
+ import TextSelectController from "./text_select_controller.js"
19
+
20
+ // Export controllers for manual registration if needed
21
+ export {
22
+ AutoscrollController,
23
+ ChatFormController,
24
+ CopyController,
25
+ DashboardController,
26
+ DrawerController,
27
+ ExpandableController,
28
+ FilterController,
29
+ JsonViewerController,
30
+ MessageFormController,
31
+ PromptVariablesController,
32
+ TextSelectController
33
+ }
34
+
35
+ // Auto-register if Stimulus application is available globally
36
+ if (typeof window.Stimulus !== "undefined") {
37
+ const application = window.Stimulus
38
+
39
+ application.register("observ--autoscroll", AutoscrollController)
40
+ application.register("observ--chat-form", ChatFormController)
41
+ application.register("observ--copy", CopyController)
42
+ application.register("observ--dashboard", DashboardController)
43
+ application.register("observ--drawer", DrawerController)
44
+ application.register("observ--expandable", ExpandableController)
45
+ application.register("observ--filter", FilterController)
46
+ application.register("observ--json-viewer", JsonViewerController)
47
+ application.register("observ--message-form", MessageFormController)
48
+ application.register("observ--prompt-variables", PromptVariablesController)
49
+ application.register("observ--text-select", TextSelectController)
50
+
51
+ console.log("Observ Stimulus controllers registered")
52
+ }