MAC / frontend /src /lib /utils.js
Aaryan17's picture
chore: upload MAC codebase to HF Space
0e76632 verified
/** Formatting and utility helpers */
export function formatNumber(n) {
if (n == null) return '—';
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
if (n >= 1_000) return (n / 1_000).toFixed(1) + 'K';
return String(n);
}
export function formatBytes(bytes) {
if (!bytes) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
export function formatDate(ts) {
if (!ts) return '—';
return new Date(ts).toLocaleString();
}
export function formatRelative(ts) {
if (!ts) return '—';
const diff = Date.now() - new Date(ts).getTime();
const s = Math.floor(diff / 1000);
if (s < 60) return 'just now';
const m = Math.floor(s / 60);
if (m < 60) return `${m}m ago`;
const h = Math.floor(m / 60);
if (h < 24) return `${h}h ago`;
const d = Math.floor(h / 24);
return `${d}d ago`;
}
export function roleColor(role) {
return { admin: 'red', faculty: 'yellow', student: 'blue' }[role] ?? 'gray';
}
export function tierColor(tier) {
return {
GPU_NVIDIA: 'green',
GPU_AMD: 'yellow',
CPU_ONLY: 'gray',
}[tier] ?? 'gray';
}
export function modelTagColor(tag) {
return {
RECOMMENDED: 'green',
POSSIBLE: 'yellow',
NOT_RECOMMENDED: 'red',
CPU_ONLY: 'gray',
}[tag] ?? 'gray';
}
/** Debounce a function */
export function debounce(fn, ms = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
}
/** Copy text to clipboard and return a promise */
export async function copyToClipboard(text) {
await navigator.clipboard.writeText(text);
}
/** Simple markdown-to-HTML for chat messages (code blocks, bold, italic, lists) */
export function renderMarkdown(text) {
if (!text) return '';
let html = text
// Code blocks
.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) =>
`<pre><code class="language-${lang || 'text'}">${escapeHtml(code.trim())}</code></pre>`)
// Inline code
.replace(/`([^`]+)`/g, (_, c) => `<code class="inline-code">${escapeHtml(c)}</code>`)
// Bold
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
// Italic
.replace(/\*(.*?)\*/g, '<em>$1</em>')
// Headers
.replace(/^### (.+)$/gm, '<h3 class="text-lg font-semibold mt-3 mb-1">$1</h3>')
.replace(/^## (.+)$/gm, '<h2 class="text-xl font-semibold mt-4 mb-2">$1</h2>')
.replace(/^# (.+)$/gm, '<h1 class="text-2xl font-bold mt-4 mb-2">$1</h1>')
// Unordered list
.replace(/^- (.+)$/gm, '<li class="ml-4 list-disc">$1</li>')
.replace(/(<li.*<\/li>\n?)+/g, m => `<ul class="my-2 space-y-1">${m}</ul>`)
// Numbered list
.replace(/^\d+\. (.+)$/gm, '<li class="ml-4 list-decimal">$1</li>')
// Paragraphs (double newlines)
.replace(/\n\n/g, '</p><p class="mb-2">')
// Single newlines
.replace(/\n/g, '<br>');
return `<p class="mb-2">${html}</p>`;
}
function escapeHtml(str) {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
/** Generate a heatmap grid for 26 weeks of activity data */
export function buildHeatmap(dailyData) {
// dailyData: [{date: "2026-01-01", count: 5}, ...]
const map = {};
dailyData.forEach(d => { map[d.date] = d.count; });
const today = new Date();
const cells = [];
for (let i = 181; i >= 0; i--) {
const d = new Date(today);
d.setDate(d.getDate() - i);
const key = d.toISOString().slice(0, 10);
cells.push({ date: key, count: map[key] ?? 0 });
}
return cells;
}
export function heatmapColor(count) {
if (!count) return 'var(--surface3)';
if (count < 3) return 'rgba(217,116,73,0.25)';
if (count < 8) return 'rgba(217,116,73,0.50)';
if (count < 20) return 'rgba(217,116,73,0.75)';
return 'var(--accent)';
}