leva 0.1.5 → 0.1.7

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 +55 -16
  3. data/app/controllers/leva/dataset_records_controller.rb +21 -0
  4. data/app/controllers/leva/datasets_controller.rb +9 -2
  5. data/app/controllers/leva/experiments_controller.rb +34 -9
  6. data/app/controllers/leva/runner_results_controller.rb +8 -0
  7. data/app/controllers/leva/workbench_controller.rb +85 -12
  8. data/app/helpers/leva/application_helper.rb +39 -0
  9. data/app/javascript/controllers/prompt_form_controller.js +45 -0
  10. data/app/javascript/controllers/prompt_selector_controller.js +31 -0
  11. data/app/jobs/leva/experiment_job.rb +9 -4
  12. data/app/jobs/leva/run_eval_job.rb +40 -0
  13. data/app/models/concerns/leva/recordable.rb +37 -0
  14. data/app/models/leva/dataset.rb +15 -6
  15. data/app/models/leva/dataset_record.rb +40 -1
  16. data/app/models/leva/evaluation_result.rb +15 -7
  17. data/app/models/leva/experiment.rb +24 -12
  18. data/app/models/leva/prompt.rb +14 -1
  19. data/app/models/leva/runner_result.rb +56 -0
  20. data/app/views/layouts/leva/application.html.erb +24 -13
  21. data/app/views/leva/dataset_records/index.html.erb +49 -0
  22. data/app/views/leva/dataset_records/show.html.erb +30 -0
  23. data/app/views/leva/datasets/_dataset.html.erb +18 -0
  24. data/app/views/leva/datasets/_form.html.erb +24 -0
  25. data/app/views/leva/datasets/edit.html.erb +5 -0
  26. data/app/views/leva/datasets/index.html.erb +51 -38
  27. data/app/views/leva/datasets/new.html.erb +5 -0
  28. data/app/views/leva/datasets/show.html.erb +160 -8
  29. data/app/views/leva/experiments/_experiment.html.erb +42 -0
  30. data/app/views/leva/experiments/_form.html.erb +49 -0
  31. data/app/views/leva/experiments/edit.html.erb +5 -0
  32. data/app/views/leva/experiments/index.html.erb +53 -37
  33. data/app/views/leva/experiments/new.html.erb +5 -0
  34. data/app/views/leva/experiments/show.html.erb +115 -19
  35. data/app/views/leva/runner_results/show.html.erb +64 -0
  36. data/app/views/leva/workbench/_evaluation_area.html.erb +5 -0
  37. data/app/views/leva/workbench/_prompt_content.html.erb +216 -0
  38. data/app/views/leva/workbench/_prompt_form.html.erb +89 -0
  39. data/app/views/leva/workbench/_prompt_sidebar.html.erb +21 -0
  40. data/app/views/leva/workbench/_results_section.html.erb +159 -0
  41. data/app/views/leva/workbench/_top_bar.html.erb +10 -0
  42. data/app/views/leva/workbench/edit.html.erb +20 -0
  43. data/app/views/leva/workbench/index.html.erb +5 -91
  44. data/app/views/leva/workbench/new.html.erb +79 -36
  45. data/config/routes.rb +15 -6
  46. data/db/migrate/20240813172916_create_leva_datasets.rb +1 -0
  47. data/db/migrate/20240813173035_create_leva_experiments.rb +1 -0
  48. data/db/migrate/20240816201419_create_leva_runner_results.rb +11 -0
  49. data/db/migrate/20240816201433_update_leva_evaluation_results.rb +8 -0
  50. data/db/migrate/20240821163608_make_experiment_optional_for_runner_results.rb +6 -0
  51. data/db/migrate/20240821181934_add_prompt_to_leva_runner_results.rb +5 -0
  52. data/db/migrate/20240821183153_add_runner_and_evaluator_to_leva_experiments.rb +6 -0
  53. data/db/migrate/20240821191713_add_actual_result_to_leva_dataset_records.rb +5 -0
  54. data/db/migrate/20240822143201_remove_actual_result_from_leva_runner_results.rb +5 -0
  55. data/db/migrate/20240912183556_add_runner_class_to_leva_runner_results.rb +5 -0
  56. data/lib/generators/leva/templates/eval.rb.erb +7 -8
  57. data/lib/generators/leva/templates/runner.rb.erb +25 -0
  58. data/lib/leva/version.rb +1 -1
  59. data/lib/leva.rb +84 -44
  60. metadata +49 -5
  61. data/app/evals/test_sentiment_accuracy_eval.rb +0 -6
  62. data/app/runners/test_sentiment_run.rb +0 -13
  63. data/lib/leva/base_eval.rb +0 -75
