QueryLensV2b / web /static /index.html
Aniruddha7's picture
Full deploy of QueryLensV2 (code + demo db) to a fresh Space
19dfd32 verified
Raw History Blame Contribute Delete
49.4 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>QueryLens — Transform natural language into SQL</title>
<link rel="icon"
href='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><rect width="48" height="48" rx="10" fill="%230F151F"/><g stroke="%2322D3EE" stroke-width="2.6" fill="none"><ellipse cx="20" cy="14" rx="10.5" ry="4"/><path d="M9.5 14v20c0 2.2 4.7 4 10.5 4s10.5-1.8 10.5-4V14"/><path d="M9.5 23c0 2.2 4.7 4 10.5 4s10.5-1.8 10.5-4"/></g><circle cx="32.5" cy="27.5" r="8.2" fill="rgba(34,211,238,.18)" stroke="%237DECFB" stroke-width="2.4"/><path d="M38.6 33.6L44.4 39.4" stroke="%237DECFB" stroke-width="2.8" stroke-linecap="round"/></svg>'>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;600&display=swap"
rel="stylesheet">
<style>
:root {
/* ── Graphite Halo — professional dark w/ electric cyan ────── */
--accent: #22D3EE; /* electric cyan (primary) */
--accent-bright: #7DECFB;
--accent-ink: #05242B; /* text on cyan */
--accent-soft: rgba(34, 211, 238, 0.12);
--violet: #A78BFA; /* secondary data accent */
--ocean: #60A5FA; /* info */
--bg-page: #0A0E15;
--bg-raised: #0E141F;
--panel: #111927;
--panel-inset: #0B101A;
--border: #1F2A3A;
--border-light: #17202D;
--border-strong: #2A3A50;
--text-primary: #E8EEF7;
--text-secondary: #9DAFC4;
--text-tertiary: #64788F;
--success: #34D399;
--warning: #FBBF24;
--error: #F87171;
/* Spacing */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-5: 1.25rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-10: 2.5rem;
/* Shadows — cool, tight, professional */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.35);
--shadow-md: 0 4px 14px -4px rgb(0 0 0 / 0.45), 0 1px 3px -1px rgb(0 0 0 / 0.3);
--shadow-glow: 0 0 18px rgb(34 211 238 / 0.22);
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 20px;
--transition-fast: 160ms ease;
--transition-normal: 240ms ease;
}
/* ── Light mode (paper) — same multi-hue system, calmer ambient ── */
html.light {
--bg-page: #F2F5FA;
--bg-raised: #FFFFFF;
--panel: #FFFFFF;
--panel-inset: #F5F8FC;
--border: #DFE6EF;
--border-light: #EAEFF6;
--border-strong: #C9D4E2;
--text-primary: #172231;
--text-secondary: #4C5D75;
--text-tertiary: #8091A6;
--shadow-sm: 0 1px 2px 0 rgb(16 24 40 / 0.07);
--shadow-md: 0 4px 14px -4px rgb(16 24 40 / 0.12), 0 1px 3px -1px rgb(16 24 40 / 0.08);
--shadow-glow: 0 0 18px rgb(34 211 238 / 0.28);
}
html.light ::-webkit-scrollbar-thumb { background: #C3CEDC; }
html.light ::selection { background: rgb(34 211 238 / .25); color: var(--text-primary); }
html.light body {
background:
radial-gradient(1100px 420px at 50% -220px, rgb(34 211 238 / 0.08), transparent 70%),
radial-gradient(900px 400px at 8% 108%, rgb(167 139 250 / 0.05), transparent 70%),
var(--bg-page);
}
html.light .brand-icon {
background: linear-gradient(150deg, #DFF7FA 0%, #CBEDF5 100%);
border-color: rgb(8 145 178 / .35);
}
html.light .brand-text h1 .lens,
html.light .header-tag,
html.light .profile-btn:hover { color: #0E7490; }
html.light .profile-btn:hover { border-color: rgb(8 145 178 / .45); }
html.light .table-resize-btn:hover { border-color: rgb(8 145 178 / .5); color: #0E7490; }
html.light .answer-table thead th,
html.light .answer-content th { color: #0E7490; }
html.light .answer-table tbody tr:hover td { background: rgb(34 211 238 / .07); }
html.light code.language-sql, html.light pre.sql-content code { color: #24435C; }
html.light .c-cyan { color: #0E7490; background: rgb(14 116 144 / .09); border-color: rgb(14 116 144 / .28); }
html.light .c-violet { color: #7C3AED; background: rgb(124 58 237 / .08); border-color: rgb(124 58 237 / .26); }
html.light .c-emerald { color: #059669; background: rgb(5 150 105 / .09); border-color: rgb(5 150 105 / .28); }
html.light .c-blue { color: #2563EB; background: rgb(37 99 235 / .08); border-color: rgb(37 99 235 / .26); }
html.light .c-amber { color: #B45309; background: rgb(180 83 9 / .09); border-color: rgb(180 83 9 / .28); }
html.light .c-rose { color: #DB2777; background: rgb(219 39 119 / .08); border-color: rgb(219 39 119 / .26); }
html.light .btn-outline-violet { color: #7C3AED; }
html.light .btn-outline-violet:hover { background: rgb(124 58 237 / .08); border-color: rgb(124 58 237 / .7); color: #6D28D9; }
html.light .btn-outline-rose { color: #DB2777; }
html.light .btn-outline-rose:hover { background: rgb(219 39 119 / .08); border-color: rgb(219 39 119 / .7); color: #9D174D; }
html.light .btn-outline-amber { color: #B45309; }
html.light .btn-outline-amber:hover { background: rgb(180 83 9 / .08); border-color: rgb(180 83 9 / .7); color: #92400E; }
html.light .profile-btn.primary { border-color: rgb(5 150 105 / .55); color: #059669; }
html.light .profile-btn.primary:hover { background: rgb(5 150 105 / .09); border-color: #059669; color: #047857; }
html.light .db-badge { color: #059669; background: rgb(5 150 105 / .09); border-color: rgb(5 150 105 / .30); }
/* ── Theme toggle (header) ─────────────────────────────────── */
.header-right { display: inline-flex; align-items: center; gap: 10px; }
.theme-toggle {
width: 34px; height: 34px; border-radius: var(--radius-sm);
display: inline-flex; align-items: center; justify-content: center;
cursor: pointer; color: var(--text-secondary);
background: var(--panel); border: 1px solid var(--border-strong);
transition: color var(--transition-fast), border-color var(--transition-fast),
box-shadow var(--transition-fast);
}
.theme-toggle:hover { color: var(--accent); border-color: rgb(34 211 238 / .45); box-shadow: 0 0 10px rgb(34 211 238 / .25); }
.theme-toggle .icon-moon { display: none; }
html.light .theme-toggle { color: #0E7490; border-color: rgb(8 145 178 / .35); }
html.light .theme-toggle:hover { color: #0E7490; border-color: rgb(8 145 178 / .6); box-shadow: 0 0 10px rgb(8 145 178 / .22); }
html.light .theme-toggle .icon-sun { display: none; }
html.light .theme-toggle .icon-moon { display: inline; }
*, *::before, *::after { box-sizing: border-box; }
html, body {
height: 100%;
margin: 0;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background:
radial-gradient(1100px 420px at 50% -220px, rgb(34 211 238 / 0.07), transparent 70%),
radial-gradient(900px 400px at 8% 108%, rgb(167 139 250 / 0.06), transparent 70%),
var(--bg-page);
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 15.5px;
line-height: 1.6;
}
h1, h2, h3, h4 {
font-family: 'Space Grotesk', 'Inter', sans-serif;
font-weight: 700;
letter-spacing: -0.015em;
}
::selection { background: rgba(34, 211, 238, 0.30); color: var(--text-primary); }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: #253349; border-radius: 8px; border: 2px solid var(--bg-page); }
::-webkit-scrollbar-track { background: transparent; }
/* ── Layout ──────────────────────────────────────────────────── */
.app-container {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.header {
background: var(--bg-page);
border-bottom: 1px solid var(--border-light);
position: relative;
}
/* Tron-inspired hairline: a single cyan energy line along the top edge */
.header::before {
content: "";
position: absolute;
inset: 0 0 auto 0;
height: 2px;
background: linear-gradient(90deg,
transparent 2%, rgb(34 211 238 / .55) 30%, rgb(34 211 238 / .15) 65%, transparent 98%);
pointer-events: none;
}
/* Header shares exact alignment with .main-content so the brand line
up with the query card edge breaks. */
.header-content {
max-width: 1240px;
margin: 0 auto;
padding: var(--space-4) var(--space-8);
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
}
.brand { display: flex; align-items: center; gap: 0.85rem; }
.brand-icon {
width: 42px; height: 42px;
border-radius: var(--radius-md);
background: linear-gradient(150deg, #0F2A35 0%, #123441 100%);
border: 1px solid rgb(34 211 238 / 0.35);
color: var(--accent-bright);
display: grid; place-items: center;
box-shadow: var(--shadow-glow);
}
.brand-text h1 {
margin: 0; font-size: 1.3rem; line-height: 1.2; color: var(--text-primary);
}
.brand-text h1 .lens { color: var(--accent); }
.brand-text p {
margin: 1px 0 0; font-size: 0.82rem; color: var(--text-tertiary);
letter-spacing: 0.01em;
}
.header-tag {
font-family: 'JetBrains Mono', monospace;
font-size: 0.74rem;
color: var(--accent);
border: 1px solid rgb(34 211 238 / 0.30);
background: var(--accent-soft);
border-radius: 999px;
padding: 0.3rem 0.8rem;
white-space: nowrap;
}
.main-content {
max-width: 1240px;
width: 100%;
margin: 0 auto;
padding: var(--space-6) var(--space-8) var(--space-10);
flex: 1;
}
/* ── Query composer ──────────────────────────────────────────── */
.query-section { margin-bottom: var(--space-6); }
.query-body {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
padding: var(--space-5);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.query-body:focus-within {
border-color: rgb(34 211 238 / 0.55);
box-shadow: 0 0 0 3px rgb(34 211 238 / 0.14), var(--shadow-md);
}
.query-textarea {
width: 100%;
min-height: 96px;
border: none;
outline: none;
resize: vertical;
font-family: inherit;
font-size: 1.02rem;
color: var(--text-primary);
background: transparent;
line-height: 1.55;
}
.query-textarea::placeholder { color: var(--text-tertiary); }
.query-controls {
display: flex; align-items: center; gap: var(--space-3);
margin-top: var(--space-3);
padding-top: var(--space-3);
border-top: 1px solid var(--border-light);
flex-wrap: wrap;
}
.mode-select {
font-family: inherit;
font-size: 0.86rem;
color: var(--text-secondary);
background: var(--panel-inset);
border: 1px solid var(--border-strong);
border-radius: var(--radius-sm);
padding: 0.42rem 0.6rem;
cursor: pointer;
}
.btn {
display: inline-flex; align-items: center; gap: 0.45rem;
font-family: inherit; font-size: 0.9rem; font-weight: 600;
border-radius: var(--radius-sm);
padding: 0.5rem 1.05rem;
cursor: pointer;
border: 1px solid transparent;
transition: background var(--transition-fast), border-color var(--transition-fast),
transform var(--transition-fast), opacity var(--transition-fast),
box-shadow var(--transition-fast);
}
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.55; cursor: not-allowed; }
.btn-primary {
background: var(--accent);
color: var(--accent-ink);
border-color: var(--accent);
box-shadow: 0 0 14px rgb(34 211 238 / 0.25);
}
.btn-primary:hover:not(:disabled) {
background: var(--accent-bright);
border-color: var(--accent-bright);
box-shadow: 0 0 20px rgb(34 211 238 / 0.4);
}
.btn-secondary {
background: transparent;
color: var(--text-secondary);
border-color: var(--border-strong);
}
.btn-secondary:hover:not(:disabled) {
background: var(--accent-soft);
border-color: rgb(34 211 238 / 0.45);
color: var(--text-primary);
}
.status-area {
margin-left: auto;
display: flex; align-items: center; gap: 0.8rem;
font-size: 0.84rem; color: var(--text-tertiary);
}
.spinner {
width: 16px; height: 16px;
border: 2px solid rgb(34 211 238 / 0.25);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.hidden { display: none !important; }
/* ── Results grid ────────────────────────────────────────────── */
.results-grid {
display: grid;
grid-template-columns: minmax(0, 1fr) 340px;
gap: var(--space-6);
align-items: start;
}
@media (max-width: 1020px) {
.results-grid { grid-template-columns: 1fr; }
}
.card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
overflow: hidden;
}
.card + .card { margin-top: var(--space-6); }
.card-header {
display: flex; align-items: center; gap: 0.5rem;
padding: 0.95rem var(--space-5);
border-bottom: 1px solid var(--border-light);
background: var(--bg-raised);
}
.card-title {
margin: 0; font-size: 0.98rem; color: var(--text-primary);
display: flex; align-items: center; gap: 0.5rem;
font-family: 'Space Grotesk', 'Inter', sans-serif;
}
.card-content { padding: var(--space-5); }
.answer-content { white-space: normal; color: var(--text-primary); }
/* ── Chart box ───────────────────────────────────────────────── */
.chart-box {
margin-top: var(--space-4);
border: 1px solid var(--border-light);
background: var(--panel-inset);
border-radius: var(--radius-md);
padding: var(--space-3);
}
.chart-pending {
margin-top: var(--space-4);
border: 1px dashed var(--border-strong);
background: var(--panel-inset);
border-radius: var(--radius-md);
padding: var(--space-4) var(--space-3);
font-size: 0.82rem;
color: var(--text-tertiary);
line-height: 1.6;
}
.chart-pending.hidden { display: none; }
#chartCanvas { width: 100%; height: 340px; }
.chart-caption {
margin: 0.4rem 0.2rem 0;
font-family: 'JetBrains Mono', monospace;
font-size: 0.74rem; color: var(--text-tertiary);
text-align: right;
}
/* ── SQL block ──────────────────────────────────────────────── */
details summary { outline: none; }
.sql-content {
margin: 0;
background: var(--panel-inset);
border: 1px solid var(--border-light);
border-radius: var(--radius-md);
padding: var(--space-4);
overflow-x: auto;
font-size: 0.87rem;
line-height: 1.6;
}
code.language-sql, pre.sql-content code {
font-family: 'JetBrains Mono', 'Cascadia Code', Consolas, monospace;
color: #D9E7F6;
}
/* ── Agent panels sidebar ───────────────────────────────────── */
.agent-panels { display: flex; flex-direction: column; gap: var(--space-4); }
.panel {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
overflow: hidden;
}
.panel summary { list-style: none; }
.panel summary::-webkit-details-marker { display: none; }
.panel-header {
display: flex; align-items: center; justify-content: space-between;
padding: 0.8rem var(--space-4);
cursor: pointer;
user-select: none;
}
.panel-header:hover { background: var(--bg-raised); }
.panel-title {
display: flex; align-items: center; gap: 0.5rem;
font-size: 0.84rem; font-weight: 600; color: var(--text-primary);
font-family: 'Space Grotesk', 'Inter', sans-serif;
}
.panel-title .chip svg { width: 14px; height: 14px; }
.panel-content {
padding: 0 var(--space-4) var(--space-4);
font-size: 0.76rem;
color: var(--text-secondary);
max-height: 260px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-word;
font-family: 'JetBrains Mono', Consolas, monospace;
}
/* ── Database panel ─────────────────────────────────────────── */
.db-badge {
margin-left: auto;
font-family: 'JetBrains Mono', monospace;
font-size: 0.7rem; font-weight: 600;
color: #34D399;
background: rgba(52, 211, 153, 0.10);
border: 1px solid rgba(52, 211, 153, 0.3);
border-radius: 999px;
padding: 0.12rem 0.55rem;
}
.profile-list { display: flex; flex-direction: column; gap: 6px; padding: 0 var(--space-4) var(--space-3); }
.profile-item {
display: flex; align-items: center; gap: 0.5rem;
border: 1px solid var(--border);
background: var(--panel-inset);
border-radius: var(--radius-sm);
padding: 0.5rem 0.65rem;
font-size: 0.8rem;
}
.profile-item.active {
border-color: rgba(52, 211, 153, 0.55);
background: rgba(52, 211, 153, 0.08);
}
.profile-name { font-weight: 600; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.profile-kind { font-size: 0.68rem; color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.04em; }
.profile-actions { margin-left: auto; display: flex; gap: 4px; }
.profile-btn {
border: 1px solid var(--border-strong);
background: var(--panel);
color: var(--text-secondary);
font-size: 0.7rem; font-weight: 600;
border-radius: 4px; padding: 2px 8px; cursor: pointer;
font-family: inherit;
}
.profile-btn:hover { border-color: var(--accent); color: var(--accent); }
.profile-btn.danger:hover { border-color: var(--error); color: var(--error); }
.profile-btn.primary { border-color: rgb(52 211 153 / .55); color: #34d399; }
.profile-btn.primary:hover { background: rgb(52 211 153 / .1); border-color: #34d399; color: #34d399; }
.notes-editor {
border: 1px solid var(--border);
border-top: 2px solid rgb(52 211 153 / .45);
background: var(--panel-inset);
border-radius: var(--radius-sm);
padding: 0.5rem 0.65rem;
display: flex; flex-direction: column; gap: 6px;
}
.notes-editor textarea {
width: 100%; resize: vertical;
background: var(--panel); color: var(--text-primary);
border: 1px solid var(--border-strong); border-radius: 4px;
font-family: inherit; font-size: 0.72rem; line-height: 1.45;
padding: 0.4rem 0.5rem;
}
.notes-editor textarea:focus { outline: none; border-color: rgb(52 211 153 / .55); }
.notes-editor-actions { display: flex; gap: 6px; justify-content: flex-end; }
.profile-dot {
width: 7px; height: 7px; border-radius: 50%;
background: var(--border-strong); flex-shrink: 0;
}
.profile-dot.on { background: var(--success); box-shadow: 0 0 6px rgb(52 211 153 / .6); }
.schema-add { padding: 0 var(--space-4) var(--space-4); border-top: 1px dashed var(--border); padding-top: var(--space-3); }
.schema-add label {
display: block; font-size: 0.72rem; font-weight: 600;
color: var(--text-tertiary); margin: 0.5rem 0 3px;
text-transform: uppercase; letter-spacing: 0.05em;
}
.schema-add input[type="text"] {
width: 100%; font-family: inherit; font-size: 0.8rem;
border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
padding: 0.4rem 0.55rem; color: var(--text-primary);
background: var(--panel-inset);
}
.schema-add input[type="text"]:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgb(34 211 238 / .18); }
.schema-add textarea {
width: 100%; font-family: inherit; font-size: 0.8rem; line-height: 1.45;
border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
padding: 0.4rem 0.55rem; color: var(--text-primary);
background: var(--panel-inset); resize: vertical; min-height: 44px;
}
.schema-add textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgb(34 211 238 / .18); }
.schema-add-hint {
margin-top: 0.45rem;
font-size: 0.72rem;
color: var(--text-tertiary);
line-height: 1.5;
}
.schema-add .row2 { display: flex; gap: 6px; margin-top: 0.55rem; }
.schema-add .row2 .btn { flex: 1; justify-content: center; padding: 0.42rem 0.5rem; font-size: 0.78rem; }
.schema-tables { padding: 0 var(--space-4) var(--space-4); }
.schema-tables .tbl {
font-size: 0.74rem; color: var(--text-secondary);
border-bottom: 1px dotted var(--border);
padding: 3px 0;
}
.schema-tables .tbl b { color: var(--text-primary); font-weight: 600; }
.schema-tables .tbl .rc { color: var(--text-tertiary); font-size: 0.68rem; }
/* ── Footer ─────────────────────────────────────────────────── */
.footer {
text-align: center;
padding: var(--space-6);
color: var(--text-tertiary);
font-size: 0.8rem;
border-top: 1px solid var(--border-light);
}
.footer p { margin: 0; }
/* ── Section accent system: one hue family per functional area ── */
.accent-cyan { border-top: 2px solid rgba(34, 211, 238, 0.55); }
.accent-violet { border-top: 2px solid rgba(167, 139, 250, 0.55); }
.accent-emerald { border-top: 2px solid rgba(52, 211, 153, 0.55); }
.accent-blue { border-top: 2px solid rgba(96, 165, 250, 0.55); }
.accent-amber { border-top: 2px solid rgba(251, 191, 36, 0.55); }
.accent-rose { border-top: 2px solid rgba(244, 114, 182, 0.55); }
.chip {
width: 26px; height: 26px;
border-radius: 8px;
display: grid; place-items: center;
flex-shrink: 0;
border: 1px solid transparent;
}
.c-cyan { color: #22D3EE; background: rgba(34, 211, 238, 0.10); border-color: rgba(34, 211, 238, 0.30); }
.c-violet { color: #A78BFA; background: rgba(167, 139, 250, 0.10); border-color: rgba(167, 139, 250, 0.30); }
.c-emerald { color: #34D399; background: rgba(52, 211, 153, 0.10); border-color: rgba(52, 211, 153, 0.30); }
.c-blue { color: #60A5FA; background: rgba(96, 165, 250, 0.10); border-color: rgba(96, 165, 250, 0.30); }
.c-amber { color: #FBBF24; background: rgba(251, 191, 36, 0.10); border-color: rgba(251, 191, 36, 0.30); }
.c-rose { color: #F472B6; background: rgba(244, 114, 182, 0.10); border-color: rgba(244, 114, 182, 0.30); }
.btn-emerald {
background: #34D399; color: #03130B; border-color: #34D399;
box-shadow: 0 0 14px rgb(52 211 153 / 0.22);
}
.btn-emerald:hover:not(:disabled) {
background: #6EE7B7; border-color: #6EE7B7;
box-shadow: 0 0 20px rgb(52 211 153 / 0.38);
}
.btn-outline-violet, .btn-outline-rose, .btn-outline-amber {
background: transparent; font-weight: 600;
}
.btn-outline-violet { color: #C4B5FD; border-color: rgba(167, 139, 250, 0.45); }
.btn-outline-violet:hover:not(:disabled) {
background: rgba(167, 139, 250, 0.12); border-color: rgba(167, 139, 250, 0.75); color: #DDD6FE;
}
.btn-outline-rose { color: #F9A8D4; border-color: rgba(244, 114, 182, 0.45); }
.btn-outline-rose:hover:not(:disabled) {
background: rgba(244, 114, 182, 0.12); border-color: rgba(244, 114, 182, 0.75); color: #FBCFE8;
}
.btn-outline-amber { color: #FCD34D; border-color: rgba(251, 191, 36, 0.45); }
.btn-outline-amber:hover:not(:disabled) {
background: rgba(251, 191, 36, 0.10); border-color: rgba(251, 191, 36, 0.75); color: #FDE68A;
}
/* ── Result table widget (built by app.js) ──────────────────── */
.table-wrapper {
margin: 0.8rem 0 0.4rem;
border: 1px solid var(--border);
border-radius: var(--radius-md);
overflow: hidden;
background: var(--panel-inset);
}
.table-controls {
display: flex; align-items: center; justify-content: space-between;
gap: var(--space-2);
padding: 0.5rem 0.7rem;
border-bottom: 1px solid var(--border-light);
background: var(--bg-raised);
}
.table-info {
font-family: 'JetBrains Mono', monospace;
font-size: 0.72rem; color: var(--text-tertiary);
}
.table-resize-controls { display: flex; gap: 4px; }
.table-resize-btn {
font-family: inherit; font-size: 0.7rem; font-weight: 600;
color: var(--text-secondary);
background: var(--panel);
border: 1px solid var(--border-strong);
border-radius: 6px; padding: 2px 9px; cursor: pointer;
transition: color var(--transition-fast), border-color var(--transition-fast),
background var(--transition-fast);
}
.table-resize-btn:hover { border-color: rgb(34 211 238 / .5); color: var(--accent-bright); }
.table-resize-btn.active {
color: var(--accent-ink);
background: var(--accent);
border-color: var(--accent);
}
.table-container { overflow-x: auto; }
.answer-table {
border-collapse: collapse;
width: 100%;
font-size: 0.85rem;
}
.answer-table th, .answer-table td {
border-bottom: 1px solid var(--border-light);
text-align: left;
color: var(--text-primary);
}
.answer-table thead th {
background: var(--bg-raised);
font-weight: 600;
color: var(--accent-bright);
border-bottom: 1px solid var(--border-strong);
}
.answer-table tbody tr:hover td { background: rgba(34, 211, 238, 0.05); }
.answer-table.compact th, .answer-table.compact td { padding: 0.25rem 0.7rem; font-size: 0.78rem; }
.answer-table.comfortable th, .answer-table.comfortable td { padding: 0.45rem 0.8rem; }
.answer-table.spacious th, .answer-table.spacious td { padding: 0.75rem 1rem; font-size: 0.9rem; }
.status-text { color: var(--text-tertiary); }
.text-success { color: var(--success); }
.text-warning { color: var(--warning); }
.text-error { color: var(--error); }
/* Markdown tables inside answer */
.answer-content table {
border-collapse: collapse;
width: 100%;
margin: 0.6rem 0;
font-size: 0.86rem;
}
.answer-content th, .answer-content td {
border: 1px solid var(--border);
padding: 0.4rem 0.7rem;
text-align: left;
}
.answer-content th {
background: var(--bg-raised);
font-weight: 600;
color: var(--accent-bright);
}
.answer-content tr:nth-child(even) td { background: var(--panel-inset); }
</style>
<script>
/* Theme before first paint: avoid a dark flash for light-mode users */
try { if (localStorage.getItem('ql.theme') === 'light') { document.documentElement.classList.add('light'); } } catch (e) { /* no-op */ }
</script>
</head>
<body>
<div class="app-container">
<!-- Header -->
<header class="header">
<div class="header-content">
<div class="brand">
<div class="brand-icon">
<svg width="26" height="26" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- magnifying lens scanning a database disk stack -->
<g stroke="#22D3EE" stroke-width="2.6" fill="none">
<ellipse cx="20" cy="14" rx="10.5" ry="4" />
<path d="M9.5 14v20c0 2.2 4.7 4 10.5 4s10.5-1.8 10.5-4V14" />
<path d="M9.5 23c0 2.2 4.7 4 10.5 4s10.5-1.8 10.5-4" />
</g>
<circle cx="32.5" cy="27.5" r="8.2" fill="rgba(34,211,238,.16)" stroke="#7DECFB" stroke-width="2.4" />
<path d="M38.6 33.6L44.4 39.4" stroke="#7DECFB" stroke-width="2.8" stroke-linecap="round" />
</svg>
</div>
<div class="brand-text">
<h1>Query<span class="lens">Lens</span></h1>
<p>Transform natural language into SQL, and documents into insight.</p>
</div>
</div>
<div class="header-right">
<button id="themeToggle" class="theme-toggle" type="button" aria-label="Switch to light mode"
title="Switch to light mode">
<svg class="icon-sun" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
</svg>
<svg class="icon-moon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
</svg>
</button>
<span class="header-tag" id="dbTag">demo_employee</span>
</div>
</div>
</header>
<!-- Main Content -->
<main class="main-content">
<!-- Query Section -->
<section class="query-section">
<div class="query-body">
<textarea id="question" class="query-textarea"
placeholder="Ask a question about your data — e.g. “Which department has the highest average salary?” or “Show me sales by month as a chart.”"></textarea>
<div class="query-controls">
<select id="modeToggle" class="mode-select">
<option value="text2sql">Text-to-SQL</option>
<option value="ocr_qa">Document Q&amp;A</option>
</select>
<button id="ask" class="btn btn-primary">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
</svg>
Ask
</button>
<button id="recalc" class="btn btn-secondary btn-outline-violet">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 4v6h6" />
<path d="M23 20v-6h-6" />
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15" />
</svg>
Re-execute
</button>
<div id="uploadControls" class="hidden" style="display:flex; gap: 0.5rem; align-items: center;">
<button id="uploadBtn" class="btn btn-secondary btn-outline-rose">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="17,8 12,3 7,8" />
<line x1="12" y1="3" x2="12" y2="15" />
</svg>
Upload Image
</button>
<span id="uploadStatus">No document</span>
</div>
<div class="status-area">
<div id="spinner" class="spinner hidden"></div>
<span id="spinnerText" class="hidden">Thinking…</span>
<span id="status">Ready</span>
<span id="time"></span>
</div>
</div>
</div>
</section>
<!-- Results Grid -->
<div class="results-grid">
<!-- Main Results Column -->
<div class="main-results">
<!-- Answer Card -->
<div class="card accent-cyan">
<div class="card-header">
<h3 class="card-title">
<span class="chip c-cyan">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 11l3 3L22 4" />
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
</svg>
</span>
Answer
</h3>
</div>
<div class="card-content">
<div id="answer" class="answer-content">No answer yet — ask something above.</div>
<div id="chartPending" class="chart-pending">
Charts &amp; analytics render here automatically when a successful query
returns chart-friendly data — try “Show average salary by department”.
</div>
<div id="chartBox" class="chart-box hidden">
<div id="chartCanvas"></div>
<p class="chart-caption" id="chartCaption"></p>
</div>
</div>
</div>
<!-- SQL Card -->
<div class="card accent-violet">
<details id="sql-details" open>
<summary class="card-header">
<h3 class="card-title">
<span class="chip c-violet">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="16,18 22,12 16,6" />
<polyline points="8,6 2,12 8,18" />
</svg>
</span>
Generated SQL
</h3>
</summary>
<div class="card-content">
<pre
class="sql-content"><code id="sql" class="language-sql">-- SQL will appear here after the agent crafts a query --</code></pre>
</div>
</details>
</div>
</div>
<!-- Sidebar -->
<div class="agent-panels">
<!-- Database panel -->
<div class="panel accent-emerald">
<details open>
<summary class="panel-header">
<span class="panel-title">
<span class="chip c-emerald">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
</svg>
</span>
Database
<span class="db-badge" id="dbBadge">—</span>
</span>
</summary>
<div class="profile-list" id="schemaProfiles"></div>
<div class="schema-add">
<label for="schemaName">Add a database</label>
<input type="text" id="schemaName" placeholder="Profile name (e.g. sales_2024)">
<label for="schemaUrl" style="margin-top:0.6rem">Connection URL — or upload a file</label>
<input type="text" id="schemaUrl"
placeholder="postgresql:// · mysql:// · mssql:// · oracle:// · sqlite:///path/file.db">
<div class="schema-add-hint">PostgreSQL, MySQL, SQL Server, Oracle &amp; SQLite are all supported — paste any of those connection URLs.</div>
<label for="schemaNotes">Schema notes — optional</label>
<textarea id="schemaNotes" rows="2"
placeholder="Optional: what your columns mean — e.g. 'sold_date IS NULL = still in inventory', 'msrp = list price, sale_price = actual', revenue rules, aliases"></textarea>
<div class="row2">
<button id="schemaAddBtn" class="btn btn-emerald">Connect</button>
<button id="schemaFileBtn" class="btn btn-secondary btn-outline-amber">Upload .sqlite / .csv</button>
</div>
</div>
<div class="schema-tables" id="schemaTables"></div>
</details>
</div>
<!-- Agent panels -->
<div class="panel accent-blue">
<details open>
<summary class="panel-header">
<span class="panel-title">
<span class="chip c-blue">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- RAM chip / memory module -->
<rect x="4" y="4" width="16" height="16" rx="2" />
<rect x="9" y="9" width="6" height="6" />
<line x1="9" y1="1" x2="9" y2="4" />
<line x1="15" y1="1" x2="15" y2="4" />
<line x1="9" y1="20" x2="9" y2="23" />
<line x1="15" y1="20" x2="15" y2="23" />
<line x1="20" y1="9" x2="23" y2="9" />
<line x1="20" y1="14" x2="23" y2="14" />
<line x1="1" y1="9" x2="4" y2="9" />
<line x1="1" y1="14" x2="4" y2="14" />
</svg>
</span>
Memory Agent
</span>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6,9 12,15 18,9" />
</svg>
</summary>
<div id="memory" class="panel-content">—</div>
</details>
</div>
<div class="panel accent-violet">
<details open>
<summary class="panel-header">
<span class="panel-title">
<span class="chip c-violet">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- terminal: the agent crafts SQL -->
<polyline points="4,17 10,11 4,5" />
<line x1="12" y1="19" x2="20" y2="19" />
</svg>
</span>
Query Craft Agent
</span>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6,9 12,15 18,9" />
</svg>
</summary>
<div id="querycraft" class="panel-content">—</div>
</details>
</div>
<div class="panel accent-rose">
<details open>
<summary class="panel-header">
<span class="panel-title">
<span class="chip c-rose">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14,2 14,8 20,8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
<polyline points="10,9 9,9 8,9" />
</svg>
</span>
Result Presenter Agent
</span>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6,9 12,15 18,9" />
</svg>
</summary>
<div id="presenter" class="panel-content">—</div>
</details>
</div>
</div>
</div>
</main>
<!-- Footer -->
<footer class="footer">
<p>QueryLens — multi-agent Text-to-SQL &amp; document intelligence</p>
</footer>
</div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<script src="/static/app.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-sql.min.js"></script>
</body>
</html>