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
@@ -1,76 +1,74 @@
1
1
  <%= form_with model: form, url: prompts_path, method: :post do |f| %>
2
2
 
3
3
  <% if form.errors.any? %>
4
- <div class="observ-card" style="margin-bottom: 1.5rem; border-left: 4px solid #dc2626;">
5
- <div class="observ-card__body">
6
- <h3 style="color: #dc2626; font-weight: 500; margin: 0 0 0.5rem;">
7
- <%= pluralize(form.errors.count, "error") %> prohibited this prompt from being saved:
8
- </h3>
9
- <ul style="list-style: disc; padding-left: 1.5rem; color: #dc2626; font-size: 0.875rem; margin: 0;">
10
- <% form.errors.full_messages.each do |message| %>
11
- <li><%= message %></li>
12
- <% end %>
13
- </ul>
14
- </div>
4
+ <div class="observ-alert observ-alert--danger">
5
+ <h3 class="observ-alert__title">
6
+ <%= pluralize(form.errors.count, "error") %> prohibited this prompt from being saved:
7
+ </h3>
8
+ <ul class="observ-alert__list">
9
+ <% form.errors.full_messages.each do |message| %>
10
+ <li><%= message %></li>
11
+ <% end %>
12
+ </ul>
15
13
  </div>
16
14
  <% end %>
17
15
 
18
16
  <div class="observ-card">
19
17
  <div class="observ-card__body">
20
18
  <!-- Prompt Name -->
21
- <div style="margin-bottom: 1.5rem;">
22
- <%= f.label :name, "Prompt Name", style: "display: block; margin-bottom: 0.5rem; font-weight: 500;" %>
19
+ <div class="observ-form__group">
20
+ <%= f.label :name, "Prompt Name", class: "observ-form__label" %>
23
21
  <%= f.text_field :name,
24
22
  placeholder: "e.g., research-agent-system-prompt",
25
- style: "width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 0.375rem;" %>
26
- <p style="margin-top: 0.25rem; font-size: 0.875rem; color: #6b7280;">
23
+ class: "observ-form__input" %>
24
+ <p class="observ-form__hint">
27
25
  Use kebab-case. This will be used to identify the prompt (e.g., "my-agent-system-prompt")
28
26
  </p>
29
27
  </div>
30
28
 
31
29
  <!-- Commit Message -->
32
- <div style="margin-bottom: 1.5rem;">
33
- <%= f.label :commit_message, "Commit Message", style: "display: block; margin-bottom: 0.5rem; font-weight: 500;" %>
30
+ <div class="observ-form__group">
31
+ <%= f.label :commit_message, "Commit Message", class: "observ-form__label" %>
34
32
  <%= f.text_area :commit_message,
35
33
  rows: 2,
36
34
  placeholder: "Describe what changed in this version...",
37
- style: "width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 0.375rem; font-family: inherit;" %>
38
- <p style="margin-top: 0.25rem; font-size: 0.875rem; color: #6b7280;">
35
+ class: "observ-form__textarea" %>
36
+ <p class="observ-form__hint">
39
37
  Optional but recommended for tracking changes
40
38
  </p>
41
39
  </div>
42
40
 
43
41
  <!-- Prompt Content -->
44
- <div style="margin-bottom: 1.5rem;" data-controller="observ--prompt-variables">
45
- <%= f.label :prompt, "Prompt Content", style: "display: block; margin-bottom: 0.5rem; font-weight: 500;" %>
42
+ <div class="observ-form__group" data-controller="observ--prompt-variables">
43
+ <%= f.label :prompt, "Prompt Content", class: "observ-form__label" %>
46
44
  <%= f.text_area :prompt,
47
45
  rows: 15,
48
46
  placeholder: "Enter your prompt here...\n\nUse {{variable_name}} for dynamic variables.",
