leva 0.1.9.1 → 0.1.11

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -5
  3. data/app/assets/stylesheets/leva/application.css +3083 -15
  4. data/app/controllers/leva/application_controller.rb +1 -1
  5. data/app/controllers/leva/dataset_records_controller.rb +1 -1
  6. data/app/controllers/leva/datasets_controller.rb +6 -6
  7. data/app/controllers/leva/design_system_controller.rb +9 -0
  8. data/app/controllers/leva/experiments_controller.rb +8 -8
  9. data/app/controllers/leva/runner_results_controller.rb +1 -1
  10. data/app/controllers/leva/workbench_controller.rb +26 -15
  11. data/app/helpers/leva/application_helper.rb +7 -7
  12. data/app/jobs/leva/experiment_job.rb +1 -1
  13. data/app/jobs/leva/run_eval_job.rb +1 -1
  14. data/app/models/concerns/leva/recordable.rb +5 -5
  15. data/app/models/leva/dataset.rb +1 -1
  16. data/app/models/leva/evaluation_result.rb +1 -1
  17. data/app/models/leva/experiment.rb +1 -1
  18. data/app/models/leva/prompt.rb +1 -1
  19. data/app/views/layouts/leva/application.html.erb +23 -24
  20. data/app/views/leva/dataset_records/index.html.erb +70 -43
  21. data/app/views/leva/dataset_records/show.html.erb +115 -25
  22. data/app/views/leva/datasets/_dataset.html.erb +11 -18
  23. data/app/views/leva/datasets/_form.html.erb +18 -14
  24. data/app/views/leva/datasets/edit.html.erb +16 -4
  25. data/app/views/leva/datasets/index.html.erb +33 -41
  26. data/app/views/leva/datasets/new.html.erb +15 -4
  27. data/app/views/leva/datasets/show.html.erb +120 -139
  28. data/app/views/leva/design_system/index.html.erb +1731 -0
  29. data/app/views/leva/experiments/_experiment.html.erb +46 -31
  30. data/app/views/leva/experiments/_form.html.erb +62 -35
  31. data/app/views/leva/experiments/edit.html.erb +17 -3
  32. data/app/views/leva/experiments/index.html.erb +41 -36
  33. data/app/views/leva/experiments/new.html.erb +52 -4
  34. data/app/views/leva/experiments/show.html.erb +155 -98
  35. data/app/views/leva/runner_results/show.html.erb +271 -54
  36. data/app/views/leva/workbench/_evaluation_area.html.erb +18 -4
  37. data/app/views/leva/workbench/_prompt_content.html.erb +124 -73
  38. data/app/views/leva/workbench/_prompt_form.html.erb +24 -23
  39. data/app/views/leva/workbench/_prompt_sidebar.html.erb +57 -12
  40. data/app/views/leva/workbench/_results_section.html.erb +274 -112
  41. data/app/views/leva/workbench/_top_bar.html.erb +16 -6
  42. data/app/views/leva/workbench/edit.html.erb +46 -15
  43. data/app/views/leva/workbench/index.html.erb +5 -8
  44. data/app/views/leva/workbench/new.html.erb +74 -42
  45. data/config/routes.rb +11 -9
  46. data/db/migrate/20240813173033_create_leva_dataset_records.rb +1 -0
  47. data/db/migrate/20240813173035_create_leva_experiments.rb +2 -0
  48. data/db/migrate/{20240816201419_create_leva_runner_results.rb → 20240813173040_create_leva_runner_results.rb} +4 -1
  49. data/db/migrate/20240813173050_create_leva_evaluation_results.rb +3 -3
  50. data/lib/generators/leva/eval_generator.rb +4 -4
  51. data/lib/generators/leva/runner_generator.rb +4 -4
  52. data/lib/generators/leva/templates/runner.rb.erb +20 -0
  53. data/lib/leva/version.rb +1 -1
  54. data/lib/leva.rb +24 -2
  55. metadata +5 -11
  56. data/db/migrate/20240816201433_update_leva_evaluation_results.rb +0 -8
  57. data/db/migrate/20240821163608_make_experiment_optional_for_runner_results.rb +0 -6
  58. data/db/migrate/20240821181934_add_prompt_to_leva_runner_results.rb +0 -5
  59. data/db/migrate/20240821183153_add_runner_and_evaluator_to_leva_experiments.rb +0 -6
  60. data/db/migrate/20240821191713_add_actual_result_to_leva_dataset_records.rb +0 -5
  61. data/db/migrate/20240822143201_remove_actual_result_from_leva_runner_results.rb +0 -5
  62. data/db/migrate/20240912183556_add_runner_class_to_leva_runner_results.rb +0 -5
  63. data/lib/tasks/auto_annotate_models.rake +0 -59
