rag-visualizer / frontend /index.html
Vasanth6's picture
Replace Gemma embedding model with BAAI/bge-small-en-v1.5 and cache at build time
1142544
Raw
History Blame Contribute Delete
23.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="RAG Visualizer — An X-Ray machine for RAG pipelines. Visualize and compare chunking strategies." />
<title>RAG Visualizer — Chunking Lab</title>
<link rel="stylesheet" href="/static/styles.css" />
</head>
<body>
<div class="app-wrapper">
<!-- ========== HEADER ========== -->
<header class="app-header">
<div class="app-logo">
<div class="app-logo-icon">🔬</div>
<div class="app-logo-text">RAG <span>Visualizer</span></div>
</div>
</header>
<!-- ========== MAIN 3-COLUMN LAYOUT ========== -->
<main class="app-main">
<!-- ===== LEFT PANEL: Configuration ===== -->
<aside class="panel" id="config-panel">
<div class="panel-header">
<h2><span class="icon">⚙️</span> Configuration</h2>
</div>
<div class="panel-body">
<!-- Text Input -->
<div class="config-section">
<div class="config-section-title" style="display: flex; justify-content: space-between; align-items: center;">
<span>Input Text</span>
<button type="button" id="btn-upload-file" class="btn-sm" style="cursor: pointer; background: var(--superman-blue-muted); border: 1px solid var(--superman-blue); color: var(--superman-blue); padding: 2px 8px; border-radius: 4px; font-size: 0.75rem; font-weight: bold;">📎 Attach File</button>
</div>
<div class="text-input-wrapper">
<input type="file" id="file-input" style="display: none;" />
<textarea
id="text-input"
class="text-input"
placeholder="Paste your text here to visualize chunking strategies..."
spellcheck="false"
></textarea>
<span class="char-count" id="char-count">0 chars</span>
</div>
</div>
<!-- Strategy Selector -->
<div class="config-section">
<div class="config-section-title">Chunking Strategy</div>
<div class="strategy-grid" id="strategy-grid">
<label class="strategy-card active" data-strategy="fixed_size">
<input type="radio" name="strategy" value="fixed_size" checked />
<div class="strategy-card-icon">📏</div>
<div class="strategy-card-name">Fixed Size</div>
<div class="strategy-card-desc">Cut every N tokens</div>
</label>
<label class="strategy-card" data-strategy="sentence">
<input type="radio" name="strategy" value="sentence" />
<div class="strategy-card-icon">💬</div>
<div class="strategy-card-name">Sentence</div>
<div class="strategy-card-desc">Split on boundaries</div>
</label>
<label class="strategy-card" data-strategy="recursive">
<input type="radio" name="strategy" value="recursive" />
<div class="strategy-card-icon">🔄</div>
<div class="strategy-card-name">Recursive</div>
<div class="strategy-card-desc">Hierarchy of separators</div>
</label>
<label class="strategy-card" data-strategy="parent_child">
<input type="radio" name="strategy" value="parent_child" />
<div class="strategy-card-icon">🌳</div>
<div class="strategy-card-name">Parent-Child</div>
<div class="strategy-card-desc">Nested chunking</div>
</label>
<label class="strategy-card" data-strategy="semantic">
<input type="radio" name="strategy" value="semantic" />
<div class="strategy-card-icon">🧠</div>
<div class="strategy-card-name">Semantic</div>
<div class="strategy-card-desc">Split on topic shifts</div>
</label>
</div>
</div>
<!-- Standard Config Sliders -->
<div class="config-section" id="standard-config">
<div class="config-section-title">Parameters</div>
<div class="config-field">
<label>
Chunk Size (tokens)
<span class="value-badge" id="chunk-size-value">500</span>
</label>
<input type="range" id="chunk-size" min="50" max="2000" step="50" value="500" />
</div>
<div class="config-field">
<label>
Overlap (tokens)
<span class="value-badge" id="overlap-value">20</span>
</label>
<input type="range" id="overlap" min="0" max="500" step="10" value="20" />
</div>
</div>
<!-- Parent-Child Config (Hidden by default) -->
<div class="parent-child-config" id="parent-child-config">
<div class="pc-section-label parent">🔵 Parent Config</div>
<div class="config-field">
<label>
Parent Chunk Size
<span class="value-badge" id="parent-size-value">1000</span>
</label>
<input type="range" id="parent-size" min="100" max="4000" step="100" value="1000" />
</div>
<div class="config-field">
<label>
Parent Overlap
<span class="value-badge" id="parent-overlap-value">100</span>
</label>
<input type="range" id="parent-overlap" min="0" max="500" step="10" value="100" />
</div>
<div class="pc-section-label child">🟢 Child Config</div>
<div class="config-field">
<label>
Child Chunk Size
<span class="value-badge" id="child-size-value">200</span>
</label>
<input type="range" id="child-size" min="50" max="1000" step="50" value="200" />
</div>
<div class="config-field">
<label>
Child Overlap
<span class="value-badge" id="child-overlap-value">20</span>
</label>
<input type="range" id="child-overlap" min="0" max="200" step="10" value="20" />
</div>
</div>
<!-- Semantic Config (Hidden by default) -->
<div class="config-section" id="semantic-config" style="display: none;">
<div class="config-section-title">Semantic Settings</div>
<div class="config-field">
<label>
Similarity Threshold
<span class="value-badge" id="semantic-threshold-value">0.50</span>
</label>
<input type="range" id="semantic-threshold" min="0.1" max="1.0" step="0.05" value="0.5" />
</div>
</div>
<!-- Separators (for recursive) -->
<div class="config-section" id="separators-section" style="display: none;">
<div class="config-section-title">Separators</div>
<div class="separator-tags" id="separator-tags">
<span class="separator-tag" data-sep="\n\n"><code>\\n\\n</code><span class="remove">&times;</span></span>
<span class="separator-tag" data-sep="\n"><code>\\n</code><span class="remove">&times;</span></span>
<span class="separator-tag" data-sep=". "><code>. </code><span class="remove">&times;</span></span>
<span class="separator-tag" data-sep=" "><code></code><span class="remove">&times;</span></span>
<input type="text" class="separator-input" id="separator-input" placeholder="Add..." />
</div>
</div>
<!-- Embedding & Vector Space Config -->
<div class="config-section">
<div class="config-section-title">Vector Space Settings</div>
<div class="config-field">
<label for="embedding-model">Embedding Model</label>
<select id="embedding-model" class="select-input">
<option value="nomic-embed-text" selected>Nomic Embed Text</option>
<option value="bge-small-en">BGE Small English</option>
<option value="qwen3-embedding:0.6b">Qwen3 Embedding</option>
</select>
</div>
<div class="config-field">
<label>
UMAP n_neighbors
<span class="value-badge" id="n-neighbors-value">15</span>
</label>
<input type="range" id="n-neighbors" min="2" max="100" step="1" value="15" />
</div>
<div class="config-field">
<label>
UMAP min_dist
<span class="value-badge" id="min-dist-value">0.10</span>
</label>
<input type="range" id="min-dist" min="0.01" max="1.0" step="0.05" value="0.1" />
</div>
</div>
<!-- Run Button -->
<button class="btn-run ready" id="btn-run">
<span class="spinner"></span>
<span class="btn-text">⚡ Run Chunking</span>
</button>
</div>
</aside>
<!-- ===== CENTER PANEL: X-Ray Text Viewer & Vector Space ===== -->
<section class="panel xray-viewer" id="xray-panel">
<div class="panel-header tabs-header">
<div class="tab-buttons">
<button class="tab-btn active" data-tab="xray-tab">
<span class="icon">🔍</span> Document Viewer
</button>
<button class="tab-btn" data-tab="vector-tab">
<span class="icon">🌌</span> Vector Space 2D
</button>
</div>
</div>
<!-- Tab Content 1: X-Ray Text Viewer -->
<div class="panel-body tab-content active" id="xray-tab">
<div class="empty-state" id="xray-empty">
<div class="empty-state-icon">🔬</div>
<div class="empty-state-title">Ready to Analyze</div>
<div class="empty-state-desc">
Paste your text, choose a chunking strategy, and hit <strong>Run</strong> to see how your text gets sliced.
</div>
</div>
<div class="xray-text-container" id="xray-text" style="display: none;"></div>
</div>
<!-- Tab Content 2: Vector Space 2D -->
<div class="panel-body tab-content" id="vector-tab" style="display: none;">
<div class="vector-space-container">
<div class="canvas-wrapper">
<canvas id="vector-canvas"></canvas>
<!-- Floating Tooltip inside canvas -->
<div id="vector-tooltip" class="vector-tooltip" style="display: none;"></div>
<!-- Help overlay for canvas controls -->
<div class="canvas-help-overlay">
<span>🖱️ Drag to Pan</span>
<span>☸️ Scroll to Zoom</span>
<span>✨ Click node to select</span>
</div>
</div>
<!-- Sonar Query Simulator Section -->
<div class="query-simulator-section">
<div class="query-section-title" style="display: flex; justify-content: space-between; align-items: center;">
<span>🔮 Sonar Query Simulator (RAG Retrieval)</span>
<button id="btn-open-arena" class="btn-sm" style="background: var(--superman-red); color: white; border: none; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-weight: bold; font-family: var(--font-sans);">⚔️ Arena Comparison</button>
</div>
<div class="query-input-wrapper">
<select id="query-retrieval-mode" class="select-input" style="width: 110px; flex-shrink: 0;">
<option value="dense" selected>Dense</option>
<option value="sparse">Sparse</option>
<option value="hybrid">Hybrid</option>
</select>
<input type="text" id="query-input" placeholder="Type a concept query to fetch chunks (e.g. 'linear regression')..." />
<button id="btn-query" class="btn-query">🔍 Query</button>
</div>
<div class="query-options-row" style="display: flex; gap: var(--space-md); margin-top: var(--space-sm); font-size: 0.85rem; color: var(--text-secondary); align-items: center; flex-wrap: wrap;">
<label style="display: flex; align-items: center; gap: var(--space-xs); cursor: pointer; user-select: none;">
<input type="checkbox" id="query-use-hyde" style="cursor: pointer; width: 14px; height: 14px;" />
🔮 Use HyDE
</label>
<label style="display: flex; align-items: center; gap: var(--space-xs); cursor: pointer; user-select: none;">
<input type="checkbox" id="query-use-reranking" style="cursor: pointer; width: 14px; height: 14px;" />
⚡ Use Re-Ranking
</label>
<div style="display: flex; align-items: center; gap: var(--space-xs); margin-left: auto;">
<span style="font-size: 0.8rem; color: var(--text-tertiary);">Filter Level:</span>
<select id="query-filter-level" class="select-input" style="padding: 4px 8px; font-size: 0.78rem;">
<option value="all">All Levels</option>
<option value="parent">Only Parents (Lvl 0)</option>
<option value="child">Only Children (Lvl 1)</option>
</select>
</div>
</div>
<div class="query-results-drawer" id="query-results-drawer" style="display: none;">
<div class="drawer-header">
<h3>Retrieved Context</h3>
<span class="close-drawer" id="close-drawer">&times;</span>
</div>
<div class="query-results-list" id="query-results-list">
<!-- Retrieved chunks will go here -->
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ===== RIGHT PANEL: Chunk Inspector ===== -->
<aside class="panel" id="inspector-panel">
<div class="panel-header">
<h2><span class="icon">📊</span> Chunk Inspector</h2>
</div>
<div class="panel-body">
<!-- Stats -->
<div class="stats-grid" id="stats-grid">
<div class="stat-card">
<div class="stat-value" id="stat-total"></div>
<div class="stat-label">Total Chunks</div>
</div>
<div class="stat-card">
<div class="stat-value" id="stat-avg-tokens"></div>
<div class="stat-label">Avg Tokens</div>
</div>
<div class="stat-card">
<div class="stat-value" id="stat-total-tokens"></div>
<div class="stat-label">Total Tokens</div>
</div>
<div class="stat-card">
<div class="stat-value" id="stat-strategy"></div>
<div class="stat-label">Strategy</div>
</div>
</div>
<!-- Chunk List -->
<div class="config-section-title">Chunks</div>
<div class="chunk-list" id="chunk-list">
<div class="empty-state" style="min-height: 200px;">
<div class="empty-state-desc">Run chunking to see results here</div>
</div>
</div>
</div>
</aside>
<!-- ===== THE ARENA MODAL ===== -->
<div id="arena-modal" class="arena-modal-overlay" style="display: none;">
<div class="arena-modal-content">
<div class="arena-header">
<h2>⚔️ The Grand Comparison Arena</h2>
<button id="btn-close-arena" class="btn-close-arena">&times;</button>
</div>
<div class="arena-query-bar">
<input type="text" id="arena-query-input" placeholder="Type a query to compare configurations (e.g., 'What is Clark Kent's weakness?')..." />
<button class="btn-run" id="btn-arena-fight" style="width: 150px; flex-shrink: 0;">🔥 FIGHT!</button>
</div>
<div class="arena-options-row" style="display: flex; gap: var(--space-md); margin: 0 0 var(--space-md) 0; padding: 0 var(--space-xl); font-size: 0.85rem; color: var(--text-secondary); justify-content: flex-start; align-items: center; flex-wrap: wrap;">
<label style="display: flex; align-items: center; gap: var(--space-xs); cursor: pointer; user-select: none;">
<input type="checkbox" id="arena-use-hyde" style="cursor: pointer; width: 14px; height: 14px;" />
🔮 Use HyDE
</label>
<label style="display: flex; align-items: center; gap: var(--space-xs); cursor: pointer; user-select: none;">
<input type="checkbox" id="arena-use-reranking" style="cursor: pointer; width: 14px; height: 14px;" />
⚡ Use Re-Ranking
</label>
<div style="display: flex; align-items: center; gap: var(--space-xs); margin-left: auto;">
<span style="font-size: 0.8rem; color: var(--text-tertiary);">Filter Level:</span>
<select id="arena-filter-level" class="select-input" style="padding: 4px 8px; font-size: 0.78rem;">
<option value="all">All Levels</option>
<option value="parent">Only Parents (Lvl 0)</option>
<option value="child">Only Children (Lvl 1)</option>
</select>
</div>
</div>
<!-- Dynamic HyDE box in Arena -->
<div id="arena-hyde-container" style="display: none; margin: 0 var(--space-lg) var(--space-md) var(--space-lg); padding: var(--space-sm) var(--space-md); border-left: 4px solid var(--superman-blue); background-color: var(--superman-blue-muted); border-radius: var(--radius-sm);">
<div style="font-weight: bold; color: var(--superman-blue); font-size: 0.85rem; margin-bottom: 4px;">🔮 Generated Hypothetical Answer (HyDE)</div>
<div id="arena-hyde-text" style="font-style: italic; color: var(--text-primary); font-size: 0.85rem;"></div>
</div>
<div class="arena-body">
<div class="arena-columns">
<!-- COLUMN A -->
<div class="arena-col">
<div class="arena-col-header">
<h3>Corner A</h3>
<div class="arena-config-row">
<select id="arena-retrieval-mode-a" class="select-input">
<option value="dense" selected>Dense</option>
<option value="sparse">Sparse</option>
<option value="hybrid">Hybrid</option>
</select>
<select id="arena-model-a" class="select-input">
<option value="nomic-embed-text">Nomic Embed</option>
<option value="bge-small-en">BGE Small</option>
<option value="qwen3-embedding:0.6b">Qwen3 Embed</option>
</select>
<select id="arena-strategy-a" class="select-input">
<option value="fixed_size">Fixed Size</option>
<option value="sentence">Sentence</option>
<option value="recursive">Recursive</option>
<option value="semantic">Semantic</option>
<option value="parent_child">Parent/Child</option>
</select>
</div>
</div>
<div class="arena-results-list" id="arena-results-a">
<div style="text-align: center; color: var(--text-tertiary); padding: 2rem;">Awaiting challengers...</div>
</div>
</div>
<!-- COLUMN B -->
<div class="arena-col">
<div class="arena-col-header" style="border-bottom-color: var(--info-color);">
<h3>Corner B</h3>
<div class="arena-config-row">
<select id="arena-retrieval-mode-b" class="select-input">
<option value="dense" selected>Dense</option>
<option value="sparse">Sparse</option>
<option value="hybrid">Hybrid</option>
</select>
<select id="arena-model-b" class="select-input">
<option value="nomic-embed-text">Nomic Embed</option>
<option value="bge-small-en">BGE Small</option>
<option value="qwen3-embedding:0.6b">Qwen3 Embed</option>
</select>
<select id="arena-strategy-b" class="select-input">
<option value="fixed_size">Fixed Size</option>
<option value="sentence">Sentence</option>
<option value="recursive">Recursive</option>
<option value="semantic">Semantic</option>
<option value="parent_child">Parent/Child</option>
</select>
</div>
</div>
<div class="arena-results-list" id="arena-results-b">
<div style="text-align: center; color: var(--text-tertiary); padding: 2rem;">Awaiting challengers...</div>
</div>
</div>
</div>
<!-- THE AI REFEREE PANEL (TASK 5.1) -->
<div class="arena-referee-panel" id="arena-referee-panel" style="display: none;">
<div class="referee-divider">
<span class="referee-badge">⚖️ THE AI REFEREE</span>
</div>
<div class="referee-trigger-box" id="referee-trigger-box">
<p class="referee-prompt-text">Call upon the AI evaluation referee to analyze, score, and judge both contexts! (Takes 2-3 seconds)</p>
<button class="btn-run" id="btn-call-referee" style="margin: 0 auto; display: block; max-width: 250px;">⚖️ CALL THE REFEREE</button>
</div>
<!-- Loading scanner status -->
<div class="referee-loading-box" id="referee-loading-box" style="display: none;">
<div class="skeleton-loader" style="max-width: 400px; margin: 0 auto 1rem; border-radius: 6px;"></div>
<div class="referee-status-text" id="referee-status-text">Summoning AI referee to review the case...</div>
</div>
<!-- Scorecard Verdict Display -->
<div class="referee-verdict-box" id="referee-verdict-box" style="display: none;"></div>
</div>
</div>
</div>
</div>
</main>
</div>
<script src="/static/app.js"></script>
</body>
</html>