@@ -3,6 +3,7 @@
3
3
  # Table name: leva_dataset_records
4
4
  #
5
5
  # id :integer not null, primary key
6
+ # actual_result :text
6
7
  # recordable_type :string not null
7
8
  # created_at :datetime not null
8
9
  # updated_at :datetime not null
@@ -16,11 +17,49 @@
16
17
  #
17
18
  # Foreign Keys
18
19
  #
19
- # dataset_id (dataset_id => datasets.id)
20
+ # dataset_id (dataset_id => leva_datasets.id)
20
21
  #
21
22
  module Leva
22
23
  class DatasetRecord < ApplicationRecord
23
24
  belongs_to :dataset
24
25
  belongs_to :recordable, polymorphic: true
26
+
27
+ has_many :runner_results, dependent: :destroy
28
+ has_many :evaluation_results, dependent: :destroy, through: :runner_results
29
+
30
+ delegate :ground_truth, to: :recordable
31
+
32
+ # @return [Hash] A hash of attributes to be displayed in the dataset records index
33
+ def index_attributes
34
+ if recordable.respond_to?(:index_attributes)
35
+ recordable.index_attributes
36
+ elsif recordable.respond_to?(:name)
37
+ { name: recordable.name }
38
+ else
39
+ { to_s: recordable.to_s }
40
+ end
41
+ end
42
+
43
+ # @return [Hash] A hash of attributes to be displayed in the dataset record show view
44
+ def show_attributes
45
+ if recordable.respond_to?(:show_attributes)
46
+ recordable.show_attributes
47
+ elsif recordable.respond_to?(:dataset_attributes)
48
+ recordable.dataset_attributes
49
+ else
50
+ { to_s: recordable.to_s }
51
+ end
52
+ end
53
+
54
+ # @return [String] A string representation of the record for display purposes
55
+ def display_name
56
+ if recordable.respond_to?(:name)
57
+ recordable.name
58
+ elsif recordable.respond_to?(:title)
59
+ recordable.title
60
+ else
61
+ "#{recordable_type} ##{recordable_id}"
62
+ end
63
+ end
25
64
  end
26
65
  end
@@ -3,29 +3,37 @@
3
3
  # Table name: leva_evaluation_results
4
4
  #
5
5
  # id :integer not null, primary key
6
- # label :string
7
- # prediction :string
6
+ # evaluator_class :string not null
8
7
  # score :float
9
8
  # created_at :datetime not null
10
9
  # updated_at :datetime not null
11
10
  # dataset_record_id :integer not null
12
- # experiment_id :integer not null
11
+ # experiment_id :integer
12
+ # runner_result_id :integer not null
13
13
  #
14
14
  # Indexes
15
15
  #
16
16
  # index_leva_evaluation_results_on_dataset_record_id (dataset_record_id)
17
17
  # index_leva_evaluation_results_on_experiment_id (experiment_id)
18
+ # index_leva_evaluation_results_on_runner_result_id (runner_result_id)
18
19
  #
19
20
  # Foreign Keys
20
21
  #
