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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +96 -0
- data/.rubocop.yml +54 -0
- data/CHANGELOG.md +88 -0
- data/CLAUDE.md +115 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/README.md +354 -0
- data/app/assets/config/glancer_manifest.js +1 -0
- data/app/assets/javascripts/glancer/application.js +15 -0
- data/app/assets/javascripts/glancer/controllers/chat_controller.js +101 -0
- data/app/assets/javascripts/glancer/controllers/message_controller.js +1052 -0
- data/app/assets/javascripts/glancer/controllers/toast_controller.js +63 -0
- data/app/assets/stylesheets/glancer/application.css +350 -0
- data/app/assets/stylesheets/glancer/code-blocks.css +6 -0
- data/app/assets/stylesheets/glancer/list.css +31 -0
- data/app/assets/stylesheets/glancer/scrollbar.css +16 -0
- data/app/assets/stylesheets/glancer/table.css +97 -0
- data/app/controllers/glancer/application_controller.rb +33 -0
- data/app/controllers/glancer/chats_controller.rb +49 -0
- data/app/controllers/glancer/messages_controller.rb +144 -0
- data/app/controllers/glancer/schema_controller.rb +29 -0
- data/app/controllers/glancer/settings_controller.rb +23 -0
- data/app/helpers/glancer/application_helper.rb +17 -0
- data/app/jobs/glancer/application_job.rb +6 -0
- data/app/jobs/glancer/process_message_job.rb +38 -0
- data/app/models/glancer/audit.rb +12 -0
- data/app/models/glancer/chat.rb +8 -0
- data/app/models/glancer/code_version.rb +12 -0
- data/app/models/glancer/embedding.rb +6 -0
- data/app/models/glancer/message.rb +25 -0
- data/app/models/glancer/setting.rb +23 -0
- data/app/models/glancer/sql_version.rb +6 -0
- data/app/views/glancer/_data/_importmap.json.erb +7 -0
- data/app/views/glancer/chats/_chat_sidebar.html.erb +2 -0
- data/app/views/glancer/chats/_show.html.erb +52 -0
- data/app/views/glancer/chats/_sidebar_chat_list.html.erb +30 -0
- data/app/views/glancer/chats/index.html.erb +10 -0
- data/app/views/glancer/chats/show.html.erb +1 -0
- data/app/views/glancer/messages/_data_table.html.erb +268 -0
- data/app/views/glancer/messages/_execution_error.html.erb +26 -0
- data/app/views/glancer/messages/_form.html.erb +93 -0
- data/app/views/glancer/messages/_message.html.erb +206 -0
- data/app/views/glancer/messages/_message_info.html.erb +176 -0
- data/app/views/glancer/messages/_temp_form.html.erb +100 -0
- data/app/views/glancer/messages/create.turbo_stream.erb +25 -0
- data/app/views/glancer/schema/show.html.erb +123 -0
- data/app/views/glancer/settings/show.html.erb +306 -0
- data/app/views/glancer/shared/_icons.html.erb +126 -0
- data/app/views/layouts/glancer/application.html.erb +234 -0
- data/config/locales/glancer.en.yml +90 -0
- data/config/locales/glancer.es.yml +90 -0
- data/config/locales/glancer.pt-BR.yml +90 -0
- data/config/routes.rb +20 -0
- data/db/migrate/20250629212642_create_glancer_audits.rb +19 -0
- data/db/migrate/20250629212643_create_glancer_chats.rb +10 -0
- data/db/migrate/20250629212645_create_glancer_embeddings.rb +17 -0
- data/db/migrate/20250629212647_create_glancer_messages.rb +29 -0
- data/db/migrate/20260513204129_add_user_edited_sql_to_glancer_messages.rb +11 -0
- data/db/migrate/20260513210647_create_glancer_sql_versions.rb +18 -0
- data/db/migrate/20260513210648_add_message_id_to_glancer_audits.rb +8 -0
- data/db/migrate/20260513220000_create_glancer_settings.rb +12 -0
- data/db/migrate/20260514083509_add_llm_model_to_glancer_messages.rb +9 -0
- data/db/migrate/20260523120000_rename_code_columns_in_glancer_messages.rb +8 -0
- data/db/migrate/20260523120001_rename_code_column_in_glancer_audits.rb +7 -0
- data/db/migrate/20260523120002_add_code_type_to_glancer_tables.rb +10 -0
- data/db/migrate/20260523120003_rename_glancer_sql_versions_to_code_versions.rb +8 -0
- data/db/migrate/20260523130000_add_enriched_question_to_glancer_messages.rb +7 -0
- data/db/migrate/20260524100000_add_status_to_glancer_messages.rb +9 -0
- data/lib/generators/glancer/install/install_generator.rb +74 -0
- data/lib/generators/glancer/install/templates/glancer.rb +227 -0
- data/lib/generators/glancer/install/templates/llm_context.glancer.md +51 -0
- data/lib/glancer/async_runner.rb +50 -0
- data/lib/glancer/chart_analyzer.rb +230 -0
- data/lib/glancer/configuration.rb +372 -0
- data/lib/glancer/engine.rb +90 -0
- data/lib/glancer/indexer/context_indexer.rb +58 -0
- data/lib/glancer/indexer/model_indexer.rb +64 -0
- data/lib/glancer/indexer/schema_indexer.rb +171 -0
- data/lib/glancer/indexer.rb +50 -0
- data/lib/glancer/retriever.rb +114 -0
- data/lib/glancer/utils/logger.rb +83 -0
- data/lib/glancer/utils/markdown_helper.rb +56 -0
- data/lib/glancer/utils/result_formatter.rb +25 -0
- data/lib/glancer/utils/table_stats.rb +18 -0
- data/lib/glancer/utils/transaction.rb +59 -0
- data/lib/glancer/version.rb +5 -0
- data/lib/glancer/workflow/ar_executor.rb +104 -0
- data/lib/glancer/workflow/ar_extractor.rb +25 -0
- data/lib/glancer/workflow/ar_prompt_builder.rb +64 -0
- data/lib/glancer/workflow/ar_sanitizer.rb +88 -0
- data/lib/glancer/workflow/builder.rb +129 -0
- data/lib/glancer/workflow/cache.rb +55 -0
- data/lib/glancer/workflow/executor.rb +72 -0
- data/lib/glancer/workflow/llm.rb +123 -0
- data/lib/glancer/workflow/prompt_builder.rb +143 -0
- data/lib/glancer/workflow/query_enricher.rb +117 -0
- data/lib/glancer/workflow/sql_extractor.rb +42 -0
- data/lib/glancer/workflow/sql_sanitizer.rb +42 -0
- data/lib/glancer/workflow/sql_validator.rb +67 -0
- data/lib/glancer/workflow.rb +158 -0
- data/lib/glancer.rb +50 -0
- data/lib/tasks/glancer/tailwind.rake +8 -0
- data/lib/tasks/glancer.rake +99 -0
- data/spec/glancer_spec.rb +62 -0
- data/spec/lib/glancer/async_runner_spec.rb +133 -0
- data/spec/lib/glancer/chart_analyzer_spec.rb +296 -0
- data/spec/lib/glancer/configuration_spec.rb +858 -0
- data/spec/lib/glancer/engine_spec.rb +209 -0
- data/spec/lib/glancer/indexer/context_indexer_spec.rb +96 -0
- data/spec/lib/glancer/indexer/model_indexer_spec.rb +103 -0
- data/spec/lib/glancer/indexer/schema_indexer_spec.rb +382 -0
- data/spec/lib/glancer/indexer_spec.rb +95 -0
- data/spec/lib/glancer/retriever_spec.rb +179 -0
- data/spec/lib/glancer/utils/logger_spec.rb +85 -0
- data/spec/lib/glancer/utils/markdown_helper_spec.rb +92 -0
- data/spec/lib/glancer/utils/result_formatter_spec.rb +73 -0
- data/spec/lib/glancer/utils/table_stats_spec.rb +34 -0
- data/spec/lib/glancer/utils/transaction_spec.rb +73 -0
- data/spec/lib/glancer/workflow/ar_executor_spec.rb +155 -0
- data/spec/lib/glancer/workflow/ar_extractor_spec.rb +50 -0
- data/spec/lib/glancer/workflow/ar_prompt_builder_spec.rb +79 -0
- data/spec/lib/glancer/workflow/ar_sanitizer_spec.rb +175 -0
- data/spec/lib/glancer/workflow/builder_spec.rb +204 -0
- data/spec/lib/glancer/workflow/cache_spec.rb +142 -0
- data/spec/lib/glancer/workflow/executor_spec.rb +149 -0
- data/spec/lib/glancer/workflow/llm_spec.rb +124 -0
- data/spec/lib/glancer/workflow/prompt_builder_spec.rb +196 -0
- data/spec/lib/glancer/workflow/query_enricher_spec.rb +184 -0
- data/spec/lib/glancer/workflow/sql_extractor_spec.rb +82 -0
- data/spec/lib/glancer/workflow/sql_sanitizer_spec.rb +98 -0
- data/spec/lib/glancer/workflow/sql_validator_spec.rb +166 -0
- data/spec/lib/glancer/workflow_spec.rb +308 -0
- data/spec/models/glancer/audit_spec.rb +82 -0
- data/spec/models/glancer/chat_spec.rb +60 -0
- data/spec/models/glancer/code_version_spec.rb +71 -0
- data/spec/models/glancer/embedding_spec.rb +73 -0
- data/spec/models/glancer/message_spec.rb +144 -0
- data/spec/models/glancer/setting_spec.rb +88 -0
- data/spec/models/glancer/sql_version_spec.rb +4 -0
- data/spec/spec_helper.rb +128 -0
- data/spec/support/schema.rb +55 -0
- metadata +255 -0
|
@@ -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,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,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
|