rubyllm-observ 0.5.0 → 0.6.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +54 -6
  3. data/app/assets/stylesheets/observ/_annotations.scss +114 -103
  4. data/app/assets/stylesheets/observ/_card.scss +58 -49
  5. data/app/assets/stylesheets/observ/_chat.scss +247 -155
  6. data/app/assets/stylesheets/observ/_components.scss +622 -340
  7. data/app/assets/stylesheets/observ/_dashboard.scss +31 -28
  8. data/app/assets/stylesheets/observ/_datasets.scss +494 -547
  9. data/app/assets/stylesheets/observ/_drawer.scss +250 -228
  10. data/app/assets/stylesheets/observ/_filters.scss +139 -0
  11. data/app/assets/stylesheets/observ/_json_viewer.scss +103 -97
  12. data/app/assets/stylesheets/observ/_layout.scss +443 -178
  13. data/app/assets/stylesheets/observ/_metrics.scss +79 -76
  14. data/app/assets/stylesheets/observ/_namespace.scss +18 -0
  15. data/app/assets/stylesheets/observ/_observations.scss +122 -119
  16. data/app/assets/stylesheets/observ/_pagination.scss +129 -112
  17. data/app/assets/stylesheets/observ/_prompts.scss +485 -269
  18. data/app/assets/stylesheets/observ/_reset.scss +249 -0
  19. data/app/assets/stylesheets/observ/_table.scss +46 -38
  20. data/app/assets/stylesheets/observ/_variables.scss +54 -0
  21. data/app/assets/stylesheets/observ/application.scss +3 -0
  22. data/app/controllers/observ/dataset_run_items_controller.rb +0 -1
  23. data/app/controllers/observ/review_queue_controller.rb +154 -0
  24. data/app/controllers/observ/scores_controller.rb +64 -0
  25. data/app/controllers/observ/sessions_controller.rb +23 -0
  26. data/app/helpers/observ/application_helper.rb +1 -0
  27. data/app/helpers/observ/reviews_helper.rb +33 -0
  28. data/app/models/concerns/observ/json_queryable.rb +138 -0
  29. data/app/models/concerns/observ/reviewable.rb +41 -0
  30. data/app/models/concerns/observ/scoreable.rb +34 -0
  31. data/app/models/observ/dataset_run_item.rb +3 -13
  32. data/app/models/observ/review_item.rb +48 -0
  33. data/app/models/observ/score.rb +38 -6
  34. data/app/models/observ/session.rb +5 -1
  35. data/app/models/observ/trace.rb +3 -0
  36. data/app/services/observ/evaluators/base_evaluator.rb +0 -1
  37. data/app/services/observ/guardrail_service.rb +128 -0
  38. data/app/views/kaminari/_first_page.html.erb +1 -1
  39. data/app/views/kaminari/_gap.html.erb +1 -1
  40. data/app/views/kaminari/_last_page.html.erb +1 -1
  41. data/app/views/kaminari/_next_page.html.erb +1 -1
  42. data/app/views/kaminari/_page.html.erb +1 -1
  43. data/app/views/kaminari/_paginator.html.erb +1 -1
  44. data/app/views/kaminari/_prev_page.html.erb +1 -1
  45. data/app/views/kaminari/observ/_first_page.html.erb +1 -1
  46. data/app/views/kaminari/observ/_gap.html.erb +1 -1
  47. data/app/views/kaminari/observ/_last_page.html.erb +1 -1
  48. data/app/views/kaminari/observ/_next_page.html.erb +1 -1
  49. data/app/views/kaminari/observ/_page.html.erb +1 -1
  50. data/app/views/kaminari/observ/_paginator.html.erb +1 -1
  51. data/app/views/kaminari/observ/_prev_page.html.erb +1 -1
  52. data/app/views/layouts/observ/application.html.erb +96 -58
  53. data/app/views/observ/annotations/_form.html.erb +5 -5
  54. data/app/views/observ/annotations/index.html.erb +4 -4
  55. data/app/views/observ/annotations/sessions_index.html.erb +9 -9
  56. data/app/views/observ/annotations/traces_index.html.erb +9 -9
  57. data/app/views/observ/chats/_form.html.erb +7 -7
  58. data/app/views/observ/datasets/index.html.erb +6 -6
  59. data/app/views/observ/messages/_form.html.erb +11 -12
  60. data/app/views/observ/observations/index.html.erb +3 -4
  61. data/app/views/observ/prompts/_form.html.erb +37 -38
  62. data/app/views/observ/prompts/_new_form.html.erb +37 -38
  63. data/app/views/observ/prompts/compare.html.erb +59 -55
  64. data/app/views/observ/prompts/edit.html.erb +3 -3
  65. data/app/views/observ/prompts/index.html.erb +9 -9
  66. data/app/views/observ/prompts/new.html.erb +3 -3
  67. data/app/views/observ/prompts/show.html.erb +2 -2
  68. data/app/views/observ/prompts/versions.html.erb +22 -22
  69. data/app/views/observ/review_queue/_item.html.erb +39 -0
  70. data/app/views/observ/review_queue/_stats.html.erb +18 -0
  71. data/app/views/observ/review_queue/index.html.erb +49 -0
  72. data/app/views/observ/review_queue/show.html.erb +76 -0
  73. data/app/views/observ/review_queue/stats.html.erb +100 -0
  74. data/app/views/observ/scores/_form.html.erb +39 -0
  75. data/app/views/observ/scores/create.turbo_stream.erb +10 -0
  76. data/app/views/observ/sessions/_chat.html.erb +59 -0
  77. data/app/views/observ/sessions/_metadata.html.erb +17 -0
  78. data/app/views/observ/sessions/_metrics.html.erb +81 -0
  79. data/app/views/observ/sessions/_traces.html.erb +92 -0
  80. data/app/views/observ/sessions/annotations_drawer.turbo_stream.erb +8 -1
  81. data/app/views/observ/sessions/index.html.erb +60 -4
  82. data/app/views/observ/sessions/show.html.erb +4 -217
  83. data/app/views/observ/traces/_details.html.erb +47 -0
  84. data/app/views/observ/traces/_input.html.erb +10 -0
  85. data/app/views/observ/traces/_metadata.html.erb +10 -0
  86. data/app/views/observ/traces/_observations.html.erb +172 -0
  87. data/app/views/observ/traces/_output.html.erb +10 -0
  88. data/app/views/observ/traces/annotations_drawer.turbo_stream.erb +8 -1
  89. data/app/views/observ/traces/index.html.erb +3 -4
  90. data/app/views/observ/traces/show.html.erb +5 -232
  91. data/config/routes.rb +14 -0
  92. data/db/migrate/015_refactor_scores_to_polymorphic.rb +27 -0
  93. data/db/migrate/016_create_observ_review_items.rb +25 -0
  94. data/lib/observ/version.rb +1 -1
  95. data/lib/rubyllm-observ.rb +1 -0
  96. metadata +31 -1