21
- # dataset_record_id (dataset_record_id => dataset_records.id)
22
- # experiment_id (experiment_id => experiments.id)
22
+ # dataset_record_id (dataset_record_id => leva_dataset_records.id)
23
+ # experiment_id (experiment_id => leva_experiments.id)
24
+ # runner_result_id (runner_result_id => leva_runner_results.id)
23
25
  #
24
26
  module Leva
25
27
  class EvaluationResult < ApplicationRecord
26
- belongs_to :experiment
28
+ belongs_to :runner_result
29
+ belongs_to :experiment, optional: true
27
30
  belongs_to :dataset_record
28
31
 
32
+ validates :score, presence: true
33
+ validates :evaluator_class, presence: true
34
+
29
35
  delegate :record, to: :dataset_record, allow_nil: true
36
+
37
+ scope :for_evaluator, ->(evaluator_class) { where(evaluator_class: evaluator_class.name) }
30
38
  end
31
- end
39
+ end
@@ -2,14 +2,17 @@
2
2
  #
3
3
  # Table name: leva_experiments
4
4
  #
5
- # id :integer not null, primary key
6
- # metadata :text
7
- # name :string
8
- # status :integer
9
- # created_at :datetime not null
10
- # updated_at :datetime not null
11
- # dataset_id :integer not null
12
- # prompt_id :integer
5
+ # id :integer not null, primary key
6
+ # description :text
7
+ # evaluator_classes :text
8
+ # metadata :text
9
+ # name :string
10
+ # runner_class :string
11
+ # status :integer
12
+ # created_at :datetime not null
13
+ # updated_at :datetime not null
14
+ # dataset_id :integer not null
15
+ # prompt_id :integer
13
16
  #
14
17
  # Indexes
15
18
  #
@@ -18,14 +21,23 @@
18
21
  #
19
22
  # Foreign Keys
20
23
  #
21
- # dataset_id (dataset_id => datasets.id)
22
- # prompt_id (prompt_id => prompts.id)
24
+ # dataset_id (dataset_id => leva_datasets.id)
25
+ # prompt_id (prompt_id => leva_prompts.id)
23
26
  #
24
27
  module Leva
25
28
  class Experiment < ApplicationRecord
26
29
  belongs_to :dataset
27
30
  belongs_to :prompt, optional: true
31
+ has_many :runner_results, dependent: :destroy
32
+ has_many :evaluation_results, through: :runner_results
28
33
 
29
- has_many :evaluation_results, dependent: :destroy
34
+ validates :name, presence: true
35
+ validates :dataset, presence: true
36
+ validates :runner_class, presence: true
37
+ validates :evaluator_classes, presence: true
38
+
39
+ enum :status, { pending: 0, running: 1, completed: 2, failed: 3 }, default: :pending
40
+
41
+ serialize :evaluator_classes, coder: JSON, type: Array
30
42
  end
31
- end
43
+ end
@@ -13,5 +13,18 @@
13
13
  #
14
14
  module Leva
15
15
  class Prompt < ApplicationRecord
16
+ has_many :experiments
17
+
18
+ validates :name, presence: true
19
+ validates :system_prompt, presence: true
20
+ validates :user_prompt, presence: true
21
+
22
+ before_save :increment_version
23
+
24
+ # @return [void]
25
+ def increment_version
26
+ self.version ||= 0
27
+ self.version += 1
28
+ end
16
29
  end
