studio-engine 0.122.0 → 0.123.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -0
  3. data/README.md +2 -2
  4. data/app/controllers/concerns/studio/admin_models.rb +24 -16
  5. data/app/controllers/concerns/studio/error_handling.rb +13 -0
  6. data/app/controllers/studio/agent_chats_controller.rb +31 -4
  7. data/app/controllers/studio/voice_takes_controller.rb +11 -0
  8. data/app/controllers/style_controller.rb +2 -5
  9. data/app/controllers/theme_settings_controller.rb +8 -8
  10. data/app/helpers/studio/agent_chat_helper.rb +37 -0
  11. data/app/javascript/studio/agent_chat.js +42 -2
  12. data/app/javascript/studio/agent_chat_voice.js +318 -0
  13. data/app/javascript/studio/alpine_shims.js +1 -3
  14. data/app/javascript/studio/controllers/agent_chat_voice_controller.js +17 -0
  15. data/app/javascript/studio/controllers/style_section_nav_controller.js +80 -0
  16. data/app/javascript/studio/microphone_chip.js +1 -0
  17. data/app/javascript/studio/stimulus.js +3 -0
  18. data/app/javascript/studio/style_guide.js +2 -0
  19. data/app/javascript/studio/style_section_nav.js +16 -0
  20. data/app/javascript/studio/theme_editor.js +6 -106
  21. data/app/javascript/studio/voice_recorder.js +23 -2
  22. data/app/models/studio/user_session.rb +76 -0
  23. data/app/models/theme_setting.rb +3 -0
  24. data/app/views/studio/admin_models/_sessions_table.html.erb +28 -0
  25. data/app/views/studio/admin_models/show.html.erb +22 -11
  26. data/app/views/studio/agent_chat/_chat.html.erb +17 -2
  27. data/app/views/studio/agent_chat/_form.html.erb +3 -2
  28. data/app/views/studio/agent_chat/_voice.html.erb +54 -0
  29. data/app/views/studio/voice/_microphone_help.html.erb +6 -3
  30. data/app/views/style/_theme.html.erb +75 -12
  31. data/app/views/style/index.html.erb +28 -30
  32. data/db/migrate/20261103120000_create_studio_user_sessions.rb +16 -0
  33. data/docs/SITE_FOOTER.md +33 -0
  34. data/lib/studio/agent_chat.rb +83 -5
  35. data/lib/studio/site_footer.rb +45 -2
  36. data/lib/studio/ui_primitives.rb +4 -2
  37. data/lib/studio/version.rb +1 -1
  38. data/lib/studio.rb +64 -6
  39. metadata +11 -4
  40. data/app/views/studio/admin_models/index.html.erb +0 -31
  41. data/app/views/theme_settings/edit.html.erb +0 -304
