glancer 1.0.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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +96 -0
  3. data/.rubocop.yml +54 -0
  4. data/CHANGELOG.md +88 -0
  5. data/CLAUDE.md +115 -0
  6. data/CODE_OF_CONDUCT.md +132 -0
  7. data/README.md +354 -0
  8. data/app/assets/config/glancer_manifest.js +1 -0
  9. data/app/assets/javascripts/glancer/application.js +15 -0
  10. data/app/assets/javascripts/glancer/controllers/chat_controller.js +101 -0
  11. data/app/assets/javascripts/glancer/controllers/message_controller.js +1052 -0
  12. data/app/assets/javascripts/glancer/controllers/toast_controller.js +63 -0
  13. data/app/assets/stylesheets/glancer/application.css +350 -0
  14. data/app/assets/stylesheets/glancer/code-blocks.css +6 -0
  15. data/app/assets/stylesheets/glancer/list.css +31 -0
  16. data/app/assets/stylesheets/glancer/scrollbar.css +16 -0
  17. data/app/assets/stylesheets/glancer/table.css +97 -0
  18. data/app/controllers/glancer/application_controller.rb +33 -0
  19. data/app/controllers/glancer/chats_controller.rb +49 -0
  20. data/app/controllers/glancer/messages_controller.rb +144 -0
  21. data/app/controllers/glancer/schema_controller.rb +29 -0
  22. data/app/controllers/glancer/settings_controller.rb +23 -0
  23. data/app/helpers/glancer/application_helper.rb +17 -0
  24. data/app/jobs/glancer/application_job.rb +6 -0
  25. data/app/jobs/glancer/process_message_job.rb +38 -0
  26. data/app/models/glancer/audit.rb +12 -0
  27. data/app/models/glancer/chat.rb +8 -0
  28. data/app/models/glancer/code_version.rb +12 -0
  29. data/app/models/glancer/embedding.rb +6 -0
  30. data/app/models/glancer/message.rb +25 -0
  31. data/app/models/glancer/setting.rb +23 -0
  32. data/app/models/glancer/sql_version.rb +6 -0
  33. data/app/views/glancer/_data/_importmap.json.erb +7 -0
  34. data/app/views/glancer/chats/_chat_sidebar.html.erb +2 -0
  35. data/app/views/glancer/chats/_show.html.erb +52 -0
  36. data/app/views/glancer/chats/_sidebar_chat_list.html.erb +30 -0
  37. data/app/views/glancer/chats/index.html.erb +10 -0
  38. data/app/views/glancer/chats/show.html.erb +1 -0
  39. data/app/views/glancer/messages/_data_table.html.erb +268 -0
  40. data/app/views/glancer/messages/_execution_error.html.erb +26 -0
  41. data/app/views/glancer/messages/_form.html.erb +93 -0
  42. data/app/views/glancer/messages/_message.html.erb +206 -0
  43. data/app/views/glancer/messages/_message_info.html.erb +176 -0
  44. data/app/views/glancer/messages/_temp_form.html.erb +100 -0
  45. data/app/views/glancer/messages/create.turbo_stream.erb +25 -0
  46. data/app/views/glancer/schema/show.html.erb +123 -0
  47. data/app/views/glancer/settings/show.html.erb +306 -0
  48. data/app/views/glancer/shared/_icons.html.erb +126 -0
  49. data/app/views/layouts/glancer/application.html.erb +234 -0
  50. data/config/locales/glancer.en.yml +90 -0
  51. data/config/locales/glancer.es.yml +90 -0
  52. data/config/locales/glancer.pt-BR.yml +90 -0
  53. data/config/routes.rb +20 -0
  54. data/db/migrate/20250629212642_create_glancer_audits.rb +19 -0
  55. data/db/migrate/20250629212643_create_glancer_chats.rb +10 -0
  56. data/db/migrate/20250629212645_create_glancer_embeddings.rb +17 -0
  57. data/db/migrate/20250629212647_create_glancer_messages.rb +29 -0
  58. data/db/migrate/20260513204129_add_user_edited_sql_to_glancer_messages.rb +11 -0
  59. data/db/migrate/20260513210647_create_glancer_sql_versions.rb +18 -0
  60. data/db/migrate/20260513210648_add_message_id_to_glancer_audits.rb +8 -0
  61. data/db/migrate/20260513220000_create_glancer_settings.rb +12 -0
  62. data/db/migrate/20260514083509_add_llm_model_to_glancer_messages.rb +9 -0
  63. data/db/migrate/20260523120000_rename_code_columns_in_glancer_messages.rb +8 -0
  64. data/db/migrate/20260523120001_rename_code_column_in_glancer_audits.rb +7 -0
  65. data/db/migrate/20260523120002_add_code_type_to_glancer_tables.rb +10 -0
  66. data/db/migrate/20260523120003_rename_glancer_sql_versions_to_code_versions.rb +8 -0
  67. data/db/migrate/20260523130000_add_enriched_question_to_glancer_messages.rb +7 -0
  68. data/db/migrate/20260524100000_add_status_to_glancer_messages.rb +9 -0
  69. data/lib/generators/glancer/install/install_generator.rb +74 -0
  70. data/lib/generators/glancer/install/templates/glancer.rb +227 -0
  71. data/lib/generators/glancer/install/templates/llm_context.glancer.md +51 -0
  72. data/lib/glancer/async_runner.rb +50 -0
  73. data/lib/glancer/chart_analyzer.rb +230 -0
  74. data/lib/glancer/configuration.rb +372 -0
  75. data/lib/glancer/engine.rb +90 -0
  76. data/lib/glancer/indexer/context_indexer.rb +58 -0
  77. data/lib/glancer/indexer/model_indexer.rb +64 -0
  78. data/lib/glancer/indexer/schema_indexer.rb +171 -0
  79. data/lib/glancer/indexer.rb +50 -0
  80. data/lib/glancer/retriever.rb +114 -0
  81. data/lib/glancer/utils/logger.rb +83 -0
  82. data/lib/glancer/utils/markdown_helper.rb +56 -0
  83. data/lib/glancer/utils/result_formatter.rb +25 -0
  84. data/lib/glancer/utils/table_stats.rb +18 -0
  85. data/lib/glancer/utils/transaction.rb +59 -0
  86. data/lib/glancer/version.rb +5 -0
  87. data/lib/glancer/workflow/ar_executor.rb +104 -0
  88. data/lib/glancer/workflow/ar_extractor.rb +25 -0
  89. data/lib/glancer/workflow/ar_prompt_builder.rb +64 -0
  90. data/lib/glancer/workflow/ar_sanitizer.rb +88 -0
  91. data/lib/glancer/workflow/builder.rb +129 -0
  92. data/lib/glancer/workflow/cache.rb +55 -0
  93. data/lib/glancer/workflow/executor.rb +72 -0
  94. data/lib/glancer/workflow/llm.rb +123 -0
  95. data/lib/glancer/workflow/prompt_builder.rb +143 -0
  96. data/lib/glancer/workflow/query_enricher.rb +117 -0
  97. data/lib/glancer/workflow/sql_extractor.rb +42 -0
  98. data/lib/glancer/workflow/sql_sanitizer.rb +42 -0
  99. data/lib/glancer/workflow/sql_validator.rb +67 -0
  100. data/lib/glancer/workflow.rb +158 -0
  101. data/lib/glancer.rb +50 -0
  102. data/lib/tasks/glancer/tailwind.rake +8 -0
  103. data/lib/tasks/glancer.rake +99 -0
  104. data/spec/glancer_spec.rb +62 -0
  105. data/spec/lib/glancer/async_runner_spec.rb +133 -0
  106. data/spec/lib/glancer/chart_analyzer_spec.rb +296 -0
  107. data/spec/lib/glancer/configuration_spec.rb +858 -0
  108. data/spec/lib/glancer/engine_spec.rb +209 -0
  109. data/spec/lib/glancer/indexer/context_indexer_spec.rb +96 -0
  110. data/spec/lib/glancer/indexer/model_indexer_spec.rb +103 -0
  111. data/spec/lib/glancer/indexer/schema_indexer_spec.rb +382 -0
  112. data/spec/lib/glancer/indexer_spec.rb +95 -0
  113. data/spec/lib/glancer/retriever_spec.rb +179 -0
  114. data/spec/lib/glancer/utils/logger_spec.rb +85 -0
  115. data/spec/lib/glancer/utils/markdown_helper_spec.rb +92 -0
  116. data/spec/lib/glancer/utils/result_formatter_spec.rb +73 -0
  117. data/spec/lib/glancer/utils/table_stats_spec.rb +34 -0
  118. data/spec/lib/glancer/utils/transaction_spec.rb +73 -0
  119. data/spec/lib/glancer/workflow/ar_executor_spec.rb +155 -0
  120. data/spec/lib/glancer/workflow/ar_extractor_spec.rb +50 -0
  121. data/spec/lib/glancer/workflow/ar_prompt_builder_spec.rb +79 -0
  122. data/spec/lib/glancer/workflow/ar_sanitizer_spec.rb +175 -0
  123. data/spec/lib/glancer/workflow/builder_spec.rb +204 -0
  124. data/spec/lib/glancer/workflow/cache_spec.rb +142 -0
  125. data/spec/lib/glancer/workflow/executor_spec.rb +149 -0
  126. data/spec/lib/glancer/workflow/llm_spec.rb +124 -0
  127. data/spec/lib/glancer/workflow/prompt_builder_spec.rb +196 -0
  128. data/spec/lib/glancer/workflow/query_enricher_spec.rb +184 -0
  129. data/spec/lib/glancer/workflow/sql_extractor_spec.rb +82 -0
  130. data/spec/lib/glancer/workflow/sql_sanitizer_spec.rb +98 -0
  131. data/spec/lib/glancer/workflow/sql_validator_spec.rb +166 -0
  132. data/spec/lib/glancer/workflow_spec.rb +308 -0
  133. data/spec/models/glancer/audit_spec.rb +82 -0
  134. data/spec/models/glancer/chat_spec.rb +60 -0
  135. data/spec/models/glancer/code_version_spec.rb +71 -0
  136. data/spec/models/glancer/embedding_spec.rb +73 -0
  137. data/spec/models/glancer/message_spec.rb +144 -0
  138. data/spec/models/glancer/setting_spec.rb +88 -0
  139. data/spec/models/glancer/sql_version_spec.rb +4 -0
  140. data/spec/spec_helper.rb +128 -0
  141. data/spec/support/schema.rb +55 -0
  142. metadata +255 -0