@@ -4,4 +4,4 @@ module Leva
4
4
  class ApplicationController < ActionController::Base
5
5
  protect_from_forgery with: :exception
6
6
  end
7
- end
7
+ end
@@ -18,4 +18,4 @@ module Leva
18
18
  @dataset = Dataset.find(params[:dataset_id])
19
19
  end
20
20
  end
21
- end
21
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Leva
4
4
  class DatasetsController < ApplicationController
5
- before_action :set_dataset, only: [:show, :edit, :update, :destroy]
5
+ before_action :set_dataset, only: [ :show, :edit, :update, :destroy ]
6
6
 
7
7
  # GET /datasets
8
8
  # @return [void]
@@ -35,7 +35,7 @@ module Leva
35
35
  @dataset = Dataset.new(dataset_params)
36
36
 
37
37
  if @dataset.save
38
- redirect_to @dataset, notice: 'Dataset was successfully created.'
38
+ redirect_to @dataset, notice: "Dataset was successfully created."
39
39
  else
40
40
  render :new
41
41
  end
@@ -45,7 +45,7 @@ module Leva
45
45
  # @return [void]
46
46
  def update
47
47
  if @dataset.update(dataset_params)
48
- redirect_to @dataset, notice: 'Dataset was successfully updated.'
48
+ redirect_to @dataset, notice: "Dataset was successfully updated."
49
49
  else
50
50
  render :edit
51
51
  end
@@ -55,10 +55,10 @@ module Leva
55
55
  # @return [void]
56
56
  def destroy
57
57
  if @dataset.dataset_records.any?
58
- redirect_to @dataset, alert: 'Cannot delete dataset with existing records.'
58
+ redirect_to @dataset, alert: "Cannot delete dataset with existing records."
59
59
  else
60
60
  @dataset.destroy
61
- redirect_to datasets_url, notice: 'Dataset was successfully destroyed.'
61
+ redirect_to datasets_url, notice: "Dataset was successfully destroyed."
62
62
  end
63
63
  end
64
64
 
@@ -76,4 +76,4 @@ module Leva
76
76
  params.require(:dataset).permit(:name, :description)
77
77
  end
78
78
  end
79
- end
79
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leva
4
+ # Controller for viewing the design system components
5
+ class DesignSystemController < ApplicationController
6
+ def index
7
+ end
8
+ end
9
+ end
@@ -4,9 +4,9 @@ module Leva
4
4
  class ExperimentsController < ApplicationController
5
5
  include ApplicationHelper
6
6
 
7
- before_action :set_experiment, only: [:show, :edit, :update]
8
- before_action :check_editable, only: [:edit, :update]
9
- before_action :load_runners_and_evaluators, only: [:new, :edit, :create, :update]
7
+ before_action :set_experiment, only: [ :show, :edit, :update ]
8
+ before_action :check_editable, only: [ :edit, :update ]
9
+ before_action :load_runners_and_evaluators, only: [ :new, :edit, :create, :update ]
10
10
 
11
11
  # GET /experiments
12
12
  # @return [void]
@@ -39,7 +39,7 @@ module Leva
39
39
 
40
40
  if @experiment.save
41
41
  ExperimentJob.perform_later(@experiment) unless @experiment.completed?
42
- redirect_to @experiment, notice: 'Experiment was successfully created and is now running.'
42
+ redirect_to @experiment, notice: "Experiment was successfully created and is now running."
43
43
  else
44
44
  render :new
45
45
  end
@@ -49,7 +49,7 @@ module Leva
49
49
  # @return [void]
