[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: "�";
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>🎯 选择功能</label>
<select id="functionSelector" class="function-selector">
<!-- 选项由JavaScript动态生成 -->
</select>
</div>
<div class="function-selector-wrapper" style="margin-top:8px;">
<label>🤖 模型</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;">🧠 思考模式</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>📝 文本内容</label>
<textarea id="inputText" class="textarea-input" placeholder="请输入文本内容..."></textarea>
</div>
<div class="right-panel">
<div class="result-header">
<h2>🔍 输出结果</h2>
<div class="button-group">
<button id="copyBtn" class="btn-copy" style="display: none;">📋 复制结果</button>
<button id="checkBtn" class="btn-primary">✨ 开始执行</button>
<button id="stopBtn" class="btn-secondary" style="display: none; background:#98D3EF; color:white;">⏹️ 终止</button>
<button id="clearBtn" class="btn-secondary">🗑️ 清空内容</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;">📎</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 = '📦 导入并生成内嵌版';
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 = '🔄 已自动去重 ' + 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 = '📋 已有模板(' + 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 = '📥 新导入模板(' + 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 = '🔑 密钥选择(必选):';
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 = '💡 输入密码可加密存储密钥,留空则明文存储';
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 = '💡 生成的内嵌版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 = '📦 生成内嵌版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;">📎</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 = '✅ 已复制';
copyBtn.classList.add('btn-copy-success');
// 2秒后恢复原状态
setTimeout(() => {
copyBtn.innerHTML = '📋 复制结果';
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 = '✅ 已复制';
copyBtn.classList.add('btn-copy-success');
setTimeout(() => {
copyBtn.innerHTML = '📋 复制结果';
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">🧠 模型: ${modelLabel}</div>
<div class="token-item">📥 输入 tokens: ${promptTokens.toLocaleString()}</div>
<div class="token-item">📤 输出 tokens: ${completionTokens.toLocaleString()}</div>
<div class="token-item">🔁 总计 tokens: ${totalTokens.toLocaleString()}</div>
<div class="token-item">💰 本次费用: ¥${totalCost.toFixed(6)} 元</div>
<div class="token-item">📊 今日消耗: ¥${todayTotalCost.toFixed(6)} 元</div>
${balance !== null ? `<div class="token-item">💳 账户余额: ¥${balance.toFixed(2)} 元</div>` : ''}
<div class="token-item">💵 价格: 输入${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>⚠️ API 请求失败:账户余额不足或授权受限</strong>
<div style="margin-top: 8px; color: #c4450c;">${escapeHtml(originalMsg)}</div>
<div style="margin-top: 12px; font-size: 13px;">
🔍 请前往 <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>🧠 思考过程</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 = '▶';
}
});
// 最终回答显示区域
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 = `⏳ ${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;">⚙️ 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;">❌ ${config.name}失败: ${escapeHtml(friendlyMsg)}<br><br>请稍后再试或刷新页面重新输入密码。</div>`;
tokenFooter.style.display = 'none';
// 失败后隐藏复制按钮
if (copyBtn) copyBtn.style.display = 'none';
}
} finally {
checkBtn.disabled = false;
checkBtn.innerHTML = `✨ 开始${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 = '✨ 开始' + config.name;
} else {
checkBtn.innerHTML = '✨ 开始执行';
}
}
// 更新输入框提示(根据选择的功能)
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">📊 今日消耗: ¥${todayCost.toFixed(6)} 元</div>
<div class="token-item">💳 账户余额: ¥${balance.toFixed(2)} 元</div>
<div class="token-item">🧠 模型: DeepSeek V4 Pro</div>
<div class="token-item">💵 价格: 输入3元/百万tokens,输出6元/百万tokens</div>
<button id="importTemplatesIcon" class="key-generator-icon" title="重新导入模版和key">📁</button>
<button id="importHelpIcon" class="key-generator-icon" title="查看导入说明">❓</button>
<button id="keyGeneratorIcon" class="key-generator-icon" title="密钥生成器">🔐</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 = '📁';
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;">📋 文件格式说明:</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 = '🔑 手动输入密钥(选中后可不导入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 = '💡 输入密码可加密存储密钥,留空则明文存储';
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 = '📁 选择文件并导入';
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 = '📁';
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;">📋 模板文件格式:</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 = '📁 选择文件并导入';
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">🔒</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>📋 导入说明</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;">📁 模板文件格式(任意文件名.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;">🔑 密钥文件格式(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;">🤖 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;">💡 导入步骤:</div>
<div style="padding-left: 10px;">
1. 点击 📁 导入按钮<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;">
⚠️ 必须包含至少一个模板文件,密钥可导入key.txt或手动输入<br>
💡 导入后生成内嵌版HTML,可直接双击运行
</div>
</div>
<div class="key-generator-buttons" style="margin-top: 20px;">
<button class="btn-generate" id="importNowBtn">📁 立即导入</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>🔐 密钥生成器</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 = '📋 复制加密密钥';
copyBtn.className = 'btn-generate';
copyBtn.style.marginTop = '10px';
copyBtn.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(encryptedKey);
copyBtn.textContent = '✅ 已复制';
setTimeout(() => {
copyBtn.textContent = '📋 复制加密密钥';
}, 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>