49
- style: "width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 0.375rem; font-family: monospace; font-size: 0.875rem;",
47
+ class: "observ-form__textarea observ-form__textarea--code",
50
48
  data: {
51
49
  observ__prompt_variables_target: "input",
52
50
  action: "input->observ--prompt-variables#detectVariables"
53
51
  } %>
54
- <p style="margin-top: 0.25rem; font-size: 0.875rem; color: #6b7280;">
55
- Use <code style="background-color: #f3f4f6; padding: 0.125rem 0.25rem; border-radius: 0.25rem;">{{variable_name}}</code> syntax for variables
52
+ <p class="observ-form__hint">
53
+ Use <code class="observ-code">{{variable_name}}</code> syntax for variables
56
54
  </p>
57
55
 
58
56
  <!-- Variable Preview -->
59
- <div data-observ--prompt-variables-target="preview" style="margin-top: 0.75rem; padding: 0.75rem; background-color: #eff6ff; border-radius: 0.375rem; display: none;">
60
- <p style="font-size: 0.875rem; font-weight: 500; color: #1e40af; margin: 0 0 0.5rem;">Detected Variables:</p>
61
- <div data-observ--prompt-variables-target="list" style="display: flex; flex-wrap: wrap; gap: 0.5rem;"></div>
57
+ <div data-observ--prompt-variables-target="preview" class="observ-prompt-form__variable-preview">
58
+ <p class="observ-prompt-form__variable-title">Detected Variables:</p>
59
+ <div data-observ--prompt-variables-target="list" class="observ-prompt-form__variable-list"></div>
62
60
  </div>
63
61
  </div>
64
62
 
65
63
  <!-- Configuration (JSON) -->
66
- <div style="margin-bottom: 1.5rem;">
67
- <%= f.label :config, "Configuration (JSON)", style: "display: block; margin-bottom: 0.5rem; font-weight: 500;" %>
64
+ <div class="observ-form__group">
65
+ <%= f.label :config, "Configuration (JSON)", class: "observ-form__label" %>
68
66
  <%= f.text_area :config,
69
67
  rows: 8,
70
68
  placeholder: '{\n "model": "gpt-4o",\n "temperature": 0.7,\n "max_tokens": 2000\n}',
71
- style: "width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 0.375rem; font-family: monospace; font-size: 0.875rem;",
69
+ class: "observ-form__textarea observ-form__textarea--code",
72
70
  data: { controller: "json-editor" } %>
73
- <p style="margin-top: 0.25rem; font-size: 0.875rem; color: #6b7280;">
71
+ <p class="observ-form__hint">
74
72
  Optional JSON configuration for model parameters and metadata
75
73
  </p>
76
74
  </div>
@@ -79,20 +77,21 @@
79
77
  <%= f.hidden_field :from_version %>
80
78
 
81
79
  <!-- Actions -->
82
- <div style="display: flex; align-items: center; justify-content: space-between; padding-top: 1.5rem; border-top: 1px solid #e5e7eb;">
83
- <div>
84
- <label style="display: flex; align-items: center;">
85
- <%= f.check_box :promote_to_production, style: "margin-right: 0.5rem;" %>
86
- <span style="font-size: 0.875rem; color: #374151;">
80
+ <div class="observ-form__actions observ-form__actions--between">
81
+ <div class="observ-form__checkbox-group">
82
+ <label class="observ-form__checkbox-label">
83
+ <%= f.check_box :promote_to_production, class: "observ-form__checkbox" %>
84
+ <span class="observ-form__checkbox-custom"></span>
85
+ <span class="observ-form__checkbox-text">
87
86
  Promote to production immediately
88
87
  </span>
89
88
  </label>
90
- <p style="margin-left: 1.5rem; font-size: 0.75rem; color: #6b7280; margin-top: 0.25rem;">
89
+ <p class="observ-form__checkbox-hint">
91
90
  If unchecked, prompt will be saved as draft
92
91
  </p>
93
92
  </div>
94
93
 
