sage-rails 0.1.8 → 0.2.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.
- checksums.yaml +4 -4
- data/app/controllers/sage/base_controller.rb +8 -1
- data/app/controllers/sage/dashboards_controller.rb +6 -2
- data/app/controllers/sage/queries_controller.rb +6 -2
- data/app/helpers/sage/application_helper.rb +11 -2
- data/app/views/sage/dashboards/index.html.erb +1 -1
- data/app/views/sage/queries/_statement_box.html.erb +6 -0
- data/app/views/sage/queries/index.html.erb +1 -1
- data/app/views/sage/queries/new.html.erb +25 -42
- data/config/initializers/pagy.rb +17 -2
- data/lib/generators/sage/install/install_generator.rb +14 -7
- data/lib/sage/engine.rb +6 -1
- data/lib/sage/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '085941f32e26330f8fccca54aa1908dddd9c295e89157539a20a851bc020e98c'
|
|
4
|
+
data.tar.gz: 01f0c003a16fcac98c33cbfb0d53007947f99fb65972ab15db14dd8c9a0ce890
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ea7714fffba89cc55b14102e97203b8792918b91627b0ad59610279d71ed4b6edb08b8470ab87513a5e625ab30a2a7defbf63dbffec75557d756486b7bfa573c
|
|
7
|
+
data.tar.gz: 783b058656f060a52cc55177656bf06c46cb66390beaf214f8a7b8831018d941d514804d54046020603e220b2fbb0e735b5fed5d3ad95a61816aba8eeee7f634
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
module Sage
|
|
2
2
|
class BaseController < Blazer::BaseController
|
|
3
|
-
|
|
3
|
+
# Support both Pagy 9.x (Backend) and Pagy 43.x (Method)
|
|
4
|
+
if defined?(Pagy::Backend)
|
|
5
|
+
include Pagy::Backend # Pagy 9.x
|
|
6
|
+
elsif defined?(Pagy::Method)
|
|
7
|
+
include Pagy::Method # Pagy 43.x
|
|
8
|
+
else
|
|
9
|
+
raise "Pagy must be loaded before Sage::BaseController"
|
|
10
|
+
end
|
|
4
11
|
|
|
5
12
|
layout "sage/application"
|
|
6
13
|
helper Importmap::ImportmapTagsHelper
|
|
@@ -16,8 +16,12 @@ module Sage
|
|
|
16
16
|
@dashboards = @dashboards.where(creator_id: blazer_user.id).reorder(updated_at: :desc)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
# Apply pagination with Pagy
|
|
20
|
-
@pagy, @dashboards =
|
|
19
|
+
# Apply pagination with Pagy (support both 9.x and 43.x)
|
|
20
|
+
@pagy, @dashboards = if defined?(Pagy::Backend)
|
|
21
|
+
pagy(@dashboards) # Pagy 9.x
|
|
22
|
+
else
|
|
23
|
+
pagy(:offset, @dashboards) # Pagy 43.x
|
|
24
|
+
end
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
def new
|
|
@@ -23,8 +23,12 @@ module Sage
|
|
|
23
23
|
# Filter out private queries (starting with #) unless they belong to the current user
|
|
24
24
|
@queries = @queries.where("name NOT LIKE ? OR creator_id = ?", "#%", blazer_user.try(:id))
|
|
25
25
|
|
|
26
|
-
# Apply pagination with Pagy
|
|
27
|
-
@pagy, @queries =
|
|
26
|
+
# Apply pagination with Pagy (support both 9.x and 43.x)
|
|
27
|
+
@pagy, @queries = if defined?(Pagy::Backend)
|
|
28
|
+
pagy(@queries) # Pagy 9.x
|
|
29
|
+
else
|
|
30
|
+
pagy(:offset, @queries) # Pagy 43.x
|
|
31
|
+
end
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
def new
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
module Sage
|
|
2
2
|
module ApplicationHelper
|
|
3
|
-
include Pagy::Frontend
|
|
4
|
-
|
|
5
3
|
# Provide access to Sage engine routes
|
|
6
4
|
def sage
|
|
7
5
|
Sage::Engine.routes.url_helpers
|
|
@@ -26,5 +24,16 @@ module Sage
|
|
|
26
24
|
end
|
|
27
25
|
end
|
|
28
26
|
end
|
|
27
|
+
|
|
28
|
+
# Support both Pagy 9.x and 43.x
|
|
29
|
+
def pagy_navigation(pagy_object)
|
|
30
|
+
if defined?(Pagy::Backend)
|
|
31
|
+
# Pagy 9.x uses pagy_nav helper
|
|
32
|
+
pagy_nav(pagy_object)
|
|
33
|
+
else
|
|
34
|
+
# Pagy 43.x uses instance method
|
|
35
|
+
pagy_object.series_nav
|
|
36
|
+
end
|
|
37
|
+
end
|
|
29
38
|
end
|
|
30
39
|
end
|
|
@@ -26,6 +26,12 @@
|
|
|
26
26
|
outline: 2px solid rgba(66, 165, 245, 1);
|
|
27
27
|
outline-offset: 2px;
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
/* ACE Editor cursor styling - sharp vertical line */
|
|
31
|
+
.ace_cursor {
|
|
32
|
+
border-left: 2px solid !important;
|
|
33
|
+
border-radius: 0 !important;
|
|
34
|
+
}
|
|
29
35
|
</style>
|
|
30
36
|
|
|
31
37
|
<div id=<%= dom_id(query, 'statement-box') %> class='' style="position: relative; border-radius: 10px;">
|
|
@@ -198,6 +198,12 @@
|
|
|
198
198
|
text-overflow: ellipsis;
|
|
199
199
|
margin-bottom: 10px;
|
|
200
200
|
}
|
|
201
|
+
|
|
202
|
+
/* ACE Editor cursor styling - sharp vertical line */
|
|
203
|
+
.ace_cursor {
|
|
204
|
+
border-left: 2px solid !important;
|
|
205
|
+
border-radius: 0 !important;
|
|
206
|
+
}
|
|
201
207
|
</style>
|
|
202
208
|
|
|
203
209
|
<script>
|
|
@@ -251,12 +257,16 @@
|
|
|
251
257
|
}
|
|
252
258
|
}
|
|
253
259
|
|
|
254
|
-
|
|
260
|
+
let tabsInitialized = false;
|
|
261
|
+
|
|
262
|
+
function setupTabSwitching() {
|
|
255
263
|
const promptTab = document.getElementById('prompt-tab');
|
|
256
264
|
const sqlTab = document.getElementById('sql-tab');
|
|
257
265
|
const promptPage = document.getElementById('prompt-page');
|
|
258
266
|
const sqlPage = document.getElementById('sql-page');
|
|
259
267
|
|
|
268
|
+
if (!promptTab || !sqlTab || !promptPage || !sqlPage) return;
|
|
269
|
+
|
|
260
270
|
function switchToPromptMode() {
|
|
261
271
|
promptTab.classList.add('active');
|
|
262
272
|
sqlTab.classList.remove('active');
|
|
@@ -269,32 +279,29 @@
|
|
|
269
279
|
promptTab.classList.remove('active');
|
|
270
280
|
sqlPage.classList.add('active');
|
|
271
281
|
promptPage.classList.remove('active');
|
|
282
|
+
|
|
283
|
+
// Initialize ACE editor when switching to SQL mode
|
|
284
|
+
setTimeout(() => {
|
|
285
|
+
initializeAceEditor();
|
|
286
|
+
}, 100);
|
|
272
287
|
}
|
|
273
288
|
|
|
274
|
-
|
|
275
|
-
|
|
289
|
+
// Only attach listeners once
|
|
290
|
+
if (!tabsInitialized) {
|
|
291
|
+
promptTab.addEventListener('click', switchToPromptMode);
|
|
292
|
+
sqlTab.addEventListener('click', switchToSqlMode);
|
|
293
|
+
tabsInitialized = true;
|
|
294
|
+
}
|
|
276
295
|
|
|
277
296
|
// If we have fork parameters, automatically switch to SQL mode
|
|
278
297
|
const urlParams = new URLSearchParams(window.location.search);
|
|
279
298
|
if (urlParams.get('fork_query_id') || urlParams.get('name')) {
|
|
280
|
-
console.log('Fork detected, switching to SQL mode');
|
|
281
299
|
switchToSqlMode();
|
|
282
|
-
// Initialize ACE editor immediately for forks
|
|
283
|
-
setTimeout(() => {
|
|
284
|
-
console.log('Initializing ACE editor for fork');
|
|
285
|
-
initializeAceEditor();
|
|
286
|
-
}, 200);
|
|
287
300
|
}
|
|
288
|
-
}
|
|
301
|
+
}
|
|
289
302
|
|
|
290
|
-
|
|
291
|
-
document.addEventListener('
|
|
292
|
-
if (e.target.id === 'sql-tab') {
|
|
293
|
-
setTimeout(() => {
|
|
294
|
-
initializeAceEditor();
|
|
295
|
-
}, 100);
|
|
296
|
-
}
|
|
297
|
-
});
|
|
303
|
+
document.addEventListener('DOMContentLoaded', setupTabSwitching);
|
|
304
|
+
document.addEventListener('turbo:load', setupTabSwitching);
|
|
298
305
|
|
|
299
306
|
// Ensure form submission captures the editor value
|
|
300
307
|
document.addEventListener('submit', function(e) {
|
|
@@ -305,28 +312,4 @@
|
|
|
305
312
|
}
|
|
306
313
|
}
|
|
307
314
|
});
|
|
308
|
-
|
|
309
|
-
// Handle Turbo navigation for fork detection
|
|
310
|
-
document.addEventListener('turbo:load', function() {
|
|
311
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
312
|
-
if (urlParams.get('fork_query_id') || urlParams.get('name')) {
|
|
313
|
-
console.log('Fork detected via turbo:load, switching to SQL mode');
|
|
314
|
-
const promptTab = document.getElementById('prompt-tab');
|
|
315
|
-
const sqlTab = document.getElementById('sql-tab');
|
|
316
|
-
const promptPage = document.getElementById('prompt-page');
|
|
317
|
-
const sqlPage = document.getElementById('sql-page');
|
|
318
|
-
|
|
319
|
-
if (sqlTab && promptTab && sqlPage && promptPage) {
|
|
320
|
-
sqlTab.classList.add('active');
|
|
321
|
-
promptTab.classList.remove('active');
|
|
322
|
-
sqlPage.classList.add('active');
|
|
323
|
-
promptPage.classList.remove('active');
|
|
324
|
-
|
|
325
|
-
setTimeout(() => {
|
|
326
|
-
console.log('Initializing ACE editor for fork via turbo:load');
|
|
327
|
-
initializeAceEditor();
|
|
328
|
-
}, 200);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
315
|
</script>
|
data/config/initializers/pagy.rb
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
|
-
Pagy::DEFAULT
|
|
2
|
-
Pagy
|
|
1
|
+
# Handle both mutable and frozen Pagy::DEFAULT (frozen in Pagy 9.x and Rails 8.1.1+ / Ruby 3.4+)
|
|
2
|
+
# Also support both Pagy 9.x (items/overflow) and Pagy 43.x (limit/overflow)
|
|
3
|
+
|
|
4
|
+
# Pagy 43.x uses :limit instead of :items and it's already in DEFAULT
|
|
5
|
+
if Pagy::DEFAULT.key?(:limit)
|
|
6
|
+
# Pagy 43.x - DEFAULT already has :limit, just need to set overflow if not present
|
|
7
|
+
unless Pagy::DEFAULT.key?(:overflow)
|
|
8
|
+
new_defaults = Pagy::DEFAULT.merge(overflow: :last_page)
|
|
9
|
+
Pagy.send(:remove_const, :DEFAULT)
|
|
10
|
+
Pagy.const_set(:DEFAULT, new_defaults)
|
|
11
|
+
end
|
|
12
|
+
else
|
|
13
|
+
# Pagy 9.x - use :items and :overflow
|
|
14
|
+
new_defaults = Pagy::DEFAULT.merge(items: 10, overflow: :last_page)
|
|
15
|
+
Pagy.send(:remove_const, :DEFAULT)
|
|
16
|
+
Pagy.const_set(:DEFAULT, new_defaults)
|
|
17
|
+
end
|
|
@@ -50,8 +50,16 @@ module Sage
|
|
|
50
50
|
def create_migrations
|
|
51
51
|
say "Creating Sage database migrations...", :green
|
|
52
52
|
|
|
53
|
-
# Generate timestamp for migration
|
|
54
|
-
|
|
53
|
+
# Generate unique timestamp for migration by checking existing migrations
|
|
54
|
+
# and ensuring we don't create a duplicate timestamp
|
|
55
|
+
existing_timestamps = Dir.glob("db/migrate/*").map do |file|
|
|
56
|
+
File.basename(file).match(/^(\d+)_/)[1].to_i
|
|
57
|
+
end.compact
|
|
58
|
+
|
|
59
|
+
timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
|
60
|
+
while existing_timestamps.include?(timestamp)
|
|
61
|
+
timestamp += 1
|
|
62
|
+
end
|
|
55
63
|
|
|
56
64
|
# Create the migration file
|
|
57
65
|
migration_file = "db/migrate/#{timestamp}_create_sage_messages.rb"
|
|
@@ -86,14 +94,13 @@ module Sage
|
|
|
86
94
|
say "Sage installation complete!", :green
|
|
87
95
|
say "="*50, :green
|
|
88
96
|
say "\nNext steps:"
|
|
89
|
-
say "1. Run '
|
|
90
|
-
say "2.
|
|
91
|
-
say "3.
|
|
92
|
-
say "4. Visit #{root_url}sage to start using Sage"
|
|
97
|
+
say "1. Run 'rails db:migrate' to create Blazer tables"
|
|
98
|
+
say "2. Configure your AI service in config/initializers/sage.rb"
|
|
99
|
+
say "3. Visit #{root_url}sage to start using Sage"
|
|
93
100
|
say "\nFor AI integration, you'll need to:"
|
|
94
101
|
say "- Set up an Anthropic API key (or OpenAI if preferred)"
|
|
95
102
|
say "- Add the API key to Rails credentials or .env file"
|
|
96
|
-
say "-
|
|
103
|
+
say "- Leverage model scopes for better SQL inference"
|
|
97
104
|
end
|
|
98
105
|
|
|
99
106
|
private
|
data/lib/sage/engine.rb
CHANGED
|
@@ -51,9 +51,14 @@ module Sage
|
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
# Support both Pagy 9.x (Frontend) and Pagy 43.x (instance methods)
|
|
54
55
|
initializer "sage.pagy_helpers" do
|
|
55
56
|
ActiveSupport.on_load(:action_view) do
|
|
56
|
-
|
|
57
|
+
if defined?(Pagy::Frontend)
|
|
58
|
+
# Pagy 9.x uses Frontend module
|
|
59
|
+
include Pagy::Frontend
|
|
60
|
+
end
|
|
61
|
+
# Pagy 43.x uses instance methods on @pagy objects, no module needed
|
|
57
62
|
end
|
|
58
63
|
end
|
|
59
64
|
|
data/lib/sage/version.rb
CHANGED