分享一个悬浮显示claude用量的油猴插件

参考两位大神的插件,让claude做了些修改: f14XuanLv/Claude-Powerest-Manager_Enhancer: An all-in-one Tampermonkey script to supercharge your Claude.ai experience with a powerful conversation manager, advanced export, and unique chat enhancements. Claude Floating Control Panel

1、在原本悬浮插件的基础上,增加了收起状态下也能显示用量和手动刷新
2、对面板UI做了汉化
3、参考大神整体插件,提取了中文重命名逻辑,增加成独立按钮

// ==UserScript==
// @name         Claude Floating Control Panel
// @name:en         Claude Floating Control Panel
// @namespace    usage-and-quick-settings-of-claude
// @author       Yalums, f14xuanlv
// @version      2.2
// @description 浮动面板显示Claude的Usage和功能开关,支持智能中文重命名
// @description:en  Floating panel to display Claude usage and feature toggles with conversation rename
// @match        https://claude.ai/*
// @grant        none
// @run-at       document-idle
// @license      GNU General Public License v3.0
// ==/UserScript==

(function() {
    'use strict';

    const FEATURES = [
        { key: 'enabled_monkeys_in_a_barrel', name: '代码执行', desc: '虚拟代码环境(支持压缩包)', exclusive: 'enabled_artifacts_attachments' },
        { key: 'enabled_artifacts_attachments', name: 'Analysis tool', desc: 'Artifacts的附加功能(不建议)', exclusive: 'enabled_monkeys_in_a_barrel' },
        { key: 'enabled_saffron', name: '记忆功能', desc: '跨窗口记忆' },
        { key: 'enabled_saffron_search', name: '搜索对话', desc: '对话搜索功能' },
        { key: 'enabled_sourdough', name: '项目功能', desc: '项目记忆' },
    ];

    let panelState = {
        isExpanded: localStorage.getItem('claudePanel_expanded') !== 'false',
        position: JSON.parse(localStorage.getItem('claudePanel_position') || '{"right":"20px","bottom":"20px"}'),
        autoRename: localStorage.getItem('claudePanel_autoRename') === 'true'
    };

    async function getUserSettings() {
        try {
            const response = await fetch('/api/account', {
                credentials: 'include'
            });
            const data = await response.json();
            return data.settings;
        } catch (err) {
            console.error('[Claude Panel] Failed to fetch settings:', err);
            return null;
        }
    }

    async function toggleFeature(key, currentValue, exclusiveKey = null) {
        try {
            const body = { [key]: !currentValue };

            if (exclusiveKey && !currentValue) {
                body[exclusiveKey] = false;
            }

            const response = await fetch('/api/account/settings', {
                method: 'PATCH',
                headers: {
                    'Content-Type': 'application/json'
                },
                credentials: 'include',
                body: JSON.stringify(body)
            });

            if (!response.ok) {
                console.error('[Claude Panel] HTTP error:', response.status);
                return { success: false };
            }

            const result = await response.json();
            return { success: true, data: result };
        } catch (err) {
            console.error('[Claude Panel] Toggle failed:', err);
            return { success: false };
        }
    }

    async function getUsageData() {
        try {
            const orgsResponse = await fetch('/api/organizations', {
                credentials: 'include'
            });
            const orgs = await orgsResponse.json();
            const orgId = orgs[0]?.uuid;
            if (!orgId) return null;

            const usageResponse = await fetch(`/api/organizations/${orgId}/usage`, {
                credentials: 'include'
            });
            return await usageResponse.json();
        } catch (err) {
            console.error('[Claude Panel] Failed to fetch usage:', err);
            return null;
        }
    }

    // ========== 重命名功能相关函数 ==========
    
    async function getOrgId() {
        try {
            const r = await fetch('/api/organizations');
            const orgs = await r.json();
            return orgs[0]?.uuid || null;
        } catch (err) {
            console.error('[Claude Panel] 获取组织ID失败:', err);
            return null;
        }
    }

    function getCurrentConversationUuid() {
        const match = window.location.pathname.match(/\/chat\/([a-f0-9-]+)/);
        return match ? match[1] : null;
    }

    async function getConversation(orgId, uuid) {
        try {
            const r = await fetch(`/api/organizations/${orgId}/chat_conversations/${uuid}`);
            return await r.json();
        } catch (err) {
            console.error('[Claude Panel] 获取对话失败:', err);
            return null;
        }
    }

    async function createTempConversation(orgId) {
        try {
            const r = await fetch(`/api/organizations/${orgId}/chat_conversations`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ uuid: crypto.randomUUID(), name: '' })
            });
            return await r.json();
        } catch (err) {
            console.error('[Claude Panel] 创建临时对话失败:', err);
            return null;
        }
    }

    async function generateTitle(orgId, tempUuid, messageContent) {
        try {
            const r = await fetch(`/api/organizations/${orgId}/chat_conversations/${tempUuid}/title`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ message_content: messageContent, recent_titles: [] })
            });
            if (!r.ok) {
                console.error('[Claude Panel] 生成标题失败, HTTP状态:', r.status);
                return null;
            }
            const data = await r.json();
            const title = data?.title?.trim();
            if (!title || title.toLowerCase().includes('untitled')) {
                console.error('[Claude Panel] 生成的标题无效:', title);
                return null;
            }
            return title;
        } catch (err) {
            console.error('[Claude Panel] 生成标题失败:', err);
            return null;
        }
    }

    async function updateConversationName(orgId, uuid, newName) {
        try {
            const r = await fetch(`/api/organizations/${orgId}/chat_conversations/${uuid}`, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ name: newName })
            });
            return r.ok;
        } catch (err) {
            console.error('[Claude Panel] 更新对话名称失败:', err);
            return false;
        }
    }

    async function deleteTempConversation(orgId, uuid) {
        try {
            await fetch(`/api/organizations/${orgId}/chat_conversations/${uuid}`, {
                method: 'DELETE'
            });
        } catch (err) {
            console.error('[Claude Panel] 删除临时对话失败:', err);
        }
    }

    function chineseRatio(s) {
        const onlyText = s.replace(/\s+/g, '');
        if (!onlyText) return 0;
        const cn = (onlyText.match(/[\u4E00-\u9FFF]/g) || []).length;
        return cn / onlyText.length;
    }

    function isAsciiOrUntitled(name) {
        if (!name || name.trim() === '') return true;
        const trimmed = name.trim();
        if (/^New Chat( \d+)?$/i.test(trimmed)) return true;
        return /^[\x00-\x7F]+$/.test(trimmed);
    }

    // 检测标题是否为英文(包含大量英文字母)
    function isEnglishTitle(name) {
        if (!name || name.trim() === '') return true;
        const trimmed = name.trim();
        
        // 如果是默认标题,视为英文
        if (/^New Chat( \d+)?$/i.test(trimmed)) return true;
        
        // 如果包含中文字符,不是英文标题
        if (/[\u4E00-\u9FFF]/.test(trimmed)) return false;
        
        // 统计英文字母比例
        const letters = trimmed.match(/[a-zA-Z]/g) || [];
        const totalChars = trimmed.replace(/\s+/g, '').length;
        
        // 如果英文字母占比超过50%,视为英文标题
        return totalChars > 0 && (letters.length / totalChars) > 0.5;
    }

    function hasBeenRenamed(uuid) {
        const key = `claude_renamed_${uuid}`;
        return localStorage.getItem(key) === 'true';
    }

    function markAsRenamed(uuid) {
        const key = `claude_renamed_${uuid}`;
        localStorage.setItem(key, 'true');
    }

    async function renameCurrentConversation() {
        const uuid = getCurrentConversationUuid();
        if (!uuid) {
            console.log('[Claude Panel] 未找到当前对话UUID');
            return { success: false, message: '未找到当前对话' };
        }

        if (hasBeenRenamed(uuid)) {
            console.log('[Claude Panel] 对话已重命名过,跳过');
            return { success: false, message: '该对话已重命名过' };
        }

        const orgId = await getOrgId();
        if (!orgId) {
            return { success: false, message: '获取组织ID失败' };
        }

        const conv = await getConversation(orgId, uuid);
        if (!conv) {
            return { success: false, message: '获取对话信息失败' };
        }

        const currentName = conv.name || '';
        // 如果标题不是英文(已经是中文或其他语言),跳过重命名
        if (!isEnglishTitle(currentName)) {
            console.log('[Claude Panel] 对话名称已非英文,跳过');
            markAsRenamed(uuid);
            return { success: false, message: '对话名称已非英文' };
        }

        const messages = conv.chat_messages || [];
        if (messages.length < 2) {
            console.log('[Claude Panel] 对话消息不足2条,跳过');
            return { success: false, message: '对话消息不足,无法生成标题' };
        }

        // 检查前2轮对话的中文比例
        let totalText = '';
        for (let i = 0; i < Math.min(4, messages.length); i++) {
            const msg = messages[i];
            if (msg.text) totalText += msg.text;
        }

        if (chineseRatio(totalText) < 0.25) {
            console.log('[Claude Panel] 中文比例不足25%,跳过');
            return { success: false, message: '对话中文内容不足' };
        }

        // 构建用于生成标题的消息内容(前2轮对话)
        const maxRounds = 2;
        const roundsToUse = Math.min(Math.floor(messages.length / 2), maxRounds);
        const messagesToProcess = messages.slice(0, roundsToUse * 2);
        
        let messageParts = [];
        messagesToProcess.forEach((msg, index) => {
            const senderLabel = 'Message ' + (index + 1) + ' (' + (msg.sender === 'human' ? 'User' : 'Assistant') + ')';
            let textContent = '';
            
            // 提取文本内容
            if (Array.isArray(msg.content)) {
                textContent = msg.content
                    .filter(b => b.type === 'text' && b.text)
                    .map(b => b.text)
                    .join('\\n');
            }
            if (!textContent && msg.text) {
                textContent = msg.text;
            }
            
            if (textContent.trim()) {
                messageParts.push(senderLabel + ':\\n\\n' + textContent.trim());
            }
        });

        if (messageParts.length === 0) {
            return { success: false, message: '没有有效的文本内容' };
        }

        // 组装完整的消息内容,添加中文标题生成指令
        const langPrompt = '中文';
        const startInstruction = 'TASK: Generate a title for the following conversation.\\nRULE: The title language must be strictly ' + langPrompt + '.\\n\\n--- Conversation Start ---';
        const endInstruction = '\\n--- Conversation End ---\\nREMINDER: Generate the title in ' + langPrompt + ' now.';
        const finalMessageContent = startInstruction + '\\n\\n' + messageParts.join('\\n\\n') + '\\n' + endInstruction;

        // 创建临时对话用于生成标题
        const tempConv = await createTempConversation(orgId);
        if (!tempConv || !tempConv.uuid) {
            return { success: false, message: '创建临时对话失败' };
        }

        const tempUuid = tempConv.uuid;
        const newTitle = await generateTitle(orgId, tempUuid, finalMessageContent);
        
        // 清理临时对话
        await deleteTempConversation(orgId, tempUuid);

        if (!newTitle) {
            return { success: false, message: '生成标题失败' };
        }

        const success = await updateConversationName(orgId, uuid, newTitle);
        if (success) {
            markAsRenamed(uuid);
            console.log('[Claude Panel] 重命名成功:', newTitle);
            
            // 延迟刷新页面,让用户看到成功提示
            setTimeout(() => {
                console.log('[Claude Panel] 正在刷新页面以更新标题...');
                window.location.reload();
            }, 1000);
            
            return { success: true, message: `已重命名为: ${newTitle},即将刷新页面...` };
        } else {
            return { success: false, message: '更新对话名称失败' };
        }
    }

    function formatResetTime(isoTime) {
        if (!isoTime) return '未知';
        const date = new Date(isoTime);
        const now = new Date();
        const diff = date - now;
        const minutes = Math.floor(diff / 60000);
        const hours = Math.floor(diff / 3600000);
        const days = Math.floor(diff / 86400000);

        if (minutes < 1) return '即将重置';
        if (minutes < 60) return `${minutes}分钟后`;
        if (hours < 24) return `${hours}小时后`;
        return `${days}天后`;
    }

    function injectStyles() {
        const style = document.createElement('style');
        style.textContent = `
            #claude-control-panel {
                position: fixed;
                z-index: 9999;
                background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
                border: 1px solid #404040;
                border-radius: 16px;
                box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
                color: #ffffff;
                max-width: 400px;
                max-height: 90vh;
                overflow: hidden;
                transition: all 0.3s ease;
                will-change: transform;
            }

            #claude-control-panel.dragging {
                transition: none;
            }

            #claude-control-panel.collapsed {
                width: 200px !important;
                height: 44px !important;
                opacity: 0.7;
            }

            #claude-control-panel.collapsed:hover {
                opacity: 1;
            }

            .collapsed-usage {
                display: none;
            }

            .collapsed .collapsed-usage {
                display: flex;
                align-items: center;
                flex: 1;
                min-width: 0;
            }

            .collapsed-usage-value {
                font-size: 14px;
                font-weight: 700;
                color: #4ade80;
                line-height: 1;
                white-space: nowrap;
                min-width: 35px;
            }

            .collapsed-usage-value.warning {
                color: #fbbf24;
            }

            .collapsed-usage-value.danger {
                color: #f87171;
            }

            .collapsed-refresh {
                display: none;
            }

            .collapsed .collapsed-refresh {
                display: flex;
                align-items: center;
            }

            .collapsed-refresh-btn {
                background: rgba(255, 255, 255, 0.1);
                border: none;
                border-radius: 6px;
                width: 28px;
                height: 28px;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                transition: all 0.2s;
                font-size: 13px;
                padding: 0;
                flex-shrink: 0;
            }

            .collapsed-refresh-btn:hover {
                background: rgba(255, 255, 255, 0.2);
            }

            .collapsed-refresh-btn:active {
                transform: scale(0.95);
            }

            .collapsed-refresh-btn.refreshing {
                animation: pulse 0.8s ease-in-out infinite;
            }

            @keyframes pulse {
                0%, 100% { 
                    transform: scale(1);
                    opacity: 1;
                }
                50% { 
                    transform: scale(1.15);
                    opacity: 0.7;
                }
            }
            
            .header-rename-btn {
                background: rgba(255, 255, 255, 0.1);
                border: none;
                border-radius: 6px;
                padding: 4px 10px;
                cursor: pointer;
                display: flex;
                align-items: center;
                gap: 4px;
                transition: all 0.2s;
                font-size: 12px;
                color: #e0e0e0;
                white-space: nowrap;
            }
            
            .header-rename-btn:hover {
                background: rgba(255, 255, 255, 0.2);
            }
            
            .header-rename-btn:disabled {
                opacity: 0.5;
                cursor: not-allowed;
            }

            .panel-header {
                padding: 16px;
                background: rgba(255, 255, 255, 0.05);
                border-bottom: 1px solid #404040;
                display: flex;
                align-items: center;
                justify-content: space-between;
                cursor: move;
                user-select: none;
            }

            .collapsed .panel-header {
                padding: 8px 12px;
                border-bottom: none;
                justify-content: space-between;
                gap: 10px;
            }

            .panel-header h3 {
                margin: 0;
                font-size: 16px;
                font-weight: 600;
                display: flex;
                align-items: center;
                gap: 8px;
            }

            .panel-controls {
                display: flex;
                align-items: center;
                gap: 6px;
                flex-shrink: 0;
            }
            
            .collapsed .panel-controls {
                gap: 4px;
            }

            .panel-btn {
                background: rgba(255, 255, 255, 0.1);
                border: none;
                border-radius: 6px;
                width: 28px;
                height: 28px;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                transition: all 0.2s;
                flex-shrink: 0;
            }

            .panel-btn:hover {
                background: rgba(255, 255, 255, 0.2);
            }

            .panel-content {
                max-height: calc(90vh - 60px);
                overflow-y: auto;
                padding: 16px;
            }

            .collapsed .panel-content {
                display: none;
            }

            .collapsed .panel-header h3 {
                display: none;
            }
            
            .collapsed .header-rename-btn {
                font-size: 11px;
                padding: 4px 8px;
            }
            
            .collapsed .header-rename-btn {
                padding: 4px 8px;
                font-size: 11px;
            }

            .usage-section {
                background: rgba(255, 255, 255, 0.05);
                border-radius: 12px;
                padding: 16px;
                margin-bottom: 16px;
            }

            .usage-item {
                margin-bottom: 16px;
            }

            .usage-item:last-child {
                margin-bottom: 0;
            }

            .usage-label {
                display: flex;
                justify-content: space-between;
                margin-bottom: 8px;
                font-size: 13px;
            }

            .usage-label-text {
                font-weight: 500;
                color: #e0e0e0;
            }

            .usage-label-time {
                color: #909090;
                font-size: 12px;
            }

            .usage-bar {
                height: 8px;
                background: rgba(255, 255, 255, 0.1);
                border-radius: 4px;
                overflow: hidden;
                margin-bottom: 4px;
            }

            .usage-bar-fill {
                height: 100%;
                background: linear-gradient(90deg, #4ade80 0%, #22c55e 100%);
                transition: width 0.3s ease;
            }

            .usage-bar-fill.warning {
                background: linear-gradient(90deg, #fbbf24 0%, #f59e0b 100%);
            }

            .usage-bar-fill.danger {
                background: linear-gradient(90deg, #f87171 0%, #ef4444 100%);
            }

            .usage-percent {
                text-align: right;
                font-size: 12px;
                color: #b0b0b0;
            }

            .features-section {
                background: rgba(255, 255, 255, 0.05);
                border-radius: 12px;
                padding: 16px;
            }

            .section-title {
                font-size: 14px;
                font-weight: 600;
                margin-bottom: 12px;
                color: #e0e0e0;
            }

            .feature-item {
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding: 12px 0;
                border-bottom: 1px solid rgba(255, 255, 255, 0.1);
                gap: 24px;
            }

            .feature-item:last-child {
                border-bottom: none;
            }

            .feature-info {
                flex: 1;
                min-width: 0;
            }

            .feature-name {
                font-size: 13px;
                font-weight: 500;
                color: #e0e0e0;
                margin-bottom: 4px;
            }

            .feature-desc {
                font-size: 11px;
                color: #909090;
                line-height: 1.3;
            }

            .feature-toggle {
                padding: 6px 12px;
                border-radius: 6px;
                border: none;
                font-size: 12px;
                font-weight: 600;
                cursor: pointer;
                transition: all 0.2s;
                min-width: 50px;
                flex-shrink: 0;
            }

            .feature-toggle.on {
                background: #22c55e;
                color: white;
            }

            .feature-toggle.off {
                background: rgba(255, 255, 255, 0.1);
                color: #909090;
            }

            .feature-toggle:hover:not(:disabled) {
                transform: scale(1.05);
            }

            .feature-toggle:disabled {
                opacity: 0.5;
                cursor: not-allowed;
            }

            .refresh-btn {
                width: 100%;
                padding: 10px;
                background: rgba(255, 255, 255, 0.1);
                border: 1px solid rgba(255, 255, 255, 0.2);
                border-radius: 8px;
                color: #e0e0e0;
                font-size: 13px;
                cursor: pointer;
                margin-top: 16px;
                transition: all 0.2s;
            }

            .refresh-btn:hover {
                background: rgba(255, 255, 255, 0.15);
            }

            .loading {
                text-align: center;
                padding: 20px;
                color: #909090;
                font-size: 13px;
            }

            .panel-content::-webkit-scrollbar {
                width: 6px;
            }

            .panel-content::-webkit-scrollbar-track {
                background: rgba(255, 255, 255, 0.05);
            }

            .panel-content::-webkit-scrollbar-thumb {
                background: rgba(255, 255, 255, 0.2);
                border-radius: 3px;
            }

            .panel-content::-webkit-scrollbar-thumb:hover {
                background: rgba(255, 255, 255, 0.3);
            }
        `;
        document.head.appendChild(style);
    }

    function createPanel() {
        const panel = document.createElement('div');
        panel.id = 'claude-control-panel';
        panel.style.right = panelState.position.right || '20px';
        panel.style.bottom = panelState.position.bottom || '20px';

        if (!panelState.isExpanded) {
            panel.classList.add('collapsed');
        }

        panel.innerHTML = `
            <div class="panel-header">
                <h3>
                    <span>⚡</span>
                    <span>Claude 控制面板</span>
                </h3>
                <div class="collapsed-usage" id="collapsed-usage"></div>
                <div class="panel-controls">
                    <button class="header-rename-btn" id="header-rename-btn" title="重命名当前对话">
                        ✏️ 重命名
                    </button>
                    <div class="collapsed-refresh">
                        <button class="collapsed-refresh-btn" id="collapsed-refresh-btn" title="刷新数据">🔄</button>
                    </div>
                    <button class="panel-btn" id="toggle-panel" title="收起/展开">
                        ${panelState.isExpanded ? '−' : '+'}
                    </button>
                </div>
            </div>
            <div class="panel-content">
                <div class="loading">加载中...</div>
            </div>
        `;

        document.body.appendChild(panel);
        makeDraggable(panel);

        panel.querySelector('#toggle-panel').addEventListener('click', () => {
            panelState.isExpanded = !panelState.isExpanded;
            localStorage.setItem('claudePanel_expanded', panelState.isExpanded);
            panel.classList.toggle('collapsed');
            panel.querySelector('#toggle-panel').textContent = panelState.isExpanded ? '−' : '+';
        });

        // 收起状态的刷新按钮
        panel.querySelector('#collapsed-refresh-btn').addEventListener('click', async (e) => {
            const btn = e.target;
            if (btn.classList.contains('refreshing')) return;
            
            btn.classList.add('refreshing');
            await updatePanelContent(panel);
            
            setTimeout(() => {
                btn.classList.remove('refreshing');
            }, 1000);
        });

        // 标题栏重命名按钮
        panel.querySelector('#header-rename-btn').addEventListener('click', async (e) => {
            const btn = e.target;
            if (btn.disabled) return;
            
            btn.disabled = true;
            btn.innerHTML = '⏳ 重命名中...';
            
            const result = await renameCurrentConversation();
            
            if (result.success) {
                btn.innerHTML = '✅ 成功';
                btn.style.background = 'rgba(34, 197, 94, 0.3)';
            } else {
                btn.innerHTML = '❌ ' + result.message;
                btn.style.background = 'rgba(239, 68, 68, 0.3)';
            }
            
            setTimeout(() => {
                btn.disabled = false;
                btn.innerHTML = '✏️ 重命名';
                btn.style.background = '';
            }, 3000);
        });

        return panel;
    }

    async function updatePanelContent(panel) {
        const content = panel.querySelector('.panel-content');

        const [usageData, settings] = await Promise.all([
            getUsageData(),
            getUserSettings()
        ]);

        if (!usageData && !settings) {
            content.innerHTML = '<div class="loading">❌ 加载失败</div>';
            return;
        }

        // 更新收起状态的用量显示
        const collapsedUsage = panel.querySelector('#collapsed-usage');
        if (usageData && usageData.five_hour) {
            const percent = usageData.five_hour.utilization || 0;
            const colorClass = percent > 80 ? 'danger' : percent > 60 ? 'warning' : '';
            collapsedUsage.innerHTML = `<span class="collapsed-usage-value ${colorClass}">${percent}%</span>`;
        }

        let html = '';

        if (usageData) {
            html += '<div class="usage-section"><div class="section-title">📊 使用情况</div>';

            if (usageData.five_hour) {
                const percent = usageData.five_hour.utilization || 0;
                const barClass = percent > 80 ? 'danger' : percent > 60 ? 'warning' : '';
                html += `
                    <div class="usage-item">
                        <div class="usage-label">
                            <span class="usage-label-text">当前会话</span>
                            <span class="usage-label-time">${formatResetTime(usageData.five_hour.resets_at)}</span>
                        </div>
                        <div class="usage-bar">
                            <div class="usage-bar-fill ${barClass}" style="width: ${percent}%"></div>
                        </div>
                        <div class="usage-percent">已使用 ${percent}%</div>
                    </div>
                `;
            }

            if (usageData.seven_day) {
                const percent = usageData.seven_day.utilization || 0;
                const barClass = percent > 80 ? 'danger' : percent > 60 ? 'warning' : '';
                html += `
                    <div class="usage-item">
                        <div class="usage-label">
                            <span class="usage-label-text">每周限额(所有模型)</span>
                            <span class="usage-label-time">${formatResetTime(usageData.seven_day.resets_at)}</span>
                        </div>
                        <div class="usage-bar">
                            <div class="usage-bar-fill ${barClass}" style="width: ${percent}%"></div>
                        </div>
                        <div class="usage-percent">已使用 ${percent}%</div>
                    </div>
                `;
            }

            if (usageData.seven_day_opus) {
                const percent = usageData.seven_day_opus.utilization || 0;
                const barClass = percent > 80 ? 'danger' : percent > 60 ? 'warning' : '';
                html += `
                    <div class="usage-item">
                        <div class="usage-label">
                            <span class="usage-label-text">每周限额(仅Opus)</span>
                            <span class="usage-label-time">${formatResetTime(usageData.seven_day_opus.resets_at)}</span>
                        </div>
                        <div class="usage-bar">
                            <div class="usage-bar-fill ${barClass}" style="width: ${percent}%"></div>
                        </div>
                        <div class="usage-percent">已使用 ${percent}%</div>
                    </div>
                `;
            }

            html += '</div>';
        }

        if (settings) {
            html += '<div class="features-section"><div class="section-title">🔧 功能开关</div>';

            FEATURES.forEach(feature => {
                const isEnabled = settings[feature.key] === true;
                html += `
                    <div class="feature-item">
                        <div class="feature-info">
                            <div class="feature-name">${feature.name}</div>
                            <div class="feature-desc">${feature.desc}</div>
                        </div>
                        <button class="feature-toggle ${isEnabled ? 'on' : 'off'}"
                                data-key="${feature.key}"
                                data-value="${isEnabled}"
                                data-exclusive="${feature.exclusive || ''}">
                            ${isEnabled ? '✓ 开启' : '关闭'}
                        </button>
                    </div>
                `;
            });

            // 添加自动重命名开关
            html += `
                <div class="feature-item">
                    <div class="feature-info">
                        <div class="feature-name">自动重命名</div>
                        <div class="feature-desc">打开对话时自动重命名为中文</div>
                    </div>
                    <button class="feature-toggle ${panelState.autoRename ? 'on' : 'off'}"
                            id="auto-rename-toggle">
                        ${panelState.autoRename ? '✓ 开启' : '关闭'}
                    </button>
                </div>
            `;

            html += '</div>';
        }

        html += '<button class="refresh-btn" id="refresh-panel">🔄 刷新数据</button>';

        content.innerHTML = html;

        content.querySelectorAll('.feature-toggle').forEach(btn => {
            btn.addEventListener('click', async (e) => {
                const button = e.target;
                
                // 处理自动重命名开关
                if (button.id === 'auto-rename-toggle') {
                    panelState.autoRename = !panelState.autoRename;
                    localStorage.setItem('claudePanel_autoRename', panelState.autoRename);
                    button.className = `feature-toggle ${panelState.autoRename ? 'on' : 'off'}`;
                    button.textContent = panelState.autoRename ? '✓ 开启' : '关闭';
                    return;
                }
                
                const key = button.dataset.key;
                const currentValue = button.dataset.value === 'true';
                const exclusiveKey = button.dataset.exclusive || null;

                button.disabled = true;
                button.textContent = '...';

                const result = await toggleFeature(key, currentValue, exclusiveKey);

                setTimeout(() => updatePanelContent(panel), 300);
            });
        });

        content.querySelector('#refresh-panel')?.addEventListener('click', () => {
            content.querySelector('.loading')?.remove();
            const loading = document.createElement('div');
            loading.className = 'loading';
            loading.textContent = '刷新中...';
            content.insertBefore(loading, content.firstChild);

            setTimeout(() => {
                loading.remove();
                updatePanelContent(panel);
            }, 300);
        });


    }

    function makeDraggable(element) {
        const header = element.querySelector('.panel-header');
        let isDragging = false;
        let currentX, currentY, initialX, initialY;
        let rafId = null;

        header.addEventListener('mousedown', (e) => {
            if (e.target.closest('.panel-btn')) return;

            isDragging = true;
            element.classList.add('dragging');
            
            const rect = element.getBoundingClientRect();
            initialX = e.clientX - rect.left;
            initialY = e.clientY - rect.top;
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;

            e.preventDefault();
            
            if (rafId) {
                cancelAnimationFrame(rafId);
            }
            
            rafId = requestAnimationFrame(() => {
                currentX = e.clientX - initialX;
                currentY = e.clientY - initialY;

                element.style.left = currentX + 'px';
                element.style.top = currentY + 'px';
                element.style.right = 'auto';
                element.style.bottom = 'auto';
            });
        });

        document.addEventListener('mouseup', () => {
            if (isDragging) {
                isDragging = false;
                element.classList.remove('dragging');
                
                if (rafId) {
                    cancelAnimationFrame(rafId);
                    rafId = null;
                }
                
                panelState.position = {
                    left: element.style.left,
                    top: element.style.top
                };
                localStorage.setItem('claudePanel_position', JSON.stringify(panelState.position));
            }
        });
    }

    // 监听URL变化,自动重命名
    let lastUrl = location.href;
    let renameTimeout = null;
    
    function checkUrlChange() {
        const currentUrl = location.href;
        if (currentUrl !== lastUrl) {
            lastUrl = currentUrl;
            
            // 检查是否进入了对话页面
            const match = currentUrl.match(/\/chat\/([a-f0-9-]+)/);
            if (match && panelState.autoRename) {
                // 延迟2秒后执行重命名,确保页面加载完成
                if (renameTimeout) clearTimeout(renameTimeout);
                renameTimeout = setTimeout(async () => {
                    console.log('[Claude Panel] 检测到新对话,准备自动重命名...');
                    const result = await renameCurrentConversation();
                    if (result.success) {
                        console.log('[Claude Panel] 自动重命名成功:', result.message);
                    } else {
                        console.log('[Claude Panel] 自动重命名跳过:', result.message);
                    }
                }, 2000);
            }
        }
    }
    
    function init() {
        injectStyles();
        const panel = createPanel();
        updatePanelContent(panel);

        // 自动刷新:每30秒更新一次
        setInterval(() => updatePanelContent(panel), 30000);
        
        // 监听URL变化
        setInterval(checkUrlChange, 500);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();

感谢大佬

感谢大佬