Jaimodiji's picture
Upload folder using huggingface_hub
aeb61fc verified
// Global Console Logger - Vercel-inspired design
// Include in any page: <script type="module" src="/scripts/Logger.js"></script>
const STYLES = `
#logger-fab {
position: fixed;
bottom: 20px;
right: 20px;
width: 48px;
height: 48px;
border-radius: 50%;
background: #000;
border: 1px solid #333;
color: #fff;
font-size: 20px;
cursor: pointer;
z-index: 99998;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 14px rgba(0,0,0,0.25);
transition: transform 0.15s ease, background 0.15s ease;
}
#logger-fab:hover {
transform: scale(1.05);
background: #111;
}
#logger-fab:active {
transform: scale(0.95);
}
#logger-fab .badge {
position: absolute;
top: -4px;
right: -4px;
min-width: 18px;
height: 18px;
padding: 0 5px;
border-radius: 9px;
background: #f00;
color: #fff;
font-size: 11px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
#logger-modal {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.6);
backdrop-filter: blur(4px);
z-index: 99999;
display: none;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
#logger-modal.open {
display: flex;
align-items: center;
justify-content: center;
}
#logger-container {
width: 90%;
max-width: 900px;
height: 80%;
max-height: 700px;
background: #0a0a0a;
border: 1px solid #333;
border-radius: 12px;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5);
}
#logger-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #333;
background: #0a0a0a;
}
#logger-header h3 {
margin: 0;
font-size: 14px;
font-weight: 500;
color: #fafafa;
display: flex;
align-items: center;
gap: 8px;
}
#logger-header h3::before {
content: '';
width: 8px;
height: 8px;
border-radius: 50%;
background: #22c55e;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
#logger-actions {
display: flex;
gap: 8px;
}
#logger-actions button {
padding: 8px 12px;
border-radius: 6px;
border: 1px solid #333;
background: transparent;
color: #888;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
display: flex;
align-items: center;
gap: 6px;
}
#logger-actions button:hover {
background: #1a1a1a;
color: #fafafa;
border-color: #444;
}
#logger-actions button.primary {
background: #fafafa;
color: #0a0a0a;
border-color: #fafafa;
}
#logger-actions button.primary:hover {
background: #e5e5e5;
}
#logger-actions button.danger {
color: #f87171;
border-color: #7f1d1d;
}
#logger-actions button.danger:hover {
background: #7f1d1d;
color: #fafafa;
}
#logger-filters {
display: flex;
gap: 4px;
padding: 12px 20px;
border-bottom: 1px solid #222;
background: #0a0a0a;
}
.filter-btn {
padding: 6px 12px;
border-radius: 20px;
border: none;
background: transparent;
color: #666;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}
.filter-btn:hover {
color: #999;
}
.filter-btn.active {
background: #1a1a1a;
color: #fafafa;
}
.filter-btn .count {
margin-left: 4px;
padding: 2px 6px;
border-radius: 10px;
background: #222;
font-size: 11px;
}
#logger-content {
flex: 1;
overflow-y: auto;
padding: 0;
background: #0a0a0a;
}
#logger-content::-webkit-scrollbar {
width: 8px;
}
#logger-content::-webkit-scrollbar-track {
background: #0a0a0a;
}
#logger-content::-webkit-scrollbar-thumb {
background: #333;
border-radius: 4px;
}
.log-entry {
display: flex;
padding: 10px 20px;
border-bottom: 1px solid #1a1a1a;
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
font-size: 12px;
line-height: 1.5;
transition: background 0.1s ease;
}
.log-entry:hover {
background: #111;
}
.log-entry.error {
background: rgba(239, 68, 68, 0.1);
border-left: 3px solid #ef4444;
}
.log-entry.warn {
background: rgba(245, 158, 11, 0.1);
border-left: 3px solid #f59e0b;
}
.log-entry .time {
color: #666;
margin-right: 12px;
white-space: nowrap;
min-width: 85px;
}
.log-entry .level {
margin-right: 12px;
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
min-width: 50px;
text-align: center;
}
.log-entry .level.log { background: #1e3a5f; color: #60a5fa; }
.log-entry .level.info { background: #1e3a5f; color: #60a5fa; }
.log-entry .level.warn { background: #422006; color: #fbbf24; }
.log-entry .level.error { background: #450a0a; color: #f87171; }
.log-entry .level.debug { background: #1a1a2e; color: #a78bfa; }
.log-entry .msg {
color: #e5e5e5;
word-break: break-all;
white-space: pre-wrap;
flex: 1;
}
#logger-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 20px;
border-top: 1px solid #333;
background: #0a0a0a;
font-size: 12px;
color: #666;
}
#logger-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #666;
font-size: 14px;
}
#logger-empty svg {
margin-bottom: 16px;
opacity: 0.5;
}
`;
export const Logger = {
logs: [],
maxLogs: 2000,
errorCount: 0,
filter: 'all',
isInitialized: false,
init() {
if (this.isInitialized) return;
this.isInitialized = true;
// Inject styles
const style = document.createElement('style');
style.textContent = STYLES;
document.head.appendChild(style);
// Create FAB and Modal
this.createUI();
// Store original console methods
this.originalConsole = {
log: console.log.bind(console),
warn: console.warn.bind(console),
error: console.error.bind(console),
info: console.info.bind(console),
debug: console.debug.bind(console)
};
// Intercept console methods
const self = this;
console.log = (...args) => { self.capture('log', args); self.originalConsole.log(...args); };
console.warn = (...args) => { self.capture('warn', args); self.originalConsole.warn(...args); };
console.error = (...args) => { self.capture('error', args); self.originalConsole.error(...args); };
console.info = (...args) => { self.capture('info', args); self.originalConsole.info(...args); };
console.debug = (...args) => { self.capture('debug', args); self.originalConsole.debug(...args); };
// Capture uncaught errors
window.addEventListener('error', (e) => {
self.capture('error', [`Uncaught: ${e.message} at ${e.filename}:${e.lineno}`]);
});
window.addEventListener('unhandledrejection', (e) => {
self.capture('error', [`Promise: ${e.reason?.message || e.reason}`]);
});
this.capture('info', ['🚀 Logger initialized']);
},
createUI() {
// FAB Button
const fab = document.createElement('button');
fab.id = 'logger-fab';
fab.innerHTML = `<span style="font-family: monospace;">›_</span><span class="badge" style="display:none">0</span>`;
fab.onclick = () => this.toggle();
document.body.appendChild(fab);
// Modal
const modal = document.createElement('div');
modal.id = 'logger-modal';
modal.innerHTML = `
<div id="logger-container">
<div id="logger-header">
<h3>Console</h3>
<div id="logger-actions">
<button onclick="Logger.clear()">Clear</button>
<button onclick="Logger.copyToClipboard()">Copy</button>
<button class="primary" onclick="Logger.export()">Export</button>
<button class="danger" onclick="Logger.toggle()">✕</button>
</div>
</div>
<div id="logger-filters">
<button class="filter-btn active" data-filter="all" onclick="Logger.setFilter('all')">All<span class="count" id="count-all">0</span></button>
<button class="filter-btn" data-filter="error" onclick="Logger.setFilter('error')">Errors<span class="count" id="count-error">0</span></button>
<button class="filter-btn" data-filter="warn" onclick="Logger.setFilter('warn')">Warnings<span class="count" id="count-warn">0</span></button>
<button class="filter-btn" data-filter="log" onclick="Logger.setFilter('log')">Logs<span class="count" id="count-log">0</span></button>
</div>
<div id="logger-content"></div>
<div id="logger-footer">
<span id="logger-stats"></span>
<span id="logger-path"></span>
</div>
</div>
`;
modal.onclick = (e) => { if (e.target === modal) this.toggle(); };
document.body.appendChild(modal);
},
capture(level, args) {
const timestamp = new Date();
const message = args.map(arg => {
if (arg === null) return 'null';
if (arg === undefined) return 'undefined';
if (typeof arg === 'object') {
try { return JSON.stringify(arg, null, 2); }
catch { return String(arg); }
}
return String(arg);
}).join(' ');
this.logs.push({ timestamp, level, message });
if (level === 'error') {
this.errorCount++;
this.updateBadge();
}
if (this.logs.length > this.maxLogs) {
this.logs = this.logs.slice(-this.maxLogs);
}
// Write to Android if available
if (window.AndroidNative?.writeLog) {
try { window.AndroidNative.writeLog(level.toUpperCase(), message); } catch {}
}
},
updateBadge() {
const badge = document.querySelector('#logger-fab .badge');
if (badge) {
badge.style.display = this.errorCount > 0 ? 'flex' : 'none';
badge.textContent = this.errorCount > 99 ? '99+' : this.errorCount;
}
},
toggle() {
const modal = document.getElementById('logger-modal');
if (modal.classList.contains('open')) {
modal.classList.remove('open');
} else {
modal.classList.add('open');
this.render();
}
},
setFilter(filter) {
this.filter = filter;
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.filter === filter);
});
this.render();
},
render() {
const content = document.getElementById('logger-content');
if (!content) return;
const filtered = this.filter === 'all'
? this.logs
: this.logs.filter(l => l.level === this.filter);
// Update counts
const counts = { all: this.logs.length, error: 0, warn: 0, log: 0, info: 0, debug: 0 };
this.logs.forEach(l => counts[l.level] = (counts[l.level] || 0) + 1);
counts.log += counts.info + counts.debug;
['all', 'error', 'warn', 'log'].forEach(k => {
const el = document.getElementById(`count-${k}`);
if (el) el.textContent = counts[k];
});
if (filtered.length === 0) {
content.innerHTML = `
<div id="logger-empty">
<svg width="48" height="48" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path d="M9 12h6m-3-3v6m-7 4h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
<span>No logs yet</span>
</div>
`;
return;
}
content.innerHTML = filtered.map(log => {
const time = log.timestamp.toLocaleTimeString('en-US', { hour12: false });
const levelClass = log.level;
const entryClass = ['error', 'warn'].includes(log.level) ? log.level : '';
return `
<div class="log-entry ${entryClass}">
<span class="time">${time}</span>
<span class="level ${levelClass}">${log.level}</span>
<span class="msg">${this.escapeHtml(log.message)}</span>
</div>
`;
}).join('');
content.scrollTop = content.scrollHeight;
// Update footer
const stats = document.getElementById('logger-stats');
if (stats) stats.textContent = `${filtered.length} entries`;
const path = document.getElementById('logger-path');
if (path && window.AndroidNative?.getLogFilePath) {
try { path.textContent = window.AndroidNative.getLogFilePath(); } catch {}
}
},
escapeHtml(str) {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
},
getLogsText() {
return this.logs.map(l => {
const ts = l.timestamp.toISOString();
return `[${ts}] [${l.level.toUpperCase()}] ${l.message}`;
}).join('\n');
},
export() {
const content = this.getLogsText();
const filename = `logs_${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
if (window.AndroidNative?.saveBlob) {
const blob = new Blob([content], { type: 'text/plain' });
const reader = new FileReader();
reader.onloadend = () => {
window.AndroidNative.saveBlob(reader.result.split(',')[1], filename, 'text/plain');
};
reader.readAsDataURL(blob);
return;
}
const blob = new Blob([content], { type: 'text/plain' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
},
async copyToClipboard() {
try {
await navigator.clipboard.writeText(this.getLogsText());
this.capture('info', ['📋 Logs copied to clipboard']);
this.render();
} catch {
prompt('Copy logs:', this.getLogsText().slice(0, 5000));
}
},
clear() {
this.logs = [];
this.errorCount = 0;
this.updateBadge();
this.capture('info', ['🗑️ Logs cleared']);
this.render();
}
};
// Auto-initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => Logger.init());
} else {
Logger.init();
}
// Global access
window.Logger = Logger;