ChatGPT模型调用计数器

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

目前為 2024-08-13 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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(); // 开始定时保存
})();