leva 0.1.10 → 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 +1 -2
- data/app/assets/stylesheets/leva/application.css +3083 -15
- data/app/controllers/leva/design_system_controller.rb +9 -0
- data/app/views/layouts/leva/application.html.erb +23 -24
- data/app/views/leva/dataset_records/index.html.erb +63 -61
- 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 +40 -19
- 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 +116 -111
- 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 +2 -0
- data/lib/leva/version.rb +1 -1
- metadata +4 -2
|
@@ -1,126 +1,182 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
<div class="panel-right" data-controller="button-loader collapsible-panel" data-resizable-target="panel" data-collapsible-panel-key-value="leva-panel-right-collapsed" style="position: relative;">
|
|
2
|
+
<button type="button" class="panel-right-collapse-btn" data-action="click->collapsible-panel#toggle" title="Toggle panel">
|
|
3
|
+
<svg viewBox="0 0 20 20" fill="currentColor">
|
|
4
|
+
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
|
|
5
|
+
</svg>
|
|
6
|
+
</button>
|
|
7
|
+
<%# Run Controls %>
|
|
8
|
+
<div class="run-controls">
|
|
9
|
+
<div class="flex items-center gap-2 mb-3">
|
|
10
|
+
<svg class="icon-sm text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
11
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
12
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
13
|
+
</svg>
|
|
14
|
+
<span class="text-xs font-semibold uppercase text-muted" style="letter-spacing: 0.05em;">Run Configuration</span>
|
|
15
|
+
</div>
|
|
16
|
+
<%= form_tag workbench_index_path, method: :get, class: "run-form" do %>
|
|
17
|
+
<div class="run-selects">
|
|
18
|
+
<div class="run-select-group">
|
|
19
|
+
<label class="run-label">Runner</label>
|
|
20
|
+
<%= select_tag :runner,
|
|
21
|
+
options_from_collection_for_select(@runners, :name, :name, @selected_runner),
|
|
22
|
+
include_blank: "Select runner...",
|
|
23
|
+
onchange: "this.form.submit()",
|
|
24
|
+
class: "form-select form-select-sm" %>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="run-select-group">
|
|
27
|
+
<label class="run-label">Record</label>
|
|
28
|
+
<%= select_tag :dataset_record_id,
|
|
29
|
+
options_from_collection_for_select(Leva::DatasetRecord.all, :id, :display_name, @selected_dataset_record),
|
|
30
|
+
include_blank: "Select record...",
|
|
31
|
+
onchange: "this.form.submit()",
|
|
32
|
+
class: "form-select form-select-sm" %>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
24
35
|
<%= hidden_field_tag :prompt_id, @prompt&.id %>
|
|
25
|
-
<%= hidden_field_tag :runner, @selected_runner %>
|
|
26
36
|
<% end %>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<span data-button-loader-target="buttonText" class="flex items-center">
|
|
32
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
|
37
|
+
|
|
38
|
+
<%= button_to run_workbench_index_path, method: :post, params: { runner: @selected_runner, prompt_id: @prompt&.id, dataset_record_id: @selected_dataset_record }, class: "btn btn-primary btn-run", disabled: @selected_runner.blank? || @selected_dataset_record.blank?, data: { action: "click->button-loader#handleClick", "button-loader-target": "button" } do %>
|
|
39
|
+
<span data-button-loader-target="buttonText" class="flex items-center justify-center">
|
|
40
|
+
<svg class="icon-sm" style="margin-right: 6px;" viewBox="0 0 20 20" fill="currentColor">
|
|
33
41
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd" />
|
|
34
42
|
</svg>
|
|
35
|
-
Run
|
|
43
|
+
Run Prompt
|
|
36
44
|
</span>
|
|
37
45
|
<% end %>
|
|
38
46
|
</div>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<div class="
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<h4 class="text-xs font-semibold text-indigo-200 mb-1">Raw Prediction:</h4>
|
|
49
|
-
<pre class="text-sm text-gray-300 whitespace-pre-wrap break-words overflow-x-auto max-w-full bg-gray-700 p-2 rounded"><%= runner_result.prediction %></pre>
|
|
47
|
+
|
|
48
|
+
<%# Output Section %>
|
|
49
|
+
<div class="output-section">
|
|
50
|
+
<div class="output-header">
|
|
51
|
+
<div class="flex items-center gap-2">
|
|
52
|
+
<svg class="icon-sm text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
53
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
54
|
+
</svg>
|
|
55
|
+
<span class="output-title">Output</span>
|
|
50
56
|
</div>
|
|
51
|
-
<% if runner_result
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<% end %>
|
|
57
|
-
</div>
|
|
57
|
+
<% if @dataset_record && (runner_result = @dataset_record.runner_results.last) %>
|
|
58
|
+
<span class="output-meta">
|
|
59
|
+
<span class="badge badge-default" style="font-size: 10px; padding: 2px 6px;">v<%= runner_result.prompt.version %></span>
|
|
60
|
+
<span style="margin-left: 4px;"><%= time_ago_in_words(runner_result.created_at) %> ago</span>
|
|
61
|
+
</span>
|
|
58
62
|
<% end %>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<% if @dataset_record && (runner_result = @dataset_record.runner_results.last) %>
|
|
66
|
+
<div class="output-grid">
|
|
67
|
+
<div class="output-block output-block--expected">
|
|
68
|
+
<span class="output-label">
|
|
69
|
+
<svg class="icon-sm" style="display: inline; width: 12px; height: 12px; margin-right: 4px; vertical-align: -2px; color: var(--success-500);" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
70
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
71
|
+
</svg>
|
|
72
|
+
Expected
|
|
73
|
+
</span>
|
|
74
|
+
<span class="output-value"><%= runner_result.ground_truth %></span>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="output-block output-block--got">
|
|
77
|
+
<span class="output-label">
|
|
78
|
+
<svg class="icon-sm" style="display: inline; width: 12px; height: 12px; margin-right: 4px; vertical-align: -2px; color: var(--gray-500);" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
79
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
|
80
|
+
</svg>
|
|
81
|
+
Result
|
|
82
|
+
</span>
|
|
83
|
+
<span class="output-value"><%= runner_result.prediction %></span>
|
|
84
|
+
</div>
|
|
85
|
+
<% if runner_result.dataset_record.recordable.extract_regex_pattern && runner_result.parsed_predictions.any? %>
|
|
86
|
+
<div class="output-block" style="background: rgba(106, 159, 196, 0.08); border-left: 2px solid var(--info-500);">
|
|
87
|
+
<span class="output-label">
|
|
88
|
+
<svg class="icon-sm" style="display: inline; width: 12px; height: 12px; margin-right: 4px; vertical-align: -2px; color: var(--info-500);" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
89
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
|
|
90
|
+
</svg>
|
|
91
|
+
Parsed
|
|
92
|
+
</span>
|
|
93
|
+
<span class="output-value"><%= runner_result.parsed_predictions.join(", ") %></span>
|
|
94
|
+
</div>
|
|
95
|
+
<% end %>
|
|
62
96
|
</div>
|
|
63
97
|
<% else %>
|
|
64
|
-
<div class="
|
|
65
|
-
<svg
|
|
66
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="
|
|
98
|
+
<div class="output-empty">
|
|
99
|
+
<svg class="icon-lg" style="margin: 0 auto var(--space-2); color: var(--gray-600);" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
100
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
|
101
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
67
102
|
</svg>
|
|
68
|
-
<p class="text-sm text-
|
|
103
|
+
<p class="text-sm text-muted">Click Run to see output</p>
|
|
69
104
|
</div>
|
|
70
105
|
<% end %>
|
|
71
106
|
</div>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
107
|
+
|
|
108
|
+
<%# Evaluators Section %>
|
|
109
|
+
<div class="eval-section">
|
|
110
|
+
<div class="eval-header">
|
|
111
|
+
<div class="flex items-center gap-2">
|
|
112
|
+
<svg class="icon-sm text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
113
|
+
<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" />
|
|
114
|
+
</svg>
|
|
115
|
+
<span class="eval-title">Evaluations</span>
|
|
116
|
+
</div>
|
|
117
|
+
<% if @evaluators.any? %>
|
|
118
|
+
<%= button_to run_all_evals_workbench_index_path, method: :post, params: { runner: @selected_runner, prompt_id: @prompt&.id, dataset_record_id: @selected_dataset_record }, class: "btn btn-ghost btn-sm", disabled: @selected_dataset_record.blank?, style: "font-size: 11px;", data: { action: "click->button-loader#handleClick", "button-loader-target": "button" } do %>
|
|
119
|
+
<span data-button-loader-target="buttonText" class="flex items-center gap-1">
|
|
120
|
+
<svg class="icon-sm" style="width: 12px; height: 12px;" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
121
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
122
|
+
</svg>
|
|
123
|
+
Run All
|
|
124
|
+
</span>
|
|
125
|
+
<% end %>
|
|
83
126
|
<% end %>
|
|
84
127
|
</div>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
<
|
|
111
|
-
|
|
128
|
+
<% if @evaluators.any? %>
|
|
129
|
+
<div class="eval-grid">
|
|
130
|
+
<% @evaluators.each do |evaluator_class| %>
|
|
131
|
+
<% evaluation_result = @dataset_record&.evaluation_results&.for_evaluator(evaluator_class)&.last %>
|
|
132
|
+
<% score = evaluation_result&.score %>
|
|
133
|
+
<%
|
|
134
|
+
score_class = case score
|
|
135
|
+
when 0...0.2 then 'score-bad'
|
|
136
|
+
when 0.2...0.4 then 'score-poor'
|
|
137
|
+
when 0.4...0.6 then 'score-fair'
|
|
138
|
+
when 0.6...0.8 then 'score-good'
|
|
139
|
+
when 0.8..1.0 then 'score-excellent'
|
|
140
|
+
else ''
|
|
141
|
+
end
|
|
142
|
+
bg_style = case score
|
|
143
|
+
when 0...0.2 then 'background: rgba(207, 111, 98, 0.08);'
|
|
144
|
+
when 0.2...0.4 then 'background: rgba(232, 161, 88, 0.08);'
|
|
145
|
+
when 0.4...0.6 then 'background: rgba(212, 168, 74, 0.08);'
|
|
146
|
+
when 0.6...0.8 then 'background: rgba(159, 204, 111, 0.08);'
|
|
147
|
+
when 0.8..1.0 then 'background: rgba(125, 179, 103, 0.08);'
|
|
148
|
+
else ''
|
|
149
|
+
end
|
|
150
|
+
%>
|
|
151
|
+
<%= button_to run_evaluator_workbench_index_path, method: :post, params: { evaluator: evaluator_class.name, runner: @selected_runner, prompt_id: @prompt&.id, dataset_record_id: @selected_dataset_record }, class: "eval-card #{score ? 'eval-card--has-score' : ''}", style: bg_style, disabled: @selected_dataset_record.blank?, data: { action: "click->button-loader#handleClick", "button-loader-target": "button" } do %>
|
|
152
|
+
<span data-button-loader-target="buttonText" class="eval-card-inner">
|
|
153
|
+
<span class="eval-name"><%= evaluator_class.name.demodulize.gsub(/Evaluator$/, '').gsub(/([a-z])([A-Z])/, '\1 \2') %></span>
|
|
154
|
+
<div class="flex items-center gap-2">
|
|
155
|
+
<% if score %>
|
|
156
|
+
<span class="eval-score <%= score_class %>"><%= sprintf('%.0f', score * 100) %></span>
|
|
157
|
+
<span class="text-xs text-muted" style="font-size: 10px;">%</span>
|
|
158
|
+
<% else %>
|
|
159
|
+
<span class="eval-score eval-score--empty">
|
|
160
|
+
<svg class="icon-sm" style="width: 14px; height: 14px; color: var(--gray-600);" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
161
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
|
162
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
163
|
+
</svg>
|
|
164
|
+
</span>
|
|
165
|
+
<% end %>
|
|
112
166
|
</div>
|
|
113
|
-
|
|
114
|
-
<div class="text-sm text-gray-500">
|
|
115
|
-
No evaluation result yet.
|
|
116
|
-
</div>
|
|
117
|
-
<% end %>
|
|
167
|
+
</span>
|
|
118
168
|
<% end %>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
169
|
+
<% end %>
|
|
170
|
+
</div>
|
|
171
|
+
<% else %>
|
|
172
|
+
<div class="text-center p-4" style="background: var(--gray-850, #222120); border-radius: var(--radius-md);">
|
|
173
|
+
<p class="text-sm text-muted">No evaluators configured</p>
|
|
174
|
+
<p class="text-xs text-subtle mt-1">Add evaluators in app/evals</p>
|
|
175
|
+
</div>
|
|
176
|
+
<% end %>
|
|
122
177
|
</div>
|
|
123
178
|
</div>
|
|
179
|
+
|
|
124
180
|
<script>
|
|
125
181
|
(() => {
|
|
126
182
|
const application = Stimulus.Application.start()
|
|
@@ -135,25 +191,131 @@
|
|
|
135
191
|
|
|
136
192
|
this.disableButton(button)
|
|
137
193
|
this.showSpinner(button)
|
|
138
|
-
|
|
139
|
-
// Submit the form
|
|
140
194
|
form.submit()
|
|
141
195
|
}
|
|
142
196
|
|
|
143
197
|
disableButton(button) {
|
|
144
198
|
button.disabled = true
|
|
145
|
-
button.
|
|
199
|
+
button.style.opacity = '0.5'
|
|
200
|
+
button.style.cursor = 'not-allowed'
|
|
146
201
|
}
|
|
147
202
|
|
|
148
203
|
showSpinner(button) {
|
|
149
204
|
const buttonText = button.querySelector('[data-button-loader-target="buttonText"]')
|
|
150
|
-
buttonText.innerHTML =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
205
|
+
buttonText.innerHTML = '<span class="spinner"></span>'
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
application.register("resizable", class extends Stimulus.Controller {
|
|
210
|
+
static targets = ["handle", "panel"]
|
|
211
|
+
|
|
212
|
+
connect() {
|
|
213
|
+
this.isResizing = false
|
|
214
|
+
this.startX = 0
|
|
215
|
+
this.startWidth = 0
|
|
216
|
+
this.minWidth = 200
|
|
217
|
+
this.maxWidth = window.innerWidth * 0.5
|
|
218
|
+
|
|
219
|
+
// Restore saved width from localStorage
|
|
220
|
+
const savedWidth = localStorage.getItem('leva-panel-right-width')
|
|
221
|
+
if (savedWidth && this.hasPanelTarget) {
|
|
222
|
+
this.panelTarget.style.width = savedWidth + 'px'
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Bind methods for event listeners
|
|
226
|
+
this.boundOnMouseMove = this.onMouseMove.bind(this)
|
|
227
|
+
this.boundOnMouseUp = this.onMouseUp.bind(this)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
startResize(event) {
|
|
231
|
+
event.preventDefault()
|
|
232
|
+
this.isResizing = true
|
|
233
|
+
this.startX = event.clientX
|
|
234
|
+
this.startWidth = this.panelTarget.offsetWidth
|
|
235
|
+
|
|
236
|
+
this.handleTarget.classList.add('dragging')
|
|
237
|
+
this.panelTarget.classList.add('resizing')
|
|
238
|
+
document.body.style.cursor = 'col-resize'
|
|
239
|
+
document.body.style.userSelect = 'none'
|
|
240
|
+
|
|
241
|
+
document.addEventListener('mousemove', this.boundOnMouseMove)
|
|
242
|
+
document.addEventListener('mouseup', this.boundOnMouseUp)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
onMouseMove(event) {
|
|
246
|
+
if (!this.isResizing) return
|
|
247
|
+
|
|
248
|
+
const deltaX = this.startX - event.clientX
|
|
249
|
+
let newWidth = this.startWidth + deltaX
|
|
250
|
+
|
|
251
|
+
// Clamp width between min and max
|
|
252
|
+
newWidth = Math.max(this.minWidth, Math.min(this.maxWidth, newWidth))
|
|
253
|
+
|
|
254
|
+
this.panelTarget.style.width = newWidth + 'px'
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
onMouseUp() {
|
|
258
|
+
if (!this.isResizing) return
|
|
259
|
+
|
|
260
|
+
this.isResizing = false
|
|
261
|
+
this.handleTarget.classList.remove('dragging')
|
|
262
|
+
this.panelTarget.classList.remove('resizing')
|
|
263
|
+
document.body.style.cursor = ''
|
|
264
|
+
document.body.style.userSelect = ''
|
|
265
|
+
|
|
266
|
+
// Save width to localStorage
|
|
267
|
+
localStorage.setItem('leva-panel-right-width', this.panelTarget.offsetWidth)
|
|
268
|
+
|
|
269
|
+
document.removeEventListener('mousemove', this.boundOnMouseMove)
|
|
270
|
+
document.removeEventListener('mouseup', this.boundOnMouseUp)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
disconnect() {
|
|
274
|
+
document.removeEventListener('mousemove', this.boundOnMouseMove)
|
|
275
|
+
document.removeEventListener('mouseup', this.boundOnMouseUp)
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
application.register("collapsible-panel", class extends Stimulus.Controller {
|
|
280
|
+
static values = { key: String }
|
|
281
|
+
|
|
282
|
+
connect() {
|
|
283
|
+
this.widthKey = 'leva-panel-right-width'
|
|
284
|
+
const isCollapsed = localStorage.getItem(this.keyValue) === 'true'
|
|
285
|
+
|
|
286
|
+
if (isCollapsed) {
|
|
287
|
+
this.element.classList.add('collapsed')
|
|
288
|
+
} else {
|
|
289
|
+
// Restore saved width when not collapsed
|
|
290
|
+
const savedWidth = localStorage.getItem(this.widthKey)
|
|
291
|
+
if (savedWidth) {
|
|
292
|
+
this.element.style.width = savedWidth + 'px'
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
toggle() {
|
|
298
|
+
const isCurrentlyCollapsed = this.element.classList.contains('collapsed')
|
|
299
|
+
|
|
300
|
+
if (isCurrentlyCollapsed) {
|
|
301
|
+
// Expanding - restore the saved width
|
|
302
|
+
this.element.classList.remove('collapsed')
|
|
303
|
+
const savedWidth = localStorage.getItem(this.widthKey)
|
|
304
|
+
if (savedWidth) {
|
|
305
|
+
this.element.style.width = savedWidth + 'px'
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
// Collapsing - save current width first, then collapse
|
|
309
|
+
const currentWidth = this.element.offsetWidth
|
|
310
|
+
if (currentWidth > 48) {
|
|
311
|
+
localStorage.setItem(this.widthKey, currentWidth)
|
|
312
|
+
}
|
|
313
|
+
this.element.classList.add('collapsed')
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const isNowCollapsed = this.element.classList.contains('collapsed')
|
|
317
|
+
localStorage.setItem(this.keyValue, isNowCollapsed)
|
|
156
318
|
}
|
|
157
319
|
})
|
|
158
320
|
})()
|
|
159
|
-
</script>
|
|
321
|
+
</script>
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<div>
|
|
1
|
+
<div class="content-header">
|
|
2
|
+
<div class="header-inline">
|
|
3
3
|
<% if selected_prompt.present? %>
|
|
4
|
-
<
|
|
5
|
-
<span class="
|
|
4
|
+
<h1 class="content-header-title"><%= selected_prompt.name %></h1>
|
|
5
|
+
<span class="badge badge-default" style="font-family: var(--font-mono); font-size: 11px;">v<%= selected_prompt.version %></span>
|
|
6
6
|
<% else %>
|
|
7
|
-
<span class="
|
|
7
|
+
<span class="content-header-title text-muted">Select a prompt to begin</span>
|
|
8
8
|
<% end %>
|
|
9
9
|
</div>
|
|
10
|
-
|
|
10
|
+
<% if selected_prompt.present? %>
|
|
11
|
+
<div class="flex items-center gap-2">
|
|
12
|
+
<%= link_to edit_workbench_path(selected_prompt), class: "btn btn-ghost btn-sm" do %>
|
|
13
|
+
<svg class="icon-sm" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
14
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
15
|
+
</svg>
|
|
16
|
+
Edit
|
|
17
|
+
<% end %>
|
|
18
|
+
</div>
|
|
19
|
+
<% end %>
|
|
20
|
+
</div>
|
|
@@ -1,20 +1,51 @@
|
|
|
1
1
|
<% content_for :title, "Edit Prompt: #{@prompt.name}" %>
|
|
2
|
-
<div class="container
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
<div class="container page">
|
|
3
|
+
<div class="page-header">
|
|
4
|
+
<div class="flex items-center gap-3">
|
|
5
|
+
<%= link_to workbench_index_path(prompt_id: @prompt.id), class: "btn btn-ghost btn-sm" do %>
|
|
6
|
+
<svg class="icon-sm" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
7
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
8
|
+
</svg>
|
|
9
|
+
<% end %>
|
|
10
|
+
<div>
|
|
11
|
+
<h1 class="page-title" style="margin-bottom: 0;">Edit Prompt</h1>
|
|
12
|
+
<p class="text-sm text-muted" style="margin: 0;"><%= @prompt.name %></p>
|
|
13
|
+
</div>
|
|
8
14
|
</div>
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
<span class="badge badge-default" style="font-family: var(--font-mono); font-size: 11px;">v<%= @prompt.version %></span>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<%= form_with(model: @prompt, url: workbench_path(@prompt), method: :patch, local: false, class: "card", data: { controller: "prompt-form" }) do |form| %>
|
|
19
|
+
<div class="form-group">
|
|
20
|
+
<div class="flex items-center gap-2 mb-2">
|
|
21
|
+
<svg class="icon-sm text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
22
|
+
<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" />
|
|
23
|
+
</svg>
|
|
24
|
+
<%= form.label :name, class: "form-label", style: "margin-bottom: 0;" %>
|
|
25
|
+
</div>
|
|
26
|
+
<%= form.text_field :name, class: "form-input", data: { action: "input->prompt-form#autoSave" } %>
|
|
12
27
|
</div>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
|
|
29
|
+
<div class="form-group">
|
|
30
|
+
<div class="flex items-center gap-2 mb-2">
|
|
31
|
+
<svg class="icon-sm text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
32
|
+
<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" />
|
|
33
|
+
</svg>
|
|
34
|
+
<%= form.label :system_prompt, "System Prompt", class: "form-label", style: "margin-bottom: 0;" %>
|
|
35
|
+
</div>
|
|
36
|
+
<%= form.text_area :system_prompt, rows: 5, class: "form-textarea prompt-textarea", placeholder: "Define the AI's role and behavior...", data: { action: "input->prompt-form#autoSave" } %>
|
|
16
37
|
</div>
|
|
17
|
-
|
|
38
|
+
|
|
39
|
+
<div class="form-group">
|
|
40
|
+
<div class="flex items-center gap-2 mb-2">
|
|
41
|
+
<svg class="icon-sm text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
42
|
+
<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" />
|
|
43
|
+
</svg>
|
|
44
|
+
<%= form.label :user_prompt, "User Prompt Template", class: "form-label", style: "margin-bottom: 0;" %>
|
|
45
|
+
</div>
|
|
46
|
+
<%= form.text_area :user_prompt, rows: 8, class: "form-textarea prompt-textarea", placeholder: "Use {{ variable }} syntax for dynamic content...", data: { action: "input->prompt-form#autoSave" } %>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div id="form-status" class="autosave-status" data-prompt-form-target="status"></div>
|
|
18
50
|
<% end %>
|
|
19
|
-
|
|
20
|
-
</div>
|
|
51
|
+
</div>
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
<% content_for :title, 'Workbench' %>
|
|
2
|
-
<div class="
|
|
3
|
-
<!-- Left Sidebar -->
|
|
2
|
+
<div class="layout-workbench">
|
|
4
3
|
<%= render 'prompt_sidebar', prompts: @prompts, selected_prompt: @selected_prompt %>
|
|
5
|
-
|
|
6
|
-
<div class="flex-1 flex flex-col">
|
|
7
|
-
<!-- Top Bar -->
|
|
4
|
+
<div class="main-content">
|
|
8
5
|
<%= render 'top_bar', selected_prompt: @selected_prompt %>
|
|
9
|
-
|
|
10
|
-
<div class="flex-1 flex overflow-hidden">
|
|
6
|
+
<div class="content-body" data-controller="resizable">
|
|
11
7
|
<%= render 'prompt_content', selected_prompt: @selected_prompt %>
|
|
8
|
+
<div class="resize-handle" data-resizable-target="handle" data-action="mousedown->resizable#startResize"></div>
|
|
12
9
|
<%= render 'results_section', evaluators: @evaluators, dataset_record: @dataset_record %>
|
|
13
10
|
</div>
|
|
14
11
|
</div>
|
|
15
|
-
</div>
|
|
12
|
+
</div>
|