anycoder-7ef6bbc0 / index.html
ldostadi's picture
Upload folder using huggingface_hub
6c6f3fc verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RAG Nexus | Intelligent Document Analysis</title>
<!-- Importing Phosphor Icons for modern UI -->
<script src="https://unpkg.com/@phosphor-icons/web"></script>
<style>
:root {
/* Theme Colors - Gradio Soft Theme inspired */
--primary: #4f46e5;
--primary-hover: #4338ca;
--secondary: #8b5cf6;
--background: #f3f4f6;
--surface: #ffffff;
--text-main: #1f2937;
--text-muted: #6b7280;
--border: #e5e7eb;
--danger: #ef4444;
--success: #10b981;
--warning: #f59e0b;
/* Spacing & Radius */
--radius-md: 8px;
--radius-lg: 12px;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
/* Fonts */
--font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-family);
background-color: var(--background);
color: var(--text-main);
line-height: 1.5;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* --- Header --- */
header {
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: var(--shadow-sm);
}
.brand {
display: flex;
align-items: center;
gap: 0.75rem;
}
.brand h1 {
font-size: 1.25rem;
font-weight: 700;
background: linear-gradient(to right, var(--primary), var(--secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.brand i {
font-size: 1.5rem;
color: var(--primary);
}
.anycoder-link {
font-size: 0.875rem;
color: var(--text-muted);
text-decoration: none;
transition: color 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
}
.anycoder-link:hover {
color: var(--primary);
}
/* --- Main Layout --- */
main {
flex: 1;
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
width: 100%;
}
/* --- Tabs --- */
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
overflow-x: auto;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border);
}
.tab-btn {
background: none;
border: none;
padding: 0.75rem 1.25rem;
font-size: 0.95rem;
font-weight: 500;
color: var(--text-muted);
cursor: pointer;
border-radius: var(--radius-md);
transition: all 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
}
.tab-btn:hover {
background-color: rgba(79, 70, 229, 0.05);
color: var(--primary);
}
.tab-btn.active {
background-color: var(--primary);
color: white;
}
/* --- Content Sections --- */
.tab-content {
display: none;
animation: fadeIn 0.3s ease-out;
}
.tab-content.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(5px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
padding: 1.5rem;
box-shadow: var(--shadow-sm);
margin-bottom: 1.5rem;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.card-title {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-main);
}
/* --- Forms & Inputs --- */
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
font-size: 0.9rem;
color: var(--text-main);
}
input[type="text"],
textarea,
select {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border);
border-radius: var(--radius-md);
font-family: inherit;
font-size: 0.95rem;
transition: border-color 0.2s;
}
input[type="text"]:focus,
textarea:focus,
select:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
}
/* --- Buttons --- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
border-radius: var(--radius-md);
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: none;
font-size: 0.95rem;
}
.btn-primary {
background-color: var(--primary);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-hover);
}
.btn-secondary {
background-color: white;
border: 1px solid var(--border);
color: var(--text-main);
}
.btn-secondary:hover {
border-color: var(--text-muted);
}
.btn-danger {
background-color: #fee2e2;
color: var(--danger);
}
.btn-danger:hover {
background-color: #fecaca;
}
/* --- Tables --- */
.table-container {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
th, td {
text-align: left;
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border);
}
th {
background-color: #f9fafb;
font-weight: 600;
color: var(--text-muted);
}
tr:last-child td {
border-bottom: none;
}
/* --- Upload Area --- */
.upload-area {
border: 2px dashed var(--border);
border-radius: var(--radius-lg);
padding: 3rem;
text-align: center;
cursor: pointer;
transition: all 0.2s;
background-color: #f9fafb;
}
.upload-area:hover, .upload-area.dragover {
border-color: var(--primary);
background-color: rgba(79, 70, 229, 0.02);
}
.upload-icon {
font-size: 3rem;
color: var(--text-muted);
margin-bottom: 1rem;
}
/* --- Analytics Grid --- */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
padding: 1.5rem;
border-radius: var(--radius-lg);
border: 1px solid var(--border);
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.stat-value {
font-size: 2.5rem;
font-weight: 700;
color: var(--primary);
margin: 0.5rem 0;
}
.stat-label {
color: var(--text-muted);
font-weight: 500;
}
.stat-icon {
font-size: 1.5rem;
color: var(--secondary);
background: #f3f4f6;
padding: 0.5rem;
border-radius: 50%;
}
/* --- Toast Notification --- */
#toast-container {
position: fixed;
bottom: 2rem;
right: 2rem;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.toast {
background: white;
padding: 1rem 1.5rem;
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
border-left: 4px solid var(--primary);
display: flex;
align-items: center;
gap: 0.75rem;
animation: slideIn 0.3s ease-out;
max-width: 400px;
}
.toast.success { border-left-color: var(--success); }
.toast.error { border-left-color: var(--danger); }
.toast.warning { border-left-color: var(--warning); }
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* --- Markdown Output --- */
.markdown-body {
line-height: 1.7;
color: var(--text-main);
}
.markdown-body h3 { margin-top: 1.5rem; margin-bottom: 0.75rem; }
.markdown-body ul { padding-left: 1.5rem; margin-bottom: 1rem; }
.markdown-body strong { color: var(--primary); }
/* Responsive */
@media (max-width: 768px) {
header { padding: 1rem; flex-direction: column; gap: 1rem; align-items: flex-start; }
.tabs { padding-bottom: 1rem; }
main { padding: 1rem; }
}
</style>
</head>
<body>
<header>
<div class="brand">
<i class="ph ph-magic-wand"></i>
<h1>RAG Nexus</h1>
</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
Built with anycoder <i class="ph ph-arrow-square-out"></i>
</a>
</header>
<main>
<!-- Navigation Tabs -->
<nav class="tabs">
<button class="tab-btn active" onclick="switchTab('upload')">
<i class="ph ph-upload-simple"></i> Upload
</button>
<button class="tab-btn" onclick="switchTab('documents')">
<i class="ph ph-files"></i> Documents
</button>
<button class="tab-btn" onclick="switchTab('axioms')">
<i class="ph ph-lightning"></i> Axioms
</button>
<button class="tab-btn" onclick="switchTab('generate')">
<i class="ph ph-robot"></i> Generate
</button>
<button class="tab-btn" onclick="switchTab('analytics')">
<i class="ph ph-chart-bar"></i> Analytics
</button>
</nav>
<!-- Upload Tab -->
<section id="tab-upload" class="tab-content active">
<div class="card">
<div class="card-header">
<h2 class="card-title">Upload Documents</h2>
<div id="upload-status"></div>
</div>
<div class="upload-area" id="drop-zone">
<i class="ph ph-file-arrow-up upload-icon"></i>
<h3>Drag & Drop files here</h3>
<p class="text-muted">or click to browse (.txt, .md, .json, .csv)</p>
<input type="file" id="file-input" multiple accept=".txt,.md,.json,.csv" style="display: none;">
</div>
<div style="margin-top: 1.5rem; text-align: right;">
<button class="btn btn-primary" onclick="processFiles()">
<i class="ph ph-cpu"></i> Process Files
</button>
</div>
<div id="upload-queue" style="margin-top: 1.5rem; display: none;">
<h4>Upload Queue</h4>
<div class="table-container">
<table id="queue-table">
<thead>
<tr>
<th>File</th>
<th>Status</th>
<th>Size</th>
</tr>
</thead>
<tbody><!-- JS populates this --></tbody>
</table>
</div>
</div>
</div>
</section>
<!-- Documents Tab -->
<section id="tab-documents" class="tab-content">
<div class="card">
<div class="card-header">
<h2 class="card-title">Indexed Documents</h2>
<button class="btn btn-danger" onclick="clearAllData()">
<i class="ph ph-trash"></i> Clear All
</button>
</div>
<div class="form-group">
<input type="text" id="doc-search" placeholder="Search documents..." onkeyup="renderDocuments()">
</div>
<div class="table-container">
<table id="documents-table">
<thead>
<tr>
<th>Name</th>
<th>Size</th>
<th>Uploaded</th>
<th>ID</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</section>
<!-- Axioms Tab -->
<section id="tab-axioms" class="tab-content">
<div class="card">
<div class="card-header">
<h2 class="card-title">Extracted Axioms</h2>
<button class="btn btn-secondary" onclick="exportAxioms()">
<i class="ph ph-download-simple"></i> Export JSON
</button>
</div>
<div style="display: flex; gap: 1rem; margin-bottom: 1rem;">
<div style="flex: 2;">
<input type="text" id="axiom-search" placeholder="Search axioms..." onkeyup="renderAxioms()">
</div>
<div style="flex: 1;">
<select id="axiom-filter" onchange="renderAxioms()">
<option value="">All Documents</option>
</select>
</div>
</div>
<div class="table-container">
<table id="axioms-table">
<thead>
<tr>
<th>Document</th>
<th>Source</th>
<th>Axiom</th>
<th>Confidence</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</section>
<!-- Generate Tab -->
<section id="tab-generate" class="tab-content">
<div class="card">
<div class="card-header">
<h2 class="card-title">Intelligent Response Generation</h2>
</div>
<div class="form-group">
<label>Enter your query</label>
<textarea id="query-input" rows="4" placeholder="Ask anything about your documents... (e.g., 'What are the fundamental principles based on the uploaded documents?')"></textarea>
</div>
<div style="display: flex; gap: 2rem; margin-bottom: 1.5rem;">
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<input type="checkbox" id="use-axioms" checked> Use Axioms
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<input type="checkbox" id="use-context" checked> Use Context (RAG)
</label>
</div>
<button class="btn btn-primary" onclick="generateResponse()" style="width: 100%; margin-bottom: 2rem;">
<i class="ph ph-sparkle"></i> Generate Response
</button>
<div id="response-area" style="display: none;">
<div class="card" style="background: #f9fafb; border: none;">
<label>Generated Response</label>
<div id="markdown-output" class="markdown-body"></div>
</div>
<details style="margin-top: 1rem;">
<summary style="cursor: pointer; padding: 0.5rem; font-weight: 600;">📚 Retrieved Context & Axioms</summary>
<div class="card" style="margin-top: 1rem;">
<textarea id="context-output" rows="5" readonly style="background: white; font-family: monospace; font-size: 0.85rem;"></textarea>
</div>
</details>
</div>
</div>
</section>
<!-- Analytics Tab -->
<section id="tab-analytics" class="tab-content">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon"><i class="ph ph-file-text"></i></div>
<div class="stat-value" id="stat-doc-count">0</div>
<div class="stat-label">Documents</div>
</div>
<div class="stat-card">
<div class="stat-icon"><i class="ph ph-lightning"></i></div>
<div class="stat-value" id="stat-axiom-count">0</div>
<div class="stat-label">Axioms</div>
</div>
<div class="stat-card">
<div class="stat-icon"><i class="ph ph-hard-drives"></i></div>
<div class="stat-value" id="stat-storage">0MB</div>
<div class="stat-label">Storage Used</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">Recent Activity</h2>
</div>
<div class="table-container">
<table id="activity-table">
<thead>
<tr>
<th>Action</th>
<th>Details</th>
<th>Timestamp</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</section>
</main>
<div id="toast-container"></div>
<script>
/**
* RAG Nexus - Frontend & Simulated Backend
* Handles document processing, storage, and retrieval using LocalStorage and JS logic.
*/
// --- State Management ---
const DB_KEY = 'rag_nexus_db';
let state = {
documents: [], // { id, name, content, size, uploaded_at, chunk_count }
axioms: [], // { id, doc_id, source, axiom, confidence }
activity: [] // { action, details, timestamp }
};
// --- Initialization ---
function init() {
loadState();
updateStats();
renderDocuments();
renderAxioms();
renderActivity();
updateAxiomFilter();
logActivity('system', 'Application initialized');
}
function loadState() {
const saved = localStorage.getItem(DB_KEY);
if (saved) {
try {
state = JSON.parse(saved);
} catch (e) {
console.error("Failed to load state", e);
}
}
}
function saveState() {
try {
localStorage.setItem(DB_KEY, JSON.stringify(state));
updateStats();
} catch (e) {
showToast('Storage quota exceeded! Please clear data.', 'error');
}
}
function logActivity(action, details) {
const entry = {
action,
details: typeof details === 'object' ? JSON.stringify(details) : details,
timestamp: new Date().toISOString()
};
state.activity.unshift(entry);
if (state.activity.length > 50) state.activity.pop(); // Keep last 50
saveState();
renderActivity();
}
// --- UI Helpers ---
function switchTab(tabId) {
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
// Find specific button based on onclick attribute matching is tricky, simpler to use index or query
// Just iterating manually for simplicity
const buttons = document.querySelectorAll('.tab-btn');
if(tabId === 'upload') buttons[0].classList.add('active');
if(tabId === 'documents') buttons[1].classList.add('active');
if(tabId === 'axioms') buttons[2].classList.add('active');
if(tabId === 'generate') buttons[3].classList.add('active');
if(tabId === 'analytics') buttons[4].classList.add('active');
document.getElementById(`tab-${tabId}`).classList.add('active');
}
function showToast(message, type = 'info') {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
let icon = 'info';
if(type === 'success') icon = 'check-circle';
if(type === 'error') icon = 'warning-circle';
if(type === 'warning') icon = 'warning';
toast.innerHTML = `<i class="ph ph-${icon}" style="font-size: 1.25rem;"></i> <span>${message}</span>`;
container.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(100%)';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// --- Upload & Processing ---
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
let queuedFiles = [];
dropZone.addEventListener('click', () => fileInput.click());
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('dragover'));
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
handleFiles(e.dataTransfer.files);
});
fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
function handleFiles(files) {
if (!files.length) return;
const queueDiv = document.getElementById('upload-queue');
const tbody = document.querySelector('#queue-table tbody');
tbody.innerHTML = '';
queueDiv.style.display = 'block';
queuedFiles = Array.from(files);
queuedFiles.forEach(file => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${file.name}</td>
<td><span style="color: var(--warning)">Pending</span></td>
<td>${formatBytes(file.size)}</td>
`;
tbody.appendChild(tr);
});
}
async function processFiles() {
if (queuedFiles.length === 0) {
showToast("No files to process", "warning");
return;
}
let processedCount = 0;
const tbody = document.querySelector('#queue-table tbody');
const rows = tbody.querySelectorAll('tr');
for (let i = 0; i < queuedFiles.length; i++) {
const file = queuedFiles[i];
const statusCell = rows[i].cells[1];
try {
// Read file content
const text = await readFileAsText(file);
// Create Document Object
const doc = {
id: generateHash(file.name + file.size + Date.now()),
name: file.name,
content: text,
size: file.size,
uploaded_at: new Date().toISOString(),
chunk_count: Math.ceil(text.length / 500)
};
// Extract Axioms
const newAxioms = extractAxioms(text, doc.id);
// Update State
state.documents.push(doc);
state.axioms.push(...newAxioms);
statusCell.innerHTML = '<span style="color: var(--success)">Processed</span>';
processedCount++;
logActivity('document_uploaded', { name: file.name, size: file.size });
} catch (err) {
console.error(err);
statusCell.innerHTML = '<span style="color: var(--danger)">Failed</span>';
logActivity('upload_failed', { name: file.name, error: err.message });
}
}
saveState();
showToast(`Processed ${processedCount}/${queuedFiles.length} files`, processedCount === queuedFiles.length ? 'success' : 'warning');
queuedFiles = []; // Clear queue
// Refresh other tabs
renderDocuments();
renderAxioms();
updateAxiomFilter();
}
function readFileAsText(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = (e) => reject(e);
reader.readAsText(file);
});
}
function generateHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash).toString(16);
}
function formatBytes(bytes, decimals = 2) {
if (!+bytes) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
}
// --- NLP / Backend Simulation Logic ---
function extractAxioms(text, docId) {
const axioms = [];
// Split into sentences
const sentences = text.match(/[^\.!\?]+[\.!\?]+/g) || [text];
const keyPhrases = [
"principle", "law", "theorem", "axiom", "fundamental",
"always", "never", "must", "should", "rule", "definition",
"is defined as", "refers to", "means that", "implies", "important"
];
sentences.forEach((sentence, idx) => {
sentence = sentence.trim();
if (sentence.length < 20 || sentence.length > 200) return;
let confidence = 0.0;
const lower = sentence.toLowerCase();
keyPhrases.forEach(phrase => {
if (lower.includes(phrase)) confidence += 0.2;
});
// Structural checks
if (sentence[0] === sentence[0].toUpperCase() && sentence.includes(":")) confidence += 0.1;
// Normalize
confidence = Math.min(1.0, confidence);
if (confidence > 0.3) {
axioms.push({
id: Date.now() + Math.random(),
doc_id: docId,
source: `Section ${idx + 1}`,
axiom: sentence,
confidence: parseFloat(confidence.toFixed(2))
});
}
});
return axioms;
}
function generateResponse() {
const query = document.getElementById('query-input').value.trim();
const useAxioms = document.getElementById('use-axioms').checked;
const useContext = document.getElementById('use-context').checked;
if (!query) {
showToast("Please enter a query", "warning");
return;
}
// UI Feedback
const btn = document.querySelector('#tab-generate .btn-primary');
const originalText = btn.innerHTML;
btn.innerHTML = '<i class="ph ph-spinner ph-spin"></i> Analyzing...';
btn.disabled = true;
setTimeout(() => {
// 1. Retrieve Context (Simple TF-IDF / Keyword matching simulation)
let contextText = "";
let retrievedDocs = [];
if (useContext && state.documents.length > 0) {
const queryTokens = tokenize(query);
const scoredDocs = state.documents.map(doc => {
const contentTokens = tokenize(doc.content);
let score = 0;
queryTokens.forEach(t => {
if (contentTokens.includes(t)) score++;
});
return { doc, score };
});
scoredDocs.sort((a, b) => b.score - a.score);
const topDocs = scoredDocs.slice(0, 3).filter(d => d.score > 0);
if (topDocs.length > 0) {
topDocs.forEach(d => {
contextText += `\n\n--- From ${d.doc.name} ---\n${d.doc.content.substring(0, 300)}...`;
retrievedDocs.push(d.doc.name);
});
} else {
contextText = "No specific relevant context found in documents.";
}
}
// 2. Retrieve Axioms
let usedAxioms = [];
if (useAxioms) {
// Randomly pick some axioms or filter by query keywords
const filtered = state.axioms.filter(a => {
const tokens = tokenize(a.axiom);
return tokenize(query).some(q => tokens.includes(q));
});
usedAxioms = filtered.length > 0 ? filtered.slice(0, 3) : state.axioms.slice(0, 3);
}
// 3. Construct Response (Template-based)
const responseMarkdown = buildResponseTemplate(query, contextText, usedAxioms);
// 4. Update UI
const responseArea = document.getElementById('response-area');
const markdownOutput = document.getElementById('markdown-output');
const contextOutput = document.getElementById('context-output');
responseArea.style.display = 'block';
markdownOutput.innerHTML = parseMarkdown(responseMarkdown);
contextOutput.value = `Retrieved Documents:\n${retrievedDocs.join('\n') || 'None'}\n\nUsed Axioms:\n${usedAxioms.map(a => a.axiom).join('\n') || 'None'}`;
logActivity('response_generated', { query: query.substring(0, 50) });
btn.innerHTML = originalText;
btn.disabled = false;
showToast("Response generated successfully", "success");
}, 1500); // Fake delay for "processing"
}
function tokenize(text) {
return text.toLowerCase()
.replace(/[^\w\s]/g, '')
.split(/\s+/)
.filter(w => w.length > 2);
}
function buildResponseTemplate(query, context, axioms) {
let html = `<h3>Response to: "${query}"</h3>`;
if (axioms.length > 0) {
html += `<p><strong>Key Principles:</strong></p><ul>`;
axioms.forEach(a => {
html += `<li>${a.axiom}</li>`;
});
html += `</ul>`;
}
html += `<p><strong>Analysis:</strong><br>`;
if (context && context !== "No specific relevant context found in documents.") {
html += `Based on the uploaded documents, I found relevant sections that address your query. The context suggests that the subject matter is related to the core principles extracted above. <br><br>`;
html += `<strong>Synthesized Answer:</strong><br>`;
html += `According to the extracted principles${axioms.length > 0 ? `, such as "${axioms[0].axiom.substring(0, 50)}..."` : ''}, the documents support the conclusion that the query involves specific rules or definitions. The contextual fragments provide evidence that these rules are applied consistently within the provided text.`;
} else {
html += `I could not find specific context in the documents to fully answer this query. However, based on the general axioms extracted: <br>`;
if(axioms.length > 0) html += `"${axioms[0].axiom}"`;
else html += "No axioms available.";
}
html += `</p>`;
return html;
}
function parseMarkdown(md) {
// Very simple markdown parser
return md
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/\*\*(.*)\*\*/gim, '<strong>$1</strong>')
.replace(/\*(.*)\*/gim, '<em>$1</em>')
.replace(/\n/gim, '<br>');
}
// --- Rendering Functions ---
function renderDocuments() {
const tbody = document.querySelector('#documents-table tbody');
const search = document.getElementById('doc-search').value.toLowerCase();
tbody.innerHTML = '';
const filtered = state.documents.filter(d => d.name.toLowerCase().includes(search));
if (filtered.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; color: var(--text-muted)">No documents found</td></tr>';
return;
}
filtered.forEach(doc => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td style="font-weight: 500">${doc.name}</td>
<td>${formatBytes(doc.size)}</td>
<td>${new Date(doc.uploaded_at).toLocaleDateString()}</td>
<td style="font-family: monospace; color: var(--text-muted)">${doc.id.substring(0, 8)}...</td>
`;
tbody.appendChild(tr);
});
}
function renderAxioms() {
const tbody = document.querySelector('#axioms-table tbody');
const search = document.getElementById('axiom-search').value.toLowerCase();
const filter = document.getElementById('axiom-filter').value;
tbody.innerHTML = '';
let filtered = state.axioms.filter(a => a.axiom.toLowerCase().includes(search));
if (filter) {
const doc = state.documents.find(d => d.id === filter);
const docName = doc ? doc.name : "";
filtered = filtered.filter(a => {
const d = state.documents.find(doc => doc.id === a.doc_id);
return d && d.name === docName;
});
}
// Sort by confidence
filtered.sort((a, b) => b.confidence - a.confidence);
if (filtered.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; color: var(--text-muted)">No axioms found</td></tr>';
return;
}
filtered.forEach(ax => {
const doc = state.documents.find(d => d.id === ax.doc_id);
const tr = document.createElement('tr');
tr.innerHTML =