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
@@ -1,5 +1,19 @@
1
- <div class="bg-gray-800 rounded-lg shadow-lg p-6">
2
- <h3 class="text-xl font-semibold mb-4 text-indigo-300">Evaluation Results</h3>
3
- <!-- Add evaluation results display here -->
4
- <p class="text-gray-400">No evaluation results available yet. Run an evaluation to see results.</p>
1
+ <div class="card">
2
+ <div class="card-header">
3
+ <div class="flex items-center gap-2">
4
+ <svg class="icon-sm text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
5
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
6
+ </svg>
7
+ <h3 class="card-title">Evaluation Results</h3>
8
+ </div>
9
+ </div>
10
+ <div class="card-body">
11
+ <div class="empty-state-inline">
12
+ <svg class="icon-lg" style="margin: 0 auto var(--space-3); color: var(--gray-500);" fill="none" viewBox="0 0 24 24" stroke="currentColor">
13
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
14
+ </svg>
15
+ <p class="text-muted text-sm">No evaluation results available yet.</p>
16
+ <p class="text-subtle text-xs mt-2">Run an evaluation to see results here.</p>
17
+ </div>
18
+ </div>
5
19
  </div>
@@ -1,87 +1,146 @@
1
1
  <% if @selected_prompt %>
2
- <div class="flex-1 overflow-y-auto p-6 space-y-6" data-controller="prompt-autosave collapsible clipboard" data-prompt-autosave-url-value="<%= workbench_path(@selected_prompt) %>">
3
- <!-- System Prompt -->
4
- <div class="bg-gray-900 p-5 rounded-lg shadow-lg">
5
- <div class="flex justify-between items-center mb-3">
6
- <h2 class="text-sm font-semibold text-indigo-400">SYSTEM PROMPT</h2>
7
- <button class="btn btn-small text-blue-400 hover:text-blue-300 flex items-center" data-action="clipboard#copy" data-clipboard-source="systemPrompt">
8
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
2
+ <div class="panel" data-controller="prompt-autosave collapsible clipboard" data-prompt-autosave-url-value="<%= workbench_path(@selected_prompt) %>">
3
+ <%# System Prompt %>
4
+ <div class="panel-section" style="padding-bottom: var(--space-4); border-bottom: 1px solid var(--gray-800);">
5
+ <div class="label-inline" style="margin-bottom: var(--space-3);">
6
+ <div class="flex items-center gap-2">
7
+ <svg class="icon-sm text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
8
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
9
+ </svg>
10
+ <span class="panel-section-title">System Prompt</span>
11
+ </div>
12
+ <button class="btn btn-ghost btn-sm" data-action="clipboard#copy" data-clipboard-source="systemPrompt" title="Copy system prompt">
13
+ <svg class="icon-sm" fill="none" viewBox="0 0 24 24" stroke="currentColor">
9
14
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
10
15
  </svg>
11
- Copy
12
16
  </button>
13
17
  </div>
14
18
  <textarea
15
- class="w-full bg-gray-800 text-white p-3 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none min-h-[100px] overflow-y-auto resize-none break-words"
19
+ class="prompt-textarea"
16
20
  name="prompt[system_prompt]"
17
21
  data-prompt-autosave-target="input"
18
22
  id="systemPrompt"
19
23
  data-action="input->prompt-autosave#debouncedSave"
24
+ placeholder="Define the AI's role and behavior..."
20
25
  ><%= @selected_prompt.system_prompt %></textarea>
21
26
  </div>
22
- <!-- User Message (Prompt Template) -->
23
- <div class="bg-gray-900 p-5 rounded-lg shadow-lg">
24
- <div class="flex justify-between items-center mb-3">
25
- <h2 class="text-sm font-semibold text-indigo-400">USER (PROMPT TEMPLATE)</h2>
26
- <button class="btn btn-small text-blue-400 hover:text-blue-300 flex items-center" data-action="clipboard#copy" data-clipboard-source="userPrompt">
27
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
27
+
28
+ <%# User Prompt Template %>
29
+ <div class="panel-section" style="padding-top: var(--space-4); padding-bottom: var(--space-4); border-bottom: 1px solid var(--gray-800);">
30
+ <div class="label-inline" style="margin-bottom: var(--space-3);">
31
+ <div class="flex items-center gap-2">
32
+ <svg class="icon-sm text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
33
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
34
+ </svg>
35
+ <span class="panel-section-title">User Prompt Template</span>
36
+ </div>
37
+ <button class="btn btn-ghost btn-sm" data-action="clipboard#copy" data-clipboard-source="userPrompt" title="Copy user prompt">
38
+ <svg class="icon-sm" fill="none" viewBox="0 0 24 24" stroke="currentColor">
28
39
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
29
40
  </svg>
