Spaces:
Paused
Paused
| // components/carbon-tracker.js - Track carbon emissions per model | |
| import { StateManager } from '../services/state-manager.js'; | |
| import { TranslationService } from '../services/translation-service.js'; | |
| export const CarbonTracker = { | |
| elements: { | |
| toolbarEmissions: null, | |
| moreInfoBtn: null, | |
| emissionsModal: null, | |
| okModalBtn: null, | |
| closeModalBtn: null, | |
| emissionsTableBody: null, | |
| totalEmissionsCell: null, | |
| totalTokensCell: null, | |
| totalRepliesCell: null, | |
| }, | |
| // Model display names | |
| modelNames: { | |
| "champ": "CHAMP_v1", | |
| "qwen": "CHAMP_v2", | |
| "openai": "GPT-5.2", | |
| "google-conservative": translations[StateManager.currentLang]["gemini_conservative"], | |
| "google-creative": translations[StateManager.currentLang]["gemini_creative"], | |
| }, | |
| /** | |
| * Initialize the carbon tracker | |
| */ | |
| init() { | |
| this.elements.toolbarEmissions = document.getElementById('toolbar-emissions'); | |
| this.elements.moreInfoBtn = document.getElementById('emissions-more-info'); | |
| this.elements.emissionsModal = document.getElementById('emissions-modal'); | |
| this.elements.okModalBtn = document.getElementById('ok-emissions-btn'); | |
| this.elements.closeModalBtn = document.getElementById('close-emissions-btn'); | |
| this.elements.emissionsTableBody = document.getElementById('emissions-table-body'); | |
| this.elements.totalEmissionsCell = document.getElementById('totalEmissions'); | |
| this.elements.totalTokensCell = document.getElementById('totalTokens'); | |
| this.elements.totalRepliesCell = document.getElementById('totalReplies'); | |
| this.attachEventListeners(); | |
| }, | |
| attachEventListeners() { | |
| this.elements.moreInfoBtn.addEventListener('click', () => this.openModal()); | |
| this.elements.okModalBtn.addEventListener('click', () => this.closeModal()); | |
| this.elements.closeModalBtn.addEventListener('click', () => this.closeModal()); | |
| this.elements.emissionsModal.addEventListener('click', (e) => { | |
| if (e.target === this.elements.emissionsModal) { | |
| this.closeModal(); | |
| } | |
| }); | |
| }, | |
| /** | |
| * Format emissions value for display | |
| * @param {number} kgCO2eq - The emissions value in kgCO2eq | |
| * @returns {string} Formatted string with appropriate unit | |
| */ | |
| formatEmissions(kgCO2eq) { | |
| if (kgCO2eq < 0.001) { | |
| return `${(kgCO2eq * 1000000).toFixed(3)} mg`; | |
| } else if (kgCO2eq < 1) { | |
| return `${(kgCO2eq * 1000).toFixed(3)} g`; | |
| } else { | |
| return `${kgCO2eq.toFixed(3)} kg`; | |
| } | |
| }, | |
| /** | |
| * Format number with thousands separator | |
| * @param {number} num - Number to format | |
| * @returns {string} Formatted number | |
| */ | |
| formatNumber(num) { | |
| return num.toLocaleString(); | |
| }, | |
| countReplies(messages) { | |
| return messages.filter(msg => | |
| msg.role === 'assistant' && msg.content && msg.content !== 'no_reply' | |
| ).length; | |
| }, | |
| /** | |
| * Count tokens in messages | |
| * @param {Array} messages - Array of message objects | |
| * @returns {number} Total token count | |
| */ | |
| countTokens(messages) { | |
| return messages.reduce((total, msg) => { | |
| return total + (msg.nTokens || 0); | |
| }, 0); | |
| }, | |
| /** | |
| * Update the toolbar emissions display | |
| */ | |
| updateEmissions() { | |
| const totalEmissions = StateManager.getAllEmissions(); | |
| this.elements.toolbarEmissions.innerHTML = this.formatEmissions(totalEmissions); | |
| }, | |
| /** | |
| * Populate the emissions table | |
| */ | |
| populateTable() { | |
| this.elements.emissionsTableBody.innerHTML = ''; | |
| let totalEmissions = 0; | |
| let totalTokens = 0; | |
| let totalReplies = 0; | |
| // Populate each model row | |
| Object.keys(StateManager.modelChats).forEach(modelType => { | |
| const chat = StateManager.modelChats[modelType]; | |
| const emissions = StateManager.getTotalEmissions(modelType); | |
| const tokens = this.countTokens(chat.messages); | |
| const replies = this.countReplies(chat.messages); | |
| const emissionsPerToken = tokens > 0 ? emissions / tokens : 0; | |
| totalEmissions += emissions; | |
| totalTokens += tokens; | |
| totalReplies += replies; | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td>${this.modelNames[modelType] || modelType}</td> | |
| <td>${this.formatEmissions(emissions)}</td> | |
| <td>${this.formatNumber(tokens)}</td> | |
| <td>${this.formatNumber(replies)}</td> | |
| <td>${emissionsPerToken > 0 ? this.formatEmissions(emissionsPerToken) : '—'}</td> | |
| `; | |
| this.elements.emissionsTableBody.appendChild(row); | |
| }); | |
| // Update totals | |
| this.elements.totalEmissionsCell.textContent = this.formatEmissions(totalEmissions); | |
| this.elements.totalTokensCell.textContent = this.formatNumber(totalTokens); | |
| this.elements.totalRepliesCell.textContent = this.formatNumber(totalReplies); | |
| // Update equivalents | |
| this.updateEquivalents(totalEmissions); | |
| }, | |
| /** | |
| * Update the equivalent statistics | |
| * @param {number} kgCO2 - Total emissions in kgCO2eq | |
| */ | |
| updateEquivalents(kgCO2) { | |
| // Conversion factors (approximate) | |
| const CAR_KM_PER_KG = 1 / 0.2; // 0.2 kgCO2/km | |
| const BEEF_MEALS_PER_KG = 1 / 7; // 7 kgCO2/100g | |
| const CARROT_MEALS_PER_KG = 1 / 0.04 // 0.04 kgCO2 / 100g | |
| const SMS_PER_KG = 1 / (0.014 / 1000) ; // 0.014 gCO2/SMS | |
| const SPAM_PER_KG = 1 / (0.03 / 1000) ; // 0.03 gCO2/spam | |
| const carKm = kgCO2 * CAR_KM_PER_KG; | |
| const beefMeals = kgCO2 * BEEF_MEALS_PER_KG; | |
| const carrotMeals = kgCO2 * CARROT_MEALS_PER_KG; | |
| const sms = kgCO2 * SMS_PER_KG; | |
| const spam = kgCO2 * SPAM_PER_KG; | |
| document.getElementById('carKm').textContent = carKm.toFixed(1); | |
| document.getElementById('beefMeals').textContent = beefMeals.toFixed(1); | |
| document.getElementById('carrot').textContent = carrotMeals.toFixed(1); | |
| document.getElementById('spam').textContent = spam.toFixed(1); | |
| document.getElementById('sms').textContent = sms.toFixed(1); | |
| }, | |
| /** | |
| * Open the emissions modal | |
| */ | |
| openModal() { | |
| this.populateTable(); | |
| this.elements.emissionsModal.style.display = ''; | |
| TranslationService.applyTranslation(); | |
| }, | |
| /** | |
| * Close the emissions modal | |
| */ | |
| closeModal() { | |
| this.elements.emissionsModal.style.display = 'none'; | |
| }, | |
| }; |