50
50
  def update
51
51
  if @experiment.update(experiment_params)
52
- redirect_to @experiment, notice: 'Experiment was successfully updated.'
52
+ redirect_to @experiment, notice: "Experiment was successfully updated."
53
53
  else
54
54
  render :edit
55
55
  end
@@ -69,7 +69,7 @@ module Leva
69
69
  # Queue the job again
70
70
  ExperimentJob.perform_later(@experiment)
71
71
 
72
- redirect_to @experiment, notice: 'Experiment has been reset and is now running again.'
72
+ redirect_to @experiment, notice: "Experiment has been reset and is now running again."
73
73
  end
74
74
 
75
75
  private
@@ -92,7 +92,7 @@ module Leva
92
92
  end
93
93
 
94
94
  def check_editable
95
- redirect_to @experiment, alert: 'Completed experiments cannot be edited.' if @experiment.completed?
95
+ redirect_to @experiment, alert: "Completed experiments cannot be edited." if @experiment.completed?
96
96
  end
97
97
  end
98
- end
98
+ end
@@ -5,4 +5,4 @@ module Leva
5
5
  @runner_result = @experiment.runner_results.find(params[:id])
6
6
  end
7
7
  end
8
- end
8
+ end
@@ -4,9 +4,9 @@ module Leva
4
4
  class WorkbenchController < ApplicationController
5
5
  include ApplicationHelper
6
6
 
7
- before_action :set_prompt, only: [:index, :edit, :update, :run, :run_all_evals, :run_evaluator]
8
- before_action :set_dataset_record, only: [:index, :run, :run_all_evals, :run_evaluator]
9
- before_action :set_runner_result, only: [:index, :run_all_evals, :run_evaluator]
7
+ before_action :set_prompt, only: [ :index, :edit, :update, :run, :run_all_evals, :run_evaluator ]
8
+ before_action :set_dataset_record, only: [ :index, :run, :run_all_evals, :run_evaluator ]
9
+ before_action :set_runner_result, only: [ :index, :run_all_evals, :run_evaluator ]
10
10
 
11
11
  # GET /workbench
12
12
  # @return [void]
@@ -17,6 +17,17 @@ module Leva
17
17
  @runners = load_runners
18
18
  @selected_runner = params[:runner] || @runners.first&.name
19
19
  @selected_dataset_record = params[:dataset_record_id] || DatasetRecord.first&.id
20
+
21
+ # Get merged context if runner and dataset record are available
22
+ if @selected_runner && @dataset_record
23
+ runner_class = @selected_runner.constantize rescue nil
24
+ if runner_class && runner_class < Leva::BaseRun
25
+ runner = runner_class.new
26
+ @record_context = @dataset_record.recordable.to_llm_context
27
+ @runner_context = runner.to_llm_context(@dataset_record.recordable)
28
+ @merged_context = @record_context.merge(@runner_context)
29
+ end
30
+ end
20
31
  end
21
32
 
22
33
  # GET /workbench/new
@@ -31,7 +42,7 @@ module Leva
31
42
  def create
32
43
  @prompt = Prompt.new(prompt_params)
33
44
  if @prompt.save
34
- redirect_to workbench_index_path(prompt_id: @prompt.id), notice: 'Prompt was successfully created.'
45
+ redirect_to workbench_index_path(prompt_id: @prompt.id), notice: "Prompt was successfully created."
35
46
  else
36
47
  render :new
37
48
  end
@@ -47,45 +58,45 @@ module Leva
47
58
  def update
48
59
  @prompt = Prompt.find(params[:id])
49
60
  if @prompt.update(prompt_params)
50
- render json: { status: 'success', message: 'Prompt updated successfully' }
61
+ render json: { status: "success", message: "Prompt updated successfully" }
51
62
  else
52
- render json: { status: 'error', errors: @prompt.errors.full_messages }, status: :unprocessable_entity
63
+ render json: { status: "error", errors: @prompt.errors.full_messages }, status: :unprocessable_entity
53
64
  end
54
65
  end
55
66
 
56
67
  def run
