rubyllm-observ 0.5.1 → 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.
- checksums.yaml +4 -4
- data/README.md +54 -6
- data/app/assets/stylesheets/observ/_annotations.scss +114 -103
- data/app/assets/stylesheets/observ/_card.scss +58 -49
- data/app/assets/stylesheets/observ/_chat.scss +247 -155
- data/app/assets/stylesheets/observ/_components.scss +622 -340
- data/app/assets/stylesheets/observ/_dashboard.scss +31 -28
- data/app/assets/stylesheets/observ/_datasets.scss +494 -547
- data/app/assets/stylesheets/observ/_drawer.scss +250 -228
- data/app/assets/stylesheets/observ/_filters.scss +139 -0
- data/app/assets/stylesheets/observ/_json_viewer.scss +103 -97
- data/app/assets/stylesheets/observ/_layout.scss +443 -178
- data/app/assets/stylesheets/observ/_metrics.scss +79 -76
- data/app/assets/stylesheets/observ/_namespace.scss +18 -0
- data/app/assets/stylesheets/observ/_observations.scss +122 -119
- data/app/assets/stylesheets/observ/_pagination.scss +129 -112
- data/app/assets/stylesheets/observ/_prompts.scss +485 -269
- data/app/assets/stylesheets/observ/_reset.scss +249 -0
- data/app/assets/stylesheets/observ/_table.scss +46 -38
- data/app/assets/stylesheets/observ/_variables.scss +54 -0
- data/app/assets/stylesheets/observ/application.scss +3 -0
- data/app/controllers/observ/dataset_run_items_controller.rb +0 -1
- data/app/controllers/observ/review_queue_controller.rb +154 -0
- data/app/controllers/observ/scores_controller.rb +64 -0
- data/app/controllers/observ/sessions_controller.rb +23 -0
- data/app/helpers/observ/application_helper.rb +1 -0
- data/app/helpers/observ/reviews_helper.rb +33 -0
- data/app/models/concerns/observ/json_queryable.rb +138 -0
- data/app/models/concerns/observ/reviewable.rb +41 -0
- data/app/models/concerns/observ/scoreable.rb +34 -0
- data/app/models/observ/dataset_run_item.rb +3 -13
- data/app/models/observ/review_item.rb +48 -0
- data/app/models/observ/score.rb +38 -6
- data/app/models/observ/session.rb +5 -1
- data/app/models/observ/trace.rb +3 -0
- data/app/services/observ/evaluators/base_evaluator.rb +0 -1
- data/app/services/observ/guardrail_service.rb +128 -0
- data/app/views/kaminari/_first_page.html.erb +1 -1
- data/app/views/kaminari/_gap.html.erb +1 -1
- data/app/views/kaminari/_last_page.html.erb +1 -1
- data/app/views/kaminari/_next_page.html.erb +1 -1
- data/app/views/kaminari/_page.html.erb +1 -1
- data/app/views/kaminari/_paginator.html.erb +1 -1
- data/app/views/kaminari/_prev_page.html.erb +1 -1
- data/app/views/kaminari/observ/_first_page.html.erb +1 -1
- data/app/views/kaminari/observ/_gap.html.erb +1 -1
- data/app/views/kaminari/observ/_last_page.html.erb +1 -1
- data/app/views/kaminari/observ/_next_page.html.erb +1 -1
- data/app/views/kaminari/observ/_page.html.erb +1 -1
- data/app/views/kaminari/observ/_paginator.html.erb +1 -1
- data/app/views/kaminari/observ/_prev_page.html.erb +1 -1
- data/app/views/layouts/observ/application.html.erb +96 -58
- data/app/views/observ/annotations/_form.html.erb +5 -5
- data/app/views/observ/annotations/index.html.erb +4 -4
- data/app/views/observ/annotations/sessions_index.html.erb +9 -9
- data/app/views/observ/annotations/traces_index.html.erb +9 -9
- data/app/views/observ/chats/_form.html.erb +7 -7
- data/app/views/observ/datasets/index.html.erb +6 -6
- data/app/views/observ/messages/_form.html.erb +11 -12
- data/app/views/observ/observations/index.html.erb +3 -4
- data/app/views/observ/prompts/_form.html.erb +37 -38
- data/app/views/observ/prompts/_new_form.html.erb +37 -38
- data/app/views/observ/prompts/compare.html.erb +59 -55
- data/app/views/observ/prompts/edit.html.erb +3 -3
- data/app/views/observ/prompts/index.html.erb +9 -9
- data/app/views/observ/prompts/new.html.erb +3 -3
- data/app/views/observ/prompts/show.html.erb +2 -2
- data/app/views/observ/prompts/versions.html.erb +22 -22
- data/app/views/observ/review_queue/_item.html.erb +39 -0
- data/app/views/observ/review_queue/_stats.html.erb +18 -0
- data/app/views/observ/review_queue/index.html.erb +49 -0
- data/app/views/observ/review_queue/show.html.erb +76 -0
- data/app/views/observ/review_queue/stats.html.erb +100 -0
- data/app/views/observ/scores/_form.html.erb +39 -0
- data/app/views/observ/scores/create.turbo_stream.erb +10 -0
- data/app/views/observ/sessions/_chat.html.erb +59 -0
- data/app/views/observ/sessions/_metadata.html.erb +17 -0
- data/app/views/observ/sessions/_metrics.html.erb +81 -0
- data/app/views/observ/sessions/_traces.html.erb +92 -0
- data/app/views/observ/sessions/annotations_drawer.turbo_stream.erb +8 -1
- data/app/views/observ/sessions/index.html.erb +60 -4
- data/app/views/observ/sessions/show.html.erb +4 -217
- data/app/views/observ/traces/_details.html.erb +47 -0
- data/app/views/observ/traces/_input.html.erb +10 -0
- data/app/views/observ/traces/_metadata.html.erb +10 -0
- data/app/views/observ/traces/_observations.html.erb +172 -0
- data/app/views/observ/traces/_output.html.erb +10 -0
- data/app/views/observ/traces/annotations_drawer.turbo_stream.erb +8 -1
- data/app/views/observ/traces/index.html.erb +3 -4
- data/app/views/observ/traces/show.html.erb +5 -232
- data/config/routes.rb +14 -0
- data/db/migrate/015_refactor_scores_to_polymorphic.rb +27 -0
- data/db/migrate/016_create_observ_review_items.rb +25 -0
- data/lib/observ/version.rb +1 -1
- metadata +30 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<div class="observ-page">
|
|
2
|
+
<div class="observ-page__header">
|
|
3
|
+
<h1 class="observ-page__title">Review Queue</h1>
|
|
4
|
+
<div class="observ-page__actions">
|
|
5
|
+
<%= link_to "Stats", stats_reviews_path, class: "observ-button observ-button--secondary" %>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="observ-tabs">
|
|
10
|
+
<%= link_to "All", reviews_path, class: "observ-tab #{'observ-tab--active' if @filter == :all}" %>
|
|
11
|
+
<%= link_to "Sessions", sessions_reviews_path, class: "observ-tab #{'observ-tab--active' if @filter == :sessions}" %>
|
|
12
|
+
<%= link_to "Traces", traces_reviews_path, class: "observ-tab #{'observ-tab--active' if @filter == :traces}" %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<%= render "stats", stats: @stats %>
|
|
16
|
+
|
|
17
|
+
<% if @review_items.any? %>
|
|
18
|
+
<div class="observ-card">
|
|
19
|
+
<div class="observ-card__body">
|
|
20
|
+
<table class="observ-table">
|
|
21
|
+
<thead class="observ-table__header">
|
|
22
|
+
<tr class="observ-table__row">
|
|
23
|
+
<th class="observ-table__cell">Priority</th>
|
|
24
|
+
<th class="observ-table__cell">Type</th>
|
|
25
|
+
<th class="observ-table__cell">Reason</th>
|
|
26
|
+
<th class="observ-table__cell">Status</th>
|
|
27
|
+
<th class="observ-table__cell">Created</th>
|
|
28
|
+
<th class="observ-table__cell"></th>
|
|
29
|
+
</tr>
|
|
30
|
+
</thead>
|
|
31
|
+
<tbody>
|
|
32
|
+
<% @review_items.each do |review_item| %>
|
|
33
|
+
<%= render "item", review_item: review_item %>
|
|
34
|
+
<% end %>
|
|
35
|
+
</tbody>
|
|
36
|
+
</table>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<%= observ_pagination(@review_items) %>
|
|
41
|
+
<% else %>
|
|
42
|
+
<div class="observ-empty-state">
|
|
43
|
+
<p class="observ-empty-state__text">No items to review</p>
|
|
44
|
+
<p class="observ-empty-state__subtext">
|
|
45
|
+
Items will appear here when they are flagged by guardrails or added manually.
|
|
46
|
+
</p>
|
|
47
|
+
</div>
|
|
48
|
+
<% end %>
|
|
49
|
+
</div>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<div class="observ-page">
|
|
2
|
+
<div class="observ-page__header">
|
|
3
|
+
<div class="observ-page__breadcrumb">
|
|
4
|
+
<%= link_to "Review Queue", reviews_path %> /
|
|
5
|
+
<span>Review Item</span>
|
|
6
|
+
</div>
|
|
7
|
+
<h1 class="observ-page__title">
|
|
8
|
+
Review <%= @review_item.reviewable_type_display %>
|
|
9
|
+
<span class="observ-badge <%= @review_item.priority_badge_class %>">
|
|
10
|
+
<%= @review_item.priority.capitalize %>
|
|
11
|
+
</span>
|
|
12
|
+
</h1>
|
|
13
|
+
<% if @queue_position %>
|
|
14
|
+
<p class="observ-text--muted">
|
|
15
|
+
Reviewing <%= @queue_position[:current] %> of <%= @queue_position[:total] %>
|
|
16
|
+
</p>
|
|
17
|
+
<% end %>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="observ-review-layout">
|
|
21
|
+
<div class="observ-review-main">
|
|
22
|
+
<div class="observ-alert observ-alert--warning">
|
|
23
|
+
<strong>Flagged: <%= @review_item.reason_display %></strong>
|
|
24
|
+
<% if @review_item.reason_details.present? %>
|
|
25
|
+
<% @review_item.reason_details.each do |key, value| %>
|
|
26
|
+
<br><%= key.to_s.humanize %>: <%= value %>
|
|
27
|
+
<% end %>
|
|
28
|
+
<% end %>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<% if @reviewable.is_a?(Observ::Trace) %>
|
|
32
|
+
<%= render "observ/traces/details", trace: @reviewable, open_in_new_tab: true %>
|
|
33
|
+
|
|
34
|
+
<%= render "observ/traces/input", trace: @reviewable %>
|
|
35
|
+
|
|
36
|
+
<%= render "observ/traces/output", trace: @reviewable %>
|
|
37
|
+
|
|
38
|
+
<%= render "observ/traces/metadata", trace: @reviewable %>
|
|
39
|
+
|
|
40
|
+
<%= render "observ/traces/observations", observations: @observations, trace: @reviewable, show_total: true, total_count: @reviewable.observations.count %>
|
|
41
|
+
<% elsif @reviewable.is_a?(Observ::Session) %>
|
|
42
|
+
<%= render "observ/sessions/metrics", session_metrics: @session_metrics, session: @reviewable %>
|
|
43
|
+
|
|
44
|
+
<%= render "observ/sessions/metadata", session: @reviewable %>
|
|
45
|
+
|
|
46
|
+
<%= render "observ/sessions/traces", traces: @traces, session: @reviewable, show_total: true, total_count: @session_metrics[:total_traces], open_in_new_tab: true %>
|
|
47
|
+
|
|
48
|
+
<%= render "observ/sessions/chat", chat: @chat, limit: 10, hide_actions: true %>
|
|
49
|
+
<% end %>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="observ-review-sidebar">
|
|
53
|
+
<div class="observ-card">
|
|
54
|
+
<div class="observ-card__header">
|
|
55
|
+
<h2 class="observ-card__title">Score This Item</h2>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="observ-card__body">
|
|
58
|
+
<%= render "observ/scores/form", scoreable: @reviewable %>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div class="observ-review-actions">
|
|
63
|
+
<%= form_with url: complete_review_path(@review_item), method: :post, local: true do |f| %>
|
|
64
|
+
<input type="hidden" name="next" value="true">
|
|
65
|
+
<%= f.submit "Save & Next", class: "observ-button observ-button--primary observ-button--block" %>
|
|
66
|
+
<% end %>
|
|
67
|
+
|
|
68
|
+
<%= form_with url: skip_review_path(@review_item), method: :post, local: true do |f| %>
|
|
69
|
+
<%= f.submit "Skip", class: "observ-button observ-button--secondary observ-button--block" %>
|
|
70
|
+
<% end %>
|
|
71
|
+
|
|
72
|
+
<%= link_to "Back to Queue", reviews_path, class: "observ-button observ-button--link observ-button--block" %>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<div class="observ-page">
|
|
2
|
+
<div class="observ-page__header">
|
|
3
|
+
<h1 class="observ-page__title">Review Queue Statistics</h1>
|
|
4
|
+
<div class="observ-page__actions">
|
|
5
|
+
<%= link_to "Back to Queue", reviews_path, class: "observ-button observ-button--secondary" %>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="observ-page__content">
|
|
10
|
+
<div class="observ-grid observ-grid--4">
|
|
11
|
+
<div class="observ-card">
|
|
12
|
+
<div class="observ-card__body observ-text--center">
|
|
13
|
+
<div class="observ-stat__value observ-stat__value--large"><%= @stats[:total_pending] %></div>
|
|
14
|
+
<div class="observ-stat__label">Total Pending</div>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="observ-card">
|
|
19
|
+
<div class="observ-card__body observ-text--center">
|
|
20
|
+
<div class="observ-stat__value observ-stat__value--large"><%= @stats[:total_completed] %></div>
|
|
21
|
+
<div class="observ-stat__label">Total Completed</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="observ-card">
|
|
26
|
+
<div class="observ-card__body observ-text--center">
|
|
27
|
+
<div class="observ-stat__value observ-stat__value--large"><%= @stats[:completed_today] %></div>
|
|
28
|
+
<div class="observ-stat__label">Completed Today</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="observ-card">
|
|
33
|
+
<div class="observ-card__body observ-text--center">
|
|
34
|
+
<div class="observ-stat__value observ-stat__value--large"><%= @stats[:completed_this_week] %></div>
|
|
35
|
+
<div class="observ-stat__label">This Week</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="observ-grid observ-grid--2">
|
|
41
|
+
<div class="observ-card">
|
|
42
|
+
<div class="observ-card__header">
|
|
43
|
+
<h3 class="observ-card__title">Pass Rate</h3>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="observ-card__body observ-text--center">
|
|
46
|
+
<% if @stats[:pass_rate] %>
|
|
47
|
+
<div class="observ-stat__value observ-stat__value--large <%= @stats[:pass_rate] >= 70 ? 'observ-text--success' : (@stats[:pass_rate] >= 40 ? 'observ-text--warning' : 'observ-text--danger') %>">
|
|
48
|
+
<%= @stats[:pass_rate] %>%
|
|
49
|
+
</div>
|
|
50
|
+
<div class="observ-stat__label">of reviewed items passed</div>
|
|
51
|
+
<% else %>
|
|
52
|
+
<div class="observ-text--muted">No completed reviews yet</div>
|
|
53
|
+
<% end %>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="observ-card">
|
|
58
|
+
<div class="observ-card__header">
|
|
59
|
+
<h3 class="observ-card__title">By Priority</h3>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="observ-card__body">
|
|
62
|
+
<% if @stats[:by_priority].any? %>
|
|
63
|
+
<div class="observ-stat-list">
|
|
64
|
+
<% @stats[:by_priority].each do |priority, count| %>
|
|
65
|
+
<div class="observ-stat-list__item">
|
|
66
|
+
<span class="observ-badge <%= priority_badge_class(priority) %>">
|
|
67
|
+
<%= priority.to_s.titleize %>
|
|
68
|
+
</span>
|
|
69
|
+
<span class="observ-stat-list__value"><%= count %></span>
|
|
70
|
+
</div>
|
|
71
|
+
<% end %>
|
|
72
|
+
</div>
|
|
73
|
+
<% else %>
|
|
74
|
+
<div class="observ-text--muted">No data</div>
|
|
75
|
+
<% end %>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div class="observ-card">
|
|
81
|
+
<div class="observ-card__header">
|
|
82
|
+
<h3 class="observ-card__title">By Reason</h3>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="observ-card__body">
|
|
85
|
+
<% if @stats[:by_reason].any? %>
|
|
86
|
+
<div class="observ-stat-list observ-stat-list--spacious">
|
|
87
|
+
<% @stats[:by_reason].sort_by { |_, count| -count }.each do |reason, count| %>
|
|
88
|
+
<div class="observ-stat-list__item">
|
|
89
|
+
<span class="observ-stat-list__label"><%= reason&.titleize&.gsub("_", " ") || "Manual" %></span>
|
|
90
|
+
<span class="observ-stat-list__value"><%= count %></span>
|
|
91
|
+
</div>
|
|
92
|
+
<% end %>
|
|
93
|
+
</div>
|
|
94
|
+
<% else %>
|
|
95
|
+
<div class="observ-text--muted">No data</div>
|
|
96
|
+
<% end %>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<%# Score form partial for scoring sessions and traces %>
|
|
2
|
+
<%# Usage: render "observ/scores/form", scoreable: @trace %>
|
|
3
|
+
|
|
4
|
+
<% score_url = scoreable.is_a?(Observ::Session) ? session_scores_path(scoreable) : trace_scores_path(scoreable) %>
|
|
5
|
+
<% existing = scoreable.manual_score %>
|
|
6
|
+
|
|
7
|
+
<%= form_with(
|
|
8
|
+
url: score_url,
|
|
9
|
+
id: "score-form",
|
|
10
|
+
class: "observ-score-form",
|
|
11
|
+
data: { turbo_frame: "_top" }
|
|
12
|
+
) do |form| %>
|
|
13
|
+
<div class="observ-form-field">
|
|
14
|
+
<label class="observ-form-label">Score this <%= scoreable.class.name.demodulize.downcase %></label>
|
|
15
|
+
<div class="observ-score-buttons">
|
|
16
|
+
<label class="observ-score-button <%= 'observ-score-button--selected' if existing&.passed? %>">
|
|
17
|
+
<input type="radio" name="value" value="1" <%= "checked" if existing&.passed? %>>
|
|
18
|
+
<span class="observ-score-icon observ-score-icon--pass">✓</span>
|
|
19
|
+
Good
|
|
20
|
+
</label>
|
|
21
|
+
<label class="observ-score-button <%= 'observ-score-button--selected' if existing&.failed? %>">
|
|
22
|
+
<input type="radio" name="value" value="0" <%= "checked" if existing&.failed? %>>
|
|
23
|
+
<span class="observ-score-icon observ-score-icon--fail">✕</span>
|
|
24
|
+
Bad
|
|
25
|
+
</label>
|
|
26
|
+
</div>
|
|
27
|
+
<input type="hidden" name="data_type" value="boolean">
|
|
28
|
+
<input type="hidden" name="name" value="manual">
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="observ-form-field">
|
|
32
|
+
<label class="observ-form-label" for="score_comment">Comment (optional)</label>
|
|
33
|
+
<textarea name="comment" id="score_comment" class="observ-form-textarea" rows="2" placeholder="Add a comment..."><%= existing&.comment %></textarea>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="observ-form-actions">
|
|
37
|
+
<%= form.submit "Save Score", class: "observ-button observ-button--primary" %>
|
|
38
|
+
</div>
|
|
39
|
+
<% end %>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%= turbo_stream.replace "score-form" do %>
|
|
2
|
+
<%= render "observ/scores/form", scoreable: @scoreable %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<%= turbo_stream.replace "score-status" do %>
|
|
6
|
+
<div id="score-status" class="observ-score-status observ-score-status--saved">
|
|
7
|
+
<span class="observ-score-status__icon">✓</span>
|
|
8
|
+
Score saved
|
|
9
|
+
</div>
|
|
10
|
+
<% end %>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<%
|
|
2
|
+
# Local variables:
|
|
3
|
+
# - chat: the chat object
|
|
4
|
+
# - limit: optional, number of messages to show (default: all)
|
|
5
|
+
%>
|
|
6
|
+
|
|
7
|
+
<% if chat.present? %>
|
|
8
|
+
<section class="observ-card">
|
|
9
|
+
<header class="observ-card__header">
|
|
10
|
+
<h2 class="observ-card__title">Chat Conversation</h2>
|
|
11
|
+
<% unless local_assigns[:hide_actions] %>
|
|
12
|
+
<div class="observ-card__actions">
|
|
13
|
+
<%= link_to "View Chat", chat_path(chat), class: "observ-button observ-button--sm" %>
|
|
14
|
+
</div>
|
|
15
|
+
<% end %>
|
|
16
|
+
</header>
|
|
17
|
+
<div class="observ-card__body">
|
|
18
|
+
<% if chat.messages.any? %>
|
|
19
|
+
<% messages = local_assigns[:limit] ? chat.messages.order(created_at: :asc).limit(limit) : chat.messages.order(created_at: :asc) %>
|
|
20
|
+
<div class="observ-chat-messages">
|
|
21
|
+
<% messages.each do |message| %>
|
|
22
|
+
<div class="observ-chat-message observ-chat-message--<%= message.role %>">
|
|
23
|
+
<div class="observ-chat-message__header">
|
|
24
|
+
<span class="observ-chat-message__role"><%= message.role.capitalize %></span>
|
|
25
|
+
<span class="observ-chat-message__time"><%= observ_relative_time(message.created_at) %></span>
|
|
26
|
+
<% if message.traces.exists? %>
|
|
27
|
+
<%= link_to "View Trace", trace_path(message.traces.first), class: "observ-chat-message__trace-link" %>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% if message.input_tokens || message.output_tokens %>
|
|
30
|
+
<span class="observ-chat-message__tokens">
|
|
31
|
+
<% if message.input_tokens %>
|
|
32
|
+
<span>In: <%= format_tokens(message.input_tokens) %></span>
|
|
33
|
+
<% end %>
|
|
34
|
+
<% if message.output_tokens %>
|
|
35
|
+
<span>Out: <%= format_tokens(message.output_tokens) %></span>
|
|
36
|
+
<% end %>
|
|
37
|
+
</span>
|
|
38
|
+
<% end %>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="observ-chat-message__content">
|
|
41
|
+
<%= simple_format(message.content) %>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<% end %>
|
|
45
|
+
</div>
|
|
46
|
+
<% if local_assigns[:limit] && chat.messages.count > limit %>
|
|
47
|
+
<p class="observ-text--muted observ-review-preview-notice">
|
|
48
|
+
Showing first <%= limit %> messages.
|
|
49
|
+
<%= link_to "View all #{chat.messages.count} messages", chat_path(chat), class: "observ-link", target: "_blank" %>
|
|
50
|
+
</p>
|
|
51
|
+
<% end %>
|
|
52
|
+
<% else %>
|
|
53
|
+
<div class="observ-card__empty">
|
|
54
|
+
<p>No messages found in this chat.</p>
|
|
55
|
+
</div>
|
|
56
|
+
<% end %>
|
|
57
|
+
</div>
|
|
58
|
+
</section>
|
|
59
|
+
<% end %>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<% if session.metadata.present? && session.metadata.any? %>
|
|
2
|
+
<section class="observ-card">
|
|
3
|
+
<header class="observ-card__header">
|
|
4
|
+
<h2 class="observ-card__title">Metadata</h2>
|
|
5
|
+
</header>
|
|
6
|
+
<div class="observ-card__body">
|
|
7
|
+
<dl class="observ-definition-list">
|
|
8
|
+
<% session.metadata.each do |key, value| %>
|
|
9
|
+
<div class="observ-definition-list__item">
|
|
10
|
+
<dt class="observ-definition-list__term"><%= key %></dt>
|
|
11
|
+
<dd class="observ-definition-list__definition"><%= value %></dd>
|
|
12
|
+
</div>
|
|
13
|
+
<% end %>
|
|
14
|
+
</dl>
|
|
15
|
+
</div>
|
|
16
|
+
</section>
|
|
17
|
+
<% end %>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<section class="observ-metrics-grid observ-metrics-grid--4col">
|
|
2
|
+
<div class="observ-metric-card">
|
|
3
|
+
<div class="observ-metric-card__header">
|
|
4
|
+
<h3 class="observ-metric-card__label">Traces</h3>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="observ-metric-card__body">
|
|
7
|
+
<p class="observ-metric-card__value"><%= session_metrics[:total_traces] %></p>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div class="observ-metric-card">
|
|
12
|
+
<div class="observ-metric-card__header">
|
|
13
|
+
<h3 class="observ-metric-card__label">LLM Calls</h3>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="observ-metric-card__body">
|
|
16
|
+
<p class="observ-metric-card__value"><%= session_metrics[:total_llm_calls] %></p>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="observ-metric-card">
|
|
21
|
+
<div class="observ-metric-card__header">
|
|
22
|
+
<h3 class="observ-metric-card__label">Total Tokens</h3>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="observ-metric-card__body">
|
|
25
|
+
<p class="observ-metric-card__value"><%= format_tokens(session_metrics[:total_tokens]) %></p>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="observ-metric-card observ-metric-card--highlighted">
|
|
30
|
+
<div class="observ-metric-card__header">
|
|
31
|
+
<h3 class="observ-metric-card__label">Total Cost</h3>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="observ-metric-card__body">
|
|
34
|
+
<p class="observ-metric-card__value"><%= format_currency(session_metrics[:total_cost]) %></p>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="observ-metric-card">
|
|
39
|
+
<div class="observ-metric-card__header">
|
|
40
|
+
<h3 class="observ-metric-card__label">Duration</h3>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="observ-metric-card__body">
|
|
43
|
+
<p class="observ-metric-card__value">
|
|
44
|
+
<% if session_metrics[:duration_s] %>
|
|
45
|
+
<%= format_duration_s(session_metrics[:duration_s]) %>
|
|
46
|
+
<% else %>
|
|
47
|
+
<span class="observ-text--muted">In Progress</span>
|
|
48
|
+
<% end %>
|
|
49
|
+
</p>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="observ-metric-card">
|
|
54
|
+
<div class="observ-metric-card__header">
|
|
55
|
+
<h3 class="observ-metric-card__label">Avg Latency</h3>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="observ-metric-card__body">
|
|
58
|
+
<p class="observ-metric-card__value"><%= format_duration_ms(session_metrics[:average_llm_latency_ms]) %></p>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div class="observ-metric-card">
|
|
63
|
+
<div class="observ-metric-card__header">
|
|
64
|
+
<h3 class="observ-metric-card__label">Total LLM Time</h3>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="observ-metric-card__body">
|
|
67
|
+
<p class="observ-metric-card__value"><%= format_duration_ms(session_metrics[:total_llm_duration_ms]) %></p>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="observ-metric-card">
|
|
72
|
+
<div class="observ-metric-card__header">
|
|
73
|
+
<h3 class="observ-metric-card__label">Started</h3>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="observ-metric-card__body">
|
|
76
|
+
<p class="observ-metric-card__value observ-metric-card__value--small">
|
|
77
|
+
<%= observ_relative_time(session.start_time) %>
|
|
78
|
+
</p>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</section>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<%
|
|
2
|
+
# Local variables:
|
|
3
|
+
# - traces: collection of traces to display
|
|
4
|
+
# - session: the session object
|
|
5
|
+
# - limit: optional, if set will show "X of Y" notice
|
|
6
|
+
%>
|
|
7
|
+
|
|
8
|
+
<section class="observ-card">
|
|
9
|
+
<header class="observ-card__header">
|
|
10
|
+
<h2 class="observ-card__title">
|
|
11
|
+
Traces (<%= traces.count %>)
|
|
12
|
+
<% if local_assigns[:show_total] && local_assigns[:total_count] && total_count > traces.count %>
|
|
13
|
+
of <%= total_count %>
|
|
14
|
+
<% end %>
|
|
15
|
+
</h2>
|
|
16
|
+
</header>
|
|
17
|
+
<div class="observ-card__body">
|
|
18
|
+
<% if traces.any? %>
|
|
19
|
+
<table class="observ-table">
|
|
20
|
+
<thead class="observ-table__header">
|
|
21
|
+
<tr class="observ-table__row">
|
|
22
|
+
<th class="observ-table__cell">Trace ID</th>
|
|
23
|
+
<th class="observ-table__cell">Name</th>
|
|
24
|
+
<th class="observ-table__cell">Model(s)</th>
|
|
25
|
+
<th class="observ-table__cell">Start Time</th>
|
|
26
|
+
<th class="observ-table__cell">Duration</th>
|
|
27
|
+
<th class="observ-table__cell observ-table__cell--numeric">Observations</th>
|
|
28
|
+
<th class="observ-table__cell observ-table__cell--numeric">Tokens</th>
|
|
29
|
+
<th class="observ-table__cell observ-table__cell--numeric">Cost</th>
|
|
30
|
+
<th class="observ-table__cell">Status</th>
|
|
31
|
+
<th class="observ-table__cell"></th>
|
|
32
|
+
</tr>
|
|
33
|
+
</thead>
|
|
34
|
+
<tbody>
|
|
35
|
+
<% traces.each do |trace| %>
|
|
36
|
+
<tr class="observ-table__row">
|
|
37
|
+
<td class="observ-table__cell">
|
|
38
|
+
<code class="observ-code observ-code--inline"><%= truncate_id(trace.trace_id, 12) %></code>
|
|
39
|
+
</td>
|
|
40
|
+
<td class="observ-table__cell">
|
|
41
|
+
<%= trace.name || "—" %>
|
|
42
|
+
</td>
|
|
43
|
+
<td class="observ-table__cell">
|
|
44
|
+
<% models = trace.models_used %>
|
|
45
|
+
<% if models.any? %>
|
|
46
|
+
<%= models.join(", ") %>
|
|
47
|
+
<% else %>
|
|
48
|
+
<span class="observ-text--muted">—</span>
|
|
49
|
+
<% end %>
|
|
50
|
+
</td>
|
|
51
|
+
<td class="observ-table__cell">
|
|
52
|
+
<%= observ_timestamp(trace.start_time) %>
|
|
53
|
+
</td>
|
|
54
|
+
<td class="observ-table__cell">
|
|
55
|
+
<% if trace.duration_ms %>
|
|
56
|
+
<%= format_duration_ms(trace.duration_ms) %>
|
|
57
|
+
<% else %>
|
|
58
|
+
<span class="observ-text--muted">—</span>
|
|
59
|
+
<% end %>
|
|
60
|
+
</td>
|
|
61
|
+
<td class="observ-table__cell observ-table__cell--numeric">
|
|
62
|
+
<%= trace.observations.count %>
|
|
63
|
+
</td>
|
|
64
|
+
<td class="observ-table__cell observ-table__cell--numeric">
|
|
65
|
+
<%= format_tokens(trace.total_tokens) %>
|
|
66
|
+
</td>
|
|
67
|
+
<td class="observ-table__cell observ-table__cell--numeric">
|
|
68
|
+
<%= format_currency(trace.total_cost) %>
|
|
69
|
+
</td>
|
|
70
|
+
<td class="observ-table__cell">
|
|
71
|
+
<%= observ_status_badge(observ_trace_status(trace)) %>
|
|
72
|
+
</td>
|
|
73
|
+
<td class="observ-table__cell observ-table__cell--actions">
|
|
74
|
+
<%= link_to "View", trace_path(trace), class: "observ-button observ-button--sm", target: local_assigns[:open_in_new_tab] ? "_blank" : nil %>
|
|
75
|
+
</td>
|
|
76
|
+
</tr>
|
|
77
|
+
<% end %>
|
|
78
|
+
</tbody>
|
|
79
|
+
</table>
|
|
80
|
+
<% if local_assigns[:show_total] && local_assigns[:total_count] && total_count > traces.count %>
|
|
81
|
+
<p class="observ-text--muted observ-review-preview-notice">
|
|
82
|
+
Showing first <%= traces.count %> traces.
|
|
83
|
+
<%= link_to "View all #{total_count} traces", session_path(session), class: "observ-link", target: "_blank" %>
|
|
84
|
+
</p>
|
|
85
|
+
<% end %>
|
|
86
|
+
<% else %>
|
|
87
|
+
<div class="observ-card__empty">
|
|
88
|
+
<p>No traces found for this session.</p>
|
|
89
|
+
</div>
|
|
90
|
+
<% end %>
|
|
91
|
+
</div>
|
|
92
|
+
</section>
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
<%= turbo_stream.update "drawer-header-title" do %>
|
|
2
|
-
|
|
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: @session %>
|
|
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: @session, annotation: @annotation %>
|
|
8
15
|
</div>
|
|
9
16
|
|
|
@@ -7,6 +7,63 @@
|
|
|
7
7
|
<% end %>
|
|
8
8
|
|
|
9
9
|
<div class="observ-container">
|
|
10
|
+
<!-- Filters -->
|
|
11
|
+
<section class="observ-card observ-filters">
|
|
12
|
+
<div class="observ-card__body">
|
|
13
|
+
<%= form_with url: sessions_path, method: :get, class: "observ-filters__form" do |f| %>
|
|
14
|
+
<div class="observ-filters__field observ-filters__field--md">
|
|
15
|
+
<%= f.label "filter[agent_type]", "Agent", class: "observ-filters__label" %>
|
|
16
|
+
<%= f.select "filter[agent_type]",
|
|
17
|
+
options_for_select(
|
|
18
|
+
[["All Agents", ""]] + @agent_types.map { |t| [t, t] },
|
|
19
|
+
params.dig(:filter, :agent_type)
|
|
20
|
+
),
|
|
21
|
+
{},
|
|
22
|
+
class: "observ-filters__select" %>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="observ-filters__field observ-filters__field--flex">
|
|
26
|
+
<%= f.label "filter[user_id]", "User ID", class: "observ-filters__label" %>
|
|
27
|
+
<%= f.text_field "filter[user_id]",
|
|
28
|
+
value: params.dig(:filter, :user_id),
|
|
29
|
+
placeholder: "Filter by user ID...",
|
|
30
|
+
class: "observ-filters__input" %>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="observ-filters__field observ-filters__field--md">
|
|
34
|
+
<%= f.label "filter[start_date]", "From", class: "observ-filters__label" %>
|
|
35
|
+
<%= f.date_field "filter[start_date]",
|
|
36
|
+
value: params.dig(:filter, :start_date),
|
|
37
|
+
class: "observ-filters__input" %>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="observ-filters__field observ-filters__field--md">
|
|
41
|
+
<%= f.label "filter[end_date]", "To", class: "observ-filters__label" %>
|
|
42
|
+
<%= f.date_field "filter[end_date]",
|
|
43
|
+
value: params.dig(:filter, :end_date),
|
|
44
|
+
class: "observ-filters__input" %>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="observ-filters__field observ-filters__field--md">
|
|
48
|
+
<%= f.label "filter[status]", "Status", class: "observ-filters__label" %>
|
|
49
|
+
<%= f.select "filter[status]",
|
|
50
|
+
options_for_select(
|
|
51
|
+
[["All", ""], ["Completed", "completed"], ["In Progress", "in_progress"]],
|
|
52
|
+
params.dig(:filter, :status)
|
|
53
|
+
),
|
|
54
|
+
{},
|
|
55
|
+
class: "observ-filters__select" %>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="observ-filters__actions">
|
|
59
|
+
<%= f.submit "Filter", class: "observ-button observ-button--secondary" %>
|
|
60
|
+
<%= link_to "Clear", sessions_path, class: "observ-button" %>
|
|
61
|
+
</div>
|
|
62
|
+
<% end %>
|
|
63
|
+
</div>
|
|
64
|
+
</section>
|
|
65
|
+
|
|
66
|
+
<!-- Sessions Table -->
|
|
10
67
|
<section class="observ-card">
|
|
11
68
|
<div class="observ-card__body">
|
|
12
69
|
<% if @sessions.any? %>
|
|
@@ -81,11 +138,10 @@
|
|
|
81
138
|
<% else %>
|
|
82
139
|
<div class="observ-card__empty">
|
|
83
140
|
<p>No sessions found.</p>
|
|
84
|
-
|
|
85
|
-
|
|
141
|
+
</div>
|
|
142
|
+
<% end %>
|
|
143
|
+
</div>
|
|
86
144
|
</section>
|
|
87
145
|
|
|
88
146
|
<%= observ_pagination(@sessions) %>
|
|
89
|
-
</div>
|
|
90
|
-
</section>
|
|
91
147
|
</div>
|