30
- Copy
31
41
  </button>
32
42
  </div>
33
43
  <textarea
34
- class="w-full bg-gray-800 text-white p-3 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none min-h-[200px] overflow-y-auto resize-none break-words"
44
+ class="prompt-textarea"
35
45
  name="prompt[user_prompt]"
36
46
  data-prompt-autosave-target="input"
37
47
  id="userPrompt"
38
48
  data-action="input->prompt-autosave#debouncedSave"
49
+ placeholder="Use {{ variable }} syntax for dynamic content..."
50
+ style="min-height: 140px;"
39
51
  ><%= @selected_prompt.user_prompt %></textarea>
40
52
  </div>
41
- <!-- Available Liquid Tags -->
53
+
54
+ <%# Available Liquid Tags %>
42
55
  <% if @dataset_record && @dataset_record.recordable.respond_to?(:to_llm_context) %>
43
- <div class="bg-gray-900 p-5 rounded-lg shadow-lg" data-controller="collapsible">
44
- <h2 class="text-sm font-semibold mb-3 text-indigo-400 cursor-pointer flex items-center" data-action="click->collapsible#toggle">
45
- AVAILABLE LIQUID TAGS
46
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
47
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
48
- </svg>
49
- </h2>
50
- <div class="bg-gray-800 p-3 rounded-lg text-sm hidden" data-collapsible-target="content">
51
- <% @dataset_record.recordable.to_llm_context.each do |key, value| %>
52
- <details class="mb-2">
53
- <summary class="text-green-400 cursor-pointer flex items-center justify-between">
54
- <span>{{ <%= key %> }}</span>
55
- <button class="btn btn-small text-blue-400 hover:text-blue-300 flex items-center" data-action="clipboard#copy" data-clipboard-source="liquidTag<%= key %>">
56
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
57
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
58
- </svg>
59
- Copy
60
- </button>
61
- </summary>
62
- <pre class="text-xs text-gray-300 mt-1 whitespace-pre-wrap break-words overflow-x-auto max-w-full" id="liquidTag<%= key %>"><%= value.to_s %></pre>
63
- </details>
64
- <% end %>
65
- </div>
56
+ <div class="panel-section" style="padding-top: var(--space-4); padding-bottom: var(--space-4); border-bottom: 1px solid var(--gray-800);">
57
+ <details class="collapsible" data-controller="collapsible">
58
+ <summary class="collapsible-header">
59
+ <div class="flex items-center gap-2">
60
+ <svg class="icon-sm" fill="none" viewBox="0 0 24 24" stroke="currentColor" style="color: var(--accent-400);">
61
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
62
+ </svg>
63
+ <span class="collapsible-title">Available Variables</span>
64
+ </div>
65
+ <svg class="collapsible-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
66
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
67
+ </svg>
68
+ </summary>
69
+ <div class="collapsible-body">
70
+ <% context_to_display = @merged_context || @dataset_record.recordable.to_llm_context %>
71
+
72
+ <% if @record_context && @runner_context %>
73
+ <%# Record Context %>
74
+ <div class="mb-4">
75
+ <p class="text-xs uppercase text-muted font-semibold mb-2">From Record:</p>
76
+ <% @record_context.each do |key, value| %>
77
+ <details class="mb-2">
78
+ <summary class="flex items-center justify-between cursor-pointer">
79
+ <span class="tag tag-green">{{ <%= key %> }}</span>
80
+ <button class="btn btn-ghost btn-sm" data-action="clipboard#copy" data-clipboard-source="liquidTag<%= key %>">Copy</button>
81
+ </summary>
82
+ <pre class="mt-2" id="liquidTag<%= key %>"><code><%= value.to_s %></code></pre>
83
+ </details>
84
+ <% end %>
85
+ </div>
86
+
87
+ <%# Runner Context %>
88
+ <% if @runner_context.any? %>
89
+ <div>
90
+ <p class="text-xs uppercase text-muted font-semibold mb-2">From Runner:</p>
91
+ <% @runner_context.each do |key, value| %>
92
+ <details class="mb-2">
93
+ <summary class="flex items-center justify-between cursor-pointer">
94
+ <span class="tag tag-yellow">{{ <%= key %> }}</span>
95
+ <button class="btn btn-ghost btn-sm" data-action="clipboard#copy" data-clipboard-source="liquidTag<%= key %>">Copy</button>
96
+ </summary>
97
+ <pre class="mt-2" id="liquidTag<%= key %>"><code><%= value.to_s %></code></pre>
98
+ </details>
99
+ <% end %>
100
+ </div>
101
+ <% end %>
102
+ <% else %>
103
+ <% context_to_display.each do |key, value| %>
104
+ <details class="mb-2">
105
+ <summary class="flex items-center justify-between cursor-pointer">
106
+ <span class="tag tag-green">{{ <%= key %> }}</span>
107
+ <button class="btn btn-ghost btn-sm" data-action="clipboard#copy" data-clipboard-source="liquidTag<%= key %>">Copy</button>
108
+ </summary>
109
+ <pre class="mt-2" id="liquidTag<%= key %>"><code><%= value.to_s %></code></pre>
110
+ </details>
111
+ <% end %>
112
+ <% end %>
113
+ </div>
114
+ </details>
66
115
  </div>