57
- return redirect_to workbench_index_path, alert: 'Please select a record and a runner' unless @dataset_record && run_params[:runner]
68
+ return redirect_to workbench_index_path, alert: "Please select a record and a runner" unless @dataset_record && run_params[:runner]
58
69
 
59
70
  runner_class = run_params[:runner].constantize
60
- return redirect_to workbench_index_path, alert: 'Invalid runner selected' unless runner_class < Leva::BaseRun
71
+ return redirect_to workbench_index_path, alert: "Invalid runner selected" unless runner_class < Leva::BaseRun
61
72
 
62
73
  runner = runner_class.new
63
74
  runner_result = runner.execute_and_store(nil, @dataset_record, @prompt)
64
75
 
65
- redirect_to workbench_index_path(prompt_id: @prompt.id, dataset_record_id: @dataset_record.id, runner: run_params[:runner]), notice: 'Run completed successfully'
76
+ redirect_to workbench_index_path(prompt_id: @prompt.id, dataset_record_id: @dataset_record.id, runner: run_params[:runner]), notice: "Run completed successfully"
66
77
  end
67
78
 
68
79
  def run_all_evals
69
- return redirect_to workbench_index_path, alert: 'No runner result available' unless @runner_result
80
+ return redirect_to workbench_index_path, alert: "No runner result available" unless @runner_result
70
81
 
71
82
  load_evaluators.each do |evaluator_class|
72
83
  evaluator = evaluator_class.new
73
84
  evaluator.evaluate_and_store(nil, @runner_result)
74
85
  end
75
86
 
76
- redirect_to workbench_index_path(prompt_id: @prompt.id, dataset_record_id: @dataset_record.id, runner: params[:runner]), notice: 'All evaluations completed successfully'
87
+ redirect_to workbench_index_path(prompt_id: @prompt.id, dataset_record_id: @dataset_record.id, runner: params[:runner]), notice: "All evaluations completed successfully"
77
88
  end
78
89
 
79
90
  def run_evaluator
80
- return redirect_to workbench_index_path, alert: 'No runner result available' unless @runner_result
91
+ return redirect_to workbench_index_path, alert: "No runner result available" unless @runner_result
81
92
 
82
93
  evaluator_class = params[:evaluator].constantize
83
- return redirect_to workbench_index_path, alert: 'Invalid evaluator selected' unless evaluator_class < Leva::BaseEval
94
+ return redirect_to workbench_index_path, alert: "Invalid evaluator selected" unless evaluator_class < Leva::BaseEval
84
95
 
85
96
  evaluator = evaluator_class.new
86
97
  evaluator.evaluate_and_store(nil, @runner_result)
87
98
 
88
- redirect_to workbench_index_path(prompt_id: @prompt.id, dataset_record_id: @dataset_record.id, runner: params[:runner]), notice: 'Evaluator run successfully'
99
+ redirect_to workbench_index_path(prompt_id: @prompt.id, dataset_record_id: @dataset_record.id, runner: params[:runner]), notice: "Evaluator run successfully"
89
100
  end
90
101
 
91
102
  private
@@ -110,4 +121,4 @@ module Leva
110
121
  @runner_result = @dataset_record.runner_results.last if @dataset_record
111
122
  end
112
123
  end
113
- end
124
+ end
@@ -4,24 +4,24 @@ module Leva
4
4
  #
5
5
  # @return [Array<Class>] An array of evaluator classes
6
6
  def load_evaluators
7
- load_classes_from_directory('app/evals', Leva::BaseEval) || []
7
+ load_classes_from_directory("app/evals", Leva::BaseEval) || []
8
8
  end
9
9
 
10
10
  # Loads all runner classes that inherit from Leva::BaseRun
11
11
  #
12
12
  # @return [Array<Class>] An array of runner classes
13
13
  def load_runners
14
- load_classes_from_directory('app/runners', Leva::BaseRun) || []
14
+ load_classes_from_directory("app/runners", Leva::BaseRun) || []
15
15
  end
16
16
 
17
17
  # Loads predefined prompts from markdown files
18
18
  #
19
19
  # @return [Array<Array<String, String>>] An array of prompt name and content pairs
20
20
  def load_predefined_prompts
