leva 0.2.0 → 0.3.1
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 +55 -1
- data/app/assets/stylesheets/leva/application.css +165 -25
- data/app/controllers/leva/dataset_optimizations_controller.rb +64 -0
- data/app/controllers/leva/experiments_controller.rb +14 -6
- data/app/controllers/leva/workbench_controller.rb +26 -10
- data/app/helpers/leva/application_helper.rb +32 -16
- data/app/models/leva/dataset.rb +1 -0
- data/app/models/leva/experiment.rb +1 -0
- data/app/models/leva/optimization_run.rb +137 -0
- data/app/models/leva/prompt.rb +10 -0
- data/app/services/leva/class_loader.rb +37 -0
- data/app/services/leva/dataset_converter.rb +64 -0
- data/app/services/leva/optimizers/base.rb +183 -0
- data/app/services/leva/optimizers/bootstrap.rb +92 -0
- data/app/services/leva/optimizers/gepa_optimizer.rb +59 -0
- data/app/services/leva/optimizers/miprov2_optimizer.rb +52 -0
- data/app/services/leva/prompt_optimizer.rb +305 -0
- data/app/services/leva/signature_generator.rb +129 -0
- data/app/views/leva/datasets/show.html.erb +3 -0
- data/app/views/leva/experiments/_experiment.html.erb +9 -10
- data/app/views/leva/experiments/_form.html.erb +10 -0
- data/app/views/leva/experiments/index.html.erb +2 -1
- data/app/views/leva/experiments/show.html.erb +20 -21
- data/app/views/leva/optimization_runs/show.html.erb +698 -0
- data/app/views/leva/runner_results/show.html.erb +18 -48
- data/app/views/leva/workbench/_results_section.html.erb +3 -11
- data/db/migrate/20241204000001_create_leva_optimization_runs.rb +25 -0
- data/lib/generators/leva/templates/eval.rb.erb +4 -2
- data/lib/leva/errors.rb +18 -0
- data/lib/leva/version.rb +1 -1
- data/lib/leva.rb +1 -0
- metadata +16 -3
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
<% content_for :title, "Optimization Progress" %>
|
|
2
|
+
|
|
3
|
+
<% content_for :head do %>
|
|
4
|
+
<script>
|
|
5
|
+
// Optimization Progress Polling Controller
|
|
6
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
7
|
+
const container = document.querySelector('[data-optimization-progress]');
|
|
8
|
+
if (!container) return;
|
|
9
|
+
|
|
10
|
+
const url = container.dataset.optimizationProgress;
|
|
11
|
+
const pollInterval = 10000; // 10 seconds
|
|
12
|
+
|
|
13
|
+
function updateUI(data) {
|
|
14
|
+
// Update progress bar
|
|
15
|
+
const progressBar = document.querySelector('[data-progress-bar]');
|
|
16
|
+
if (progressBar) {
|
|
17
|
+
progressBar.style.width = data.progress + '%';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Update progress text
|
|
21
|
+
const progressText = document.querySelector('[data-progress-text]');
|
|
22
|
+
if (progressText) {
|
|
23
|
+
progressText.textContent = data.progress + '%';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Update step label
|
|
27
|
+
const stepLabel = document.querySelector('[data-step-label]');
|
|
28
|
+
if (stepLabel) {
|
|
29
|
+
stepLabel.textContent = data.current_step_label;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Update examples count
|
|
33
|
+
const examplesCount = document.querySelector('[data-examples-count]');
|
|
34
|
+
if (examplesCount && data.total_examples) {
|
|
35
|
+
examplesCount.textContent = data.examples_processed + ' / ' + data.total_examples;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Update elapsed time
|
|
39
|
+
const elapsedTime = document.querySelector('[data-elapsed-time]');
|
|
40
|
+
if (elapsedTime) {
|
|
41
|
+
elapsedTime.textContent = data.elapsed_time;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Update status indicator
|
|
45
|
+
const statusIndicator = document.querySelector('[data-status-indicator]');
|
|
46
|
+
if (statusIndicator) {
|
|
47
|
+
statusIndicator.className = 'status-indicator status-' + data.status;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Handle completion
|
|
51
|
+
if (data.status === 'completed') {
|
|
52
|
+
const completedSection = document.querySelector('[data-completed-section]');
|
|
53
|
+
const runningSection = document.querySelector('[data-running-section]');
|
|
54
|
+
if (completedSection) completedSection.style.display = 'block';
|
|
55
|
+
if (runningSection) runningSection.style.display = 'none';
|
|
56
|
+
|
|
57
|
+
// Update prompt link
|
|
58
|
+
const promptLink = document.querySelector('[data-prompt-link]');
|
|
59
|
+
if (promptLink && data.prompt_id) {
|
|
60
|
+
promptLink.href = promptLink.href.replace('PROMPT_ID', data.prompt_id);
|
|
61
|
+
promptLink.style.display = 'inline-flex';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Handle failure
|
|
66
|
+
if (data.status === 'failed') {
|
|
67
|
+
const failedSection = document.querySelector('[data-failed-section]');
|
|
68
|
+
const runningSection = document.querySelector('[data-running-section]');
|
|
69
|
+
if (failedSection) {
|
|
70
|
+
failedSection.style.display = 'block';
|
|
71
|
+
const errorMessage = failedSection.querySelector('[data-error-message]');
|
|
72
|
+
if (errorMessage) errorMessage.textContent = data.error_message;
|
|
73
|
+
}
|
|
74
|
+
if (runningSection) runningSection.style.display = 'none';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function poll() {
|
|
79
|
+
fetch(url, { headers: { 'Accept': 'application/json' } })
|
|
80
|
+
.then(response => response.json())
|
|
81
|
+
.then(data => {
|
|
82
|
+
updateUI(data);
|
|
83
|
+
if (data.status === 'pending' || data.status === 'running') {
|
|
84
|
+
setTimeout(poll, pollInterval);
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
.catch(error => {
|
|
88
|
+
console.error('Error polling optimization status:', error);
|
|
89
|
+
setTimeout(poll, pollInterval * 2); // Retry with longer delay
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Start polling
|
|
94
|
+
poll();
|
|
95
|
+
});
|
|
96
|
+
</script>
|
|
97
|
+
<% end %>
|
|
98
|
+
|
|
99
|
+
<div class="container page" data-optimization-progress="<%= optimization_run_path(@optimization_run, format: :json) %>">
|
|
100
|
+
<div class="page-header">
|
|
101
|
+
<div>
|
|
102
|
+
<div class="breadcrumb mb-2">
|
|
103
|
+
<%= link_to "Datasets", datasets_path, class: "breadcrumb-link" %>
|
|
104
|
+
<span class="breadcrumb-sep">/</span>
|
|
105
|
+
<%= link_to @optimization_run.dataset.name, dataset_path(@optimization_run.dataset), class: "breadcrumb-link" %>
|
|
106
|
+
<span class="breadcrumb-sep">/</span>
|
|
107
|
+
<span class="breadcrumb-current">Optimization</span>
|
|
108
|
+
</div>
|
|
109
|
+
<h1 class="page-title">Optimizing Prompt</h1>
|
|
110
|
+
<p class="text-muted text-sm mt-2">
|
|
111
|
+
Creating optimized prompt: <strong><%= @optimization_run.prompt_name %></strong>
|
|
112
|
+
</p>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<%# Running State %>
|
|
117
|
+
<section data-running-section style="<%= @optimization_run.completed? || @optimization_run.failed? ? 'display: none;' : '' %>">
|
|
118
|
+
<div class="card">
|
|
119
|
+
<div class="card-body">
|
|
120
|
+
<div class="progress-container">
|
|
121
|
+
<div class="progress-header">
|
|
122
|
+
<div class="progress-status">
|
|
123
|
+
<span class="status-indicator status-<%= @optimization_run.status %>" data-status-indicator></span>
|
|
124
|
+
<span class="step-label" data-step-label><%= @optimization_run.current_step_label %></span>
|
|
125
|
+
</div>
|
|
126
|
+
<span class="progress-percentage" data-progress-text><%= @optimization_run.progress %>%</span>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<div class="progress-bar-container">
|
|
130
|
+
<div class="progress-bar" data-progress-bar style="width: <%= @optimization_run.progress %>%;"></div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div class="progress-details">
|
|
134
|
+
<% if @optimization_run.total_examples %>
|
|
135
|
+
<span class="detail-item">
|
|
136
|
+
<svg class="icon-xs" viewBox="0 0 20 20" fill="currentColor">
|
|
137
|
+
<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/>
|
|
138
|
+
<path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd"/>
|
|
139
|
+
</svg>
|
|
140
|
+
<span data-examples-count><%= @optimization_run.examples_processed %> / <%= @optimization_run.total_examples %></span>
|
|
141
|
+
examples
|
|
142
|
+
</span>
|
|
143
|
+
<% end %>
|
|
144
|
+
<span class="detail-item">
|
|
145
|
+
<svg class="icon-xs" viewBox="0 0 20 20" fill="currentColor">
|
|
146
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/>
|
|
147
|
+
</svg>
|
|
148
|
+
<span data-elapsed-time><%= @optimization_run.elapsed_time_formatted %></span>
|
|
149
|
+
</span>
|
|
150
|
+
<span class="detail-item">
|
|
151
|
+
<svg class="icon-xs" viewBox="0 0 20 20" fill="currentColor">
|
|
152
|
+
<path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clip-rule="evenodd"/>
|
|
153
|
+
</svg>
|
|
154
|
+
<%= @optimization_run.mode.capitalize %> mode
|
|
155
|
+
</span>
|
|
156
|
+
<% if @optimization_run.model.present? %>
|
|
157
|
+
<span class="detail-item">
|
|
158
|
+
<svg class="icon-xs" viewBox="0 0 20 20" fill="currentColor">
|
|
159
|
+
<path d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z"/>
|
|
160
|
+
</svg>
|
|
161
|
+
<%= model_display_name(@optimization_run.model) %>
|
|
162
|
+
</span>
|
|
163
|
+
<% end %>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div class="optimization-steps">
|
|
168
|
+
<% Leva::OptimizationRun::STEPS.each do |step_key, step_info| %>
|
|
169
|
+
<% step_status = optimization_step_status(@optimization_run, step_key) %>
|
|
170
|
+
<div class="step-item step-<%= step_status %>">
|
|
171
|
+
<div class="step-icon">
|
|
172
|
+
<% if step_status == 'completed' %>
|
|
173
|
+
<svg viewBox="0 0 20 20" fill="currentColor">
|
|
174
|
+
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
|
175
|
+
</svg>
|
|
176
|
+
<% elsif step_status == 'active' %>
|
|
177
|
+
<div class="spinner"></div>
|
|
178
|
+
<% else %>
|
|
179
|
+
<div class="step-dot"></div>
|
|
180
|
+
<% end %>
|
|
181
|
+
</div>
|
|
182
|
+
<span class="step-text"><%= step_info[:label] %></span>
|
|
183
|
+
</div>
|
|
184
|
+
<% end %>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</section>
|
|
189
|
+
|
|
190
|
+
<%# Completed State %>
|
|
191
|
+
<section data-completed-section style="<%= @optimization_run.completed? ? '' : 'display: none;' %>">
|
|
192
|
+
<%# Success Header %>
|
|
193
|
+
<div class="card mb-4">
|
|
194
|
+
<div class="card-body">
|
|
195
|
+
<div class="completed-header">
|
|
196
|
+
<div class="completed-header-left">
|
|
197
|
+
<div class="success-icon-sm">
|
|
198
|
+
<svg viewBox="0 0 20 20" fill="currentColor">
|
|
199
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
200
|
+
</svg>
|
|
201
|
+
</div>
|
|
202
|
+
<div>
|
|
203
|
+
<h2 class="completed-title">Optimization Complete</h2>
|
|
204
|
+
<p class="completed-subtitle"><%= @optimization_run.prompt_name %></p>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="completed-actions">
|
|
208
|
+
<%= link_to workbench_index_path(prompt_id: @optimization_run.prompt_id), class: "btn btn-primary", data: { prompt_link: true }, style: @optimization_run.prompt_id ? '' : 'display: none;' do %>
|
|
209
|
+
<svg class="icon-sm" viewBox="0 0 20 20" fill="currentColor">
|
|
210
|
+
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"/>
|
|
211
|
+
</svg>
|
|
212
|
+
Open in Workbench
|
|
213
|
+
<% end %>
|
|
214
|
+
<%= link_to "Back to Dataset", dataset_path(@optimization_run.dataset), class: "btn btn-ghost" %>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<% if @optimization_run.prompt.present? %>
|
|
221
|
+
<% metadata = @optimization_run.prompt.metadata&.dig("optimization") || {} %>
|
|
222
|
+
|
|
223
|
+
<%# Stats Row %>
|
|
224
|
+
<div class="optimization-stats mb-4">
|
|
225
|
+
<div class="stat-card">
|
|
226
|
+
<div class="stat-value <%= score_class(metadata["score"]) %>">
|
|
227
|
+
<%= metadata["score"] ? "#{(metadata["score"] * 100).round}%" : "—" %>
|
|
228
|
+
</div>
|
|
229
|
+
<div class="stat-label">Optimization Score</div>
|
|
230
|
+
</div>
|
|
231
|
+
<div class="stat-card">
|
|
232
|
+
<div class="stat-value"><%= metadata["train_size"] || "—" %></div>
|
|
233
|
+
<div class="stat-label">Training Examples</div>
|
|
234
|
+
</div>
|
|
235
|
+
<div class="stat-card">
|
|
236
|
+
<div class="stat-value"><%= metadata["val_size"] || "—" %></div>
|
|
237
|
+
<div class="stat-label">Validation Examples</div>
|
|
238
|
+
</div>
|
|
239
|
+
<div class="stat-card">
|
|
240
|
+
<div class="stat-value"><%= @optimization_run.elapsed_time_formatted %></div>
|
|
241
|
+
<div class="stat-label">Duration</div>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<%# Optimized Prompt Content %>
|
|
246
|
+
<div class="card">
|
|
247
|
+
<div class="card-body">
|
|
248
|
+
<div class="section-header mb-3">
|
|
249
|
+
<h3 class="section-title">
|
|
250
|
+
<svg class="icon-sm" style="color: var(--gray-400);" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
251
|
+
<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"/>
|
|
252
|
+
</svg>
|
|
253
|
+
System Prompt
|
|
254
|
+
</h3>
|
|
255
|
+
</div>
|
|
256
|
+
<div class="prompt-preview">
|
|
257
|
+
<pre class="prompt-code"><code><%= @optimization_run.prompt.system_prompt %></code></pre>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<% few_shot_examples = metadata["few_shot_examples"] || [] %>
|
|
261
|
+
<% if few_shot_examples.any? %>
|
|
262
|
+
<div class="section-header mb-3 mt-5">
|
|
263
|
+
<h3 class="section-title">
|
|
264
|
+
<svg class="icon-sm" style="color: var(--gray-400);" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
265
|
+
<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"/>
|
|
266
|
+
</svg>
|
|
267
|
+
Few-Shot Examples
|
|
268
|
+
</h3>
|
|
269
|
+
<span class="section-count"><%= few_shot_examples.size %> examples</span>
|
|
270
|
+
</div>
|
|
271
|
+
<div class="few-shot-list">
|
|
272
|
+
<% few_shot_examples.each_with_index do |example, idx| %>
|
|
273
|
+
<details class="collapsible">
|
|
274
|
+
<summary class="collapsible-header">
|
|
275
|
+
<span class="collapsible-title">Example <%= idx + 1 %></span>
|
|
276
|
+
<svg class="collapsible-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
277
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
278
|
+
</svg>
|
|
279
|
+
</summary>
|
|
280
|
+
<div class="collapsible-body">
|
|
281
|
+
<div class="example-grid">
|
|
282
|
+
<div class="example-block">
|
|
283
|
+
<span class="example-label">Input</span>
|
|
284
|
+
<pre class="example-code"><code><%= example["input"].is_a?(Hash) ? example["input"].to_json : example["input"] %></code></pre>
|
|
285
|
+
</div>
|
|
286
|
+
<div class="example-block example-block--output">
|
|
287
|
+
<span class="example-label">Output</span>
|
|
288
|
+
<pre class="example-code"><code><%= example["output"] %></code></pre>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
</details>
|
|
293
|
+
<% end %>
|
|
294
|
+
</div>
|
|
295
|
+
<% end %>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
<% end %>
|
|
299
|
+
</section>
|
|
300
|
+
|
|
301
|
+
<%# Failed State %>
|
|
302
|
+
<section data-failed-section style="<%= @optimization_run.failed? ? '' : 'display: none;' %>">
|
|
303
|
+
<div class="card">
|
|
304
|
+
<div class="card-body">
|
|
305
|
+
<div class="error-state">
|
|
306
|
+
<div class="error-icon">
|
|
307
|
+
<svg viewBox="0 0 20 20" fill="currentColor">
|
|
308
|
+
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
|
309
|
+
</svg>
|
|
310
|
+
</div>
|
|
311
|
+
<h2 class="error-title">Optimization Failed</h2>
|
|
312
|
+
<p class="error-message" data-error-message><%= @optimization_run.error_message %></p>
|
|
313
|
+
<div class="error-actions">
|
|
314
|
+
<%= link_to "Try Again", new_dataset_optimization_path(@optimization_run.dataset), class: "btn btn-primary" %>
|
|
315
|
+
<%= link_to "Back to Dataset", dataset_path(@optimization_run.dataset), class: "btn btn-ghost" %>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
</section>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<style>
|
|
324
|
+
.progress-container {
|
|
325
|
+
margin-bottom: 2rem;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.progress-header {
|
|
329
|
+
display: flex;
|
|
330
|
+
justify-content: space-between;
|
|
331
|
+
align-items: center;
|
|
332
|
+
margin-bottom: 0.75rem;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.progress-status {
|
|
336
|
+
display: flex;
|
|
337
|
+
align-items: center;
|
|
338
|
+
gap: 0.5rem;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.status-indicator {
|
|
342
|
+
width: 8px;
|
|
343
|
+
height: 8px;
|
|
344
|
+
border-radius: 50%;
|
|
345
|
+
background-color: var(--text-muted);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.status-indicator.status-pending {
|
|
349
|
+
background-color: var(--text-muted);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.status-indicator.status-running {
|
|
353
|
+
background-color: var(--primary);
|
|
354
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.status-indicator.status-completed {
|
|
358
|
+
background-color: rgb(34, 197, 94);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.status-indicator.status-failed {
|
|
362
|
+
background-color: rgb(239, 68, 68);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
@keyframes pulse {
|
|
366
|
+
0%, 100% { opacity: 1; }
|
|
367
|
+
50% { opacity: 0.5; }
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.step-label {
|
|
371
|
+
font-weight: 500;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.progress-percentage {
|
|
375
|
+
font-size: 1.5rem;
|
|
376
|
+
font-weight: 600;
|
|
377
|
+
font-family: 'Fira Code', monospace;
|
|
378
|
+
color: var(--primary);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.progress-bar-container {
|
|
382
|
+
height: 8px;
|
|
383
|
+
background-color: var(--bg-tertiary);
|
|
384
|
+
border-radius: 4px;
|
|
385
|
+
overflow: hidden;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.progress-bar {
|
|
389
|
+
height: 100%;
|
|
390
|
+
background-color: var(--primary);
|
|
391
|
+
transition: width 0.3s ease;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.progress-details {
|
|
395
|
+
display: flex;
|
|
396
|
+
gap: 1.5rem;
|
|
397
|
+
margin-top: 0.75rem;
|
|
398
|
+
color: var(--text-muted);
|
|
399
|
+
font-size: 0.875rem;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.detail-item {
|
|
403
|
+
display: flex;
|
|
404
|
+
align-items: center;
|
|
405
|
+
gap: 0.375rem;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.optimization-steps {
|
|
409
|
+
display: flex;
|
|
410
|
+
flex-direction: column;
|
|
411
|
+
gap: 0.5rem;
|
|
412
|
+
padding: 1rem;
|
|
413
|
+
background-color: var(--bg-secondary);
|
|
414
|
+
border-radius: 0.5rem;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.step-item {
|
|
418
|
+
display: flex;
|
|
419
|
+
align-items: center;
|
|
420
|
+
gap: 0.75rem;
|
|
421
|
+
padding: 0.5rem;
|
|
422
|
+
color: var(--text-muted);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.step-item.step-completed {
|
|
426
|
+
color: rgb(34, 197, 94);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.step-item.step-active {
|
|
430
|
+
color: var(--primary);
|
|
431
|
+
font-weight: 500;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.step-icon {
|
|
435
|
+
width: 20px;
|
|
436
|
+
height: 20px;
|
|
437
|
+
display: flex;
|
|
438
|
+
align-items: center;
|
|
439
|
+
justify-content: center;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.step-icon svg {
|
|
443
|
+
width: 16px;
|
|
444
|
+
height: 16px;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.step-dot {
|
|
448
|
+
width: 6px;
|
|
449
|
+
height: 6px;
|
|
450
|
+
border-radius: 50%;
|
|
451
|
+
background-color: currentColor;
|
|
452
|
+
opacity: 0.5;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.spinner {
|
|
456
|
+
width: 16px;
|
|
457
|
+
height: 16px;
|
|
458
|
+
border: 2px solid currentColor;
|
|
459
|
+
border-top-color: transparent;
|
|
460
|
+
border-radius: 50%;
|
|
461
|
+
animation: spin 1s linear infinite;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
@keyframes spin {
|
|
465
|
+
to { transform: rotate(360deg); }
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.success-state, .error-state {
|
|
469
|
+
text-align: center;
|
|
470
|
+
padding: 2rem;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.success-icon, .error-icon {
|
|
474
|
+
width: 64px;
|
|
475
|
+
height: 64px;
|
|
476
|
+
margin: 0 auto 1rem;
|
|
477
|
+
border-radius: 50%;
|
|
478
|
+
display: flex;
|
|
479
|
+
align-items: center;
|
|
480
|
+
justify-content: center;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.success-icon {
|
|
484
|
+
background-color: rgba(34, 197, 94, 0.15);
|
|
485
|
+
color: rgb(34, 197, 94);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.error-icon {
|
|
489
|
+
background-color: rgba(239, 68, 68, 0.15);
|
|
490
|
+
color: rgb(239, 68, 68);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.success-icon svg, .error-icon svg {
|
|
494
|
+
width: 32px;
|
|
495
|
+
height: 32px;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.success-title, .error-title {
|
|
499
|
+
font-size: 1.25rem;
|
|
500
|
+
font-weight: 600;
|
|
501
|
+
margin-bottom: 0.5rem;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.success-description, .error-message {
|
|
505
|
+
color: var(--text-muted);
|
|
506
|
+
margin-bottom: 1.5rem;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.error-message {
|
|
510
|
+
font-family: 'Fira Code', monospace;
|
|
511
|
+
font-size: 0.875rem;
|
|
512
|
+
background-color: var(--bg-secondary);
|
|
513
|
+
padding: 1rem;
|
|
514
|
+
border-radius: 0.5rem;
|
|
515
|
+
text-align: left;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.success-actions, .error-actions {
|
|
519
|
+
display: flex;
|
|
520
|
+
gap: 0.75rem;
|
|
521
|
+
justify-content: center;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* Completed State - New Design */
|
|
525
|
+
.completed-header {
|
|
526
|
+
display: flex;
|
|
527
|
+
justify-content: space-between;
|
|
528
|
+
align-items: center;
|
|
529
|
+
gap: 1rem;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.completed-header-left {
|
|
533
|
+
display: flex;
|
|
534
|
+
align-items: center;
|
|
535
|
+
gap: 1rem;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.success-icon-sm {
|
|
539
|
+
width: 48px;
|
|
540
|
+
height: 48px;
|
|
541
|
+
border-radius: 50%;
|
|
542
|
+
background-color: rgba(34, 197, 94, 0.15);
|
|
543
|
+
color: rgb(34, 197, 94);
|
|
544
|
+
display: flex;
|
|
545
|
+
align-items: center;
|
|
546
|
+
justify-content: center;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.success-icon-sm svg {
|
|
550
|
+
width: 24px;
|
|
551
|
+
height: 24px;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.completed-title {
|
|
555
|
+
font-size: 1.25rem;
|
|
556
|
+
font-weight: 600;
|
|
557
|
+
margin: 0;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.completed-subtitle {
|
|
561
|
+
color: var(--text-muted);
|
|
562
|
+
margin: 0.25rem 0 0 0;
|
|
563
|
+
font-size: 0.875rem;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.completed-actions {
|
|
567
|
+
display: flex;
|
|
568
|
+
gap: 0.75rem;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.optimization-stats {
|
|
572
|
+
display: grid;
|
|
573
|
+
grid-template-columns: repeat(4, 1fr);
|
|
574
|
+
gap: 1rem;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.stat-card {
|
|
578
|
+
background: var(--bg-secondary);
|
|
579
|
+
border-radius: 0.5rem;
|
|
580
|
+
padding: 1.25rem;
|
|
581
|
+
text-align: center;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.stat-value {
|
|
585
|
+
font-size: 1.5rem;
|
|
586
|
+
font-weight: 600;
|
|
587
|
+
font-family: 'Fira Code', monospace;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.stat-label {
|
|
591
|
+
font-size: 0.75rem;
|
|
592
|
+
color: var(--text-muted);
|
|
593
|
+
text-transform: uppercase;
|
|
594
|
+
letter-spacing: 0.04em;
|
|
595
|
+
margin-top: 0.25rem;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.section-header {
|
|
599
|
+
display: flex;
|
|
600
|
+
align-items: center;
|
|
601
|
+
justify-content: space-between;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.section-title {
|
|
605
|
+
display: flex;
|
|
606
|
+
align-items: center;
|
|
607
|
+
gap: 0.5rem;
|
|
608
|
+
font-size: 0.875rem;
|
|
609
|
+
font-weight: 500;
|
|
610
|
+
color: var(--text-secondary);
|
|
611
|
+
margin: 0;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.section-count {
|
|
615
|
+
font-size: 0.75rem;
|
|
616
|
+
color: var(--text-muted);
|
|
617
|
+
background: var(--bg-tertiary);
|
|
618
|
+
padding: 0.25rem 0.5rem;
|
|
619
|
+
border-radius: 0.25rem;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.prompt-preview {
|
|
623
|
+
background: var(--bg-secondary);
|
|
624
|
+
border-radius: 0.5rem;
|
|
625
|
+
overflow: hidden;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.prompt-code {
|
|
629
|
+
margin: 0;
|
|
630
|
+
padding: 1rem;
|
|
631
|
+
font-family: 'Fira Code', monospace;
|
|
632
|
+
font-size: 0.8125rem;
|
|
633
|
+
line-height: 1.6;
|
|
634
|
+
white-space: pre-wrap;
|
|
635
|
+
word-break: break-word;
|
|
636
|
+
color: var(--text-primary);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.few-shot-list {
|
|
640
|
+
display: flex;
|
|
641
|
+
flex-direction: column;
|
|
642
|
+
gap: 0.5rem;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.example-grid {
|
|
646
|
+
display: grid;
|
|
647
|
+
grid-template-columns: 1fr 1fr;
|
|
648
|
+
gap: 1rem;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.example-block {
|
|
652
|
+
background: var(--bg-tertiary);
|
|
653
|
+
border-radius: 0.375rem;
|
|
654
|
+
padding: 0.75rem;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.example-block--output {
|
|
658
|
+
border-left: 2px solid rgb(34, 197, 94);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.example-label {
|
|
662
|
+
display: block;
|
|
663
|
+
font-size: 0.625rem;
|
|
664
|
+
font-weight: 600;
|
|
665
|
+
text-transform: uppercase;
|
|
666
|
+
letter-spacing: 0.04em;
|
|
667
|
+
color: var(--text-muted);
|
|
668
|
+
margin-bottom: 0.5rem;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.example-code {
|
|
672
|
+
margin: 0;
|
|
673
|
+
font-family: 'Fira Code', monospace;
|
|
674
|
+
font-size: 0.75rem;
|
|
675
|
+
line-height: 1.5;
|
|
676
|
+
white-space: pre-wrap;
|
|
677
|
+
word-break: break-word;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.mb-3 { margin-bottom: 0.75rem; }
|
|
681
|
+
.mb-4 { margin-bottom: 1rem; }
|
|
682
|
+
.mt-5 { margin-top: 1.25rem; }
|
|
683
|
+
|
|
684
|
+
@media (max-width: 768px) {
|
|
685
|
+
.optimization-stats {
|
|
686
|
+
grid-template-columns: repeat(2, 1fr);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.completed-header {
|
|
690
|
+
flex-direction: column;
|
|
691
|
+
align-items: flex-start;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.example-grid {
|
|
695
|
+
grid-template-columns: 1fr;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
</style>
|