leva 0.1.5 → 0.1.6

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +52 -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 -2
  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 +54 -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/lib/generators/leva/templates/eval.rb.erb +6 -7
  56. data/lib/leva/version.rb +1 -1
  57. data/lib/leva.rb +62 -45
  58. metadata +48 -5
  59. data/app/evals/test_sentiment_accuracy_eval.rb +0 -6
  60. data/app/runners/test_sentiment_run.rb +0 -13
  61. data/lib/leva/base_eval.rb +0 -75
@@ -16,11 +16,49 @@
16
16
  #
17
17
  # Foreign Keys
18
18
  #
19
- # dataset_id (dataset_id => datasets.id)
19
+ # dataset_id (dataset_id => leva_datasets.id)
20
20
  #
21
21
  module Leva
22
22
  class DatasetRecord < ApplicationRecord
23
23
  belongs_to :dataset
24
24
  belongs_to :recordable, polymorphic: true
25
+
26
+ has_many :runner_results, dependent: :destroy
27
+ has_many :evaluation_results, dependent: :destroy, through: :runner_results
28
+
29
+ delegate :ground_truth, to: :recordable
30
+
31
+ # @return [Hash] A hash of attributes to be displayed in the dataset records index
32
+ def index_attributes
33
+ if recordable.respond_to?(:index_attributes)
34
+ recordable.index_attributes
35
+ elsif recordable.respond_to?(:name)
36
+ { name: recordable.name }
37
+ else
38
+ { to_s: recordable.to_s }
39
+ end
40
+ end
41
+
42
+ # @return [Hash] A hash of attributes to be displayed in the dataset record show view
43
+ def show_attributes
44
+ if recordable.respond_to?(:show_attributes)
45
+ recordable.show_attributes
46
+ elsif recordable.respond_to?(:dataset_attributes)
47
+ recordable.dataset_attributes
48
+ else
49
+ { to_s: recordable.to_s }
50
+ end
51
+ end
52
+
53
+ # @return [String] A string representation of the record for display purposes
54
+ def display_name
55
+ if recordable.respond_to?(:name)
56
+ recordable.name
57
+ elsif recordable.respond_to?(:title)
58
+ recordable.title
59
+ else
60
+ "#{recordable_type} ##{recordable_id}"
61
+ end
62
+ end
25
63
  end
26
- end
64
+ 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,54 @@
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
+ # created_at :datetime not null
9
+ # updated_at :datetime not null
10
+ # dataset_record_id :integer not null
11
+ # experiment_id :integer
12
+ # prompt_id :integer not null
13
+ #
14
+ # Indexes
15
+ #
16
+ # index_leva_runner_results_on_dataset_record_id (dataset_record_id)
17
+ # index_leva_runner_results_on_experiment_id (experiment_id)
18
+ # index_leva_runner_results_on_prompt_id (prompt_id)
19
+ #
20
+ # Foreign Keys
21
+ #
22
+ # dataset_record_id (dataset_record_id => leva_dataset_records.id)
23
+ # experiment_id (experiment_id => leva_experiments.id)
24
+ # prompt_id (prompt_id => leva_prompts.id)
25
+ #
26
+ module Leva
27
+ class RunnerResult < ApplicationRecord
28
+ belongs_to :experiment, optional: true
29
+ belongs_to :dataset_record
30
+ belongs_to :prompt
31
+ has_many :evaluation_results, dependent: :destroy
32
+
33
+ validates :prediction, presence: true
34
+ validates :prompt, presence: true
35
+
36
+ delegate :ground_truth, to: :dataset_record
37
+
38
+ # @return [Array<String>] The parsed draft responses
39
+ def parsed_predictions
40
+ @parsed_predictions ||=
41
+ if extract_regex_pattern
42
+ prediction.scan(extract_regex_pattern).map { |match| match.first&.strip }.compact
43
+ else
44
+ [prediction]
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def extract_regex_pattern
51
+ dataset_record.recordable.extract_regex_pattern
52
+ end
53
+ end
54
+ 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>