21
- prompts = Dir.glob(Rails.root.join('app', 'prompts', '*.md')).map do |file|
22
- name = File.basename(file, '.md').titleize
21
+ prompts = Dir.glob(Rails.root.join("app", "prompts", "*.md")).map do |file|
22
+ name = File.basename(file, ".md").titleize
23
23
  content = File.read(file)
24
- [name, content]
24
+ [ name, content ]
25
25
  end
26
26
  prompts
27
27
  end
@@ -34,8 +34,8 @@ module Leva
34
34
  # @param base_class [Class] The base class that loaded classes should inherit from
35
35
  # @return [Array<Class>] An array of loaded classes
36
36
  def load_classes_from_directory(directory, base_class)
37
- classes = Dir[Rails.root.join(directory, '*.rb')].map do |file|
38
- File.basename(file, '.rb').camelize.constantize
37
+ classes = Dir[Rails.root.join(directory, "*.rb")].map do |file|
38
+ File.basename(file, ".rb").camelize.constantize
39
39
  end.select { |klass| klass < base_class }
40
40
  classes.empty? ? [] : classes
41
41
  end
@@ -18,4 +18,4 @@ module Leva
18
18
  end
19
19
  end
20
20
  end
21
- end
21
+ end
@@ -37,4 +37,4 @@ module Leva
37
37
  experiment.dataset.dataset_records.count == experiment.runner_results.count
38
38
  end
39
39
  end
40
- end
40
+ end
@@ -3,10 +3,10 @@ module Leva
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- has_many :dataset_records, as: :recordable, class_name: 'Leva::DatasetRecord', dependent: :destroy
7
- has_many :datasets, through: :dataset_records, class_name: 'Leva::Dataset'
8
- has_many :runner_results, through: :dataset_records, class_name: 'Leva::RunnerResult'
9
- has_many :evaluation_results, through: :runner_results, class_name: 'Leva::EvaluationResult'
6
+ has_many :dataset_records, as: :recordable, class_name: "Leva::DatasetRecord", dependent: :destroy
7
+ has_many :datasets, through: :dataset_records, class_name: "Leva::Dataset"
8
+ has_many :runner_results, through: :dataset_records, class_name: "Leva::RunnerResult"
9
+ has_many :evaluation_results, through: :runner_results, class_name: "Leva::EvaluationResult"
10
10
  end
11
11
 
12
12
  # @return [String] The ground truth label for the record
@@ -34,4 +34,4 @@ module Leva
34
34
  false
35
35
  end
36
36
  end
37
- end
37
+ end
@@ -25,4 +25,4 @@ module Leva
25
25
  end
26
26
  end
27
27
  end
28
- end
28
+ end
@@ -36,4 +36,4 @@ module Leva
36
36
 
37
37
  scope :for_evaluator, ->(evaluator_class) { where(evaluator_class: evaluator_class.name) }
38
38
  end
39
- end
39
+ end
@@ -40,4 +40,4 @@ module Leva
40
40
 
41
41
  serialize :evaluator_classes, coder: JSON, type: Array
42
42
  end
43
- end
43
+ end
@@ -27,4 +27,4 @@ module Leva
27
27
  self.version += 1
28
28
  end
29
29
  end
30
- end
30
+ end
@@ -1,40 +1,39 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en" class="bg-gray-950">
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Leva - <%= yield(:title) || 'AI Evaluation Engine' %></title>
7
7
  <%= csrf_meta_tags %>
8
8
  <%= csp_meta_tag %>
9
- <script src="https://cdn.tailwindcss.com"></script>
9
+
10
+ <%# Fira Code for monospace, Helvetica is system font %>
11
+ <link rel="preconnect" href="https://fonts.googleapis.com">
12
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13
+ <link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
14
+
15
+ <%= stylesheet_link_tag "leva/application", media: "all" %>
10
16
  <script src="https://cdn.jsdelivr.net/npm/stimulus@3.2.2/dist/stimulus.umd.min.js"></script>
11
17
  <%= yield(:head) %>
12
18
  </head>
