champ-chatbot / static /components /carbon-tracker-component.js
qyle's picture
deployment for load testing
e82f783 verified
// 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';
},
};