67
116
  <% end %>
68
- <!-- Full Prompt Preview -->
117
+
118
+ <%# Full Prompt Preview %>
69
119
  <% if @dataset_record %>
70
- <div class="bg-gray-900 p-5 rounded-lg shadow-lg">
71
- <div class="flex justify-between items-center mb-3">
72
- <h2 class="text-sm font-semibold text-indigo-400">FULL PROMPT PREVIEW</h2>
73
- <button class="btn btn-small text-blue-400 hover:text-blue-300 flex items-center" data-action="clipboard#copy" data-clipboard-source="fullPrompt">
74
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
120
+ <div class="panel-section" style="padding-top: var(--space-4);">
121
+ <div class="label-inline" style="margin-bottom: var(--space-3);">
122
+ <div class="flex items-center gap-2">
123
+ <svg class="icon-sm text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
124
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
125
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
126
+ </svg>
127
+ <span class="panel-section-title">Rendered Preview</span>
128
+ </div>
129
+ <button class="btn btn-ghost btn-sm" data-action="clipboard#copy" data-clipboard-source="fullPrompt" title="Copy rendered prompt">
130
+ <svg class="icon-sm" fill="none" viewBox="0 0 24 24" stroke="currentColor">
75
131
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
76
132
  </svg>
77
- Copy
78
133
  </button>
79
134
  </div>
80
- <pre class="w-full bg-gray-800 text-white p-3 rounded-lg text-sm whitespace-pre-wrap overflow-x-auto break-words max-w-full" id="fullPrompt"><%= Liquid::Template.parse(@selected_prompt.user_prompt).render(@dataset_record.recordable.to_llm_context.stringify_keys) %></pre>
135
+ <div class="result-block" style="background: var(--gray-850, #222120); border: 1px solid var(--gray-700); max-height: 200px; overflow-y: auto;">
136
+ <pre id="fullPrompt" class="result-value" style="color: var(--gray-300);"><%= Liquid::Template.parse(@selected_prompt.user_prompt).render((@merged_context || @dataset_record.recordable.to_llm_context).stringify_keys) %></pre>
137
+ </div>
81
138
  </div>
82
139
  <% end %>
83
- <div class="text-sm text-center" data-prompt-autosave-target="status"></div>
140
+
141
+ <div class="autosave-status" data-prompt-autosave-target="status"></div>
84
142
  </div>
143
+
85
144
  <script>
86
145
  (() => {
87
146
  const application = Stimulus.Application.start()
@@ -103,8 +162,6 @@
103
162
  textareas.forEach(ta => {
104
163
  ta.style.height = 'auto'
105
164
  ta.style.height = (ta.scrollHeight + 5) + 'px'
106
-
107
- // Ensure horizontal text wrapping
108
165
  ta.style.wordBreak = 'break-word'
109
166
  ta.style.wordWrap = 'break-word'
110
167
  })
@@ -121,7 +178,7 @@
121
178
  })
122
179
 
123
180
  this.statusTarget.textContent = "Saving..."