@@ -0,0 +1,234 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= I18n.locale %>" class="h-full" data-speech-lang="<%= Glancer::Setting.get('speech_language', default: 'auto') %>">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="turbo-cache-control" content="no-preview">
7
+ <meta name="description" content="Glancer — natural language database explorer powered by AI">
8
+ <meta name="theme-color" content="#9333ea">
9
+ <title>Glancer</title>
10
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%239333ea'/%3E%3Cellipse cx='16' cy='10' rx='8' ry='3' fill='none' stroke='white' stroke-width='1.5'/%3E%3Cpath d='M8 10v5c0 1.657 3.582 3 8 3s8-1.343 8-3v-5' fill='none' stroke='white' stroke-width='1.5'/%3E%3Cpath d='M8 15v5c0 1.657 3.582 3 8 3s8-1.343 8-3v-5' fill='none' stroke='white' stroke-width='1.5'/%3E%3C/svg%3E">
11
+ <%= csrf_meta_tags %>
12
+ <%= csp_meta_tag %>
13
+ <meta name="glancer-schema-path" content="<%= glancer.db_schema_path %>">
14
+
15
+ <%# Theme: runs before CSS to prevent flash of wrong theme %>
16
+ <script>
17
+ (function() {
18
+ var t = localStorage.getItem('glancer-theme') || 'dark';
19
+ document.documentElement.classList.toggle('dark', t === 'dark');
20
+ })();
21
+ </script>
22
+
23
+ <script src="https://cdn.tailwindcss.com"></script>
24
+ <script>
25
+ tailwind.config = {
26
+ darkMode: 'class',
27
+ theme: {
28
+ extend: {
29
+ colors: {
30
+ primary: {
31
+ '50': '#faf5ff',
32
+ '100': '#f3e8ff',
33
+ '200': '#e9d5ff',
34
+ '300': '#d8b4fe',
35
+ '400': '#c084fc',
36
+ '500': '#a855f7',
37
+ '600': '#9333ea',
38
+ '700': '#7e22ce',
39
+ '800': '#6b21a8',
40
+ '900': '#581c87',
41
+ '950': '#3b0764',
42
+ }
43
+ },
44
+ fontFamily: {
45
+ sans: ['Inter', 'system-ui', 'sans-serif'],
46
+ }
47
+ }
48
+ }
49
+ }
50
+ </script>
51
+
52
+ <link rel="preconnect" href="https://fonts.googleapis.com">
53
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
54
+
55
+ <%# Prism.js for SQL syntax highlighting — light theme; dark overrides are in application.css %>
56
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css">
57
+
58
+ <%= stylesheet_link_tag "glancer/application", media: "all", "data-turbo-track": "reload" %>
59
+ <%= stylesheet_link_tag "glancer/table", media: "all", "data-turbo-track": "reload" %>
60
+ <%= stylesheet_link_tag "glancer/list", media: "all", "data-turbo-track": "reload" %>
61
+ <%= stylesheet_link_tag "glancer/scrollbar", media: "all", "data-turbo-track": "reload" %>
62
+
63
+ <%= tag.script type: "importmap" do %>
64
+ <%= render partial: "glancer/_data/importmap", formats: [:json] %>
65
+ <% end %>
66
+ <%= tag.script type: "module", src: asset_path("glancer/application.js") %>
67
+ </head>
68
+
69
+ <body class="h-full overflow-hidden bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-gray-100 font-sans antialiased">
70
+
71
+ <%# SVG sprite — zero HTTP requests for icons %>
72
+ <%= render "glancer/shared/icons" %>
73
+
74
+ <div class="flex h-full" data-controller="chat">
75
+
76
+ <%# Mobile sidebar overlay %>
77
+ <div id="sidebar-overlay"
78
+ class="hidden fixed inset-0 z-20 bg-black/40 backdrop-blur-sm lg:hidden"
79
+ data-action="click->chat#closeSidebar"
80
+ aria-hidden="true"></div>
81
+
82
+ <%# Sidebar %>
83
+ <aside id="sidebar"
84
+ class="fixed inset-y-0 left-0 z-30 flex flex-col w-72 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 shadow-xl transform -translate-x-full transition-transform duration-200 ease-in-out lg:relative lg:translate-x-0 lg:w-64 lg:shadow-none flex-shrink-0"
85
+ aria-label="Chat navigation">
86
+
87
+ <%# Logo + desktop collapse toggle %>
88
+ <div class="flex items-center justify-between px-4 py-4 border-b border-gray-100 dark:border-gray-800">
89
+ <div class="flex items-center gap-2.5">
90
+ <div class="flex items-center justify-center w-7 h-7 rounded-lg bg-primary-600">
91
+ <svg class="w-4 h-4 text-white" aria-hidden="true"><use href="#icon-database"/></svg>
92
+ </div>
93
+ <span class="font-bold text-lg tracking-tight text-gray-900 dark:text-white">Glancer</span>
94
+ </div>
95
+ <button class="hidden lg:flex p-1 rounded-md text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
96
+ data-action="click->chat#toggleDesktopSidebar"
97
+ aria-label="<%= t('glancer.nav.collapse_sidebar') %>">
98
+ <svg class="w-4 h-4" aria-hidden="true"><use href="#icon-chevron-left"/></svg>
99
+ </button>
100
+ </div>
101
+
102
+ <%# New chat button %>
103
+ <div class="px-3 pt-3 pb-2">
104
+ <%= link_to glancer.root_path,
105
+ class: "flex items-center gap-2 w-full px-3 py-2 text-sm font-medium rounded-lg bg-primary-600 hover:bg-primary-700 active:bg-primary-800 text-white transition-colors",
106
+ data: { action: "click->chat#create", turbo: "false" },
107
+ aria: { label: t("glancer.nav.new_chat") } do %>
108
+ <svg class="w-4 h-4" aria-hidden="true"><use href="#icon-plus"/></svg>
109
+ <%= t("glancer.nav.new_chat") %>
110
+ <% end %>
111
+ </div>
112
+
113
+ <%# Chat list label %>
114
+ <div class="px-4 pb-1">
115
+ <p class="text-[10px] font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-500"><%= t("glancer.nav.recent_chats") %></p>
116
+ </div>
117
+
118
+ <%# Scrollable chat list %>
119
+ <%= render "glancer/chats/sidebar_chat_list", chats: @chats, chat: @chat %>
120
+
121
+ <%# Sidebar footer: schema + settings + theme toggle %>
122
+ <div class="px-3 py-3 border-t border-gray-100 dark:border-gray-800 flex-shrink-0 space-y-1">
123
+ <%= link_to glancer.db_schema_path,
124
+ class: "flex items-center gap-2 w-full px-3 py-2 text-sm rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors",
125
+ rel: "noopener",
126
+ aria: { label: t("glancer.nav.schema") } do %>
127
+ <svg class="w-4 h-4" aria-hidden="true"><use href="#icon-table"/></svg>
128
+ <%= t("glancer.nav.schema") %>
129
+ <% end %>
130
+ <%= link_to glancer.settings_path,
131
+ class: "flex items-center gap-2 w-full px-3 py-2 text-sm rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors",
132
+ data: { turbo: false },
133
+ aria: { label: t("glancer.nav.settings") } do %>
134
+ <svg class="w-4 h-4" aria-hidden="true"><use href="#icon-settings"/></svg>
135
+ <%= t("glancer.nav.settings") %>
136
+ <% end %>
137
+ <button
138
+ class="flex items-center gap-2 w-full px-3 py-2 text-sm rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
139
+ data-action="click->chat#toggleTheme"
140
+ aria-label="Toggle color theme">
141
+ <svg class="w-4 h-4 dark:hidden" aria-hidden="true"><use href="#icon-moon"/></svg>
142
+ <svg class="w-4 h-4 hidden dark:block" aria-hidden="true"><use href="#icon-sun"/></svg>
143
+ <span class="dark:hidden"><%= t("glancer.nav.dark_mode") %></span>
144
+ <span class="hidden dark:block"><%= t("glancer.nav.light_mode") %></span>
145
+ </button>
146
+ </div>
147
+ </aside>
148
+
149
+ <%# Main area %>
150
+ <div class="flex-1 flex flex-col min-w-0 min-h-0">
151
+
152
+ <%# Desktop sidebar expand button (visible only when sidebar is collapsed) %>
153
+ <div id="sidebar-expand-btn"
154
+ class="hidden fixed top-4 left-4 z-20"
155
+ aria-hidden="true">
156
+ <button class="p-1.5 rounded-lg bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 shadow-sm text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
157
+ data-action="click->chat#toggleDesktopSidebar"
158
+ aria-label="Expandir menu">
159
+ <svg class="w-4 h-4" aria-hidden="true"><use href="#icon-chevron-right"/></svg>
160
+ </button>
161
+ </div>
162
+
163
+ <%# Mobile header %>
164
+ <header class="flex items-center gap-3 px-4 py-3 border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 flex-shrink-0 lg:hidden">
165
+ <button
166
+ class="p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
167
+ data-action="click->chat#openSidebar"
168
+ aria-label="Open menu"
169
+ aria-expanded="false"
170
+ aria-controls="sidebar">
171
+ <svg class="w-5 h-5" aria-hidden="true"><use href="#icon-menu"/></svg>
172
+ </button>
173
+ <div class="flex items-center gap-2">
174
+ <div class="flex items-center justify-center w-5 h-5 rounded bg-primary-600">
175
+ <svg class="w-3 h-3 text-white" aria-hidden="true"><use href="#icon-database"/></svg>
176
+ </div>
177
+ <span class="font-semibold text-sm text-gray-900 dark:text-white">Glancer</span>
178
+ </div>
179
+ </header>
180
+
181
+ <%# Chat content area %>
182
+ <div id="main-content" class="flex-1 flex flex-col min-h-0">
183
+ <% if content_for?(:main_content) %>
184
+ <%= yield :main_content %>
185
+ <% else %>
186
+ <%= render "glancer/chats/show",
187
+ chat: @chat,
188
+ chats: @chats,
189
+ messages: @chat ? @chat.messages.order(:created_at) : [] %>
190
+ <% end %>
191
+ </div>
192
+ </div>
193
+ </div>
194
+
195
+ <%# Toast notification container %>
196
+ <div id="toast-container"
197
+ class="fixed bottom-4 right-4 z-50 flex flex-col-reverse gap-2 pointer-events-none"
198
+ data-controller="toast"
199
+ aria-live="polite"
200
+ aria-atomic="false">
201
+ </div>
202
+
203
+ <%# Slide-out message info panel %>
204
+ <%= render "glancer/messages/message_info", message_info: nil %>
205
+
206
+ <%# Prism.js: syntax highlighting — loaded WITHOUT defer so they execute before
207
+ Stimulus controllers connect (which call highlightCode on connect). %>
208
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
209
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-sql.min.js"></script>
210
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-ruby.min.js"></script>
211
+ <script>
212
+ // Highlight on initial full-page load and every Turbo navigation
213
+ document.addEventListener("DOMContentLoaded", function() { Prism.highlightAll(); });
214
+ document.addEventListener("turbo:render", function() { Prism.highlightAll(); });
215
+
216
+ // Upgrade @mention <a> tags with the correct schema URL (href="#" at render time).
217
+ // Exposed on window so Stimulus controllers can call it after Turbo Stream renders.
218
+ window.glancerUpgradeMentions = function glancerUpgradeMentions() {
219
+ var base = document.querySelector("meta[name='glancer-schema-path']")?.content;
220
+ if (!base) return;
221
+ document.querySelectorAll("a.glancer-mention[data-table]").forEach(function(el) {
222
+ el.href = base + "?table=" + encodeURIComponent(el.dataset.table);
223
+ el.target = "_blank";
224
+ el.rel = "noopener noreferrer";
225
+ });
226
+ }
227
+ document.addEventListener("DOMContentLoaded", window.glancerUpgradeMentions);
228
+ document.addEventListener("turbo:render", window.glancerUpgradeMentions);
229
+ </script>
230
+
231
+ <%# Chart.js for query result visualizations %>
232
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js" defer></script>
233
+ </body>
234
+ </html>
@@ -0,0 +1,90 @@
1
+ en:
2
+ glancer:
3
+ nav:
4
+ new_chat: "New chat"
5
+ schema: "Schema"
6
+ settings: "Settings"
7
+ back_to_chat: "Back to chat"
8
+ collapse_sidebar: "Collapse menu"
9
+ expand_sidebar: "Expand menu"
10
+ dark_mode: "Dark mode"
11
+ light_mode: "Light mode"
12
+ recent_chats: "Recent Chats"
13
+ chat:
14
+ empty_title: "Ready to query"
15
+ empty_subtitle: "Ask anything about your database in natural language."
16
+ placeholder: "Ask anything about your database..."
17
+ hint: "Enter to send · Shift+Enter for new line"
18
+ mention_tip: "mention a table"
19
+ mention_tip_title: "Type @ to mention a specific table and guide the query"
20
+ send: "Send message"
21
+ record: "Record audio"
22
+ messages:
23
+ copy: "Copy message"
24
+ copy_response: "Copy response"
25
+ copy_sql: "Copy SQL"
26
+ edit_sql: "Edit SQL"
27
+ run: "Run"
28
+ save_run: "Save & Run"
29
+ csv: "CSV"
30
+ open_blazer: "Blazer"
31
+ details: "Message details"
32
+ edited_badge: "User edited"
33
+ no_results: "No results returned."
34
+ large_warning: "Large result (%{count} rows). Consider adding LIMIT."
35
+ no_limit_warning: "The query does not have an explicit LIMIT. Very big results can be slow."
36
+ row: "row"
37
+ rows: "rows"
38
+ ran_at: "Ran at %{time}"
39
+ execution_failed: "Execution failed"
40
+ info_panel:
41
+ title: "Message details"
42
+ user_question: "User question"
43
+ enriched_question: "Enriched question"
44
+ current_sql: "Current SQL"
45
+ current_code: "Generated Code"
46
+ user_edited: "User edited"
47
+ code_versions: "Code Versions (%{count})"
48
+ execution_history: "Execution History (%{count})"
49
+ status: "Status"
50
+ created_at: "Created"
51
+ success: "Query generated successfully"
52
+ failed: "Generation failed"
53
+ generated: "Generated"
54
+ user: "User"
55
+ close: "Close panel"
56
+ workflow_steps:
57
+ enriching: "Enriching question…"
58
+ retrieving_context: "Retrieving context…"
59
+ generating_code: "Generating query…"
60
+ validating: "Validating query…"
61
+ executing: "Executing on database…"
62
+ humanizing: "Preparing response…"
63
+ schema:
64
+ title: "Database schema"
65
+ tables_count: "%{count} indexed tables"
66
+ textual: "Textual"
67
+ erd: "ERD"
68
+ filter: "Filter tables..."
69
+ no_tables: "No tables found for \"%{term}\"."
70
+ cols: "cols"
71
+ back: "Back to chat"
72
+ settings:
73
+ title: "Settings"
74
+ saved: "Settings saved!"
75
+ language:
76
+ label: "Interface language"
77
+ en: "English"
78
+ pt_br: "Português (Brasil)"
79
+ es: "Español"
80
+ speech:
81
+ label: "Speech recognition language"
82
+ auto: "Auto (browser language)"
83
+ en_us: "English (US)"
84
+ pt_br: "Português (Brasil)"
85
+ es_es: "Español (ES)"
86
+ instructions:
87
+ label: "Custom LLM instructions"
88
+ help: "Additional context or rules appended to every LLM prompt."
89
+ placeholder: "e.g. Always use table aliases. Prefer CTEs over subqueries."
90
+ save: "Save settings"
@@ -0,0 +1,90 @@
1
+ es:
2
+ glancer:
3
+ nav:
4
+ new_chat: "Nuevo chat"
5
+ schema: "Esquema"
6
+ settings: "Configuración"
7
+ back_to_chat: "Volver al chat"
8
+ collapse_sidebar: "Cerrar menú"
9
+ expand_sidebar: "Abrir menú"
10
+ dark_mode: "Modo oscuro"
11
+ light_mode: "Modo claro"
12
+ recent_chats: "Conversaciones Recientes"
13
+ chat:
14
+ empty_title: "Listo para consultar"
15
+ empty_subtitle: "Pregunta cualquier cosa sobre tu base de datos en lenguaje natural."
16
+ placeholder: "Pregunta cualquier cosa sobre tu base de datos..."
17
+ hint: "Enter para enviar · Shift+Enter para nueva línea"
18
+ mention_tip: "menciona una tabla"
19
+ mention_tip_title: "Escribe @ para mencionar una tabla y guiar la consulta"
20
+ send: "Enviar mensaje"
21
+ record: "Grabar audio"
22
+ messages:
23
+ copy: "Copiar mensaje"
24
+ copy_response: "Copiar respuesta"
25
+ copy_sql: "Copiar SQL"
26
+ edit_sql: "Editar SQL"
27
+ run: "Ejecutar"
28
+ save_run: "Guardar & Ejecutar"
29
+ csv: "CSV"
30
+ open_blazer: "Blazer"
31
+ details: "Detalles del mensaje"
32
+ edited_badge: "Editado por usuario"
33
+ no_results: "No se encontraron resultados."
34
+ large_warning: "Resultado grande (%{count} filas). Considera agregar LIMIT."
35
+ no_limit_warning: "A consulta não possui LIMIT explícito. Resultados muito grandes podem ser lentos."
36
+ row: "fila"
37
+ rows: "filas"
38
+ ran_at: "Ejecutado a las %{time}"
39
+ execution_failed: "Error de ejecución"
40
+ info_panel:
41
+ title: "Detalles del mensaje"
42
+ user_question: "Pregunta del usuario"
43
+ enriched_question: "Pregunta enriquecida"
44
+ current_sql: "SQL actual"
45
+ current_code: "Código generado"
46
+ user_edited: "Editado por usuario"
47
+ code_versions: "Versiones de Código (%{count})"
48
+ execution_history: "Historial de ejecución (%{count})"
49
+ status: "Estado"
50
+ created_at: "Creado"
51
+ success: "Consulta generada con éxito"
52
+ failed: "Error en la generación"
53
+ generated: "Generado"
54
+ user: "Usuario"
55
+ close: "Cerrar panel"
56
+ workflow_steps:
57
+ enriching: "Enriqueciendo la pregunta…"
58
+ retrieving_context: "Recuperando contexto…"
59
+ generating_code: "Generando consulta…"
60
+ validating: "Validando consulta…"
61
+ executing: "Ejecutando en la base de datos…"
62
+ humanizing: "Preparando respuesta…"
63
+ schema:
64
+ title: "Esquema de base de datos"
65
+ tables_count: "%{count} tablas indexadas"
66
+ textual: "Textual"
67
+ erd: "ERD"
68
+ filter: "Filtrar tablas..."
69
+ no_tables: "No se encontraron tablas para \"%{term}\"."
70
+ cols: "cols"
71
+ back: "Volver al chat"
72
+ settings:
73
+ title: "Configuración"
74
+ saved: "¡Configuración guardada!"
75
+ language:
76
+ label: "Idioma de la interfaz"
77
+ en: "English"
78
+ pt_br: "Português (Brasil)"
79
+ es: "Español"
80
+ speech:
81
+ label: "Idioma de reconocimiento de voz"
82
+ auto: "Automático (idioma del navegador)"
83
+ en_us: "English (US)"
84
+ pt_br: "Português (Brasil)"
85
+ es_es: "Español (ES)"
86
+ instructions:
87
+ label: "Instrucciones personalizadas para el LLM"
88
+ help: "Contexto o reglas adicionales añadidas a cada prompt del LLM."
89
+ placeholder: "Ej: Siempre usa alias en las tablas. Prefiere CTEs a subconsultas."
90
+ save: "Guardar configuración"
@@ -0,0 +1,90 @@
1
+ pt-BR:
2
+ glancer:
3
+ nav:
4
+ new_chat: "Novo chat"
5
+ schema: "Schema"
6
+ settings: "Configurações"
7
+ back_to_chat: "Voltar ao chat"
8
+ collapse_sidebar: "Recolher menu"
9
+ expand_sidebar: "Expandir menu"
10
+ dark_mode: "Modo escuro"
11
+ light_mode: "Modo claro"
12
+ recent_chats: "Conversas Recentes"
13
+ chat:
14
+ empty_title: "Pronto para consultar"
15
+ empty_subtitle: "Pergunte qualquer coisa sobre seu banco de dados em linguagem natural."
16
+ placeholder: "Pergunte qualquer coisa sobre seu banco de dados..."
17
+ hint: "Enter para enviar · Shift+Enter para nova linha"
18
+ mention_tip: "mencione uma tabela"
19
+ mention_tip_title: "Digite @ para mencionar uma tabela e direcionar a consulta"
20
+ send: "Enviar mensagem"
21
+ record: "Gravar áudio"
22
+ messages:
23
+ copy: "Copiar mensagem"
24
+ copy_response: "Copiar resposta"
25
+ copy_sql: "Copiar SQL"
26
+ edit_sql: "Editar SQL"
27
+ run: "Executar"
28
+ save_run: "Salvar & Executar"
29
+ csv: "CSV"
30
+ open_blazer: "Blazer"
31
+ details: "Detalhes da mensagem"
32
+ edited_badge: "Editado pelo usuário"
33
+ no_results: "Nenhum resultado retornado."
34
+ large_warning: "Resultado grande (%{count} linhas). Considere adicionar LIMIT."
35
+ no_limit_warning: "A consulta não possui LIMIT explicito. Resultados muito grandes podem ser lentos."
36
+ row: "linha"
37
+ rows: "linhas"
38
+ ran_at: "Executado às %{time}"
39
+ execution_failed: "Falha na execução"
40
+ info_panel:
41
+ title: "Detalhes da mensagem"
42
+ user_question: "Pergunta do usuário"
43
+ enriched_question: "Pergunta enriquecida"
44
+ current_sql: "SQL Atual"
45
+ current_code: "Código Gerado"
46
+ user_edited: "Editado pelo usuário"
47
+ code_versions: "Versões de Código (%{count})"
48
+ execution_history: "Histórico de Execução (%{count})"
49
+ status: "Status"
50
+ created_at: "Criado em"
51
+ success: "Consulta gerada com sucesso"
52
+ failed: "Falha na geração"
53
+ generated: "Gerado"
54
+ user: "Usuário"
55
+ close: "Fechar painel"
56
+ workflow_steps:
57
+ enriching: "Enriquecendo a pergunta…"
58
+ retrieving_context: "Buscando contexto…"
59
+ generating_code: "Gerando consulta…"
60
+ validating: "Validando consulta…"
61
+ executing: "Executando no banco…"
62
+ humanizing: "Preparando resposta…"
63
+ schema:
64
+ title: "Schema do banco de dados"
65
+ tables_count: "%{count} tabelas indexadas"
66
+ textual: "Textual"
67
+ erd: "ERD"
68
+ filter: "Filtrar tabelas..."
69
+ no_tables: "Nenhuma tabela encontrada para \"%{term}\"."
70
+ cols: "cols"
71
+ back: "Voltar ao chat"
72
+ settings:
73
+ title: "Configurações"
74
+ saved: "Configurações salvas!"
75
+ language:
76
+ label: "Idioma da interface"
77
+ en: "English"
78
+ pt_br: "Português (Brasil)"
79
+ es: "Español"
80
+ speech:
81
+ label: "Idioma do reconhecimento de voz"
82
+ auto: "Automático (idioma do navegador)"
83
+ en_us: "English (US)"
84
+ pt_br: "Português (Brasil)"
85
+ es_es: "Español (ES)"
86
+ instructions:
87
+ label: "Instruções personalizadas para o LLM"
88
+ help: "Contexto ou regras adicionais anexados a cada prompt do LLM."
89
+ placeholder: "Ex: Sempre use aliases nas tabelas. Prefira CTEs a subqueries."
90
+ save: "Salvar configurações"
data/config/routes.rb ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ Glancer::Engine.routes.draw do
4
+ root to: "chats#index"
5
+
6
+ resources :chats, only: %i[index show destroy] do
7
+ resources :messages, only: [:create]
8
+ end
9
+
10
+ post "/start", to: "chats#start", as: :start_session
11
+
12
+ get "/messages/:id/info", to: "messages#message_info", as: :message_info
13
+ get "/messages/:id/poll", to: "messages#poll", as: :poll_message
14
+ post "/messages/:id/run_code", to: "messages#run_code", as: :run_message_code
15
+ post "/messages/:id/open_in_blazer", to: "messages#open_in_blazer", as: :open_message_in_blazer
16
+
17
+ get "/db-schema", to: "schema#show", as: :db_schema
18
+ get "/settings", to: "settings#show", as: :settings
19
+ patch "/settings", to: "settings#update"
20
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateGlancerAudits < ActiveRecord::Migration[6.1]
4
+ def change
5
+ create_table :glancer_audits do |t|
6
+ t.text :question
7
+ t.text :code, null: false
8
+ t.string :code_type, null: false, default: "sql"
9
+ t.string :adapter, null: false
10
+ t.string :run_id, null: false
11
+ t.datetime :executed_at, null: false
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :glancer_audits, :run_id, unique: true
17
+ add_index :glancer_audits, :executed_at
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateGlancerChats < ActiveRecord::Migration[6.1]
4
+ def change
5
+ create_table :glancer_chats do |t|
6
+ t.string :title
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateGlancerEmbeddings < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :glancer_embeddings do |t|
6
+ t.text :content, null: false
7
+ if ActiveRecord::Base.connection.adapter_name.downcase.include?("postgresql")
8
+ t.jsonb :embedding, null: false
9
+ else
10
+ t.json :embedding, null: false
11
+ end
12
+ t.string :source_type
13
+ t.string :source_path
14
+ t.timestamps
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # 20250629212644_create_glancer_messages.rb
4
+ class CreateGlancerMessages < ActiveRecord::Migration[6.1]
5
+ def change
6
+ # MySQL requires removing FKs from dependent tables before dropping the referenced table
7
+ if table_exists?(:glancer_sql_versions) && foreign_key_exists?(:glancer_sql_versions, :glancer_messages)
8
+ remove_foreign_key :glancer_sql_versions, :glancer_messages
9
+ end
10
+ if table_exists?(:glancer_code_versions) && foreign_key_exists?(:glancer_code_versions, :glancer_messages)
11
+ remove_foreign_key :glancer_code_versions, :glancer_messages
12
+ end
13
+
14
+ drop_table :glancer_messages, if_exists: true
15
+
16
+ create_table :glancer_messages do |t|
17
+ t.references :chat, null: false, foreign_key: { to_table: :glancer_chats }
18
+ t.references :user_message, null: true, foreign_key: { to_table: :glancer_messages }
19
+ t.string :role
20
+ t.text :content
21
+ t.text :code
22
+ t.string :code_type, null: false, default: "sql"
23
+ t.boolean :successful, default: true
24
+ t.boolean :user_edited_code, default: false, null: false
25
+ t.string :llm_model
26
+ t.timestamps
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddUserEditedSqlToGlancerMessages < ActiveRecord::Migration[6.1]
4
+ def change
5
+ # Column was renamed to user_edited_code; skip if either name already exists
6
+ return if column_exists?(:glancer_messages, :user_edited_code)
7
+ return if column_exists?(:glancer_messages, :user_edited_sql)
8
+
9
+ add_column :glancer_messages, :user_edited_code, :boolean, default: false, null: false
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateGlancerSqlVersions < ActiveRecord::Migration[6.1]
4
+ def change
5
+ table_name = table_exists?(:glancer_code_versions) ? :glancer_code_versions : :glancer_sql_versions
6
+
7
+ create_table table_name do |t|
8
+ t.references :message, null: false, foreign_key: { to_table: :glancer_messages }
9
+ t.text :code, null: false
10
+ t.string :source, null: false, default: "generated"
11
+ t.timestamps
12
+ end
13
+
14
+ add_index table_name, :created_at
15
+ rescue ActiveRecord::StatementInvalid
16
+ # Table already exists — skip
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddMessageIdToGlancerAudits < ActiveRecord::Migration[6.1]
4
+ def change
5
+ add_column :glancer_audits, :message_id, :bigint
6
+ add_index :glancer_audits, :message_id
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateGlancerSettings < ActiveRecord::Migration[6.1]
4
+ def change
5
+ create_table :glancer_settings do |t|
6
+ t.string :key, null: false
7
+ t.text :value
8
+ t.timestamps
9
+ end
10
+ add_index :glancer_settings, :key, unique: true
11
+ end
12
+ end