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
@@ -0,0 +1,155 @@
1
+ <% content_for :title, "Compare Versions" %>
2
+
3
+ <% content_for :page_header do %>
4
+ <div class="observ-page-header__content">
5
+ <div>
6
+ <%= link_to "← Back to Prompt", prompt_path(@prompt_name), style: "display: inline-block; margin-bottom: 0.5rem; color: #2563eb;" %>
7
+ <h1 class="observ-page-header__title">Compare Versions</h1>
8
+ <p style="color: #6b7280; margin-top: 0.25rem;"><%= @prompt_name %></p>
9
+ </div>
10
+ </div>
11
+ <% end %>
12
+
13
+ <div class="observ-container">
14
+ <!-- Version Selector -->
15
+ <div class="observ-card" style="margin-bottom: 1.5rem;">
16
+ <div class="observ-card__body">
17
+ <%= form_with url: compare_prompt_path(@prompt_name), method: :get, style: "display: flex; gap: 1rem; align-items: flex-end;" do |f| %>
18
+
19
+ <div style="flex: 1;">
20
+ <%= f.label :from, "From Version", style: "display: block; margin-bottom: 0.5rem; font-weight: 500;" %>
21
+ <%= f.select :from,
22
+ options_for_select(
23
+ Observ::Prompt.where(name: @prompt_name).order(version: :desc).map { |v|
24
+ ["v#{v.version} (#{v.state})", v.version]
25
+ },
26
+ params[:from]
27
+ ),
28
+ {},
29
+ style: "width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 0.375rem;" %>
30
+ </div>
31
+
32
+ <div style="flex: 1;">
33
+ <%= f.label :to, "To Version", style: "display: block; margin-bottom: 0.5rem; font-weight: 500;" %>
34
+ <%= f.select :to,
35
+ options_for_select(
36
+ Observ::Prompt.where(name: @prompt_name).order(version: :desc).map { |v|
37
+ ["v#{v.version} (#{v.state})", v.version]
38
+ },
39
+ params[:to]
40
+ ),
41
+ {},
42
+ style: "width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 0.375rem;" %>
43
+ </div>
44
+
45
+ <%= f.submit "Compare", class: "observ-button observ-button--primary" %>
46
+ <% end %>
47
+ </div>
48
+ </div>
49
+
50
+ <!-- Metadata Comparison -->
51
+ <div class="observ-card" style="margin-bottom: 1.5rem;">
52
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); border-top: 1px solid #e5e7eb;">
53
+ <div style="padding: 1.5rem; border-right: 1px solid #e5e7eb;">
54
+ <div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem;">
55
+ <h2 style="font-size: 1.25rem; font-weight: 600; margin: 0;">Version <%= @from_version.version %></h2>
56
+ <span class="observ-badge <%= case @from_version.state
57
+ when 'draft' then 'observ-badge--warning'
58
+ when 'production' then 'observ-badge--success'
59
+ when 'archived' then 'observ-badge--default'
60
+ end %>" style="font-size: 0.75rem;">
61
+ <%= @from_version.state %>
62
+ </span>
63
+ </div>
64
+
65
+ <dl style="display: flex; flex-direction: column; gap: 0.5rem; font-size: 0.875rem;">
66
+ <div>
67
+ <dt style="color: #6b7280;">Created</dt>
68
+ <dd style="font-weight: 500; margin-top: 0.125rem;"><%= @from_version.created_at.strftime("%b %d, %Y %I:%M %p") %></dd>
69
+ </div>
70
+ <div>
71
+ <dt style="color: #6b7280;">Created By</dt>
72
+ <dd style="font-weight: 500; margin-top: 0.125rem;"><%= @from_version.created_by || "system" %></dd>
73
+ </div>
74
+ <% if @from_version.commit_message.present? %>
75
+ <div>
76
+ <dt style="color: #6b7280;">Commit Message</dt>
77
+ <dd style="font-weight: 500; margin-top: 0.125rem; font-style: italic;">"<%= @from_version.commit_message %>"</dd>
78
+ </div>
79
+ <% end %>
80
+ </dl>
81
+ </div>
82
+
83
+ <div style="padding: 1.5rem;">
84
+ <div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem;">
85
+ <h2 style="font-size: 1.25rem; font-weight: 600; margin: 0;">Version <%= @to_version.version %></h2>
86
+ <span class="observ-badge <%= case @to_version.state
87
+ when 'draft' then 'observ-badge--warning'
88
+ when 'production' then 'observ-badge--success'
89
+ when 'archived' then 'observ-badge--default'
90
+ end %>" style="font-size: 0.75rem;">
91
+ <%= @to_version.state %>
92
+ </span>
93
+ </div>
94
+
95
+ <dl style="display: flex; flex-direction: column; gap: 0.5rem; font-size: 0.875rem;">
96
+ <div>
97
+ <dt style="color: #6b7280;">Created</dt>
98
+ <dd style="font-weight: 500; margin-top: 0.125rem;"><%= @to_version.created_at.strftime("%b %d, %Y %I:%M %p") %></dd>
99
+ </div>
100
+ <div>
101
+ <dt style="color: #6b7280;">Created By</dt>
102
+ <dd style="font-weight: 500; margin-top: 0.125rem;"><%= @to_version.created_by || "system" %></dd>
103
+ </div>
104
+ <% if @to_version.commit_message.present? %>
105
+ <div>
106
+ <dt style="color: #6b7280;">Commit Message</dt>
107
+ <dd style="font-weight: 500; margin-top: 0.125rem; font-style: italic;">"<%= @to_version.commit_message %>"</dd>
108
+ </div>
109
+ <% end %>
110
+ </dl>
111
+ </div>
112
+ </div>
113
+ </div>
114
+
115
+ <!-- Side-by-Side Diff -->
116
+ <div class="observ-card">
117
+ <div class="observ-card__header">
118
+ <h2 style="font-size: 1.25rem; font-weight: 600; margin: 0;">Prompt Content Comparison</h2>
119
+ </div>
120
+
121
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); border-top: 1px solid #e5e7eb;">
122
+ <!-- Left: From Version -->
123
+ <div style="padding: 1.5rem; border-right: 1px solid #e5e7eb;">
124
+ <div style="background-color: #f9fafb; padding: 1rem; border-radius: 0.375rem; overflow-x: auto;">
125
+ <pre style="margin: 0; font-family: monospace; font-size: 0.875rem; white-space: pre-wrap;"><%= render "diff_content", content: @from_version.prompt, diff: @diff, side: :from %></pre>
126
+ </div>
127
+ </div>
128
+
129
+ <!-- Right: To Version -->
130
+ <div style="padding: 1.5rem;">
131
+ <div style="background-color: #f9fafb; padding: 1rem; border-radius: 0.375rem; overflow-x: auto;">
132
+ <pre style="margin: 0; font-family: monospace; font-size: 0.875rem; white-space: pre-wrap;"><%= render "diff_content", content: @to_version.prompt, diff: @diff, side: :to %></pre>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </div>
137
+
138
+ <!-- Config Comparison -->
139
+ <% if @from_version.config.present? || @to_version.config.present? %>
140
+ <div class="observ-card" style="margin-top: 1.5rem;">
141
+ <div class="observ-card__header">
142
+ <h2 style="font-size: 1.25rem; font-weight: 600; margin: 0;">Configuration Comparison</h2>
143
+ </div>
144
+
145
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); border-top: 1px solid #e5e7eb;">
146
+ <div style="padding: 1.5rem; border-right: 1px solid #e5e7eb;">
147
+ <pre style="background-color: #f9fafb; padding: 1rem; border-radius: 0.375rem; overflow-x: auto; font-size: 0.875rem; margin: 0;"><%= JSON.pretty_generate(@from_version.config) %></pre>
148
+ </div>
149
+ <div style="padding: 1.5rem;">
150
+ <pre style="background-color: #f9fafb; padding: 1rem; border-radius: 0.375rem; overflow-x: auto; font-size: 0.875rem; margin: 0;"><%= JSON.pretty_generate(@to_version.config) %></pre>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ <% end %>
155
+ </div>
@@ -0,0 +1,17 @@
1
+ <% content_for :title, "Edit Prompt Draft" %>
2
+
3
+ <% content_for :page_header do %>
4
+ <div class="observ-page-header__content">
5
+ <div>
6
+ <%= link_to "← Back to Prompt", prompt_path(@prompt.name), style: "display: inline-block; margin-bottom: 0.5rem; color: #2563eb;" %>
7
+ <h1 class="observ-page-header__title">Edit Prompt Draft</h1>
8
+ <p style="color: #6b7280; margin-top: 0.25rem;">
9
+ Editing <%= @prompt.name %> (v<%= @prompt.version %> - Draft)
10
+ </p>
11
+ </div>
12
+ </div>
13
+ <% end %>
14
+
15
+ <div class="observ-container" style="max-width: 56rem; margin-left: auto; margin-right: auto;">
16
+ <%= render "form", prompt: @prompt %>
17
+ </div>
@@ -0,0 +1,108 @@
1
+ <% content_for :title, "Prompt Management" %>
2
+
3
+ <% content_for :page_header do %>
4
+ <div class="observ-page-header__content">
5
+ <h1 class="observ-page-header__title">Prompt Management</h1>
6
+ <%= link_to "New Prompt", new_prompt_path, class: "observ-button observ-button--primary" %>
7
+ </div>
8
+ <% end %>
9
+
10
+ <div class="observ-container">
11
+ <!-- Search and Filters -->
12
+ <section class="observ-card observ-prompts-filters">
13
+ <div class="observ-card__body">
14
+ <%= form_with url: prompts_path, method: :get, class: "observ-prompts-filters__form" do |f| %>
15
+ <div class="observ-prompts-filters__field observ-prompts-filters__field--search">
16
+ <%= f.label :search, "Search prompts", class: "observ-prompts-filters__label" %>
17
+ <%= f.text_field :search,
18
+ value: params[:search],
19
+ placeholder: "Search by name...",
20
+ class: "observ-prompts-filters__input" %>
21
+ </div>
22
+
23
+ <div class="observ-prompts-filters__field observ-prompts-filters__field--state">
24
+ <%= f.label :state, "Filter by state", class: "observ-prompts-filters__label" %>
25
+ <%= f.select :state,
26
+ options_for_select(
27
+ [["All States", ""], ["Draft", "draft"], ["Production", "production"], ["Archived", "archived"]],
28
+ params[:state]
29
+ ),
30
+ {},
31
+ class: "observ-prompts-filters__select" %>
32
+ </div>
33
+
34
+ <div class="observ-prompts-filters__actions">
35
+ <%= f.submit "Filter", class: "observ-button observ-button--secondary" %>
36
+ <%= link_to "Clear", prompts_path, class: "observ-button" %>
37
+ </div>
38
+ <% end %>
39
+ </div>
40
+ </section>
41
+
42
+ <!-- Prompts Table -->
43
+ <section class="observ-card">
44
+ <div class="observ-card__body">
45
+ <% if @prompt_data.any? %>
46
+ <table class="observ-table">
47
+ <thead class="observ-table__header">
48
+ <tr class="observ-table__row">
49
+ <th class="observ-table__cell">Name</th>
50
+ <th class="observ-table__cell observ-table__cell--numeric">Versions</th>
51
+ <th class="observ-table__cell">Production</th>
52
+ <th class="observ-table__cell">Latest</th>
53
+ <th class="observ-table__cell">Last Updated</th>
54
+ <th class="observ-table__cell observ-table__cell--actions"></th>
55
+ </tr>
56
+ </thead>
57
+ <tbody>
58
+ <% @prompt_data.each do |data| %>
59
+ <tr class="observ-table__row">
60
+ <td class="observ-table__cell">
61
+ <%= link_to data[:name], prompt_path(data[:name]), class: "observ-prompts-table__link" %>
62
+ </td>
63
+ <td class="observ-table__cell observ-table__cell--numeric">
64
+ <%= data[:total_versions] %>
65
+ </td>
66
+ <td class="observ-table__cell">
67
+ <% if data[:production_version] %>
68
+ <span class="observ-badge observ-badge--success">
69
+ v<%= data[:production_version] %>
70
+ </span>
71
+ <% else %>
72
+ <span class="observ-text--muted">None</span>
73
+ <% end %>
74
+ </td>
75
+ <td class="observ-table__cell">
76
+ <span class="observ-badge <%= case data[:latest_state]
77
+ when 'draft' then 'observ-badge--warning'
78
+ when 'production' then 'observ-badge--success'
79
+ when 'archived' then 'observ-badge--default'
80
+ end %>">
81
+ v<%= data[:latest_version] %> · <%= data[:latest_state] %>
82
+ </span>
83
+ </td>
84
+ <td class="observ-table__cell">
85
+ <%= time_ago_in_words(data[:last_updated]) %> ago
86
+ </td>
87
+ <td class="observ-table__cell observ-table__cell--actions">
88
+ <div class="observ-prompts-table__action-group">
89
+ <%= link_to "View", prompt_path(data[:name]), class: "observ-button observ-button--sm" %>
90
+ <%= link_to "History", versions_prompt_path(data[:name]), class: "observ-button observ-button--sm" %>
91
+ </div>
92
+ </td>
93
+ </tr>
94
+ <% end %>
95
+ </tbody>
96
+ </table>
97
+ <% else %>
98
+ <div class="observ-card__empty">
99
+ <p class="observ-card__empty-text">No prompts found</p>
100
+ <%= link_to "Create your first prompt", new_prompt_path, class: "observ-button observ-button--primary" %>
101
+ </div>
102
+ <% end %>
103
+ </div>
104
+ </section>
105
+
106
+ <!-- Pagination -->
107
+ <%= observ_pagination(@prompts) %>
108
+ </div>
@@ -0,0 +1,17 @@
1
+ <% content_for :title, "Create New Prompt" %>
2
+
3
+ <% content_for :page_header do %>
4
+ <div class="observ-page-header__content">
5
+ <div>
6
+ <%= link_to "← Back to Prompts", prompts_path, style: "display: inline-block; margin-bottom: 0.5rem; color: #2563eb;" %>
7
+ <h1 class="observ-page-header__title">Create New Prompt</h1>
8
+ <p style="color: #6b7280; margin-top: 0.25rem;">
9
+ Create a new prompt version. All new prompts start as drafts.
10
+ </p>
11
+ </div>
12
+ </div>
13
+ <% end %>
14
+
15
+ <div class="observ-container" style="max-width: 56rem; margin-left: auto; margin-right: auto;">
16
+ <%= render "new_form", form: @form %>
17
+ </div>
@@ -0,0 +1,138 @@
1
+ <% content_for :title, @prompt.name %>
2
+
3
+ <% content_for :page_header do %>
4
+ <div class="observ-page-header__content">
5
+ <div>
6
+ <%= link_to "← Back to Prompts", prompts_path, class: "prompt-page-header__back-link" %>
7
+ <h1 class="observ-page-header__title"><%= @prompt.name %></h1>
8
+ <p class="prompt-page-header__subtitle">
9
+ Currently viewing version <%= @prompt.version %>
10
+ </p>
11
+ </div>
12
+ <%= render "prompt_actions", prompt: @prompt %>
13
+ </div>
14
+ <% end %>
15
+
16
+ <div class="observ-container">
17
+ <div class="prompt-details">
18
+ <!-- Left Sidebar: Version List -->
19
+ <aside>
20
+ <div class="observ-card prompt-versions__sidebar">
21
+ <div class="prompt-versions__header">
22
+ <h2 class="prompt-versions__title">Versions</h2>
23
+ </div>
24
+ <div class="prompt-versions__list">
25
+ <% @all_versions.each do |version| %>
26
+ <%= link_to prompt_path(@prompt_name, version: version.version),
27
+ class: "prompt-versions__item #{version.id == @prompt.id ? 'prompt-versions__item--active' : ''}" do %>
28
+
29
+ <div class="prompt-versions__item-header">
30
+ <span class="prompt-versions__version-number">v<%= version.version %></span>
31
+
32
+ <span class="observ-badge <%= case version.state
33
+ when 'draft' then 'observ-badge--warning'
34
+ when 'production' then 'observ-badge--success'
35
+ when 'archived' then 'observ-badge--default'
36
+ end %>">
37
+ <%= version.state %>
38
+ </span>
39
+ </div>
40
+
41
+ <% if version.commit_message.present? %>
42
+ <p class="prompt-versions__commit-message">
43
+ <%= version.commit_message %>
44
+ </p>
45
+ <% end %>
46
+
47
+ <p class="prompt-versions__timestamp">
48
+ <%= time_ago_in_words(version.created_at) %> ago
49
+ </p>
50
+ <% end %>
51
+ <% end %>
52
+ </div>
53
+ <div class="prompt-versions__footer">
54
+ <%= link_to "Compare Versions",
55
+ compare_prompt_path(@prompt_name, from: @production_version&.version, to: @prompt.version),
56
+ class: "observ-button observ-button--sm" %>
57
+ </div>
58
+ </div>
59
+ </aside>
60
+
61
+ <!-- Right Panel: Version Details -->
62
+ <div class="observ-card">
63
+ <div class="observ-card__header prompt-detail__header">
64
+ <!-- Badges and Actions Section -->
65
+ <div class="prompt-detail__header-section">
66
+ <div class="prompt-detail__badges-row">
67
+ <div class="prompt-detail__badges">
68
+ <span class="observ-badge <%= case @prompt.state
69
+ when 'draft' then 'observ-badge--warning'
70
+ when 'production' then 'observ-badge--success'
71
+ when 'archived' then 'observ-badge--default'
72
+ end %>">
73
+ <%= @prompt.state.titleize %>
74
+ </span>
75
+ <% if @prompt.production? %>
76
+ <span class="observ-badge observ-badge--info">
77
+ Currently in Use
78
+ </span>
79
+ <% end %>
80
+ </div>
81
+ <%= render "version_actions", prompt: @prompt %>
82
+ </div>
83
+ </div>
84
+
85
+ <!-- Metadata Section -->
86
+ <div class="prompt-detail__header-section">
87
+ <dl class="prompt-detail__metadata">
88
+ <div class="prompt-detail__metadata-item">
89
+ <dt class="prompt-detail__metadata-label">Version</dt>
90
+ <dd class="prompt-detail__metadata-value"><%= @prompt.version %></dd>
91
+ </div>
92
+ <div class="prompt-detail__metadata-item">
93
+ <dt class="prompt-detail__metadata-label">Created</dt>
94
+ <dd class="prompt-detail__metadata-value"><%= @prompt.created_at.strftime("%b %d, %Y at %I:%M %p") %></dd>
95
+ </div>
96
+ <div class="prompt-detail__metadata-item">
97
+ <dt class="prompt-detail__metadata-label">Created By</dt>
98
+ <dd class="prompt-detail__metadata-value"><%= @prompt.created_by || "system" %></dd>
99
+ </div>
100
+ <div class="prompt-detail__metadata-item">
101
+ <dt class="prompt-detail__metadata-label">Last Updated</dt>
102
+ <dd class="prompt-detail__metadata-value"><%= time_ago_in_words(@prompt.updated_at) %> ago</dd>
103
+ </div>
104
+ </dl>
105
+ </div>
106
+
107
+ <!-- Commit Message Section -->
108
+ <% if @prompt.commit_message.present? %>
109
+ <div class="prompt-detail__header-section">
110
+ <div class="prompt-detail__commit">
111
+ <p class="prompt-detail__commit-title">Commit Message:</p>
112
+ <p class="prompt-detail__commit-message"><%= @prompt.commit_message %></p>
113
+ </div>
114
+ </div>
115
+ <% end %>
116
+ </div>
117
+
118
+ <!-- Prompt Content -->
119
+ <div class="observ-card__body prompt-detail__body-section">
120
+ <h3 class="prompt-detail__section-title">Prompt Content</h3>
121
+ <div class="prompt-detail__content-block">
122
+ <pre><%= render "prompt_content_highlighted", content: @prompt.prompt %></pre>
123
+ </div>
124
+ </div>
125
+
126
+ <!-- Configuration -->
127
+ <div class="observ-card__body">
128
+ <h3 class="prompt-detail__section-title">Configuration</h3>
129
+ <% if @prompt.config.present? %>
130
+ <pre class="prompt-detail__config-block"><%= JSON.pretty_generate(@prompt.config) %></pre>
131
+ <% else %>
132
+ <p class="prompt-detail__no-config">No configuration specified</p>
133
+ <% end %>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </div>
@@ -0,0 +1,87 @@
1
+ <% content_for :title, "Version History" %>
2
+
3
+ <% content_for :page_header do %>
4
+ <div class="observ-page-header__content">
5
+ <div>
6
+ <%= link_to "← Back to Prompt", prompt_path(@prompt_name), style: "display: inline-block; margin-bottom: 0.5rem; color: #2563eb;" %>
7
+ <h1 class="observ-page-header__title">Version History</h1>
8
+ <p style="color: #6b7280; margin-top: 0.25rem;"><%= @prompt_name %></p>
9
+ </div>
10
+ </div>
11
+ <% end %>
12
+
13
+ <div class="observ-container">
14
+ <!-- Timeline View -->
15
+ <div class="observ-card">
16
+ <div class="observ-card__body">
17
+ <div style="display: flex; flex-direction: column; gap: 1.5rem;">
18
+ <% @versions.each_with_index do |version, index| %>
19
+ <div style="display: flex; gap: 1rem;">
20
+ <!-- Timeline Line -->
21
+ <div style="display: flex; flex-direction: column; align-items: center;">
22
+ <div style="width: 1rem; height: 1rem; border-radius: 50%; <%= case version.state
23
+ when 'production' then 'background-color: #10b981;'
24
+ when 'draft' then 'background-color: #f59e0b;'
25
+ when 'archived' then 'background-color: #9ca3af;'
26
+ end %>">
27
+ </div>
28
+ <% unless index == @versions.count - 1 %>
29
+ <div style="width: 2px; height: 100%; background-color: #d1d5db; flex: 1; margin: 0.25rem 0;"></div>
30
+ <% end %>
31
+ </div>
32
+
33
+ <!-- Version Card -->
34
+ <div style="flex: 1; padding-bottom: 1.5rem;">
35
+ <div style="border: 1px solid #e5e7eb; border-radius: 0.5rem; padding: 1rem; transition: box-shadow 0.2s;">
36
+ <div style="display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 0.75rem;">
37
+ <div style="flex: 1;">
38
+ <div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem;">
39
+ <h3 style="font-size: 1.125rem; font-weight: 600; margin: 0;">Version <%= version.version %></h3>
40
+ <span class="observ-badge <%= case version.state
41
+ when 'draft' then 'observ-badge--warning'
42
+ when 'production' then 'observ-badge--success'
43
+ when 'archived' then 'observ-badge--default'
44
+ end %>" style="font-size: 0.75rem;">
45
+ <%= version.state.titleize %>
46
+ </span>
47
+ </div>
48
+
49
+ <p style="font-size: 0.875rem; color: #6b7280; margin: 0;">
50
+ <%= version.created_at.strftime("%B %d, %Y at %I:%M %p") %>
51
+ <% if version.created_by.present? %>
52
+ by <span style="font-weight: 500;"><%= version.created_by %></span>
53
+ <% end %>
54
+ </p>
55
+
56
+ <% if version.commit_message.present? %>
57
+ <p style="font-size: 0.875rem; color: #374151; margin: 0.5rem 0 0; font-style: italic;">
58
+ "<%= version.commit_message %>"
59
+ </p>
60
+ <% end %>
61
+ </div>
62
+
63
+ <div style="display: flex; gap: 0.5rem;">
64
+ <%= link_to "View", prompt_path(@prompt_name, version: version.version), class: "observ-button observ-button--sm observ-button--secondary" %>
65
+
66
+ <% if @production_version && version != @production_version %>
67
+ <%= link_to "Compare with Production",
68
+ compare_prompt_path(@prompt_name,
69
+ from: @production_version.version,
70
+ to: version.version),
71
+ class: "observ-button observ-button--sm" %>
72
+ <% end %>
73
+ </div>
74
+ </div>
75
+
76
+ <!-- Preview -->
77
+ <div style="margin-top: 0.75rem; padding: 0.75rem; background-color: #f9fafb; border-radius: 0.375rem; font-family: monospace; font-size: 0.875rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
78
+ <%= truncate(version.prompt, length: 200) %>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ <% end %>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
@@ -0,0 +1,25 @@
1
+ <%= turbo_stream.update "drawer-header-title" do %>
2
+ Session Annotations
3
+ <% end %>
4
+
5
+ <%= turbo_stream.update "drawer-content" do %>
6
+ <div class="observ-drawer__section">
7
+ <%= render "observ/annotations/form", annotatable: @session, annotation: @annotation %>
8
+ </div>
9
+
10
+ <div class="observ-drawer__section">
11
+ <h3 class="observ-drawer__section-title">
12
+ Annotations (<span id="annotations-count"><%= @annotations.count %></span>)
13
+ </h3>
14
+
15
+ <div id="annotations-list" class="observ-annotations-list">
16
+ <% if @annotations.any? %>
17
+ <%= render partial: "observ/annotations/annotation", collection: @annotations, locals: { annotatable: @session } %>
18
+ <% else %>
19
+ <div id="annotations-empty-state" class="observ-empty-state">
20
+ <p class="observ-empty-state__text">No annotations yet</p>
21
+ </div>
22
+ <% end %>
23
+ </div>
24
+ </div>
25
+ <% end %>
@@ -0,0 +1,49 @@
1
+ <%= turbo_stream.update "drawer-header-title" do %>
2
+ Test Drawer - Session <%= truncate_id(@session.session_id, 12) %>
3
+ <% end %>
4
+
5
+ <%= turbo_stream.update "drawer-content" do %>
6
+ <div class="observ-card">
7
+ <header class="observ-card__header">
8
+ <h2 class="observ-card__title">Lorem Ipsum Content</h2>
9
+ </header>
10
+ <div class="observ-card__body">
11
+ <p>
12
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
13
+ Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
14
+ </p>
15
+
16
+ <h3 style="margin-top: 1.5rem; font-size: 1.125rem; font-weight: 600;">Session Details</h3>
17
+ <dl class="observ-definition-list">
18
+ <div class="observ-definition-list__item">
19
+ <dt class="observ-definition-list__term">Session ID</dt>
20
+ <dd class="observ-definition-list__definition"><code class="observ-code observ-code--inline"><%= @session.session_id %></code></dd>
21
+ </div>
22
+ <div class="observ-definition-list__item">
23
+ <dt class="observ-definition-list__term">Started</dt>
24
+ <dd class="observ-definition-list__definition"><%= observ_timestamp(@session.start_time) %></dd>
25
+ </div>
26
+ <% if @session.end_time %>
27
+ <div class="observ-definition-list__item">
28
+ <dt class="observ-definition-list__term">Ended</dt>
29
+ <dd class="observ-definition-list__definition"><%= observ_timestamp(@session.end_time) %></dd>
30
+ </div>
31
+ <% end %>
32
+ </dl>
33
+
34
+ <p style="margin-top: 1.5rem;">
35
+ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
36
+ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
37
+ </p>
38
+
39
+ <p style="margin-top: 1rem;">
40
+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
41
+ totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
42
+ </p>
43
+
44
+ <div style="margin-top: 1.5rem;">
45
+ <%= link_to "View Full Session", session_path(@session), class: "observ-button observ-button--primary" %>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ <% end %>