124
- this.statusTarget.classList.add("text-yellow-500")
181
+ this.statusTarget.style.color = "var(--warning-400)"
125
182
 
126
183
  fetch(this.urlValue, {
127
184
  method: 'PATCH',
@@ -135,24 +192,20 @@
135
192
  .then(response => response.json())
136
193
  .then(data => {
137
194
  if (data.status === 'success') {
138
- this.statusTarget.textContent = "Changes saved successfully"
139
- this.statusTarget.classList.remove("text-yellow-500")
140
- this.statusTarget.classList.add("text-green-500")
195
+ this.statusTarget.textContent = "Changes saved"
196
+ this.statusTarget.style.color = "var(--success-400)"
141
197
  } else {
142
198
  this.statusTarget.textContent = `Error: ${data.errors.join(", ")}`
143
- this.statusTarget.classList.remove("text-yellow-500")
144
- this.statusTarget.classList.add("text-red-500")
199
+ this.statusTarget.style.color = "var(--error-400)"
145
200
  }
146
201
  setTimeout(() => {
147
202
  this.statusTarget.textContent = ""
148
- this.statusTarget.classList.remove("text-green-500", "text-red-500")
149
203
  }, 3000)
150
204
  })
151
205
  .catch(error => {
152
206
  console.error('Error:', error)
153
207
  this.statusTarget.textContent = "Error saving changes"
154
- this.statusTarget.classList.remove("text-yellow-500")
155
- this.statusTarget.classList.add("text-red-500")
208
+ this.statusTarget.style.color = "var(--error-400)"
156
209
  })
157
210
  }
158
211
 
@@ -178,8 +231,6 @@
178
231
  })
179
232
 
180
233
  application.register("clipboard", class extends Stimulus.Controller {
181
- static targets = ["content"]
182
-
183
234
  copy(event) {
184
235
  const sourceId = event.currentTarget.dataset.clipboardSource
185
236
  const sourceElement = document.getElementById(sourceId)
@@ -188,19 +239,19 @@
188
239
  this.showFeedback(event.currentTarget, "Copied!")
189
240
  }, (err) => {
190
241
  console.error('Could not copy text: ', err)
191
- this.showFeedback(event.currentTarget, "Failed to copy", true)
242
+ this.showFeedback(event.currentTarget, "Failed", true)
192
243
  })
193
244
  }
194
245
 
195
246
  showFeedback(button, message, isError = false) {
196
247
  const originalText = button.textContent
197
248
  button.textContent = message
198
- button.classList.add(isError ? "text-red-500" : "text-green-500")
249
+ button.style.color = isError ? "var(--error-400)" : "var(--success-400)"
199
250
  button.disabled = true
200
251
 
201
252
  setTimeout(() => {
202
253
  button.textContent = originalText
203
- button.classList.remove("text-green-500", "text-red-500")
254
+ button.style.color = ""
204
255
  button.disabled = false
205
256
  }, 2000)
206
257
  }
@@ -208,13 +259,13 @@
208
259
  })()
209
260
  </script>
210
261
  <% else %>
211
- <div class="flex-1 flex items-center justify-center">
212
- <div class="text-center text-gray-500">
213
- <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
214
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
262
+ <div class="panel flex items-center justify-center">
263
+ <div class="empty-state">
264
+ <svg class="empty-state-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
265
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
215
266
  </svg>
216
- <h3 class="text-xl font-medium mb-2">No Prompt Selected</h3>
217
- <p class="text-sm">Please select a prompt from the sidebar to begin editing.</p>
267
+ <h3 class="empty-state-title">No Prompt Selected</h3>
268
+ <p class="empty-state-description">Select a prompt from the sidebar to begin editing.</p>
218
269
  </div>
219
270
  </div>
220
- <% end %>
271
+ <% end %>
@@ -1,22 +1,23 @@
1
- <%= form_with(model: prompt, url: workbench_path(prompt), method: :patch, local: false, class: "bg-gray-800 rounded-lg shadow-lg p-6", data: { controller: "prompt-form" }) do |form| %>
2
- <div class="mb-4">
3
- <%= form.label :name, class: "block text-sm font-semibold mb-2 text-indigo-300" %>
4
- <%= form.text_field :name, class: "w-full bg-gray-700 text-white p-3 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none", data: { action: "input->prompt-form#autoSave" } %>
1
+ <%= form_with(model: prompt, url: workbench_path(prompt), method: :patch, local: false, class: "card", data: { controller: "prompt-form" }) do |form| %>
2
+ <div class="form-group">
3
+ <%= form.label :name, class: "form-label" %>
4
+ <%= form.text_field :name, class: "form-input", data: { action: "input->prompt-form#autoSave" } %>
5
5
  </div>
