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.
- checksums.yaml +4 -4
- data/README.md +21 -5
- data/app/assets/stylesheets/leva/application.css +3083 -15
- data/app/controllers/leva/application_controller.rb +1 -1
- data/app/controllers/leva/dataset_records_controller.rb +1 -1
- data/app/controllers/leva/datasets_controller.rb +6 -6
- data/app/controllers/leva/design_system_controller.rb +9 -0
- data/app/controllers/leva/experiments_controller.rb +8 -8
- data/app/controllers/leva/runner_results_controller.rb +1 -1
- data/app/controllers/leva/workbench_controller.rb +26 -15
- data/app/helpers/leva/application_helper.rb +7 -7
- data/app/jobs/leva/experiment_job.rb +1 -1
- data/app/jobs/leva/run_eval_job.rb +1 -1
- data/app/models/concerns/leva/recordable.rb +5 -5
- data/app/models/leva/dataset.rb +1 -1
- data/app/models/leva/evaluation_result.rb +1 -1
- data/app/models/leva/experiment.rb +1 -1
- data/app/models/leva/prompt.rb +1 -1
- data/app/views/layouts/leva/application.html.erb +23 -24
- data/app/views/leva/dataset_records/index.html.erb +70 -43
- data/app/views/leva/dataset_records/show.html.erb +115 -25
- data/app/views/leva/datasets/_dataset.html.erb +11 -18
- data/app/views/leva/datasets/_form.html.erb +18 -14
- data/app/views/leva/datasets/edit.html.erb +16 -4
- data/app/views/leva/datasets/index.html.erb +33 -41
- data/app/views/leva/datasets/new.html.erb +15 -4
- data/app/views/leva/datasets/show.html.erb +120 -139
- data/app/views/leva/design_system/index.html.erb +1731 -0
- data/app/views/leva/experiments/_experiment.html.erb +46 -31
- data/app/views/leva/experiments/_form.html.erb +62 -35
- data/app/views/leva/experiments/edit.html.erb +17 -3
- data/app/views/leva/experiments/index.html.erb +41 -36
- data/app/views/leva/experiments/new.html.erb +52 -4
- data/app/views/leva/experiments/show.html.erb +155 -98
- data/app/views/leva/runner_results/show.html.erb +271 -54
- data/app/views/leva/workbench/_evaluation_area.html.erb +18 -4
- data/app/views/leva/workbench/_prompt_content.html.erb +124 -73
- data/app/views/leva/workbench/_prompt_form.html.erb +24 -23
- data/app/views/leva/workbench/_prompt_sidebar.html.erb +57 -12
- data/app/views/leva/workbench/_results_section.html.erb +274 -112
- data/app/views/leva/workbench/_top_bar.html.erb +16 -6
- data/app/views/leva/workbench/edit.html.erb +46 -15
- data/app/views/leva/workbench/index.html.erb +5 -8
- data/app/views/leva/workbench/new.html.erb +74 -42
- data/config/routes.rb +11 -9
- data/db/migrate/20240813173033_create_leva_dataset_records.rb +1 -0
- data/db/migrate/20240813173035_create_leva_experiments.rb +2 -0
- data/db/migrate/{20240816201419_create_leva_runner_results.rb → 20240813173040_create_leva_runner_results.rb} +4 -1
- data/db/migrate/20240813173050_create_leva_evaluation_results.rb +3 -3
- data/lib/generators/leva/eval_generator.rb +4 -4
- data/lib/generators/leva/runner_generator.rb +4 -4
- data/lib/generators/leva/templates/runner.rb.erb +20 -0
- data/lib/leva/version.rb +1 -1
- data/lib/leva.rb +24 -2
- metadata +5 -11
- data/db/migrate/20240816201433_update_leva_evaluation_results.rb +0 -8
- data/db/migrate/20240821163608_make_experiment_optional_for_runner_results.rb +0 -6
- data/db/migrate/20240821181934_add_prompt_to_leva_runner_results.rb +0 -5
- data/db/migrate/20240821183153_add_runner_and_evaluator_to_leva_experiments.rb +0 -6
- data/db/migrate/20240821191713_add_actual_result_to_leva_dataset_records.rb +0 -5
- data/db/migrate/20240822143201_remove_actual_result_from_leva_runner_results.rb +0 -5
- data/db/migrate/20240912183556_add_runner_class_to_leva_runner_results.rb +0 -5
- data/lib/tasks/auto_annotate_models.rake +0 -59
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
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="
|
|
3
|
-
|
|
4
|
-
<div class="
|
|
5
|
-
<div class="
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
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="
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
<svg
|
|
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="
|
|
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
|
-
|
|
53
|
+
|
|
54
|
+
<%# Available Liquid Tags %>
|
|
42
55
|
<% if @dataset_record && @dataset_record.recordable.respond_to?(:to_llm_context) %>
|
|
43
|
-
<div class="
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
117
|
+
|
|
118
|
+
<%# Full Prompt Preview %>
|
|
69
119
|
<% if @dataset_record %>
|
|
70
|
-
<div class="
|
|
71
|
-
<div class="
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
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.
|
|
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
|
|
139
|
-
this.statusTarget.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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="
|
|
212
|
-
<div class="
|
|
213
|
-
<svg
|
|
214
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="
|
|
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="
|
|
217
|
-
<p class="
|
|
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: "
|
|
2
|
-
<div class="
|
|
3
|
-
<%= form.label :name, class: "
|
|
4
|
-
<%= form.text_field :name, class: "
|
|
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="
|
|
7
|
-
<%= form.label :version, class: "
|
|
8
|
-
<%= form.number_field :version, class: "
|
|
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="
|
|
11
|
-
<%= form.label :system_prompt, class: "
|
|
12
|
-
<%= form.text_area :system_prompt, rows: 5, class: "
|
|
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="
|
|
15
|
-
<%= form.label :user_prompt, class: "
|
|
16
|
-
<%= form.text_area :user_prompt, rows: 5, class: "
|
|
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="
|
|
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
|
|
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-
|
|
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-
|
|
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
|
|
64
|
+
this.showStatus("Changes saved", "text-success")
|
|
64
65
|
this.lastSavedContent = this.formContent()
|
|
65
66
|
} else {
|
|
66
|
-
this.showStatus(`Error: ${data.errors.join(", ")}`, "text-
|
|
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-
|
|
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 = `
|
|
78
|
+
this.statusTarget.className = `text-sm text-center ${className}`
|
|
78
79
|
setTimeout(() => {
|
|
79
80
|
this.statusTarget.textContent = ""
|
|
80
|
-
this.statusTarget.className = "
|
|
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="
|
|
2
|
-
<div class="
|
|
3
|
-
<
|
|
4
|
-
|
|
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: "
|
|
7
|
-
<
|
|
8
|
-
|
|
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
|
-
|
|
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="
|
|
14
|
-
<%= link_to new_workbench_path, class: "
|
|
15
|
-
<svg
|
|
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>
|