吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 567|回复: 4
上一主题 下一主题
收起左侧

[学习记录] DeepSeek文本生成聊天,单html生成器

[复制链接]
跳转到指定楼层
楼主
858983646 发表于 2026-6-8 15:39 回帖奖励
可以导出预制好提示词和加密key的html单文件的小工具适合给不熟悉ai的女朋友老婆之类的提供文本处理工具,双击就可以使用,简单无脑。
需要自己注册https://platform.deepseek.com/api_keys申请key和充值,一次一分钱左右还是挺便宜的
运行第一步,导入提示词文件和key,然后导出单文件版本,就可以正式使用了。
提示词文件格式,第一行是功能名称,第二行输入内容提醒,第三行开始是提示词。

[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
    <title></title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Microsoft YaHei', 'Segoe UI', -apple-system, 'Roboto', 'Noto Sans', system-ui, sans-serif;
            background: #F0F7FB;
            min-height: 100vh;
            padding: 12px;
        }

        .container {
            max-width: 1600px;
            margin: 0 auto;
            background: #ffffff;
            border-radius: 20px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
            overflow: hidden;
            transition: all 0.2s ease;
            display: flex;
            flex-direction: column;
        }

        .header {
            background: #98D3EF;
            color: white;
            padding: 16px 28px;
            text-align: center;
            border-bottom: 1px solid rgba(255,255,255,0.2);
        }

        .header h1 {
            font-size: 26px;
            font-weight: 600;
            letter-spacing: -0.2px;
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
            flex-wrap: wrap;
        }

        .header h1::before {
            content: "&#65533;";
            font-size: 28px;
        }

        .header p {
            font-size: 14px;
            opacity: 0.92;
        }

        .model-badge {
            background: rgba(255,255,255,0.2);
            border-radius: 40px;
            display: inline-block;
            padding: 3px 14px;
            font-size: 11px;
            margin-top: 10px;
            font-weight: 500;
            backdrop-filter: blur(2px);
        }

        /* 左右双栏核心区 */
        .two-columns {
            display: flex;
            flex-direction: row;
            gap: 24px;
            padding: 28px 32px 20px 32px;
            background: #ffffff;
            flex: 1;
        }

        .left-panel {
            flex: 1;
            min-width: 0;
            display: flex;
            flex-direction: column;
        }

        .left-panel label {
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 16px;
            font-weight: 700;
            color: #1e3b4a;
            margin-bottom: 12px;
        }

        .textarea-input {
            width: 100%;
            height: calc(100vh - 280px);
            min-height: 460px;
            padding: 18px 20px;
            border: 2px solid #e2edf2;
            border-radius: 24px;
            font-size: 15px;
            line-height: 1.65;
            resize: vertical;
            font-family: 'Segoe UI', 'Microsoft YaHei', 'Courier New', monospace;
            background: #fefefe;
            transition: all 0.2s;
            box-shadow: 0 1px 3px rgba(0,0,0,0.02);
        }

        .textarea-input:focus {
            outline: none;
            border-color: #2c7da0;
            box-shadow: 0 0 0 4px rgba(44,125,160,0.12);
            background: #ffffff;
        }

        .right-panel {
            flex: 1;
            min-width: 0;
            display: flex;
            flex-direction: column;
        }

        .result-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 12px;
            flex-wrap: wrap;
            gap: 10px;
        }

        .result-header h2 {
            font-size: 18px;
            font-weight: 700;
            color: #134b62;
            display: flex;
            align-items: center;
            gap: 8px;
            letter-spacing: -0.2px;
            background: #eef4f8;
            padding: 5px 14px;
            border-radius: 40px;
        }

        .button-group {
            display: flex;
            gap: 12px;
        }

        button {
            padding: 8px 20px;
            font-size: 14px;
            font-weight: 600;
            border: none;
            border-radius: 40px;
            cursor: pointer;
            transition: all 0.2s;
            display: inline-flex;
            align-items: center;
            gap: 8px;
            background: #f0f4f7;
            color: #2c5a6e;
            border: 1px solid #cbdde6;
        }

        .btn-primary {
            background: #98D3EF;
            color: white;
            border: none;
            box-shadow: 0 3px 8px rgba(0,0,0,0.1);
        }

        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 18px -6px #98D3EF60;
            background: #7CC3E3;
        }

        .btn-primary:disabled {
            background: #aec9d9;
            transform: none;
            opacity: 0.7;
            cursor: not-allowed;
        }

        .btn-secondary:hover {
            background: #e3edf3;
            transform: translateY(-1px);
        }

        /* 功能选择下拉框样式 */
        .function-selector-wrapper {
            margin-bottom: 12px;
        }

        .function-selector-wrapper label {
            display: block;
            font-size: 14px;
            font-weight: 600;
            color: #2c5a6e;
            margin-bottom: 6px;
        }

        .function-selector {
            width: 100%;
            padding: 10px 16px;
            font-size: 14px;
            font-weight: 500;
            border: 1px solid #d4e3ec;
            border-radius: 12px;
            background: #f8fbfe;
            color: #2c5a6e;
            cursor: pointer;
            transition: all 0.2s;
            appearance: none;
            background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%232c5a6e' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: right 12px center;
            background-size: 12px;
        }

        .function-selector:hover {
            border-color: #2c7da0;
            background: #ffffff;
        }

        .function-selector:focus {
            outline: none;
            border-color: #2c7da0;
            box-shadow: 0 0 0 3px rgba(44, 125, 160, 0.1);
        }

        /* 复制按钮样式 */
        .btn-copy {
            background: #98D3EF;
            color: white;
            border: none;
            box-shadow: 0 3px 8px rgba(0,0,0,0.1);
        }

        .btn-copy:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 18px -6px #98D3EF60;
            background: #7CC3E3;
        }

        .btn-copy:disabled {
            background: #aec9d9;
            transform: none;
            opacity: 0.7;
            cursor: not-allowed;
        }

        .btn-copy-success {
            background: #98D3EF;
        }

        /* 密钥生成器样式 */
        .key-generator-icon {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            width: 24px;
            height: 24px;
            font-size: 16px;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            background: transparent;
            color: #2c5a6e;
            transition: all 0.2s;
            margin-left: 8px;
        }

        .key-generator-icon:hover {
            background: #e8f4f8;
            transform: scale(1.1);
        }

        .key-generator-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.3);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 9999;
        }

        .key-generator-modal {
            background: white;
            padding: 30px;
            border-radius: 20px;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            max-width: 500px;
            width: 90%;
            max-height: 85vh;
            overflow-y: auto;
        }

        .key-generator-modal h3 {
            margin: 0 0 20px 0;
            font-size: 20px;
            color: #2c5a6e;
            text-align: center;
        }

        .key-generator-modal label {
            display: block;
            font-size: 14px;
            font-weight: 600;
            color: #2c5a6e;
            margin-bottom: 6px;
            margin-top: 15px;
        }

        .key-generator-modal input {
            width: 100%;
            padding: 12px;
            font-size: 14px;
            border: 1px solid #d4e3ec;
            border-radius: 8px;
            margin-bottom: 10px;
            box-sizing: border-box;
        }

        .key-generator-modal input:focus {
            outline: none;
            border-color: #2c7da0;
            box-shadow: 0 0 0 3px rgba(44, 125, 160, 0.1);
        }

        .key-generator-result {
            background: #f8fbfe;
            padding: 15px;
            border-radius: 8px;
            margin-top: 20px;
            border: 1px solid #d4e3ec;
        }

        .key-generator-result-label {
            font-size: 12px;
            color: #5f8da3;
            margin-bottom: 8px;
        }

        .key-generator-result-value {
            font-size: 16px;
            font-weight: 600;
            color: #2c5a6e;
            word-break: break-all;
            font-family: monospace;
        }

        .key-generator-buttons {
            display: flex;
            gap: 10px;
            margin-top: 20px;
        }

        .key-generator-buttons button {
            flex: 1;
            padding: 12px;
            font-size: 14px;
            font-weight: 600;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.2s;
        }

        .btn-generate {
            background: #98D3EF;
            color: white;
        }

        .btn-generate:hover {
            transform: translateY(-1px);
        }

        .btn-close-generator {
            background: #f0f4f7;
            color: #2c5a6e;
            border: 1px solid #cbdde6;
        }

        .btn-close-generator:hover {
            background: #e3edf3;
        }

        .result-box-wrapper {
            flex: 1;
            background: #fafeff;
            border-radius: 24px;
            border: 1px solid #dcecf2;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.02);
            display: flex;
            flex-direction: column;
            overflow: hidden;
        }

        .result-box {
            padding: 22px 26px;
            min-height: 460px;
            height: calc(100vh - 280px);
            overflow-y: auto;
            line-height: 1.75;
            font-size: 15px;
            white-space: pre-wrap;
            word-wrap: break-word;
            font-family: 'Segoe UI', 'Microsoft YaHei', system-ui;
            background: #ffffff;
        }

        .loading-overlay {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 12px;
            padding: 18px;
            background: #f8fbfe;
            border-radius: 32px;
            margin: 0 32px 16px 32px;
        }

        .spinner {
            width: 28px;
            height: 28px;
            border: 3px solid #d4e3ec;
            border-top: 3px solid #98D3EF;
            border-radius: 50%;
            animation: spin 0.8s linear infinite;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        .token-footer {
            background: #eef5f9;
            border-top: 1px solid #d4e3ec;
            padding: 12px 32px;
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            justify-content: flex-start;
            gap: 18px;
            font-size: 13px;
            color: #2c5a6e;
        }

        .token-item {
            background: white;
            border-radius: 32px;
            padding: 5px 16px;
            font-size: 13px;
            font-weight: 500;
            display: inline-flex;
            align-items: center;
            gap: 8px;
            box-shadow: 0 1px 2px rgba(0,0,0,0.05);
        }

        .footer-bar {
            background: #f5fafd;
            padding: 12px 32px;
            text-align: center;
            font-size: 12px;
            color: #5f8da3;
            border-top: 1px solid #dfecf2;
            display: flex;
            justify-content: center;
            gap: 20px;
            flex-wrap: wrap;
        }

        .shortcut-hint {
            font-size: 12px;
            background: #ecf3f7;
            border-radius: 30px;
            padding: 4px 14px;
            display: inline-flex;
            align-items: center;
            gap: 6px;
        }

        .empty-state {
            text-align: center;
            padding: 48px 20px;
            color: #8aaec0;
        }

        .balance-error-card {
            background: #fff5f0;
            border-left: 6px solid #e67e22;
            border-radius: 18px;
            padding: 18px;
            margin: 10px 0;
        }

        /* 密码弹窗遮罩 */
        .password-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.4);
            backdrop-filter: blur(8px);
            z-index: 1000;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: system-ui, 'Segoe UI', monospace;
        }
        .password-modal {
            background: white;
            border-radius: 36px;
            max-width: 400px;
            width: 90%;
            padding: 32px 28px;
            text-align: center;
            box-shadow: 0 30px 50px rgba(0,0,0,0.3);
            animation: fadeUp 0.2s ease;
            max-height: 85vh;
            overflow-y: auto;
        }
        @keyframes fadeUp {
            from { opacity: 0; transform: translateY(20px);}
            to { opacity: 1; transform: translateY(0);}
        }
        .password-modal h3 {
            font-size: 24px;
            margin-bottom: 16px;
            color: #1e5a7a;
        }
        .password-modal input {
            width: 100%;
            padding: 14px 18px;
            font-size: 18px;
            border: 2px solid #d4e2ec;
            border-radius: 60px;
            margin: 16px 0;
            text-align: center;
            letter-spacing: 2px;
            font-weight: 500;
            outline: none;
        }
        .password-modal input:focus {
            border-color: #2c7da0;
        }
        .password-modal button {
            background: #98D3EF;
            color: white;
            border: none;
            padding: 10px 28px;
            border-radius: 40px;
            font-size: 16px;
            font-weight: bold;
            cursor: pointer;
            width: 100%;
        }
        .password-modal .error-msg {
            color: #d9534f;
            font-size: 13px;
            margin-top: 10px;
        }
        .lock-icon {
            font-size: 48px;
            margin-bottom: 8px;
        }

        [url=home.php?mod=space&uid=945662]@media[/url] (max-width: 860px) {
            .two-columns {
                flex-direction: column;
                gap: 20px;
                padding: 16px;
            }
            .textarea-input, .result-box {
                height: 380px;
                min-height: 280px;
            }
            .result-box {
                height: auto;
                min-height: 280px;
            }
            .button-group {
                flex-wrap: nowrap;
                gap: 6px;
            }
            .button-group button {
                padding: 6px 10px;
                font-size: 12px;
                white-space: nowrap;
                flex-shrink: 1;
                min-width: 0;
            }
            .token-footer {
                flex-wrap: nowrap;
                overflow-x: auto;
                gap: 8px;
                padding: 10px 12px;
                -webkit-overflow-scrolling: touch;
            }
            .token-item {
                flex-shrink: 0;
                font-size: 11px;
                padding: 4px 10px;
            }
            .result-header {
                flex-direction: column;
                align-items: flex-start;
                gap: 8px;
            }
            .result-header .button-group {
                width: 100%;
                justify-content: flex-start;
                overflow-x: auto;
            }
            .left-panel label {
                font-size: 14px;
            }
            .function-selector {
                font-size: 13px;
                padding: 8px 12px;
            }
            .key-generator-modal, .password-modal {
                width: 95%;
                max-height: 90vh;
                padding: 20px 16px;
            }
        }

        @media (max-width: 480px) {
            .two-columns {
                padding: 10px;
                gap: 12px;
            }
            .button-group button {
                padding: 5px 8px;
                font-size: 11px;
                gap: 4px;
            }
            .token-item {
                font-size: 10px;
                padding: 3px 8px;
            }
            .textarea-input {
                min-height: 200px;
                height: 200px;
                font-size: 14px;
                padding: 12px;
            }
        }
    </style>