@@ -28,224 +28,11 @@
28
28
  <% end %>
29
29
 
30
30
  <div class="observ-container">
31
- <section class="observ-metrics-grid observ-metrics-grid--4col">
32
- <div class="observ-metric-card">
33
- <div class="observ-metric-card__header">
34
- <h3 class="observ-metric-card__label">Traces</h3>
35
- </div>
36
- <div class="observ-metric-card__body">
37
- <p class="observ-metric-card__value"><%= @session_metrics[:total_traces] %></p>
38
- </div>
39
- </div>
40
-
41
- <div class="observ-metric-card">
42
- <div class="observ-metric-card__header">
43
- <h3 class="observ-metric-card__label">LLM Calls</h3>
44
- </div>
45
- <div class="observ-metric-card__body">
46
- <p class="observ-metric-card__value"><%= @session_metrics[:total_llm_calls] %></p>
47
- </div>
48
- </div>
49
-
50
- <div class="observ-metric-card">
51
- <div class="observ-metric-card__header">
52
- <h3 class="observ-metric-card__label">Total Tokens</h3>
53
- </div>
54
- <div class="observ-metric-card__body">
55
- <p class="observ-metric-card__value"><%= format_tokens(@session_metrics[:total_tokens]) %></p>
56
- </div>
57
- </div>
58
-
59
- <div class="observ-metric-card observ-metric-card--highlighted">
60
- <div class="observ-metric-card__header">
61
- <h3 class="observ-metric-card__label">Total Cost</h3>
62
- </div>
63
- <div class="observ-metric-card__body">
64
- <p class="observ-metric-card__value"><%= format_currency(@session_metrics[:total_cost]) %></p>
65
- </div>
66
- </div>
67
-
68
- <div class="observ-metric-card">
69
- <div class="observ-metric-card__header">
70
- <h3 class="observ-metric-card__label">Duration</h3>
71
- </div>
72
- <div class="observ-metric-card__body">
73
- <p class="observ-metric-card__value">
74
- <% if @session_metrics[:duration_s] %>
75
- <%= format_duration_s(@session_metrics[:duration_s]) %>
76
- <% else %>
77
- <span class="observ-text--muted">In Progress</span>
78
- <% end %>
79
- </p>
80
- </div>
81
- </div>
31
+ <%= render "observ/sessions/metrics", session_metrics: @session_metrics, session: @session %>
82
32
 
