query_lens 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 302bd0c222f001ae9997180641b03390b244395de3f4b5070b21be1812590fd8
4
- data.tar.gz: df5c31d6ab1ca79837cb0e433130b0666f45831666b96a704f4ed27ac5b22465
3
+ metadata.gz: 301dbff9b6c8506d45959771354536fea51393e70f56562f4f4a9890a167eda9
4
+ data.tar.gz: ac7ffd65456c5481f3d42caba3c1af9ab9ee1b8217e8d9cb220c6b7be9a55f86
5
5
  SHA512:
6
- metadata.gz: c70804e76cbb049fc5733868e6c0c53976a99960425c1894bd544faeff95e82d93fb6827da4ac3a3355694b478d4b92cb2586e4086f394e402087a6470e5afb1
7
- data.tar.gz: 60ba99c1ff1753974762f25f2a30def02d1fee229c334d1fcaac773e1dea3978f26a9faf9d0f2d8e2d1e2d4148caa94ae1773eda1f8a34f43b7a7f5b59521f42
6
+ metadata.gz: 79c4a4ce7fd9fc2838698b92a1b34bc25f6d97b65845c22cbef2519c0342a5f899700171145a04ae3a35a14de316b2c13fdb906e1b581ea11f8a730773c32abf
7
+ data.tar.gz: 1fb00ec3b9f5acf320dfd1a5e973e0f6e2c23f99b6e64f4e65312b54e860db2a43deaf5f2dcb70d83fdf962a0794d4281e13745c931064fb94e86ef52dfb8fa5
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # QueryLens
2
2
 