</head>
<body>
<div class="container" id="mainApp" style="display: none;">
    <div class="header">
    </div>

    <!-- 左右核心区 -->
    <div class="two-columns">
        <div class="left-panel">
            <div class="function-selector-wrapper">
                <label>&#127919; 选择功能</label>
                <select id="functionSelector" class="function-selector">
                    <!-- 选项由JavaScript动态生成 -->
                </select>
            </div>
            <div class="function-selector-wrapper" style="margin-top:8px;">
                <label>&#129302; 模型</label>
                <select id="modelSelector" class="function-selector">
                    <option value="deepseek-v4-pro">DeepSeek V4 Pro(精准)</option>
                    <option value="deepseek-v4-flash">DeepSeek V4 Flash(快速)</option>
                </select>
            </div>
            <div style="margin-top:8px;display:flex;align-items:center;gap:8px;">
                <label style="font-size:14px;font-weight:600;color:#2c5a6e;">&#129504; 思考模式</label>
                <label style="display:flex;align-items:center;cursor:pointer;font-size:13px;color:#5a7a8a;">
                    <input type="checkbox" id="thinkingToggle" style="width:16px;height:16px;margin-right:4px;">
                    启用(更准确但消耗更多tokens)
                </label>
            </div>
            <label>&#128221; 文本内容</label>
            <textarea id="inputText" class="textarea-input" placeholder="请输入文本内容..."></textarea>
        </div>

        <div class="right-panel">
            <div class="result-header">
                <h2>&#128269; 输出结果</h2>
                <div class="button-group">
                    <button id="copyBtn" class="btn-copy" style="display: none;">&#128203; 复制结果</button>
                    <button id="checkBtn" class="btn-primary">&#10024; 开始执行</button>
                    <button id="stopBtn" class="btn-secondary" style="display: none; background:#98D3EF; color:white;">&#9209;&#65039; 终止</button>
                    <button id="clearBtn" class="btn-secondary">&#128465;&#65039; 清空内容</button>
                </div>
            </div>
            <div class="result-box-wrapper">
                <div id="resultBox" class="result-box">
                    <div class="empty-state">
                        <div style="font-size: 42px; margin-bottom: 12px;">&#128206;</div>
                        <div>点击「开始执行」或按 Ctrl+Enter</div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 加载条 -->
    <div id="loading" class="loading-overlay" style="display: none;">
        <div class="spinner"></div>
        <span id="loadingText">DeepSeek V4 Pro 正在输出…</span>
    </div>

    <!-- Token 消耗显示区域 (界面下方) -->
    <div id="tokenFooter" class="token-footer" style="display: none;"></div>

    <div class="footer-bar">
    </div>
</div>

