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,63 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
connect() {
|
|
5
|
+
document.addEventListener("glancer:toast", this.handleToast.bind(this));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
disconnect() {
|
|
9
|
+
document.removeEventListener("glancer:toast", this.handleToast.bind(this));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
handleToast(event) {
|
|
13
|
+
const { message, type = "info" } = event.detail;
|
|
14
|
+
this.show(message, type);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
show(message, type = "info") {
|
|
18
|
+
const toast = document.createElement("div");
|
|
19
|
+
|
|
20
|
+
const colors = {
|
|
21
|
+
success: "bg-green-600 text-white",
|
|
22
|
+
error: "bg-red-600 text-white",
|
|
23
|
+
info: "bg-gray-800 dark:bg-gray-700 text-white",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const icons = {
|
|
27
|
+
success: "#icon-check",
|
|
28
|
+
error: "#icon-x",
|
|
29
|
+
info: "#icon-info",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
toast.className = [
|
|
33
|
+
"pointer-events-auto flex items-center gap-2.5 px-4 py-3 rounded-xl shadow-lg text-sm font-medium",
|
|
34
|
+
"translate-y-2 opacity-0 transition-all duration-200 ease-out",
|
|
35
|
+
colors[type] || colors.info,
|
|
36
|
+
].join(" ");
|
|
37
|
+
|
|
38
|
+
toast.innerHTML = `
|
|
39
|
+
<svg class="w-4 h-4 flex-shrink-0" aria-hidden="true"><use href="${icons[type] || icons.info}"/></svg>
|
|
40
|
+
<span>${this.escapeHtml(message)}</span>
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
this.element.appendChild(toast);
|
|
44
|
+
|
|
45
|
+
// Animate in
|
|
46
|
+
requestAnimationFrame(() => {
|
|
47
|
+
toast.classList.remove("translate-y-2", "opacity-0");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Auto-dismiss after 4s
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
toast.classList.add("opacity-0", "translate-y-2");
|
|
53
|
+
toast.addEventListener("transitionend", () => toast.remove(), { once: true });
|
|
54
|
+
}, 4000);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
escapeHtml(str) {
|
|
58
|
+
return String(str)
|
|
59
|
+
.replace(/&/g, "&")
|
|
60
|
+
.replace(/</g, "<")
|
|
61
|
+
.replace(/>/g, ">");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/* Smooth scroll for the messages container */
|
|
2
|
+
#chat-messages {
|
|
3
|
+
scroll-behavior: smooth;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/* Typewriter cursor */
|
|
7
|
+
.cursor-blink {
|
|
8
|
+
animation: blink 1s step-end infinite;
|
|
9
|
+
color: #a855f7;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@keyframes blink {
|
|
13
|
+
from, to { opacity: 1 }
|
|
14
|
+
50% { opacity: 0 }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Links inside message content */
|
|
18
|
+
.message-content a {
|
|
19
|
+
color: #9333ea;
|
|
20
|
+
text-decoration: underline;
|
|
21
|
+
text-decoration-color: rgba(147, 51, 234, 0.3);
|
|
22
|
+
text-underline-offset: 2px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dark .message-content a {
|
|
26
|
+
color: #c084fc;
|
|
27
|
+
text-decoration-color: rgba(192, 132, 252, 0.3);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* ── Prism base overrides ────────────────────────────────────────────────── */
|
|
31
|
+
/* Layout/font only — no background here so light/dark themes can apply. */
|
|
32
|
+
pre[class*="language-"],
|
|
33
|
+
code[class*="language-"] {
|
|
34
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', ui-monospace, monospace !important;
|
|
35
|
+
font-size: 0.75rem !important;
|
|
36
|
+
line-height: 1.6 !important;
|
|
37
|
+
text-shadow: none !important;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pre[class*="language-"] {
|
|
41
|
+
border-radius: 0 !important;
|
|
42
|
+
margin: 0 !important;
|
|
43
|
+
white-space: pre-wrap !important;
|
|
44
|
+
word-wrap: break-word !important;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* ── Dark mode: Prism token colours (GitHub dark palette) ────────────────── */
|
|
48
|
+
.dark pre[class*="language-"] {
|
|
49
|
+
background: #161b22 !important;
|
|
50
|
+
color: #e6edf3 !important;
|
|
51
|
+
}
|
|
52
|
+
code[class*="language-"] { color: #e6edf3; }
|
|
53
|
+
.dark .token.comment,
|
|
54
|
+
.dark .token.prolog,
|
|
55
|
+
.dark .token.doctype,
|
|
56
|
+
.dark .token.cdata { color: #8b949e; font-style: italic; }
|
|
57
|
+
.dark .token.punctuation { color: #c9d1d9; }
|
|
58
|
+
.dark .token.property,
|
|
59
|
+
.dark .token.tag,
|
|
60
|
+
.dark .token.boolean,
|
|
61
|
+
.dark .token.number,
|
|
62
|
+
.dark .token.constant,
|
|
63
|
+
.dark .token.symbol { color: #79c0ff; }
|
|
64
|
+
.dark .token.selector,
|
|
65
|
+
.dark .token.attr-name,
|
|
66
|
+
.dark .token.string,
|
|
67
|
+
.dark .token.char,
|
|
68
|
+
.dark .token.builtin,
|
|
69
|
+
.dark .token.inserted { color: #a5d6ff; }
|
|
70
|
+
.dark .token.operator,
|
|
71
|
+
.dark .token.url { color: #e6edf3; }
|
|
72
|
+
.dark .token.atrule,
|
|
73
|
+
.dark .token.attr-value,
|
|
74
|
+
.dark .token.keyword { color: #ff7b72; }
|
|
75
|
+
.dark .token.function,
|
|
76
|
+
.dark .token.class-name { color: #d2a8ff; }
|
|
77
|
+
.dark .token.regex,
|
|
78
|
+
.dark .token.important,
|
|
79
|
+
.dark .token.variable { color: #ffa657; }
|
|
80
|
+
.dark .token.deleted { color: #ffa198; }
|
|
81
|
+
|
|
82
|
+
/* ── SQL code viewer: always dark (Catppuccin Mocha) ────────────────────── */
|
|
83
|
+
[data-sql-block] pre[class*="language-"] {
|
|
84
|
+
background: #1e1e2e !important;
|
|
85
|
+
color: #cdd6f4 !important;
|
|
86
|
+
text-shadow: none !important;
|
|
87
|
+
}
|
|
88
|
+
[data-sql-block] .token.comment,
|
|
89
|
+
[data-sql-block] .token.prolog { color: #6c7086 !important; font-style: italic; }
|
|
90
|
+
[data-sql-block] .token.keyword { color: #cba6f7 !important; }
|
|
91
|
+
[data-sql-block] .token.string,
|
|
92
|
+
[data-sql-block] .token.char { color: #a6e3a1 !important; }
|
|
93
|
+
[data-sql-block] .token.number,
|
|
94
|
+
[data-sql-block] .token.boolean { color: #fab387 !important; }
|
|
95
|
+
[data-sql-block] .token.function { color: #89b4fa !important; }
|
|
96
|
+
[data-sql-block] .token.class-name { color: #f38ba8 !important; }
|
|
97
|
+
[data-sql-block] .token.operator { color: #89dceb !important; }
|
|
98
|
+
[data-sql-block] .token.punctuation { color: #9399b2 !important; }
|
|
99
|
+
[data-sql-block] .token.property,
|
|
100
|
+
[data-sql-block] .token.tag { color: #f2cdcd !important; }
|
|
101
|
+
[data-sql-block] .token.atrule,
|
|
102
|
+
[data-sql-block] .token.attr-value { color: #f5c2e7 !important; }
|
|
103
|
+
[data-sql-block] .token.variable { color: #cdd6f4 !important; }
|
|
104
|
+
[data-sql-block] .token.selector,
|
|
105
|
+
[data-sql-block] .token.inserted { color: #a6e3a1 !important; }
|
|
106
|
+
[data-sql-block] .token.deleted { color: #f38ba8 !important; }
|
|
107
|
+
|
|
108
|
+
/* Inline code (not inside pre) */
|
|
109
|
+
.message-content code:not(pre code) {
|
|
110
|
+
background: rgba(147, 51, 234, 0.08);
|
|
111
|
+
color: #9333ea;
|
|
112
|
+
padding: 0.125rem 0.375rem;
|
|
113
|
+
border-radius: 0.25rem;
|
|
114
|
+
font-family: ui-monospace, monospace;
|
|
115
|
+
font-size: 0.8em;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.dark .message-content code:not(pre code) {
|
|
119
|
+
background: rgba(192, 132, 252, 0.1);
|
|
120
|
+
color: #c084fc;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
hr {
|
|
124
|
+
border: none;
|
|
125
|
+
border-top: 1px solid;
|
|
126
|
+
border-color: #e5e7eb;
|
|
127
|
+
margin: 0.75rem 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.dark hr {
|
|
131
|
+
border-color: #1f2937;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Code blocks inside user message bubbles (white text on purple bg) */
|
|
135
|
+
.user-message-prose code {
|
|
136
|
+
background: rgba(255, 255, 255, 0.18);
|
|
137
|
+
color: #f0e8ff;
|
|
138
|
+
padding: 0.1em 0.35em;
|
|
139
|
+
border-radius: 3px;
|
|
140
|
+
font-family: ui-monospace, monospace;
|
|
141
|
+
font-size: 0.85em;
|
|
142
|
+
}
|
|
143
|
+
.user-message-prose pre {
|
|
144
|
+
background: rgba(0, 0, 0, 0.35) !important;
|
|
145
|
+
border-radius: 6px;
|
|
146
|
+
margin: 0.5em 0;
|
|
147
|
+
padding: 0.75em 1em !important;
|
|
148
|
+
overflow-x: auto;
|
|
149
|
+
max-width: 100%;
|
|
150
|
+
white-space: pre-wrap !important;
|
|
151
|
+
word-break: break-all;
|
|
152
|
+
}
|
|
153
|
+
.user-message-prose pre code {
|
|
154
|
+
background: transparent;
|
|
155
|
+
padding: 0;
|
|
156
|
+
color: #e8d5ff;
|
|
157
|
+
font-size: 0.78rem !important;
|
|
158
|
+
}
|
|
159
|
+
.user-message-prose a {
|
|
160
|
+
color: #e9d5ff;
|
|
161
|
+
text-decoration: underline;
|
|
162
|
+
}
|
|
163
|
+
.user-message-prose p { margin: 0.25em 0; }
|
|
164
|
+
|
|
165
|
+
/* @mention highlight in message content — purple bold, no background, real link */
|
|
166
|
+
.glancer-mention {
|
|
167
|
+
display: inline;
|
|
168
|
+
background: none;
|
|
169
|
+
color: #7e22ce;
|
|
170
|
+
font-weight: 700;
|
|
171
|
+
padding: 0 0.25em;
|
|
172
|
+
border-radius: 0.2em;
|
|
173
|
+
text-decoration: none;
|
|
174
|
+
}
|
|
175
|
+
.glancer-mention:hover {
|
|
176
|
+
text-decoration: underline;
|
|
177
|
+
text-underline-offset: 2px;
|
|
178
|
+
}
|
|
179
|
+
.dark .glancer-mention {
|
|
180
|
+
background: none;
|
|
181
|
+
color: #c084fc;
|
|
182
|
+
}
|
|
183
|
+
/* Inside user purple bubble */
|
|
184
|
+
.user-message-prose .glancer-mention {
|
|
185
|
+
background: none;
|
|
186
|
+
color: #f0e8ff;
|
|
187
|
+
font-weight: 700;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* SQL editor textarea — matches Prism code block styling */
|
|
191
|
+
.glancer-sql-editor {
|
|
192
|
+
background: #1e1e2e !important;
|
|
193
|
+
color: #cdd6f4 !important;
|
|
194
|
+
font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace !important;
|
|
195
|
+
font-size: 0.75rem !important;
|
|
196
|
+
line-height: 1.6 !important;
|
|
197
|
+
tab-size: 2;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Code editor (contenteditable + Prism live highlighting) — always dark */
|
|
201
|
+
.glancer-code-editor {
|
|
202
|
+
background: #1e1e2e !important;
|
|
203
|
+
color: #cdd6f4 !important;
|
|
204
|
+
font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace !important;
|
|
205
|
+
font-size: 0.75rem !important;
|
|
206
|
+
line-height: 1.6 !important;
|
|
207
|
+
tab-size: 2;
|
|
208
|
+
padding: 0.75rem;
|
|
209
|
+
white-space: pre-wrap;
|
|
210
|
+
word-break: break-all;
|
|
211
|
+
min-height: 80px;
|
|
212
|
+
outline: none;
|
|
213
|
+
caret-color: #cdd6f4;
|
|
214
|
+
}
|
|
215
|
+
/* Prism injects spans — preserve wrapping */
|
|
216
|
+
.glancer-code-editor * {
|
|
217
|
+
white-space: pre-wrap;
|
|
218
|
+
word-break: break-all;
|
|
219
|
+
}
|
|
220
|
+
/* Token colours inside the editor (same Catppuccin palette as [data-sql-block]) */
|
|
221
|
+
.glancer-code-editor .token.comment,
|
|
222
|
+
.glancer-code-editor .token.prolog { color: #6c7086; font-style: italic; }
|
|
223
|
+
.glancer-code-editor .token.keyword { color: #cba6f7; }
|
|
224
|
+
.glancer-code-editor .token.string,
|
|
225
|
+
.glancer-code-editor .token.char { color: #a6e3a1; }
|
|
226
|
+
.glancer-code-editor .token.number,
|
|
227
|
+
.glancer-code-editor .token.boolean { color: #fab387; }
|
|
228
|
+
.glancer-code-editor .token.function { color: #89b4fa; }
|
|
229
|
+
.glancer-code-editor .token.class-name { color: #f38ba8; }
|
|
230
|
+
.glancer-code-editor .token.operator { color: #89dceb; }
|
|
231
|
+
.glancer-code-editor .token.punctuation { color: #9399b2; }
|
|
232
|
+
.glancer-code-editor .token.property,
|
|
233
|
+
.glancer-code-editor .token.tag { color: #f2cdcd; }
|
|
234
|
+
.glancer-code-editor .token.variable { color: #cdd6f4; }
|
|
235
|
+
.glancer-code-editor .token.atrule,
|
|
236
|
+
.glancer-code-editor .token.attr-value { color: #f5c2e7; }
|
|
237
|
+
.glancer-code-editor .token.selector { color: #a6e3a1; }
|
|
238
|
+
|
|
239
|
+
/* @ mention backdrop (highlights @table tokens behind the textarea) */
|
|
240
|
+
.mention-backdrop {
|
|
241
|
+
position: absolute;
|
|
242
|
+
top: 0;
|
|
243
|
+
left: 0;
|
|
244
|
+
right: 0;
|
|
245
|
+
pointer-events: none;
|
|
246
|
+
overflow: hidden;
|
|
247
|
+
color: transparent;
|
|
248
|
+
white-space: pre-wrap;
|
|
249
|
+
word-break: break-word;
|
|
250
|
+
z-index: 0;
|
|
251
|
+
}
|
|
252
|
+
.mention-backdrop mark.mention-hl {
|
|
253
|
+
background: rgba(147, 51, 234, 0.15);
|
|
254
|
+
border-radius: 0.2em;
|
|
255
|
+
color: transparent;
|
|
256
|
+
}
|
|
257
|
+
.dark .mention-backdrop mark.mention-hl {
|
|
258
|
+
background: rgba(192, 132, 252, 0.2);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* @ mention chip in chat form — links to schema page */
|
|
262
|
+
.mention-chip {
|
|
263
|
+
display: inline-flex;
|
|
264
|
+
align-items: center;
|
|
265
|
+
gap: 0.25rem;
|
|
266
|
+
background: none;
|
|
267
|
+
color: #7e22ce;
|
|
268
|
+
border: none;
|
|
269
|
+
border-radius: 9999px;
|
|
270
|
+
padding: 0.1rem 0.4rem;
|
|
271
|
+
font-size: 0.7rem;
|
|
272
|
+
font-weight: 700;
|
|
273
|
+
cursor: pointer;
|
|
274
|
+
text-decoration: none;
|
|
275
|
+
}
|
|
276
|
+
.mention-chip:hover {
|
|
277
|
+
text-decoration: underline;
|
|
278
|
+
text-underline-offset: 2px;
|
|
279
|
+
}
|
|
280
|
+
.dark .mention-chip {
|
|
281
|
+
background: none;
|
|
282
|
+
color: #c084fc;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* ── Fullscreen table dialog ─────────────────────────────────────────────── */
|
|
286
|
+
.glancer-fullscreen-dialog {
|
|
287
|
+
position: fixed;
|
|
288
|
+
inset: 0;
|
|
289
|
+
width: 100vw;
|
|
290
|
+
max-width: 100vw;
|
|
291
|
+
height: 100vh;
|
|
292
|
+
max-height: 100vh;
|
|
293
|
+
padding: 0;
|
|
294
|
+
margin: 0;
|
|
295
|
+
border: none;
|
|
296
|
+
background: transparent;
|
|
297
|
+
overflow: hidden;
|
|
298
|
+
}
|
|
299
|
+
.glancer-fullscreen-dialog::backdrop {
|
|
300
|
+
background: rgba(0, 0, 0, 0.6);
|
|
301
|
+
}
|
|
302
|
+
.glancer-fullscreen-inner {
|
|
303
|
+
display: flex;
|
|
304
|
+
flex-direction: column;
|
|
305
|
+
width: 100%;
|
|
306
|
+
height: 100%;
|
|
307
|
+
background: #fff;
|
|
308
|
+
padding: 0;
|
|
309
|
+
}
|
|
310
|
+
.dark .glancer-fullscreen-inner {
|
|
311
|
+
background: #111827;
|
|
312
|
+
}
|
|
313
|
+
.glancer-fullscreen-toolbar {
|
|
314
|
+
display: flex;
|
|
315
|
+
align-items: center;
|
|
316
|
+
justify-content: space-between;
|
|
317
|
+
padding: 0.75rem 1.25rem;
|
|
318
|
+
border-bottom: 1px solid #e5e7eb;
|
|
319
|
+
background: #f9fafb;
|
|
320
|
+
flex-shrink: 0;
|
|
321
|
+
}
|
|
322
|
+
.dark .glancer-fullscreen-toolbar {
|
|
323
|
+
border-bottom-color: #374151;
|
|
324
|
+
background: #1f2937;
|
|
325
|
+
}
|
|
326
|
+
.glancer-fullscreen-title {
|
|
327
|
+
font-size: 0.8rem;
|
|
328
|
+
font-weight: 600;
|
|
329
|
+
color: #6b7280;
|
|
330
|
+
text-transform: uppercase;
|
|
331
|
+
letter-spacing: 0.06em;
|
|
332
|
+
}
|
|
333
|
+
.dark .glancer-fullscreen-title { color: #9ca3af; }
|
|
334
|
+
.glancer-fullscreen-close {
|
|
335
|
+
display: flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
justify-content: center;
|
|
338
|
+
width: 2rem;
|
|
339
|
+
height: 2rem;
|
|
340
|
+
border-radius: 0.5rem;
|
|
341
|
+
color: #6b7280;
|
|
342
|
+
transition: background 0.15s, color 0.15s;
|
|
343
|
+
}
|
|
344
|
+
.glancer-fullscreen-close:hover { background: #f3f4f6; color: #111827; }
|
|
345
|
+
.dark .glancer-fullscreen-close:hover { background: #374151; color: #f9fafb; }
|
|
346
|
+
.glancer-fullscreen-body {
|
|
347
|
+
flex: 1;
|
|
348
|
+
overflow: auto;
|
|
349
|
+
padding: 1rem;
|
|
350
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.message-content ul,
|
|
2
|
+
.message-content ol {
|
|
3
|
+
padding-left: 1.5rem;
|
|
4
|
+
margin: 1rem 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.message-content ul {
|
|
8
|
+
list-style-type: disc;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.message-content ol {
|
|
12
|
+
list-style-type: decimal;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.message-content li {
|
|
16
|
+
margin-bottom: 0.5rem;
|
|
17
|
+
line-height: 1.5;
|
|
18
|
+
color: #374151; /* gray-700 */
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.dark .message-content li {
|
|
22
|
+
color: #d1d5db; /* gray-300 */
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.message-content li::marker {
|
|
26
|
+
color: #6b7280; /* gray-500 */
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dark .message-content li::marker {
|
|
30
|
+
color: #9ca3af; /* gray-400 */
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
::-webkit-scrollbar {
|
|
2
|
+
width: 8px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
::-webkit-scrollbar-track {
|
|
6
|
+
background: #1a1a2e;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
::-webkit-scrollbar-thumb {
|
|
10
|
+
background: #4b5563;
|
|
11
|
+
border-radius: 4px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
::-webkit-scrollbar-thumb:hover {
|
|
15
|
+
background: #6b7280;
|
|
16
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
table {
|
|
2
|
+
width: 100%;
|
|
3
|
+
border-collapse: collapse;
|
|
4
|
+
font-size: 0.875rem; /* Tailwind's text-sm */
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
thead {
|
|
8
|
+
background-color: #f9fafb; /* gray-50 */
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
thead th {
|
|
12
|
+
text-align: left;
|
|
13
|
+
padding: 0.75rem 1rem;
|
|
14
|
+
border-bottom: 1px solid #e5e7eb; /* gray-200 */
|
|
15
|
+
font-weight: 600;
|
|
16
|
+
color: #374151; /* gray-700 */
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
tbody td {
|
|
21
|
+
padding: 0.75rem 1rem;
|
|
22
|
+
border-bottom: 1px solid #e5e7eb; /* gray-200 */
|
|
23
|
+
color: #374151; /* gray-700 */
|
|
24
|
+
vertical-align: top;
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
tr:hover td {
|
|
29
|
+
background-color: #f3f4f6; /* gray-100 */
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Dark mode support */
|
|
33
|
+
.dark thead {
|
|
34
|
+
background-color: #1f2937; /* gray-800 */
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.dark thead th,
|
|
38
|
+
.dark tbody td {
|
|
39
|
+
border-color: #374151; /* gray-700 */
|
|
40
|
+
color: #d1d5db; /* gray-300 */
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.dark tr:hover td {
|
|
44
|
+
background-color: #374151; /* gray-700 */
|
|
45
|
+
}
|
|
46
|
+
.message-content table {
|
|
47
|
+
width: 100%;
|
|
48
|
+
border-collapse: collapse;
|
|
49
|
+
font-size: 0.875rem;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.message-content th,
|
|
53
|
+
.message-content td {
|
|
54
|
+
padding: 0.5rem 0.75rem;
|
|
55
|
+
border: 1px solid #e5e7eb;
|
|
56
|
+
text-align: left;
|
|
57
|
+
white-space: nowrap;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.message-content thead {
|
|
61
|
+
background-color: #f9fafb;
|
|
62
|
+
position: sticky;
|
|
63
|
+
top: 0;
|
|
64
|
+
z-index: 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.dark .message-content th,
|
|
68
|
+
.dark .message-content td {
|
|
69
|
+
border-color: #374151;
|
|
70
|
+
color: #d1d5db;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.dark .message-content thead {
|
|
74
|
+
background-color: #1f2937;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.table-scroll-wrapper {
|
|
78
|
+
overflow-x: auto;
|
|
79
|
+
max-width: 100%;
|
|
80
|
+
margin: 1rem 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.table-scroll-inner {
|
|
84
|
+
max-height: 28rem; /* cerca de 10 linhas */
|
|
85
|
+
overflow-y: auto;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Scrollbar melhorada (opcional) */
|
|
89
|
+
.table-scroll-inner::-webkit-scrollbar {
|
|
90
|
+
height: 6px;
|
|
91
|
+
width: 6px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.table-scroll-inner::-webkit-scrollbar-thumb {
|
|
95
|
+
background-color: rgba(100, 116, 139, 0.4); /* slate-500 */
|
|
96
|
+
border-radius: 3px;
|
|
97
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glancer
|
|
4
|
+
# :nodoc:
|
|
5
|
+
class ApplicationController < ::ApplicationController
|
|
6
|
+
before_action :configure_locale
|
|
7
|
+
|
|
8
|
+
# ActionController::Live runs each request in a new thread. Devise/Warden
|
|
9
|
+
# signals unauthenticated access via `throw :warden`, which Rack's middleware
|
|
10
|
+
# normally catches. Inside a Live thread that throw escapes as an
|
|
11
|
+
# UncaughtThrowError. Rescue it here so controllers that include Live (e.g.
|
|
12
|
+
# MessagesController) return a proper response instead of a 500.
|
|
13
|
+
rescue_from UncaughtThrowError do |e|
|
|
14
|
+
raise e unless defined?(::Warden) && e.tag == :warden
|
|
15
|
+
|
|
16
|
+
login_path = begin
|
|
17
|
+
main_app.new_user_session_path
|
|
18
|
+
rescue NoMethodError
|
|
19
|
+
"/"
|
|
20
|
+
end
|
|
21
|
+
request.format.html? ? redirect_to(login_path) : head(:unauthorized)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def configure_locale
|
|
27
|
+
lang = Glancer::Setting.get("ui_language", default: "en")
|
|
28
|
+
I18n.locale = lang.to_sym
|
|
29
|
+
rescue StandardError
|
|
30
|
+
I18n.locale = :en
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glancer
|
|
4
|
+
# :nodoc:
|
|
5
|
+
class ChatsController < Glancer::ApplicationController
|
|
6
|
+
layout "glancer/application"
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@chats = Glancer::Chat.order(created_at: :desc)
|
|
10
|
+
@chat = nil
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def show
|
|
14
|
+
@chat = Glancer::Chat.find_by(id: params[:id])
|
|
15
|
+
return redirect_to(glancer.root_path, alert: "Chat not found") unless @chat
|
|
16
|
+
|
|
17
|
+
@chats = Glancer::Chat.order(created_at: :desc)
|
|
18
|
+
@messages = @chat.messages.order(:created_at)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def start
|
|
22
|
+
content = params[:content].to_s.strip
|
|
23
|
+
return render(json: { error: "Message required" }, status: :unprocessable_entity) if content.blank?
|
|
24
|
+
|
|
25
|
+
@chat = Glancer::Chat.create!(title: "New Chat")
|
|
26
|
+
@message = @chat.messages.create!(role: :user, content: content)
|
|
27
|
+
|
|
28
|
+
@response_message = @chat.messages.create!(
|
|
29
|
+
role: :assistant,
|
|
30
|
+
content: "",
|
|
31
|
+
code_type: "sql",
|
|
32
|
+
user_message: @message,
|
|
33
|
+
status: :processing
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
Glancer::AsyncRunner.call(@response_message.id, content)
|
|
37
|
+
|
|
38
|
+
render json: { chat_id: @chat.id }
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
Glancer::Utils::Logger.error("ChatsController#start", e.message)
|
|
41
|
+
render json: { error: e.message }, status: :internal_server_error
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def destroy
|
|
45
|
+
Glancer::Chat.find(params[:id]).destroy!
|
|
46
|
+
redirect_to glancer.root_path
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|