83
- <div class="observ-metric-card">
84
- <div class="observ-metric-card__header">
85
- <h3 class="observ-metric-card__label">Avg Latency</h3>
86
- </div>
87
- <div class="observ-metric-card__body">
88
- <p class="observ-metric-card__value"><%= format_duration_ms(@session_metrics[:average_llm_latency_ms]) %></p>
89
- </div>
90
- </div>
91
-
92
- <div class="observ-metric-card">
93
- <div class="observ-metric-card__header">
94
- <h3 class="observ-metric-card__label">Total LLM Time</h3>
95
- </div>
96
- <div class="observ-metric-card__body">
97
- <p class="observ-metric-card__value"><%= format_duration_ms(@session_metrics[:total_llm_duration_ms]) %></p>
98
- </div>
99
- </div>
100
-
101
- <div class="observ-metric-card">
102
- <div class="observ-metric-card__header">
103
- <h3 class="observ-metric-card__label">Started</h3>
104
- </div>
105
- <div class="observ-metric-card__body">
106
- <p class="observ-metric-card__value observ-metric-card__value--small">
107
- <%= observ_relative_time(@session.start_time) %>
108
- </p>
109
- </div>
110
- </div>
111
- </section>
112
-
113
- <% if @session.metadata.present? && @session.metadata.any? %>
114
- <section class="observ-card">
115
- <header class="observ-card__header">
116
- <h2 class="observ-card__title">Metadata</h2>
117
- </header>
118
- <div class="observ-card__body">
119
- <dl class="observ-definition-list">
120
- <% @session.metadata.each do |key, value| %>
121
- <div class="observ-definition-list__item">
122
- <dt class="observ-definition-list__term"><%= key %></dt>
123
- <dd class="observ-definition-list__definition"><%= value %></dd>
124
- </div>
125
- <% end %>
126
- </dl>
127
- </div>
128
- </section>
129
- <% end %>
130
-
131
- <section class="observ-card">
132
- <header class="observ-card__header">
133
- <h2 class="observ-card__title">Traces (<%= @traces.count %>)</h2>
134
- </header>
135
- <div class="observ-card__body">
136
- <% if @traces.any? %>
137
- <table class="observ-table">
138
- <thead class="observ-table__header">
139
- <tr class="observ-table__row">
140
- <th class="observ-table__cell">Trace ID</th>
141
- <th class="observ-table__cell">Name</th>
142
- <th class="observ-table__cell">Model(s)</th>
143
- <th class="observ-table__cell">Start Time</th>
144
- <th class="observ-table__cell">Duration</th>
145
- <th class="observ-table__cell observ-table__cell--numeric">Observations</th>
146
- <th class="observ-table__cell observ-table__cell--numeric">Tokens</th>
147
- <th class="observ-table__cell observ-table__cell--numeric">Cost</th>
148
- <th class="observ-table__cell">Status</th>
149
- <th class="observ-table__cell"></th>
150
- </tr>
151
- </thead>
152
- <tbody>
153
- <% @traces.each do |trace| %>
154
- <tr class="observ-table__row">
155
- <td class="observ-table__cell">
156
- <code class="observ-code observ-code--inline"><%= truncate_id(trace.trace_id, 12) %></code>
157
- </td>
158
- <td class="observ-table__cell">
159
- <%= trace.name || "—" %>
160
- </td>
161
- <td class="observ-table__cell">
162
- <% models = trace.models_used %>
163
- <% if models.any? %>
164
- <%= models.join(", ") %>
165
- <% else %>
166
- <span class="observ-text--muted">—</span>
167
- <% end %>
168
- </td>
169
- <td class="observ-table__cell">
170
- <%= observ_timestamp(trace.start_time) %>
171
- </td>
172
- <td class="observ-table__cell">
173
- <% if trace.duration_ms %>
174
- <%= format_duration_ms(trace.duration_ms) %>
175
- <% else %>
176
- <span class="observ-text--muted">—</span>
177
- <% end %>
178
- </td>
179
- <td class="observ-table__cell observ-table__cell--numeric">
180
- <%= trace.observations.count %>
181
- </td>
182
- <td class="observ-table__cell observ-table__cell--numeric">
183
- <%= format_tokens(trace.total_tokens) %>
184
- </td>
185
- <td class="observ-table__cell observ-table__cell--numeric">
186
- <%= format_currency(trace.total_cost) %>
187
- </td>
188
- <td class="observ-table__cell">
189
- <%= observ_status_badge(observ_trace_status(trace)) %>
190
- </td>
191
- <td class="observ-table__cell observ-table__cell--actions">
192
- <%= link_to "View", trace_path(trace), class: "observ-button observ-button--sm" %>
193
- </td>
194
- </tr>
195
- <% end %>
196
- </tbody>
197
- </table>
198
- <% else %>
199
- <div class="observ-card__empty">
200
- <p>No traces found for this session.</p>
201
- </div>
202
- <% end %>
203
- </div>
204
- </section>
33
+ <%= render "observ/sessions/metadata", session: @session %>
205
34
 
