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,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,133 +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
|
-
<% # Use merged context if available, otherwise just record context %>
|
|
52
|
-
<% context_to_display = @merged_context || @dataset_record.recordable.to_llm_context %>
|
|
53
|
-
|
|
54
|
-
<% if @record_context && @runner_context %>
|
|
55
|
-
<!-- Record Context -->
|
|
56
|
-
<div class="mb-4">
|
|
57
|
-
<h3 class="text-xs font-semibold text-gray-400 mb-2">FROM RECORD:</h3>
|
|
58
|
-
<% @record_context.each do |key, value| %>
|
|
59
|
-
<details class="mb-2">
|
|
60
|
-
<summary class="text-green-400 cursor-pointer flex items-center justify-between">
|
|
61
|
-
<span>{{ <%= key %> }}</span>
|
|
62
|
-
<button class="btn btn-small text-blue-400 hover:text-blue-300 flex items-center" data-action="clipboard#copy" data-clipboard-source="liquidTag<%= key %>">
|
|
63
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
64
|
-
<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" />
|
|
65
|
-
</svg>
|
|
66
|
-
Copy
|
|
67
|
-
</button>
|
|
68
|
-
</summary>
|
|
69
|
-
<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>
|
|
70
|
-
</details>
|
|
71
|
-
<% end %>
|
|
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>
|
|
72
64
|
</div>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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 %>
|
|
76
74
|
<div class="mb-4">
|
|
77
|
-
<
|
|
78
|
-
<% @
|
|
75
|
+
<p class="text-xs uppercase text-muted font-semibold mb-2">From Record:</p>
|
|
76
|
+
<% @record_context.each do |key, value| %>
|
|
79
77
|
<details class="mb-2">
|
|
80
|
-
<summary class="
|
|
81
|
-
<span>{{ <%= key %> }}</span>
|
|
82
|
-
<button class="btn btn-
|
|
83
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
84
|
-
<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" />
|
|
85
|
-
</svg>
|
|
86
|
-
Copy
|
|
87
|
-
</button>
|
|
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>
|
|
88
81
|
</summary>
|
|
89
|
-
<pre class="
|
|
82
|
+
<pre class="mt-2" id="liquidTag<%= key %>"><code><%= value.to_s %></code></pre>
|
|
90
83
|
</details>
|
|
91
84
|
<% end %>
|
|
92
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 %>
|
|
93
112
|
<% end %>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<% context_to_display.each do |key, value| %>
|
|
97
|
-
<details class="mb-2">
|
|
98
|
-
<summary class="text-green-400 cursor-pointer flex items-center justify-between">
|
|
99
|
-
<span>{{ <%= key %> }}</span>
|
|
100
|
-
<button class="btn btn-small text-blue-400 hover:text-blue-300 flex items-center" data-action="clipboard#copy" data-clipboard-source="liquidTag<%= key %>">
|
|
101
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
102
|
-
<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" />
|
|
103
|
-
</svg>
|
|
104
|
-
Copy
|
|
105
|
-
</button>
|
|
106
|
-
</summary>
|
|
107
|
-
<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>
|
|
108
|
-
</details>
|
|
109
|
-
<% end %>
|
|
110
|
-
<% end %>
|
|
111
|
-
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</details>
|
|
112
115
|
</div>
|
|
113
116
|
<% end %>
|
|
114
|
-
|
|
117
|
+
|
|
118
|
+
<%# Full Prompt Preview %>
|
|
115
119
|
<% if @dataset_record %>
|
|
116
|
-
<div class="
|
|
117
|
-
<div class="
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
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">
|
|
121
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" />
|
|
122
132
|
</svg>
|
|
123
|
-
Copy
|
|
124
133
|
</button>
|
|
125
134
|
</div>
|
|
126
|
-
<
|
|
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>
|
|
127
138
|
</div>
|
|
128
139
|
<% end %>
|
|
129
|
-
|
|
140
|
+
|
|
141
|
+
<div class="autosave-status" data-prompt-autosave-target="status"></div>
|
|
130
142
|
</div>
|
|
143
|
+
|
|
131
144
|
<script>
|
|
132
145
|
(() => {
|
|
133
146
|
const application = Stimulus.Application.start()
|
|
@@ -149,8 +162,6 @@
|
|
|
149
162
|
textareas.forEach(ta => {
|
|
150
163
|
ta.style.height = 'auto'
|
|
151
164
|
ta.style.height = (ta.scrollHeight + 5) + 'px'
|
|
152
|
-
|
|
153
|
-
// Ensure horizontal text wrapping
|
|
154
165
|
ta.style.wordBreak = 'break-word'
|
|
155
166
|
ta.style.wordWrap = 'break-word'
|
|
156
167
|
})
|
|
@@ -167,7 +178,7 @@
|
|
|
167
178
|
})
|
|
168
179
|
|
|
169
180
|
this.statusTarget.textContent = "Saving..."
|
|
170
|
-
this.statusTarget.
|
|
181
|
+
this.statusTarget.style.color = "var(--warning-400)"
|
|
171
182
|
|
|
172
183
|
fetch(this.urlValue, {
|
|
173
184
|
method: 'PATCH',
|
|
@@ -181,24 +192,20 @@
|
|
|
181
192
|
.then(response => response.json())
|
|
182
193
|
.then(data => {
|
|
183
194
|
if (data.status === 'success') {
|
|
184
|
-
this.statusTarget.textContent = "Changes saved
|
|
185
|
-
this.statusTarget.
|
|
186
|
-
this.statusTarget.classList.add("text-green-500")
|
|
195
|
+
this.statusTarget.textContent = "Changes saved"
|
|
196
|
+
this.statusTarget.style.color = "var(--success-400)"
|
|
187
197
|
} else {
|
|
188
198
|
this.statusTarget.textContent = `Error: ${data.errors.join(", ")}`
|
|
189
|
-
this.statusTarget.
|
|
190
|
-
this.statusTarget.classList.add("text-red-500")
|
|
199
|
+
this.statusTarget.style.color = "var(--error-400)"
|
|
191
200
|
}
|
|
192
201
|
setTimeout(() => {
|
|
193
202
|
this.statusTarget.textContent = ""
|
|
194
|
-
this.statusTarget.classList.remove("text-green-500", "text-red-500")
|
|
195
203
|
}, 3000)
|
|
196
204
|
})
|
|
197
205
|
.catch(error => {
|
|
198
206
|
console.error('Error:', error)
|
|
199
207
|
this.statusTarget.textContent = "Error saving changes"
|
|
200
|
-
this.statusTarget.
|
|
201
|
-
this.statusTarget.classList.add("text-red-500")
|
|
208
|
+
this.statusTarget.style.color = "var(--error-400)"
|
|
202
209
|
})
|
|
203
210
|
}
|
|
204
211
|
|
|
@@ -224,8 +231,6 @@
|
|
|
224
231
|
})
|
|
225
232
|
|
|
226
233
|
application.register("clipboard", class extends Stimulus.Controller {
|
|
227
|
-
static targets = ["content"]
|
|
228
|
-
|
|
229
234
|
copy(event) {
|
|
230
235
|
const sourceId = event.currentTarget.dataset.clipboardSource
|
|
231
236
|
const sourceElement = document.getElementById(sourceId)
|
|
@@ -234,19 +239,19 @@
|
|
|
234
239
|
this.showFeedback(event.currentTarget, "Copied!")
|
|
235
240
|
}, (err) => {
|
|
236
241
|
console.error('Could not copy text: ', err)
|
|
237
|
-
this.showFeedback(event.currentTarget, "Failed
|
|
242
|
+
this.showFeedback(event.currentTarget, "Failed", true)
|
|
238
243
|
})
|
|
239
244
|
}
|
|
240
245
|
|
|
241
246
|
showFeedback(button, message, isError = false) {
|
|
242
247
|
const originalText = button.textContent
|
|
243
248
|
button.textContent = message
|
|
244
|
-
button.
|
|
249
|
+
button.style.color = isError ? "var(--error-400)" : "var(--success-400)"
|
|
245
250
|
button.disabled = true
|
|
246
251
|
|
|
247
252
|
setTimeout(() => {
|
|
248
253
|
button.textContent = originalText
|
|
249
|
-
button.
|
|
254
|
+
button.style.color = ""
|
|
250
255
|
button.disabled = false
|
|
251
256
|
}, 2000)
|
|
252
257
|
}
|
|
@@ -254,13 +259,13 @@
|
|
|
254
259
|
})()
|
|
255
260
|
</script>
|
|
256
261
|
<% else %>
|
|
257
|
-
<div class="
|
|
258
|
-
<div class="
|
|
259
|
-
<svg
|
|
260
|
-
<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" />
|
|
261
266
|
</svg>
|
|
262
|
-
<h3 class="
|
|
263
|
-
<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>
|
|
264
269
|
</div>
|
|
265
270
|
</div>
|
|
266
|
-
<% 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>
|