active_canvas 0.0.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.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +318 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/javascripts/active_canvas/editor/ai_panel.js +1607 -0
  6. data/app/assets/javascripts/active_canvas/editor/asset_manager.js +498 -0
  7. data/app/assets/javascripts/active_canvas/editor/blocks.js +1083 -0
  8. data/app/assets/javascripts/active_canvas/editor/code_panel.js +572 -0
  9. data/app/assets/javascripts/active_canvas/editor/component_toolbar.js +394 -0
  10. data/app/assets/javascripts/active_canvas/editor/panels.js +460 -0
  11. data/app/assets/javascripts/active_canvas/editor/utils.js +56 -0
  12. data/app/assets/javascripts/active_canvas/editor.js +295 -0
  13. data/app/assets/stylesheets/active_canvas/application.css +15 -0
  14. data/app/assets/stylesheets/active_canvas/editor.css +2929 -0
  15. data/app/controllers/active_canvas/admin/ai_controller.rb +181 -0
  16. data/app/controllers/active_canvas/admin/application_controller.rb +56 -0
  17. data/app/controllers/active_canvas/admin/media_controller.rb +61 -0
  18. data/app/controllers/active_canvas/admin/page_types_controller.rb +57 -0
  19. data/app/controllers/active_canvas/admin/page_versions_controller.rb +23 -0
  20. data/app/controllers/active_canvas/admin/pages_controller.rb +133 -0
  21. data/app/controllers/active_canvas/admin/partials_controller.rb +88 -0
  22. data/app/controllers/active_canvas/admin/settings_controller.rb +256 -0
  23. data/app/controllers/active_canvas/application_controller.rb +20 -0
  24. data/app/controllers/active_canvas/pages_controller.rb +18 -0
  25. data/app/controllers/concerns/active_canvas/current_user.rb +12 -0
  26. data/app/controllers/concerns/active_canvas/rate_limitable.rb +75 -0
  27. data/app/controllers/concerns/active_canvas/tailwind_compilation.rb +39 -0
  28. data/app/helpers/active_canvas/application_helper.rb +4 -0
  29. data/app/jobs/active_canvas/application_job.rb +4 -0
  30. data/app/jobs/active_canvas/compile_tailwind_job.rb +64 -0
  31. data/app/mailers/active_canvas/application_mailer.rb +6 -0
  32. data/app/models/active_canvas/ai_model.rb +136 -0
  33. data/app/models/active_canvas/application_record.rb +5 -0
  34. data/app/models/active_canvas/media.rb +141 -0
  35. data/app/models/active_canvas/page.rb +85 -0
  36. data/app/models/active_canvas/page_type.rb +22 -0
  37. data/app/models/active_canvas/page_version.rb +80 -0
  38. data/app/models/active_canvas/partial.rb +73 -0
  39. data/app/models/active_canvas/setting.rb +292 -0
  40. data/app/services/active_canvas/ai_configuration.rb +40 -0
  41. data/app/services/active_canvas/ai_models.rb +128 -0
  42. data/app/services/active_canvas/ai_service.rb +289 -0
  43. data/app/services/active_canvas/content_sanitizer.rb +112 -0
  44. data/app/services/active_canvas/tailwind_compiler.rb +156 -0
  45. data/app/views/active_canvas/admin/media/index.html.erb +401 -0
  46. data/app/views/active_canvas/admin/media/show.html.erb +297 -0
  47. data/app/views/active_canvas/admin/page_types/_form.html.erb +25 -0
  48. data/app/views/active_canvas/admin/page_types/edit.html.erb +13 -0
  49. data/app/views/active_canvas/admin/page_types/index.html.erb +29 -0
  50. data/app/views/active_canvas/admin/page_types/new.html.erb +9 -0
  51. data/app/views/active_canvas/admin/page_types/show.html.erb +18 -0
  52. data/app/views/active_canvas/admin/page_versions/show.html.erb +469 -0
  53. data/app/views/active_canvas/admin/pages/_form.html.erb +62 -0
  54. data/app/views/active_canvas/admin/pages/content.html.erb +139 -0
  55. data/app/views/active_canvas/admin/pages/edit.html.erb +335 -0
  56. data/app/views/active_canvas/admin/pages/editor.html.erb +710 -0
  57. data/app/views/active_canvas/admin/pages/index.html.erb +149 -0
  58. data/app/views/active_canvas/admin/pages/new.html.erb +19 -0
  59. data/app/views/active_canvas/admin/pages/show.html.erb +258 -0
  60. data/app/views/active_canvas/admin/pages/versions.html.erb +333 -0
  61. data/app/views/active_canvas/admin/partials/edit.html.erb +182 -0
  62. data/app/views/active_canvas/admin/partials/editor.html.erb +703 -0
  63. data/app/views/active_canvas/admin/partials/index.html.erb +131 -0
  64. data/app/views/active_canvas/admin/settings/show.html.erb +1864 -0
  65. data/app/views/active_canvas/pages/no_homepage.html.erb +45 -0
  66. data/app/views/active_canvas/pages/show.html.erb +113 -0
  67. data/app/views/layouts/active_canvas/admin/application.html.erb +960 -0
  68. data/app/views/layouts/active_canvas/admin/editor.html.erb +826 -0
  69. data/app/views/layouts/active_canvas/application.html.erb +55 -0
  70. data/config/routes.rb +48 -0
  71. data/db/migrate/20260202000001_create_active_canvas_tables.rb +113 -0
  72. data/db/migrate/20260202000002_create_active_canvas_ai_models.rb +26 -0
  73. data/lib/active_canvas/configuration.rb +232 -0
  74. data/lib/active_canvas/engine.rb +44 -0
  75. data/lib/active_canvas/version.rb +3 -0
  76. data/lib/active_canvas.rb +26 -0
  77. data/lib/generators/active_canvas/install/install_generator.rb +263 -0
  78. data/lib/generators/active_canvas/install/templates/initializer.rb.tt +163 -0
  79. data/lib/tasks/active_canvas_tasks.rake +69 -0
  80. metadata +150 -0