206
- <% if @chat.present? %>
207
- <section class="observ-card">
208
- <header class="observ-card__header">
209
- <h2 class="observ-card__title">Chat Conversation</h2>
210
- <div class="observ-card__actions">
211
- <%= link_to "View Chat", chat_path(@chat), class: "observ-button observ-button--sm" %>
212
- </div>
213
- </header>
214
- <div class="observ-card__body">
215
- <% if @chat.messages.any? %>
216
- <div class="observ-chat-messages">
217
- <% @chat.messages.order(created_at: :asc).each do |message| %>
218
- <div class="observ-chat-message observ-chat-message--<%= message.role %>">
219
- <div class="observ-chat-message__header">
220
- <span class="observ-chat-message__role"><%= message.role.capitalize %></span>
221
- <span class="observ-chat-message__time"><%= observ_relative_time(message.created_at) %></span>
222
- <% if message.traces.exists? %>
223
- <%= link_to "View Trace", trace_path(message.traces.first), class: "observ-chat-message__trace-link" %>
224
- <% end %>
225
- <% if message.input_tokens || message.output_tokens %>
226
- <span class="observ-chat-message__tokens">
227
- <% if message.input_tokens %>
228
- <span>In: <%= format_tokens(message.input_tokens) %></span>
229
- <% end %>
230
- <% if message.output_tokens %>
231
- <span>Out: <%= format_tokens(message.output_tokens) %></span>
232
- <% end %>
233
- </span>
234
- <% end %>
235
- </div>
236
- <div class="observ-chat-message__content">
237
- <%= simple_format(message.content) %>
238
- </div>
239
- </div>
240
- <% end %>
241
- </div>
242
- <% else %>
243
- <div class="observ-card__empty">
244
- <p>No messages found in this chat.</p>
245
- </div>
246
- <% end %>
247
- </div>
248
- </section>
249
- <% end %>
35
+ <%= render "observ/sessions/traces", traces: @traces, session: @session %>
250
36
 
37
+ <%= render "observ/sessions/chat", chat: @chat %>
251
38
  </div>