3
+ **[querylens.ai](https://querylens.ai)**
4
+
3
5
  A mountable Rails engine that lets users write natural language questions and get SQL queries generated by AI, executed against their database, with results displayed — all in one interface. Think "Blazer meets ChatGPT."
4
6
 
5
7
  Powered by [RubyLLM](https://rubyllm.com), QueryLens works with any major AI provider: OpenAI, Anthropic (Claude), Google Gemini, DeepSeek, Mistral, Ollama (local models), and more.
@@ -21,7 +23,7 @@ Powered by [RubyLLM](https://rubyllm.com), QueryLens works with any major AI pro
21
23
  - Interactive conversation with context (follow-up questions refine queries)
22
24
  - Read-only query execution (safety enforced at transaction level)
23
25
  - Editable SQL editor with syntax highlighting
24
- - Results displayed as sortable tables
26
+ - Results displayed as sortable tables with **Chart.js charting** (bar, line, pie, scatter) and auto-detected chart types
25
27
  - Configurable authentication, timeouts, and row limits
26
28
  - Zero frontend dependencies (self-contained CSS, vanilla JS)
27
29
 
@@ -5,6 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>QueryLens</title>
7
7
  <%= csrf_meta_tags %>
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
8
9
  <style>
9
10
  /* ── Reset & Base ── */
10
11
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
@@ -511,6 +512,47 @@
511
512
  color: #ca8a04; font-size: 11px; font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
512
513
  }
513
514
 
515
+ /* ── Chart Toggle & Controls ── */
516
+ .ql-chart-toggle {
517
+ display: inline-flex; border-radius: 8px; overflow: hidden;
518
+ background: #1c1c21; border: 1px solid #27272a; margin-left: 12px;
519
+ }
520
+ .ql-chart-toggle-btn {
521
+ padding: 4px 14px; font-size: 12px; font-weight: 600; border: none;
522
+ background: transparent; color: #71717a; cursor: pointer;
523
+ transition: all 0.15s; letter-spacing: 0.01em;
524
+ }
525
+ .ql-chart-toggle-btn:hover { color: #a1a1aa; }
526
+ .ql-chart-toggle-btn.active {
527
+ background: #6366f1; color: #fff;
528
+ }
529
+ .ql-chart-controls {
530
+ display: flex; align-items: center; gap: 16px; padding: 10px 20px;
531
+ background: #141417; border-bottom: 1px solid #27272a; flex-shrink: 0;
532
+ }
533
+ .ql-chart-control-group {
534
+ display: flex; align-items: center; gap: 6px;
535
+ }
536
+ .ql-chart-label {
537
+ font-size: 11px; font-weight: 600; color: #71717a;
538
+ text-transform: uppercase; letter-spacing: 0.04em;
539
+ }
540
+ .ql-chart-select {
541
+ padding: 5px 28px 5px 10px; font-size: 12px; font-family: inherit;
542
+ background: #0f0f12; color: #d4d4d8; border: 1px solid #3f3f46;
543
+ border-radius: 6px; outline: none; cursor: pointer;
544
+ appearance: none; -webkit-appearance: none;
545
+ background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%2371717a' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
546
+ background-repeat: no-repeat; background-position: right 8px center;
547
+ transition: border-color 0.15s;
548
+ }
549
+ .ql-chart-select:focus { border-color: #6366f1; }
550
+ .ql-chart-container {
551
+ display: flex; align-items: center; justify-content: center;
552
+ height: 360px; padding: 16px 20px;
553
+ }
554
+ .ql-chart-container canvas { max-height: 100%; }
555
+
514
556
  /* ── Animations ── */
515
557
  @keyframes ql-spin { to { transform: rotate(360deg); } }
516
558
  @keyframes ql-fadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
@@ -178,6 +178,8 @@
178
178
  let conversation = [];
179
179
  let currentConversationId = null;
180
180
  let conversationsList = [];
181
+ let chartInstance = null;
182
+ let lastResultData = null;
181
183
 
182
184
  // ── Load restricted tables ──
183
185
  (async function loadInfo() {
@@ -286,6 +288,10 @@
286
288
  if (!sql) return;
287
289
 
288
290
  setRunning(true);
291
+ destroyChart();
292
+ lastResultData = null;
293
+ const oldToggle = document.querySelector('.ql-chart-toggle');
294
+ if (oldToggle) oldToggle.remove();
289
295
  el.results.innerHTML = '<div class="ql-loading-row"><div class="ql-spinner"></div>Running query...</div>';
290
296
  el.rowCount.textContent = '';
291
297
 
@@ -324,33 +330,260 @@
324
330
  }
325
331
  }
326
332
 
333
+ // ── Chart Constants ──
334
+ const CHART_COLORS = ['#818CF8','#F472B6','#34D399','#FBBF24','#60A5FA','#A78BFA','#FB923C','#2DD4BF','#E879F9','#4ADE80'];
335
+
336
+ // ── Column Classification ──
337
+ function classifyColumns(data) {
338
+ const sampleRows = data.rows.slice(0, 20);
339
+ return data.columns.map((col, i) => {
340
+ const values = sampleRows.map(r => r[i]).filter(v => v !== null && v !== undefined && String(v).trim() !== '');
341
+ if (values.length === 0) return { name: col, index: i, type: 'categorical' };
342
+ const dateCount = values.filter(v => /^\d{4}-\d{2}-\d{2}/.test(String(v))).length;
343
+ if (dateCount / values.length > 0.7) return { name: col, index: i, type: 'date' };
344
+ const numCount = values.filter(v => !isNaN(Number(v))).length;
345
+ if (numCount / values.length > 0.7) return { name: col, index: i, type: 'numeric' };
346
+ return { name: col, index: i, type: 'categorical' };
347
+ });
348
+ }
349
+
350
+ function isChartable(classified) {
351
+ if (classified.length < 2) return false;
352
+ return classified.some(c => c.type === 'numeric');
353
+ }
354
+
355
+ function autoDetectChart(classified, data) {
356
+ const dates = classified.filter(c => c.type === 'date');
357
+ const nums = classified.filter(c => c.type === 'numeric');
358
+ const cats = classified.filter(c => c.type === 'categorical');
359
+
360
+ let type = 'bar', xCol = classified[0], yCol = nums[0];
361
+
362
+ if (dates.length > 0 && nums.length > 0) {
363
+ type = 'line'; xCol = dates[0]; yCol = nums[0];
364
+ } else if (cats.length > 0 && nums.length > 0) {
365
+ const uniqueLabels = new Set(data.rows.map(r => r[cats[0].index]));
366
+ if (uniqueLabels.size <= 10) { type = 'pie'; xCol = cats[0]; yCol = nums[0]; }
367
+ else { type = 'bar'; xCol = cats[0]; yCol = nums[0]; }
368
+ } else if (nums.length >= 2 && cats.length === 0 && dates.length === 0) {
369
+ type = 'scatter'; xCol = nums[0]; yCol = nums[1];
370
+ }
371
+
372
+ return { type, xIndex: xCol.index, yIndex: yCol.index };
373
+ }
374
+
375
+ // ── Chart Rendering ──
376
+ function destroyChart() {
377
+ if (chartInstance) { chartInstance.destroy(); chartInstance = null; }
378
+ }
379
+
380
+ function renderChart(data, chartType, xIndex, yIndex) {
381
+ destroyChart();
382
+ const container = document.getElementById('ql-chart-container');
383
+ if (!container) return;
384
+ container.innerHTML = '<canvas id="ql-chart-canvas"></canvas>';
385
+ const canvas = document.getElementById('ql-chart-canvas');
386
+ const ctx = canvas.getContext('2d');
387
+
388
+ const labels = data.rows.map(r => r[xIndex] === null ? '' : String(r[xIndex]));
389
+ const values = data.rows.map(r => r[yIndex] === null ? null : Number(r[yIndex]));
390
+ const xLabel = data.columns[xIndex];
391
+ const yLabel = data.columns[yIndex];
392
+
393
+ const baseOpts = {
394
+ responsive: true, maintainAspectRatio: false,
395
+ plugins: {
396
+ legend: { labels: { color: '#a1a1aa', font: { size: 12 } } },
397
+ tooltip: { backgroundColor: '#27272a', titleColor: '#e4e4e7', bodyColor: '#d4d4d8', borderColor: '#3f3f46', borderWidth: 1 }
398
+ }
399
+ };
400
+
401
+ let config;
402
+
403
+ if (chartType === 'pie') {
404
+ config = {
405
+ type: 'pie',
406
+ data: {
407
+ labels: labels,
408
+ datasets: [{ data: values, backgroundColor: CHART_COLORS.slice(0, labels.length), borderColor: '#0f0f12', borderWidth: 2 }]
409
+ },
410
+ options: { ...baseOpts, plugins: { ...baseOpts.plugins, legend: { position: 'right', labels: { color: '#a1a1aa', font: { size: 11 }, padding: 12 } } } }
411
+ };
412
+ } else if (chartType === 'scatter') {
413
+ const points = data.rows.map(r => ({
414
+ x: r[xIndex] === null ? null : Number(r[xIndex]),
415
+ y: r[yIndex] === null ? null : Number(r[yIndex])
416
+ })).filter(p => p.x !== null && p.y !== null);
417
+ config = {
418
+ type: 'scatter',
419
+ data: { datasets: [{ data: points, backgroundColor: CHART_COLORS[0], borderColor: CHART_COLORS[0], pointRadius: 4 }] },
420
+ options: {
421
+ ...baseOpts,
422
+ plugins: { ...baseOpts.plugins, legend: { display: false } },
423
+ scales: {
424
+ x: { title: { display: true, text: xLabel, color: '#a1a1aa' }, ticks: { color: '#71717a' }, grid: { color: '#1c1c21' } },
425
+ y: { title: { display: true, text: yLabel, color: '#a1a1aa' }, ticks: { color: '#71717a' }, grid: { color: '#1c1c21' } }
426
+ }
427
+ }
428
+ };
429
+ } else {
430
+ // bar or line
431
+ config = {
432
+ type: chartType,
433
+ data: {
434
+ labels: labels,
435
+ datasets: [{
436
+ label: yLabel,
437
+ data: values,
438
+ backgroundColor: chartType === 'line' ? 'rgba(129,140,248,0.15)' : CHART_COLORS[0],
439
+ borderColor: CHART_COLORS[0],
440
+ borderWidth: chartType === 'line' ? 2 : 0,
441
+ fill: chartType === 'line',
442
+ tension: 0.3,
443
+ pointBackgroundColor: CHART_COLORS[0],
444
+ pointRadius: chartType === 'line' ? 3 : 0,
445
+ borderRadius: chartType === 'bar' ? 4 : 0
446
+ }]
447
+ },
448
+ options: {
449
+ ...baseOpts,
450
+ plugins: { ...baseOpts.plugins, legend: { display: false } },
451
+ scales: {
452
+ x: { ticks: { color: '#71717a', maxRotation: 45, font: { size: 11 } }, grid: { color: '#1c1c21' } },
453
+ y: { ticks: { color: '#71717a', font: { size: 11 } }, grid: { color: '#1c1c21' }, beginAtZero: true }
454
+ }
455
+ }
456
+ };
457
+ }
458
+
459
+ chartInstance = new Chart(ctx, config);
460
+ }
461
+
327
462
  // ── Render Results ──
328
463
  function renderResults(data) {
464
+ destroyChart();
465
+ lastResultData = null;
466
+
329
467
  if (!data.columns || !data.columns.length) {
330
468
  el.results.innerHTML = '<div class="ql-results-empty"><span>Query returned no results</span></div>';
331
469
  el.rowCount.textContent = '';
470
+ // Remove chart toggle if present
471
+ const oldToggle = document.querySelector('.ql-chart-toggle');
472
+ if (oldToggle) oldToggle.remove();
332
473
  return;
333
474
  }
334
475
 
335
- let html = '';
336
- if (data.truncated) html += '<div class="ql-truncated">Results truncated to ' + data.row_count + ' rows</div>';
476
+ // Build table HTML
477
+ let tableHtml = '';
478
+ if (data.truncated) tableHtml += '<div class="ql-truncated">Results truncated to ' + data.row_count + ' rows</div>';
337
479
 
338
- html += '<table class="ql-table"><thead><tr>';
339
- data.columns.forEach(c => { html += '<th>' + esc(c) + '</th>'; });
340
- html += '</tr></thead><tbody>';
480
+ tableHtml += '<table class="ql-table"><thead><tr>';
481
+ data.columns.forEach(c => { tableHtml += '<th>' + esc(c) + '</th>'; });
482
+ tableHtml += '</tr></thead><tbody>';
341
483
 
342
484
  data.rows.forEach(row => {
343
- html += '<tr>';
485
+ tableHtml += '<tr>';
344
486
  row.forEach(cell => {
345
- if (cell === null) html += '<td><span class="ql-null">NULL</span></td>';
346
- else html += '<td>' + esc(String(cell)) + '</td>';
487
+ if (cell === null) tableHtml += '<td><span class="ql-null">NULL</span></td>';
488
+ else tableHtml += '<td>' + esc(String(cell)) + '</td>';
347
489
  });
348
- html += '</tr>';
490
+ tableHtml += '</tr>';
349
491
  });
492
+ tableHtml += '</tbody></table>';
493
+
494
+ // Check if chartable
495
+ const classified = classifyColumns(data);
496
+ const chartable = isChartable(classified);
497
+
498
+ // Build results area: table wrapper + chart container (hidden)
499
+ let resultsHtml = '<div id="ql-table-wrap">' + tableHtml + '</div>';
500
+ if (chartable) {
501
+ resultsHtml += '<div id="ql-chart-controls" class="ql-chart-controls" style="display:none;">';
502
+ resultsHtml += '<div class="ql-chart-control-group"><span class="ql-chart-label">Type</span>';
503
+ resultsHtml += '<select class="ql-chart-select" id="ql-chart-type-select"><option value="bar">Bar</option><option value="line">Line</option><option value="pie">Pie</option><option value="scatter">Scatter</option></select></div>';
504
+ resultsHtml += '<div class="ql-chart-control-group"><span class="ql-chart-label">X Axis</span>';
505
+ resultsHtml += '<select class="ql-chart-select" id="ql-chart-x-select">';
506
+ data.columns.forEach((c, i) => { resultsHtml += '<option value="' + i + '">' + esc(c) + '</option>'; });
507
+ resultsHtml += '</select></div>';
508
+ resultsHtml += '<div class="ql-chart-control-group"><span class="ql-chart-label">Y Axis</span>';
509
+ resultsHtml += '<select class="ql-chart-select" id="ql-chart-y-select">';
510
+ data.columns.forEach((c, i) => { resultsHtml += '<option value="' + i + '">' + esc(c) + '</option>'; });
511
+ resultsHtml += '</select></div>';
512
+ resultsHtml += '</div>';
513
+ resultsHtml += '<div id="ql-chart-container" class="ql-chart-container" style="display:none;"></div>';
514
+ }
350
515
 
351
- html += '</tbody></table>';
352
- el.results.innerHTML = html;
516
+ el.results.innerHTML = resultsHtml;
353
517
  el.rowCount.textContent = data.row_count + ' row' + (data.row_count === 1 ? '' : 's') + (data.truncated ? ' (truncated)' : '');
518
+
519
+ // Add or remove toggle in toolbar
520
+ const oldToggle = document.querySelector('.ql-chart-toggle');
521
+ if (oldToggle) oldToggle.remove();
522
+
523
+ if (chartable) {
524
+ lastResultData = data;
525
+ const auto = autoDetectChart(classified, data);
526
+
527
+ // Create toggle in results toolbar
528
+ const toolbar = document.querySelector('.ql-results-toolbar');
529
+ const toggle = document.createElement('div');
530
+ toggle.className = 'ql-chart-toggle';
531
+ toggle.innerHTML = '<button class="ql-chart-toggle-btn active" data-view="table">Table</button>' +
532
+ '<button class="ql-chart-toggle-btn" data-view="chart">Chart</button>';
533
+ toolbar.querySelector('.ql-results-title').after(toggle);
534
+
535
+ // Set auto-detected defaults on dropdowns
536
+ document.getElementById('ql-chart-type-select').value = auto.type;
537
+ document.getElementById('ql-chart-x-select').value = auto.xIndex;
538
+ document.getElementById('ql-chart-y-select').value = auto.yIndex;
539
+
540
+ let chartRendered = false;
541
+
542
+ // Toggle behavior
543
+ toggle.addEventListener('click', (e) => {
544
+ const btn = e.target.closest('.ql-chart-toggle-btn');
545
+ if (!btn) return;
546
+ const view = btn.dataset.view;
547
+ toggle.querySelectorAll('.ql-chart-toggle-btn').forEach(b => b.classList.remove('active'));
548
+ btn.classList.add('active');
549
+
550
+ const tableWrap = document.getElementById('ql-table-wrap');
551
+ const chartControls = document.getElementById('ql-chart-controls');
552
+ const chartContainer = document.getElementById('ql-chart-container');
553
+
554
+ if (view === 'table') {
555
+ if (tableWrap) tableWrap.style.display = '';
556
+ if (chartControls) chartControls.style.display = 'none';
557
+ if (chartContainer) chartContainer.style.display = 'none';
558
+ } else {
559
+ if (tableWrap) tableWrap.style.display = 'none';
560
+ if (chartControls) chartControls.style.display = 'flex';
561
+ if (chartContainer) chartContainer.style.display = 'flex';
562
+ if (!chartRendered) {
563
+ renderChart(data,
564
+ document.getElementById('ql-chart-type-select').value,
565
+ parseInt(document.getElementById('ql-chart-x-select').value, 10),
566
+ parseInt(document.getElementById('ql-chart-y-select').value, 10)
567
+ );
568
+ chartRendered = true;
569
+ }
570
+ }
571
+ });
572
+
573
+ // Re-render chart on control changes
574
+ const rerender = () => {
575
+ if (!lastResultData) return;
576
+ chartRendered = true;
577
+ renderChart(lastResultData,
578
+ document.getElementById('ql-chart-type-select').value,
579
+ parseInt(document.getElementById('ql-chart-x-select').value, 10),
580
+ parseInt(document.getElementById('ql-chart-y-select').value, 10)
581
+ );
582
+ };
583
+ document.getElementById('ql-chart-type-select').addEventListener('change', rerender);
584
+ document.getElementById('ql-chart-x-select').addEventListener('change', rerender);
585
+ document.getElementById('ql-chart-y-select').addEventListener('change', rerender);
586
+ }
354
587
  }
355
588
 
356
589
  // ── Messages ──
@@ -533,7 +766,11 @@
533
766
  // Restore SQL editor
534
767
  el.sql.value = data.last_sql || '';
535
768
 
536
- // Clear results
769
+ // Clear results and chart
770
+ destroyChart();
771
+ lastResultData = null;
772
+ const oldToggle = document.querySelector('.ql-chart-toggle');
773
+ if (oldToggle) oldToggle.remove();
537
774
  el.results.innerHTML = '<div class="ql-results-empty"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 0 1-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0 1 12 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m17.25-3.75h-7.5c-.621 0-1.125.504-1.125 1.125m8.625-1.125c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M12 10.875v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125M13.125 12h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125M20.625 12c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5M12 14.625v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 14.625c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m0 0v.375"/></svg><span>Run a query to see results</span></div>';
538
775
  el.rowCount.textContent = '';
539
776
 
@@ -544,6 +781,10 @@
544
781
  function startNewChat() {
545
782
  currentConversationId = null;
546
783
  conversation = [];
784
+ destroyChart();
785
+ lastResultData = null;
786
+ const oldToggle = document.querySelector('.ql-chart-toggle');
787
+ if (oldToggle) oldToggle.remove();
547
788
  el.messages.innerHTML = '<div class="ql-empty-state" id="ql-empty-state"><div class="ql-empty-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 6v12m-3-2.818.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/></svg></div><div class="ql-empty-title">Ask about your data</div><div class="ql-empty-text">Try something like:<br>"How many users signed up this month?"<br>"What\'s the total revenue by plan?"</div></div>';
548
789
  el.sql.value = '';
549
790
  el.results.innerHTML = '<div class="ql-results-empty"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 0 1-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0 1 12 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m17.25-3.75h-7.5c-.621 0-1.125.504-1.125 1.125m8.625-1.125c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M12 10.875v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125M13.125 12h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125M20.625 12c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5M12 14.625v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 14.625c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m0 0v.375"/></svg><span>Run a query to see results</span></div>';
@@ -1,3 +1,3 @@
1
1
  module QueryLens
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: query_lens
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Beshore
@@ -114,13 +114,13 @@ files:
114
114
  - lib/query_lens/configuration.rb
115
115
  - lib/query_lens/engine.rb
116
116
  - lib/query_lens/version.rb
117
- homepage: https://github.com/bryanbeshore/query_lens
117
+ homepage: https://querylens.ai
118
118
  licenses:
119
119
  - MIT
120
120
  metadata:
121
- homepage_uri: https://github.com/bryanbeshore/query_lens
121
+ homepage_uri: https://querylens.ai
122
122
  source_code_uri: https://github.com/bryanbeshore/query_lens
123
- changelog_uri: https://github.com/bryanbeshore/query_lens/blob/main/CHANGELOG.md
123
+ changelog_uri: https://querylens.ai/blob/main/CHANGELOG.md
124
124
  rdoc_options: []
125
125
  require_paths:
126
126
  - lib