95
- <div style="display: flex; gap: 0.75rem;">
94
+ <div class="observ-form__actions-group">
96
95
  <%= link_to "Cancel", prompts_path, class: "observ-button" %>
97
96
  <%= f.submit "Create Prompt", class: "observ-button observ-button--primary" %>
98
97
  </div>
@@ -3,21 +3,21 @@
3
3
  <% content_for :page_header do %>
4
4
  <div class="observ-page-header__content">
5
5
  <div>
6
- <%= link_to "← Back to Prompt", prompt_path(@prompt_name), style: "display: inline-block; margin-bottom: 0.5rem; color: #2563eb;" %>
6
+ <%= link_to "← Back to Prompt", prompt_path(@prompt_name), class: "observ-link" %>
7
7
  <h1 class="observ-page-header__title">Compare Versions</h1>
8
- <p style="color: #6b7280; margin-top: 0.25rem;"><%= @prompt_name %></p>
8
+ <p class="observ-prompt-header__subtitle"><%= @prompt_name %></p>
9
9
  </div>
10
10
  </div>
11
11
  <% end %>
12
12
 
13
13
  <div class="observ-container">
14
14
  <!-- Version Selector -->
15
- <div class="observ-card" style="margin-bottom: 1.5rem;">
15
+ <div class="observ-card">
16
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| %>
17
+ <%= form_with url: compare_prompt_path(@prompt_name), method: :get, class: "observ-compare__selector" do |f| %>
18
18
 
19
- <div style="flex: 1;">
20
- <%= f.label :from, "From Version", style: "display: block; margin-bottom: 0.5rem; font-weight: 500;" %>
19
+ <div class="observ-compare__field">
20
+ <%= f.label :from, "From Version", class: "observ-form__label" %>
21
21
  <%= f.select :from,
