ChatGPT 一周用量分析报告
分析时间段: ${report.dailyData[0].date} 至 ${report.dailyData[6].date}
生成时间: ${new Date().toLocaleString('zh-CN')}
总请求数
${report.totalRequests}
最近7天
日均使用
${report.averageDaily}
活跃天数平均
使用高峰日
${report.peakDay || 'N/A'}
活跃模型数
${Object.keys(report.modelBreakdown).length}
有使用记录
每日使用趋势
模型使用分布
详细数据表
日期 |
星期 |
总请求数 |
${Object.keys(report.modelBreakdown).map(model => `${model} | `).join('')}
${report.dailyData.map((day, index) => `
${day.date} ${index === 6 ? '(今天)' : ''} |
${day.dayOfWeek} |
${day.total} |
${Object.keys(report.modelBreakdown).map(model =>
`${day.models[model] || 0} | `
).join('')}
`).join('')}
总计 |
${report.totalRequests} |
${Object.entries(report.modelBreakdown).map(([model, count]) =>
`${count} | `
).join('')}
`;
// 下载HTML文件
const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `chatgpt-weekly-analysis-${new Date().toISOString().split('T')[0]}.html`;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast("一周用量分析报告已导出", "success");
}, 100);
}
// Component Functions
function createModelRow(model, modelKey, isSettings = false) {
const row = document.createElement("div");
row.className = "model-row";
if (isSettings) {
return createSettingsModelRow(model, modelKey, row);
}
return createUsageModelRow(model, modelKey, row);
}
function createSettingsModelRow(model, modelKey, row) {
// Model ID cell
const keyLabel = document.createElement("div");
keyLabel.textContent = modelKey;
row.appendChild(keyLabel);
// Quota input cell
const quotaInput = document.createElement("input");
quotaInput.type = "number";
quotaInput.value = model.quota;
quotaInput.placeholder = "配额";
quotaInput.dataset.modelKey = modelKey;
quotaInput.dataset.field = "quota";
row.appendChild(quotaInput);
// Window Type Select
const windowSelect = document.createElement("select");
windowSelect.dataset.modelKey = modelKey;
windowSelect.dataset.field = "windowType";
const hour3Option = document.createElement("option");
hour3Option.value = "hour3";
hour3Option.textContent = "3小时窗口";
const dailyOption = document.createElement("option");
dailyOption.value = "daily";
dailyOption.textContent = "24小时窗口";
const weeklyOption = document.createElement("option");
weeklyOption.value = "weekly";
weeklyOption.textContent = "7天窗口";
const monthlyOption = document.createElement("option");
monthlyOption.value = "monthly";
monthlyOption.textContent = "30天窗口";
windowSelect.appendChild(hour3Option);
windowSelect.appendChild(dailyOption);
windowSelect.appendChild(weeklyOption);
windowSelect.appendChild(monthlyOption);
// Set the current value
windowSelect.value = model.windowType || "daily";
const controlsContainer = document.createElement("div");
controlsContainer.style.display = "flex";
controlsContainer.style.alignItems = "center";
controlsContainer.style.gap = "4px";
controlsContainer.appendChild(windowSelect);
// Delete button
const delBtn = document.createElement("button");
delBtn.className = "btn delete-btn";
delBtn.textContent = "删除";
delBtn.dataset.modelKey = modelKey;
delBtn.addEventListener("click", () => handleDeleteModel(modelKey));
controlsContainer.appendChild(delBtn);
row.appendChild(controlsContainer);
return row;
}
function createUsageModelRow(model, modelKey) {
const now = Date.now();
// Filter requests to only include those within the time window
const windowDuration = TIME_WINDOWS[model.windowType];
const activeRequests = model.requests.filter(req =>
now - req.timestamp < windowDuration
);
const count = activeRequests.length;
let lastRequestTime = count > 0 ?
formatTimeAgo(Math.max(...activeRequests.map(req => req.timestamp))) :
"never";
// Calculate time until oldest request expires (window end time)
let windowEndInfo = "";
if (count > 0 && usageData.showWindowResetTime) {
const oldestActiveTimestamp = Math.min(...activeRequests.map(req => req.timestamp));
const windowEnd = getWindowEnd(oldestActiveTimestamp, model.windowType);
if (windowEnd > now) {
windowEndInfo = `Window resets in: ${formatTimeLeft(windowEnd)}`;
}
}
const row = document.createElement("div");
row.className = "model-row";
// Model Name cell with window type badge
const modelNameContainer = document.createElement("div");
modelNameContainer.style.display = "flex";
modelNameContainer.style.alignItems = "center";
const modelName = document.createElement("span");
modelName.textContent = modelKey;
modelNameContainer.appendChild(modelName);
// Add window type badge
const windowBadge = document.createElement("span");
windowBadge.className = `window-badge ${model.windowType}`;
// Display badge based on window type
if (model.windowType === "hour3") {
windowBadge.textContent = "3h";
} else if (model.windowType === "daily") {
windowBadge.textContent = "24h";
} else if (model.windowType === "weekly") {
windowBadge.textContent = "7d";
} else {
windowBadge.textContent = "30d";
}
windowBadge.title = `${model.windowType === "hour3" ? "3 hour" :
model.windowType === "daily" ? "24 hour" :
model.windowType === "weekly" ? "7 day" : "30 day"} sliding window`;
modelNameContainer.appendChild(windowBadge);
row.appendChild(modelNameContainer);
// Last Request Time cell
const lastUpdateValue = document.createElement("div");
lastUpdateValue.className = "request-time";
lastUpdateValue.textContent = lastRequestTime;
row.appendChild(lastUpdateValue);
// Usage cell
const usageValue = document.createElement("div");
// 处理不同的配额显示
let quotaDisplay;
if (model.quota === 0) {
// 检查当前套餐类型
const currentPlan = usageData.planType || "team";
if (currentPlan === "pro") {
quotaDisplay = "∞"; // Pro套餐无限制
} else {
quotaDisplay = "不可用"; // 其他套餐中0配额表示不可用
}
} else {
quotaDisplay = model.quota;
}
// If model is gpt-4o, show numeric quota (实测修正:100次/3小时)
if (modelKey === "gpt-4o") {
usageValue.innerHTML = `${count} / ${quotaDisplay}`;
} else {
usageValue.textContent = `${count} / ${quotaDisplay}`;
}
// 根据设置决定是否显示窗口刷新时间
if (windowEndInfo && usageData.showWindowResetTime) {
const windowInfoEl = document.createElement("div");
windowInfoEl.className = "window-info";
windowInfoEl.textContent = windowEndInfo;
usageValue.appendChild(windowInfoEl);
}
row.appendChild(usageValue);
// Progress Bar cell
const progressCell = document.createElement("div");
// 处理进度条显示
if (model.quota === 0) {
const currentPlan = usageData.planType || "team";
if (currentPlan === "pro") {
progressCell.textContent = "无限制";
progressCell.style.color = COLORS.success;
progressCell.style.fontStyle = "italic";
} else {
progressCell.textContent = "不可用";
progressCell.style.color = COLORS.disabled;
progressCell.style.fontStyle = "italic";
}
} else {
// For models with quota > 0
const usagePercent = count / model.quota;
if (usageData.progressType === "dots") {
// Dot-based progress implementation
const dotContainer = document.createElement("div");
dotContainer.className = "dot-progress";
const totalDots = 8;
for (let i = 0; i < totalDots; i++) {
const dot = document.createElement("div");
dot.className = "dot";
const dotThreshold = (i + 1) / totalDots;
if (usagePercent >= 1) {
dot.classList.add("dot-exceeded");
} else if (usagePercent >= dotThreshold) {
dot.classList.add("dot-full");
} else if (usagePercent >= dotThreshold - 0.1) {
dot.classList.add("dot-partial");
} else {
dot.classList.add("dot-empty");
}
dotContainer.appendChild(dot);
}
progressCell.appendChild(dotContainer);
} else {
// Enhanced progress bar implementation
const progressContainer = document.createElement("div");
progressContainer.className = "progress-container";
const progressBar = document.createElement("div");
progressBar.className = "progress-bar";
if (usagePercent > 1) {
progressBar.classList.add("exceeded");
} else if (usagePercent < 0.3) {
progressBar.classList.add("low-usage");
}
progressBar.style.width = `${Math.min(usagePercent * 100, 100)}%`;
progressContainer.appendChild(progressBar);
progressCell.appendChild(progressContainer);
}
}
row.appendChild(progressCell);
return row;
}
// 创建特殊模型gpt-4-1-mini的行
function createSpecialModelRow() {
const row = document.createElement("div");
row.className = "model-row special-model-row";
// Model Name cell
const modelNameContainer = document.createElement("div");
const modelName = document.createElement("span");
modelName.className = "special-model-name";
modelName.textContent = SPECIAL_MODEL;
modelNameContainer.appendChild(modelName);
row.appendChild(modelNameContainer);
// Last Request Cell (空的)
const lastUpdateValue = document.createElement("div");
lastUpdateValue.textContent = "-";
lastUpdateValue.className = "request-time";
row.appendChild(lastUpdateValue);
// Usage cell (只显示总使用次数)
const usageValue = document.createElement("div");
usageValue.textContent = `${usageData.miniCount} 次`;
row.appendChild(usageValue);
// Progress Cell (空的)
const progressCell = document.createElement("div");
progressCell.textContent = "无限制";
progressCell.style.color = COLORS.disabled;
progressCell.style.fontStyle = "italic";
row.appendChild(progressCell);
return row;
}
// 创建DeepResearch模型行
function createDeepResearchModelRow() {
const row = document.createElement("div");
row.className = "model-row";
// Model Name cell
const modelNameContainer = document.createElement("div");
const modelName = document.createElement("span");
modelName.textContent = DEEPRESEARCH_MODEL;
modelName.style.color = COLORS.text; // 使用正常模型的颜色,不是特殊模型样式
modelNameContainer.appendChild(modelName);
row.appendChild(modelNameContainer);
// Last Request Cell
const lastUpdateValue = document.createElement("div");
lastUpdateValue.className = "request-time";
lastUpdateValue.textContent = "-";
row.appendChild(lastUpdateValue);
// Usage cell (显示剩余次数)
const usageValue = document.createElement("div");
if (usageData.deepResearch.remaining !== null) {
usageValue.textContent = `剩余: ${usageData.deepResearch.remaining}`;
} else {
usageValue.textContent = "未知";
usageValue.style.color = COLORS.secondaryText;
}
row.appendChild(usageValue);
// Reset Time cell (显示重置时间)
const resetTimeCell = document.createElement("div");
resetTimeCell.style.fontSize = STYLE.textSize.xs;
if (usageData.deepResearch.resetAfter) {
try {
const resetDate = new Date(usageData.deepResearch.resetAfter);
const now = new Date();
const isToday = resetDate.toDateString() === now.toDateString();
if (isToday) {
resetTimeCell.textContent = `今日 ${resetDate.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}`;
} else {
resetTimeCell.textContent = resetDate.toLocaleDateString('zh-CN', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
resetTimeCell.style.color = COLORS.secondaryText;
} catch (e) {
resetTimeCell.textContent = "重置时间未知";
resetTimeCell.style.color = COLORS.disabled;
}
} else {
resetTimeCell.textContent = "重置时间未知";
resetTimeCell.style.color = COLORS.disabled;
}
row.appendChild(resetTimeCell);
return row;
}
// 应用套餐配置
function applyPlanConfig(planType) {
const planConfig = PLAN_CONFIGS[planType];
if (!planConfig) {
console.warn(`[monitor] Unknown plan type: ${planType}`);
return;
}
console.log(`[monitor] Applying ${planConfig.name} plan configuration`);
// 更新所有模型的配额和时间窗口
Object.entries(planConfig.models).forEach(([modelKey, config]) => {
if (usageData.models[modelKey]) {
usageData.models[modelKey].quota = config.quota;
usageData.models[modelKey].windowType = config.windowType;
}
});
// 保存更新后的数据
Storage.set(usageData);
console.log(`[monitor] Successfully applied ${planConfig.name} plan`);
}
// Event Handlers
function handleDeleteModel(modelKey) {
if (confirm(`确定要删除模型 "${modelKey}" 的配置吗?`)) {
delete usageData.models[modelKey];
Storage.set(usageData);
updateUI();
showToast(`模型 "${modelKey}" 已删除。`);
}
}
function animateText(el, config) {
const animator = new TextScrambler(el, {...config});
animator.initialize();
animator.start();
}
// UI Updates
function updateUI() {
const usageContent = document.getElementById("usageContent");
const settingsContent = document.getElementById("settingsContent");
if (usageContent) {
console.debug("[monitor] update usage");
updateUsageContent(usageContent);
animateText(usageContent, { duration: 500, delay: 0, reverse: false, absolute: false, pointerEvents: true });
}
if (settingsContent) {
console.debug("[monitor] update setting");
updateSettingsContent(settingsContent);
animateText(settingsContent, { duration: 500, delay: 0, reverse: false, absolute: false, pointerEvents: true });
}
}
let sortDescending = true;
function updateUsageContent(container) {
container.innerHTML = "";
// Sliding window explanation
const infoSection = document.createElement("div");
infoSection.className = "reset-info";
infoSection.innerHTML = `