@@ -1,304 +0,0 @@
1
- <%# The editor is studio/theme_editor's themeEditor factory, seeded with the
2
- server-resolved role colours. studio/alpine_shims publishes it. %>
3
- <div class="max-w-5xl mx-auto px-4 py-8" x-data="themeEditor(<%= @theme_setting.resolved_colors.to_json %>)">
4
- <div class="flex items-center justify-between mb-8">
5
- <h1 class="text-2xl font-bold text-heading">Theme</h1>
6
- <div class="flex gap-3">
7
- <%= form_tag("/admin/theme/regenerate", method: :post) do %>
8
- <button type="submit" class="btn btn-outline btn-sm">Regenerate Cache</button>
9
- <% end %>
10
- </div>
11
- </div>
12
-
13
- <%= form_tag("/admin/theme", method: :patch) do %>
14
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
15
- <!-- Color Inputs -->
16
- <div class="card p-6">
17
- <h2 class="text-lg font-bold text-heading mb-6">Role Colors</h2>
18
-
19
- <% ThemeSetting::ROLES.each do |role| %>
20
- <% db_col = ThemeSetting.db_column_for(role) %>
21
- <% db_val = @theme_setting.read_attribute(db_col) %>
22
- <% default_val = @defaults[role] %>
23
- <% current_val = db_val.presence || default_val %>
24
- <div class="flex items-center gap-4 mb-4">
25
- <input type="color"
26
- :value="colors.<%= role %>"
27
- @input="colors.<%= role %> = $event.target.value; updatePreview()"
28
- class="w-10 h-10 rounded cursor-pointer border border-subtle"
29
- style="padding: 1px;">
30
- <div class="flex-1">
31
- <div class="flex items-center gap-2">
32
- <span class="text-heading font-medium text-sm"><%= role.to_s.titleize %></span>
33
- <% if db_val.present? %>
34
- <span class="text-xs text-muted">(custom)</span>
35
- <% end %>
36
- </div>
37
- <input type="text"
38
- name="theme_setting[<%= db_col %>]"
39
- x-model="colors.<%= role %>"
40
- @input="updatePreview()"
41
- @blur="normalizeHex('<%= role %>')"
42
- class="text-xs text-secondary font-mono"
43
- style="background: transparent; border: none; border-bottom: 1px dashed currentColor; padding: 0; width: 5.5rem; outline: none;"
44
- spellcheck="false">
45
- </div>
46
- <% if default_val %>
47
- <button type="button"
48
- @click="resetColor('<%= role %>', '<%= default_val %>')"
49
- class="text-xs text-muted hover:text-heading transition"
50
- title="Reset to default (<%= default_val %>)">
51
- Reset
52
- </button>
53
- <% end %>
54
- </div>
55
- <% end %>
56
-
57
- <div class="flex gap-3 mt-6 pt-6 border-t border-subtle">
58
- <button type="submit" class="btn btn-primary">Save Theme</button>
59
- </div>
60
- </div>
61
-
62
- <!-- Live Preview -->
63
- <div class="space-y-6">
64
- <!-- Dark Preview -->
65
- <div class="rounded-xl border border-subtle overflow-hidden">
66
- <div class="px-4 py-2 bg-surface-alt">
67
- <span class="text-xs font-bold text-secondary uppercase tracking-wider">Dark Mode Preview</span>
68
- </div>
69
- <div class="p-6" :style="darkPreviewStyle()">
70
- <h3 class="font-bold mb-1" :style="{ color: '#ffffff' }">Page Heading</h3>
71
- <p class="text-sm mb-4" :style="{ color: '#e2e8f0' }">Body text on the page surface.</p>
72
- <div class="rounded-lg p-4 mb-4" :style="darkSurfaceStyle()">
73
- <p class="text-sm mb-3" :style="{ color: '#94a3b8' }">Card on surface with secondary text.</p>
74
- <div class="flex gap-2">
75
- <button class="btn btn-sm text-white rounded-lg px-3 py-1.5 text-xs font-bold" :style="{ backgroundColor: colors.primary }">Primary</button>
76
- <button class="btn btn-sm text-white rounded-lg px-3 py-1.5 text-xs font-bold" :style="{ backgroundColor: colors.success || '#367E3A' }">Accent</button>
77
- <button class="btn btn-sm text-white rounded-lg px-3 py-1.5 text-xs font-bold" :style="{ backgroundColor: colors.danger || '#EF4444' }">Danger</button>
78
- </div>
79
- </div>
80
- <div class="flex gap-2">
81
- <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium" :style="{ backgroundColor: colors.primary + '33', color: colors.primary, border: '1px solid ' + colors.primary + '55' }">Badge</span>
82
- <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium" :style="{ backgroundColor: (colors.success || '#367E3A') + '33', color: colors.success || '#367E3A', border: '1px solid ' + (colors.success || '#367E3A') + '55' }">Success</span>
83
- </div>
84
- </div>
85
- </div>
86
-
87
- <!-- Light Preview -->
88
- <div class="rounded-xl border border-subtle overflow-hidden">
89
- <div class="px-4 py-2 bg-surface-alt">
90
- <span class="text-xs font-bold text-secondary uppercase tracking-wider">Light Mode Preview</span>
91
- </div>
92
- <div class="p-6" :style="lightPreviewStyle()">
93
- <h3 class="font-bold mb-1" :style="{ color: '#0f172a' }">Page Heading</h3>
94
- <p class="text-sm mb-4" :style="{ color: '#334155' }">Body text on the page surface.</p>
95
- <div class="rounded-lg p-4 mb-4" :style="lightSurfaceStyle()">
96
- <p class="text-sm mb-3" :style="{ color: '#64748b' }">Card on surface with secondary text.</p>
97
- <div class="flex gap-2">
98
- <button class="btn btn-sm text-white rounded-lg px-3 py-1.5 text-xs font-bold" :style="{ backgroundColor: colors.primary }">Primary</button>
99
- <button class="btn btn-sm text-white rounded-lg px-3 py-1.5 text-xs font-bold" :style="{ backgroundColor: colors.success || '#367E3A' }">Accent</button>
100
- <button class="btn btn-sm text-white rounded-lg px-3 py-1.5 text-xs font-bold" :style="{ backgroundColor: colors.danger || '#EF4444' }">Danger</button>
101
- </div>
102
- </div>
103
- <div class="flex gap-2">
104
- <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium" :style="{ backgroundColor: colors.primary + '33', color: colors.primary, border: '1px solid ' + colors.primary + '55' }">Badge</span>
105
- <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium" :style="{ backgroundColor: (colors.success || '#367E3A') + '33', color: colors.success || '#367E3A', border: '1px solid ' + (colors.success || '#367E3A') + '55' }">Success</span>
106
- </div>
107
- </div>
108
- </div>
109
- </div>
110
- </div>
111
- <% end %>
112
-
113
- <%# ── Styleguide ── %>
114
- <div class="border-t border-subtle mt-12 pt-12 space-y-12">
115
-
116
- <% if Studio.theme_logos.any? %>
117
- <%# ── Logos ── %>
118
- <section>
119
- <h2 class="text-xl font-bold text-heading mb-4">Logos</h2>
120
- <div class="grid grid-cols-2 md:grid-cols-5 gap-4">
121
- <% Studio.theme_logos.each do |logo| %>
122
- <% file = logo.is_a?(Hash) ? logo[:file] : logo %>
123
- <% title = logo.is_a?(Hash) ? logo[:title] : file %>
124
- <div class="card p-4 space-y-3">
125
- <p class="label-upper"><%= title %></p>
126
- <div class="flex items-center justify-center w-20 h-20 rounded-lg" style="background-color: #ddd; background-image: linear-gradient(45deg, #777 25%, transparent 25%, transparent 75%, #777 75%), linear-gradient(45deg, #777 25%, transparent 25%, transparent 75%, #777 75%); background-size: 12px 12px; background-position: 0 0, 6px 6px;">
127
- <img src="/<%= file %>" alt="<%= title %>" class="max-w-16 max-h-16 rounded">
128
- </div>
129
- <p class="text-muted text-xs font-mono"><%= file %></p>
130
- </div>
131
- <% end %>
132
- </div>
133
- </section>
134
- <% end %>
135
-
136
- <%# ── Semantic Tokens ── %>
137
- <section>
138
- <h2 class="text-xl font-bold text-heading mb-4">Semantic Tokens</h2>
139
- <div class="grid grid-cols-2 md:grid-cols-5 gap-3">
140
- <%
141
- semantic_colors = [
142
- { name: "Page", utility: "bg-page", type: :bg },
143
- { name: "Surface", utility: "bg-surface", type: :bg },
144
- { name: "Surface Alt", utility: "bg-surface-alt", type: :bg },
145
- { name: "Inset", utility: "bg-inset", type: :bg },
146
- { name: "Heading", utility: "text-heading", type: :text },
147
- { name: "Body", utility: "text-body", type: :text },
148
- { name: "Secondary", utility: "text-secondary", type: :text },
149
- { name: "Muted", utility: "text-muted", type: :text },
150
- { name: "Border", utility: "border-subtle", type: :border },
151
- { name: "Border Strong", utility: "border-strong", type: :border },
152
- ]
153
- %>
154
- <% semantic_colors.each do |c| %>
155
- <div class="card p-3 flex items-center gap-3">
156
- <% if c[:type] == :bg %>
157
- <div class="w-8 h-8 rounded-full border border-subtle shrink-0 <%= c[:utility] %>"></div>
158
- <% elsif c[:type] == :text %>
159
- <div class="w-8 h-8 rounded-full border border-subtle shrink-0 bg-surface flex items-center justify-center">
160
- <span class="text-sm font-bold <%= c[:utility] %>">A</span>
161
- </div>
162
- <% else %>
163
- <div class="w-8 h-8 rounded-full shrink-0 border-2 <%= c[:utility] %> bg-transparent"></div>
164
- <% end %>
165
- <div class="min-w-0">
166
- <p class="text-heading text-sm font-semibold truncate"><%= c[:name] %></p>
167
- <p class="text-muted text-xs font-mono truncate"><%= c[:utility] %></p>
168
- </div>
169
- </div>
170
- <% end %>
171
- </div>
172
- </section>
173
-
174
- <%# ── Typography ── %>
175
- <section>
176
- <h2 class="text-xl font-bold text-heading mb-4">Typography</h2>
177
- <div class="card p-6 space-y-4">
178
- <p class="text-heading text-3xl font-extrabold">Heading — Montserrat 800</p>
179
- <p class="text-heading text-xl font-bold">Subheading — Montserrat 700</p>
180
- <p class="text-body text-base">Body text — The quick brown fox jumps over the lazy dog. Montserrat 400</p>
181
- <p class="text-secondary text-sm">Secondary text — Additional context and metadata. Montserrat 400</p>
182
- <p class="text-muted text-xs">Muted text — Timestamps, hints, and fine print. Montserrat 400</p>
183
- <hr class="border-subtle">
184
- <div class="flex flex-wrap gap-4">
185
- <% [400, 500, 600, 700, 800, 900].each do |w| %>
186
- <span class="text-heading text-lg" style="font-weight: <%= w %>"><%= w %></span>
187
- <% end %>
188
- </div>
189
- </div>
190
- </section>
191
-
192
- <%# ── Buttons ── %>
193
- <section>
194
- <h2 class="text-xl font-bold text-heading mb-4">Buttons</h2>
195
- <div class="card p-6 space-y-6">
196
- <div>
197
- <p class="label-upper mb-3">Primary</p>
198
- <div class="flex flex-wrap items-center gap-3">
199
- <button type="button" class="btn btn-primary btn-sm">Small</button>
200
- <button type="button" class="btn btn-primary">Medium</button>
201
- <button type="button" class="btn btn-primary btn-lg">Large</button>
202
- </div>
203
- </div>
204
- <div>
205
- <p class="label-upper mb-3">Secondary</p>
206
- <div class="flex flex-wrap items-center gap-3">
207
- <button type="button" class="btn btn-secondary btn-sm">Small</button>
208
- <button type="button" class="btn btn-secondary">Medium</button>
209
- <button type="button" class="btn btn-secondary btn-lg">Large</button>
210
- </div>
211
- </div>
212
- <div>
213
- <p class="label-upper mb-3">Outline</p>
214
- <div class="flex flex-wrap items-center gap-3">
215
- <button type="button" class="btn btn-outline btn-sm">Small</button>
216
- <button type="button" class="btn btn-outline">Medium</button>
217
- <button type="button" class="btn btn-outline btn-lg">Large</button>
218
- </div>
219
- </div>
220
- <div>
221
- <p class="label-upper mb-3">Danger</p>
222
- <div class="flex flex-wrap items-center gap-3">
223
- <button type="button" class="btn btn-danger btn-sm">Small</button>
224
- <button type="button" class="btn btn-danger">Medium</button>
225
- <button type="button" class="btn btn-danger btn-lg">Large</button>
226
- </div>
227
- </div>
228
- <div>
229
- <p class="label-upper mb-3">Google</p>
230
- <div class="flex flex-wrap items-center gap-3">
231
- <button type="button" class="btn btn-google">Sign in with Google</button>
232
- </div>
233
- </div>
234
- <div>
235
- <p class="label-upper mb-3">Disabled</p>
236
- <div class="flex flex-wrap items-center gap-3">
237
- <button type="button" class="btn btn-primary" disabled>Primary</button>
238
- <button type="button" class="btn btn-outline" disabled>Outline</button>
239
- <button type="button" class="btn btn-danger" disabled>Danger</button>
240
- </div>
241
- </div>
242
- </div>
243
- </section>
244
-
245
- <%# ── Components ── %>
246
- <section>
247
- <h2 class="text-xl font-bold text-heading mb-4">Components</h2>
248
- <div class="space-y-6">
249
- <div>
250
- <p class="label-upper mb-3">Cards</p>
251
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
252
- <div class="card p-6">
253
- <p class="text-heading font-semibold mb-1">.card</p>
254
- <p class="text-secondary text-sm">Static card with surface background and subtle border.</p>
255
- </div>
256
- <div class="card-hover p-6">
257
- <p class="text-heading font-semibold mb-1">.card-hover</p>
258
- <p class="text-secondary text-sm">Interactive card — hover to see the border glow.</p>
259
- </div>
260
- </div>
261
- </div>
262
-
263
- <div>
264
- <p class="label-upper mb-3">Badges</p>
265
- <div class="flex flex-wrap gap-2">
266
- <span class="badge bg-mint/10 text-mint border-mint/30">Mint</span>
267
- <span class="badge bg-violet/10 text-violet border-violet/30">Violet</span>
268
- <span class="badge bg-red-600/10 text-red-400 border-red-600/30">Red</span>
269
- <span class="badge bg-yellow-500/10 text-yellow-400 border-yellow-500/30">Yellow</span>
270
- <span class="badge bg-surface-alt text-secondary border-subtle">Gray</span>
271
- </div>
272
- </div>
273
-
274
- <div>
275
- <p class="label-upper mb-3">Input Field</p>
276
- <div class="max-w-md">
277
- <input type="text" class="input-field" placeholder="Placeholder text...">
278
- </div>
279
- </div>
280
-
281
- <div>
282
- <p class="label-upper mb-3">Empty State</p>
283
- <div class="empty-state">
284
- <p class="text-secondary">No items found.</p>
285
- <p class="text-muted text-sm mt-1">Try adjusting your search or filters.</p>
286
- </div>
287
- </div>
288
-
289
- <div>
290
- <p class="label-upper mb-3">Label Upper</p>
291
- <span class="label-upper">This is a .label-upper element</span>
292
- </div>
293
-
294
- <div>
295
- <p class="label-upper mb-3">JSON Debug</p>
296
- <div class="json-debug">
297
- <pre class="text-mint text-sm font-mono"><%= JSON.pretty_generate({ id: 1, name: "Example", status: "active", created_at: Time.current.iso8601 }) %></pre>
298
- </div>
299
- </div>
300
- </div>
301
- </section>
302
-
303
- </div>
304
- </div>