@@ -0,0 +1,703 @@
1
+ <!-- Editor Header -->
2
+ <header class="editor-header">
3
+ <div class="editor-header-left">
4
+ <%= link_to admin_partials_path, class: "editor-logo" do %>
5
+ <div class="editor-logo-icon">
6
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
7
+ <polygon points="12 2 2 7 12 12 22 7 12 2"/>
8
+ <polyline points="2 17 12 22 22 17"/>
9
+ <polyline points="2 12 12 17 22 12"/>
10
+ </svg>
11
+ </div>
12
+ <% end %>
13
+ <%= link_to admin_partials_path, class: "editor-back" do %>
14
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
15
+ <path d="m15 18-6-6 6-6"/>
16
+ </svg>
17
+ Back
18
+ <% end %>
19
+ <div class="header-separator"></div>
20
+ <div class="editor-title"><%= @partial.name %></div>
21
+ </div>
22
+
23
+ <div class="editor-header-center">
24
+ <button class="device-btn active" data-device="desktop" title="Desktop">
25
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
26
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
27
+ <line x1="8" y1="21" x2="16" y2="21"/>
28
+ <line x1="12" y1="17" x2="12" y2="21"/>
29
+ </svg>
30
+ </button>
31
+ <button class="device-btn" data-device="tablet" title="Tablet">
32
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
33
+ <rect x="4" y="2" width="16" height="20" rx="2" ry="2"/>
34
+ <line x1="12" y1="18" x2="12.01" y2="18"/>
35
+ </svg>
36
+ </button>
37
+ <button class="device-btn" data-device="mobile" title="Mobile">
38
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
39
+ <rect x="5" y="2" width="14" height="20" rx="2" ry="2"/>
40
+ <line x1="12" y1="18" x2="12.01" y2="18"/>
41
+ </svg>
42
+ </button>
43
+ </div>
44
+
45
+ <div class="editor-header-right">
46
+ <button class="header-btn header-btn-secondary" id="btn-toggle-left" title="Toggle Blocks Panel">
47
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
48
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
49
+ <line x1="9" y1="3" x2="9" y2="21"/>
50
+ </svg>
51
+ </button>
52
+ <button class="header-btn header-btn-secondary" id="btn-toggle-ai" title="Toggle AI Assistant">
53
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
54
+ <path d="M12 8V4H8"/>
55
+ <rect width="16" height="12" x="4" y="8" rx="2"/>
56
+ <path d="M2 14h2"/>
57
+ <path d="M20 14h2"/>
58
+ <path d="M15 13v2"/>
59
+ <path d="M9 13v2"/>
60
+ </svg>
61
+ </button>
62
+ <button class="header-btn header-btn-secondary" id="btn-toggle-right" title="Toggle Styles Panel">
63
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
64
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
65
+ <line x1="15" y1="3" x2="15" y2="21"/>
66
+ </svg>
67
+ </button>
68
+ <div class="header-separator"></div>
69
+ <button class="header-btn header-btn-secondary" id="btn-undo" title="Undo (Ctrl+Z)">
70
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
71
+ <path d="M3 7v6h6"/>
72
+ <path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/>
73
+ </svg>
74
+ </button>
75
+ <button class="header-btn header-btn-secondary" id="btn-redo" title="Redo (Ctrl+Y)">
76
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
77
+ <path d="M21 7v6h-6"/>
78
+ <path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7"/>
79
+ </svg>
80
+ </button>
81
+ <button class="header-btn header-btn-secondary" id="btn-code" title="View Code (Ctrl+E)">
82
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
83
+ <polyline points="16 18 22 12 16 6"/>
84
+ <polyline points="8 6 2 12 8 18"/>
85
+ </svg>
86
+ Code
87
+ </button>
88
+ <div class="theme-switcher">
89
+ <button class="header-btn header-btn-secondary" id="btn-theme" title="Change Theme">
90
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
91
+ <circle cx="12" cy="12" r="5"/>
92
+ <line x1="12" y1="1" x2="12" y2="3"/>
93
+ <line x1="12" y1="21" x2="12" y2="23"/>
94
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
95
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
96
+ <line x1="1" y1="12" x2="3" y2="12"/>
97
+ <line x1="21" y1="12" x2="23" y2="12"/>
98
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
99
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
100
+ </svg>
101
+ </button>
102
+ <div class="theme-dropdown" id="theme-dropdown">
103
+ <div class="theme-dropdown-header">Editor Theme</div>
104
+ <button class="theme-option" data-theme="dark">
105
+ <span class="theme-preview theme-preview-dark"></span>
106
+ <span class="theme-name">Dark</span>
107
+ <span class="theme-check">
108
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
109
+ <polyline points="20 6 9 17 4 12"/>
110
+ </svg>
111
+ </span>
112
+ </button>
113
+ <button class="theme-option" data-theme="midnight">
114
+ <span class="theme-preview theme-preview-midnight"></span>
115
+ <span class="theme-name">Midnight</span>
116
+ <span class="theme-check">
117
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
118
+ <polyline points="20 6 9 17 4 12"/>
119
+ </svg>
120
+ </span>
121
+ </button>
122
+ <button class="theme-option" data-theme="ocean">
123
+ <span class="theme-preview theme-preview-ocean"></span>
124
+ <span class="theme-name">Ocean</span>
125
+ <span class="theme-check">
126
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
127
+ <polyline points="20 6 9 17 4 12"/>
128
+ </svg>
129
+ </span>
130
+ </button>
131
+ <button class="theme-option" data-theme="charcoal">
132
+ <span class="theme-preview theme-preview-charcoal"></span>
133
+ <span class="theme-name">Charcoal</span>
134
+ <span class="theme-check">
135
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
136
+ <polyline points="20 6 9 17 4 12"/>
137
+ </svg>
138
+ </span>
139
+ </button>
140
+ <button class="theme-option" data-theme="light">
141
+ <span class="theme-preview theme-preview-light"></span>
142
+ <span class="theme-name">Light</span>
143
+ <span class="theme-check">
144
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
145
+ <polyline points="20 6 9 17 4 12"/>
146
+ </svg>
147
+ </span>
148
+ </button>
149
+ </div>
150
+ </div>
151
+ <div class="header-separator"></div>
152
+ <button class="header-btn header-btn-primary" id="btn-save">
153
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
154
+ <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
155
+ <polyline points="17 21 17 13 7 13 7 21"/>
156
+ <polyline points="7 3 7 8 15 8"/>
157
+ </svg>
158
+ Save
159
+ </button>
160
+ </div>
161
+ </header>
162
+
163
+ <!-- Main Editor -->
164
+ <div class="editor-wrapper">
165
+ <div class="editor-main">
166
+ <div class="editor-container">
167
+ <!-- Left Panel: Blocks -->
168
+ <div class="editor-panel-left collapsed" id="panel-left">
169
+ <div class="panel-tabs">
170
+ <button class="panel-tab active" data-panel="blocks">Blocks</button>
171
+ <button class="panel-tab" data-panel="assets">Assets</button>
172
+ <button class="panel-tab" data-panel="layers">Layers</button>
173
+ </div>
174
+ <div class="panel-content">
175
+ <div id="blocks-container"></div>
176
+ <div id="assets-container" style="display: none;">
177
+ <div class="assets-panel">
178
+ <div class="assets-toolbar">
179
+ <button class="assets-upload-btn" id="btn-upload-asset" title="Upload new image">
180
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
181
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
182
+ <polyline points="17 8 12 3 7 8"/>
183
+ <line x1="12" y1="3" x2="12" y2="15"/>
184
+ </svg>
185
+ Upload
186
+ </button>
187
+ <button class="assets-refresh-btn" id="btn-refresh-assets" title="Refresh assets">
188
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
189
+ <polyline points="23 4 23 10 17 10"/>
190
+ <polyline points="1 20 1 14 7 14"/>
191
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
192
+ </svg>
193
+ </button>
194
+ </div>
195
+ <div class="assets-grid" id="assets-grid">
196
+ <div class="assets-loading">Loading assets...</div>
197
+ </div>
198
+ <input type="file" id="asset-upload-input" accept="image/*" multiple style="display: none;">
199
+ </div>
200
+ </div>
201
+ <div id="layers-container" style="display: none;"></div>
202
+ </div>
203
+ </div>
204
+
205
+ <!-- AI Panel (separate from left panel) -->
206
+ <div class="editor-panel-ai collapsed" id="panel-ai">
207
+ <div class="ai-panel-header">
208
+ <div class="ai-panel-title">
209
+ <div class="ai-panel-icon">
210
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
211
+ <path d="M12 8V4H8"/>
212
+ <rect width="16" height="12" x="4" y="8" rx="2"/>
213
+ <path d="M2 14h2"/>
214
+ <path d="M20 14h2"/>
215
+ <path d="M15 13v2"/>
216
+ <path d="M9 13v2"/>
217
+ </svg>
218
+ </div>
219
+ <div class="ai-panel-title-text">
220
+ <span>AI Assistant</span>
221
+ <span class="ai-status-indicator" id="ai-status-badge"></span>
222
+ </div>
223
+ </div>
224
+ <button class="ai-panel-close" id="btn-close-ai" title="Close panel">
225
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
226
+ <line x1="18" y1="6" x2="6" y2="18"/>
227
+ <line x1="6" y1="6" x2="18" y2="18"/>
228
+ </svg>
229
+ </button>
230
+ </div>
231
+
232
+ <div class="ai-panel-body">
233
+ <!-- Not configured state -->
234
+ <div class="ai-empty-state" id="ai-not-configured" style="display: none;">
235
+ <div class="ai-empty-state-glow"></div>
236
+ <div class="ai-empty-state-icon">
237
+ <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
238
+ <path d="M12 8V4H8"/>
239
+ <rect width="16" height="12" x="4" y="8" rx="2"/>
240
+ <path d="M2 14h2"/>
241
+ <path d="M20 14h2"/>
242
+ <path d="M15 13v2"/>
243
+ <path d="M9 13v2"/>
244
+ </svg>
245
+ </div>
246
+ <h3>AI Not Configured</h3>
247
+ <p>Connect your API keys to unlock AI-powered content generation.</p>
248
+ <%= link_to admin_settings_path(tab: "ai"), class: "ai-empty-state-btn" do %>
249
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
250
+ <circle cx="12" cy="12" r="3"/>
251
+ <path d="M12 1v6m0 6v10"/>
252
+ <path d="m4.93 4.93 4.24 4.24m5.66 5.66 4.24 4.24"/>
253
+ <path d="M1 12h6m6 0h10"/>
254
+ <path d="m4.93 19.07 4.24-4.24m5.66-5.66 4.24-4.24"/>
255
+ </svg>
256
+ Configure AI
257
+ <% end %>
258
+ </div>
259
+
260
+ <!-- Feature Tabs -->
261
+ <div class="ai-tabs" id="ai-panel-tabs">
262
+ <button class="ai-tab active" data-tab="text">
263
+ <span class="ai-tab-icon">
264
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
265
+ <path d="M17 6.1H3"/>
266
+ <path d="M21 12.1H3"/>
267
+ <path d="M15.1 18H3"/>
268
+ </svg>
269
+ </span>
270
+ <span class="ai-tab-label">Generate</span>
271
+ </button>
272
+ <button class="ai-tab" data-tab="image">
273
+ <span class="ai-tab-icon">
274
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
275
+ <rect width="18" height="18" x="3" y="3" rx="2" ry="2"/>
276
+ <circle cx="9" cy="9" r="2"/>
277
+ <path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>
278
+ </svg>
279
+ </span>
280
+ <span class="ai-tab-label">Image</span>
281
+ </button>
282
+ <button class="ai-tab" data-tab="screenshot">
283
+ <span class="ai-tab-icon">
284
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
285
+ <path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/>
286
+ <circle cx="12" cy="13" r="3"/>
287
+ </svg>
288
+ </span>
289
+ <span class="ai-tab-label">Screenshot</span>
290
+ </button>
291
+ </div>
292
+
293
+ <!-- Text Generation Tab -->
294
+ <div class="ai-tab-panel active" data-tab="text">
295
+ <!-- Mode Toggle -->
296
+ <div class="ai-mode-toggle" id="ai-mode-selector">
297
+ <button class="ai-mode-btn active" data-mode="page">
298
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
299
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
300
+ <line x1="3" y1="9" x2="21" y2="9"/>
301
+ </svg>
302
+ Page
303
+ </button>
304
+ <button class="ai-mode-btn" data-mode="element">
305
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
306
+ <path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/>
307
+ <path d="M13 13l6 6"/>
308
+ </svg>
309
+ Element
310
+ </button>
311
+ <div class="ai-mode-slider"></div>
312
+ </div>
313
+
314
+ <div class="ai-element-indicator" id="ai-element-info" style="display: none;">
315
+ <span class="ai-element-label">Editing:</span>
316
+ <code id="ai-selected-element">No element selected</code>
317
+ </div>
318
+
319
+ <!-- Model Selector -->
320
+ <div class="ai-model-row">
321
+ <label class="ai-model-label">Model</label>
322
+ <div class="ai-model-picker" id="ai-text-model-picker" data-tab="text">
323
+ <button type="button" class="ai-model-picker-btn" aria-haspopup="listbox">
324
+ <span class="ai-model-picker-label">Loading...</span>
325
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="ai-select-chevron"><path d="m6 9 6 6 6-6"/></svg>
326
+ </button>
327
+ <div class="ai-model-picker-dropdown">
328
+ <input type="text" class="ai-model-picker-search" placeholder="Search models..." autocomplete="off" />
329
+ <ul class="ai-model-picker-list" role="listbox"></ul>
330
+ </div>
331
+ </div>
332
+ </div>
333
+
334
+ <!-- Conversation Area -->
335
+ <div class="ai-conversation" id="ai-text-conversation">
336
+ <div class="ai-conversation-empty">
337
+ <div class="ai-conversation-empty-icon">
338
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
339
+ <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
340
+ </svg>
341
+ </div>
342
+ <p>Describe what you want to create</p>
343
+ </div>
344
+ </div>
345
+
346
+ <!-- Response Output -->
347
+ <div class="ai-response-area" id="ai-text-output-wrapper" style="display: none;">
348
+ <div class="ai-response-header">
349
+ <div class="ai-response-avatar">
350
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
351
+ <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
352
+ </svg>
353
+ </div>
354
+ <span>AI Response</span>
355
+ <button class="ai-copy-btn" id="btn-ai-copy" title="Copy to clipboard">
356
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
357
+ <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
358
+ <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
359
+ </svg>
360
+ </button>
361
+ </div>
362
+ <div class="ai-response-content" id="ai-text-output"></div>
363
+ <div class="ai-response-actions">
364
+ <button class="ai-action-btn ai-action-primary" id="btn-ai-text-insert">
365
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
366
+ <polyline points="9 11 12 14 22 4"/>
367
+ <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
368
+ </svg>
369
+ Insert to Page
370
+ </button>
371
+ <button class="ai-action-btn" id="btn-ai-regenerate">
372
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
373
+ <path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
374
+ <path d="M21 3v5h-5"/>
375
+ </svg>
376
+ Regenerate
377
+ </button>
378
+ </div>
379
+ </div>
380
+
381
+ <!-- Prompt Input -->
382
+ <div class="ai-prompt-container">
383
+ <textarea id="ai-text-prompt" class="ai-prompt-input" placeholder="Create a hero section with..." rows="1"></textarea>
384
+ <button class="ai-send-btn" id="btn-ai-text-generate" disabled>
385
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
386
+ <path d="m5 12 7-7 7 7"/>
387
+ <path d="M12 19V5"/>
388
+ </svg>
389
+ </button>
390
+ </div>
391
+ <div class="ai-prompt-hint">
392
+ <kbd>Enter</kbd> to send <span class="ai-hint-separator">|</span> <kbd>Shift+Enter</kbd> for new line
393
+ </div>
394
+ </div>
395
+
396
+ <!-- Image Generation Tab -->
397
+ <div class="ai-tab-panel" data-tab="image">
398
+ <div class="ai-model-row">
399
+ <label class="ai-model-label">Model</label>
400
+ <div class="ai-model-picker" id="ai-image-model-picker" data-tab="image">
401
+ <button type="button" class="ai-model-picker-btn" aria-haspopup="listbox">
402
+ <span class="ai-model-picker-label">Loading...</span>
403
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="ai-select-chevron"><path d="m6 9 6 6 6-6"/></svg>
404
+ </button>
405
+ <div class="ai-model-picker-dropdown">
406
+ <input type="text" class="ai-model-picker-search" placeholder="Search models..." autocomplete="off" />
407
+ <ul class="ai-model-picker-list" role="listbox"></ul>
408
+ </div>
409
+ </div>
410
+ </div>
411
+
412
+ <!-- Image Output -->
413
+ <div class="ai-image-output" id="ai-image-output-wrapper" style="display: none;">
414
+ <div class="ai-image-preview" id="ai-image-output"></div>
415
+ <div class="ai-response-actions">
416
+ <button class="ai-action-btn ai-action-primary" id="btn-ai-image-insert">
417
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
418
+ <polyline points="9 11 12 14 22 4"/>
419
+ <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
420
+ </svg>
421
+ Insert Image
422
+ </button>
423
+ </div>
424
+ </div>
425
+
426
+ <div class="ai-image-empty" id="ai-image-empty">
427
+ <div class="ai-image-empty-icon">
428
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
429
+ <rect width="18" height="18" x="3" y="3" rx="2" ry="2"/>
430
+ <circle cx="9" cy="9" r="2"/>
431
+ <path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>
432
+ </svg>
433
+ </div>
434
+ <p>Generate images with AI</p>
435
+ </div>
436
+
437
+ <div class="ai-prompt-container">
438
+ <textarea id="ai-image-prompt" class="ai-prompt-input" placeholder="A minimalist illustration of..." rows="1"></textarea>
439
+ <button class="ai-send-btn" id="btn-ai-image-generate" disabled>
440
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
441
+ <path d="m5 12 7-7 7 7"/>
442
+ <path d="M12 19V5"/>
443
+ </svg>
444
+ </button>
445
+ </div>
446
+ </div>
447
+
448
+ <!-- Screenshot to Code Tab -->
449
+ <div class="ai-tab-panel" data-tab="screenshot">
450
+ <div class="ai-upload-zone" id="ai-screenshot-drop">
451
+ <input type="file" id="ai-screenshot-input" accept="image/*" class="ai-upload-input" />
452
+ <div class="ai-upload-content" id="ai-upload-placeholder">
453
+ <div class="ai-upload-icon">
454
+ <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
455
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
456
+ <polyline points="17 8 12 3 7 8"/>
457
+ <line x1="12" y1="3" x2="12" y2="15"/>
458
+ </svg>
459
+ </div>
460
+ <span class="ai-upload-text">Drop screenshot here</span>
461
+ <span class="ai-upload-subtext">or click to browse</span>
462
+ </div>
463
+ <div class="ai-upload-preview" id="ai-screenshot-preview"></div>
464
+ </div>
465
+
466
+ <div class="ai-screenshot-output" id="ai-screenshot-output-wrapper" style="display: none;">
467
+ <div class="ai-response-header">
468
+ <div class="ai-response-avatar">
469
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
470
+ <polyline points="16 18 22 12 16 6"/>
471
+ <polyline points="8 6 2 12 8 18"/>
472
+ </svg>
473
+ </div>
474
+ <span>Generated Code</span>
475
+ </div>
476
+ <div class="ai-response-content" id="ai-screenshot-output"></div>
477
+ <div class="ai-response-actions">
478
+ <button class="ai-action-btn ai-action-primary" id="btn-ai-screenshot-insert">
479
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
480
+ <polyline points="9 11 12 14 22 4"/>
481
+ <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
482
+ </svg>
483
+ Insert to Page
484
+ </button>
485
+ </div>
486
+ </div>
487
+
488
+ <div class="ai-model-row">
489
+ <label class="ai-model-label">Model</label>
490
+ <div class="ai-model-picker" id="ai-screenshot-model-picker" data-tab="screenshot">
491
+ <button type="button" class="ai-model-picker-btn" aria-haspopup="listbox">
492
+ <span class="ai-model-picker-label">Loading...</span>
493
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="ai-select-chevron"><path d="m6 9 6 6 6-6"/></svg>
494
+ </button>
495
+ <div class="ai-model-picker-dropdown">
496
+ <input type="text" class="ai-model-picker-search" placeholder="Search models..." autocomplete="off" />
497
+ <ul class="ai-model-picker-list" role="listbox"></ul>
498
+ </div>
499
+ </div>
500
+ </div>
501
+
502
+ <div class="ai-prompt-container">
503
+ <textarea id="ai-screenshot-prompt" class="ai-prompt-input" placeholder="Additional instructions (optional)..." rows="1"></textarea>
504
+ <button class="ai-send-btn" id="btn-ai-screenshot-convert" disabled>
505
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
506
+ <polyline points="16 18 22 12 16 6"/>
507
+ <polyline points="8 6 2 12 8 18"/>
508
+ </svg>
509
+ </button>
510
+ </div>
511
+ </div>
512
+ </div>
513
+ </div>
514
+
515
+ <!-- Canvas -->
516
+ <div class="editor-canvas">
517
+ <div id="gjs"></div>
518
+ <button class="add-section-btn" id="btn-add-section" title="Add a new section">
519
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
520
+ <line x1="12" y1="5" x2="12" y2="19"/>
521
+ <line x1="5" y1="12" x2="19" y2="12"/>
522
+ </svg>
523
+ Add Section
524
+ </button>
525
+ </div>
526
+
527
+ <!-- Right Panel: Styles/Settings -->
528
+ <div class="editor-panel-right collapsed" id="panel-right">
529
+ <div class="panel-tabs">
530
+ <button class="panel-tab active" data-panel="styles">Styles</button>
531
+ <button class="panel-tab" data-panel="settings">Settings</button>
532
+ </div>
533
+ <div class="panel-content">
534
+ <div id="styles-container"></div>
535
+ <div id="traits-container" style="display: none;"></div>
536
+ </div>
537
+ </div>
538
+ </div>
539
+ </div>
540
+
541
+ <!-- Bottom Panel: Code Editor -->
542
+ <div class="editor-code-panel" id="code-panel">
543
+ <div class="code-panel-header">
544
+ <div class="code-panel-tabs">
545
+ <button class="code-panel-tab active" data-type="html">HTML</button>
546
+ <button class="code-panel-tab" data-type="css">CSS</button>
547
+ <button class="code-panel-tab" data-type="js">JavaScript</button>
548
+ </div>
549
+ <div class="code-panel-mode" id="code-panel-mode" style="display: none;">
550
+ <span class="mode-badge">
551
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
552
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
553
+ </svg>
554
+ <span id="component-name">Component</span>
555
+ </span>
556
+ <button class="code-panel-btn" id="code-full-page" title="Switch to full page">Full Page</button>
557
+ </div>
558
+ <div class="code-panel-actions">
559
+ <span id="code-status" class="code-status synced">Synced</span>
560
+ <button class="code-panel-btn" id="code-format" title="Format (Ctrl+Shift+F)">Format</button>
561
+ <button class="code-panel-btn primary" id="code-apply" title="Apply (Ctrl+S)">Apply</button>
562
+ <button class="code-panel-btn" id="code-close" title="Close">
563
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
564
+ <line x1="18" y1="6" x2="6" y2="18"></line>
565
+ <line x1="6" y1="6" x2="18" y2="18"></line>
566
+ </svg>
567
+ </button>
568
+ </div>
569
+ </div>
570
+ <div class="code-panel-resize" id="code-panel-resize"></div>
571
+ <div class="code-panel-content">
572
+ <div id="monaco-html-container" class="monaco-container"></div>
573
+ <div id="monaco-css-container" class="monaco-container" style="display: none;"></div>
574
+ <div id="monaco-js-container" class="monaco-container" style="display: none;"></div>
575
+ </div>
576
+ </div>
577
+ </div>
578
+
579
+ <!-- Toast Notification -->
580
+ <div id="editor-toast" class="editor-toast"></div>
581
+
582
+ <!-- Loading Overlay -->
583
+ <div id="editor-loading" class="editor-loading hidden">
584
+ <div class="editor-loading-spinner"></div>
585
+ </div>
586
+
587
+ <%= stylesheet_link_tag "active_canvas/editor", media: "all" %>
588
+
589
+ <!-- Theme Initialization (runs immediately to prevent flash) -->
590
+ <script>
591
+ (function() {
592
+ const savedTheme = localStorage.getItem('activecanvas-editor-theme') || 'dark';
593
+ document.documentElement.setAttribute('data-theme', savedTheme);
594
+ })();
595
+ </script>
596
+
597
+ <!-- Editor Configuration -->
598
+ <script>
599
+ window.ActiveCanvasEditor = {
600
+ config: {
601
+ entityType: 'partial',
602
+ partialId: <%= @partial.id %>,
603
+ saveUrl: '<%= save_editor_admin_partial_path(@partial) %>',
604
+ mediaUrl: '<%= admin_media_path(format: :json) %>',
605
+ uploadUrl: '<%= admin_media_path %>',
606
+ content: <%= raw (@partial.content || '').to_json %>,
607
+ contentCss: <%= raw (@partial.content_css || '').to_json %>,
608
+ contentJs: <%= raw (@partial.content_js || '').to_json %>,
609
+ contentComponents: <%= raw (@partial.content_components || '').to_json %>,
610
+ cssFramework: '<%= ActiveCanvas::Setting.css_framework %>',
611
+ cssFrameworkUrl: <%= raw (ActiveCanvas::Setting.css_framework_url || '').to_json %>,
612
+ cssFrameworkType: '<%= ActiveCanvas::Setting.css_framework_type %>',
613
+ tailwindConfig: <%= raw ActiveCanvas::Setting.tailwind_config_js %>,
614
+ globalCss: <%= raw (ActiveCanvas::Setting.global_css || '').to_json %>,
615
+ globalJs: <%= raw (ActiveCanvas::Setting.global_js || '').to_json %>,
616
+ aiEndpoints: {
617
+ status: '<%= admin_ai_status_path %>',
618
+ models: '<%= admin_ai_models_path %>',
619
+ chat: '<%= admin_ai_chat_path %>',
620
+ image: '<%= admin_ai_image_path %>',
621
+ screenshot: '<%= admin_ai_screenshot_to_code_path %>'
622
+ }
623
+ }
624
+ };
625
+ </script>
626
+
627
+ <!-- Editor JavaScript Modules (order matters) -->
628
+ <%= javascript_include_tag "active_canvas/editor/utils" %>
629
+ <%= javascript_include_tag "active_canvas/editor/blocks" %>
630
+ <%= javascript_include_tag "active_canvas/editor/asset_manager" %>
631
+ <%= javascript_include_tag "active_canvas/editor/code_panel" %>
632
+ <%= javascript_include_tag "active_canvas/editor/component_toolbar" %>
633
+ <%= javascript_include_tag "active_canvas/editor/panels" %>
634
+ <%= javascript_include_tag "active_canvas/editor/ai_panel" %>
635
+ <%= javascript_include_tag "active_canvas/editor" %>
636
+
637
+ <!-- Theme Switcher JavaScript -->
638
+ <script>
639
+ document.addEventListener('DOMContentLoaded', function() {
640
+ // ==================== Theme Switcher ====================
641
+ const themeBtn = document.getElementById('btn-theme');
642
+ const themeDropdown = document.getElementById('theme-dropdown');
643
+ const themeOptions = document.querySelectorAll('.theme-option');
644
+ const THEME_STORAGE_KEY = 'activecanvas-editor-theme';
645
+
646
+ // Get current theme
647
+ function getCurrentTheme() {
648
+ return localStorage.getItem(THEME_STORAGE_KEY) || 'dark';
649
+ }
650
+
651
+ // Apply theme
652
+ function applyTheme(theme) {
653
+ document.documentElement.setAttribute('data-theme', theme);
654
+ localStorage.setItem(THEME_STORAGE_KEY, theme);
655
+
656
+ // Update active state in dropdown
657
+ themeOptions.forEach(option => {
658
+ option.classList.toggle('active', option.dataset.theme === theme);
659
+ });
660
+
661
+ // Update Monaco editors if they exist
662
+ if (window.ActiveCanvasEditor && window.ActiveCanvasEditor.setMonacoTheme) {
663
+ window.ActiveCanvasEditor.setMonacoTheme(theme);
664
+ }
665
+ }
666
+
667
+ // Initialize theme on load
668
+ applyTheme(getCurrentTheme());
669
+
670
+ // Toggle dropdown
671
+ themeBtn.addEventListener('click', function(e) {
672
+ e.stopPropagation();
673
+ themeDropdown.classList.toggle('open');
674
+ });
675
+
676
+ // Close dropdown when clicking outside
677
+ document.addEventListener('click', function(e) {
678
+ if (!themeDropdown.contains(e.target) && e.target !== themeBtn) {
679
+ themeDropdown.classList.remove('open');
680
+ }
681
+ });
682
+
683
+ // Handle theme selection
684
+ themeOptions.forEach(option => {
685
+ option.addEventListener('click', function() {
686
+ const theme = this.dataset.theme;
687
+ applyTheme(theme);
688
+ themeDropdown.classList.remove('open');
689
+
690
+ if (window.ActiveCanvasEditor && window.ActiveCanvasEditor.showToast) {
691
+ window.ActiveCanvasEditor.showToast(`Theme changed to ${this.querySelector('.theme-name').textContent}`, 'success');
692
+ }
693
+ });
694
+ });
695
+
696
+ // Close dropdown on Escape
697
+ document.addEventListener('keydown', function(e) {
698
+ if (e.key === 'Escape' && themeDropdown.classList.contains('open')) {
699
+ themeDropdown.classList.remove('open');
700
+ }
701
+ });
702
+ });
703
+ </script>