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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddLlmModelToGlancerMessages < ActiveRecord::Migration[6.1]
4
+ def change
5
+ return if column_exists?(:glancer_messages, :llm_model)
6
+
7
+ add_column :glancer_messages, :llm_model, :string
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RenameCodeColumnsInGlancerMessages < ActiveRecord::Migration[6.1]
4
+ def change
5
+ rename_column :glancer_messages, :sql, :code if column_exists?(:glancer_messages, :sql)
6
+ rename_column :glancer_messages, :user_edited_sql, :user_edited_code if column_exists?(:glancer_messages, :user_edited_sql)
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RenameCodeColumnInGlancerAudits < ActiveRecord::Migration[6.1]
4
+ def change
5
+ rename_column :glancer_audits, :sql, :code if column_exists?(:glancer_audits, :sql)
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddCodeTypeToGlancerTables < ActiveRecord::Migration[6.1]
4
+ def change
5
+ add_column :glancer_messages, :code_type, :string, null: false, default: "sql" unless column_exists?(:glancer_messages, :code_type)
6
+ return if column_exists?(:glancer_audits, :code_type)
7
+
8
+ add_column :glancer_audits, :code_type, :string, null: false, default: "sql"
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RenameGlancerSqlVersionsToCodeVersions < ActiveRecord::Migration[6.1]
4
+ def change
5
+ rename_table :glancer_sql_versions, :glancer_code_versions if table_exists?(:glancer_sql_versions)
6
+ rename_column :glancer_code_versions, :sql, :code if column_exists?(:glancer_code_versions, :sql)
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddEnrichedQuestionToGlancerMessages < ActiveRecord::Migration[7.0]
4
+ def change
5
+ add_column :glancer_messages, :enriched_question, :text
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddStatusToGlancerMessages < ActiveRecord::Migration[7.0]
4
+ def change
5
+ # 0=pending, 1=processing, 2=complete, 3=failed
6
+ # default 2 (complete) so existing records are treated as already done.
7
+ add_column :glancer_messages, :status, :integer, default: 2, null: false
8
+ end
9
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/base"
5
+
6
+ module Glancer
7
+ module Generators
8
+ class InstallGenerator < Rails::Generators::Base
9
+ source_root File.expand_path("templates", __dir__)
10
+ desc "Installs Glancer: initializer, migrations, and mounts engine"
11
+
12
+ def copy_initializer
13
+ template "glancer.rb", "config/initializers/glancer.rb"
14
+ end
15
+
16
+ def copy_context_file
17
+ template "llm_context.glancer.md", "config/glancer/llm_context.glancer.md"
18
+ end
19
+
20
+ def mount_engine
21
+ inject_into_file "config/routes.rb", after: "Rails.application.routes.draw do\n" do
22
+ " mount Glancer::Engine => '/glancer'\n"
23
+ end
24
+ end
25
+
26
+ def show_readme
27
+ info = "\e[32m"
28
+ warn = "\e[33m"
29
+ debug = "\e[36m"
30
+ error = "\e[31m"
31
+ reset = "\e[0m"
32
+
33
+ say <<~MSG
34
+
35
+ #{info}╔════════════════════════════════════════════════════════════════════╗
36
+ ║ ✔ Glancer installed successfully! ✔ ║
37
+ ╚════════════════════════════════════════════════════════════════════╝#{reset}
38
+
39
+ #{debug}Next steps:#{reset}
40
+
41
+ #{warn}1. Review and customize the configuration:#{reset}
42
+ ├── File: #{debug}config/initializers/glancer.rb#{reset}
43
+ └── You can set:
44
+ ✔ Adapter
45
+ ✔ Read-only DB
46
+ ✔ LLM provider
47
+ ✔ Logging options
48
+ ✔ ...
49
+
50
+ #{warn}2. Edit the context file (optional but recommended):#{reset}
51
+ ├── File: #{debug}config/glancer/llm_context.glancer.md#{reset}
52
+ ├── Currently ignored (first line is '#{error}--glancer-ignore#{reset}')
53
+ └── Remove or modify first line to enable indexing.
54
+ Use it to describe:
55
+ ✔ Business rules
56
+ ✔ Table usage
57
+ ✔ Domain logic
58
+ ✔ ...
59
+
60
+ #{warn}3. Apply the database migrations:#{reset}
61
+ └── Run: #{info}rails db:migrate#{reset}
62
+
63
+ #{warn}4. Index your schema, models, and context:#{reset}
64
+ ├── Run: #{info}rails glancer:index:all#{reset}
65
+ └── #{info}✱#{reset} Or do it separately
66
+ ├── Run: #{info}rails glancer:index:schema#{reset}
67
+ ├── Run: #{info}rails glancer:index:context#{reset}
68
+ └── Run: #{info}rails glancer:index:models#{reset}
69
+
70
+ MSG
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Glancer configuration
4
+ # Full reference: https://github.com/ernanej/glancer
5
+
6
+ Glancer.configure do |config|
7
+ # ─────────────────────────────────────────────────────────────────────────────
8
+ # Database
9
+ # ─────────────────────────────────────────────────────────────────────────────
10
+
11
+ # Adapter used to execute queries. Auto-detected from ActiveRecord when nil.
12
+ # Accepted: :postgres | :mysql | :mysql2 | :sqlite
13
+ config.adapter = nil
14
+
15
+ # Optional read-only replica URL. Queries run against this connection when set.
16
+ # Accepts a full Rails database URL string or :read_only (uses current connection
17
+ # in read-only mode).
18
+ config.read_only_db = nil
19
+
20
+ # Maximum time a single query may run before being killed.
21
+ # PostgreSQL uses SET statement_timeout; MySQL uses SET max_execution_time.
22
+ # SQLite has no server-side timeout enforcement.
23
+ config.statement_timeout = 30.seconds
24
+
25
+ # ─────────────────────────────────────────────────────────────────────────────
26
+ # Query mode
27
+ # ─────────────────────────────────────────────────────────────────────────────
28
+
29
+ # Controls how Glancer generates and executes database queries.
30
+ # :sql — LLM generates a raw SELECT statement (default, most portable)
31
+ # :activerecord — LLM generates a Ruby/ActiveRecord expression; lets the LLM
32
+ # reuse your model scopes, associations, and named queries
33
+ config.query_mode = :sql
34
+
35
+ # ─────────────────────────────────────────────────────────────────────────────
36
+ # Query enrichment
37
+ # ─────────────────────────────────────────────────────────────────────────────
38
+
39
+ # When enabled, the user's question is rewritten by a fast LLM before retrieval.
40
+ # The enricher translates natural-language terms into actual table names so that
41
+ # the vector search and code-generation steps are more accurate. The original
42
+ # question is always shown to the user; the final answer is always in the user's
43
+ # language regardless of what language the enriched question was generated in.
44
+ config.query_enrichment_enabled = false
45
+
46
+ # Provider and model for the enrichment step. Use a cheap, fast model here
47
+ # (e.g. gemini-2.0-flash, gpt-4o-mini) — it only translates terminology.
48
+ # When nil, falls back to llm_provider / llm_model.
49
+ # config.enrichment_provider = :gemini
50
+ # config.enrichment_model = "gemini-2.0-flash"
51
+
52
+ # ─────────────────────────────────────────────────────────────────────────────
53
+ # LLM — Default provider (fallback for all roles below)
54
+ # ─────────────────────────────────────────────────────────────────────────────
55
+
56
+ # Default provider for all LLM calls. Used when role-specific providers are nil.
57
+ # Accepted: :gemini | :openai | :openrouter
58
+ config.llm_provider = :gemini
59
+
60
+ # Default model. Used when role-specific models are nil.
61
+ # Gemini: 'gemini-2.0-flash', 'gemini-1.5-pro', ...
62
+ # OpenAI: 'gpt-4o', 'gpt-4o-mini', ...
63
+ # OpenRouter: 'anthropic/claude-3.5-sonnet', 'openai/gpt-4o', ...
64
+ config.llm_model = "gemini-2.0-flash"
65
+
66
+ # ─────────────────────────────────────────────────────────────────────────────
67
+ # LLM — Code generation (overrides default for query building)
68
+ # ─────────────────────────────────────────────────────────────────────────────
69
+
70
+ # Provider used for code generation. Nil → falls back to llm_provider.
71
+ # Useful when you want a code-focused model (e.g. gpt-4o) for query generation
72
+ # and a cheaper model for responses.
73
+ # Accepted: nil | :gemini | :openai | :openrouter
74
+ config.code_provider = nil
75
+
76
+ # Model used for code generation. Nil → falls back to llm_model.
77
+ config.code_model = nil
78
+
79
+ # ─────────────────────────────────────────────────────────────────────────────
80
+ # LLM — Chat responses (overrides default for humanized answers)
81
+ # ─────────────────────────────────────────────────────────────────────────────
82
+
83
+ # Provider used for humanizing results and generating chat responses.
84
+ # Nil → falls back to llm_provider.
85
+ # Accepted: nil | :gemini | :openai | :openrouter
86
+ config.chat_provider = nil
87
+
88
+ # Model used for chat responses. Nil → falls back to llm_model.
89
+ config.chat_model = nil
90
+
91
+ # ─────────────────────────────────────────────────────────────────────────────
92
+ # LLM — Embeddings
93
+ # ─────────────────────────────────────────────────────────────────────────────
94
+
95
+ # Provider used exclusively for generating embeddings (indexing + retrieval).
96
+ # Defaults to llm_provider when nil.
97
+ #
98
+ # IMPORTANT — OpenRouter embedding support:
99
+ # OpenRouter does not expose a native embedding API. If you use OpenRouter
100
+ # for chat/code generation, set embedding_provider to :gemini or :openai to
101
+ # avoid errors. If you must use an OpenRouter embedding model anyway, set
102
+ # embedding_model explicitly (e.g., 'openai/text-embedding-3-small') —
103
+ # Glancer will bypass the model registry check automatically.
104
+ #
105
+ # Accepted: nil | :gemini | :openai | :openrouter
106
+ config.embedding_provider = nil
107
+
108
+ # Embedding model override. When nil, Glancer uses the provider default:
109
+ # Gemini: 'text-embedding-004'
110
+ # OpenAI: 'text-embedding-3-large'
111
+ # OpenRouter: 'openai/text-embedding-3-small' (must be passed as model ID)
112
+ config.embedding_model = nil
113
+
114
+ # ─────────────────────────────────────────────────────────────────────────────
115
+ # API Keys
116
+ # ─────────────────────────────────────────────────────────────────────────────
117
+
118
+ # Use provider-specific keys (preferred) or api_key as a generic fallback.
119
+ config.gemini_api_key = ENV.fetch("GEMINI_API_KEY", nil)
120
+ # config.openai_api_key = ENV.fetch('OPENAI_API_KEY', nil)
121
+ # config.openrouter_api_key = ENV.fetch('OPENROUTER_API_KEY', nil)
122
+ # config.api_key = ENV.fetch('LLM_API_KEY', nil) # generic fallback for any provider
123
+
124
+ # ─── Example: different providers per role ──────────────────────────────────
125
+ # config.llm_provider = :gemini # default fallback
126
+ # config.llm_model = 'gemini-2.0-flash'
127
+ # config.code_provider = :openai # code-focused model for queries
128
+ # config.code_model = 'gpt-4o'
129
+ # config.chat_provider = :gemini # cheaper for chat
130
+ # config.chat_model = 'gemini-2.0-flash'
131
+ # config.embedding_provider = :gemini # dedicated embeddings
132
+ # config.embedding_model = 'text-embedding-004'
133
+ # config.gemini_api_key = ENV.fetch('GEMINI_API_KEY', nil)
134
+ # config.openai_api_key = ENV.fetch('OPENAI_API_KEY', nil)
135
+ # ─── Example: OpenRouter for chat/code gen, Gemini for embeddings (recommended) ──
136
+ # OpenRouter does not expose a native embedding API, so always pair it with
137
+ # a dedicated embedding provider (:gemini or :openai).
138
+ # config.llm_provider = :openrouter
139
+ # config.openrouter_api_key = ENV.fetch('OPENROUTER_API_KEY', nil)
140
+ # config.llm_model = 'anthropic/claude-3.5-sonnet'
141
+ # config.code_model = 'deepseek/deepseek-r1:free'
142
+ # config.embedding_provider = :gemini
143
+ # config.gemini_api_key = ENV.fetch('GEMINI_API_KEY', nil)
144
+ # config.embedding_model = 'text-embedding-004'
145
+ # ───────────────────────────────────────────────────────────────────────────
146
+
147
+ # ─────────────────────────────────────────────────────────────────────────────
148
+ # Indexing permissions
149
+ # ─────────────────────────────────────────────────────────────────────────────
150
+
151
+ # Allow Glancer to index db/schema.rb (table and column definitions).
152
+ config.schema_permission = true
153
+
154
+ # Allow Glancer to index app/models/**/*.rb (associations, validations, scopes).
155
+ config.models_permission = false
156
+
157
+ # Path to a Markdown file with domain context: business rules, table aliases,
158
+ # common query patterns, etc. Add '--glancer-ignore' as the first line to skip.
159
+ config.context_file_path = "config/glancer/llm_context.glancer.md"
160
+
161
+ # ─────────────────────────────────────────────────────────────────────────────
162
+ # Chunking (controls how documents are split before embedding)
163
+ # ─────────────────────────────────────────────────────────────────────────────
164
+
165
+ # Maximum characters per chunk. Smaller chunks are more precise; larger chunks
166
+ # preserve more context. Default: 1000.
167
+ config.chunk_size = 1000
168
+
169
+ # Characters of overlap between consecutive chunks. Helps prevent context loss
170
+ # at chunk boundaries. Default: 150 (~15% of chunk_size).
171
+ config.chunk_overlap = 150
172
+
173
+ # ─────────────────────────────────────────────────────────────────────────────
174
+ # Retrieval
175
+ # ─────────────────────────────────────────────────────────────────────────────
176
+
177
+ # Number of top embedding chunks returned per query.
178
+ config.k = 10
179
+
180
+ # Minimum cosine similarity score [0.0–1.0] for a chunk to be included.
181
+ # Lower values return more results but may include noise.
182
+ config.min_score = 0.6
183
+
184
+ # Relevance multipliers per document type (must be ≥ 1.0).
185
+ # Higher weight = ranked higher in retrieved context.
186
+ config.schema_documents_weight = 1.3 # db/schema.rb table definitions
187
+ config.context_documents_weight = 1.2 # custom context Markdown file
188
+ config.models_documents_weight = 1.1 # ActiveRecord model files
189
+
190
+ # ─────────────────────────────────────────────────────────────────────────────
191
+ # Conversation
192
+ # ─────────────────────────────────────────────────────────────────────────────
193
+
194
+ # Number of past messages included in the prompt for multi-turn context.
195
+ config.history_limit = 6
196
+
197
+ # ─────────────────────────────────────────────────────────────────────────────
198
+ # Caching
199
+ # ─────────────────────────────────────────────────────────────────────────────
200
+
201
+ # How long identical questions are served from the in-memory cache without
202
+ # calling the LLM again. Set to 0 to disable. Cache is process-local (not
203
+ # shared across Puma workers or restarts).
204
+ config.workflow_cache_ttl = 5.minutes
205
+
206
+ # ─────────────────────────────────────────────────────────────────────────────
207
+ # Logging
208
+ # ─────────────────────────────────────────────────────────────────────────────
209
+
210
+ # File path for Glancer logs. When nil, output goes to Rails.logger / STDOUT.
211
+ config.log_output_path = nil # e.g. 'log/glancer.log'
212
+
213
+ # Log verbosity level.
214
+ # :none → silent
215
+ # :info → normal operational messages (default)
216
+ # :debug → verbose, includes prompts and full backtraces
217
+ config.log_verbosity = :info
218
+
219
+ # ─────────────────────────────────────────────────────────────────────────────
220
+ # Integrations
221
+ # ─────────────────────────────────────────────────────────────────────────────
222
+
223
+ # Blazer integration: enables an 'Open in Blazer' button on generated queries.
224
+ # Auto-detected if the blazer gem is installed (defaults to '/blazer').
225
+ # Set explicitly to override the path, or to '' / nil to disable.
226
+ # config.blazer_path = '/blazer'
227
+ end
@@ -0,0 +1,51 @@
1
+ --glancer-ignore
2
+
3
+ # Glancer LLM Context Template
4
+ #
5
+ # This file lets you define custom business rules, domain logic,
6
+ # naming conventions, and assumptions about your data model
7
+ # that the language model should take into account when answering questions.
8
+
9
+ ## How It Works
10
+
11
+ - These rules and notes will be embedded and used as retrieval context.
12
+ - They **do not need to be machine-parsable** — use natural language.
13
+ - Be specific and concise. Avoid redundant or vague information.
14
+
15
+ ## Business Rules
16
+
17
+ - "Users with role = 'admin' are considered system administrators."
18
+ - "sales" table is very large, avoid bringing everything. Always filter by period of 30 days to 1 year
19
+ - "Orders with status = 'cancelled' should not be counted in sales totals."
20
+ - "Only 'paid' invoices should be considered for revenue aggregation."
21
+ - "Products may belong to multiple categories via `product_categories` join table."
22
+
23
+ ## Domain Definitions
24
+
25
+ - "`vendas` = sales"
26
+ - "`usuarios` = users or system operators"
27
+ - "`filiais` = branches or physical offices"
28
+ - "A `lead` represents a potential customer, not yet converted to a client."
29
+
30
+ ## Special Table Considerations
31
+
32
+ - "`users` table stores authentication data and personal details. Use with care."
33
+ - "`clients_blacklists` table indicates blocked clients; should be checked before allowing new orders."
34
+ - "`blazer_*` tables are internal and should be ignored."
35
+
36
+ ## Common Query Patterns
37
+
38
+ - "When grouping by month, use the `created_at` column, unless otherwise specified."
39
+ - "Default date range for metrics is the last 30 days unless the question says otherwise."
40
+
41
+ ## Output Expectations
42
+
43
+ - "Always alias columns using `AS` with a human-readable label."
44
+ - "All queries should return fields in snake_case."
45
+ - "Include rows with zero results when grouping by time (use LEFT JOINs or CTEs if needed)."
46
+
47
+ ## 📝 Notes
48
+
49
+ - You can add Markdown formatting here, but it's optional.
50
+ - Keep this file short and relevant. Avoid dumping full tables or schemas here.
51
+ - This file is **ignored** by Glancer indexing unless you remove the `--glancer-ignore` line.
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glancer
4
+ # Runs message processing in a background thread without relying on the host
5
+ # app's Active Job queue adapter. Checks out a dedicated database connection
6
+ # for the thread and releases it when done, regardless of success or failure.
7
+ module AsyncRunner
8
+ def self.call(message_id, question)
9
+ Thread.new do
10
+ ActiveRecord::Base.connection_pool.with_connection do
11
+ run(message_id, question)
12
+ end
13
+ rescue StandardError => e
14
+ Glancer::Utils::Logger.error("AsyncRunner", "Thread raised outside job: #{e.message}")
15
+ end
16
+ end
17
+
18
+ def self.run(message_id, question)
19
+ message = Glancer::Message.find(message_id)
20
+ chat = message.chat
21
+
22
+ message.update!(status: :processing)
23
+
24
+ result = Glancer::Workflow.run(chat.id, question)
25
+ cfg = Glancer.configuration
26
+ used_model = "#{cfg.resolved_chat_provider}/#{cfg.resolved_chat_model}"
27
+
28
+ message.update!(
29
+ content: result[:content].to_s,
30
+ code: result[:code],
31
+ code_type: result[:code_type] || "sql",
32
+ successful: result[:successful],
33
+ llm_model: used_model,
34
+ enriched_question: result[:enriched_question],
35
+ status: :complete
36
+ )
37
+
38
+ message.code_versions.create!(code: message.code, source: :generated) if message.code.present?
39
+
40
+ chat.update!(title: Glancer::Workflow::LLM.generate_title(question)) if chat.messages.where(role: :user).count == 1
41
+ rescue StandardError => e
42
+ Glancer::Utils::Logger.error("AsyncRunner", "Failed for message #{message_id}: #{e.message}")
43
+ begin
44
+ message&.update!(content: e.message, successful: false, status: :failed)
45
+ rescue StandardError => update_error
46
+ Glancer::Utils::Logger.error("AsyncRunner", "Could not mark message as failed: #{update_error.message}")
47
+ end
48
+ end
49
+ end
50
+ end