6
- <div class="mb-4">
7
- <%= form.label :version, class: "block text-sm font-semibold mb-2 text-indigo-300" %>
8
- <%= form.number_field :version, class: "w-full bg-gray-700 text-white p-3 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none", data: { action: "input->prompt-form#autoSave" } %>
6
+ <div class="form-group">
7
+ <%= form.label :version, class: "form-label" %>
8
+ <%= form.number_field :version, class: "form-input", data: { action: "input->prompt-form#autoSave" } %>
9
9
  </div>
10
- <div class="mb-4">
11
- <%= form.label :system_prompt, class: "block text-sm font-semibold mb-2 text-indigo-300" %>
12
- <%= form.text_area :system_prompt, rows: 5, class: "w-full bg-gray-700 text-white p-3 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none", data: { action: "input->prompt-form#autoSave" } %>
10
+ <div class="form-group">
11
+ <%= form.label :system_prompt, class: "form-label" %>
12
+ <%= form.text_area :system_prompt, rows: 5, class: "form-textarea", data: { action: "input->prompt-form#autoSave" } %>
13
13
  </div>
14
- <div class="mb-4">
15
- <%= form.label :user_prompt, class: "block text-sm font-semibold mb-2 text-indigo-300" %>
16
- <%= form.text_area :user_prompt, rows: 5, class: "w-full bg-gray-700 text-white p-3 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none", data: { action: "input->prompt-form#autoSave" } %>
14
+ <div class="form-group">
15
+ <%= form.label :user_prompt, class: "form-label" %>
16
+ <%= form.text_area :user_prompt, rows: 5, class: "form-textarea", data: { action: "input->prompt-form#autoSave" } %>
17
17
  </div>
18
- <div id="form-status" class="mb-4 text-center" data-prompt-form-target="status"></div>
18
+ <div id="form-status" class="text-sm text-center" data-prompt-form-target="status"></div>
19
19
  <% end %>
20
+
20
21
  <script>
21
22
  (() => {
22
23
  const application = Stimulus.Application.start()
@@ -26,7 +27,7 @@
26
27
 
27
28
  connect() {
28
29
  this.timeout = null
29
- this.debounceTime = 1000 // 1 second debounce
30
+ this.debounceTime = 1000
30
31
  this.lastSavedContent = this.formContent()
31
32
  }
32
33
 
@@ -37,7 +38,7 @@
37
38
  if (currentContent !== this.lastSavedContent) {
38
39
  this.submitForm()
39
40
  } else {
40
- this.showStatus("No changes to save", "text-gray-500")
41
+ this.showStatus("No changes to save", "text-muted")
41
42
  }
42
43
  }, this.debounceTime)
43
44
  }
@@ -46,7 +47,7 @@
46
47
  const form = this.element
47
48
  const formData = new FormData(form)
48
49
 
49
- this.showStatus("Saving...", "text-yellow-500")
50
+ this.showStatus("Saving...", "text-warning")
50
51
 
51
52
  fetch(form.action, {
52
53
  method: form.method,
@@ -60,24 +61,24 @@
60
61
  .then(response => response.json())
61
62
  .then(data => {
62
63
  if (data.status === "success") {
63
- this.showStatus("Changes saved successfully", "text-green-500")
64
+ this.showStatus("Changes saved", "text-success")
64
65
  this.lastSavedContent = this.formContent()
65
66
  } else {
66
- this.showStatus(`Error: ${data.errors.join(", ")}`, "text-red-500")
67
+ this.showStatus(`Error: ${data.errors.join(", ")}`, "text-error")
67
68
  }
68
69
  })
69
70
  .catch(error => {
70
71
  console.error("Error:", error)
71
- this.showStatus("Error saving changes", "text-red-500")
72
+ this.showStatus("Error saving changes", "text-error")
72
73
  })
73
74
  }
74
75
 
75
76
  showStatus(message, className) {
76
77
  this.statusTarget.textContent = message
77
- this.statusTarget.className = `mb-4 text-center ${className}`
78
+ this.statusTarget.className = `text-sm text-center ${className}`
78
79
  setTimeout(() => {
79
80
  this.statusTarget.textContent = ""
80
- this.statusTarget.className = "mb-4 text-center"
81
+ this.statusTarget.className = "text-sm text-center"
81
82
  }, 3000)
82
83
  }
83
84
 
@@ -86,4 +87,4 @@
86
87
  }