17
- end
30
+ end
@@ -0,0 +1,56 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: leva_runner_results
4
+ #
5
+ # id :integer not null, primary key
6
+ # prediction :text
7
+ # prompt_version :integer
8
+ # runner_class :string
9
+ # created_at :datetime not null
10
+ # updated_at :datetime not null
11
+ # dataset_record_id :integer not null
12
+ # experiment_id :integer
13
+ # prompt_id :integer not null
14
+ #
15
+ # Indexes
16
+ #
17
+ # index_leva_runner_results_on_dataset_record_id (dataset_record_id)
18
+ # index_leva_runner_results_on_experiment_id (experiment_id)
19
+ # index_leva_runner_results_on_prompt_id (prompt_id)
20
+ #
21
+ # Foreign Keys
22
+ #
23
+ # dataset_record_id (dataset_record_id => leva_dataset_records.id)
24
+ # experiment_id (experiment_id => leva_experiments.id)
25
+ # prompt_id (prompt_id => leva_prompts.id)
26
+ #
27
+ module Leva
28
+ class RunnerResult < ApplicationRecord
29
+ belongs_to :experiment, optional: true
30
+ belongs_to :dataset_record
31
+ belongs_to :prompt
32
+ has_many :evaluation_results, dependent: :destroy
33
+
34
+ validates :prediction, presence: true
35
+ validates :prompt, presence: true
36
+ validates :runner_class, presence: true
37
+
38
+ delegate :ground_truth, to: :dataset_record
39
+
40
+ # @return [Array<String>] The parsed draft responses
41
+ def parsed_predictions
42
+ @parsed_predictions ||= runner&.parsed_predictions(self) || []
43
+ end
44
+
45
+ # @return [String] The ground truth for this runner result
46
+ def ground_truth
47
+ @ground_truth ||= runner&.ground_truth(self)
48
+ end
49
+
50
+ private
51
+
52
+ def runner
53
+ @runner ||= runner_class&.constantize&.new
54
+ end
55
+ end
56
+ end
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en" class="bg-gray-900">
2
+ <html lang="en" class="bg-gray-950">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -7,23 +7,34 @@
7
7
  <%= csrf_meta_tags %>
8
8
  <%= csp_meta_tag %>
9
9
  <script src="https://cdn.tailwindcss.com"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/stimulus@3.2.2/dist/stimulus.umd.min.js"></script>
11
+ <%= yield(:head) %>
10
12
  </head>
11
- <body class="bg-gray-900 text-white">
12
- <nav class="bg-gray-800 border-b border-gray-700">
13
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
14
- <div class="flex justify-between h-16">
15
- <div class="flex">
16
- <%= link_to 'Leva', leva.root_path, class: 'flex-shrink-0 flex items-center text-2xl font-bold text-indigo-400' %>
17
- <div class="hidden sm:ml-6 sm:flex sm:space-x-8">
18
- <%= link_to 'Workbench', leva.workbench_index_path, class: "border-transparent #{request.path.start_with?(leva.workbench_index_path) ? 'border-indigo-500 text-white' : 'text-gray-400 hover:border-gray-700 hover:text-gray-300'} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium" %>
19
- <%= link_to 'Datasets', leva.datasets_path, class: "border-transparent #{request.path.start_with?(leva.datasets_path) ? 'border-indigo-500 text-white' : 'text-gray-400 hover:border-gray-700 hover:text-gray-300'} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium" %>
20
- <%= link_to 'Experiments', leva.experiments_path, class: "border-transparent #{request.path.start_with?(leva.experiments_path) ? 'border-indigo-500 text-white' : 'text-gray-400 hover:border-gray-700 hover:text-gray-300'} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium" %>
21
- </div>
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'}" %>
22
33
  </div>
23
34
  </div>
24
35
  </div>
25
36
  </nav>
26
- <main class="bg-gray-900">
37
+ <main class="bg-gray-950">
27
38
  <%= yield %>
28
39
  </main>
29
40
  </body>
