class CopilotAction { constructor($UD, context) { this.$UD = $UD; this.context = context; this.config = { url: '', refreshRate: '4', }; this.refreshTimer = null; this.debounceTimer = 0; } setActive() {} onClear() { if (this.refreshTimer) { clearInterval(this.refreshTimer); this.refreshTimer = null; } if (this.debounceTimer) { clearTimeout(this.debounceTimer); this.debounceTimer = 0; } } setParams(jsn) { this.config = Object.assign(this.config, (jsn && jsn.param) || {}); this.start(); } onRun() { this.fetchData(); } start() { this.onClear(); this.fetchData(); const duration = this.getRefreshDuration(); if (duration > 0) { this.refreshTimer = setInterval(() => this.fetchData(), duration * 60 * 1000); } } async fetchData() { if (this.debounceTimer) clearTimeout(this.debounceTimer); this.debounceTimer = setTimeout(async () => { if (!this.config.url) { this.renderGauge(null); return; } try { const response = await fetch(this.config.url); const text = await response.text(); const value = this.parsePrometheus(text); this.renderGauge(value !== null ? parseFloat(value) : null); } catch (e) { this.renderGauge(null); } }, 300); } parsePrometheus(text) { for (const line of text.split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; if (trimmed.startsWith('github_copilot_usage_percentage')) { const closingBrace = trimmed.indexOf('}'); if (closingBrace !== -1) return trimmed.slice(closingBrace + 1).trim(); // no labels const parts = trimmed.split(/\s+/); if (parts.length >= 2) return parts[1]; } } return null; } renderGauge(value) { const canvas = document.createElement('canvas'); canvas.width = 196; canvas.height = 196; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, 196, 196); const cx = 98, cy = 100; const r = 68; const lineWidth = 14; // Gauge runs from 225° to 315° clockwise (270° sweep) const startAngle = 135 * Math.PI / 180; const totalSweep = 270 * Math.PI / 180; const greenEnd = startAngle + 0.50 * totalSweep; // 50% const yellowEnd = startAngle + 0.80 * totalSweep; // 80% const endAngle = startAngle + totalSweep; // 100% // Background track ctx.beginPath(); ctx.arc(cx, cy, r, startAngle, endAngle, false); ctx.strokeStyle = '#333333'; ctx.lineWidth = lineWidth; ctx.lineCap = 'round'; ctx.stroke(); if (value !== null) { const pct = Math.max(0, Math.min(100, value)) / 100; const valueAngle = startAngle + pct * totalSweep; // Green segment (0–50%) if (pct > 0) { ctx.beginPath(); ctx.arc(cx, cy, r, startAngle, Math.min(valueAngle, greenEnd), false); ctx.strokeStyle = '#4caf50'; ctx.lineWidth = lineWidth; ctx.lineCap = 'round'; ctx.stroke(); } // Yellow segment (50–80%) if (pct > 0.5) { ctx.beginPath(); ctx.arc(cx, cy, r, greenEnd, Math.min(valueAngle, yellowEnd), false); ctx.strokeStyle = '#ffeb3b'; ctx.lineWidth = lineWidth; ctx.lineCap = 'butt'; ctx.stroke(); } // Red segment (80–100%) if (pct > 0.8) { ctx.beginPath(); ctx.arc(cx, cy, r, yellowEnd, valueAngle, false); ctx.strokeStyle = '#f44336'; ctx.lineWidth = lineWidth; ctx.lineCap = 'butt'; ctx.stroke(); } // Value text — centered, colored const color = pct <= 0.5 ? '#4caf50' : pct <= 0.8 ? '#ffeb3b' : '#f44336'; const displayText = value.toFixed(1) + '%'; const fSize = displayText.length > 6 ? 28 : 34; ctx.fillStyle = color; ctx.font = `bold ${fSize}px "Source Han Sans SC"`; ctx.textBaseline = 'middle'; ctx.textAlign = 'center'; ctx.fillText(displayText, cx, cy + 8); } else { ctx.fillStyle = '#666666'; ctx.font = 'bold 28px "Source Han Sans SC"'; ctx.textBaseline = 'middle'; ctx.textAlign = 'center'; ctx.fillText('N/A', cx, cy + 8); } // Label — bottom ctx.fillStyle = '#7ec8e3'; ctx.font = 'bold 20px "Source Han Sans SC"'; ctx.fillText('Copilot', cx, 165); this.$UD.setBaseDataIcon(this.context, canvas.toDataURL('image/png')); } getRefreshDuration() { switch (this.config.refreshRate) { case '1': return 1; case '2': return 2; case '3': return 5; case '4': return 10; case '5': return 30; case '6': return 60; default: return 0; } } }