87
88
  })
88
89
  })()
89
- </script>
90
+ </script>
@@ -1,21 +1,66 @@
1
- <div class="w-64 h-full bg-gray-900 border-r border-gray-800 flex flex-col">
2
- <div class="p-4">
3
- <h2 class="text-xl font-bold mb-4 text-indigo-400">Prompts</h2>
4
- <div class="space-y-2">
1
+ <div class="sidebar" data-controller="collapsible-sidebar" data-collapsible-sidebar-key-value="leva-sidebar-collapsed">
2
+ <div class="sidebar-header">
3
+ <div class="flex items-center justify-between">
4
+ <h2 class="sidebar-title">Prompts</h2>
5
+ <div class="flex items-center gap-1">
6
+ <span class="text-xs text-muted font-mono" data-collapsible-sidebar-target="hideable"><%= prompts.count %></span>
7
+ <button type="button" class="sidebar-collapse-btn" data-action="click->collapsible-sidebar#toggle" title="Toggle sidebar">
8
+ <svg viewBox="0 0 20 20" fill="currentColor">
9
+ <path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
10
+ </svg>
11
+ </button>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ <div class="sidebar-content">
16
+ <% if prompts.any? %>
5
17
  <% prompts.each do |prompt| %>
6
- <%= link_to workbench_index_path(prompt_id: prompt.id), class: "block bg-gray-800 p-3 rounded-lg hover:bg-gray-700 transition duration-150 ease-in-out #{'bg-indigo-600' if prompt == selected_prompt}" do %>
7
- <span class="text-sm font-medium"><%= prompt.name %></span>
8
- <span class="text-xs text-indigo-300 ml-2">v<%= prompt.version %></span>
18
+ <%= link_to workbench_index_path(prompt_id: prompt.id), class: "sidebar-item #{prompt == selected_prompt ? 'active' : ''}" do %>
19
+ <div class="flex items-center gap-2 min-w-0">
20
+ <% if prompt == selected_prompt %>
21
+ <span class="status-dot" style="background: var(--accent-500); margin-right: 0;"></span>
22
+ <% end %>
23
+ <span class="truncate"><%= prompt.name %></span>
24
+ </div>
25
+ <span class="sidebar-item-badge">v<%= prompt.version %></span>
9
26
  <% end %>
10
27
  <% end %>
11
- </div>
28
+ <% else %>
29
+ <div class="p-4 text-center">
30
+ <p class="text-sm text-muted">No prompts yet</p>
31
+ </div>
32
+ <% end %>
12
33
  </div>
13
- <div class="mt-auto p-4">
14
- <%= link_to new_workbench_path, class: "w-full flex items-center justify-center space-x-2 bg-indigo-600 hover:bg-indigo-700 p-3 rounded-lg transition duration-150 ease-in-out" do %>
15
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
34
+ <div class="sidebar-footer">
35
+ <%= link_to new_workbench_path, class: "btn btn-secondary btn-sm", style: "width: 100%;" do %>
36
+ <svg class="icon-sm" viewBox="0 0 20 20" fill="currentColor">
16
37
  <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" />
17
38
  </svg>
18
39
  <span>New Prompt</span>
19
40
  <% end %>
20
41
  </div>
21
- </div>
42
+ </div>
43
+
44
+ <script>
45
+ (() => {
46
+ const application = Stimulus.Application.start()
47
+
48
+ application.register("collapsible-sidebar", class extends Stimulus.Controller {
49
+ static targets = ["hideable"]
50
+ static values = { key: String }
51
+
52
+ connect() {
53
+ const isCollapsed = localStorage.getItem(this.keyValue) === 'true'
54
+ if (isCollapsed) {
55
+ this.element.classList.add('collapsed')
56
+ }
57
+ }
58
+
59
+ toggle() {
60
+ this.element.classList.toggle('collapsed')
61
+ const isCollapsed = this.element.classList.contains('collapsed')
62
+ localStorage.setItem(this.keyValue, isCollapsed)
63
+ }
64
+ })
65
+ })()
66
+ </script>