@@ -0,0 +1,49 @@
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 %>
12
+ </div>
13
+ </div>
14
+
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>
36
+ <% 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>
45
+
46
+ <div class="mt-4 text-gray-400 text-sm">
47
+ Total records: <%= @records.count %>
48
+ </div>
49
+ </div>
@@ -0,0 +1,30 @@
1
+ <% content_for :title, "#{@dataset.name} - Record Details" %>
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 %> - Record Details</h1>
6
+ <div class="flex space-x-4">
7
+ <%= link_to dataset_dataset_records_path(@dataset), class: 'btn btn-secondary flex items-center' do %>
8
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
9
+ <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" />
10
+ </svg>
11
+ Back to Records
12
+ <% end %>
13
+ <%= link_to workbench_index_path(dataset_record_id: @record.id), class: 'btn btn-primary flex items-center' do %>
14
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
15
+ <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" />
16
+ </svg>
17
+ Use in Workbench
18
+ <% end %>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ <div class="bg-gray-800 rounded-lg shadow-lg p-6">
23
+ <% @record.show_attributes.each do |key, value| %>
24
+ <div class="mb-4">
25
+ <h3 class="text-lg font-semibold text-indigo-300 mb-2"><%= key.to_s.humanize %></h3>
26
+ <p class="text-gray-300"><%= value %></p>
27
+ </div>
28
+ <% end %>
29
+ </div>
30
+ </div>
@@ -0,0 +1,18 @@
1
+ <tr class="hover:bg-gray-700 transition-colors duration-200">
2
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">
3
+ <%= dataset.name %>
4
+ </td>
5
+ <td class="px-6 py-4 text-sm text-gray-300">
6
+ <%= truncate(dataset.description, length: 100) %>
7
+ </td>
8
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">
9
+ <%= dataset.dataset_records.count %>
10
+ </td>
11
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">
12
+ <%= dataset.experiments.count %>
13
+ </td>
14
+ <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
15
+ <%= link_to 'View', dataset_path(dataset), class: 'text-indigo-400 hover:text-indigo-300 transition-colors duration-200 mr-3' %>
16
+ <%= link_to 'Edit', edit_dataset_path(dataset), class: 'text-indigo-400 hover:text-indigo-300 transition-colors duration-200' %>
17
+ </td>
18
+ </tr>
@@ -0,0 +1,24 @@
1
+ <%= form_with(model: dataset, local: true, class: "bg-gray-800 rounded-lg shadow-lg p-6") do |form| %>
2
+ <% if dataset.errors.any? %>
3
+ <div class="bg-red-900 border border-red-700 text-red-100 px-4 py-3 rounded-lg mb-4" role="alert">
4
+ <strong class="font-bold">Error:</strong>
5
+ <ul class="list-disc list-inside">
6
+ <% dataset.errors.full_messages.each do |message| %>
7
+ <li><%= message %></li>
8
+ <% end %>
9
+ </ul>
10
+ </div>
11
+ <% end %>
12
+ <div class="mb-4">
13
+ <%= form.label :name, class: "block text-sm font-semibold mb-2 text-indigo-300" %>
14
+ <%= form.text_field :name, autofocus: true, class: "w-full bg-gray-700 text-white p-3 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none" %>
15
+ </div>
16
+ <div class="mb-6">
17
+ <%= form.label :description, class: "block text-sm font-semibold mb-2 text-indigo-300" %>
18
+ <%= form.text_area :description, rows: 4, class: "w-full bg-gray-700 text-white p-3 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none" %>
19
+ </div>
20
+ <div class="flex items-center justify-end space-x-4">
21
+ <%= link_to "Cancel", dataset.persisted? ? dataset_path(dataset) : datasets_path, class: "px-3 py-2 rounded-md text-sm font-medium text-gray-300 hover:bg-gray-800 hover:text-white transition-colors duration-150 ease-in-out" %>
22
+ <%= form.submit dataset.persisted? ? "Update Dataset" : "Create Dataset", class: "px-3 py-2 rounded-md text-sm font-medium bg-indigo-600 text-white shadow-lg hover:bg-indigo-700 transition-colors duration-150 ease-in-out" %>
23
+ </div>
24
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <% content_for :title, "Edit #{@dataset.name}" %>
2
+ <div class="container mx-auto px-4 py-8 bg-gray-950 text-white">
3
+ <h1 class="text-3xl font-bold text-indigo-400 mb-6">Edit Dataset</h1>
4
+ <%= render 'form', dataset: @dataset %>
5
+ </div>
@@ -1,43 +1,56 @@
1
1
  <% content_for :title, 'Datasets' %>
