ChatGPT模型调用计数器

2024-8-13更新,美化显示各模型总计调用次数和当天各自模型调用次数,可下载一天中不同时间段调用模型次数历史纪录。右键面板实时显示当天调用情况。

当前为 2024-08-13 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         ChatGPT模型调用计数器
// @namespace    http://tampermonkey.net/
// @version      1.9
// @description  2024-8-13更新,美化显示各模型总计调用次数和当天各自模型调用次数,可下载一天中不同时间段调用模型次数历史纪录。右键面板实时显示当天调用情况。
// @author       狐狸的狐狸画
// @match        https://chatgpt.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 定义模型对应的SVG图标
    const modelIcons = {
        'gpt-4o': `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" d="M19.92.897a.447.447 0 0 0-.89-.001c-.12 1.051-.433 1.773-.922 2.262-.49.49-1.21.801-2.262.923a.447.447 0 0 0 0 .888c1.035.117 1.772.43 2.274.922.499.49.817 1.21.91 2.251a.447.447 0 0 0 .89 0c.09-1.024.407-1.76.91-2.263.502-.502 1.238-.82 2.261-.908a.447.447 0 0 0 .001-.891c-1.04-.093-1.76-.411-2.25-.91-.493-.502-.806-1.24-.923-2.273ZM11.993 3.82a1.15 1.15 0 0 0-2.285-.002c-.312 2.704-1.115 4.559-2.373 5.817-1.258 1.258-3.113 2.06-5.817 2.373a.15 1.15 0 0 0 .003 2.285c2.658.3 4.555 1.104 5.845 2.37 1.283 1.26 2.1 3.112 2.338 5.789a.15 1.15 0 0 0 2.292-.003c.227-2.631 1.045-4.525 2.336-5.817 1.292-1.291 3.186-2.109 5.817-2.336a1.15 1.15 0 0 0 .003-2.291c-2.677-.238-4.529-1.056-5.789-2.34-1.266-1.29-2.07-3.186-2.37-5.844Z"></path></svg>`,
        'gpt-4': `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" d="M12.001 1.75c.496 0 .913.373.969.866.306 2.705 1.126 4.66 2.44 6 1.31 1.333 3.223 2.17 5.95 2.412a.976.976 0 0 1-.002 1.945c-2.682.232-4.637 1.067-5.977 2.408-1.34 1.34-2.176 3.295-2.408 5.977a.976.976 0 0 1-1.945.002c-.243-2.727-1.08-4.64-2.412-5.95-1.34-1.314-3.295-2.134-6-2.44a.976.976 0 0 1-.002-1.94c2.75-.317 4.665-1.137 5.972-2.444 1.307-1.307 2.127-3.221 2.444-5.972a.976.976 0 0 1 .971-.864Z"></path></svg>`,
        'gpt-4o-mini': `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M12.566 2.11c1.003-1.188 2.93-.252 2.615 1.271L14.227 8h5.697c1.276 0 1.97 1.492 1.146 2.467L11.434 21.89c-1.003 1.19-2.93.253-2.615-1.27L9.772 16H4.076c-1.276 0-1.97-1.492-1.147-2.467L12.565 2.11Z" clip-rule="evenodd"></path></svg>`,
    };

    // 拦截fetch请求以监控模型调用
    const originalFetch = window.fetch;
    window.fetch = function(url, options) {
        // 检查请求是否是对话请求并解析其请求体
        if (url.includes("/backend-api/conversation") && options && options.method === "POST") {
            try {
                const body = JSON.parse(options.body);
                if (body.model) {
                    updateModelCount(body.model); // 更新模型调用计数
                }
            } catch (e) {
                console.error('解析请求体失败:', e);
            }
        }
        return originalFetch.apply(this, arguments);
    };

    // 更新模型调用计数
    function updateModelCount(model) {
        console.log(`模型调用: ${model}`); // 打印模型名称
        const modelCountKey = 'model_counts'; // 本地存储键
        const counts = JSON.parse(localStorage.getItem(modelCountKey) || '{}');
        counts[model] = (counts[model] || 0) + 1; // 更新计数
        localStorage.setItem(modelCountKey, JSON.stringify(counts));
        updateHourlyCounts(model); // 更新每小时计数
        displayCounts(); // 更新显示
    }

    // 更新每小时计数
    function updateHourlyCounts(model) {
        const date = new Date();
        const hours = date.getHours();
        const dayKey = date.toISOString().split('T')[0]; // YYYY-MM-DD 格式
        const hourlyCountKey = `hourly_counts_${dayKey}`;
        const hourlyCounts = JSON.parse(localStorage.getItem(hourlyCountKey) || '{}');

        if (!hourlyCounts[hours]) {
            hourlyCounts[hours] = {};
        }

        hourlyCounts[hours][model] = (hourlyCounts[hours][model] || 0) + 1;
        localStorage.setItem(hourlyCountKey, JSON.stringify(hourlyCounts));
    }

    // 获取今天每个模型的调用次数
    function getTodayCounts() {
        const date = new Date().toISOString().split('T')[0]; // YYYY-MM-DD 格式
        const hourlyCountKey = `hourly_counts_${date}`;
        const hourlyCounts = JSON.parse(localStorage.getItem(hourlyCountKey) || '{}');

        const todayCounts = {};
        for (let hour in hourlyCounts) {
            for (let model in hourlyCounts[hour]) {
                todayCounts[model] = (todayCounts[model] || 0) + hourlyCounts[hour][model];
            }
        }
        return todayCounts;
    }

    // 保存数据到文件
    function saveDataToFile() {
        const date = new Date().toISOString().split('T')[0]; // YYYY-MM-DD 格式
        const modelCountKey = 'model_counts';
        const hourlyCountKey = `hourly_counts_${date}`;

        const totalCounts = JSON.parse(localStorage.getItem(modelCountKey) || '{}');
        const hourlyCounts = JSON.parse(localStorage.getItem(hourlyCountKey) || '{}');
        const todayCounts = getTodayCounts();

        // 准备保存的数据
        const data = {
            total: totalCounts, // 总调用次数
            hourly: hourlyCounts, // 每小时调用次数
            today: todayCounts // 当天每个模型的调用次数
        };

        // 创建并下载JSON文件
        const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');
        a.href = url;
        a.download = `model_usage_${date}.json`; // 文件命名
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);

        URL.revokeObjectURL(url);
    }

    // 定时每小时保存数据
    function scheduleHourlySave() {
        const now = new Date();
        const delay = (60 - now.getMinutes()) * 60 * 1000; // 距离下一个小时的延迟时间

        setTimeout(() => {
            saveDataToFile();
            setInterval(saveDataToFile, 60 * 60 * 1000); // 每小时保存一次
        }, delay);
    }

    // 显示计数
    function displayCounts() {
        const modelCountKey = 'model_counts';
        const counts = JSON.parse(localStorage.getItem(modelCountKey) || '{}');
        let displayText = '';
        for (let model in counts) {
            const icon = modelIcons[model] || '❓'; // 默认图标
            displayText += `<div style="display: flex; align-items: center; margin-right: 10px;">${icon} ${counts[model]}</div>`;
        }

        let displayDiv = document.getElementById('model-count-display');
        if (!displayDiv) {
            displayDiv = document.createElement('div');
            displayDiv.id = 'model-count-display';
            Object.assign(displayDiv.style, {
                display: 'flex',
                position: 'fixed',
                top: '5px',
                left: '450px',
                backgroundColor: 'rgba(0,0,0,0)',
                color: 'rgba(66,66,66,1)',
                padding: '10px',
                borderRadius: '5px',
                zIndex: '1000',
                fontSize: '14px',
                fontWeight: 'bold',
            });

            document.body.appendChild(displayDiv);
        }
        displayDiv.innerHTML = displayText;
    }

    // 清除数据
    function clearData() {
        const date = new Date().toISOString().split('T')[0]; // YYYY-MM-DD 格式
        localStorage.removeItem('model_counts');
        localStorage.removeItem(`hourly_counts_${date}`);
        displayCounts();
    }

    // 显示今天模型调用次数和按钮的面板
    function showTodayCountsPanel(x, y) {
        const todayCounts = getTodayCounts();
        let panel = document.getElementById('today-counts-panel');
        if (!panel) {
            panel = document.createElement('div');
            panel.id = 'today-counts-panel';
            Object.assign(panel.style, {
                position: 'absolute',
                top: `${y}px`,
                left: `${x}px`,
                backgroundColor: 'rgba(255, 255, 255, 0.9)',
                color: 'black',
                padding: '10px',
                border: '1px solid #ccc',
                borderRadius: '5px',
                zIndex: '1001',
                fontSize: '14px',
                fontWeight: 'bold',
                maxHeight: '400px',
                overflowY: 'auto',
                width: '220px', // 固定宽度
                boxShadow: '0px 4px 8px rgba(0,0,0,0.1)', // 阴影效果
            });

            document.body.appendChild(panel);
        }

        let panelContent = '<strong>今天各模型调用次数:</strong><br>';
        for (let model in todayCounts) {
            const icon = modelIcons[model] || '❓';
            panelContent += `<div style="display: flex; align-items: center; margin-bottom: 5px;">${icon} ${model}: ${todayCounts[model]}</div>`;
        }

        // 添加按钮到面板,垂直排列,并添加hover效果
        panelContent += '<hr style="margin: 10px 0;">';
        panelContent += '<div style="display: flex; flex-direction: column; align-items: center;">';
        panelContent += '<button id="clear-data-button" style="margin: 5px; padding: 8px 16px; width: 100%; border: none; border-radius: 5px; background-color: #f0f0f0; cursor: pointer;">清除数据</button>';
        panelContent += '<button id="download-data-button" style="margin: 5px; padding: 8px 16px; width: 100%; border: none; border-radius: 5px; background-color: #f0f0f0; cursor: pointer;">下载数据</button>';
        panelContent += '</div>';
        panel.innerHTML = panelContent;

        // 绑定按钮事件
        document.getElementById('clear-data-button').onclick = () => {
            clearData();
            panel.style.display = 'none';
        };

        document.getElementById('download-data-button').onclick = () => {
            saveDataToFile();
            panel.style.display = 'none';
        };

        // 添加hover效果
        const buttons = panel.querySelectorAll('button');
        buttons.forEach(button => {
            button.addEventListener('mouseover', () => {
                button.style.backgroundColor = '#e0e0e0';
            });
            button.addEventListener('mouseout', () => {
                button.style.backgroundColor = '#f0f0f0';
            });
        });

        panel.style.display = 'block';
    }

    // 右键点击事件
    window.addEventListener('contextmenu', (e) => {
        // 只在显示面板上显示菜单
        const displayDiv = document.getElementById('model-count-display');
        if (displayDiv && displayDiv.contains(e.target)) {
            e.preventDefault();
            showTodayCountsPanel(e.clientX, e.clientY); // 显示今天模型调用次数和按钮的面板
        } else {
            const panel = document.getElementById('today-counts-panel');
            if (panel) {
                panel.style.display = 'none';
            }
        }
    });

    // 点击其他地方隐藏面板
    window.addEventListener('click', (e) => {
        const panel = document.getElementById('today-counts-panel');
        if (panel && !panel.contains(e.target)) {
            panel.style.display = 'none';
        }
    });

    displayCounts(); // 初始化显示计数
    scheduleHourlySave(); // 开始定时保存
})();