ocr-based-qwen/worker.js

2099 lines
83 KiB
JavaScript
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.

addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
const cookie = request.headers.get('x-custom-cookie') || ''; // 从自定义请求头获取 cookie
// 处理 CORS 预检请求
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-custom-cookie', // 添加自定义请求头
},
});
}
// API路由处理
switch (url.pathname) {
// 1. 通过图片URL识别
case '/api/recognize/url':
if (request.method === 'POST') {
return handleImageUrlRecognition(request);
}
break;
// 2. 通过Base64识别
case '/api/recognize/base64':
if (request.method === 'POST') {
return handleBase64Recognition(request);
}
break;
// 3. 通过图片文件识别 (原有的/recognize端点)
case '/recognize':
if (request.method === 'POST') {
return handleFileRecognition(request);
}
break;
// 添加代理路由
case '/proxy/upload':
if (request.method === 'POST') {
return handleProxyUpload(request);
}
break;
// 返回前端界面
case '/':
return new Response(getHTML(), {
headers: { 'Content-Type': 'text/html' },
});
}
return new Response('Not Found', { status: 404 });
}
// 处理图片URL识别
async function handleImageUrlRecognition(request) {
try {
const { imageUrl } = await request.json();
const cookie = request.headers.get('x-custom-cookie');
if (!cookie || !imageUrl) {
return new Response(JSON.stringify({
error: 'Missing cookie or imageUrl'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
// 从cookie中提取token
const tokenMatch = cookie.match(/token=([^;]+)/);
if (!tokenMatch) {
return new Response(JSON.stringify({
error: 'Invalid cookie format: missing token'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
const token = tokenMatch[1];
// 下载图片
const imageResponse = await fetch(imageUrl);
const imageBlob = await imageResponse.blob();
// 上传到QwenLM
const formData = new FormData();
formData.append('file', imageBlob);
const uploadResponse = await fetch('https://chat.qwenlm.ai/api/v1/files/', {
method: 'POST',
headers: {
'accept': 'application/json',
'authorization': `Bearer ${token}`,
'cookie': cookie
},
body: formData,
});
const uploadData = await uploadResponse.json();
if (!uploadData.id) throw new Error('File upload failed');
// 调用通用识别函数
return await recognizeImage(token, uploadData.id, request);
} catch (error) {
return new Response(JSON.stringify({
error: error.message || 'Internal Server Error'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
// 处理Base64识别
async function handleBase64Recognition(request) {
try {
const { base64Image } = await request.json();
const cookie = request.headers.get('x-custom-cookie');
if (!cookie || !base64Image) {
return new Response(JSON.stringify({
error: 'Missing cookie or base64Image'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
// 从cookie中提取token
const tokenMatch = cookie.match(/token=([^;]+)/);
if (!tokenMatch) {
return new Response(JSON.stringify({
error: 'Invalid cookie format: missing token'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
const token = tokenMatch[1];
// 转换Base64为Blob
const imageData = base64Image.startsWith('data:') ? base64Image : 'data:image/png;base64,' + base64Image;
const response = await fetch(imageData);
const blob = await response.blob();
// 上传到QwenLM
const formData = new FormData();
formData.append('file', blob);
const uploadResponse = await fetch('https://chat.qwenlm.ai/api/v1/files/', {
method: 'POST',
headers: {
'accept': 'application/json',
'authorization': `Bearer ${token}`,
'cookie': cookie
},
body: formData,
});
const uploadData = await uploadResponse.json();
if (!uploadData.id) throw new Error('File upload failed');
// 调用通用识别函数
return await recognizeImage(token, uploadData.id, request);
} catch (error) {
return new Response(JSON.stringify({
error: error.message || 'Internal Server Error'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
// 处理文件识别 (原有功能)
async function handleFileRecognition(request) {
try {
const { imageId } = await request.json();
const cookie = request.headers.get('x-custom-cookie') || '';
if (!cookie || !imageId) {
return new Response(JSON.stringify({
error: 'Missing cookie or imageId'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
// 从cookie中提取token
const tokenMatch = cookie.match(/token=([^;]+)/);
const token = tokenMatch ? tokenMatch[1] : '';
return await recognizeImage(token, imageId, request);
} catch (error) {
return new Response(JSON.stringify({
error: error.message || 'Internal Server Error'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
// 添加代理处理函数
async function handleProxyUpload(request) {
try {
const formData = await request.formData();
const cookie = request.headers.get('x-custom-cookie') || '';
// 从cookie中提取token
const tokenMatch = cookie.match(/token=([^;]+)/);
const token = tokenMatch ? tokenMatch[1] : '';
const response = await fetch('https://chat.qwenlm.ai/api/v1/files/', {
method: 'POST',
headers: {
'accept': 'application/json',
'authorization': `Bearer ${token}`,
'cookie': cookie
},
body: formData,
});
const data = await response.json();
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
} catch (error) {
return new Response(JSON.stringify({
error: error.message || 'Proxy upload failed'
}), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
}
// 通用的识别函数
async function recognizeImage(token, imageId, request) {
const cookie = request.headers.get('x-custom-cookie') || '';
// 从请求头中获取高级模式状态和自定义prompt
const advancedMode = request.headers.get('x-advanced-mode') === 'true';
// 解码自定义prompt
let customPrompt = '';
try {
const encodedPrompt = request.headers.get('x-custom-prompt');
if (encodedPrompt) {
customPrompt = decodeURIComponent(atob(encodedPrompt));
}
} catch (error) {
console.error('Prompt解码错误:', error);
}
const defaultPrompt =
'不要输出任何额外的解释或说明,禁止输出例如:识别内容、以上内容已严格按照要求进行格式化和转换等相关无意义的文字!' + '请识别图片中的内容,注意以下要求:\n' +
'对于数学公式和普通文本:\n' +
'1. 所有数学公式和数学符号都必须使用标准的LaTeX格式\n' +
'2. 行内公式使用单个$符号包裹,如:$x^2$\n' +
'3. 独立公式块使用两个$$符号包裹,如:$$\\sum_{i=1}^n i^2$$\n' +
'4. 普通文本保持原样不要使用LaTeX格式\n' +
'5. 保持原文的段落格式和换行\n' +
'6. 明显的换行使用\\n表示\n' +
'7. 确保所有数学符号都被正确包裹在$或$$中\n\n' +
'对于验证码图片:\n' +
'1. 只输出验证码字符,不要加任何额外解释\n' +
'2. 忽略干扰线和噪点\n' +
'3. 注意区分相似字符如0和O、1和l、2和Z等\n' +
'4. 验证码通常为4-6位字母数字组合\n\n' +
'';
const response = await fetch('https://chat.qwenlm.ai/api/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'PostmanRuntime/7.43.0',
'accept': '*/*',
'authorization': `Bearer ${token}`,
'cookie': cookie,
},
body: JSON.stringify({
stream: false,
chat_type: "t2t",
model: 'qwen-max-latest',
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: advancedMode ? customPrompt : defaultPrompt,
chat_type: "t2t"
},
{
type: 'image',
image: imageId,
chat_type: "t2t"
},
],
},
],
session_id: '1',
chat_id: '2',
id: '3',
}),
});
const data = await response.json();
let result = data.choices[0]?.message?.content || '识别失败';
// 只在非高级模式下进行格式化处理
if (!advancedMode) {
// 如果结果长度小于10且只包含字母数字很可能是验证码
if (result.length <= 10 && /^[A-Za-z0-9]+$/.test(result)) {
return new Response(JSON.stringify({
success: true,
result: result.toUpperCase(),
type: 'captcha'
}), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
// 其他情况(数学公式和普通文本)的处理
result = result
.replace(/\\/g, '\\(')
.replace(/\\/g, '\\)')
.replace(/\n{3,}/g, '\n\n')
.replace(/([^\n])\n([^\n])/g, '$1\n$2')
.replace(/\$\s+/g, '$')
.replace(/\s+\$/g, '$')
.replace(/\$\$/g, '$$')
.trim();
}
return new Response(JSON.stringify({
success: true,
result: result,
type: 'text'
}), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
function getHTML() {
const html = [
'<!DOCTYPE html>',
'<html lang="zh-CN">',
'<head>',
'<meta charset="UTF-8">',
'<meta name="viewport" content="width=device-width, initial-scale=1.0">',
'<link rel="icon" type="image/png" href="data:image/png;base64,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">',
'<title>Qwen VL 智能识别系统</title>',
// 添加 MathJax 支持
'<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>',
'<script>',
'window.MathJax = {',
' tex: {',
' inlineMath: [["$", "$"]],',
' displayMath: [["$$", "$$"]]',
' },',
' startup: {',
' pageReady: () => {',
' return MathJax.startup.defaultPageReady().then(() => {',
' // MathJax 加载完成后刷新历史记录',
' if (currentToken) {',
' historyManager.displayHistory(currentToken);',
' }',
' });',
' }',
' },',
' options: {',
' enableMenu: false',
' }',
'};',
'</script>',
'<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>',
'<script>',
'function waitForMathJax(callback, maxTries = 30) {',
' let tries = 0;',
' const checkMathJax = () => {',
' tries++;',
' if (window.MathJax && window.MathJax.typesetPromise) {',
' callback();',
' } else if (tries < maxTries) {',
' setTimeout(checkMathJax, 100);',
' }',
' };',
' checkMathJax();',
'}',
'</script>',
'<style>',
' * {',
' box-sizing: border-box;',
' margin: 0;',
' padding: 0;',
' }',
' body {',
' font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Oxygen, Ubuntu, sans-serif;',
' background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);',
' min-height: 100vh;',
' display: flex;',
' justify-content: center;',
' align-items: center;',
' padding: 20px;',
' }',
' .container {',
' background: rgba(255, 255, 255, 0.95);',
' padding: 2.5rem;',
' padding-bottom: 4rem;',
' border-radius: 16px;',
' box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);',
' width: 90%;',
' max-width: 800px;',
' transition: all 0.3s ease;',
' }',
' h1 {',
' color: #2c3e50;',
' margin-bottom: 0.5rem;',
' font-size: 2.2rem;',
' text-align: center;',
' font-weight: 700;',
' text-transform: uppercase;',
' letter-spacing: 2px;',
' background: linear-gradient(135deg, #1a5fb4 0%, #3498db 100%);',
' -webkit-background-clip: text;',
' -webkit-text-fill-color: transparent;',
' text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);',
' position: relative;',
' padding-bottom: 10px;',
' animation: titleFadeIn 1s ease-out;',
' }',
' @keyframes titleFadeIn {',
' from {',
' opacity: 0;',
' transform: translateY(-20px);',
' }',
' to {',
' opacity: 1;',
' transform: translateY(0);',
' }',
' }',
' h1::after {',
' content: "";',
' position: absolute;',
' bottom: 0;',
' left: 50%;',
' transform: translateX(-50%);',
' width: 100px;',
' height: 3px;',
' background: linear-gradient(90deg, transparent, #3498db, transparent);',
' }',
' .subtitle {',
' color: #7f8c8d;',
' text-align: center;',
' font-size: 1.1rem;',
' margin-bottom: 1.5rem;',
' font-weight: 300;',
' letter-spacing: 1px;',
' opacity: 0.8;',
' animation: subtitleFadeIn 1s ease-out 0.3s both;',
' }',
' @keyframes subtitleFadeIn {',
' from {',
' opacity: 0;',
' transform: translateY(10px);',
' }',
' to {',
' opacity: 0.8;',
' transform: translateY(0);',
' }',
' }',
' .upload-area {',
' border: 2px dashed #8e9eab;',
' border-radius: 12px;',
' padding: 2rem;',
' text-align: center;',
' transition: all 0.3s ease;',
' margin-bottom: 1.5rem;',
' cursor: pointer;',
' position: relative;',
' overflow: hidden;',
' }',
' .upload-area:hover {',
' border-color: #3498db;',
' background: rgba(52, 152, 219, 0.05);',
' }',
' .upload-area.dragover {',
' border-color: #3498db;',
' background: rgba(52, 152, 219, 0.1);',
' transform: scale(1.02);',
' }',
' .upload-area i {',
' font-size: 2rem;',
' color: #8e9eab;',
' margin-bottom: 1rem;',
' }',
' .upload-text {',
' color: #7f8c8d;',
' font-size: 0.9rem;',
' }',
' #tokens {',
' width: 100%;',
' padding: 0.8rem;',
' border: 1px solid #dcdde1;',
' border-radius: 8px;',
' margin-bottom: 1rem;',
' font-size: 0.9rem;',
' resize: none;',
' }',
' .result-container {',
' margin-top: 1.5rem;',
' opacity: 0;',
' transform: translateY(20px);',
' transition: all 0.3s ease;',
' }',
' .result-container.show {',
' opacity: 1;',
' transform: translateY(0);',
' }',
' .result {',
' background: #f8f9fa;',
' padding: 1.2rem;',
' border-radius: 8px;',
' color: #2c3e50;',
' font-size: 1rem;',
' line-height: 1.6;',
' white-space: pre-wrap;',
' }',
' .loading {',
' display: none;',
' text-align: center;',
' margin: 1rem 0;',
' }',
' .loading::after {',
' content: \'\';',
' display: inline-block;',
' width: 20px;',
' height: 20px;',
' border: 2px solid #3498db;',
' border-radius: 50%;',
' border-top-color: transparent;',
' animation: spin 0.8s linear infinite;',
' }',
' @keyframes spin {',
' to { transform: rotate(360deg); }',
' }',
' .preview-image {',
' max-width: 100%;',
' max-height: 200px;',
' margin: 1rem 0;',
' border-radius: 8px;',
' display: none;',
' }',
' /* 侧边栏样式 */',
' .sidebar {',
' position: fixed;',
' right: -300px;',
' top: 0;',
' width: 300px;',
' height: 100vh;',
' background: white;',
' box-shadow: -5px 0 15px rgba(0, 0, 0, 0.1);',
' transition: right 0.3s ease;',
' padding: 20px;',
' z-index: 1000;',
' }',
' .sidebar.open {',
' right: 0;',
' }',
' .sidebar-toggle {',
' position: fixed;',
' right: 20px;',
' top: 20px;',
' background: #3498db;',
' color: white;',
' border: none;',
' padding: 10px 15px;',
' border-radius: 5px;',
' cursor: pointer;',
' z-index: 1001;',
' }',
' .token-list {',
' margin-top: 20px;',
' }',
' .token-item {',
' background: #f8f9fa;',
' padding: 10px;',
' margin-bottom: 10px;',
' border-radius: 5px;',
' cursor: pointer;',
' word-break: break-all;',
' }',
' .token-item:hover {',
' background: #e9ecef;',
' }',
' #tokenInput {',
' width: 100%;',
' padding: 10px;',
' margin-bottom: 10px;',
' border: 1px solid #dcdde1;',
' border-radius: 5px;',
' }',
' .save-btn {',
' background: #3498db;',
' color: white;',
' border: none;',
' padding: 10px 15px;',
' border-radius: 5px;',
' cursor: pointer;',
' width: 100%;',
' }',
' /* 历史记录样式 */',
' .history-container {',
' margin-top: 2rem;',
' border-top: 1px solid #eee;',
' padding-top: 1rem;',
' }',
' .history-title {',
' color: #2c3e50;',
' font-size: 1.2rem;',
' margin-bottom: 1rem;',
' }',
' .history-item {',
' display: flex;',
' align-items: flex-start;',
' padding: 1rem;',
' background: #f8f9fa;',
' border-radius: 8px;',
' margin-bottom: 1rem;',
' }',
' .history-image {',
' width: 100px;',
' height: 100px;',
' object-fit: cover;',
' border-radius: 4px;',
' margin-right: 1rem;',
' }',
' .history-content {',
' flex: 1;',
' }',
' .history-text {',
' color: #2c3e50;',
' font-size: 0.9rem;',
' line-height: 1.4;',
' }',
' .history-time {',
' color: #7f8c8d;',
' font-size: 0.8rem;',
' margin-top: 0.5rem;',
' }',
' .no-history {',
' text-align: center;',
' color: #7f8c8d;',
' padding: 1rem;',
' }',
' .modal {',
' display: none;',
' position: fixed;',
' top: 0;',
' left: 0;',
' width: 100%;',
' height: 100%;',
' background-color: rgba(0, 0, 0, 0.9);',
' z-index: 2000;',
' cursor: pointer;',
' }',
' .modal-content {',
' max-width: 90%;',
' max-height: 90vh;',
' margin: auto;',
' display: block;',
' position: relative;',
' top: 50%;',
' transform: translateY(-50%);',
' }',
' /* 修改侧边栏样式 */',
' .sidebar {',
' position: fixed;',
' right: -400px;',
' top: 0;',
' width: 400px;',
' height: 100vh;',
' background: rgba(255, 255, 255, 0.95);',
' backdrop-filter: blur(10px);',
' box-shadow: -5px 0 15px rgba(0, 0, 0, 0.1);',
' transition: right 0.3s ease;',
' padding: 30px;',
' z-index: 1000;',
' }',
' .sidebar-header {',
' display: flex;',
' justify-content: space-between;',
' align-items: center;',
' margin-bottom: 20px;',
' padding-bottom: 15px;',
' border-bottom: 2px solid #eee;',
' }',
' .sidebar-header h2 {',
' margin: 0;',
' color: #2c3e50;',
' font-size: 1.5rem;',
' }',
' .close-sidebar {',
' background: none;',
' border: none;',
' font-size: 1.5rem;',
' cursor: pointer;',
' color: #7f8c8d;',
' }',
' .token-section {',
' margin-bottom: 25px;',
' }',
' .token-section label {',
' display: block;',
' margin-bottom: 10px;',
' color: #34495e;',
' font-weight: 500;',
' }',
' #tokenInput {',
' width: 100%;',
' padding: 12px;',
' border: 2px solid #e9ecef;',
' border-radius: 8px;',
' font-size: 0.95rem;',
' transition: border-color 0.3s ease;',
' margin-bottom: 15px;',
' }',
' #tokenInput:focus {',
' outline: none;',
' border-color: #3498db;',
' }',
' .save-btn {',
' background: #3498db;',
' color: white;',
' border: none;',
' padding: 12px 20px;',
' border-radius: 8px;',
' cursor: pointer;',
' width: 100%;',
' font-size: 1rem;',
' transition: background 0.3s ease;',
' }',
' .save-btn:hover {',
' background: #2980b9;',
' }',
' .token-list {',
' margin-top: 25px;',
' max-height: calc(100vh - 250px);',
' overflow-y: auto;',
' }',
' .token-item {',
' background: #f8f9fa;',
' padding: 15px;',
' margin-bottom: 12px;',
' border-radius: 8px;',
' cursor: pointer;',
' transition: all 0.3s ease;',
' border: 2px solid transparent;',
' }',
' .token-item:hover {',
' background: #e9ecef;',
' transform: translateX(-5px);',
' }',
' .token-item.active {',
' border-color: #3498db;',
' background: #f1f9ff;',
' }',
' /* 添加左侧边栏样式 */',
' .history-sidebar {',
' position: fixed;',
' left: -400px;',
' top: 0;',
' width: 400px;',
' height: 100vh;',
' background: rgba(255, 255, 255, 0.98);',
' backdrop-filter: blur(10px);',
' box-shadow: 5px 0 15px rgba(0, 0, 0, 0.1);',
' transition: left 0.3s ease;',
' padding: 20px;',
' z-index: 1000;',
' overflow-y: auto;',
' }',
' .history-sidebar.open {',
' left: 0;',
' }',
' .history-toggle {',
' position: fixed;',
' left: 20px;',
' top: 20px;',
' background: #3498db;',
' color: white;',
' border: none;',
' padding: 10px 15px;',
' border-radius: 5px;',
' cursor: pointer;',
' z-index: 1001;',
' }',
' /* 添加复制按钮样式 */',
' .result-header {',
' display: flex;',
' justify-content: space-between;',
' align-items: center;',
' margin-bottom: 10px;',
' }',
' .copy-btn {',
' background: #3498db;',
' color: white;',
' border: none;',
' padding: 5px 10px;',
' border-radius: 4px;',
' cursor: pointer;',
' font-size: 0.9rem;',
' transition: background 0.3s ease;',
' }',
' .copy-btn:hover {',
' background: #2980b9;',
' }',
' .copy-btn.copied {',
' background: #27ae60;',
' }',
' /* Base64输入相关样式 */',
' #base64Input {',
' width: 100%;',
' height: 100px;',
' padding: 10px;',
' margin-top: 10px;',
' border: 1px solid #dcdde1;',
' border-radius: 8px;',
' resize: vertical;',
' }',
' .toggle-btn {',
' background: #3498db;',
' color: white;',
' border: none;',
' padding: 8px 15px;',
' border-radius: 5px;',
' cursor: pointer;',
' margin-top: 10px;',
' transition: background 0.3s ease;',
' }',
' .toggle-btn:hover {',
' background: #2980b9;',
' }',
' /* 修改历史记录侧边栏样式 */',
' .history-item {',
' background: #ffffff;',
' border-radius: 12px;',
' margin-bottom: 20px;',
' box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);',
' overflow: hidden;',
' transition: transform 0.2s ease, box-shadow 0.2s ease;',
' }',
' .history-item:hover {',
' transform: translateY(-2px);',
' box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);',
' }',
' .history-image-container {',
' position: relative;',
' width: 100%;',
' height: 200px;',
' overflow: hidden;',
' }',
' .history-image {',
' width: 100%;',
' height: 100%;',
' object-fit: cover;',
' transition: transform 0.3s ease;',
' }',
' .image-overlay {',
' position: absolute;',
' top: 0;',
' left: 0;',
' width: 100%;',
' height: 100%;',
' background: rgba(0, 0, 0, 0.4);',
' display: flex;',
' justify-content: center;',
' align-items: center;',
' opacity: 0;',
' transition: opacity 0.3s ease;',
' }',
' .history-image-container:hover .image-overlay {',
' opacity: 1;',
' }',
' .history-image-container:hover .history-image {',
' transform: scale(1.05);',
' }',
' .overlay-btn {',
' background: rgba(255, 255, 255, 0.9);',
' color: #2c3e50;',
' border: none;',
' padding: 8px 16px;',
' border-radius: 20px;',
' cursor: pointer;',
' font-size: 0.9rem;',
' transition: all 0.2s ease;',
' }',
' .overlay-btn:hover {',
' background: #ffffff;',
' transform: scale(1.05);',
' }',
' .history-content {',
' padding: 16px;',
' }',
' .history-header {',
' display: flex;',
' justify-content: space-between;',
' align-items: center;',
' margin-bottom: 12px;',
' padding-bottom: 12px;',
' border-bottom: 1px solid #eee;',
' }',
' .history-time {',
' color: #7f8c8d;',
' font-size: 0.9rem;',
' }',
' .history-actions {',
' display: flex;',
' gap: 8px;',
' }',
' .action-btn {',
' background: none;',
' border: 1px solid #e0e0e0;',
' padding: 4px 8px;',
' border-radius: 4px;',
' cursor: pointer;',
' font-size: 0.8rem;',
' transition: all 0.2s ease;',
' }',
' .action-btn.copy-btn {',
' color: #3498db;',
' }',
' .action-btn.delete-btn {',
' color: #e74c3c;',
' }',
' .action-btn:hover {',
' background: #f8f9fa;',
' transform: translateY(-1px);',
' }',
' .history-text {',
' color: #2c3e50;',
' font-size: 0.95rem;',
' line-height: 1.6;',
' max-height: 200px;',
' overflow-y: auto;',
' padding-right: 8px;',
' }',
' .history-text::-webkit-scrollbar {',
' width: 4px;',
' }',
' .history-text::-webkit-scrollbar-track {',
' background: #f1f1f1;',
' }',
' .history-text::-webkit-scrollbar-thumb {',
' background: #c0c0c0;',
' border-radius: 2px;',
' }',
' .footer {',
' position: fixed;',
' bottom: 0;',
' left: 0;',
' width: 100%;',
' padding: 15px;',
' text-align: center;',
' background: rgba(255, 255, 255, 0.9);',
' backdrop-filter: blur(5px);',
' z-index: 900;',
' display: flex;',
' justify-content: center;',
' align-items: center;',
' }',
' .footer-content {',
' text-align: center;',
' width: 100%;',
' }',
' .powered-by {',
' color: #7f8c8d;',
' font-size: 0.9rem;',
' }',
' .powered-by a {',
' color: #3498db;',
' text-decoration: none;',
' transition: color 0.3s ease;',
' font-weight: 500;',
' }',
' .powered-by a:hover {',
' color: #2980b9;',
' }',
' .powered-by {',
' color: #7f8c8d;',
' font-size: 0.9rem;',
' }',
' .powered-by a {',
' color: #3498db;',
' text-decoration: none;',
' transition: color 0.3s ease;',
' font-weight: 500;',
' }',
' .powered-by a:hover {',
' color: #2980b9;',
' }',
' /* 输入控件组样式 */',
' .input-controls {',
' margin-top: 15px;',
' width: 100%;',
' }',
' .button-group {',
' display: flex;',
' gap: 10px;',
' margin-top: 10px;',
' justify-content: center;',
' }',
' #urlInput {',
' width: 100%;',
' padding: 10px;',
' border: 1px solid #dcdde1;',
' border-radius: 8px;',
' resize: none;',
' font-size: 14px;',
' }',
' .github-link {',
' position: fixed;',
' right: 150px;',
' top: 20px;',
' background: #333;',
' color: white;',
' border: none;',
' padding: 10px;',
' border-radius: 50%;',
' cursor: pointer;',
' z-index: 1001;',
' width: 40px;',
' height: 40px;',
' display: flex;',
' align-items: center;',
' justify-content: center;',
' transition: background 0.3s ease;',
' }',
' .github-link:hover {',
' background: #24292e;',
' }',
' .github-icon {',
' width: 24px;',
' height: 24px;',
' }',
' .cookie-input-container {',
' margin-bottom: 15px;',
' }',
' #cookieInput {',
' width: 100%;',
' padding: 12px;',
' border: 2px solid #e9ecef;',
' border-radius: 8px;',
' font-size: 0.95rem;',
' resize: vertical;',
' min-height: 120px;',
' font-family: monospace;',
' line-height: 1.4;',
' }',
' #cookieInput:focus {',
' outline: none;',
' border-color: #3498db;',
' }',
' .cookie-info {',
' background: #f8f9fa;',
' padding: 12px;',
' border-radius: 8px;',
' margin-bottom: 15px;',
' }',
' .cookie-info p {',
' margin: 0;',
' color: #2c3e50;',
' font-size: 0.9rem;',
' margin-bottom: 8px;', // 添加底部间距
' }',
' .cookie-info p:last-child {',
' margin-bottom: 0;', // 最后一个p元素不需要底部间距
' }',
' .token-expiry {',
' color: #7f8c8d;',
' font-size: 0.85rem;',
' }',
' .token-expiry.expired {',
' color: #e74c3c;',
' }',
' #currentTokenDisplay {',
' color: #3498db;',
' font-family: monospace;',
' word-break: break-all;',
' }',
// 添加获取cookie按钮样式
' .get-cookie-link {',
' position: fixed;',
' left: 150px;', // 放在github图标左边
' top: 22px;',
' background: #2ecc71;', // 使用不同的颜色区分
' color: white;',
' border: none;',
' padding: 8px 15px;',
' border-radius: 5px;',
' cursor: pointer;',
' z-index: 1001;',
' text-decoration: none;',
' font-size: 14px;',
' transition: background 0.3s ease;',
' }',
' .get-cookie-link:hover {',
' background: #27ae60;',
' }',
' .advanced-mode-toggle {',
' display: flex;',
' align-items: center;',
' margin-bottom: 15px;',
' padding: 10px;',
' background: #f8f9fa;',
' border-radius: 8px;',
' }',
' .advanced-mode-toggle input[type="checkbox"] {',
' margin-right: 10px;',
' }',
' .prompt-container {',
' display: none;', // 默认隐藏
' margin-bottom: 15px;',
' }',
' .prompt-container.show {',
' display: block;',
' }',
' #promptInput {',
' width: 100%;',
' padding: 12px;',
' border: 2px solid #e9ecef;',
' border-radius: 8px;',
' font-size: 0.95rem;',
' resize: vertical;',
' min-height: 120px;',
' font-family: monospace;',
' line-height: 1.4;',
' }',
' #promptInput:focus {',
' outline: none;',
' border-color: #3498db;',
' }',
'</style>',
'</head>',
'<body>',
'<a href="https://chat.qwenlm.ai/" target="_blank" rel="noopener noreferrer" class="get-cookie-link">',
' 获取Cookie',
'</a>',
'<a href="https://github.com/Cunninger/ocr-based-qwen" target="_blank" rel="noopener noreferrer" class="github-link" title="View on GitHub">',
' <svg class="github-icon" viewBox="0 0 16 16" fill="currentColor">',
' <path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>',
' </svg>',
'</a>',
'<button class="sidebar-toggle" id="sidebarToggle">⚙️ Cookie设置</button>',
'<div class="sidebar" id="sidebar">',
'<div class="sidebar-header">',
'<h2>Cookie管理</h2>',
'<button class="close-sidebar" id="closeSidebar">×</button>',
'</div>',
'<div class="token-section">',
'<label for="cookieInput">输入Cookie</label>',
'<div class="cookie-input-container">',
'<textarea id="cookieInput" placeholder="请输入完整的cookie字符串..." rows="8"></textarea>',
'</div>',
'<div class="cookie-info">',
'<p>当前Token: <span id="currentTokenDisplay">未设置</span></p>',
'<p>过期时间: <span id="tokenExpiryDisplay" class="token-expiry">未知</span></p>',
'</div>',
'<button class="save-btn" id="saveTokens">保存设置</button>',
'</div>',
// 添加 tokenList 容器
'<div class="token-list" id="tokenList"></div>', // 添加这一行
'<div class="advanced-mode-toggle">',
' <input type="checkbox" id="advancedMode">',
' <label for="advancedMode">高级模式 (自定义Prompt)</label>',
'</div>',
'<div class="prompt-container" id="promptContainer">',
' <label for="promptInput">自定义Prompt</label>',
' <textarea id="promptInput" placeholder="输入自定义prompt...">请识别图片中的内容,注意以下要求:\n对于数学公式和普通文本\n1. 所有数学公式和数学符号都必须使用标准的LaTeX格式\n2. 行内公式使用单个$符号包裹,如:$x^2$\n3. 独立公式块使用两个$$符号包裹,如:$$\\sum_{i=1}^n i^2$$\n4. 普通文本保持原样不要使用LaTeX格式\n5. 保持原文的段落格式和换行\n6. 明显的换行使用\\n表示\n7. 确保所有数学符号都被正确包裹在$或$$中\n\n对于验证码图片\n1. 只输出验证码字符,不要加任何额外解释\n2. 忽略干扰线和噪点\n3. 注意区分相似字符如0和O、1和l、2和Z等\n4. 验证码通常为4-6位字母数字组合\n\n不要输出任何额外的解释或说明</textarea>',
'</div>',
'</div>',
'<div class="container">',
'<h1>Qwen VL 智能识别系统</h1>',
'<div class="subtitle">基于通义千问大模型的多模态智能识别引擎</div>',
'<div class="upload-area" id="uploadArea">',
'<i>📸</i>',
'<div class="upload-text">',
'拖拽图片到这里,点击上传,或粘贴图片/Base64/URL<br>',
'支持多种输入方式',
'</div>',
'<div class="input-controls">',
'<textarea id="base64Input" placeholder="在此输入Base64格式的图片内容..." style="display: none; width: 100%; height: 100px; margin-top: 10px;"></textarea>',
'<textarea id="urlInput" placeholder="在此输入图片URL..." style="display: none; width: 100%; height: 40px; margin-top: 10px;"></textarea>',
'<div class="button-group">',
'<button id="toggleBase64" class="toggle-btn">Base64输入</button>',
'<button id="toggleUrl" class="toggle-btn">URL输入</button>',
'</div>',
'</div>',
'<img id="previewImage" class="preview-image">',
'</div>',
'<div class="loading" id="loading"></div>',
'<div class="result-container" id="resultContainer">',
'<div class="result-header">',
'<span>识别结果</span>',
'<button class="copy-btn" id="copyBtn">复制结果</button>',
'</div>',
'<div class="result" id="result"></div>',
'</div>',
'<button class="history-toggle" id="historyToggle">📋 识别历史</button>',
'<div class="history-sidebar" id="historySidebar">',
'<h2>识别历史</h2>',
'<div id="historyList"></div>',
'</div>',
'<div class="footer-content">',
'<div class="powered-by">',
'由 <a href="https://chat.qwenlm.ai/" target="_blank" rel="noopener noreferrer">Qwen VL</a> 提供支持,一切仅用于学习使用!',
'</div>',
'</div>',
'</div>',
'<div id="imageModal" class="modal">',
'<img class="modal-content" id="modalImage">',
'</div>',
'<script>',
' // 首先定义类',
' function HistoryManager() {',
' this.maxHistory = 10;',
' }',
' // 添加原型方法',
' HistoryManager.prototype.getHistoryKey = function(token) {',
' return \'imageRecognition_history_\' + token;',
' };',
' HistoryManager.prototype.loadHistory = function(token) {',
' const history = localStorage.getItem(this.getHistoryKey(token));',
' return history ? JSON.parse(history) : [];',
' };',
' HistoryManager.prototype.saveHistory = function(token, history) {',
' localStorage.setItem(this.getHistoryKey(token), JSON.stringify(history));',
' };',
' HistoryManager.prototype.addHistory = function(token, imageData, result) {',
' const history = this.loadHistory(token);',
' const newRecord = {',
' image: imageData,',
' result: result,',
' timestamp: new Date().toISOString()',
' };',
' history.unshift(newRecord);',
' if (history.length > this.maxHistory) {',
' history.pop();',
' }',
' this.saveHistory(token, history);',
' this.displayHistory(token);',
' };',
' HistoryManager.prototype.displayHistory = function(token) {',
' const history = this.loadHistory(token);',
' if (history.length === 0) {',
' historyList.innerHTML = \'<div class="no-history">暂无识别历史</div>\';',
' return;',
' }',
' var html = \'\';',
' history.forEach((record, i) => {',
' // 确保 image 数据存在且格式正确',
' const imageUrl = record.image && (',
' record.image.startsWith(\'data:\') ? ',
' record.image : ',
' `data:image/png;base64,${record.image}`',
' );',
' const timestamp = new Date(record.timestamp);',
' const timeStr = timestamp.toLocaleString(\'zh-CN\', {',
' year: \'numeric\',',
' month: \'2-digit\',',
' day: \'2-digit\',', // Add comma here
' hour: \'2-digit\',',
' minute: \'2-digit\'',
' });',
' html += `',
' <div class="history-item" data-index="${i}">',
' <div class="history-image-container">',
' <img src="${imageUrl}" ',
' class="history-image" ',
' alt="历史图片" ',
' onerror="this.src=\'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\'"',
' onclick="event.stopPropagation(); showFullImage(\'${imageUrl}\')">',
' <div class="image-overlay">',
' <button class="overlay-btn" onclick="event.stopPropagation(); showFullImage(\'${imageUrl}\')">查看大图</button>',
' </div>',
' </div>',
' <div class="history-content">',
' <div class="history-header">',
' <span class="history-time">${timeStr}</span>',
' <div class="history-actions">',
' <button class="action-btn copy-btn" onclick="event.stopPropagation(); copyHistoryResult(${i}, this)">复制结果</button>',
' <button class="action-btn delete-btn" onclick="event.stopPropagation(); deleteHistoryItem(${i})">删除</button>',
' </div>',
' </div>',
' <div class="history-text" data-original-text="${record.result || \'无识别结果\'}">${record.result || \'无识别结果\'}</div>',
' </div>',
' </div>',
' `;',
' });',
' historyList.innerHTML = html;',
' // 使用 waitForMathJax 函数处理公式渲染',
' waitForMathJax(() => {',
' try {',
' MathJax.typesetPromise([historyList])',
' .catch(err => console.error("MathJax渲染错误:", err));',
' } catch (err) {',
' console.error("MathJax处理错误:", err);',
' }',
' });',
' };',
' // 初始化变量',
' const uploadArea = document.getElementById(\'uploadArea\');',
' const tokensInput = document.getElementById(\'tokenInput\');',
' const resultDiv = document.getElementById(\'result\');',
' const resultContainer = document.getElementById(\'resultContainer\');',
' const loading = document.getElementById(\'loading\');',
' const previewImage = document.getElementById(\'previewImage\');',
' const historyList = document.getElementById(\'historyList\');',
' const sidebar = document.getElementById(\'sidebar\');',
' const sidebarToggle = document.getElementById(\'sidebarToggle\');',
' const tokenInput = document.getElementById(\'tokenInput\');',
' const saveTokensBtn = document.getElementById(\'saveTokens\');',
' const tokenList = document.getElementById(\'tokenList\');',
' const historySidebar = document.getElementById(\'historySidebar\');',
' const historyToggle = document.getElementById(\'historyToggle\');',
' let currentToken = \'\';',
' let tokens = [];',
' const historyManager = new HistoryManager();',
' // 从localStorage加载保存的tokens',
' function loadTokens() {',
' const savedTokens = localStorage.getItem(\'imageRecognitionTokens\');',
' if (savedTokens) {',
' tokens = savedTokens.split(\',\');',
' updateTokenList();',
' if (tokens.length > 0) {',
' currentToken = tokens[0];',
' }',
' }',
' }',
' // 修改 updateTokenList 函数',
' function updateTokenList() {',
' const tokenList = document.getElementById(\'tokenList\');',
' if (!tokenList) {',
' console.error(\'找不到tokenList元素\');',
' return;',
' }',
' tokenList.innerHTML = "";',
' tokens.forEach(function(token, index) {',
' var truncatedToken = token.slice(0, 10) + "..." + token.slice(-10);',
' var div = document.createElement("div");',
' div.className = "token-item" + (token === currentToken ? " active" : "");',
' div.textContent = "Token " + (index + 1) + ": " + truncatedToken;',
' div.addEventListener("click", function() {',
' document.querySelectorAll(".token-item").forEach(item => item.classList.remove("active"));',
' div.classList.add("active");',
' currentToken = token;',
' historyManager.displayHistory(currentToken);',
' });',
' tokenList.appendChild(div);',
' });',
' const tokenInput = document.getElementById(\'tokenInput\');',
' if (tokenInput) {',
' tokenInput.value = tokens.join(",");',
' }',
' }',
' // 保存tokens',
' saveTokensBtn.addEventListener(\'click\', () => {',
' const cookieValue = document.getElementById(\'cookieInput\').value.trim();',
' if (!cookieValue) {',
' alert(\'请输入Cookie\');',
' return;',
' }',
' ',
' // 从cookie中提取token',
' const tokenMatch = cookieValue.match(/token=([^;]+)/);',
' if (!tokenMatch) {',
' alert(\'无法从Cookie中提取token请确保Cookie中包含token字段\');',
' return;',
' }',
' ',
' const token = tokenMatch[1];',
' tokens = [token];',
' currentToken = token;',
' ',
' // 保存cookie和token',
' localStorage.setItem(\'imageRecognitionCookie\', cookieValue);',
' localStorage.setItem(\'imageRecognitionTokens\', token);',
' ',
' // 更新显示',
' document.getElementById(\'currentTokenDisplay\').textContent = ',
' token.slice(0, 10) + "..." + token.slice(-10);',
' ',
' // 更新过期时间显示',
' updateTokenExpiryDisplay(token);',
' ',
' alert(\'设置已保存\');',
' });',
' // 侧边栏开关',
' sidebarToggle.addEventListener(\'click\', () => {',
' sidebar.classList.toggle(\'open\');',
' });',
' // 处理文件上传和识别',
' async function processImage(file) {',
' if (!currentToken) {',
' alert(\'请先设置并选择一个Token\');',
' sidebar.classList.add(\'open\');',
' return;',
' }',
' const savedCookie = localStorage.getItem(\'imageRecognitionCookie\');',
' if (!savedCookie) {',
' alert(\'请先设置Cookie\');',
' sidebar.classList.add(\'open\');',
' return;',
' }',
' // 显示图片预览',
' const reader = new FileReader();',
' let imageData;',
' reader.onload = (e) => {',
' imageData = e.target.result;',
' previewImage.src = imageData;',
' previewImage.style.display = \'block\';',
' };',
' reader.readAsDataURL(file);',
' // 显示加载动画',
' loading.style.display = \'block\';',
' resultContainer.classList.remove(\'show\');',
' try {',
' // 上传文件',
' const formData = new FormData();',
' formData.append(\'file\', file);',
' const uploadResponse = await fetch(\'/proxy/upload\', {',
' method: \'POST\',',
' headers: {',
' \'x-custom-cookie\': savedCookie,',
' },',
' body: formData,',
' });',
' const uploadData = await uploadResponse.json();',
' if (!uploadData.id) throw new Error(\'文件上传失败\');',
' // 识别图片',
' const recognizeResponse = await fetch(\'/recognize\', {',
' method: \'POST\',',
' headers: { ',
' \'Content-Type\': \'application/json\',',
' \'x-custom-cookie\': savedCookie,',
' \'x-advanced-mode\': advancedMode.checked, // 添加高级模式状态',
' \'x-custom-prompt\': btoa(encodeURIComponent(promptInput.value)), // Base64编码',
' },',
' body: JSON.stringify({ imageId: uploadData.id }),',
' });',
' const recognizeData = await recognizeResponse.json();',
' if (!recognizeData.success) {',
' throw new Error(recognizeData.error || \'识别失败\');',
' }',
' const result = recognizeData.result || \'识别失败\';',
' resultDiv.setAttribute(\'data-original-text\', result);',
' resultDiv.innerHTML = result;',
' waitForMathJax(() => {',
' try {',
' MathJax.typesetPromise([resultDiv])',
' .then(() => {',
' resultContainer.classList.add(\'show\');',
' })',
' .catch(err => {',
' console.error("MathJax渲染错误:", err);',
' resultContainer.classList.add(\'show\');',
' });',
' } catch (err) {',
' console.error("MathJax处理错误:", err);',
' resultContainer.classList.add(\'show\');',
' }',
' });',
' // 添加到历史记录',
' historyManager.addHistory(currentToken, imageData, result);',
' } catch (error) {',
' resultDiv.textContent = \'处理失败: \' + error.message;',
' resultContainer.classList.add(\'show\');',
' copyBtn.textContent = \'复制结果\';',
' copyBtn.classList.remove(\'copied\');',
' } finally {',
' loading.style.display = \'none\';',
' }',
' }',
' // 文件拖放处理',
' uploadArea.addEventListener(\'dragover\', (e) => {',
' e.preventDefault();',
' uploadArea.classList.add(\'dragover\');',
' });',
' uploadArea.addEventListener(\'dragleave\', () => {',
' uploadArea.classList.remove(\'dragover\');',
' });',
' uploadArea.addEventListener(\'drop\', (e) => {',
' e.preventDefault();',
' uploadArea.classList.remove(\'dragover\');',
' const file = e.dataTransfer.files[0];',
' if (file && file.type.startsWith(\'image/\')) {',
' processImage(file);',
' }',
' });',
' // 点击上传',
' uploadArea.addEventListener(\'click\', (e) => {',
' // 如果点击的是 base64Input 或 toggleBase64 按钮,不触发文件上传',
' if (e.target.id === \'base64Input\' || ',
' e.target.id === \'toggleBase64\' || ',
' e.target.closest(\'#base64Input\') || ',
' e.target.closest(\'#toggleBase64\')) {',
' return;',
' }',
' const input = document.createElement(\'input\');',
' input.type = \'file\';',
' input.accept = \'image/*\';',
' input.onchange = (e) => {',
' const file = e.target.files[0];',
' if (file) processImage(file);',
' };',
' input.click();',
' });',
' // 粘贴处理',
' document.addEventListener(\'paste\', (e) => {',
' const file = e.clipboardData.files[0];',
' if (file && file.type.startsWith(\'image/\')) {',
' processImage(file);',
' }',
' });',
' // 初始化',
' loadTokens();',
' if (currentToken) {',
' historyManager.displayHistory(currentToken);',
' }',
' const modal = document.getElementById(\'imageModal\');',
' const modalImg = document.getElementById(\'modalImage\');',
' function showFullImage(src) {',
' const modal = document.getElementById(\'imageModal\');',
' const modalImg = document.getElementById(\'modalImage\');',
' if (!src) {',
' console.error(\'图片源为空\');',
' return;',
' }',
' modal.style.display = \'block\';',
' modalImg.src = src;',
' // 添加加载错误处理',
' modalImg.onerror = function() {',
' alert(\'图片加载失败\');',
' modal.style.display = \'none\';',
' };',
' modalImg.style.opacity = \'0\';',
' setTimeout(() => {',
' modalImg.style.transition = \'opacity 0.3s ease\';',
' modalImg.style.opacity = \'1\';',
' }, 50);',
' }',
' // 点击模态框关闭',
' modal.onclick = function() {',
' modal.style.display = "none";',
' }',
' // ESC 键关闭模态框',
' document.addEventListener(\'keydown\', function(e) {',
' if (e.key === \'Escape\' && modal.style.display === \'block\') {',
' modal.style.display = \'none\';',
' }',
' });',
' // 左侧历史记录边栏开关',
' historyToggle.addEventListener(\'click\', () => {',
' historySidebar.classList.toggle(\'open\');',
' });',
' const copyBtn = document.getElementById(\'copyBtn\');',
' // 修改复制结果功能,保持完整的 LaTeX 格式',
' copyBtn.addEventListener(\'click\', async () => {',
' // 获取原始文本(包含完整的 LaTeX 格式)',
' const result = resultDiv.getAttribute(\'data-original-text\');',
' if (!result) return;',
' try {',
' // 直接复制包含 LaTeX 标记的文本',
' await navigator.clipboard.writeText(result);',
' copyBtn.textContent = \'已复制\';',
' copyBtn.classList.add(\'copied\');',
' setTimeout(() => {',
' copyBtn.textContent = \'复制结果\';',
' copyBtn.classList.remove(\'copied\');',
' }, 2000);',
' } catch (err) {',
' console.error(\'复制失败:\', err);',
' }',
' });',
' // 添加关闭侧边栏的功能',
' document.getElementById("closeSidebar").addEventListener("click", () => {',
' sidebar.classList.remove("open");',
' });',
' // Base64 输入相关功能',
' const base64Input = document.getElementById(\'base64Input\');',
' const toggleBase64 = document.getElementById(\'toggleBase64\');',
' // 切换 Base64 输入框显示',
' toggleBase64.addEventListener(\'click\', (e) => {',
' e.stopPropagation(); // 阻止事件冒泡到 uploadArea',
' if (base64Input.style.display === \'none\') {',
' base64Input.style.display = \'block\';',
' toggleBase64.textContent = \'隐藏Base64输入\';',
' } else {',
' base64Input.style.display = \'none\';',
' toggleBase64.textContent = \'切换Base64输入\';',
' }',
' });',
' // 为 base64Input 添加阻止事件冒泡',
' document.getElementById(\'base64Input\').addEventListener(\'click\', (e) => {',
' e.stopPropagation(); // 阻止事件冒泡到 uploadArea',
' });',
' // base64Input 的 input 事件处理也需要阻止冒泡',
' base64Input.addEventListener(\'input\', async (e) => {',
' e.stopPropagation();',
' const base64Content = base64Input.value.trim();',
' if (base64Content) {',
' try {',
' // 尝试转换Base64为Blob',
' let imageData;',
' if (base64Content.startsWith(\'data:image\')) {',
' imageData = base64Content;',
' } else {',
' imageData = \'data:image/png;base64,\' + base64Content;',
' }',
' // 验证Base64是否为有效图片',
' const img = new Image();',
' img.src = imageData;',
' await new Promise((resolve, reject) => {',
' img.onload = resolve;',
' img.onerror = reject;',
' });',
' // 转换Base64为Blob',
' const response = await fetch(imageData);',
' const blob = await response.blob();',
' const file = new File([blob], "image.png", { type: "image/png" });',
' // 显示预览',
' previewImage.src = imageData;',
' previewImage.style.display = \'block\';',
' // 处理图片',
' await processImage(file);',
' } catch (error) {',
' resultDiv.textContent = \'处理失败: \' + error.message;',
' resultContainer.classList.add(\'show\');',
' console.error(\'Base64处理错误:\', error);',
' }',
' }',
' });',
' // 复制历史记录结果,保持完整的 LaTeX 格式',
' async function copyHistoryResult(index, btn) {',
' try {',
' const history = historyManager.loadHistory(currentToken);',
' const historyItem = document.querySelector(`.history-item[data-index="${index}"] .history-text`);',
' const result = historyItem?.getAttribute(\'data-original-text\') || history[index]?.result;',
' if (!result) {',
' throw new Error(\'无法复制:结果为空\');',
' }',
' await navigator.clipboard.writeText(result);',
' btn.textContent = \'已复制\';',
' btn.classList.add(\'copied\');',
' setTimeout(() => {',
' btn.textContent = \'复制结果\';',
' btn.classList.remove(\'copied\');',
' }, 2000);',
' return true;',
' } catch (err) {',
' console.error(\'复制失败:\', err);',
' alert(\'复制失败: \' + err.message);',
' return false;',
' }',
' }',
' // 删除历史记录项',
' function deleteHistoryItem(index) {',
' const history = historyManager.loadHistory(currentToken);',
' if (!history[index]) {',
' alert(\'该记录不存在\');',
' return;',
' }',
' if (confirm(\'确定要删除这条历史记录吗?\')) {',
' history.splice(index, 1);',
' historyManager.saveHistory(currentToken, history);',
' historyManager.displayHistory(currentToken);',
' }',
' }',
' // URL输入相关功能',
' const urlInput = document.getElementById(\'urlInput\');',
' const toggleUrl = document.getElementById(\'toggleUrl\');',
' // 切换URL输入框显示',
' toggleUrl.addEventListener(\'click\', (e) => {',
' e.stopPropagation();',
' if (urlInput.style.display === \'none\') {',
' urlInput.style.display = \'block\';',
' base64Input.style.display = \'none\';',
' toggleUrl.textContent = \'隐藏URL输入\';',
' toggleBase64.textContent = \'Base64输入\';',
' } else {',
' urlInput.style.display = \'none\';',
' toggleUrl.textContent = \'URL输入\';',
' }',
' });',
' // 为urlInput添加阻止事件冒泡',
' urlInput.addEventListener(\'click\', (e) => {',
' e.stopPropagation();',
' });',
' // URL输入处理',
' urlInput.addEventListener(\'input\', debounce(async (e) => {',
' e.stopPropagation();',
' const imageUrl = urlInput.value.trim();',
' if (imageUrl) {',
' try {',
' if (!currentToken) {',
' throw new Error(\'请先设置Token\');',
' }',
' // 显示加载动画',
' loading.style.display = \'block\';',
' resultContainer.classList.remove(\'show\');',
' // 先获取图片并转换为base64',
' let imageData;',
' try {',
' const imgResponse = await fetch(imageUrl);',
' const blob = await imgResponse.blob();',
' imageData = await new Promise((resolve) => {',
' const reader = new FileReader();',
' reader.onloadend = () => resolve(reader.result);',
' reader.readAsDataURL(blob);',
' });',
' } catch (error) {',
' throw new Error(\'图片URL无法访问或格式不正确\');',
' }',
' // 调用URL识别API',
' const response = await fetch(\'/api/recognize/url\', {',
' method: \'POST\',',
' headers: { \'Content-Type\': \'application/json\' },',
' body: JSON.stringify({',
' token: currentToken,',
' imageUrl: imageUrl',
' })',
' });',
' const data = await response.json();',
' if (!data.success) {',
' throw new Error(data.error || \'识别失败\');',
' }',
' // 显示预览图',
' previewImage.src = imageData; // 使用base64数据',
' previewImage.style.display = \'block\';',
' // 显示结果',
' const result = data.result;',
' resultDiv.setAttribute(\'data-original-text\', result);',
' resultDiv.innerHTML = result;',
' waitForMathJax(() => {',
' try {',
' MathJax.typesetPromise([resultDiv])',
' .then(() => {',
' resultContainer.classList.add(\'show\');',
' })',
' .catch(err => {',
' console.error("MathJax渲染错误:", err);',
' resultContainer.classList.add(\'show\');',
' });',
' } catch (err) {',
' console.error("MathJax处理错误:", err);',
' resultContainer.classList.add(\'show\');',
' }',
' });',
' // 添加到历史记录 - 使用base64图片数据而不是URL',
' historyManager.addHistory(currentToken, imageData, result);',
' } catch (error) {',
' resultDiv.textContent = \'处理失败: \' + error.message;',
' resultContainer.classList.add(\'show\');',
' console.error(\'URL处理错误:\', error);',
' } finally {',
' loading.style.display = \'none\';',
' }',
' }',
' }, 1000)); // 1秒防抖',
' // 防抖函数',
' function debounce(func, wait) {',
' let timeout;',
' return function executedFunction(...args) {',
' const later = () => {',
' clearTimeout(timeout);',
' func(...args);',
' };',
' clearTimeout(timeout);',
' timeout = setTimeout(later, wait);',
' };',
' }',
' // 加载保存的cookie',
' function loadSettings() {',
' const savedCookie = localStorage.getItem(\'imageRecognitionCookie\');',
' if (savedCookie) {',
' document.getElementById(\'cookieInput\').value = savedCookie;',
' const tokenMatch = savedCookie.match(/token=([^;]+)/);',
' if (tokenMatch) {',
' const token = tokenMatch[1];',
' tokens = [token];',
' currentToken = token;',
' document.getElementById(\'currentTokenDisplay\').textContent = ',
' token.slice(0, 10) + "..." + token.slice(-10);',
' updateTokenExpiryDisplay(token);', // 修改这行,确保正确的字符串格式
' }',
' }',
' }',
' // 初始化时调用loadSettings',
' loadSettings();',
' // 高级模式切换处理',
' const advancedMode = document.getElementById(\'advancedMode\');',
' const promptContainer = document.getElementById(\'promptContainer\');',
' const promptInput = document.getElementById(\'promptInput\');',
' advancedMode.addEventListener(\'change\', () => {',
' promptContainer.classList.toggle(\'show\', advancedMode.checked);',
' localStorage.setItem(\'advancedMode\', advancedMode.checked);',
' localStorage.setItem(\'customPrompt\', promptInput.value);',
' });',
' // 加载保存的高级模式设置',
' function loadAdvancedSettings() {',
' const savedMode = localStorage.getItem(\'advancedMode\');',
' const savedPrompt = localStorage.getItem(\'customPrompt\');',
' if (savedMode === \'true\') {',
' advancedMode.checked = true;',
' promptContainer.classList.add(\'show\');',
' }',
' if (savedPrompt) {',
' promptInput.value = savedPrompt;',
' }',
' }',
' loadAdvancedSettings();',
' // JWT解析函数',
' function parseJwt(token) {',
' try {',
' const base64Url = token.split(\'.\')[1];',
' const base64 = base64Url.replace(/-/g, \'+\').replace(/_/g, \'/\');',
' const jsonPayload = decodeURIComponent(atob(base64).split(\'\').map(function(c) {',
' return \'%\' + (\'00\' + c.charCodeAt(0).toString(16)).slice(-2);',
' }).join(\'\'));',
' return JSON.parse(jsonPayload);',
' } catch (e) {',
' console.error(\'JWT解析错误:\', e);',
' return null;',
' }',
' }',
' // 更新Token过期时间显示',
' function updateTokenExpiryDisplay(token) {',
' const expiryDisplay = document.getElementById(\'tokenExpiryDisplay\');',
' if (!token) {',
' expiryDisplay.textContent = \'未设置\';',
' expiryDisplay.classList.remove(\'expired\');',
' return;',
' }',
' ',
' const decoded = parseJwt(token);',
' if (!decoded || !decoded.exp) {',
' expiryDisplay.textContent = \'无法解析过期时间\';',
' return;',
' }',
' ',
' const expiryDate = new Date(decoded.exp * 1000);',
' const now = new Date();',
' const isExpired = expiryDate < now;',
' ',
' expiryDisplay.textContent = expiryDate.toLocaleString(\'zh-CN\', {',
' year: \'numeric\',',
' month: \'2-digit\',',
' day: \'2-digit\',',
' hour: \'2-digit\',',
' minute: \'2-digit\',',
' second: \'2-digit\'',
' });',
' ',
' if (isExpired) {',
' expiryDisplay.classList.add(\'expired\');',
' expiryDisplay.textContent += \' (已过期)\';',
' } else {',
' expiryDisplay.classList.remove(\'expired\');',
' }',
' }',
'</script>',
'</body>',
'</html>'
].join('\n');
return html;
}