2
- <div class="px-4 sm:px-6 lg:px-8">
3
- <div class="sm:flex sm:items-center">
4
- <div class="sm:flex-auto">
5
- <h1 class="text-2xl font-semibold text-gray-900">Datasets</h1>
6
- <p class="mt-2 text-sm text-gray-700">A list of all datasets in your account.</p>
7
- </div>
8
- <div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
9
- <%= link_to 'Add Dataset', new_dataset_path, class: 'btn btn-primary' %>
10
- </div>
2
+ <div class="container mx-auto px-4 py-8 bg-gray-950 text-white">
3
+ <div class="flex justify-between items-center mb-6">
4
+ <h1 class="text-3xl font-bold text-indigo-400">Datasets</h1>
5
+ <%= link_to new_dataset_path, class: "btn btn-primary flex items-center" do %>
6
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
7
+ <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
+ </svg>
9
+ Create New Dataset
10
+ <% end %>
11
11
  </div>
12
- <div class="mt-8 flex flex-col">
13
- <div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
14
- <div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
15
- <div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
16
- <table class="min-w-full divide-y divide-gray-300">
17
- <thead class="bg-gray-50">
18
- <tr>
19
- <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">Name</th>
20
- <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Description</th>
21
- <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
22
- <span class="sr-only">Actions</span>
23
- </th>
24
- </tr>
25
- </thead>
26
- <tbody class="divide-y divide-gray-200 bg-white">
27
- <% @datasets.each do |dataset| %>
28
- <tr>
29
- <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6"><%= dataset.name %></td>
30
- <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500"><%= dataset.description %></td>
31
- <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
32
- <%= link_to 'View', dataset_path(dataset), class: 'text-indigo-600 hover:text-indigo-900' %>
33
- <%= link_to 'Edit', edit_dataset_path(dataset), class: 'ml-4 text-indigo-600 hover:text-indigo-900' %>
34
- </td>
35
- </tr>
36
- <% end %>
37
- </tbody>
38
- </table>
39
- </div>
12
+ <% if @datasets.any? %>
13
+ <div class="bg-gray-800 rounded-lg shadow-lg overflow-hidden">
14
+ <table class="min-w-full divide-y divide-gray-700">
15
+ <thead class="bg-gray-700">
16
+ <tr>
17
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">
18
+ Name
19
+ </th>
20
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">
21
+ Description
22
+ </th>
23
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">
24
+ Records
25
+ </th>
26
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">
27
+ Experiments
28
+ </th>
29
+ <th scope="col" class="relative px-6 py-3">
30
+ <span class="sr-only">Actions</span>
31
+ </th>
32
+ </tr>
33
+ </thead>
34
+ <tbody class="bg-gray-800 divide-y divide-gray-700">
35
+ <%= render @datasets %>
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ <% else %>
40
+ <div class="bg-gray-800 rounded-lg shadow-lg p-12 text-center">
41
+ <svg class="mx-auto h-12 w-12 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
42
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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" />
43
+ </svg>
44
+ <h3 class="mt-2 text-xl font-medium text-indigo-300">No datasets yet</h3>
45
+ <p class="mt-1 text-gray-400">Get started by creating a new dataset.</p>
46
+ <div class="mt-6">
47
+ <%= link_to new_dataset_path, class: "btn btn-primary inline-flex items-center" do %>
48
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
49
+ <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" />
50
+ </svg>
51
+ Create your first dataset
52
+ <% end %>
40
53
  </div>
41
54
  </div>
42
- </div>
55
+ <% end %>
43
56
  </div>
@@ -0,0 +1,5 @@
1
+ <% content_for :title, 'New Dataset' %>
2
+ <div class="container mx-auto px-4 py-8 bg-gray-950 text-white">
3
+ <h1 class="text-3xl font-bold text-indigo-400 mb-6">New Dataset</h1>
4
+ <%= render 'form', dataset: @dataset %>
5
+ </div>