Spaces:
Running
Running
Update index.html
Browse files- index.html +7 -106
index.html
CHANGED
|
@@ -171,7 +171,7 @@
|
|
| 171 |
.input-line {
|
| 172 |
display: flex;
|
| 173 |
gap: 10px;
|
| 174 |
-
margin-bottom:
|
| 175 |
flex-wrap: wrap;
|
| 176 |
}
|
| 177 |
|
|
@@ -242,51 +242,6 @@
|
|
| 242 |
.exec-btn:hover { opacity: 0.85; }
|
| 243 |
.exec-btn:disabled { opacity: 0.4; cursor: wait; }
|
| 244 |
|
| 245 |
-
/* ── Quick Examples Bar ── */
|
| 246 |
-
.quick-bar {
|
| 247 |
-
display: flex;
|
| 248 |
-
align-items: center;
|
| 249 |
-
gap: 6px;
|
| 250 |
-
flex-wrap: wrap;
|
| 251 |
-
margin-bottom: 18px;
|
| 252 |
-
padding-bottom: 14px;
|
| 253 |
-
border-bottom: 1px solid #141414;
|
| 254 |
-
}
|
| 255 |
-
|
| 256 |
-
.quick-label {
|
| 257 |
-
font-size: 0.68rem;
|
| 258 |
-
color: #555555;
|
| 259 |
-
text-transform: uppercase;
|
| 260 |
-
letter-spacing: 1px;
|
| 261 |
-
font-weight: 600;
|
| 262 |
-
margin-right: 4px;
|
| 263 |
-
}
|
| 264 |
-
|
| 265 |
-
.quick-chip {
|
| 266 |
-
background: #080808;
|
| 267 |
-
border: 1px solid #1f1f1f;
|
| 268 |
-
color: #888888;
|
| 269 |
-
padding: 4px 10px;
|
| 270 |
-
border-radius: 3px;
|
| 271 |
-
cursor: pointer;
|
| 272 |
-
font-size: 0.7rem;
|
| 273 |
-
font-weight: 500;
|
| 274 |
-
transition: all 0.15s;
|
| 275 |
-
display: inline-flex;
|
| 276 |
-
align-items: center;
|
| 277 |
-
gap: 5px;
|
| 278 |
-
}
|
| 279 |
-
.quick-chip:hover {
|
| 280 |
-
border-color: #ffffff;
|
| 281 |
-
color: #ffffff;
|
| 282 |
-
background: #111111;
|
| 283 |
-
}
|
| 284 |
-
.quick-chip .chip-tag {
|
| 285 |
-
font-size: 0.6rem;
|
| 286 |
-
color: #555555;
|
| 287 |
-
text-transform: uppercase;
|
| 288 |
-
}
|
| 289 |
-
|
| 290 |
/* ── Status ── */
|
| 291 |
#statusMsg {
|
| 292 |
padding: 10px 14px;
|
|
@@ -302,7 +257,7 @@
|
|
| 302 |
/* ── Empty / Home State ── */
|
| 303 |
.empty-state {
|
| 304 |
text-align: center;
|
| 305 |
-
padding-top:
|
| 306 |
}
|
| 307 |
|
| 308 |
.empty-title {
|
|
@@ -1068,29 +1023,6 @@
|
|
| 1068 |
</button>
|
| 1069 |
</div>
|
| 1070 |
|
| 1071 |
-
<!-- Always-visible Quick Examples Bar -->
|
| 1072 |
-
<div class="quick-bar">
|
| 1073 |
-
<span class="quick-label">examples:</span>
|
| 1074 |
-
<button class="quick-chip" data-query="prithivMLmods/Qwen-Image-Edit-2511-Unblur-Upscale">
|
| 1075 |
-
<span class="chip-tag">[model]</span> prithivMLmods/Qwen-Image-Edit-2511-Unblur-Upscale
|
| 1076 |
-
</button>
|
| 1077 |
-
<button class="quick-chip" data-query="autotrust/JEV-27B-VL">
|
| 1078 |
-
<span class="chip-tag">[org-model]</span> autotrust/JEV-27B-VL
|
| 1079 |
-
</button>
|
| 1080 |
-
<button class="quick-chip" data-query="prithivMLmods/Qwen-Image-Edit-2511-LoRAs-Fast">
|
| 1081 |
-
<span class="chip-tag">[space]</span> prithivMLmods/Qwen-Image-Edit-2511-LoRAs-Fast
|
| 1082 |
-
</button>
|
| 1083 |
-
<button class="quick-chip" data-query="prithivMLmods/FireRed-Image-Edit-1.0-Fast">
|
| 1084 |
-
<span class="chip-tag">[space]</span> prithivMLmods/FireRed-Image-Edit-1.0-Fast
|
| 1085 |
-
</button>
|
| 1086 |
-
<button class="quick-chip" data-query="autotrust">
|
| 1087 |
-
<span class="chip-tag">[org]</span> autotrust
|
| 1088 |
-
</button>
|
| 1089 |
-
<button class="quick-chip" data-query="prithivMLmods">
|
| 1090 |
-
<span class="chip-tag">[user]</span> prithivMLmods
|
| 1091 |
-
</button>
|
| 1092 |
-
</div>
|
| 1093 |
-
|
| 1094 |
<div id="statusMsg"></div>
|
| 1095 |
|
| 1096 |
<!-- Empty State -->
|
|
@@ -1238,7 +1170,7 @@
|
|
| 1238 |
repoData: null, // single repo metadata
|
| 1239 |
likers: [], // array of likers if repo
|
| 1240 |
ownerRepos: [], // all models, datasets, spaces for owner/org
|
| 1241 |
-
dailySeries: [], // [{ date, total, cumulative, models, datasets, spaces
|
| 1242 |
exactTimestamps: false,
|
| 1243 |
chartMode: 'combined', // 'combined' | 'daily' | 'cumulative' | 'breakdown'
|
| 1244 |
chartRange: 'all', // 'all' | '90' | '30' | '7'
|
|
@@ -1353,10 +1285,8 @@
|
|
| 1353 |
let s = (rawInput || '').trim();
|
| 1354 |
if (!s) return null;
|
| 1355 |
|
| 1356 |
-
// Strip URL wrapper if user pasted full https://huggingface.co/... link
|
| 1357 |
s = s.replace(/^https?:\/\/(www\.)?(huggingface\.co|hf\.co)\//i, '');
|
| 1358 |
s = s.replace(/^@/, '').replace(/\/+$/, '');
|
| 1359 |
-
// Strip tree/blob suffixes if pasted from browser bar
|
| 1360 |
s = s.replace(/\/(tree|blob|discussions|settings)\/.*$/i, '');
|
| 1361 |
|
| 1362 |
let forcedType = selectedType !== 'auto' ? selectedType : null;
|
|
@@ -1528,7 +1458,6 @@
|
|
| 1528 |
const days = getDayList(startIso, nowIso);
|
| 1529 |
const totalLikes = Math.max(repoData.likes || 0, likers.length);
|
| 1530 |
|
| 1531 |
-
// Check if likers array includes explicit timestamps
|
| 1532 |
const timestampedLikers = likers.filter(l => l.at || l.likedAt || l.createdAt || l.date);
|
| 1533 |
const hasExactTimestamps = timestampedLikers.length > 0;
|
| 1534 |
|
|
@@ -1548,7 +1477,6 @@
|
|
| 1548 |
}
|
| 1549 |
});
|
| 1550 |
} else if (totalLikes > 0) {
|
| 1551 |
-
// Distribute totalLikes across days since published, anchored on launch spike + commit activity dates
|
| 1552 |
const rng = createRng(repoData.id || repoData.modelId || 'hf');
|
| 1553 |
const commitDaySet = new Set();
|
| 1554 |
if (Array.isArray(commits)) {
|
|
@@ -1563,7 +1491,6 @@
|
|
| 1563 |
|
| 1564 |
const weights = days.map((d, idx) => {
|
| 1565 |
const progress = idx / Math.max(1, days.length - 1);
|
| 1566 |
-
// Launch surge during first 14 days + steady community discovery + commit release spikes
|
| 1567 |
const launchBoost = idx < 3 ? 4.2 : idx < 10 ? 2.6 : idx < 25 ? 1.5 : 1.0;
|
| 1568 |
const commitBoost = commitDaySet.has(d) ? 2.4 : 1.0;
|
| 1569 |
const recencyWave = 0.7 + 0.5 * Math.sin(progress * Math.PI * 3 + rng() * 1.8);
|
|
@@ -1571,15 +1498,14 @@
|
|
| 1571 |
});
|
| 1572 |
|
| 1573 |
const weightSum = weights.reduce((a, b) => a + b, 0);
|
| 1574 |
-
let allocated = 0;
|
| 1575 |
const exactQuotas = weights.map(w => (w / weightSum) * totalLikes);
|
| 1576 |
const floors = exactQuotas.map(q => Math.floor(q));
|
| 1577 |
-
allocated = floors.reduce((a, b) => a + b, 0);
|
| 1578 |
|
| 1579 |
const remainders = exactQuotas.map((q, idx) => ({ idx, rem: q - floors[idx] }));
|
| 1580 |
remainders.sort((a, b) => b.rem - a.rem);
|
| 1581 |
|
| 1582 |
-
|
| 1583 |
for (let i = 0; i < diff && i < remainders.length; i++) {
|
| 1584 |
floors[remainders[i].idx]++;
|
| 1585 |
}
|
|
@@ -1589,7 +1515,6 @@
|
|
| 1589 |
});
|
| 1590 |
}
|
| 1591 |
|
| 1592 |
-
// Assign estimated date to each liker in chronological order if not explicitly present
|
| 1593 |
if (!hasExactTimestamps && likers.length > 0) {
|
| 1594 |
const expandedDates = [];
|
| 1595 |
days.forEach(d => {
|
|
@@ -1646,10 +1571,9 @@
|
|
| 1646 |
|
| 1647 |
const dayBuckets = {};
|
| 1648 |
days.forEach(d => {
|
| 1649 |
-
dayBuckets[d] = { models: 0, datasets: 0, spaces: 0
|
| 1650 |
});
|
| 1651 |
|
| 1652 |
-
// For each repo, distribute its likes from its createdAt to today (with launch surge on its publish week)
|
| 1653 |
allRepos.forEach(repo => {
|
| 1654 |
const likes = typeof repo.likes === 'number' ? repo.likes : 0;
|
| 1655 |
const rCreated = repo.createdAt || repo.lastModified;
|
|
@@ -1670,7 +1594,7 @@
|
|
| 1670 |
const wSum = weights.reduce((a, b) => a + b, 0);
|
| 1671 |
const quotas = weights.map(w => (w / wSum) * likes);
|
| 1672 |
const floors = quotas.map(q => Math.floor(q));
|
| 1673 |
-
|
| 1674 |
|
| 1675 |
const order = quotas.map((q, i) => ({ i, r: q - floors[i] })).sort((a, b) => b.r - a.r);
|
| 1676 |
for (let k = 0; k < rem && k < order.length; k++) {
|
|
@@ -1781,7 +1705,6 @@
|
|
| 1781 |
els.tabLikersBtn.classList.remove('hidden');
|
| 1782 |
|
| 1783 |
} else {
|
| 1784 |
-
// Organization or User mode
|
| 1785 |
const owner = parsed.owner;
|
| 1786 |
showMsg(`<span class="spinner"></span> fetching organization / user profile & all repositories for ${escapeHtml(owner)}...`, 'loading');
|
| 1787 |
|
|
@@ -1913,7 +1836,6 @@
|
|
| 1913 |
}
|
| 1914 |
els.pulseSection.style.display = 'block';
|
| 1915 |
|
| 1916 |
-
// Show up to the last 365 days in the matrix
|
| 1917 |
const slice = series.slice(-365);
|
| 1918 |
const maxDaily = Math.max(1, ...slice.map(d => d.total));
|
| 1919 |
const activeDays = slice.filter(d => d.total > 0).length;
|
|
@@ -2066,7 +1988,6 @@
|
|
| 2066 |
const W = rect.width;
|
| 2067 |
const H = rect.height;
|
| 2068 |
|
| 2069 |
-
// Clear background
|
| 2070 |
ctx.fillStyle = '#000000';
|
| 2071 |
ctx.fillRect(0, 0, W, H);
|
| 2072 |
|
|
@@ -2087,14 +2008,12 @@
|
|
| 2087 |
|
| 2088 |
state.chartGeom = { padL, padR, padT, padB, plotW, plotH, W, H, data };
|
| 2089 |
|
| 2090 |
-
// Header subtitle & legend
|
| 2091 |
const firstDate = data[0].date;
|
| 2092 |
const lastDate = data[data.length - 1].date;
|
| 2093 |
const windowLikes = data.reduce((s, d) => s + d.total, 0);
|
| 2094 |
els.graphHeaderTitle.textContent = `${state.targetId} — like polling graph`;
|
| 2095 |
els.graphHeaderSub.textContent = `[${firstDate} → ${lastDate}] (${data.length}d, +${formatNumFull(windowLikes)} likes polled)`;
|
| 2096 |
|
| 2097 |
-
// Horizontal grid lines & Y-axis labels
|
| 2098 |
ctx.font = '500 10px "JetBrains Mono", monospace';
|
| 2099 |
const gridSteps = 4;
|
| 2100 |
|
|
@@ -2109,7 +2028,6 @@
|
|
| 2109 |
ctx.lineTo(padL + plotW, y);
|
| 2110 |
ctx.stroke();
|
| 2111 |
|
| 2112 |
-
// Left Y-axis label
|
| 2113 |
const leftVal = state.chartMode === 'cumulative'
|
| 2114 |
? Math.round(minCum + frac * cumSpan)
|
| 2115 |
: Math.round(frac * maxDaily);
|
|
@@ -2118,7 +2036,6 @@
|
|
| 2118 |
ctx.textBaseline = 'middle';
|
| 2119 |
ctx.fillText(formatNum(leftVal), padL - 8, y);
|
| 2120 |
|
| 2121 |
-
// Right Y-axis label for combined mode (Cumulative)
|
| 2122 |
if (state.chartMode === 'combined') {
|
| 2123 |
const rightVal = Math.round(minCum + frac * cumSpan);
|
| 2124 |
ctx.fillStyle = '#888888';
|
|
@@ -2127,7 +2044,6 @@
|
|
| 2127 |
}
|
| 2128 |
}
|
| 2129 |
|
| 2130 |
-
// X-axis date ticks
|
| 2131 |
const tickCount = Math.min(data.length, Math.max(2, Math.floor(plotW / 110)));
|
| 2132 |
ctx.fillStyle = '#666666';
|
| 2133 |
ctx.textAlign = 'center';
|
|
@@ -2143,7 +2059,6 @@
|
|
| 2143 |
const stepX = n > 1 ? plotW / (n - 1) : plotW;
|
| 2144 |
const barW = Math.max(1.5, Math.min(18, (plotW / n) * 0.72));
|
| 2145 |
|
| 2146 |
-
// 1. Render Daily Bars (Combined, Daily, or Breakdown modes)
|
| 2147 |
if (state.chartMode === 'combined' || state.chartMode === 'daily') {
|
| 2148 |
data.forEach((d, idx) => {
|
| 2149 |
if (d.total <= 0) return;
|
|
@@ -2157,7 +2072,6 @@
|
|
| 2157 |
ctx.fillRect(cx - barW / 2, by, barW, bh);
|
| 2158 |
});
|
| 2159 |
} else if (state.chartMode === 'breakdown') {
|
| 2160 |
-
// Stacked bars for Models (#ffffff), Spaces (#888888), Datasets (#444444)
|
| 2161 |
data.forEach((d, idx) => {
|
| 2162 |
if (d.total <= 0) return;
|
| 2163 |
const cx = padL + (n === 1 ? plotW / 2 : idx * stepX);
|
|
@@ -2179,7 +2093,6 @@
|
|
| 2179 |
});
|
| 2180 |
}
|
| 2181 |
|
| 2182 |
-
// 2. Render Cumulative Growth Curve (Combined or Cumulative modes)
|
| 2183 |
if (state.chartMode === 'combined' || state.chartMode === 'cumulative') {
|
| 2184 |
const pts = data.map((d, idx) => ({
|
| 2185 |
x: padL + (n === 1 ? plotW / 2 : idx * stepX),
|
|
@@ -2212,7 +2125,6 @@
|
|
| 2212 |
}
|
| 2213 |
}
|
| 2214 |
|
| 2215 |
-
// 3. Hover Crosshair & Highlight Dot
|
| 2216 |
if (state.hoverIndex >= 0 && state.hoverIndex < data.length) {
|
| 2217 |
const hi = state.hoverIndex;
|
| 2218 |
const hd = data[hi];
|
|
@@ -2239,7 +2151,6 @@
|
|
| 2239 |
}
|
| 2240 |
}
|
| 2241 |
|
| 2242 |
-
// Update Legend Bar
|
| 2243 |
renderGraphLegend();
|
| 2244 |
}
|
| 2245 |
|
|
@@ -2672,14 +2583,12 @@
|
|
| 2672 |
|
| 2673 |
let y = doc.lastAutoTable.finalY + 8;
|
| 2674 |
|
| 2675 |
-
// Embed Graph Canvas Image
|
| 2676 |
const chartImg = els.likeCanvas.toDataURL('image/png');
|
| 2677 |
doc.setFontSize(11);
|
| 2678 |
doc.text('Daily & Cumulative Likes Graph:', 14, y);
|
| 2679 |
doc.addImage(chartImg, 'PNG', 14, y + 3, 182, 62);
|
| 2680 |
y += 72;
|
| 2681 |
|
| 2682 |
-
// Top Daily Spikes Table
|
| 2683 |
const topDays = [...series].sort((a, b) => b.total - a.total).slice(0, 35);
|
| 2684 |
doc.autoTable({
|
| 2685 |
startY: y,
|
|
@@ -2782,14 +2691,6 @@
|
|
| 2782 |
els.fetchBtn.addEventListener('click', executeQuery);
|
| 2783 |
els.targetInput.addEventListener('keypress', e => { if (e.key === 'Enter') executeQuery(); });
|
| 2784 |
|
| 2785 |
-
document.querySelectorAll('.quick-chip').forEach(chip => {
|
| 2786 |
-
chip.addEventListener('click', () => {
|
| 2787 |
-
els.targetInput.value = chip.dataset.query;
|
| 2788 |
-
els.typeSelect.value = 'auto';
|
| 2789 |
-
executeQuery();
|
| 2790 |
-
});
|
| 2791 |
-
});
|
| 2792 |
-
|
| 2793 |
document.querySelectorAll('[data-mode]').forEach(btn => {
|
| 2794 |
btn.addEventListener('click', () => {
|
| 2795 |
state.chartMode = btn.dataset.mode;
|
|
|
|
| 171 |
.input-line {
|
| 172 |
display: flex;
|
| 173 |
gap: 10px;
|
| 174 |
+
margin-bottom: 20px;
|
| 175 |
flex-wrap: wrap;
|
| 176 |
}
|
| 177 |
|
|
|
|
| 242 |
.exec-btn:hover { opacity: 0.85; }
|
| 243 |
.exec-btn:disabled { opacity: 0.4; cursor: wait; }
|
| 244 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
/* ── Status ── */
|
| 246 |
#statusMsg {
|
| 247 |
padding: 10px 14px;
|
|
|
|
| 257 |
/* ── Empty / Home State ── */
|
| 258 |
.empty-state {
|
| 259 |
text-align: center;
|
| 260 |
+
padding-top: 40px;
|
| 261 |
}
|
| 262 |
|
| 263 |
.empty-title {
|
|
|
|
| 1023 |
</button>
|
| 1024 |
</div>
|
| 1025 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1026 |
<div id="statusMsg"></div>
|
| 1027 |
|
| 1028 |
<!-- Empty State -->
|
|
|
|
| 1170 |
repoData: null, // single repo metadata
|
| 1171 |
likers: [], // array of likers if repo
|
| 1172 |
ownerRepos: [], // all models, datasets, spaces for owner/org
|
| 1173 |
+
dailySeries: [], // [{ date, total, cumulative, models, datasets, spaces }]
|
| 1174 |
exactTimestamps: false,
|
| 1175 |
chartMode: 'combined', // 'combined' | 'daily' | 'cumulative' | 'breakdown'
|
| 1176 |
chartRange: 'all', // 'all' | '90' | '30' | '7'
|
|
|
|
| 1285 |
let s = (rawInput || '').trim();
|
| 1286 |
if (!s) return null;
|
| 1287 |
|
|
|
|
| 1288 |
s = s.replace(/^https?:\/\/(www\.)?(huggingface\.co|hf\.co)\//i, '');
|
| 1289 |
s = s.replace(/^@/, '').replace(/\/+$/, '');
|
|
|
|
| 1290 |
s = s.replace(/\/(tree|blob|discussions|settings)\/.*$/i, '');
|
| 1291 |
|
| 1292 |
let forcedType = selectedType !== 'auto' ? selectedType : null;
|
|
|
|
| 1458 |
const days = getDayList(startIso, nowIso);
|
| 1459 |
const totalLikes = Math.max(repoData.likes || 0, likers.length);
|
| 1460 |
|
|
|
|
| 1461 |
const timestampedLikers = likers.filter(l => l.at || l.likedAt || l.createdAt || l.date);
|
| 1462 |
const hasExactTimestamps = timestampedLikers.length > 0;
|
| 1463 |
|
|
|
|
| 1477 |
}
|
| 1478 |
});
|
| 1479 |
} else if (totalLikes > 0) {
|
|
|
|
| 1480 |
const rng = createRng(repoData.id || repoData.modelId || 'hf');
|
| 1481 |
const commitDaySet = new Set();
|
| 1482 |
if (Array.isArray(commits)) {
|
|
|
|
| 1491 |
|
| 1492 |
const weights = days.map((d, idx) => {
|
| 1493 |
const progress = idx / Math.max(1, days.length - 1);
|
|
|
|
| 1494 |
const launchBoost = idx < 3 ? 4.2 : idx < 10 ? 2.6 : idx < 25 ? 1.5 : 1.0;
|
| 1495 |
const commitBoost = commitDaySet.has(d) ? 2.4 : 1.0;
|
| 1496 |
const recencyWave = 0.7 + 0.5 * Math.sin(progress * Math.PI * 3 + rng() * 1.8);
|
|
|
|
| 1498 |
});
|
| 1499 |
|
| 1500 |
const weightSum = weights.reduce((a, b) => a + b, 0);
|
|
|
|
| 1501 |
const exactQuotas = weights.map(w => (w / weightSum) * totalLikes);
|
| 1502 |
const floors = exactQuotas.map(q => Math.floor(q));
|
| 1503 |
+
const allocated = floors.reduce((a, b) => a + b, 0);
|
| 1504 |
|
| 1505 |
const remainders = exactQuotas.map((q, idx) => ({ idx, rem: q - floors[idx] }));
|
| 1506 |
remainders.sort((a, b) => b.rem - a.rem);
|
| 1507 |
|
| 1508 |
+
const diff = totalLikes - allocated;
|
| 1509 |
for (let i = 0; i < diff && i < remainders.length; i++) {
|
| 1510 |
floors[remainders[i].idx]++;
|
| 1511 |
}
|
|
|
|
| 1515 |
});
|
| 1516 |
}
|
| 1517 |
|
|
|
|
| 1518 |
if (!hasExactTimestamps && likers.length > 0) {
|
| 1519 |
const expandedDates = [];
|
| 1520 |
days.forEach(d => {
|
|
|
|
| 1571 |
|
| 1572 |
const dayBuckets = {};
|
| 1573 |
days.forEach(d => {
|
| 1574 |
+
dayBuckets[d] = { models: 0, datasets: 0, spaces: 0 };
|
| 1575 |
});
|
| 1576 |
|
|
|
|
| 1577 |
allRepos.forEach(repo => {
|
| 1578 |
const likes = typeof repo.likes === 'number' ? repo.likes : 0;
|
| 1579 |
const rCreated = repo.createdAt || repo.lastModified;
|
|
|
|
| 1594 |
const wSum = weights.reduce((a, b) => a + b, 0);
|
| 1595 |
const quotas = weights.map(w => (w / wSum) * likes);
|
| 1596 |
const floors = quotas.map(q => Math.floor(q));
|
| 1597 |
+
const rem = likes - floors.reduce((a, b) => a + b, 0);
|
| 1598 |
|
| 1599 |
const order = quotas.map((q, i) => ({ i, r: q - floors[i] })).sort((a, b) => b.r - a.r);
|
| 1600 |
for (let k = 0; k < rem && k < order.length; k++) {
|
|
|
|
| 1705 |
els.tabLikersBtn.classList.remove('hidden');
|
| 1706 |
|
| 1707 |
} else {
|
|
|
|
| 1708 |
const owner = parsed.owner;
|
| 1709 |
showMsg(`<span class="spinner"></span> fetching organization / user profile & all repositories for ${escapeHtml(owner)}...`, 'loading');
|
| 1710 |
|
|
|
|
| 1836 |
}
|
| 1837 |
els.pulseSection.style.display = 'block';
|
| 1838 |
|
|
|
|
| 1839 |
const slice = series.slice(-365);
|
| 1840 |
const maxDaily = Math.max(1, ...slice.map(d => d.total));
|
| 1841 |
const activeDays = slice.filter(d => d.total > 0).length;
|
|
|
|
| 1988 |
const W = rect.width;
|
| 1989 |
const H = rect.height;
|
| 1990 |
|
|
|
|
| 1991 |
ctx.fillStyle = '#000000';
|
| 1992 |
ctx.fillRect(0, 0, W, H);
|
| 1993 |
|
|
|
|
| 2008 |
|
| 2009 |
state.chartGeom = { padL, padR, padT, padB, plotW, plotH, W, H, data };
|
| 2010 |
|
|
|
|
| 2011 |
const firstDate = data[0].date;
|
| 2012 |
const lastDate = data[data.length - 1].date;
|
| 2013 |
const windowLikes = data.reduce((s, d) => s + d.total, 0);
|
| 2014 |
els.graphHeaderTitle.textContent = `${state.targetId} — like polling graph`;
|
| 2015 |
els.graphHeaderSub.textContent = `[${firstDate} → ${lastDate}] (${data.length}d, +${formatNumFull(windowLikes)} likes polled)`;
|
| 2016 |
|
|
|
|
| 2017 |
ctx.font = '500 10px "JetBrains Mono", monospace';
|
| 2018 |
const gridSteps = 4;
|
| 2019 |
|
|
|
|
| 2028 |
ctx.lineTo(padL + plotW, y);
|
| 2029 |
ctx.stroke();
|
| 2030 |
|
|
|
|
| 2031 |
const leftVal = state.chartMode === 'cumulative'
|
| 2032 |
? Math.round(minCum + frac * cumSpan)
|
| 2033 |
: Math.round(frac * maxDaily);
|
|
|
|
| 2036 |
ctx.textBaseline = 'middle';
|
| 2037 |
ctx.fillText(formatNum(leftVal), padL - 8, y);
|
| 2038 |
|
|
|
|
| 2039 |
if (state.chartMode === 'combined') {
|
| 2040 |
const rightVal = Math.round(minCum + frac * cumSpan);
|
| 2041 |
ctx.fillStyle = '#888888';
|
|
|
|
| 2044 |
}
|
| 2045 |
}
|
| 2046 |
|
|
|
|
| 2047 |
const tickCount = Math.min(data.length, Math.max(2, Math.floor(plotW / 110)));
|
| 2048 |
ctx.fillStyle = '#666666';
|
| 2049 |
ctx.textAlign = 'center';
|
|
|
|
| 2059 |
const stepX = n > 1 ? plotW / (n - 1) : plotW;
|
| 2060 |
const barW = Math.max(1.5, Math.min(18, (plotW / n) * 0.72));
|
| 2061 |
|
|
|
|
| 2062 |
if (state.chartMode === 'combined' || state.chartMode === 'daily') {
|
| 2063 |
data.forEach((d, idx) => {
|
| 2064 |
if (d.total <= 0) return;
|
|
|
|
| 2072 |
ctx.fillRect(cx - barW / 2, by, barW, bh);
|
| 2073 |
});
|
| 2074 |
} else if (state.chartMode === 'breakdown') {
|
|
|
|
| 2075 |
data.forEach((d, idx) => {
|
| 2076 |
if (d.total <= 0) return;
|
| 2077 |
const cx = padL + (n === 1 ? plotW / 2 : idx * stepX);
|
|
|
|
| 2093 |
});
|
| 2094 |
}
|
| 2095 |
|
|
|
|
| 2096 |
if (state.chartMode === 'combined' || state.chartMode === 'cumulative') {
|
| 2097 |
const pts = data.map((d, idx) => ({
|
| 2098 |
x: padL + (n === 1 ? plotW / 2 : idx * stepX),
|
|
|
|
| 2125 |
}
|
| 2126 |
}
|
| 2127 |
|
|
|
|
| 2128 |
if (state.hoverIndex >= 0 && state.hoverIndex < data.length) {
|
| 2129 |
const hi = state.hoverIndex;
|
| 2130 |
const hd = data[hi];
|
|
|
|
| 2151 |
}
|
| 2152 |
}
|
| 2153 |
|
|
|
|
| 2154 |
renderGraphLegend();
|
| 2155 |
}
|
| 2156 |
|
|
|
|
| 2583 |
|
| 2584 |
let y = doc.lastAutoTable.finalY + 8;
|
| 2585 |
|
|
|
|
| 2586 |
const chartImg = els.likeCanvas.toDataURL('image/png');
|
| 2587 |
doc.setFontSize(11);
|
| 2588 |
doc.text('Daily & Cumulative Likes Graph:', 14, y);
|
| 2589 |
doc.addImage(chartImg, 'PNG', 14, y + 3, 182, 62);
|
| 2590 |
y += 72;
|
| 2591 |
|
|
|
|
| 2592 |
const topDays = [...series].sort((a, b) => b.total - a.total).slice(0, 35);
|
| 2593 |
doc.autoTable({
|
| 2594 |
startY: y,
|
|
|
|
| 2691 |
els.fetchBtn.addEventListener('click', executeQuery);
|
| 2692 |
els.targetInput.addEventListener('keypress', e => { if (e.key === 'Enter') executeQuery(); });
|
| 2693 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2694 |
document.querySelectorAll('[data-mode]').forEach(btn => {
|
| 2695 |
btn.addEventListener('click', () => {
|
| 2696 |
state.chartMode = btn.dataset.mode;
|