22
22
  options_for_select(
23
23
  Observ::Prompt.where(name: @prompt_name).order(version: :desc).map { |v|
@@ -26,11 +26,11 @@
26
26
  params[:from]
27
27
  ),
28
28
  {},
29
- style: "width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 0.375rem;" %>
29
+ class: "observ-form__select" %>
30
30
  </div>
31
31
 
32
- <div style="flex: 1;">
33
- <%= f.label :to, "To Version", style: "display: block; margin-bottom: 0.5rem; font-weight: 500;" %>
32
+ <div class="observ-compare__field">
33
+ <%= f.label :to, "To Version", class: "observ-form__label" %>
34
34
  <%= f.select :to,
35
35
  options_for_select(
36
36
  Observ::Prompt.where(name: @prompt_name).order(version: :desc).map { |v|
@@ -39,7 +39,7 @@
39
39
  params[:to]
40
40
  ),
41
41
  {},
42
- style: "width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 0.375rem;" %>
42
+ class: "observ-form__select" %>
43
43
  </div>
44
44
 
45
45
  <%= f.submit "Compare", class: "observ-button observ-button--primary" %>
@@ -48,63 +48,63 @@
48
48
  </div>
49
49
 
50
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>
51
+ <div class="observ-card">
52
+ <div class="observ-compare__grid">
53
+ <div class="observ-compare__panel observ-compare__panel--left">
54
+ <div class="observ-compare__version-header">
55
+ <h2 class="observ-compare__version-title">Version <%= @from_version.version %></h2>
56
56
  <span class="observ-badge <%= case @from_version.state
57
57
  when 'draft' then 'observ-badge--warning'
58
58
  when 'production' then 'observ-badge--success'
59
59
  when 'archived' then 'observ-badge--default'
60
- end %>" style="font-size: 0.75rem;">
60
+ end %>">
61
61
  <%= @from_version.state %>
62
62
  </span>
63
63
  </div>
64
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>
65
+ <dl class="observ-compare__metadata">
66
+ <div class="observ-compare__metadata-item">
67
+ <dt>Created</dt>
68
+ <dd><%= @from_version.created_at.strftime("%b %d, %Y %I:%M %p") %></dd>
69
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>
70
+ <div class="observ-compare__metadata-item">
71
+ <dt>Created By</dt>
72
+ <dd><%= @from_version.created_by || "system" %></dd>
73
73
  </div>
74
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>
75
+ <div class="observ-compare__metadata-item">
76
+ <dt>Commit Message</dt>
77
+ <dd class="observ-compare__metadata-item--italic">"<%= @from_version.commit_message %>"</dd>
78
78
  </div>
79
79
  <% end %>
80
80
  </dl>
81
81
  </div>
82
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>
83
+ <div class="observ-compare__panel">
84
+ <div class="observ-compare__version-header">
85
+ <h2 class="observ-compare__version-title">Version <%= @to_version.version %></h2>
86
86
  <span class="observ-badge <%= case @to_version.state
87
87
  when 'draft' then 'observ-badge--warning'
88
88
  when 'production' then 'observ-badge--success'
89
89
  when 'archived' then 'observ-badge--default'
90
- end %>" style="font-size: 0.75rem;">
90
+ end %>">
91
91
  <%= @to_version.state %>
92
92
  </span>
93
93
  </div>
94
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>
95
+ <dl class="observ-compare__metadata">
96
+ <div class="observ-compare__metadata-item">
97
+ <dt>Created</dt>
98
+ <dd><%= @to_version.created_at.strftime("%b %d, %Y %I:%M %p") %></dd>
99
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>
100
+ <div class="observ-compare__metadata-item">
101
+ <dt>Created By</dt>
102
+ <dd><%= @to_version.created_by || "system" %></dd>
103
103
  </div>
104
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>
105
+ <div class="observ-compare__metadata-item">
106
+ <dt>Commit Message</dt>
107
+ <dd class="observ-compare__metadata-item--italic">"<%= @to_version.commit_message %>"</dd>
108
108
  </div>
109
109
  <% end %>
110
110
  </dl>
@@ -115,21 +115,21 @@
115
115
  <!-- Side-by-Side Diff -->
116
116
  <div class="observ-card">
117
117
  <div class="observ-card__header">
118
- <h2 style="font-size: 1.25rem; font-weight: 600; margin: 0;">Prompt Content Comparison</h2>
118
+ <h2 class="observ-compare__section-title">Prompt Content Comparison</h2>
119
119
  </div>
120
120
 
121
- <div style="display: grid; grid-template-columns: repeat(2, 1fr); border-top: 1px solid #e5e7eb;">
121
+ <div class="observ-compare__grid">
122
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>
123
+ <div class="observ-compare__panel observ-compare__panel--left">
124
+ <div class="observ-compare__content-block">
125
+ <pre><%= render "diff_content", content: @from_version.prompt, diff: @diff, side: :from %></pre>
126
126
  </div>
127
127
  </div>
128
128
 
129
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>
130
+ <div class="observ-compare__panel">
131
+ <div class="observ-compare__content-block">
132
+ <pre><%= render "diff_content", content: @to_version.prompt, diff: @diff, side: :to %></pre>
133
133
  </div>
134
134
  </div>
135
135
  </div>
@@ -137,17 +137,21 @@
137
137
 
138
138
  <!-- Config Comparison -->
139
139
  <% if @from_version.config.present? || @to_version.config.present? %>
140
- <div class="observ-card" style="margin-top: 1.5rem;">
140
+ <div class="observ-card">
141
141
  <div class="observ-card__header">
142
- <h2 style="font-size: 1.25rem; font-weight: 600; margin: 0;">Configuration Comparison</h2>
142
+ <h2 class="observ-compare__section-title">Configuration Comparison</h2>
143
143
  </div>
144
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>
145
+ <div class="observ-compare__grid">
146
+ <div class="observ-compare__panel observ-compare__panel--left">
147
+ <div class="observ-compare__content-block">
148
+ <pre><%= JSON.pretty_generate(@from_version.config) %></pre>
149
+ </div>
148
150
  </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 class="observ-compare__panel">
152
+ <div class="observ-compare__content-block">
153
+ <pre><%= JSON.pretty_generate(@to_version.config) %></pre>
154
+ </div>
151
155
  </div>
152
156
  </div>
153
157
  </div>
@@ -3,15 +3,15 @@
3
3
  <% content_for :page_header do %>
4
4
  <div class="observ-page-header__content">
5
5
  <div>
6
- <%= link_to "← Back to Prompt", prompt_path(@prompt.name), style: "display: inline-block; margin-bottom: 0.5rem; color: #2563eb;" %>
6
+ <%= link_to "← Back to Prompt", prompt_path(@prompt.name), class: "observ-link" %>
7
7
  <h1 class="observ-page-header__title">Edit Prompt Draft</h1>
8
- <p style="color: #6b7280; margin-top: 0.25rem;">
8
+ <p class="observ-prompt-header__subtitle">
9
9
  Editing <%= @prompt.name %> (v<%= @prompt.version %> - Draft)
10
10
  </p>
11
11
  </div>
12
12
  </div>
13
13
  <% end %>
14
14
 
15
- <div class="observ-container" style="max-width: 56rem; margin-left: auto; margin-right: auto;">
15
+ <div class="observ-container">
16
16
  <%= render "form", prompt: @prompt %>
17
17
  </div>
@@ -9,29 +9,29 @@
9
9
 
10
10
  <div class="observ-container">
11
11
  <!-- Search and Filters -->
12
- <section class="observ-card observ-prompts-filters">
12
+ <section class="observ-card observ-filters">
13
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" %>
14
+ <%= form_with url: prompts_path, method: :get, class: "observ-filters__form" do |f| %>
15
+ <div class="observ-filters__field observ-filters__field--flex">
16
+ <%= f.label :search, "Search prompts", class: "observ-filters__label" %>
17
17
  <%= f.text_field :search,
18
18
  value: params[:search],
19
19
  placeholder: "Search by name...",
20
- class: "observ-prompts-filters__input" %>
20
+ class: "observ-filters__input" %>
21
21
  </div>
22
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" %>
23
+ <div class="observ-filters__field observ-filters__field--lg">
24
+ <%= f.label :state, "Filter by state", class: "observ-filters__label" %>
25
25
  <%= f.select :state,
26
26
  options_for_select(
27
27
  [["All States", ""], ["Draft", "draft"], ["Production", "production"], ["Archived", "archived"]],
28
28
  params[:state]
29
29
  ),
30
30
  {},
31
- class: "observ-prompts-filters__select" %>
31
+ class: "observ-filters__select" %>
32
32
  </div>
33
33
 
34
- <div class="observ-prompts-filters__actions">
34
+ <div class="observ-filters__actions">
35
35
  <%= f.submit "Filter", class: "observ-button observ-button--secondary" %>
36
36
  <%= link_to "Clear", prompts_path, class: "observ-button" %>
37
37
  </div>
@@ -3,15 +3,15 @@
3
3
  <% content_for :page_header do %>
4
4
  <div class="observ-page-header__content">
5
5
  <div>
6
- <%= link_to "← Back to Prompts", prompts_path, style: "display: inline-block; margin-bottom: 0.5rem; color: #2563eb;" %>
6
+ <%= link_to "← Back to Prompts", prompts_path, class: "observ-link" %>
7
7
  <h1 class="observ-page-header__title">Create New Prompt</h1>
8
- <p style="color: #6b7280; margin-top: 0.25rem;">
8
+ <p class="observ-prompt-header__subtitle">
9
9
  Create a new prompt version. All new prompts start as drafts.
10
10
  </p>
11
11
  </div>
12
12
  </div>
13
13
  <% end %>
14
14
 
15
- <div class="observ-container" style="max-width: 56rem; margin-left: auto; margin-right: auto;">
15
+ <div class="observ-container">
16
16
  <%= render "new_form", form: @form %>
17
17
  </div>
@@ -3,9 +3,9 @@
3
3
  <% content_for :page_header do %>
4
4
  <div class="observ-page-header__content">
5
5
  <div>
6
- <%= link_to "← Back to Prompts", prompts_path, class: "prompt-page-header__back-link" %>
6
+ <%= link_to "← Back to Prompts", prompts_path, class: "observ-link" %>
7
7
  <h1 class="observ-page-header__title"><%= @prompt.name %></h1>
8
- <p class="prompt-page-header__subtitle">
8
+ <p class="observ-prompt-header__subtitle">
9
9
  Currently viewing version <%= @prompt.version %>
10
10
  </p>
11
11
  </div>
@@ -3,9 +3,9 @@
3
3
  <% content_for :page_header do %>
4
4
  <div class="observ-page-header__content">
5
5
  <div>
6
- <%= link_to "← Back to Prompt", prompt_path(@prompt_name), style: "display: inline-block; margin-bottom: 0.5rem; color: #2563eb;" %>
6
+ <%= link_to "← Back to Prompt", prompt_path(@prompt_name), class: "observ-link" %>
7
7
  <h1 class="observ-page-header__title">Version History</h1>
8
- <p style="color: #6b7280; margin-top: 0.25rem;"><%= @prompt_name %></p>
8
+ <p class="observ-prompt-header__subtitle"><%= @prompt_name %></p>
9
9
  </div>
10
10
  </div>
11
11
  <% end %>
@@ -14,53 +14,53 @@
14
14
  <!-- Timeline View -->
15
15
  <div class="observ-card">
16
16
  <div class="observ-card__body">
17
- <div style="display: flex; flex-direction: column; gap: 1.5rem;">
17
+ <div class="observ-timeline">
18
18
  <% @versions.each_with_index do |version, index| %>
19
- <div style="display: flex; gap: 1rem;">
19
+ <div class="observ-timeline__item">
20
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;'
21
+ <div class="observ-timeline__marker">
22
+ <div class="observ-timeline__dot observ-timeline__dot--<%= case version.state
23
+ when 'production' then 'success'
24
+ when 'draft' then 'warning'
25
+ when 'archived' then 'muted'
26
26
  end %>">
27
27
  </div>
28
28
  <% unless index == @versions.count - 1 %>
29
- <div style="width: 2px; height: 100%; background-color: #d1d5db; flex: 1; margin: 0.25rem 0;"></div>
29
+ <div class="observ-timeline__line"></div>
30
30
  <% end %>
31
31
  </div>
32
32
 
33
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>
34
+ <div class="observ-timeline__content">
35
+ <div class="observ-version-card">
36
+ <div class="observ-version-card__header">
37
+ <div>
38
+ <div class="observ-version-card__title-row">
39
+ <h3 class="observ-version-card__title">Version <%= version.version %></h3>
40
40
  <span class="observ-badge <%= case version.state
41
41
  when 'draft' then 'observ-badge--warning'
42
42
  when 'production' then 'observ-badge--success'
43
43
  when 'archived' then 'observ-badge--default'
44
- end %>" style="font-size: 0.75rem;">
44
+ end %>">
45
45
  <%= version.state.titleize %>
46
46
  </span>
47
47
  </div>
48
48
 
49
- <p style="font-size: 0.875rem; color: #6b7280; margin: 0;">
49
+ <p class="observ-version-card__meta">
50
50
  <%= version.created_at.strftime("%B %d, %Y at %I:%M %p") %>
51
51
  <% if version.created_by.present? %>
52
- by <span style="font-weight: 500;"><%= version.created_by %></span>
52
+ by <span class="observ-version-card__author"><%= version.created_by %></span>
53
53
  <% end %>
54
54
  </p>
55
55
 
56
56
  <% if version.commit_message.present? %>
57
- <p style="font-size: 0.875rem; color: #374151; margin: 0.5rem 0 0; font-style: italic;">
57
+ <p class="observ-version-card__commit">
58
58
  "<%= version.commit_message %>"
59
59
  </p>
60
60
  <% end %>
61
61
  </div>
62
62
 
63
- <div style="display: flex; gap: 0.5rem;">
63
+ <div class="observ-version-card__actions">
64
64
  <%= link_to "View", prompt_path(@prompt_name, version: version.version), class: "observ-button observ-button--sm observ-button--secondary" %>
65
65
 
66
66
  <% if @production_version && version != @production_version %>
@@ -74,7 +74,7 @@
74
74
  </div>
75
75
 
76
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;">
77
+ <div class="observ-version-card__preview">
78
78
  <%= truncate(version.prompt, length: 200) %>
79
79
  </div>
80
80
  </div>
@@ -0,0 +1,39 @@
1
+ <tr class="observ-table__row">
2
+ <td class="observ-table__cell">
3
+ <span class="observ-badge <%= review_item.priority_badge_class %>">
4
+ <%= review_item.priority.capitalize %>
5
+ </span>
6
+ </td>
7
+ <td class="observ-table__cell">
8
+ <span class="observ-type-label">
9
+ <%= review_item.reviewable_type_display %>
10
+ </span>
11
+ </td>
12
+ <td class="observ-table__cell">
13
+ <%= review_item.reason_display %>
14
+ <% if review_item.reason_details.present? %>
15
+ <span class="observ-text--muted observ-text--small">
16
+ <% if review_item.reason_details["cost"] %>
17
+ ($<%= number_with_precision(review_item.reason_details["cost"], precision: 4) %>)
18
+ <% elsif review_item.reason_details["latency_ms"] %>
19
+ (<%= number_with_delimiter(review_item.reason_details["latency_ms"]) %>ms)
20
+ <% elsif review_item.reason_details["tokens"] %>
21
+ (<%= number_with_delimiter(review_item.reason_details["tokens"]) %> tokens)
22
+ <% end %>
23
+ </span>
24
+ <% end %>
25
+ </td>
26
+ <td class="observ-table__cell">
27
+ <span class="observ-badge observ-badge--<%= review_item.status == 'in_progress' ? 'warning' : 'secondary' %>">
28
+ <%= review_item.status.humanize %>
29
+ </span>
30
+ </td>
31
+ <td class="observ-table__cell">
32
+ <span class="observ-text--muted" title="<%= review_item.created_at %>">
33
+ <%= time_ago_in_words(review_item.created_at) %> ago
34
+ </span>
35
+ </td>
36
+ <td class="observ-table__cell observ-table__cell--actions">
37
+ <%= link_to "Review", review_path(review_item), class: "observ-button observ-button--sm" %>
38
+ </td>
39
+ </tr>
@@ -0,0 +1,18 @@
1
+ <div class="observ-stats-bar">
2
+ <div class="observ-stat">
3
+ <span class="observ-stat__value"><%= stats[:pending] %></span>
4
+ <span class="observ-stat__label">Pending</span>
5
+ </div>
6
+ <div class="observ-stat">
7
+ <span class="observ-stat__value"><%= stats[:in_progress] %></span>
8
+ <span class="observ-stat__label">In Progress</span>
9
+ </div>
10
+ <div class="observ-stat">
11
+ <span class="observ-stat__value"><%= stats[:completed_today] %></span>
12
+ <span class="observ-stat__label">Completed Today</span>
13
+ </div>
14
+ <div class="observ-stat">
15
+ <span class="observ-stat__value"><%= stats[:completed_this_week] %></span>
16
+ <span class="observ-stat__label">This Week</span>
17
+ </div>
18
+ </div>