leva 0.1.10 → 0.2.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 +1 -2
- data/app/assets/stylesheets/leva/application.css +3083 -15
- data/app/controllers/leva/design_system_controller.rb +9 -0
- data/app/views/layouts/leva/application.html.erb +23 -24
- data/app/views/leva/dataset_records/index.html.erb +63 -61
- data/app/views/leva/dataset_records/show.html.erb +115 -25
- data/app/views/leva/datasets/_dataset.html.erb +11 -18
- data/app/views/leva/datasets/_form.html.erb +18 -14
- data/app/views/leva/datasets/edit.html.erb +16 -4
- data/app/views/leva/datasets/index.html.erb +33 -41
- data/app/views/leva/datasets/new.html.erb +15 -4
- data/app/views/leva/datasets/show.html.erb +120 -139
- data/app/views/leva/design_system/index.html.erb +1731 -0
- data/app/views/leva/experiments/_experiment.html.erb +46 -31
- data/app/views/leva/experiments/_form.html.erb +62 -35
- data/app/views/leva/experiments/edit.html.erb +17 -3
- data/app/views/leva/experiments/index.html.erb +41 -36
- data/app/views/leva/experiments/new.html.erb +40 -19
- data/app/views/leva/experiments/show.html.erb +155 -98
- data/app/views/leva/runner_results/show.html.erb +271 -54
- data/app/views/leva/workbench/_evaluation_area.html.erb +18 -4
- data/app/views/leva/workbench/_prompt_content.html.erb +116 -111
- data/app/views/leva/workbench/_prompt_form.html.erb +24 -23
- data/app/views/leva/workbench/_prompt_sidebar.html.erb +57 -12
- data/app/views/leva/workbench/_results_section.html.erb +274 -112
- data/app/views/leva/workbench/_top_bar.html.erb +16 -6
- data/app/views/leva/workbench/edit.html.erb +46 -15
- data/app/views/leva/workbench/index.html.erb +5 -8
- data/app/views/leva/workbench/new.html.erb +74 -42
- data/config/routes.rb +2 -0
- data/lib/leva/engine.rb +10 -0
- data/lib/leva/version.rb +1 -1
- metadata +4 -2
|
@@ -1,42 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<%
|
|
2
|
+
# Pre-calculate status styling
|
|
3
|
+
status_dot = case experiment.status
|
|
4
|
+
when 'pending' then 'status-dot-pending'
|
|
5
|
+
when 'running' then 'status-dot-info'
|
|
6
|
+
when 'completed' then 'status-dot-success'
|
|
7
|
+
when 'failed' then 'status-dot-error'
|
|
8
|
+
else 'status-dot-pending'
|
|
9
|
+
end
|
|
10
|
+
run_count = experiment.runner_results.count
|
|
11
|
+
%>
|
|
12
|
+
<tr class="experiment-row" onclick="window.location='<%= experiment_path(experiment) %>'">
|
|
13
|
+
<td>
|
|
14
|
+
<div class="experiment-name-cell">
|
|
15
|
+
<span class="experiment-name"><%= experiment.name %></span>
|
|
16
|
+
<% if experiment.description.present? %>
|
|
17
|
+
<span class="experiment-desc"><%= truncate(experiment.description, length: 60) %></span>
|
|
18
|
+
<% end %>
|
|
19
|
+
</div>
|
|
20
|
+
</td>
|
|
21
|
+
<td>
|
|
22
|
+
<span class="cell-dataset"><%= experiment.dataset&.name || '—' %></span>
|
|
4
23
|
</td>
|
|
5
|
-
<td class="
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
end %>
|
|
13
|
-
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full <%= status_color %>">
|
|
14
|
-
<%= experiment.status&.capitalize || 'N/A' %>
|
|
24
|
+
<td class="text-right text-nowrap">
|
|
25
|
+
<span class="cell-timestamp"><%= time_ago_in_words(experiment.created_at) %></span>
|
|
26
|
+
</td>
|
|
27
|
+
<td class="text-center">
|
|
28
|
+
<span class="status-indicator">
|
|
29
|
+
<span class="status-dot <%= status_dot %>"></span>
|
|
30
|
+
<span class="status-text"><%= experiment.status&.capitalize || 'N/A' %></span>
|
|
15
31
|
</span>
|
|
16
32
|
</td>
|
|
17
|
-
<td class="
|
|
18
|
-
|
|
33
|
+
<td class="text-right">
|
|
34
|
+
<span class="cell-count"><%= run_count %></span>
|
|
19
35
|
</td>
|
|
20
36
|
<% Leva::EvaluationResult.distinct.pluck(:evaluator_class).each do |evaluator_class| %>
|
|
21
|
-
<td class="
|
|
37
|
+
<td class="text-right">
|
|
22
38
|
<% results = experiment.evaluation_results.where(evaluator_class: evaluator_class) %>
|
|
23
39
|
<% if results.any? %>
|
|
24
|
-
<%
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
<%
|
|
41
|
+
avg_score = (results.sum(&:score) / results.size.to_f)
|
|
42
|
+
score_pct = (avg_score * 100).round
|
|
43
|
+
score_class = case avg_score
|
|
44
|
+
when 0...0.2 then 'score-bad'
|
|
45
|
+
when 0.2...0.4 then 'score-poor'
|
|
46
|
+
when 0.4...0.6 then 'score-fair'
|
|
47
|
+
when 0.6...0.8 then 'score-good'
|
|
48
|
+
else 'score-excellent'
|
|
49
|
+
end
|
|
50
|
+
%>
|
|
51
|
+
<span class="score-pill <%= score_class %>"><%= score_pct %>%</span>
|
|
34
52
|
<% else %>
|
|
35
|
-
<span class="
|
|
53
|
+
<span class="score-empty">—</span>
|
|
36
54
|
<% end %>
|
|
37
55
|
</td>
|
|
38
56
|
<% end %>
|
|
39
|
-
|
|
40
|
-
<%= link_to 'View Results', experiment_path(experiment), class: "text-indigo-400 hover:text-indigo-300 transition-colors duration-200" %>
|
|
41
|
-
</td>
|
|
42
|
-
</tr>
|
|
57
|
+
</tr>
|
|
@@ -1,49 +1,76 @@
|
|
|
1
|
-
<%= form_with(model: @experiment, url: @experiment.new_record? ? experiments_path : experiment_path(@experiment), local: true, class: "
|
|
1
|
+
<%= form_with(model: @experiment, url: @experiment.new_record? ? experiments_path : experiment_path(@experiment), local: true, class: "card p-6") do |form| %>
|
|
2
2
|
<% if @experiment.errors.any? %>
|
|
3
|
-
<div class="
|
|
4
|
-
<
|
|
5
|
-
<ul
|
|
3
|
+
<div class="form-errors">
|
|
4
|
+
<p class="form-errors-title">Please fix the following errors:</p>
|
|
5
|
+
<ul>
|
|
6
6
|
<% @experiment.errors.full_messages.each do |message| %>
|
|
7
7
|
<li><%= message %></li>
|
|
8
8
|
<% end %>
|
|
9
9
|
</ul>
|
|
10
10
|
</div>
|
|
11
11
|
<% end %>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<%= form.label :prompt_id, class: "block text-sm font-semibold mb-2 text-indigo-300" %>
|
|
26
|
-
<%= form.collection_select :prompt_id, Leva::Prompt.all, :id, :name, {}, class: "w-full bg-gray-700 text-white p-3 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none" %>
|
|
12
|
+
|
|
13
|
+
<%# Basic Information %>
|
|
14
|
+
<div class="form-section">
|
|
15
|
+
<div class="form-group">
|
|
16
|
+
<%= form.label :name, class: "form-label" %>
|
|
17
|
+
<%= form.text_field :name, autofocus: true, class: "form-input", placeholder: "e.g., Sentiment Analysis v2" %>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="form-group">
|
|
21
|
+
<%= form.label :description, class: "form-label" %>
|
|
22
|
+
<%= form.text_area :description, rows: 3, class: "form-textarea", placeholder: "Optional description of this experiment..." %>
|
|
23
|
+
<p class="form-hint">Describe the purpose or hypothesis of this experiment.</p>
|
|
24
|
+
</div>
|
|
27
25
|
</div>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
|
|
27
|
+
<hr class="form-divider">
|
|
28
|
+
|
|
29
|
+
<%# Configuration %>
|
|
30
|
+
<div class="form-section">
|
|
31
|
+
<h4 class="form-section-title">Configuration</h4>
|
|
32
|
+
|
|
33
|
+
<div class="form-row">
|
|
34
|
+
<div class="form-group flex-1">
|
|
35
|
+
<%= form.label :dataset_id, "Dataset", class: "form-label" %>
|
|
36
|
+
<%= form.collection_select :dataset_id, Leva::Dataset.all, :id, :name, { prompt: "Select a dataset..." }, class: "form-select" %>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="form-group flex-1">
|
|
40
|
+
<%= form.label :prompt_id, "Prompt", class: "form-label" %>
|
|
41
|
+
<%= form.collection_select :prompt_id, Leva::Prompt.all, :id, :name, { prompt: "Select a prompt...", include_blank: "None" }, class: "form-select" %>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="form-group">
|
|
46
|
+
<%= form.label :runner_class, "Runner", class: "form-label" %>
|
|
47
|
+
<%= form.select :runner_class,
|
|
48
|
+
options_for_select(@runners.map { |r| [r.name.demodulize, r.name] }, @experiment.runner_class || @runners.first&.name),
|
|
49
|
+
{},
|
|
50
|
+
class: "form-select" %>
|
|
51
|
+
<p class="form-hint">The runner executes your model logic for each dataset record.</p>
|
|
52
|
+
</div>
|
|
34
53
|
</div>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
|
|
55
|
+
<hr class="form-divider">
|
|
56
|
+
|
|
57
|
+
<%# Evaluators %>
|
|
58
|
+
<div class="form-section">
|
|
59
|
+
<h4 class="form-section-title">Evaluators</h4>
|
|
60
|
+
<p class="form-section-desc">Select which evaluators to run on the experiment results.</p>
|
|
61
|
+
|
|
62
|
+
<div class="evaluator-checkbox-grid">
|
|
38
63
|
<%= form.collection_check_boxes :evaluator_classes, @evaluators, :name, ->(e) { e.name.demodulize } do |b| %>
|
|
39
|
-
<
|
|
40
|
-
<%= b.check_box(class: "
|
|
41
|
-
|
|
42
|
-
</
|
|
64
|
+
<label class="evaluator-checkbox">
|
|
65
|
+
<%= b.check_box(class: "form-check-input", checked: !@experiment.persisted?) %>
|
|
66
|
+
<span class="evaluator-checkbox-label"><%= b.text %></span>
|
|
67
|
+
</label>
|
|
43
68
|
<% end %>
|
|
44
69
|
</div>
|
|
45
70
|
</div>
|
|
46
|
-
|
|
47
|
-
|
|
71
|
+
|
|
72
|
+
<div class="form-actions">
|
|
73
|
+
<%= link_to "Cancel", experiments_path, class: "btn btn-ghost" %>
|
|
74
|
+
<%= form.submit @experiment.persisted? ? "Update Experiment" : "Create & Run Experiment", class: "btn btn-primary" %>
|
|
48
75
|
</div>
|
|
49
|
-
<% end %>
|
|
76
|
+
<% end %>
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
<% content_for :title, "Edit #{@experiment.name}" %>
|
|
2
|
-
<div class="container
|
|
3
|
-
<
|
|
2
|
+
<div class="container page">
|
|
3
|
+
<nav class="breadcrumb mb-4">
|
|
4
|
+
<%= link_to "Experiments", experiments_path, class: "breadcrumb-link" %>
|
|
5
|
+
<span class="breadcrumb-sep">/</span>
|
|
6
|
+
<%= link_to @experiment.name, experiment_path(@experiment), class: "breadcrumb-link" %>
|
|
7
|
+
<span class="breadcrumb-sep">/</span>
|
|
8
|
+
<span class="breadcrumb-current">Edit</span>
|
|
9
|
+
</nav>
|
|
10
|
+
|
|
11
|
+
<div class="page-header mb-6">
|
|
12
|
+
<div>
|
|
13
|
+
<h1>Edit Experiment</h1>
|
|
14
|
+
<p class="text-muted text-sm mt-1">Update configuration for "<%= @experiment.name %>"</p>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
4
18
|
<%= render 'form', experiment: @experiment %>
|
|
5
|
-
</div>
|
|
19
|
+
</div>
|
|
@@ -1,55 +1,60 @@
|
|
|
1
1
|
<% content_for :title, 'Experiments' %>
|
|
2
|
-
<div class="container
|
|
3
|
-
<div class="
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
<
|
|
2
|
+
<div class="container page">
|
|
3
|
+
<div class="page-header">
|
|
4
|
+
<div>
|
|
5
|
+
<h1>Experiments</h1>
|
|
6
|
+
<p class="text-muted text-sm mt-1">Run evaluations across datasets to measure model performance</p>
|
|
7
|
+
</div>
|
|
8
|
+
<%= link_to new_experiment_path, class: "btn btn-primary" do %>
|
|
9
|
+
<svg class="icon-sm" viewBox="0 0 20 20" fill="currentColor">
|
|
7
10
|
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd" />
|
|
8
11
|
</svg>
|
|
9
|
-
|
|
12
|
+
New Experiment
|
|
10
13
|
<% end %>
|
|
11
14
|
</div>
|
|
15
|
+
|
|
12
16
|
<% if @experiments.any? %>
|
|
13
|
-
<div class="
|
|
14
|
-
<div class="
|
|
15
|
-
<table class="
|
|
16
|
-
<thead
|
|
17
|
+
<div class="table-wrapper">
|
|
18
|
+
<div class="table-scroll">
|
|
19
|
+
<table class="table table-experiments">
|
|
20
|
+
<thead>
|
|
17
21
|
<tr>
|
|
18
|
-
<th
|
|
19
|
-
|
|
20
|
-
</th>
|
|
21
|
-
<th
|
|
22
|
-
|
|
23
|
-
</th>
|
|
24
|
-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider whitespace-nowrap">
|
|
25
|
-
Total Results
|
|
26
|
-
</th>
|
|
22
|
+
<th>Experiment</th>
|
|
23
|
+
<th style="width: 140px;">Dataset</th>
|
|
24
|
+
<th class="text-right" style="width: 90px;">Created</th>
|
|
25
|
+
<th class="text-center" style="width: 90px;">Status</th>
|
|
26
|
+
<th class="text-right" style="width: 60px;">Runs</th>
|
|
27
27
|
<% Leva::EvaluationResult.distinct.pluck(:evaluator_class).each do |evaluator_class| %>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
<%
|
|
29
|
+
# Clean up evaluator name: "SentimentAccuracyEval" -> "Accuracy"
|
|
30
|
+
# Remove common prefixes/suffixes and module names
|
|
31
|
+
short_name = evaluator_class.demodulize
|
|
32
|
+
.gsub(/Evaluator$/, '')
|
|
33
|
+
.gsub(/Eval$/, '')
|
|
34
|
+
.gsub(/^Sentiment/, '') # Remove domain prefix
|
|
35
|
+
.gsub(/^[A-Z][a-z]+(?=[A-Z])/, '') # Remove leading word if followed by another
|
|
36
|
+
short_name = short_name.presence || evaluator_class.demodulize.gsub(/Eval(uator)?$/, '')
|
|
37
|
+
%>
|
|
38
|
+
<th class="text-right cell-numeric" style="width: 90px;"><%= short_name %></th>
|
|
31
39
|
<% end %>
|
|
32
|
-
<th scope="col" class="relative px-6 py-3">
|
|
33
|
-
<span class="sr-only">Actions</span>
|
|
34
|
-
</th>
|
|
35
40
|
</tr>
|
|
36
41
|
</thead>
|
|
37
|
-
<tbody
|
|
42
|
+
<tbody>
|
|
38
43
|
<%= render @experiments %>
|
|
39
44
|
</tbody>
|
|
40
45
|
</table>
|
|
41
46
|
</div>
|
|
42
47
|
</div>
|
|
43
48
|
<% else %>
|
|
44
|
-
<div class="
|
|
45
|
-
<
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<%= link_to new_experiment_path, class: "btn btn-primary
|
|
52
|
-
<svg
|
|
49
|
+
<div class="card">
|
|
50
|
+
<div class="empty-state">
|
|
51
|
+
<svg class="empty-state-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
52
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
|
|
53
|
+
</svg>
|
|
54
|
+
<h3 class="empty-state-title">No experiments yet</h3>
|
|
55
|
+
<p class="empty-state-description">Experiments let you run evaluations across datasets and track model performance over time.</p>
|
|
56
|
+
<%= link_to new_experiment_path, class: "btn btn-primary" do %>
|
|
57
|
+
<svg class="icon-sm" viewBox="0 0 20 20" fill="currentColor">
|
|
53
58
|
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd" />
|
|
54
59
|
</svg>
|
|
55
60
|
Create your first experiment
|
|
@@ -57,4 +62,4 @@
|
|
|
57
62
|
</div>
|
|
58
63
|
</div>
|
|
59
64
|
<% end %>
|
|
60
|
-
</div>
|
|
65
|
+
</div>
|
|
@@ -1,27 +1,48 @@
|
|
|
1
1
|
<% content_for :title, 'New Experiment' %>
|
|
2
|
-
<div class="container
|
|
3
|
-
<
|
|
2
|
+
<div class="container page">
|
|
3
|
+
<nav class="breadcrumb mb-4">
|
|
4
|
+
<%= link_to "Experiments", experiments_path, class: "breadcrumb-link" %>
|
|
5
|
+
<span class="breadcrumb-sep">/</span>
|
|
6
|
+
<span class="breadcrumb-current">New</span>
|
|
7
|
+
</nav>
|
|
8
|
+
|
|
9
|
+
<div class="page-header mb-6">
|
|
10
|
+
<div>
|
|
11
|
+
<h1>New Experiment</h1>
|
|
12
|
+
<p class="text-muted text-sm mt-1">Configure and run a new evaluation experiment</p>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
4
16
|
<% if @runners.present? %>
|
|
5
17
|
<%= render 'form', experiment: @experiment %>
|
|
6
18
|
<% else %>
|
|
7
|
-
<div class="
|
|
8
|
-
<
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
<p class="
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
<div class="card p-6">
|
|
20
|
+
<div class="setup-required">
|
|
21
|
+
<div class="setup-required-icon">
|
|
22
|
+
<svg class="icon-xl" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
23
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
24
|
+
</svg>
|
|
25
|
+
</div>
|
|
26
|
+
<h3 class="setup-required-title">Setup Required</h3>
|
|
27
|
+
<p class="setup-required-desc">You need at least one runner to create an experiment.</p>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<hr class="form-divider">
|
|
31
|
+
|
|
32
|
+
<div class="setup-help">
|
|
33
|
+
<h4 class="setup-help-title">What are runners?</h4>
|
|
34
|
+
<p class="setup-help-text">Runners define how your model processes dataset records. They contain the execution logic that takes a dataset record and returns a prediction.</p>
|
|
35
|
+
|
|
36
|
+
<h4 class="setup-help-title mt-6">Create your first runner</h4>
|
|
37
|
+
<div class="setup-code-block">
|
|
38
|
+
<code>rails generate leva:runner sentiment</code>
|
|
19
39
|
</div>
|
|
20
|
-
<p class="
|
|
40
|
+
<p class="setup-help-hint">This creates <code>app/runners/sentiment_run.rb</code> with a template to implement your model logic.</p>
|
|
21
41
|
</div>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
42
|
+
|
|
43
|
+
<div class="form-actions">
|
|
44
|
+
<%= link_to experiments_path, class: "btn btn-ghost" do %>
|
|
45
|
+
<svg class="icon-sm" viewBox="0 0 20 20" fill="currentColor">
|
|
25
46
|
<path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd" />
|
|
26
47
|
</svg>
|
|
27
48
|
Back to Experiments
|
|
@@ -29,4 +50,4 @@
|
|
|
29
50
|
</div>
|
|
30
51
|
</div>
|
|
31
52
|
<% end %>
|
|
32
|
-
</div>
|
|
53
|
+
</div>
|