@@ -0,0 +1,47 @@
1
+ <section class="observ-card observ-card--compact">
2
+ <header class="observ-card__header">
3
+ <h2 class="observ-card__title">Details</h2>
4
+ </header>
5
+ <div class="observ-card__body">
6
+ <dl class="observ-definition-list observ-definition-list--compact">
7
+ <div class="observ-definition-list__item">
8
+ <dt class="observ-definition-list__term">Session</dt>
9
+ <dd class="observ-definition-list__definition">
10
+ <%= link_to truncate_id(trace.observ_session.session_id, 12), session_path(trace.observ_session), class: "observ-link", target: local_assigns[:open_in_new_tab] ? "_blank" : nil %>
11
+ </dd>
12
+ </div>
13
+ <div class="observ-definition-list__item">
14
+ <dt class="observ-definition-list__term">Name</dt>
15
+ <dd class="observ-definition-list__definition"><%= trace.name || "—" %></dd>
16
+ </div>
17
+ <div class="observ-definition-list__item">
18
+ <dt class="observ-definition-list__term">Start Time</dt>
19
+ <dd class="observ-definition-list__definition"><%= observ_timestamp(trace.start_time) %></dd>
20
+ </div>
21
+ <% if trace.end_time %>
22
+ <div class="observ-definition-list__item">
23
+ <dt class="observ-definition-list__term">End Time</dt>
24
+ <dd class="observ-definition-list__definition"><%= observ_timestamp(trace.end_time) %></dd>
25
+ </div>
26
+ <% end %>
27
+ <div class="observ-definition-list__item">
28
+ <dt class="observ-definition-list__term">Duration</dt>
29
+ <dd class="observ-definition-list__definition">
30
+ <% if trace.duration_ms %>
31
+ <%= format_duration_ms(trace.duration_ms) %>
32
+ <% else %>
33
+ <span class="observ-text--muted">In Progress</span>
34
+ <% end %>
35
+ </dd>
36
+ </div>
37
+ <div class="observ-definition-list__item">
38
+ <dt class="observ-definition-list__term">Total Tokens</dt>
39
+ <dd class="observ-definition-list__definition"><%= format_tokens(trace.total_tokens) %></dd>
40
+ </div>
41
+ <div class="observ-definition-list__item">
42
+ <dt class="observ-definition-list__term">Total Cost</dt>
43
+ <dd class="observ-definition-list__definition"><%= format_currency(trace.total_cost) %></dd>
44
+ </div>
45
+ </dl>
46
+ </div>
47
+ </section>
@@ -0,0 +1,10 @@
1
+ <% if trace.input.present? %>
2
+ <section class="observ-card observ-card--compact">
3
+ <header class="observ-card__header">
4
+ <h2 class="observ-card__title">Input</h2>
5
+ </header>
6
+ <div class="observ-card__body">
7
+ <%= render_input_output(trace.input, compact: true) %>
8
+ </div>
9
+ </section>
10
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <% if trace.metadata.present? && trace.metadata.any? %>
2
+ <section class="observ-card observ-card--compact">
3
+ <header class="observ-card__header">
4
+ <h2 class="observ-card__title">Metadata</h2>
5
+ </header>
6
+ <div class="observ-card__body">
7
+ <%= render_json_viewer(trace.metadata, compact: true) %>
8
+ </div>
9
+ </section>
10
+ <% end %>
@@ -0,0 +1,172 @@
1
+ <%
2
+ # Local variables:
3
+ # - observations: collection of observations to display
4
+ # - limit: optional, if set will show only first N observations
5
+ %>
6
+
7
+ <section class="observ-card">
8
+ <header class="observ-card__header">
9
+ <h2 class="observ-card__title">
10
+ Observations (<%= observations.count %>)
11
+ <% if local_assigns[:show_total] && local_assigns[:total_count] && total_count > observations.count %>
12
+ of <%= total_count %>
13
+ <% end %>
14
+ </h2>
15
+ </header>
16
+ <div class="observ-card__body">
17
+ <% if observations.any? %>
18
+ <div class="observ-observations-list observ-observations-list--compact">
19
+ <% observations.each do |observation| %>
20
+ <div class="observ-observation-card observ-observation-card--compact observ-observation-card--<%= observation.type.demodulize.downcase %>">
21
+ <div class="observ-observation-card__header">
22
+ <span class="observ-observation-card__type-badge">
23
+ <%= observation.type.demodulize %>
24
+ </span>
25
+ <h3 class="observ-observation-card__name"><%= observation.name %></h3>
26
+ <div class="observ-observation-card__meta">
27
+ <span class="observ-observation-card__id">
28
+ <code class="observ-code observ-code--inline"><%= truncate_id(observation.observation_id, 8) %></code>
29
+ </span>
30
+ </div>
31
+ </div>
32
+
33
+ <div class="observ-observation-card__body">
34
+ <dl class="observ-definition-list observ-definition-list--horizontal observ-definition-list--compact">
35
+ <% if observation.is_a?(Observ::Generation) %>
36
+ <div class="observ-definition-list__item">
37
+ <dt class="observ-definition-list__term">Model</dt>
38
+ <dd class="observ-definition-list__definition">
39
+ <%= observ_model_badge(observation.model) %>
40
+ </dd>
41
+ </div>
42
+ <div class="observ-definition-list__item">
43
+ <dt class="observ-definition-list__term">Tokens</dt>
44
+ <dd class="observ-definition-list__definition">
45
+ <%= format_tokens(observation.total_tokens) %>
46
+ </dd>
47
+ </div>
48
+ <div class="observ-definition-list__item">
49
+ <dt class="observ-definition-list__term">Cost</dt>
50
+ <dd class="observ-definition-list__definition">
51
+ <%= format_currency(observation.cost_usd) %>
52
+ </dd>
53
+ </div>
54
+ <% if observation.finish_reason %>
55
+ <div class="observ-definition-list__item">
56
+ <dt class="observ-definition-list__term">Finish</dt>
57
+ <dd class="observ-definition-list__definition">
58
+ <%= observation.finish_reason %>
59
+ </dd>
60
+ </div>
61
+ <% end %>
62
+ <% if observation.time_to_first_token_ms %>
63
+ <div class="observ-definition-list__item">
64
+ <dt class="observ-definition-list__term">TTFT</dt>
65
+ <dd class="observ-definition-list__definition">
66
+ <%= format_duration_ms(observation.time_to_first_token_ms) %>
67
+ </dd>
68
+ </div>
69
+ <% end %>
70
+ <div class="observ-definition-list__item">
71
+ <dt class="observ-definition-list__term">Prompt</dt>
72
+ <dd class="observ-definition-list__definition">
73
+ <% if observation.prompt_name.present? %>
74
+ <%= observation.prompt_name %>
75
+ <% if observation.prompt_version.present? %>
76
+ <span class="observ-text--muted">(v<%= observation.prompt_version %>)</span>
77
+ <% end %>
78
+ <% else %>
79
+ <span class="observ-text--muted">default prompt</span>
80
+ <% end %>
81
+ </dd>
82
+ </div>
83
+ <% end %>
84
+ <div class="observ-definition-list__item">
85
+ <dt class="observ-definition-list__term">Duration</dt>
86
+ <dd class="observ-definition-list__definition">
87
+ <% if observation.duration_ms %>
88
+ <%= format_duration_ms(observation.duration_ms) %>
89
+ <% else %>
90
+ <span class="observ-text--muted">—</span>
91
+ <% end %>
92
+ </dd>
93
+ </div>
94
+ <% if observation.is_a?(Observ::Span) && observation.status_message %>
95
+ <div class="observ-definition-list__item">
96
+ <dt class="observ-definition-list__term">Status</dt>
97
+ <dd class="observ-definition-list__definition">
98
+ <%= observation.status_message %>
99
+ </dd>
100
+ </div>
101
+ <% end %>
102
+ </dl>
103
+ </div>
104
+
105
+ <% if observation.is_a?(Observ::Generation) && observation.usage.present? && observation.usage.any? %>
106
+ <div class="observ-observation-card__section observ-observation-card__section--compact">
107
+ <h4 class="observ-observation-card__section-title">Token Usage</h4>
108
+ <dl class="observ-definition-list observ-definition-list--horizontal observ-definition-list--compact">
109
+ <% observation.usage.each do |key, value| %>
110
+ <div class="observ-definition-list__item">
111
+ <dt class="observ-definition-list__term"><%= key.humanize %></dt>
112
+ <dd class="observ-definition-list__definition"><%= format_tokens(value) %></dd>
113
+ </div>
114
+ <% end %>
115
+ </dl>
116
+ </div>
117
+ <% end %>
118
+
119
+ <% if observation.input.present? %>
120
+ <div class="observ-observation-card__section observ-observation-card__section--compact">
121
+ <h4 class="observ-observation-card__section-title">Input</h4>
122
+ <%= render_input_output(observation.input, compact: true) %>
123
+ </div>
124
+ <% end %>
125
+
126
+ <% if observation.output.present? %>
127
+ <div class="observ-observation-card__section observ-observation-card__section--compact">
128
+ <h4 class="observ-observation-card__section-title">Output</h4>
129
+ <%= render_input_output(observation.output, compact: true) %>
130
+ </div>
131
+ <% end %>
132
+
133
+ <% if observation.is_a?(Observ::Generation) && observation.messages.present? && observation.messages.any? %>
134
+ <div class="observ-observation-card__section observ-observation-card__section--compact">
135
+ <h4 class="observ-observation-card__section-title">Messages</h4>
136
+ <%= render_json_viewer(observation.messages, compact: true) %>
137
+ </div>
138
+ <% end %>
139
+
140
+ <% if observation.is_a?(Observ::Generation) && observation.provider_metadata.present? && observation.provider_metadata.any? %>
141
+ <div class="observ-observation-card__section observ-observation-card__section--compact">
142
+ <h4 class="observ-observation-card__section-title">Provider Metadata</h4>
143
+ <%= render_json_viewer(observation.provider_metadata, compact: true) %>
144
+ </div>
145
+ <% end %>
146
+
147
+ <% if observation.is_a?(Observ::Span) && observation.metadata.present? && observation.metadata.any? %>
148
+ <div class="observ-observation-card__section observ-observation-card__section--compact">
149
+ <h4 class="observ-observation-card__section-title">Metadata</h4>
150
+ <%= render_json_viewer(observation.metadata, compact: true) %>
151
+ </div>
152
+ <% end %>
153
+
154
+ <div class="observ-observation-card__footer">
155
+ <%= link_to "View Details →", observation_path(observation), class: "observ-link" %>
156
+ </div>
157
+ </div>
158
+ <% end %>
159
+ </div>
160
+ <% if local_assigns[:show_total] && local_assigns[:total_count] && total_count > observations.count %>
161
+ <p class="observ-text--muted observ-review-preview-notice">
162
+ Showing first <%= observations.count %> observations.
163
+ <%= link_to "View all #{total_count} observations", trace_path(trace), class: "observ-link", target: "_blank" %>
164
+ </p>
165
+ <% end %>
166
+ <% else %>
167
+ <div class="observ-card__empty">
168
+ <p>No observations found for this trace.</p>
169
+ </div>
170
+ <% end %>
171
+ </div>
172
+ </section>
@@ -0,0 +1,10 @@
1
+ <% if trace.output.present? %>
2
+ <section class="observ-card observ-card--compact">
3
+ <header class="observ-card__header">
4
+ <h2 class="observ-card__title">Output</h2>
5
+ </header>
6
+ <div class="observ-card__body">
7
+ <%= render_input_output(trace.output, compact: true) %>
8
+ </div>
9
+ </section>
10
+ <% end %>
@@ -1,9 +1,16 @@
1
1
  <%= turbo_stream.update "drawer-header-title" do %>
2
- Trace Annotations
2
+ Scores & Annotations
3
3
  <% end %>
4
4
 
5
5
  <%= turbo_stream.update "drawer-content" do %>
6
6
  <div class="observ-drawer__section">
7
+ <h3 class="observ-drawer__section-title">Score</h3>
8
+ <div id="score-status"></div>
9
+ <%= render "observ/scores/form", scoreable: @trace %>
10
+ </div>
11
+
12
+ <div class="observ-drawer__section">
13
+ <h3 class="observ-drawer__section-title">Add Annotation</h3>
7
14
  <%= render "observ/annotations/form", annotatable: @trace, annotation: @annotation %>
8
15
  </div>
9
16
 
@@ -77,11 +77,10 @@
77
77
  <% else %>
78
78
  <div class="observ-card__empty">
79
79
  <p>No traces found.</p>
80
- </div>
81
- <% end %>
80
+ </div>
81
+ <% end %>
82
+ </div>
82
83
  </section>
83
84
 
84
85
  <%= observ_pagination(@traces) %>
85
- </div>
86
- </section>
87
86
  </div>