13
- <body class="bg-gray-950 text-white">
14
- <nav class="bg-gray-900 border-b border-gray-800">
15
- <div class="px-4">
16
- <div class="flex h-16 items-center">
17
- <%= link_to root_path, class: 'flex items-center text-2xl font-bold text-indigo-400' do %>
18
- <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mr-2" viewBox="0 0 24 24" fill="none" stroke="url(#gradient)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
19
- <defs>
20
- <linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
21
- <stop offset="0%" stop-color="#4F46E5" />
22
- <stop offset="100%" stop-color="#9333EA" />
23
- </linearGradient>
24
- </defs>
25
- <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
26
- </svg>
27
- LLM Evals
28
- <% end %>
29
- <div class="ml-10 flex space-x-6">
30
- <%= link_to 'Workbench', workbench_index_path, class: "px-3 py-2 rounded-md text-sm font-medium transition-colors duration-150 ease-in-out #{request.path.start_with?(workbench_index_path) ? 'bg-indigo-600 text-white shadow-lg' : 'text-gray-300 hover:bg-gray-800 hover:text-white'}" %>
31
- <%= link_to 'Datasets', datasets_path, class: "px-3 py-2 rounded-md text-sm font-medium transition-colors duration-150 ease-in-out #{request.path.start_with?(datasets_path) ? 'bg-indigo-600 text-white shadow-lg' : 'text-gray-300 hover:bg-gray-800 hover:text-white'}" %>
32
- <%= link_to 'Experiments', experiments_path, class: "px-3 py-2 rounded-md text-sm font-medium transition-colors duration-150 ease-in-out #{request.path.start_with?(experiments_path) ? 'bg-indigo-600 text-white shadow-lg' : 'text-gray-300 hover:bg-gray-800 hover:text-white'}" %>
33
- </div>
19
+ <body>
20
+ <nav class="nav">
21
+ <div class="nav-inner">
22
+ <%= link_to root_path, class: 'nav-brand' do %>
23
+ <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="nav-logo">
24
+ <rect x="2" y="2" width="16" height="16" rx="3" fill="currentColor" fill-opacity="0.15"/>
25
+ <path d="M6 6V14H14" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
26
+ </svg>
27
+ <span>leva</span>
28
+ <% end %>
29
+ <div class="nav-links">
30
+ <%= link_to 'Workbench', workbench_index_path, class: "nav-link #{request.path == root_path || request.path.start_with?(workbench_index_path) ? 'active' : ''}" %>
31
+ <%= link_to 'Datasets', datasets_path, class: "nav-link #{request.path.start_with?(datasets_path) ? 'active' : ''}" %>
32
+ <%= link_to 'Experiments', experiments_path, class: "nav-link #{request.path.start_with?(experiments_path) ? 'active' : ''}" %>
34
33
  </div>
35
34
  </div>
36
35
  </nav>
37
- <main class="bg-gray-950">
36
+ <main>
38
37
  <%= yield %>
39
38
  </main>
40
39
  </body>
@@ -1,49 +1,76 @@
1
1
  <% content_for :title, "#{@dataset.name} - Records" %>
2
- <div class="container mx-auto px-4 py-8 bg-gray-950 text-white">
3
- <div class="mb-8">
4
- <div class="flex justify-between items-center">
5
- <h1 class="text-3xl font-bold text-indigo-400 mb-2"><%= @dataset.name %> - Records</h1>
6
- <%= link_to dataset_path(@dataset), class: 'btn btn-secondary flex items-center' do %>
7
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
8
- <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" />
9
- </svg>
10
- Back to Dataset
11
- <% end %>
2
+ <div class="container page">
3
+ <%# Breadcrumb Navigation %>
4
+ <nav class="breadcrumb mb-4">
5
+ <%= link_to datasets_path, class: 'breadcrumb-link' do %>Datasets<% end %>
6
+ <span class="breadcrumb-sep">/</span>
7
+ <%= link_to @dataset.name, dataset_path(@dataset), class: 'breadcrumb-link' %>
8
+ <span class="breadcrumb-sep">/</span>
9
+ <span class="breadcrumb-current">Records</span>
10
+ </nav>
11
+
12
+ <div class="page-header">
13
+ <div>
14
+ <h1 class="page-title">Dataset Records</h1>
15
+ <p class="text-muted mt-2">All records in <strong><%= @dataset.name %></strong></p>
12
16
  </div>
13
17
  </div>
14
18
 
15
- <div class="bg-gray-800 rounded-lg shadow-lg overflow-hidden">
16
- <table class="min-w-full divide-y divide-gray-700">
17
- <thead class="bg-gray-700">
18
- <tr>
19
- <% @records.first.index_attributes.keys.each do |key| %>
20
- <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">
21
- <%= key.to_s.humanize %>
22
- </th>
23
- <% end %>
24
- <th scope="col" class="relative px-6 py-3">
25
- <span class="sr-only">Actions</span>
26
- </th>
27
- </tr>
28
- </thead>
29
- <tbody class="bg-gray-800 divide-y divide-gray-700">
30
- <% @records.each do |record| %>
31
- <tr class="hover:bg-gray-700 transition-colors duration-200">
32
- <% record.index_attributes.values.each do |value| %>
33
- <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">
34
- <%= value %>
35
- </td>
19
+ <% if @records.any? %>
20
+ <%# Section Header with Count %>
21
+ <div class="section-header">
22
+ <h3 class="section-title">Records</h3>
23
+ <span class="section-count"><%= @records.count %> total</span>
24
+ </div>
25
+
26
+ <div class="table-wrapper">
27
+ <div class="table-scroll">
28
+ <table class="table table-clickable">
29
+ <thead>
30
+ <tr>
31
+ <% @records.first.index_attributes.keys.each do |key| %>
32
+ <th><%= key.to_s.humanize %></th>
33
+ <% end %>
34
+ <th class="text-right" style="width: 50px;"></th>
35
+ </tr>
36
+ </thead>
37
+ <tbody>
38
+ <% @records.each do |record| %>
39
+ <tr class="clickable-row" onclick="window.location='<%= dataset_dataset_record_path(@dataset, record) %>'">
40
+ <% record.index_attributes.each_with_index do |(key, value), index| %>
41
+ <td class="<%= index == 0 ? '' : 'cell-truncate' %>">
42
+ <%= index == 0 ? content_tag(:span, value, class: 'row-title') : value %>
43
+ </td>
44
+ <% end %>
45
+ <td class="text-right">
46
+ <svg class="icon-sm text-muted" viewBox="0 0 20 20" fill="currentColor" style="opacity: 0.5;">
47
+ <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
48
+ </svg>
49
+ </td>
50
+ </tr>
36
51
  <% end %>
37
- <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
38
- <%= link_to 'View', dataset_dataset_record_path(@dataset, record), class: 'text-indigo-400 hover:text-indigo-300 transition-colors duration-200' %>
39
- </td>
40
- </tr>
41
- <% end %>
42
- </tbody>
43
- </table>
44
- </div>
52
+ </tbody>
53
+ </table>
54
+ </div>
55
+ </div>
56
+ <% else %>
57
+ <div class="card p-6">
58
+ <div class="empty-state" style="padding: var(--space-10) var(--space-6);">
59
+ <svg class="empty-state-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
60
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
61
+ </svg>
62
+ <h3 class="empty-state-title">No records in this dataset</h3>
63
+ <p class="empty-state-description">Add records to start building your evaluation dataset.</p>
64
+ </div>
45
65
 
46
- <div class="mt-4 text-gray-400 text-sm">
47
- Total records: <%= @records.count %>
48
- </div>
49
- </div>
66
+ <div class="divider"></div>
67
+
68
+ <div class="p-5" style="background: var(--gray-850, #222120); border-radius: var(--radius-md);">
69
+ <h4 class="text-sm font-semibold mb-3" style="color: var(--gray-300);">Adding Records</h4>
70
+ <p class="text-sm text-muted mb-3">Use the Rails console to add records to this dataset:</p>
71
+ <pre style="margin: 0;"><code>dataset = Leva::Dataset.find(<%= @dataset.id %>)
72
+ dataset.add_record(your_model_instance)</code></pre>
73
+ </div>
74
+ </div>
75
+ <% end %>
76
+ </div>