Files
Snap-Solver/templates/index.html

463 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Safari兼容性设置 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="mobile-web-app-capable" content="yes">
<title>Snap Solver</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<script>
// 帮助Safari调试
window.onerror = function(message, source, lineno, colno, error) {
console.error("Error caught: ", message, "at", source, ":", lineno, ":", colno, error);
return false;
};
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
<!-- 添加Markdown解析库 -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- 添加代码高亮库 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.8.0/styles/github.min.css">
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.8.0/highlight.min.js"></script>
</head>
<body class="app-container">
<header class="app-header">
<div class="header-content">
<h1>Snap Solver</h1>
<div id="connectionStatus" class="status disconnected">未连接</div>
<div class="header-buttons">
<button id="captureBtn" class="btn-icon capture-btn-highlight" title="截图" disabled>
<i class="fas fa-camera"></i>
</button>
<button id="themeToggle" class="btn-icon" title="切换主题">
<i class="fas fa-moon"></i>
</button>
<button id="settingsToggle" class="btn-icon" title="设置">
<i class="fas fa-cog"></i>
</button>
</div>
</div>
</header>
<!-- 更新通知条 -->
<div id="updateNotice" class="update-notice hidden">
<div class="update-notice-content">
<i class="fas fa-arrow-alt-circle-up"></i>
<span>发现新版本: <span id="updateVersion"></span></span>
<a id="updateLink" href="#" target="_blank" class="update-link">查看更新</a>
<button id="closeUpdateNotice" class="btn-icon">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<main class="app-main">
<div class="content-panel">
<div class="capture-section">
<div id="emptyState" class="empty-state">
<i class="fas fa-camera-retro"></i>
<h3>准备好开始了吗?</h3>
<p>点击顶部状态栏的"相机"图标捕获屏幕然后使用AI分析图像或提取文本。您可以截取数学题、代码或任何需要帮助的内容。</p>
<p class="star-prompt">如果觉得好用,别忘了给项目点个 Star ⭐</p>
</div>
<div id="imagePreview" class="image-preview hidden">
<div class="image-container">
<img id="screenshotImg" src="" alt="截图预览">
</div>
<div class="analysis-button">
<div class="button-group">
<button id="sendToClaude" class="btn-action hidden">
<i class="fas fa-robot"></i>
<span>发送至AI</span>
</button>
<button id="extractText" class="btn-action hidden">
<i class="fas fa-font"></i>
<span>提取文本</span>
</button>
</div>
</div>
<textarea id="extractedText" class="extracted-text-area hidden" rows="6" placeholder="提取的文本将显示在这里..."></textarea>
<button id="sendExtractedText" class="btn-action send-text-btn hidden">
<i class="fas fa-paper-plane"></i>
<span>发送文本至AI</span>
</button>
</div>
</div>
<div id="claudePanel" class="claude-panel hidden">
<div class="panel-header">
<div class="header-title">
<h2><i class="fas fa-chart-bar"></i> 分析结果</h2>
<div class="analysis-indicator">
<div class="progress-line"></div>
<div class="status-text">准备中</div>
</div>
<button id="stopGenerationBtn" class="btn-stop-generation" title="停止生成">
<i class="fas fa-stop"></i>
</button>
</div>
<button class="btn-icon" id="closeClaudePanel" title="关闭分析结果">
<i class="fas fa-times"></i>
</button>
</div>
<div id="thinkingSection" class="thinking-section hidden">
<div class="thinking-header" id="thinkingToggle" title="点击查看AI思考过程">
<div class="thinking-title">
<i class="fas fa-brain"></i>
<h3>思考过程<span class="dots-animation"></span></h3>
</div>
<button class="toggle-btn">
<i class="fas fa-chevron-down"></i>
</button>
</div>
<div id="thinkingContent" class="thinking-content collapsed"></div>
</div>
<div id="responseContent" class="response-content"></div>
</div>
</div>
<aside id="settingsPanel" class="settings-panel">
<div class="settings-header">
<h2><i class="fas fa-cog"></i> 设置</h2>
<button id="closeSettings" class="btn-icon">
<i class="fas fa-times"></i>
</button>
</div>
<div class="settings-content">
<!-- 1. 首先是最常用的AI模型选择部分 -->
<div class="settings-section model-settings">
<h3><i class="fas fa-robot"></i> 模型设置</h3>
<div class="setting-group">
<label for="modelSelect"><i class="fas fa-microchip"></i> AI模型</label>
<select id="modelSelect">
<option value="gpt-4o">OpenAI - GPT-4o</option>
<option value="gpt-4-turbo">OpenAI - GPT-4 Turbo</option>
<option value="gpt-4">OpenAI - GPT-4</option>
<option value="gpt-3.5-turbo">OpenAI - GPT-3.5 Turbo</option>
<option value="claude-3-opus-20240229">Anthropic - Claude 3 Opus</option>
<option value="claude-3-sonnet-20240229">Anthropic - Claude 3 Sonnet</option>
<option value="claude-3-haiku-20240307" selected>Anthropic - Claude 3 Haiku</option>
<option value="deepseek-v2">DeepSeek - DeepSeek-V2</option>
<option value="qwen-max">Alibaba - 通义千问 Max</option>
</select>
<div id="modelVersionInfo" class="model-version-info">
<i class="fas fa-info-circle"></i> <span>版本: <span id="modelVersionText">-</span></span>
</div>
</div>
<div class="setting-group">
<label for="maxTokens"><i class="fas fa-text-width"></i> 最大输出Token</label>
<input type="number" id="maxTokens" min="1000" max="128000" step="1000" value="8192">
</div>
<div class="setting-group reasoning-setting-group">
<label for="reasoningDepth"><i class="fas fa-brain"></i> 推理深度</label>
<select id="reasoningDepth">
<option value="standard">标准模式 (快速响应)</option>
<option value="extended">深度思考 (更详细分析)</option>
</select>
</div>
<div class="setting-group think-budget-group">
<label for="thinkBudgetPercent"><i class="fas fa-hourglass-half"></i> 思考预算占比</label>
<input type="range" id="thinkBudgetPercent" min="10" max="80" step="5" value="50">
<span id="thinkBudgetPercentValue">50%</span>
</div>
<div class="setting-group">
<label for="temperature"><i class="fas fa-thermometer-half"></i> 温度</label>
<input type="range" id="temperature" min="0" max="1" step="0.1" value="0.7">
<span id="temperatureValue">0.7</span>
</div>
<div class="setting-group">
<label for="systemPrompt"><i class="fas fa-comment-alt"></i> 系统提示词</label>
<textarea id="systemPrompt" rows="3">您是一位专业的问题解决专家。请逐步分析问题,找出问题所在,并提供详细的解决方案。始终使用用户偏好的语言回答。</textarea>
</div>
</div>
<!-- 2. 所有API密钥集中在一个区域 -->
<div class="settings-section api-key-settings">
<h3><i class="fas fa-key"></i> API密钥设置</h3>
<!-- API密钥状态显示与编辑区域 -->
<div class="api-keys-list" id="apiKeysList">
<div class="api-key-status">
<span class="key-name">Anthropic API:</span>
<div class="key-status-wrapper">
<!-- 显示状态 -->
<div class="key-display">
<span id="AnthropicApiKeyStatus" class="key-status" data-key="AnthropicApiKey">未设置</span>
<button class="btn-icon edit-api-key" data-key-type="AnthropicApiKey" title="编辑此密钥">
<i class="fas fa-edit"></i>
</button>
</div>
<!-- 编辑状态 -->
<div class="key-edit hidden">
<input type="password" class="key-input" data-key-type="AnthropicApiKey" placeholder="输入 Anthropic API key">
<button class="btn-icon toggle-visibility">
<i class="fas fa-eye"></i>
</button>
<button class="btn-icon save-api-key" data-key-type="AnthropicApiKey" title="保存密钥">
<i class="fas fa-save"></i>
</button>
</div>
</div>
</div>
<div class="api-key-status">
<span class="key-name">OpenAI API:</span>
<div class="key-status-wrapper">
<!-- 显示状态 -->
<div class="key-display">
<span id="OpenaiApiKeyStatus" class="key-status" data-key="OpenaiApiKey">未设置</span>
<button class="btn-icon edit-api-key" data-key-type="OpenaiApiKey" title="编辑此密钥">
<i class="fas fa-edit"></i>
</button>
</div>
<!-- 编辑状态 -->
<div class="key-edit hidden">
<input type="password" class="key-input" data-key-type="OpenaiApiKey" placeholder="输入 OpenAI API key">
<button class="btn-icon toggle-visibility">
<i class="fas fa-eye"></i>
</button>
<button class="btn-icon save-api-key" data-key-type="OpenaiApiKey" title="保存密钥">
<i class="fas fa-save"></i>
</button>
</div>
</div>
</div>
<div class="api-key-status">
<span class="key-name">DeepSeek API:</span>
<div class="key-status-wrapper">
<!-- 显示状态 -->
<div class="key-display">
<span id="DeepseekApiKeyStatus" class="key-status" data-key="DeepseekApiKey">未设置</span>
<button class="btn-icon edit-api-key" data-key-type="DeepseekApiKey" title="编辑此密钥">
<i class="fas fa-edit"></i>
</button>
</div>
<!-- 编辑状态 -->
<div class="key-edit hidden">
<input type="password" class="key-input" data-key-type="DeepseekApiKey" placeholder="输入 DeepSeek API key">
<button class="btn-icon toggle-visibility">
<i class="fas fa-eye"></i>
</button>
<button class="btn-icon save-api-key" data-key-type="DeepseekApiKey" title="保存密钥">
<i class="fas fa-save"></i>
</button>
</div>
</div>
</div>
<div class="api-key-status">
<span class="key-name">Alibaba API:</span>
<div class="key-status-wrapper">
<!-- 显示状态 -->
<div class="key-display">
<span id="AlibabaApiKeyStatus" class="key-status" data-key="AlibabaApiKey">未设置</span>
<button class="btn-icon edit-api-key" data-key-type="AlibabaApiKey" title="编辑此密钥">
<i class="fas fa-edit"></i>
</button>
</div>
<!-- 编辑状态 -->
<div class="key-edit hidden">
<input type="password" class="key-input" data-key-type="AlibabaApiKey" placeholder="输入 Alibaba API key">
<button class="btn-icon toggle-visibility">
<i class="fas fa-eye"></i>
</button>
<button class="btn-icon save-api-key" data-key-type="AlibabaApiKey" title="保存密钥">
<i class="fas fa-save"></i>
</button>
</div>
</div>
</div>
<div class="api-key-status">
<span class="key-name">Mathpix App ID:</span>
<div class="key-status-wrapper">
<!-- 显示状态 -->
<div class="key-display">
<span id="MathpixAppIdStatus" class="key-status" data-key="MathpixAppId">未设置</span>
<button class="btn-icon edit-api-key" data-key-type="MathpixAppId" title="编辑此密钥">
<i class="fas fa-edit"></i>
</button>
</div>
<!-- 编辑状态 -->
<div class="key-edit hidden">
<input type="password" class="key-input" data-key-type="MathpixAppId" placeholder="输入 Mathpix App ID">
<button class="btn-icon toggle-visibility">
<i class="fas fa-eye"></i>
</button>
<button class="btn-icon save-api-key" data-key-type="MathpixAppId" title="保存密钥">
<i class="fas fa-save"></i>
</button>
</div>
</div>
</div>
<div class="api-key-status">
<span class="key-name">Mathpix App Key:</span>
<div class="key-status-wrapper">
<!-- 显示状态 -->
<div class="key-display">
<span id="MathpixAppKeyStatus" class="key-status" data-key="MathpixAppKey">未设置</span>
<button class="btn-icon edit-api-key" data-key-type="MathpixAppKey" title="编辑此密钥">
<i class="fas fa-edit"></i>
</button>
</div>
<!-- 编辑状态 -->
<div class="key-edit hidden">
<input type="password" class="key-input" data-key-type="MathpixAppKey" placeholder="输入 Mathpix App Key">
<button class="btn-icon toggle-visibility">
<i class="fas fa-eye"></i>
</button>
<button class="btn-icon save-api-key" data-key-type="MathpixAppKey" title="保存密钥">
<i class="fas fa-save"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 3. 不常用的其他设置放在后面 -->
<div class="settings-section proxy-settings-section">
<h3><i class="fas fa-cog"></i> 其他设置</h3>
<div class="setting-group">
<label for="language"><i class="fas fa-language"></i> 语言</label>
<input type="text" id="language" value="中文" placeholder="输入首选语言">
</div>
<div class="setting-group">
<label class="checkbox-label">
<input type="checkbox" id="proxyEnabled">
<span>启用 VPN 代理</span>
</label>
</div>
<div id="proxySettings" class="proxy-settings">
<div class="setting-group">
<label for="proxyHost"><i class="fas fa-server"></i> 代理主机</label>
<input type="text" id="proxyHost" value="127.0.0.1" placeholder="输入代理主机">
</div>
<div class="setting-group">
<label for="proxyPort"><i class="fas fa-plug"></i> 代理端口</label>
<input type="number" id="proxyPort" value="4780" placeholder="输入代理端口">
</div>
</div>
</div>
<!-- 设置面板底部按钮 -->
<div class="settings-footer">
<button id="resetSettings" class="btn btn-secondary">
<i class="fas fa-undo"></i> 重置设置
</button>
<button id="saveSettings" class="btn btn-primary">
<i class="fas fa-save"></i> 保存设置
</button>
</div>
</div>
</aside>
</main>
<div id="cropContainer" class="crop-container hidden">
<div class="crop-wrapper">
<div class="crop-area"></div>
</div>
<div class="crop-actions">
<button id="cropCancel" class="btn-secondary">
<i class="fas fa-times"></i>
<span>取消</span>
</button>
<button id="cropConfirm" class="btn-primary">
<i class="fas fa-check"></i>
<span>确认</span>
</button>
</div>
</div>
<div id="toastContainer" class="toast-container"></div>
<footer class="app-footer">
<div class="footer-content">
<div class="footer-text">
<span>© 2024 Snap-Solver <span class="version-badge">v<span id="currentVersion">{{ update_info.current_version }}</span></span></span>
</div>
<div class="footer-links">
<a href="https://github.com/Zippland/Snap-Solver/" target="_blank" class="footer-link">
<span class="star-icon"></span>
<span>GitHub</span>
</a>
<a href="https://www.xiaohongshu.com/user/profile/623e8b080000000010007721?xsec_token=YBdeHZTp_aVwi1Ijmras5CgQC6pxlpd4RmozT8Hr_-NCA%3D&xsec_source=app_share&xhsshare=CopyLink&appuid=623e8b080000000010007721&apptime=1742201089&share_id=a2704ab48e2c4e1aa321ce63168811b5&share_channel=copy_link" target="_blank" class="footer-link xiaohongshu-link">
<i class="fas fa-book"></i>
<span>小红书</span>
</a>
</div>
</div>
</footer>
<!-- 确保按照正确的顺序加载脚本 -->
<!-- 先加载UI管理器确保它能在DOM加载完成后初始化 -->
<script src="{{ url_for('static', filename='js/ui.js') }}"></script>
<!-- 然后加载设置管理器它依赖UI管理器 -->
<script src="{{ url_for('static', filename='js/settings.js') }}"></script>
<!-- 最后加载主应用逻辑 -->
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
<!-- 更新检查初始化 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// 初始化更新检查
try {
const updateInfo = JSON.parse('{{ update_info|tojson|safe }}');
if (updateInfo && updateInfo.has_update) {
showUpdateNotice(updateInfo);
}
// 24小时后再次检查更新
setTimeout(checkForUpdates, 24 * 60 * 60 * 1000);
} catch (error) {
console.error('更新检查初始化失败:', error);
}
});
function showUpdateNotice(updateInfo) {
const updateNotice = document.getElementById('updateNotice');
const updateVersion = document.getElementById('updateVersion');
const updateLink = document.getElementById('updateLink');
if (updateInfo.latest_version) {
updateVersion.textContent = updateInfo.latest_version;
}
if (updateInfo.release_url) {
updateLink.href = updateInfo.release_url;
} else {
updateLink.href = 'https://github.com/Zippland/Snap-Solver/releases/latest';
}
updateNotice.classList.remove('hidden');
// 绑定关闭按钮事件
document.getElementById('closeUpdateNotice').addEventListener('click', function() {
updateNotice.classList.add('hidden');
// 记住用户已关闭此版本的通知
localStorage.setItem('dismissedUpdate', updateInfo.latest_version);
});
}
function checkForUpdates() {
fetch('/api/check-update')
.then(function(response) { return response.json(); })
.then(function(updateInfo) {
const dismissedVersion = localStorage.getItem('dismissedUpdate');
// 只有当有更新且用户没有关闭过此版本的通知时才显示
if (updateInfo.has_update && dismissedVersion !== updateInfo.latest_version) {
showUpdateNotice(updateInfo);
}
})
.catch(function(error) { console.error('检查更新失败:', error); });
}
</script>
</body>
</html>