<script>
    // ========== 内嵌数据标记开始 ==========
    // 此区域用于存放导出时生成的内嵌数据
    // 导出时会替换此区域的内容
    // ========== 内嵌数据标记结束 ==========

    // 获取密钥(从内嵌数据读取)
    function getApiKey() {
        const localKey = loadKeyFromStorage();
        if (localKey) {
            console.log('使用内嵌密钥');
            return localKey;
        } else {
            console.log('没有密钥');
            return null;
        }
    }

    // 判断密钥类型并获取最终的API密钥
    async function getFinalApiKey(password) {
        console.log('=== getFinalApiKey ===');
        const apiKey = getApiKey();
        console.log('getApiKey返回:', apiKey ? apiKey.substring(0, 15) + '...' : 'null');

        if (!apiKey) {
            console.log('没有密钥');
            return null;
        }

        // 如果密钥以 sk- 开头,说明是明文密钥,直接使用
        if (apiKey.startsWith('sk-')) {
            console.log('明文密钥,直接使用');
            return apiKey;
        }

        // 否则是加密密钥,需要解密
        console.log('加密密钥,需要密码解密,密码:', password || '(空)');
        if (!password) {
            console.log('没有密码,无法解密');
            return null;
        }

        // 解密密钥
        const result = await decryptApiKey(apiKey, password);
        console.log('解密结果:', result ? result.substring(0, 15) + '...' : 'null');
        return result;
    }

    // 使用Web Crypto API进行SHA-256哈希
    async function sha256Hash(text) {
        const encoder = new TextEncoder();
        const data = encoder.encode(text);
        const hashBuffer = await crypto.subtle.digest('SHA-256', data);
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase();
        return hashHex;
    }

    // 对密码进行固定12624次迭代哈希
    async function hashPassword(password) {
        let hash = await sha256Hash(password);
        const iterations = 12624;
        console.log('固定迭代次数:', iterations);

        for (let i = 0; i < iterations; i++) {
            hash = await sha256Hash(hash);
        }
        return hash.substring(0, 32);
    }

    // 解密密钥
    function decryptKey(encrypted, hash) {
        let decrypted = '';
        for (let i = 0; i < encrypted.length; i++) {
            const encryptedChar = parseInt(encrypted[i], 36);
            const hashChar = parseInt(hash[i % hash.length], 36);
            const diff = (encryptedChar - hashChar + 36) % 36;
            decrypted += diff.toString(36);
        }
        return 'sk-' + decrypted;
    }

    // 使用密码解密API Key
    async function decryptApiKey(encryptedKey, password) {
        try {
            console.log('开始解密,密码:', password);
            const hash = await hashPassword(password);
            console.log('密码哈希:', hash);
            const decryptedKey = decryptKey(encryptedKey, hash);
            console.log('解密结果:', decryptedKey);
            return decryptedKey;
        } catch(e) {
            console.error("解密失败", e);
            return null;
        }
    }
    
    let DEEPSEEK_API_KEY = '';
    const DEEPSEEK_API_URL = 'https://api.deepseek.com/v1/chat/completions';
    const DEEPSEEK_BALANCE_URL = 'https://api.deepseek.com/user/balance';
    const MODEL_NAME = 'deepseek-v4-pro';

    // 获取今日日期字符串
    function getTodayDateString() {
        const now = new Date();
        return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
    }

    // 获取今日累计费用
    function getTodayCost() {
        const today = getTodayDateString();
        const stored = localStorage.getItem(COST_STORAGE_KEY);
        if (!stored) return 0;
        try {
            const data = JSON.parse(stored);
            if (data.date === today) {
                return data.cost || 0;
            }
            // 如果日期不是今天,重置为0
            return 0;
        } catch(e) {
            return 0;
        }
    }

    // 更新今日累计费用
    function updateTodayCost(newCost) {
        const today = getTodayDateString();
        const currentCost = getTodayCost();
        const totalCost = currentCost + newCost;
        localStorage.setItem(COST_STORAGE_KEY, JSON.stringify({
            date: today,
            cost: totalCost
        }));
        return totalCost;
    }

    // 查询账户余额
    async function queryAccountBalance() {
        try {
            const response = await fetch(DEEPSEEK_BALANCE_URL, {
                method: 'GET',
                headers: {
                    'Authorization': `Bearer ${DEEPSEEK_API_KEY}`
                }
            });
            if (!response.ok) {
                console.error('查询余额失败:', response.status);
                return null;
            }
            const data = await response.json();
            if (data.balance_infos && data.balance_infos.length > 0) {
                const cnyBalance = data.balance_infos.find(b => b.currency === 'CNY');
                if (cnyBalance) {
                    return parseFloat(cnyBalance.total_balance);
                }
            }
            return null;
        } catch(e) {
            console.error('查询余额异常:', e);
            return null;
        }
    }

    // 功能配置对象(方便扩展)
    // ===================== 模板管理模块 =====================

    // 模板存储键名(仅费用追踪使用localStorage)
    const COST_STORAGE_KEY = 'deepseek_daily_cost';

    // 从内嵌数据加载模板(不使用localStorage)
    function loadTemplatesFromStorage() {
        if (typeof EMBEDDED_TEMPLATES !== 'undefined' && EMBEDDED_TEMPLATES && EMBEDDED_TEMPLATES.length > 0) {
            console.log('加载内嵌模板数据,数量:', EMBEDDED_TEMPLATES.length);
            return [...EMBEDDED_TEMPLATES];
        }
        console.log('没有模板数据');
        return null;
    }

    // 从内嵌数据加载密钥(不使用localStorage)
    function loadKeyFromStorage() {
        if (typeof EMBEDDED_KEY !== 'undefined' && EMBEDDED_KEY) {
            console.log('使用内嵌密钥');
            return EMBEDDED_KEY;
        }
        console.log('没有密钥数据');
        return null;
    }


    // 自定义弹窗(替代alert)
    function showCustomAlert(message) {
        var overlay = document.createElement('div');
        overlay.className = 'key-generator-overlay';

        var modal = document.createElement('div');
        modal.className = 'key-generator-modal';
        modal.style.maxWidth = '400px';

        var msgDiv = document.createElement('div');
        msgDiv.style.cssText = 'padding:20px;font-size:15px;color:#2c5a6e;line-height:1.8;white-space:pre-line;';
        msgDiv.textContent = message;
        modal.appendChild(msgDiv);

        var btnArea = document.createElement('div');
        btnArea.className = 'key-generator-buttons';
        btnArea.style.marginTop = '15px';

        var okBtn = document.createElement('button');
        okBtn.className = 'btn-generate';
        okBtn.textContent = '确定';
        okBtn.addEventListener('click', function() {
            overlay.remove();
        });
        btnArea.appendChild(okBtn);
        modal.appendChild(btnArea);

        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        overlay.addEventListener('click', function(e) {
            if (e.target === overlay) overlay.remove();
        });
    }

    // 解析模板文件内容
    function parseTemplateContent(content) {
        const lines = content.split('\n');
        if (lines.length < 3) {
            console.error('模板格式错误:至少需要3行');
            return null;
        }

        const name = lines[0].trim();
        const placeholder = lines[1].trim();
        const prompt = lines.slice(2).join('\n').trim();

        return {
            name,
            placeholder,
            prompt
        };
    }

    // 导入模板文件并生成内嵌版HTML
    // 带手动密钥的导入(不需要key.txt)
    async function importTemplateFilesWithManualKey() {
        var input = document.createElement('input');
        input.type = 'file';
        input.multiple = true;
        input.accept = '.txt';

        input.onchange = async function(e) {
            var files = e.target.files;
            if (!files || files.length === 0) return;

            var newTemplates = [];
            var order = 1;

            for (var i = 0; i < files.length; i++) {
                var file = files[i];
                var fileName = file.name;

                // 跳过key.txt(已有手动输入的密钥)
                if (fileName.toLowerCase() === 'key.txt') continue;

                if (fileName.toLowerCase().endsWith('.txt')) {
                    try {
                        var content = await file.text();
                        var template = parseTemplateContent(content);
                        if (template) {
                            newTemplates.push({
                                order: order,
                                fileName: fileName
                            });
                            Object.assign(newTemplates[newTemplates.length - 1], template);
                            order++;
                        }
                    } catch(e) {
                        console.error('读取文件失败:', fileName, e);
                    }
                }
            }

            if (newTemplates.length === 0) {
                showCustomAlert('没有模板数据!请选择模板文件(任意文件名.txt)。');
                return;
            }

            // 获取已有的内嵌数据
            var existingTemplates = loadTemplatesFromStorage() || [];
            var existingKey = loadKeyFromStorage();

            // 自动去重
            var dedupedNewTemplates = [];
            var duplicateCount = 0;
            newTemplates.forEach(function(newT) {
                var isDuplicate = existingTemplates.some(function(existT) {
                    return existT.name === newT.name && existT.prompt === newT.prompt;
                });
                if (isDuplicate) {
                    duplicateCount++;
                } else {
                    dedupedNewTemplates.push(newT);
                }
            });

            showMergeDialog(existingTemplates, dedupedNewTemplates, existingKey, null, duplicateCount);
        };

        input.click();
    }

    async function importTemplateFiles() {
        const input = document.createElement('input');
        input.type = 'file';
        input.multiple = true;
        input.accept = '.txt';

        input.onchange = async (e) => {
            const files = e.target.files;
            if (!files || files.length === 0) {
                return;
            }

            // 读取新导入的模板和密钥
            const newTemplates = [];
            let newKeyContent = null;
            let order = 1;

            for (let i = 0; i < files.length; i++) {
                const file = files[i];
                const fileName = file.name;

                if (fileName.toLowerCase() === 'key.txt') {
                    try {
                        newKeyContent = (await file.text()).trim();
                        console.log('导入密钥文件成功');
                    } catch(e) {
                        console.error('读取密钥文件失败:', e);
                    }
                    continue;
                }

                if (fileName.toLowerCase().endsWith('.txt')) {
                    try {
                        const content = await file.text();
                        const template = parseTemplateContent(content);
                        if (template) {
                            newTemplates.push({
                                order: order,
                                fileName: fileName,
                                ...template
                            });
                            console.log('导入模板成功:', fileName, template.name);
                            order++;
                        }
                    } catch(e) {
                        console.error('读取文件失败:', fileName, e);
                    }
                }
            }

            // 获取已有的内嵌数据
            const existingTemplates = loadTemplatesFromStorage() || [];
            const existingKey = loadKeyFromStorage();

            // 自动去重:新模板与已有模板 name+prompt 完全相同时标记为重复
            const dedupedNewTemplates = [];
            const duplicateNames = [];
            newTemplates.forEach(function(newT) {
                const isDuplicate = existingTemplates.some(function(existT) {
                    return existT.name === newT.name && existT.prompt === newT.prompt;
                });
                if (isDuplicate) {
                    duplicateNames.push(newT.name);
                    console.log('去重:跳过重复模板', newT.name);
                } else {
                    dedupedNewTemplates.push(newT);
                }
            });

            if (duplicateNames.length > 0) {
                console.log('自动去重:跳过了 ' + duplicateNames.length + ' 个重复模板');
            }

            // 必须有模板(已有的或新导入的)
            if (existingTemplates.length === 0 && newTemplates.length === 0) {
                showCustomAlert('没有模板数据!请选择模板文件(任意文件名.txt)。');
                return;
            }

            // 必须有密钥(已有的或新导入的)
            if (!existingKey && !newKeyContent) {
                showCustomAlert('没有密钥!请选择 key.txt 文件。');
                return;
            }

            // 显示合并选择弹窗
            showMergeDialog(existingTemplates, dedupedNewTemplates, existingKey, newKeyContent, duplicateNames.length);
        };

        input.click();
    }

    // 显示合并选择弹窗(使用DOM API构建)
    function showMergeDialog(existingTemplates, newTemplates, existingKey, newKeyContent, duplicateCount) {
        var overlay = document.createElement('div');
        overlay.className = 'key-generator-overlay';

        var modal = document.createElement('div');
        modal.className = 'key-generator-modal';
        modal.style.maxWidth = '550px';
        modal.style.maxHeight = '85vh';
        modal.style.overflowY = 'auto';

        // 标题
        var title = document.createElement('h3');
        title.textContent = '&#128230; 导入并生成内嵌版';
        modal.appendChild(title);

        var desc = document.createElement('p');
        desc.style.cssText = 'color:#4a6f7e;margin-bottom:15px;font-size:13px;';
        desc.textContent = '选择要包含的模板和密钥,生成可独立运行的内嵌版HTML文件:';
        modal.appendChild(desc);

        // 去重提示
        if (duplicateCount && duplicateCount > 0) {
            var dedupTip = document.createElement('div');
            dedupTip.style.cssText = 'padding:8px 12px;background:#fff3e0;border-radius:8px;margin-bottom:12px;font-size:13px;color:#e65100;';
            dedupTip.textContent = '&#128260; 已自动去重 ' + duplicateCount + ' 个与已有模板完全相同的模板';
            modal.appendChild(dedupTip);
        }

        // 存储所有checkbox
        var allCheckboxes = [];
        var allTemplateData = [];

        // 已有模板区域
        if (existingTemplates.length > 0) {
            var existTitle = document.createElement('div');
            existTitle.style.cssText = 'font-weight:600;margin-bottom:8px;color:#2c5a6e;';
            existTitle.textContent = '&#128203; 已有模板(' + existingTemplates.length + '个):';
            modal.appendChild(existTitle);

            var existContainer = document.createElement('div');
            existContainer.style.cssText = 'max-height:200px;overflow-y:auto;padding:5px;margin-bottom:12px;';

            // 统计同名
            var nameCountExist = {};
            existingTemplates.forEach(function(t) {
                nameCountExist[t.name] = (nameCountExist[t.name] || 0) + 1;
            });
            var nameShownExist = {};

            existingTemplates.forEach(function(template, index) {
                var displayName = template.name;
                if (nameCountExist[template.name] > 1) {
                    if (!nameShownExist[template.name]) nameShownExist[template.name] = 1;
                    displayName = template.name + '(' + nameShownExist[template.name] + ')';
                    nameShownExist[template.name]++;
                }

                var row = document.createElement('div');
                row.style.cssText = 'display:flex;align-items:center;padding:6px 12px;background:#f0f7ff;border-radius:8px;margin-bottom:4px;';

                var cb = document.createElement('input');
                cb.type = 'checkbox';
                cb.checked = true;
                cb.style.cssText = 'margin-right:10px;width:18px;height:18px;';

                var label = document.createElement('label');
                label.style.cssText = 'flex:1;cursor:pointer;font-size:14px;';
                label.textContent = displayName;

                var span = document.createElement('span');
                span.style.cssText = 'color:#888;font-size:11px;margin-left:6px;';
                span.textContent = '(' + (template.fileName || (template.order + '.txt')) + ')';
                label.appendChild(span);

                row.appendChild(cb);
                row.appendChild(label);
                existContainer.appendChild(row);

                allCheckboxes.push(cb);
                allTemplateData.push(template);
            });

            modal.appendChild(existContainer);
        }

        // 新导入模板区域
        if (newTemplates.length > 0) {
            var newTitle = document.createElement('div');
            newTitle.style.cssText = 'font-weight:600;margin-bottom:8px;color:#2c5a6e;';
            newTitle.textContent = '&#128229; 新导入模板(' + newTemplates.length + '个):';
            modal.appendChild(newTitle);

            var newContainer = document.createElement('div');
            newContainer.style.cssText = 'max-height:200px;overflow-y:auto;padding:5px;margin-bottom:12px;';

            // 统计同名
            var nameCountNew = {};
            newTemplates.forEach(function(t) {
                nameCountNew[t.name] = (nameCountNew[t.name] || 0) + 1;
            });
            var nameShownNew = {};

            newTemplates.forEach(function(template, index) {
                var displayName = template.name;
                if (nameCountNew[template.name] > 1) {
                    if (!nameShownNew[template.name]) nameShownNew[template.name] = 1;
                    displayName = template.name + '(' + nameShownNew[template.name] + ')';
                    nameShownNew[template.name]++;
                }

                var row = document.createElement('div');
                row.style.cssText = 'display:flex;align-items:center;padding:6px 12px;background:#fff8f0;border-radius:8px;margin-bottom:4px;border:1px solid #ffe0c0;';

                var cb = document.createElement('input');
                cb.type = 'checkbox';
                cb.checked = true;
                cb.style.cssText = 'margin-right:10px;width:18px;height:18px;';

                var label = document.createElement('label');
                label.style.cssText = 'flex:1;cursor:pointer;font-size:14px;';
                label.textContent = displayName;

                var span = document.createElement('span');
                span.style.cssText = 'color:#888;font-size:11px;margin-left:6px;';
                span.textContent = '(' + (template.fileName || (template.order + '.txt')) + ')';
                label.appendChild(span);

                row.appendChild(cb);
                row.appendChild(label);
                newContainer.appendChild(row);

                allCheckboxes.push(cb);
                allTemplateData.push(template);
            });

            modal.appendChild(newContainer);
        }

        // 全选/取消全选
        var selectAllRow = document.createElement('div');
        selectAllRow.style.cssText = 'display:flex;align-items:center;padding:6px 12px;background:#e8f5e9;border-radius:8px;margin-bottom:12px;cursor:pointer;';
        var selectAllCb = document.createElement('input');
        selectAllCb.type = 'checkbox';
        selectAllCb.checked = true;
        selectAllCb.style.cssText = 'margin-right:10px;width:18px;height:18px;';
        var selectAllLabel = document.createElement('label');
        selectAllLabel.style.cssText = 'cursor:pointer;font-weight:600;color:#2e7d32;';
        selectAllLabel.textContent = '全选/取消全选';
        selectAllRow.appendChild(selectAllCb);
        selectAllRow.appendChild(selectAllLabel);
        modal.appendChild(selectAllRow);

        selectAllCb.addEventListener('change', function() {
            allCheckboxes.forEach(function(cb) { cb.checked = selectAllCb.checked; });
        });

        // 密钥选择区域(必选,只能选一个)
        var keySection = document.createElement('div');
        keySection.style.cssText = 'margin-top:10px;padding:12px;background:#F8FAFC;border-radius:8px;border:1px solid #d4e3ec;';

        var keySectionTitle = document.createElement('div');
        keySectionTitle.style.cssText = 'font-weight:600;margin-bottom:10px;color:#2c5a6e;';
        keySectionTitle.textContent = '&#128273; 密钥选择(必选):';
        keySection.appendChild(keySectionTitle);

        var selectedKey = null;
        var keyRadios = [];

        if (existingKey) {
            var existKeyRow = document.createElement('div');
            existKeyRow.style.cssText = 'display:flex;align-items:center;padding:6px 0;';
            var existKeyRadio = document.createElement('input');
            existKeyRadio.type = 'radio';
            existKeyRadio.name = 'keySelect';
            existKeyRadio.value = 'existing';
            existKeyRadio.checked = !newKeyContent;
            existKeyRadio.style.cssText = 'margin-right:10px;width:18px;height:18px;';
            var existKeyLabel = document.createElement('label');
            existKeyLabel.style.cssText = 'cursor:pointer;font-size:14px;';
            existKeyLabel.textContent = '使用现有密钥(' + existingKey.substring(0, 8) + '...)';
            existKeyRow.appendChild(existKeyRadio);
            existKeyRow.appendChild(existKeyLabel);
            keySection.appendChild(existKeyRow);
            keyRadios.push(existKeyRadio);
        }

        if (newKeyContent) {
            var newKeyRow = document.createElement('div');
            newKeyRow.style.cssText = 'display:flex;align-items:center;padding:6px 0;';
            var newKeyRadio = document.createElement('input');
            newKeyRadio.type = 'radio';
            newKeyRadio.name = 'keySelect';
            newKeyRadio.value = 'new';
            newKeyRadio.checked = true;
            newKeyRadio.style.cssText = 'margin-right:10px;width:18px;height:18px;';
            var newKeyLabel = document.createElement('label');
            newKeyLabel.style.cssText = 'cursor:pointer;font-size:14px;';
            newKeyLabel.textContent = '使用新导入密钥(' + newKeyContent.substring(0, 8) + '...)';
            newKeyRow.appendChild(newKeyRadio);
            newKeyRow.appendChild(newKeyLabel);
            keySection.appendChild(newKeyRow);
            keyRadios.push(newKeyRadio);
        }

        // 手动输入密钥选项
        var manualKeyRow = document.createElement('div');
        manualKeyRow.style.cssText = 'display:flex;align-items:center;padding:6px 0;';
        var manualKeyRadio = document.createElement('input');
        manualKeyRadio.type = 'radio';
        manualKeyRadio.name = 'keySelect';
        manualKeyRadio.value = 'manual';
        manualKeyRadio.checked = !existingKey && !newKeyContent; // 没有其他key时默认选中
        manualKeyRadio.style.cssText = 'margin-right:10px;width:18px;height:18px;';
        var manualKeyLabel = document.createElement('label');
        manualKeyLabel.style.cssText = 'cursor:pointer;font-size:14px;';
        manualKeyLabel.textContent = '手动输入密钥';
        manualKeyRow.appendChild(manualKeyRadio);
        manualKeyRow.appendChild(manualKeyLabel);
        keySection.appendChild(manualKeyRow);
        keyRadios.push(manualKeyRadio);

        // 手动输入区域(选择"手动输入"时显示)
        var manualInputArea = document.createElement('div');
        manualInputArea.style.cssText = 'display:none;margin-top:8px;padding:10px;background:#fff;border-radius:8px;border:1px solid #eee;';

        var manualKeyInput = document.createElement('input');
        manualKeyInput.type = 'text';
        manualKeyInput.placeholder = '输入API Key(sk-...)';
        manualKeyInput.style.cssText = 'width:100%;padding:8px;border:1px solid #ddd;border-radius:6px;font-size:13px;margin-bottom:6px;box-sizing:border-box;';
        manualKeyInput.autocomplete = 'off';
        manualInputArea.appendChild(manualKeyInput);

        var manualPwdInput = document.createElement('input');
        manualPwdInput.type = 'password';
        manualPwdInput.placeholder = '加密密码(留空则明文存储)';
        manualPwdInput.style.cssText = 'width:100%;padding:8px;border:1px solid #ddd;border-radius:6px;font-size:13px;margin-bottom:4px;box-sizing:border-box;';
        manualPwdInput.autocomplete = 'off';
        manualInputArea.appendChild(manualPwdInput);

        var manualPwdTip = document.createElement('div');
        manualPwdTip.style.cssText = 'font-size:11px;color:#888;';
        manualPwdTip.textContent = '&#128161; 输入密码可加密存储密钥,留空则明文存储';
        manualInputArea.appendChild(manualPwdTip);

        keySection.appendChild(manualInputArea);

        // radio切换时显示/隐藏手动输入区域
        keyRadios.forEach(function(radio) {
            radio.addEventListener('change', function() {
                manualInputArea.style.display = manualKeyRadio.checked ? 'block' : 'none';
            });
        });

        // 如果没有其他key,手动输入区域默认显示
        if (!existingKey && !newKeyContent) {
            manualInputArea.style.display = 'block';
        }

        modal.appendChild(keySection);

        // 提示
        var tip = document.createElement('div');
        tip.style.cssText = 'margin-top:12px;padding:10px;background:#e8f4f8;border-radius:8px;font-size:12px;color:#3a5a6a;';
        tip.textContent = '&#128161; 生成的内嵌版HTML文件可直接双击运行,无需外部文件';
        modal.appendChild(tip);

        // 按钮区域
        var btnArea = document.createElement('div');
        btnArea.className = 'key-generator-buttons';
        btnArea.style.marginTop = '20px';

        var generateBtn = document.createElement('button');
        generateBtn.className = 'btn-generate';
        generateBtn.textContent = '&#128230; 生成内嵌版HTML';

        var cancelBtn = document.createElement('button');
        cancelBtn.className = 'btn-close-generator';
        cancelBtn.textContent = '取消';

        btnArea.appendChild(generateBtn);
        btnArea.appendChild(cancelBtn);
        modal.appendChild(btnArea);

        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        // 生成按钮事件
        generateBtn.addEventListener('click', async function() {
            // 获取选中的模板
            var selectedTemplates = [];
            allCheckboxes.forEach(function(cb, idx) {
                if (cb.checked) {
                    selectedTemplates.push(allTemplateData[idx]);
                }
            });

            if (selectedTemplates.length === 0) {
                showCustomAlert('请至少选择一个模板!');
                return;
            }

            // 获取选中的密钥
            var finalKey = null;
            var checkedRadio = keySection.querySelector('input[name="keySelect"]:checked');
            if (checkedRadio) {
                if (checkedRadio.value === 'existing') {
                    finalKey = existingKey;
                } else if (checkedRadio.value === 'new') {
                    finalKey = newKeyContent;
                } else if (checkedRadio.value === 'manual') {
                    var manualKey = manualKeyInput.value.trim();
                    if (!manualKey) {
                        showCustomAlert('请输入API Key!');
                        return;
                    }
                    if (!manualKey.startsWith('sk-')) {
                        showCustomAlert('API Key应以sk-开头!');
                        return;
                    }
                    var password = manualPwdInput.value.trim();
                    if (password) {
                        // 加密存储
                        var keyPart = manualKey.substring(3);
                        finalKey = await encryptApiKey(keyPart, password);
                    } else {
                        // 明文存储
                        finalKey = manualKey;
                    }
                }
            }

            if (!finalKey) {
                showCustomAlert('请选择或输入密钥!');
                return;
            }

            overlay.remove();

            // 重新编号选中的模板
            selectedTemplates.forEach(function(t, idx) {
                t.order = idx + 1;
            });

            doExport(selectedTemplates, finalKey);
        });

        // 取消按钮事件
        cancelBtn.addEventListener('click', function() {
            overlay.remove();
        });

        // 点击overlay背景关闭
        overlay.addEventListener('click', function(e) {
            if (e.target === overlay) {
                overlay.remove();
            }
        });
    }

    // 获取模板配置
    function getTemplateConfig() {
        const templates = loadTemplatesFromStorage();

        if (!templates || templates.length === 0) {
            return {};
        }

        // 构建配置对象(使用序号作为key)
        const config = {};
        templates.forEach((template) => {
            config[template.order] = {
                name: template.name,
                placeholder: template.placeholder,
                getPrompt: (currentDateStr) => {
                    // 替换提示词中的日期占位符
                    return template.prompt.replace('${currentDateStr}', currentDateStr);
                }
            };
        });

        return config;
    }

    // 获取模板配置对象
    const FUNCTION_CONFIG = getTemplateConfig();

    // 动态生成 System Prompt(根据功能类型)
    function getSystemPrompt(functionType, currentDateStr) {
        const config = FUNCTION_CONFIG[functionType];
        if (!config) {
            console.error('未知的功能类型:', functionType);
            // 使用第一个可用的配置
            const firstKey = Object.keys(FUNCTION_CONFIG)[0];
            return FUNCTION_CONFIG[firstKey].getPrompt(currentDateStr);
        }
        return config.getPrompt(currentDateStr);
    }

    // 动态生成功能选择器选项
    function populateFunctionSelector() {
        if (!functionSelector) {
            console.error('functionSelector未找到');
            return;
        }

        // 清空现有选项
        functionSelector.innerHTML = '';

        // 从localStorage加载模板
        const templates = loadTemplatesFromStorage();

        if (!templates || templates.length === 0) {
            // 如果没有模板,添加提示选项
            const option = document.createElement('option');
            option.value = '';
            option.textContent = '请先导入模板';
            functionSelector.appendChild(option);
            console.log('没有模板数据');
            return;
        }

        // 按序号排序后添加选项
        templates.sort((a, b) => a.order - b.order);

        // 统计同名功能数量,自动加序号
        const nameCount = {};
        templates.forEach(template => {
            const name = template.name;
            if (!nameCount[name]) {
                nameCount[name] = 0;
            }
            nameCount[name]++;
        });

        // 记录已显示的同名数量
        const nameShown = {};

        templates.forEach(template => {
            const option = document.createElement('option');
            option.value = template.order;  // 使用序号作为value

            const name = template.name;
            // 如果同名功能超过1个,自动加序号
            if (nameCount[name] > 1) {
                if (!nameShown[name]) {
                    nameShown[name] = 1;
                }
                option.textContent = `${name}(${nameShown[name]})`;
                nameShown[name]++;
            } else {
                option.textContent = name;
            }

            functionSelector.appendChild(option);
        });

        console.log('功能选择器选项已生成,数量:', templates.length);
    }

    // 更新输入框placeholder
    // DOM 元素
    let inputTextarea, checkBtn, clearBtn, resultBox, loadingDiv, tokenFooter, mainAppContainer, functionSelector, copyBtn;

    function initDomRefs() {
        console.log('initDomRefs开始');
        inputTextarea = document.getElementById('inputText');
        console.log('inputTextarea:', inputTextarea);
        checkBtn = document.getElementById('checkBtn');
        console.log('checkBtn:', checkBtn);
        clearBtn = document.getElementById('clearBtn');
        console.log('clearBtn:', clearBtn);
        resultBox = document.getElementById('resultBox');
        console.log('resultBox:', resultBox);
        loadingDiv = document.getElementById('loading');
        console.log('loadingDiv:', loadingDiv);
        tokenFooter = document.getElementById('tokenFooter');
        console.log('tokenFooter:', tokenFooter);
        mainAppContainer = document.getElementById('mainApp');
        console.log('mainAppContainer:', mainAppContainer);
        functionSelector = document.getElementById('functionSelector');
        console.log('functionSelector:', functionSelector);
        copyBtn = document.getElementById('copyBtn');
        console.log('copyBtn:', copyBtn);
        console.log('initDomRefs完成');
    }
    
    function clearAll() {
        if (!inputTextarea) return;
        inputTextarea.value = '';
        if (resultBox) {
            resultBox.innerHTML = `<div class="empty-state">
                <div style="font-size: 42px; margin-bottom: 12px;">&#128206;</div>
                <div>点击「开始执行」或按 Ctrl+Enter</div>
            </div>`;
        }
        if (tokenFooter) tokenFooter.style.display = 'none';
        if (copyBtn) copyBtn.style.display = 'none'; // 隐藏复制按钮
    }

    // 复制结果到剪贴板
    async function copyResult() {
        if (!resultBox) return;

        // 获取结果文本
        const resultText = resultBox.textContent || resultBox.innerText;

        if (!resultText || resultText.trim() === '') {
            showCustomAlert('没有可复制的内容');
            return;
        }

        try {
            // 使用现代Clipboard API
            await navigator.clipboard.writeText(resultText);

            // 显示成功状态
            copyBtn.innerHTML = '&#9989; 已复制';
            copyBtn.classList.add('btn-copy-success');

            // 2秒后恢复原状态
            setTimeout(() => {
                copyBtn.innerHTML = '&#128203; 复制结果';
                copyBtn.classList.remove('btn-copy-success');
            }, 2000);

        } catch (err) {
            // 如果Clipboard API失败,使用传统方法
            console.error('复制失败:', err);

            // 创建临时textarea元素
            const textarea = document.createElement('textarea');
            textarea.value = resultText;
            textarea.style.position = 'fixed';
            textarea.style.left = '-9999px';
            document.body.appendChild(textarea);
            textarea.select();

            try {
                document.execCommand('copy');
                copyBtn.innerHTML = '&#9989; 已复制';
                copyBtn.classList.add('btn-copy-success');

                setTimeout(() => {
                    copyBtn.innerHTML = '&#128203; 复制结果';
                    copyBtn.classList.remove('btn-copy-success');
                }, 2000);
            } catch (e) {
                showCustomAlert('复制失败,请手动复制');
            }

            document.body.removeChild(textarea);
        }
    }
    
    async function updateTokenFooterDisplay(usage) {
        if (!usage || (!usage.prompt_tokens && !usage.completion_tokens)) {
            if (tokenFooter) tokenFooter.style.display = 'none';
            return;
        }
        const promptTokens = usage.prompt_tokens || 0;
        const completionTokens = usage.completion_tokens || 0;
        const totalTokens = usage.total_tokens || (promptTokens + completionTokens);
        // 根据当前模型选择价格
        const modelSelector = document.getElementById('modelSelector');
        const currentModel = modelSelector ? modelSelector.value : MODEL_NAME;
        const isFlash = currentModel === 'deepseek-v4-flash';
        const inputPricePerMillion = isFlash ? 1 : 3;
        const outputPricePerMillion = isFlash ? 2 : 6;
        const inputCost = (promptTokens / 1_000_000) * inputPricePerMillion;
        const outputCost = (completionTokens / 1_000_000) * outputPricePerMillion;
        const totalCost = inputCost + outputCost;

        // 更新今日累计费用
        const todayTotalCost = updateTodayCost(totalCost);

        // 查询账户余额
        const balance = await queryAccountBalance();

        const modelLabel = isFlash ? 'DeepSeek V4 Flash' : 'DeepSeek V4 Pro';
        const priceLabel = isFlash ? '1/2' : '3/6';

        tokenFooter.innerHTML = `
            <div class="token-item">&#129504; 模型: ${modelLabel}</div>
            <div class="token-item">&#128229; 输入 tokens: ${promptTokens.toLocaleString()}</div>
            <div class="token-item">&#128228; 输出 tokens: ${completionTokens.toLocaleString()}</div>
            <div class="token-item">&#128257; 总计 tokens: ${totalTokens.toLocaleString()}</div>
            <div class="token-item">&#128176; 本次费用: &#165;${totalCost.toFixed(6)} 元</div>
            <div class="token-item">&#128202; 今日消耗: &#165;${todayTotalCost.toFixed(6)} 元</div>
            ${balance !== null ? `<div class="token-item">&#128179; 账户余额: &#165;${balance.toFixed(2)} 元</div>` : ''}
            <div class="token-item">&#128181; 价格: 输入${inputPricePerMillion}元/百万tokens,输出${outputPricePerMillion}元/百万tokens</div>
        `;
        tokenFooter.style.display = 'flex';
    }
    
    function escapeHtml(str) {
        if (!str) return '';
        return str.replace(/[&<>]/g, function(m) {
            if (m === '&') return '&';
            if (m === '<') return '<';
            if (m === '>') return '>';
            return m;
        });
    }
    
    function renderInsufficientBalanceError(originalMsg) {
        const errorHtml = `
            <div class="balance-error-card">
                <strong>&#9888;&#65039; API 请求失败:账户余额不足或授权受限</strong>
                <div style="margin-top: 8px; color: #c4450c;">${escapeHtml(originalMsg)}</div>
                <div style="margin-top: 12px; font-size: 13px;">
                    &#128269; 请前往 <a href="https://platform.deepseek.com/usage" target="_blank" style="color:#2c7da0;">DeepSeek 开放平台</a> 检查余额并充值。
                </div>
            </div>
        `;
        resultBox.innerHTML = errorHtml;
        tokenFooter.style.display = 'none';
    }
    
    // 流式调用 DeepSeek(自动附带当前日期到提示词)
    async function streamDeepSeekCorrection(userText, functionType, currentDate) {
        const dynamicSystemPrompt = getSystemPrompt(functionType, currentDate);
        const config = FUNCTION_CONFIG[functionType];

        // 根据功能类型生成user message(所有功能都统一格式)
        const userMessage = `内容如下:\n\n${userText}`;

        // 获取当前选择的模型和思考模式
        const modelSelector = document.getElementById('modelSelector');
        const thinkingToggle = document.getElementById('thinkingToggle');
        const currentModel = modelSelector ? modelSelector.value : MODEL_NAME;
        const thinkingEnabled = thinkingToggle ? thinkingToggle.checked : true;

        const requestPayload = {
            model: currentModel,
            messages: [
                { role: "system", content: dynamicSystemPrompt },
                { role: "user", content: userMessage }
            ],
            stream: true,
            max_tokens: 8192
        };

        // 思考模式:设置thinking参数,不支持temperature/top_p
        if (thinkingEnabled) {
            requestPayload.thinking = { type: "enabled" };
            requestPayload.reasoning_effort = "high";
        } else {
            requestPayload.thinking = { type: "disabled" };
            requestPayload.temperature = 0.1;
            requestPayload.top_p = 0.92;
        }
    
        const response = await fetch(DEEPSEEK_API_URL, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${DEEPSEEK_API_KEY}`,
                'opt-out': 'training'
            },
            body: JSON.stringify(requestPayload)
        });
    
        if (!response.ok) {
            let errorDetail = `HTTP ${response.status}`;
            let rawError = '';
            try {
                const errJson = await response.json();
                rawError = errJson.error?.message || JSON.stringify(errJson);
                errorDetail = rawError;
            } catch (e) {
                rawError = await response.text();
                errorDetail = rawError;
            }
            const lowerDetail = errorDetail.toLowerCase();
            if (lowerDetail.includes('insufficient') || lowerDetail.includes('balance') || lowerDetail.includes('余额') || response.status === 402) {
                throw new Error(`INSUFFICIENT_BALANCE:${errorDetail}`);
            }
            throw new Error(`API_ERROR:${errorDetail}`);
        }
    
        const reader = response.body.getReader();
        const decoder = new TextDecoder('utf-8');
        let fullContent = '';
        let reasoningContent = '';
        let buffer = '';
        let usageInfo = null;
        let aborted = false;

        resultBox.innerHTML = '';

        // 思维链显示区域(可折叠)
        const reasoningDiv = document.createElement('div');
        reasoningDiv.style.cssText = 'display:none;margin-bottom:12px;border:1px solid #e0e8ee;border-radius:8px;overflow:hidden;';

        const reasoningHeader = document.createElement('div');
        reasoningHeader.style.cssText = 'padding:8px 12px;background:#f0f5f8;cursor:pointer;font-size:13px;color:#5a7a8a;display:flex;justify-content:space-between;align-items:center;';
        reasoningHeader.innerHTML = '<span>&#129504; 思考过程</span><span id="reasoningToggleIcon">▼</span>';

        const reasoningBody = document.createElement('div');
        reasoningBody.style.cssText = 'padding:10px 12px;font-size:13px;color:#6a8a9a;line-height:1.6;max-height:300px;overflow-y:auto;white-space:pre-wrap;word-break:break-word;';

        reasoningDiv.appendChild(reasoningHeader);
        reasoningDiv.appendChild(reasoningBody);
        resultBox.appendChild(reasoningDiv);

        // 折叠/展开思维链
        reasoningHeader.addEventListener('click', function() {
            if (reasoningBody.style.display === 'none') {
                reasoningBody.style.display = 'block';
                document.getElementById('reasoningToggleIcon').textContent = '▼';
            } else {
                reasoningBody.style.display = 'none';
                document.getElementById('reasoningToggleIcon').textContent = '&#9654;';
            }
        });

        // 最终回答显示区域
        const contentSpan = document.createElement('div');
        resultBox.appendChild(contentSpan);

        // 终止按钮逻辑
        const stopBtn = document.getElementById('stopBtn');
        stopBtn.style.display = 'inline-flex';
        stopBtn.onclick = function() {
            aborted = true;
            reader.cancel();
            stopBtn.style.display = 'none';
        };

        while (true) {
            if (aborted) break;
            const { done, value } = await reader.read();
            if (done) break;
            buffer += decoder.decode(value, { stream: true });
            const lines = buffer.split('\n');
            buffer = lines.pop() || '';

            for (const line of lines) {
                const trimmed = line.trim();
                if (!trimmed || !trimmed.startsWith('data: ')) continue;
                const dataPart = trimmed.slice(6);
                if (dataPart === '[DONE]') continue;
                try {
                    const jsonChunk = JSON.parse(dataPart);
                    const delta = jsonChunk.choices?.[0]?.delta;
                    // 处理思考模式的思维链内容
                    if (delta?.reasoning_content) {
                        reasoningContent += delta.reasoning_content;
                        reasoningDiv.style.display = 'block';
                        reasoningBody.textContent = reasoningContent;
                        reasoningBody.scrollTop = reasoningBody.scrollHeight;
                    }
                    // 处理最终回答内容
                    if (delta?.content) {
                        fullContent += delta.content;
                        contentSpan.textContent = fullContent;
                        resultBox.scrollTop = resultBox.scrollHeight;
                    }
                    if (jsonChunk.usage) {
                        usageInfo = jsonChunk.usage;
                    }
                } catch (err) {
                    console.warn("解析sse数据失败", err);
                }
            }
        }

        // 隐藏终止按钮
        stopBtn.style.display = 'none';
    
        let finalResult = fullContent.trim();
        if (aborted) {
            finalResult = fullContent.trim() || '(已终止)';
            contentSpan.textContent = finalResult;
        } else if (finalResult === '') {
            finalResult = '无错误 (已深度扫描,未发现6类错误)';
            contentSpan.textContent = finalResult;
        }
        return { finalContent: finalResult, usage: usageInfo };
    }
    
    async function executeCheck() {
        if (!DEEPSEEK_API_KEY) {
            showCustomAlert('API密钥未初始化,请刷新页面重新输入密码。');
            return;
        }
        const rawText = inputTextarea.value;
        if (!rawText || rawText.trim() === '') {
            showCustomAlert('请填写内容。');
            return;
        }

        // 获取当前选择的功能类型
        const functionType = functionSelector ? functionSelector.value : 'proofread';
        const config = FUNCTION_CONFIG[functionType];

        // 获取当前日期(仅在提示词中使用,界面不显示)
        const now = new Date();
        const currentDateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;

        checkBtn.disabled = true;
        checkBtn.innerHTML = `&#9203; ${config.name}中...`;
        // 更新加载文本
        const loadingText = document.getElementById('loadingText');
        const modelSel = document.getElementById('modelSelector');
        if (loadingText && modelSel) {
            const modelName = modelSel.value === 'deepseek-v4-flash' ? 'DeepSeek V4 Flash' : 'DeepSeek V4 Pro';
            const thinkingOn = document.getElementById('thinkingToggle')?.checked;
            loadingText.textContent = thinkingOn ? `${modelName} 思考中…` : `${modelName} 正在输出…`;
        }
        loadingDiv.style.display = 'flex';
        tokenFooter.style.display = 'none';
        resultBox.innerHTML = `<div style="padding: 20px; text-align: center; color: #2c7da0;">&#9881;&#65039; DeepSeek 流式分析中,请稍候...</div>`;
        copyBtn.style.display = 'none'; // 开始处理时隐藏复制按钮

        try {
            const { finalContent, usage } = await streamDeepSeekCorrection(rawText, functionType, currentDateStr);
            if (resultBox.firstChild) {
                resultBox.firstChild.textContent = finalContent;
            } else {
                resultBox.textContent = finalContent;
            }
            if (usage && (usage.prompt_tokens || usage.completion_tokens)) {
                await updateTokenFooterDisplay(usage);
            } else {
                tokenFooter.style.display = 'none';
            }
            // 成功后显示复制按钮
            if (copyBtn) copyBtn.style.display = 'inline-flex';
        } catch (error) {
            console.error('处理失败', error);
            let errMsg = error.message || String(error);
            if (errMsg.includes('INSUFFICIENT_BALANCE') || errMsg.toLowerCase().includes('insufficient') || errMsg.toLowerCase().includes('balance')) {
                const cleanMsg = errMsg.replace(/^INSUFFICIENT_BALANCE:/, '');
                renderInsufficientBalanceError(cleanMsg || 'DeepSeek 账户余额不足,请充值后使用。');
            } else {
                let friendlyMsg = errMsg;
                if (friendlyMsg.includes('401') || friendlyMsg.includes('API key') || friendlyMsg.includes('Unauthorized')) {
                    friendlyMsg = 'API Key 无效或未授权。可能原因:\n1. 密码错误,请刷新页面重新输入正确密码\n2. API Key 已失效或未授权';
                } else if (friendlyMsg.includes('429')) {
                    friendlyMsg = '请求频率过高,请稍后重试。';
                } else {
                    friendlyMsg = `服务异常: ${friendlyMsg}`;
                }
                resultBox.innerHTML = `<div style="color: #b33; padding: 18px; background: #fff5f5; border-radius: 24px;">&#10060; ${config.name}失败: ${escapeHtml(friendlyMsg)}<br><br>请稍后再试或刷新页面重新输入密码。</div>`;
                tokenFooter.style.display = 'none';
                // 失败后隐藏复制按钮
                if (copyBtn) copyBtn.style.display = 'none';
            }
        } finally {
            checkBtn.disabled = false;
            checkBtn.innerHTML = `&#10024; 开始${config.name}`;
            loadingDiv.style.display = 'none';
        }
    }

    // 更新按钮文本(根据选择的功能)
    function updateButtonText() {
        if (!functionSelector || !checkBtn) return;
        const functionType = functionSelector.value;
        const config = FUNCTION_CONFIG[functionType];
        if (config) {
            checkBtn.innerHTML = '&#10024; 开始' + config.name;
        } else {
            checkBtn.innerHTML = '&#10024; 开始执行';
        }
    }

    // 更新输入框提示(根据选择的功能)
    function updateInputPlaceholder() {
        if (!functionSelector || !inputTextarea) return;

        const selectedFunction = functionSelector.value;
        const templates = loadTemplatesFromStorage();

        if (!templates || templates.length === 0) {
            // 如果没有模板,使用默认placeholder
            inputTextarea.placeholder = '请输入文本内容...';
            return;
        }

        // 根据选择的序号找到对应的模板
        const template = templates.find(t => t.order === parseInt(selectedFunction));

        if (template && template.placeholder) {
            inputTextarea.placeholder = template.placeholder;
        } else {
            inputTextarea.placeholder = '请输入文本内容...';
        }
    }
    
    // 解锁应用(使用用户输入的密码)
    async function unlockApp(password) {
        console.log('=== unlockApp 开始 ===');
        console.log('密码:', password || '(空)');
        const decryptedKey = await getFinalApiKey(password);
        console.log('解密后的key:', decryptedKey ? decryptedKey.substring(0, 15) + '...' : 'null');
        if (!decryptedKey) {
            console.log('>>> 获取密钥失败');
            return { success: false, error: '密钥获取失败' };
        }

        // 先验证key是否正确(通过查询余额,不消耗token)
        DEEPSEEK_API_KEY = decryptedKey;
        console.log('验证key有效性...');
        const balance = await queryAccountBalance();
        console.log('余额查询结果:', balance);
        if (balance === null) {
            // file://协议下fetch可能被CORS阻止,此时如果key格式正确(sk-开头),仍然允许使用
            if (decryptedKey.startsWith('sk-')) {
                console.log('>>> 余额查询失败,但key格式正确(sk-开头),允许使用');
            } else {
                console.log('>>> key验证失败,解密后的key不是sk-开头,可能密码错误');
                DEEPSEEK_API_KEY = '';
                return { success: false, error: '密码错误或key无效' };
            }
        }

        console.log('key验证成功,余额:', balance);
        console.log('开始初始化DOM引用');
        initDomRefs();
        console.log('DOM引用初始化完成,mainAppContainer:', mainAppContainer);
        if (mainAppContainer) {
            console.log('显示主应用容器');
            mainAppContainer.style.display = 'flex';
        } else {
            console.error('mainAppContainer未找到!');
        }

        clearAll();

        // 显示余额信息(在clearAll之后,因为clearAll会隐藏tokenFooter)
        const todayCost = getTodayCost();
        tokenFooter.innerHTML = `
            <div class="token-item">&#128202; 今日消耗: &#165;${todayCost.toFixed(6)} 元</div>
            <div class="token-item">&#128179; 账户余额: &#165;${balance.toFixed(2)} 元</div>
            <div class="token-item">&#129504; 模型: DeepSeek V4 Pro</div>
            <div class="token-item">&#128181; 价格: 输入3元/百万tokens,输出6元/百万tokens</div>

            <button id="importTemplatesIcon" class="key-generator-icon" title="重新导入模版和key">&#128193;</button>
            <button id="importHelpIcon" class="key-generator-icon" title="查看导入说明">&#10067;</button>
            <button id="keyGeneratorIcon" class="key-generator-icon" title="密钥生成器">&#128272;</button>
        `;
        tokenFooter.style.display = 'flex';

        // 动态生成功能选择器选项
        populateFunctionSelector();

        // 添加导入模板按钮事件
        const importTemplatesIcon = document.getElementById('importTemplatesIcon');
        if (importTemplatesIcon) {
            importTemplatesIcon.addEventListener('click', importTemplateFiles);
        }

        // 添加导入说明按钮事件
        const importHelpIcon = document.getElementById('importHelpIcon');
        if (importHelpIcon) {
            importHelpIcon.addEventListener('click', showImportHelp);
        }

        // 添加密钥生成器图标事件
        const keyGeneratorIcon = document.getElementById('keyGeneratorIcon');
        if (keyGeneratorIcon) {
            keyGeneratorIcon.addEventListener('click', showKeyGenerator);
        }

        console.log('添加事件监听器');
        checkBtn.addEventListener('click', executeCheck);
        clearBtn.addEventListener('click', clearAll);
        inputTextarea.addEventListener('keydown', (e) => {
            if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
                e.preventDefault();
                executeCheck();
            }
        });
        functionSelector.addEventListener('change', () => {
            updateButtonText();
            updateInputPlaceholder();
        });
        copyBtn.addEventListener('click', copyResult);
        updateButtonText(); // 初始化按钮文本
        updateInputPlaceholder(); // 初始化输入框提示
        console.log('unlockApp完成');
        return { success: true, balance: balance };
    }
    
    // 密码验证界面
    async function showPasswordPrompt() {
        console.log('=== showPasswordPrompt 开始 ===');

        // 直接检查内嵌数据
        console.log('检查内嵌数据...');
        console.log('EMBEDDED_TEMPLATES 是否定义:', typeof EMBEDDED_TEMPLATES !== 'undefined');
        console.log('EMBEDDED_KEY 是否定义:', typeof EMBEDDED_KEY !== 'undefined');
        if (typeof EMBEDDED_TEMPLATES !== 'undefined') {
            console.log('EMBEDDED_TEMPLATES 长度:', EMBEDDED_TEMPLATES ? EMBEDDED_TEMPLATES.length : 'null');
        }
        if (typeof EMBEDDED_KEY !== 'undefined') {
            console.log('EMBEDDED_KEY 值:', EMBEDDED_KEY ? EMBEDDED_KEY.substring(0, 10) + '...' : 'null');
        }

        // 检查是否有密钥
        const apiKey = getApiKey();
        console.log('getApiKey 返回:', apiKey ? apiKey.substring(0, 10) + '...' : 'null');

        if (!apiKey) {
            console.log('>>> 分支A: 没有密钥,显示导入提示');
            var overlay = document.createElement('div');
            overlay.className = 'password-overlay';

            var modal = document.createElement('div');
            modal.className = 'password-modal';
            modal.style.maxWidth = '520px';

            var lockIcon = document.createElement('div');
            lockIcon.className = 'lock-icon';
            lockIcon.textContent = '&#128193;';
            modal.appendChild(lockIcon);

            var h3 = document.createElement('h3');
            h3.textContent = '需要导入配置';
            modal.appendChild(h3);

            var descP = document.createElement('p');
            descP.style.cssText = 'color:#4a6f7e;margin-bottom:15px;';
            descP.textContent = '请先导入模板和密钥文件,或勾选手动输入密钥';
            modal.appendChild(descP);

            // 文件格式说明
            var formatDiv = document.createElement('div');
            formatDiv.style.cssText = 'background:#F8FAFC;border-radius:12px;padding:15px;font-size:13px;color:#3a5a6a;line-height:1.6;';
            var formatHtml = '<div style="font-weight:600;margin-bottom:10px;">&#128203; 文件格式说明:</div>';
            formatHtml += '<div style="margin-bottom:12px;"><strong>模板文件(任意文件名.txt):</strong><br>第1行:功能名称<br>第2行:输入框提示文字<br>第3行起:AI提示词内容</div>';
            formatHtml += '<div style="margin-bottom:12px;"><strong>密钥文件(key.txt):</strong><br>明文格式:sk-xxx...<br>加密格式:32位加密字符串</div>';
            formatHtml += '<div style="margin-bottom:12px;"><strong>获取API Key:</strong><br>访问 <a href="https://platform.deepseek.com/api_keys" target="_blank" style="color:#2c5a6e;">platform.deepseek.com/api_keys</a> 注册并创建</div>';
            formatDiv.innerHTML = formatHtml;
            modal.appendChild(formatDiv);

            // 手动输入密钥复选框区域
            var manualDiv = document.createElement('div');
            manualDiv.style.cssText = 'margin-top:15px;padding:12px;background:#F8FAFC;border-radius:8px;border:1px solid #d4e3ec;';

            var manualCheckRow = document.createElement('div');
            manualCheckRow.style.cssText = 'display:flex;align-items:center;cursor:pointer;';
            var manualCheckbox = document.createElement('input');
            manualCheckbox.type = 'checkbox';
            manualCheckbox.style.cssText = 'width:18px;height:18px;margin-right:8px;';
            var manualCheckLabel = document.createElement('label');
            manualCheckLabel.style.cssText = 'cursor:pointer;font-weight:600;color:#2c5a6e;';
            manualCheckLabel.textContent = '&#128273; 手动输入密钥(选中后可不导入key.txt)';
            manualCheckRow.appendChild(manualCheckbox);
            manualCheckRow.appendChild(manualCheckLabel);
            manualDiv.appendChild(manualCheckRow);

            // 手动输入区域(默认隐藏)
            var manualInputArea = document.createElement('div');
            manualInputArea.style.cssText = 'display:none;margin-top:10px;';

            var keyInput = document.createElement('input');
            keyInput.type = 'text';
            keyInput.placeholder = '输入API Key(sk-...)';
            keyInput.style.cssText = 'width:100%;padding:10px;border:1px solid #ddd;border-radius:8px;font-size:14px;margin-bottom:8px;box-sizing:border-box;';
            keyInput.autocomplete = 'off';
            manualInputArea.appendChild(keyInput);

            var pwdInput = document.createElement('input');
            pwdInput.type = 'password';
            pwdInput.placeholder = '加密密码(留空则明文存储)';
            pwdInput.style.cssText = 'width:100%;padding:10px;border:1px solid #ddd;border-radius:8px;font-size:14px;margin-bottom:8px;box-sizing:border-box;';
            pwdInput.autocomplete = 'off';
            manualInputArea.appendChild(pwdInput);

            var pwdTip = document.createElement('div');
            pwdTip.style.cssText = 'font-size:12px;color:#888;';
            pwdTip.textContent = '&#128161; 输入密码可加密存储密钥,留空则明文存储';
            manualInputArea.appendChild(pwdTip);

            manualDiv.appendChild(manualInputArea);
            modal.appendChild(manualDiv);

            // 复选框切换显示
            manualCheckbox.addEventListener('change', function() {
                manualInputArea.style.display = manualCheckbox.checked ? 'block' : 'none';
            });
            manualCheckLabel.addEventListener('click', function() {
                manualCheckbox.checked = !manualCheckbox.checked;
                manualInputArea.style.display = manualCheckbox.checked ? 'block' : 'none';
            });

            // 导入按钮
            var importBtn = document.createElement('button');
            importBtn.textContent = '&#128193; 选择文件并导入';
            importBtn.style.cssText = 'margin-top:20px;width:100%;';
            modal.appendChild(importBtn);

            overlay.appendChild(modal);
            document.body.appendChild(overlay);

            importBtn.addEventListener('click', async function() {
                // 检查是否勾选了手动输入密钥
                if (manualCheckbox.checked) {
                    var manualKey = keyInput.value.trim();
                    if (!manualKey) {
                        showCustomAlert('请输入API Key!');
                        return;
                    }
                    if (!manualKey.startsWith('sk-')) {
                        showCustomAlert('API Key应以sk-开头!');
                        return;
                    }
                    var password = pwdInput.value.trim();
                    var finalKey;
                    if (password) {
                        var keyPart = manualKey.substring(3);
                        finalKey = await encryptApiKey(keyPart, password);
                    } else {
                        finalKey = manualKey;
                    }
                    // 设置内嵌密钥
                    if (typeof EMBEDDED_KEY === 'undefined') {
                        window.EMBEDDED_KEY = finalKey;
                    } else {
                        EMBEDDED_KEY = finalKey;
                    }
                    overlay.remove();
                    // 导入模板文件(不需要key.txt)
                    importTemplateFilesWithManualKey();
                } else {
                    // 没有勾选手动输入,走正常导入流程(需要key.txt)
                    overlay.remove();
                    importTemplateFiles();
                }
            });

            return;
        }

        // 检查是否有模板
        const templates = loadTemplatesFromStorage();
        console.log('loadTemplatesFromStorage 返回:', templates ? templates.length + '个模板' : 'null');

        if (!templates || templates.length === 0) {
            console.log('>>> 分支B: 没有模板,显示导入提示');
            var overlay = document.createElement('div');
            overlay.className = 'password-overlay';

            var modal = document.createElement('div');
            modal.className = 'password-modal';
            modal.style.maxWidth = '520px';

            var lockIcon = document.createElement('div');
            lockIcon.className = 'lock-icon';
            lockIcon.textContent = '&#128193;';
            modal.appendChild(lockIcon);

            var h3 = document.createElement('h3');
            h3.textContent = '需要导入模板';
            modal.appendChild(h3);

            var descP = document.createElement('p');
            descP.style.cssText = 'color:#4a6f7e;margin-bottom:15px;';
            descP.textContent = '已检测到密钥,但缺少模板文件';
            modal.appendChild(descP);

            var formatDiv = document.createElement('div');
            formatDiv.style.cssText = 'background:#F8FAFC;border-radius:12px;padding:15px;font-size:13px;color:#3a5a6a;line-height:1.6;';
            formatDiv.innerHTML = '<div style="font-weight:600;margin-bottom:10px;">&#128203; 模板文件格式:</div><div style="margin-bottom:12px;"><strong>文件命名:任意文件名.txt</strong><br>第1行:功能名称<br>第2行:输入框提示文字<br>第3行起:AI提示词内容</div>';
            modal.appendChild(formatDiv);

            var importBtn = document.createElement('button');
            importBtn.textContent = '&#128193; 选择文件并导入';
            importBtn.style.cssText = 'margin-top:20px;';
            modal.appendChild(importBtn);

            overlay.appendChild(modal);
            document.body.appendChild(overlay);

            importBtn.addEventListener('click', importTemplateFiles);

            return;
        }

        // 如果密钥是明文(sk-开头),直接解锁,不需要密码
        if (apiKey.startsWith('sk-')) {
            console.log('>>> 分支C: 明文密钥,直接解锁');
            unlockApp('');
            return;
        }

        // 如果密钥是加密的,显示密码输入界面
        console.log('>>> 分支D: 加密密钥,显示密码输入界面');
        var pwdOverlay = document.createElement('div');
        pwdOverlay.className = 'password-overlay';
        pwdOverlay.innerHTML = `
            <div class="password-modal">
                <div class="lock-icon">&#128274;</div>
                <h3>验证身份</h3>
                <p style="color: #4a6f7e;">请输入访问密码以使用AI助手</p>
                <input type="password" id="accessPassword" placeholder="密码" autocomplete="new-password" readonly>
                <button id="submitPasswordBtn">确认</button>
                <div id="pwdErrorMsg" class="error-msg"></div>
            </div>
        `;
        document.body.appendChild(pwdOverlay);
        var pwdInput = pwdOverlay.querySelector('#accessPassword');
        var submitBtn = pwdOverlay.querySelector('#submitPasswordBtn');
        var errorSpan = pwdOverlay.querySelector('#pwdErrorMsg');

        const attemptUnlock = async () => {
            const entered = pwdInput.value.trim();
            if (!entered) {
                errorSpan.textContent = '请输入密码。';
                pwdInput.focus();
                return;
            }
            // 显示加载提示
            submitBtn.textContent = '验证中...';
            submitBtn.disabled = true;
            errorSpan.textContent = '';
            try {
                const result = await unlockApp(entered);
                if (result.success) {
                    pwdOverlay.remove();
                } else {
                    errorSpan.textContent = result.error || '密码错误,无法访问。';
                    submitBtn.textContent = '确认';
                    submitBtn.disabled = false;
                    pwdInput.value = '';
                    pwdInput.focus();
                }
            } catch(e) {
                console.error('解锁失败', e);
                errorSpan.textContent = '解锁失败,请重试。';
                submitBtn.textContent = '确认';
                submitBtn.disabled = false;
                pwdInput.focus();
            }
        };
        submitBtn.addEventListener('click', attemptUnlock);
        pwdInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') attemptUnlock();
        });
        pwdInput.focus();
    }

    // ===================== 导入说明弹窗 =====================

    // 显示导入说明弹窗
    function showImportHelp() {
        const overlay = document.createElement('div');
        overlay.className = 'key-generator-overlay';
        overlay.innerHTML = `
            <div class="key-generator-modal" style="max-width: 550px;">
                <h3>&#128203; 导入说明</h3>
                <div style="background: #F8FAFC; border-radius: 12px; padding: 18px; font-size: 14px; color: #3a5a6a; line-height: 1.7; margin-top: 15px;">
                    <div style="font-weight: 600; margin-bottom: 12px; color: #2c5a6e;">&#128193; 模板文件格式(任意文件名.txt):</div>
                    <div style="margin-bottom: 15px; padding-left: 10px;">
                        第1行:功能名称(如:文本纠错)<br>
                        第2行:输入框提示文字<br>
                        第3行起:AI提示词内容
                    </div>
                    <div style="font-weight: 600; margin-bottom: 12px; color: #2c5a6e;">&#128273; 密钥文件格式(key.txt):</div>
                    <div style="margin-bottom: 15px; padding-left: 10px;">
                        <strong>明文格式:</strong>sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
                        <strong>加密格式:</strong>32位加密字符串(需密码解密)
                    </div>
                    <div style="font-weight: 600; margin-bottom: 12px; color: #2c5a6e;">&#129302; DeepSeek API 信息:</div>
                    <div style="margin-bottom: 15px; padding-left: 10px;">
                        <strong>模型:</strong><br>
                        deepseek-v4-pro — 精准模式(输入3元/输出6元 每百万tokens)<br>
                        deepseek-v4-flash — 快速模式(输入1元/输出2元 每百万tokens)<br>
                        <strong>思考模式:</strong>默认启用,模型先思考再回答更准确,但消耗更多tokens<br>
                        <strong>API地址:</strong>[url]https://api.deepseek.com/v1/chat/completions<br>[/url]
                        <strong>获取API Key:</strong>访问 <a href="https://platform.deepseek.com/api_keys" target="_blank" style="color:#2c5a6e;">platform.deepseek.com/api_keys</a> 注册并创建
                    </div>
                    <div style="font-weight: 600; margin-bottom: 12px; color: #2c5a6e;">&#128161; 导入步骤:</div>
                    <div style="padding-left: 10px;">
                        1. 点击 &#128193; 导入按钮<br>
                        2. 选择所有文件(key.txt + 所有模板txt)<br>
                        3. 勾选需要的模板和密钥<br>
                        4. 生成可独立运行的内嵌版HTML文件
                    </div>
                    <div style="color: #2c5a6e; font-size: 13px; margin-top: 15px; padding: 10px; background: #F8FAFC; border-radius: 8px;">
                        &#9888;&#65039; 必须包含至少一个模板文件,密钥可导入key.txt或手动输入<br>
                        &#128161; 导入后生成内嵌版HTML,可直接双击运行
                    </div>
                </div>
                <div class="key-generator-buttons" style="margin-top: 20px;">
                    <button class="btn-generate" id="importNowBtn">&#128193; 立即导入</button>
                    <button class="btn-close-generator" id="closeHelpBtn">关闭</button>
                </div>
            </div>
        `;
        document.body.appendChild(overlay);

        // 立即导入按钮事件
        const importNowBtn = overlay.querySelector('#importNowBtn');
        importNowBtn.addEventListener('click', () => {
            overlay.remove();
            importTemplateFiles();
        });

        // 关闭按钮事件
        const closeBtn = overlay.querySelector('#closeHelpBtn');
        closeBtn.addEventListener('click', () => {
            overlay.remove();
        });

        // 点击overlay背景关闭
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                overlay.remove();
            }
        });
    }

    // ===================== 生成内嵌版HTML功能 =====================

    // 执行导出(生成内嵌版HTML文件)
    function doExport(selectedTemplates, apiKey) {
        console.log('生成内嵌版,模板数量:', selectedTemplates.length);

        // 克隆整个文档,避免修改原始DOM
        var clonedDoc = document.documentElement.cloneNode(true);

        // 从克隆的body中移除所有非原始元素(浏览器扩展注入的按钮等)
        // 原始body只包含: #mainApp 和 <script>
        var clonedBody = clonedDoc.querySelector('body');
        var knownIds = ['mainApp'];
        var childrenToRemove = [];
        for (var i = 0; i < clonedBody.children.length; i++) {
            var child = clonedBody.children[i];
            var isKnown = false;
            // 保留 mainApp
            if (child.id && knownIds.indexOf(child.id) !== -1) {
                isKnown = true;
            }
            // 保留 <script> 标签
            if (child.tagName && child.tagName.toLowerCase() === 'script') {
                isKnown = true;
            }
            if (!isKnown) {
                childrenToRemove.push(child);
            }
        }
        childrenToRemove.forEach(function(el) {
            el.parentNode.removeChild(el);
        });

        // 获取清理后的HTML内容
        var currentHtml = clonedDoc.outerHTML;

        // 使用标记定位内嵌数据区域
        var markerStart = '// ========== 内嵌数据标记开始 ==========';
        var markerEnd = '// ========== 内嵌数据标记结束 ==========';

        var startPos = currentHtml.indexOf(markerStart);
        var endPos = currentHtml.indexOf(markerEnd);

        if (startPos === -1 || endPos === -1) {
            showCustomAlert('HTML结构错误,找不到内嵌数据标记!');
            return;
        }

        // 构建新的内嵌数据
        var embeddedData = markerStart + '\n';

        if (apiKey) {
            embeddedData = embeddedData + '    var EMBEDDED_KEY = "' + apiKey + '";\n';
        }

        embeddedData = embeddedData + '    var EMBEDDED_TEMPLATES = ' + JSON.stringify(selectedTemplates) + ';\n';
        embeddedData = embeddedData + '    ' + markerEnd;

        // 替换标记区域的内容
        var beforeMarker = currentHtml.substring(0, startPos);
        var afterMarker = currentHtml.substring(endPos + markerEnd.length);
        var newHtml = beforeMarker + embeddedData + afterMarker;

        // 创建下载
        var blob = new Blob([newHtml], { type: 'text/html;charset=utf-8' });
        var url = URL.createObjectURL(blob);
        var a = document.createElement('a');
        a.href = url;
        a.download = 'DeepSeek_内嵌版.html';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);

        // 显示成功弹窗
        showCustomAlert('生成成功!\n\n文件名:DeepSeek_内嵌版.html\n附带模板:' + selectedTemplates.length + '个\n附带密钥:' + (apiKey ? '是' : '否') + '\n\n可直接双击运行,无需外部文件');
    }

    // ===================== 密钥生成器功能 =====================

    // 显示密钥生成器弹窗
    function showKeyGenerator() {
        const overlay = document.createElement('div');
        overlay.className = 'key-generator-overlay';
        overlay.innerHTML = `
            <div class="key-generator-modal">
                <h3>&#128272; 密钥生成器</h3>
                <label>密码</label>
                <input type="password" id="generatorPassword" placeholder="输入密码(如:123456)" autocomplete="new-password">

                <label>API Key</label>
                <input type="text" id="generatorApiKey" placeholder="输入API Key(格式:sk-xxxxxxxx...)">

                <div class="key-generator-result" id="generatorResult" style="display: none;">
                    <div class="key-generator-result-label">加密后的密钥:</div>
                    <div class="key-generator-result-value" id="generatorResultValue"></div>
                </div>

                <div class="key-generator-buttons">
                    <button class="btn-generate" id="generateBtn">生成加密密钥</button>
                    <button class="btn-close-generator" id="closeGeneratorBtn">关闭</button>
                </div>
            </div>
        `;
        document.body.appendChild(overlay);

        // 生成按钮事件
        const generateBtn = overlay.querySelector('#generateBtn');
        generateBtn.addEventListener('click', async () => {
            const password = overlay.querySelector('#generatorPassword').value.trim();
            const apiKey = overlay.querySelector('#generatorApiKey').value.trim();

            if (!password) {
                showCustomAlert('请输入密码');
                return;
            }

            if (!apiKey) {
                showCustomAlert('请输入API Key');
                return;
            }

            if (!apiKey.startsWith('sk-')) {
                showCustomAlert('API Key格式错误,应以 sk- 开头');
                return;
            }

            // 提取sk-后面的部分
            const keyPart = apiKey.substring(3);

            if (keyPart.length !== 32) {
                showCustomAlert('API Key格式错误,sk-后面应为32位字符');
                return;
            }

            try {
                generateBtn.textContent = '生成中...';
                generateBtn.disabled = true;

                // 使用相同的加密算法
                const encryptedKey = await encryptApiKey(keyPart, password);

                // 显示结果
                const resultDiv = overlay.querySelector('#generatorResult');
                const resultValue = overlay.querySelector('#generatorResultValue');
                resultDiv.style.display = 'block';
                resultValue.textContent = encryptedKey;

                generateBtn.textContent = '生成加密密钥';
                generateBtn.disabled = false;

                // 添加复制按钮
                const copyBtn = document.createElement('button');
                copyBtn.textContent = '&#128203; 复制加密密钥';
                copyBtn.className = 'btn-generate';
                copyBtn.style.marginTop = '10px';
                copyBtn.addEventListener('click', async () => {
                    try {
                        await navigator.clipboard.writeText(encryptedKey);
                        copyBtn.textContent = '&#9989; 已复制';
                        setTimeout(() => {
                            copyBtn.textContent = '&#128203; 复制加密密钥';
                        }, 2000);
                    } catch (e) {
                        showCustomAlert('复制失败,请手动复制');
                    }
                });
                resultDiv.appendChild(copyBtn);

            } catch (e) {
                console.error('生成失败:', e);
                showCustomAlert('生成失败:' + e.message);
                generateBtn.textContent = '生成加密密钥';
                generateBtn.disabled = false;
            }
        });

        // 关闭按钮事件
        const closeBtn = overlay.querySelector('#closeGeneratorBtn');
        closeBtn.addEventListener('click', () => {
            overlay.remove();
        });

        // 点击overlay背景关闭
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                overlay.remove();
            }
        });
    }

    // 加密API Key(使用相同的算法)
    async function encryptApiKey(keyPart, password) {
        // 对密码进行固定12624次迭代哈希
        let hash = await sha256Hash(password);
        const iterations = 12624;
        for (let i = 0; i < iterations; i++) {
            hash = await sha256Hash(hash);
        }

        // 36进制加密
        let encrypted = '';
        for (let i = 0; i < keyPart.length; i++) {
            const keyChar = keyPart[i];
            const hashChar = hash[i];

            const keyVal = parseInt(keyChar, 36);
            const hashVal = parseInt(hashChar, 36);

            const sum = (keyVal + hashVal) % 36;
            encrypted += sum.toString(36);
        }

        return encrypted;
    }

    // 密钥生成器按钮事件
    document.addEventListener('DOMContentLoaded', () => {
        const keyGeneratorIcon = document.getElementById('keyGeneratorIcon');
        if (keyGeneratorIcon) {
            keyGeneratorIcon.addEventListener('click', showKeyGenerator);
        }
    });

    window.addEventListener('DOMContentLoaded', () => {
        showPasswordPrompt();
    });
</script>
</body>
</html>

DeepSeek文本生成单html生成器.7z

24.51 KB, 下载次数: 22, 下载积分: 吾爱币 -1 CB

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
wzs0777 发表于 2026-6-8 21:27
怎么做成小程序的
3#
 楼主| 858983646 发表于 2026-6-8 22:12 |楼主
wzs0777 发表于 2026-6-8 21:27
怎么做成小程序的

不是小程序啊,这个是手机打开html文件的截图。不过确实可以做apk的
4#
Sandyang 发表于 2026-6-8 23:31
不错不错,但我真的一分钱都不想给,就想白嫖呢
5#
wufashihu 发表于 2026-6-9 01:40
这个能加其他模型吗?
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-6-9 04:03

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表