ocr-based-qwen/worker.js

2099 lines
83 KiB
JavaScript
Raw Permalink Normal View History

2025-01-14 18:20:37 +08:00
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
2025-01-14 18:20:37 +08:00
// 处理 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', // 添加自定义请求头
2025-01-14 18:20:37 +08:00
},
});
}
// 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;
2025-01-14 18:20:37 +08:00
// 返回前端界面
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');
2025-01-14 18:20:37 +08:00
if (!cookie || !imageUrl) {
return new Response(JSON.stringify({
error: 'Missing cookie or imageUrl'
2025-01-14 18:20:37 +08:00
}), {
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];
2025-01-14 18:20:37 +08:00
// 下载图片
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
2025-01-14 18:20:37 +08:00
},
body: formData,
});
const uploadData = await uploadResponse.json();
if (!uploadData.id) throw new Error('File upload failed');
// 调用通用识别函数
return await recognizeImage(token, uploadData.id, request);
2025-01-14 18:20:37 +08:00
} catch (error) {
return new Response(JSON.stringify({
error: error.message || 'Internal Server Error'
2025-01-14 18:20:37 +08:00
}), {
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');
2025-01-14 18:20:37 +08:00
if (!cookie || !base64Image) {
return new Response(JSON.stringify({
error: 'Missing cookie or base64Image'
2025-01-14 18:20:37 +08:00
}), {
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;
2025-01-14 18:20:37 +08:00
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
2025-01-14 18:20:37 +08:00
},
body: formData,
});
const uploadData = await uploadResponse.json();
if (!uploadData.id) throw new Error('File upload failed');
// 调用通用识别函数
return await recognizeImage(token, uploadData.id, request);
2025-01-14 18:20:37 +08:00
} catch (error) {
return new Response(JSON.stringify({
error: error.message || 'Internal Server Error'
2025-01-14 18:20:37 +08:00
}), {
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') || '';
2025-01-14 18:20:37 +08:00
if (!cookie || !imageId) {
return new Response(JSON.stringify({
error: 'Missing cookie or imageId'
2025-01-14 18:20:37 +08:00
}), {
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);
2025-01-14 18:20:37 +08:00
} catch (error) {
return new Response(JSON.stringify({
error: error.message || 'Internal Server Error'
2025-01-14 18:20:37 +08:00
}), {
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': '*',
},
});
}
}
2025-01-14 18:20:37 +08:00
// 通用的识别函数
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' +
'';
2025-01-14 18:20:37 +08:00
const response = await fetch('https://chat.qwenlm.ai/api/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'PostmanRuntime/7.43.0',
2025-01-14 18:20:37 +08:00
'accept': '*/*',
'authorization': `Bearer ${token}`,
'cookie': cookie,
2025-01-14 18:20:37 +08:00
},
body: JSON.stringify({
stream: false,
chat_type: "t2t",
model: 'qwen-max-latest',
2025-01-14 18:20:37 +08:00
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: advancedMode ? customPrompt : defaultPrompt,
chat_type: "t2t"
},
{
type: 'image',
image: imageId,
chat_type: "t2t"
2025-01-14 18:20:37 +08:00
},
],
},
],
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': '*',
},
});
}
2025-01-14 18:20:37 +08:00
// 其他情况(数学公式和普通文本)的处理
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();
}
2025-01-14 18:20:37 +08:00
return new Response(JSON.stringify({
2025-01-14 18:20:37 +08:00
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">',
2025-01-18 14:34:47 +08:00
'<link rel="icon" type="image/png" href="data:image/png;base64,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
2025-01-14 18:20:37 +08:00
'<title>Qwen VL 智能识别系统</title>',
2025-01-14 18:20:37 +08:00
// 添加 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>',
2025-01-14 18:20:37 +08:00
'<style>',
' * {',
' box-sizing: border-box;',
' margin: 0;',
' padding: 0;',
' }',
2025-01-14 18:20:37 +08:00
' 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;',
' }',
2025-01-14 18:20:37 +08:00
' .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;',
' }',
2025-01-14 18:20:37 +08:00
' 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;',
' }',
2025-01-14 18:20:37 +08:00
' @keyframes titleFadeIn {',
' from {',
' opacity: 0;',
' transform: translateY(-20px);',
' }',
' to {',
' opacity: 1;',
' transform: translateY(0);',
' }',
' }',
2025-01-14 18:20:37 +08:00
' h1::after {',
' content: "";',
' position: absolute;',
' bottom: 0;',
' left: 50%;',
' transform: translateX(-50%);',
' width: 100px;',
' height: 3px;',
' background: linear-gradient(90deg, transparent, #3498db, transparent);',
' }',
2025-01-14 18:20:37 +08:00
' .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;',
' }',
2025-01-14 18:20:37 +08:00
' @keyframes subtitleFadeIn {',
' from {',
' opacity: 0;',
' transform: translateY(10px);',
' }',
' to {',
' opacity: 0.8;',
' transform: translateY(0);',
' }',
' }',
2025-01-14 18:20:37 +08:00
' .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;',
' }',
2025-01-14 18:20:37 +08:00
' .upload-area:hover {',
' border-color: #3498db;',
' background: rgba(52, 152, 219, 0.05);',
' }',
2025-01-14 18:20:37 +08:00
' .upload-area.dragover {',
' border-color: #3498db;',
' background: rgba(52, 152, 219, 0.1);',
' transform: scale(1.02);',
' }',
2025-01-14 18:20:37 +08:00
' .upload-area i {',
' font-size: 2rem;',
' color: #8e9eab;',
' margin-bottom: 1rem;',
' }',
2025-01-14 18:20:37 +08:00
' .upload-text {',
' color: #7f8c8d;',
' font-size: 0.9rem;',
' }',
2025-01-14 18:20:37 +08:00
' #tokens {',
' width: 100%;',
' padding: 0.8rem;',
' border: 1px solid #dcdde1;',
' border-radius: 8px;',
' margin-bottom: 1rem;',
' font-size: 0.9rem;',
' resize: none;',
' }',
2025-01-14 18:20:37 +08:00
' .result-container {',
' margin-top: 1.5rem;',
' opacity: 0;',
' transform: translateY(20px);',
' transition: all 0.3s ease;',
' }',
2025-01-14 18:20:37 +08:00
' .result-container.show {',
' opacity: 1;',
' transform: translateY(0);',
' }',
2025-01-14 18:20:37 +08:00
' .result {',
' background: #f8f9fa;',
' padding: 1.2rem;',
' border-radius: 8px;',
' color: #2c3e50;',
' font-size: 1rem;',
' line-height: 1.6;',
' white-space: pre-wrap;',
' }',
2025-01-14 18:20:37 +08:00
' .loading {',
' display: none;',
' text-align: center;',
' margin: 1rem 0;',
' }',
2025-01-14 18:20:37 +08:00
' .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;',
' }',
2025-01-14 18:20:37 +08:00
' @keyframes spin {',
' to { transform: rotate(360deg); }',
' }',
2025-01-14 18:20:37 +08:00
' .preview-image {',
' max-width: 100%;',
' max-height: 200px;',
' margin: 1rem 0;',
' border-radius: 8px;',
' display: none;',
' }',
2025-01-14 18:20:37 +08:00
' /* 侧边栏样式 */',
' .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;',
' }',
2025-01-14 18:20:37 +08:00
' .sidebar.open {',
' right: 0;',
' }',
2025-01-14 18:20:37 +08:00
' .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;',
' }',
2025-01-14 18:20:37 +08:00
' .token-list {',
' margin-top: 20px;',
' }',
2025-01-14 18:20:37 +08:00
' .token-item {',
' background: #f8f9fa;',
' padding: 10px;',
' margin-bottom: 10px;',
' border-radius: 5px;',
' cursor: pointer;',
' word-break: break-all;',
' }',
2025-01-14 18:20:37 +08:00
' .token-item:hover {',
' background: #e9ecef;',
' }',
2025-01-14 18:20:37 +08:00
' #tokenInput {',
' width: 100%;',
' padding: 10px;',
' margin-bottom: 10px;',
' border: 1px solid #dcdde1;',
' border-radius: 5px;',
' }',
2025-01-14 18:20:37 +08:00
' .save-btn {',
' background: #3498db;',
' color: white;',
' border: none;',
' padding: 10px 15px;',
' border-radius: 5px;',
' cursor: pointer;',
' width: 100%;',
' }',
2025-01-14 18:20:37 +08:00
' /* 历史记录样式 */',
' .history-container {',
' margin-top: 2rem;',
' border-top: 1px solid #eee;',
' padding-top: 1rem;',
' }',
2025-01-14 18:20:37 +08:00
' .history-title {',
' color: #2c3e50;',
' font-size: 1.2rem;',
' margin-bottom: 1rem;',
' }',
2025-01-14 18:20:37 +08:00
' .history-item {',
' display: flex;',
' align-items: flex-start;',
' padding: 1rem;',
' background: #f8f9fa;',
' border-radius: 8px;',
' margin-bottom: 1rem;',
' }',
2025-01-14 18:20:37 +08:00
' .history-image {',
' width: 100px;',
' height: 100px;',
' object-fit: cover;',
' border-radius: 4px;',
' margin-right: 1rem;',
' }',
2025-01-14 18:20:37 +08:00
' .history-content {',
' flex: 1;',
' }',
2025-01-14 18:20:37 +08:00
' .history-text {',
' color: #2c3e50;',
' font-size: 0.9rem;',
' line-height: 1.4;',
' }',
2025-01-14 18:20:37 +08:00
' .history-time {',
' color: #7f8c8d;',
' font-size: 0.8rem;',
' margin-top: 0.5rem;',
' }',
2025-01-14 18:20:37 +08:00
' .no-history {',
' text-align: center;',
' color: #7f8c8d;',
' padding: 1rem;',
' }',
2025-01-14 18:20:37 +08:00
' .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;',
' }',
2025-01-14 18:20:37 +08:00
' .modal-content {',
' max-width: 90%;',
' max-height: 90vh;',
' margin: auto;',
' display: block;',
' position: relative;',
' top: 50%;',
' transform: translateY(-50%);',
' }',
2025-01-14 18:20:37 +08:00
' /* 修改侧边栏样式 */',
' .sidebar {',
' position: fixed;',
' right: -400px;',
2025-01-14 18:20:37 +08:00
' top: 0;',
' width: 400px;',
2025-01-14 18:20:37 +08:00
' 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;',
' }',
2025-01-14 18:20:37 +08:00
' .history-sidebar.open {',
' left: 0;',
' }',
2025-01-14 18:20:37 +08:00
' .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;',
' }',
2025-01-14 18:20:37 +08:00
' /* 添加复制按钮样式 */',
' .result-header {',
' display: flex;',
' justify-content: space-between;',
' align-items: center;',
' margin-bottom: 10px;',
' }',
2025-01-14 18:20:37 +08:00
' .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;',
' }',
2025-01-14 18:20:37 +08:00
' .copy-btn:hover {',
' background: #2980b9;',
' }',
2025-01-14 18:20:37 +08:00
' .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;',
' }',
2025-01-14 18:20:37 +08:00
' .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;',
' }',
2025-01-14 18:20:37 +08:00
'</style>',
'</head>',
'<body>',
'<a href="https://chat.qwenlm.ai/" target="_blank" rel="noopener noreferrer" class="get-cookie-link">',
' 获取Cookie',
'</a>',
2025-01-18 14:34:47 +08:00
'<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>',
2025-01-14 18:20:37 +08:00
'<div class="sidebar" id="sidebar">',
'<div class="sidebar-header">',
'<h2>Cookie管理</h2>',
2025-01-14 18:20:37 +08:00
'<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>',
2025-01-14 18:20:37 +08:00
'</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>',
2025-01-14 18:20:37 +08:00
'</div>',
2025-01-14 18:20:37 +08:00
'<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>',
'支持多种输入方式',
2025-01-14 18:20:37 +08:00
'</div>',
'<div class="input-controls">',
2025-01-14 18:20:37 +08:00
'<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>',
2025-01-14 18:20:37 +08:00
'<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>',
2025-01-14 18:20:37 +08:00
'<div id="imageModal" class="modal">',
'<img class="modal-content" id="modalImage">',
'</div>',
2025-01-14 18:20:37 +08:00
'<script>',
' // 首先定义类',
' function HistoryManager() {',
' this.maxHistory = 10;',
' }',
2025-01-14 18:20:37 +08:00
' // 添加原型方法',
' HistoryManager.prototype.getHistoryKey = function(token) {',
' return \'imageRecognition_history_\' + token;',
' };',
2025-01-14 18:20:37 +08:00
' HistoryManager.prototype.loadHistory = function(token) {',
' const history = localStorage.getItem(this.getHistoryKey(token));',
' return history ? JSON.parse(history) : [];',
' };',
2025-01-14 18:20:37 +08:00
' HistoryManager.prototype.saveHistory = function(token, history) {',
' localStorage.setItem(this.getHistoryKey(token), JSON.stringify(history));',
' };',
2025-01-14 18:20:37 +08:00
' HistoryManager.prototype.addHistory = function(token, imageData, result) {',
' const history = this.loadHistory(token);',
' const newRecord = {',
' image: imageData,',
' result: result,',
' timestamp: new Date().toISOString()',
' };',
2025-01-14 18:20:37 +08:00
' history.unshift(newRecord);',
' if (history.length > this.maxHistory) {',
' history.pop();',
' }',
2025-01-14 18:20:37 +08:00
' this.saveHistory(token, history);',
' this.displayHistory(token);',
' };',
2025-01-14 18:20:37 +08:00
' HistoryManager.prototype.displayHistory = function(token) {',
' const history = this.loadHistory(token);',
2025-01-14 18:20:37 +08:00
' if (history.length === 0) {',
' historyList.innerHTML = \'<div class="no-history">暂无识别历史</div>\';',
' return;',
' }',
2025-01-14 18:20:37 +08:00
' var html = \'\';',
' history.forEach((record, i) => {',
' // 确保 image 数据存在且格式正确',
' const imageUrl = record.image && (',
' record.image.startsWith(\'data:\') ? ',
' record.image : ',
' `data:image/png;base64,${record.image}`',
' );',
2025-01-14 18:20:37 +08:00
' const timestamp = new Date(record.timestamp);',
' const timeStr = timestamp.toLocaleString(\'zh-CN\', {',
' year: \'numeric\',',
' month: \'2-digit\',',
' day: \'2-digit\',', // Add comma here
2025-01-14 18:20:37 +08:00
' hour: \'2-digit\',',
' minute: \'2-digit\'',
' });',
2025-01-14 18:20:37 +08:00
' 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>',
2025-01-14 18:20:37 +08:00
' <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>',
2025-01-14 18:20:37 +08:00
' </div>',
' </div>',
' `;',
' });',
2025-01-14 18:20:37 +08:00
' historyList.innerHTML = html;',
2025-01-14 18:20:37 +08:00
' // 使用 waitForMathJax 函数处理公式渲染',
' waitForMathJax(() => {',
' try {',
' MathJax.typesetPromise([historyList])',
' .catch(err => console.error("MathJax渲染错误:", err));',
' } catch (err) {',
' console.error("MathJax处理错误:", err);',
' }',
' });',
' };',
2025-01-14 18:20:37 +08:00
' // 初始化变量',
' 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\');',
2025-01-14 18:20:37 +08:00
' let currentToken = \'\';',
' let tokens = [];',
' const historyManager = new HistoryManager();',
2025-01-14 18:20:37 +08:00
' // 从localStorage加载保存的tokens',
' function loadTokens() {',
' const savedTokens = localStorage.getItem(\'imageRecognitionTokens\');',
' if (savedTokens) {',
' tokens = savedTokens.split(\',\');',
' updateTokenList();',
' if (tokens.length > 0) {',
' currentToken = tokens[0];',
' }',
' }',
' }',
2025-01-14 18:20:37 +08:00
' // 修改 updateTokenList 函数',
' function updateTokenList() {',
' const tokenList = document.getElementById(\'tokenList\');',
' if (!tokenList) {',
' console.error(\'找不到tokenList元素\');',
' return;',
' }',
2025-01-14 18:20:37 +08:00
' 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(",");',
' }',
2025-01-14 18:20:37 +08:00
' }',
2025-01-14 18:20:37 +08:00
' // 保存tokens',
' saveTokensBtn.addEventListener(\'click\', () => {',
' const cookieValue = document.getElementById(\'cookieInput\').value.trim();',
' if (!cookieValue) {',
' alert(\'请输入Cookie\');',
' return;',
2025-01-14 18:20:37 +08:00
' }',
' ',
' // 从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(\'设置已保存\');',
2025-01-14 18:20:37 +08:00
' });',
2025-01-14 18:20:37 +08:00
' // 侧边栏开关',
' sidebarToggle.addEventListener(\'click\', () => {',
' sidebar.classList.toggle(\'open\');',
' });',
2025-01-14 18:20:37 +08:00
' // 处理文件上传和识别',
' 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;',
' }',
2025-01-14 18:20:37 +08:00
' // 显示图片预览',
' const reader = new FileReader();',
' let imageData;',
' reader.onload = (e) => {',
' imageData = e.target.result;',
' previewImage.src = imageData;',
' previewImage.style.display = \'block\';',
' };',
' reader.readAsDataURL(file);',
2025-01-14 18:20:37 +08:00
' // 显示加载动画',
' loading.style.display = \'block\';',
' resultContainer.classList.remove(\'show\');',
2025-01-14 18:20:37 +08:00
' try {',
' // 上传文件',
' const formData = new FormData();',
' formData.append(\'file\', file);',
' const uploadResponse = await fetch(\'/proxy/upload\', {',
2025-01-14 18:20:37 +08:00
' method: \'POST\',',
' headers: {',
' \'x-custom-cookie\': savedCookie,',
2025-01-14 18:20:37 +08:00
' },',
' body: formData,',
' });',
2025-01-14 18:20:37 +08:00
' const uploadData = await uploadResponse.json();',
' if (!uploadData.id) throw new Error(\'文件上传失败\');',
2025-01-14 18:20:37 +08:00
' // 识别图片',
' 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 }),',
2025-01-14 18:20:37 +08:00
' });',
2025-01-14 18:20:37 +08:00
' const recognizeData = await recognizeResponse.json();',
' if (!recognizeData.success) {',
' throw new Error(recognizeData.error || \'识别失败\');',
' }',
2025-01-14 18:20:37 +08:00
' const result = recognizeData.result || \'识别失败\';',
' resultDiv.setAttribute(\'data-original-text\', result);',
2025-01-14 18:20:37 +08:00
' 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\');',
' }',
' });',
2025-01-14 18:20:37 +08:00
' // 添加到历史记录',
' 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\';',
' }',
' }',
2025-01-14 18:20:37 +08:00
' // 文件拖放处理',
' uploadArea.addEventListener(\'dragover\', (e) => {',
' e.preventDefault();',
' uploadArea.classList.add(\'dragover\');',
' });',
2025-01-14 18:20:37 +08:00
' uploadArea.addEventListener(\'dragleave\', () => {',
' uploadArea.classList.remove(\'dragover\');',
' });',
2025-01-14 18:20:37 +08:00
' uploadArea.addEventListener(\'drop\', (e) => {',
' e.preventDefault();',
' uploadArea.classList.remove(\'dragover\');',
' const file = e.dataTransfer.files[0];',
' if (file && file.type.startsWith(\'image/\')) {',
' processImage(file);',
' }',
' });',
2025-01-14 18:20:37 +08:00
' // 点击上传',
' uploadArea.addEventListener(\'click\', (e) => {',
' // 如果点击的是 base64Input 或 toggleBase64 按钮,不触发文件上传',
' if (e.target.id === \'base64Input\' || ',
' e.target.id === \'toggleBase64\' || ',
' e.target.closest(\'#base64Input\') || ',
' e.target.closest(\'#toggleBase64\')) {',
' return;',
' }',
2025-01-14 18:20:37 +08:00
' 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();',
' });',
2025-01-14 18:20:37 +08:00
' // 粘贴处理',
' document.addEventListener(\'paste\', (e) => {',
' const file = e.clipboardData.files[0];',
' if (file && file.type.startsWith(\'image/\')) {',
' processImage(file);',
' }',
' });',
2025-01-14 18:20:37 +08:00
' // 初始化',
' loadTokens();',
' if (currentToken) {',
' historyManager.displayHistory(currentToken);',
' }',
2025-01-14 18:20:37 +08:00
' const modal = document.getElementById(\'imageModal\');',
' const modalImg = document.getElementById(\'modalImage\');',
2025-01-14 18:20:37 +08:00
' function showFullImage(src) {',
' const modal = document.getElementById(\'imageModal\');',
' const modalImg = document.getElementById(\'modalImage\');',
2025-01-14 18:20:37 +08:00
' if (!src) {',
' console.error(\'图片源为空\');',
' return;',
' }',
2025-01-14 18:20:37 +08:00
' modal.style.display = \'block\';',
' modalImg.src = src;',
2025-01-14 18:20:37 +08:00
' // 添加加载错误处理',
' modalImg.onerror = function() {',
' alert(\'图片加载失败\');',
' modal.style.display = \'none\';',
' };',
2025-01-14 18:20:37 +08:00
' modalImg.style.opacity = \'0\';',
' setTimeout(() => {',
' modalImg.style.transition = \'opacity 0.3s ease\';',
' modalImg.style.opacity = \'1\';',
' }, 50);',
' }',
2025-01-14 18:20:37 +08:00
' // 点击模态框关闭',
' modal.onclick = function() {',
' modal.style.display = "none";',
' }',
2025-01-14 18:20:37 +08:00
' // ESC 键关闭模态框',
' document.addEventListener(\'keydown\', function(e) {',
' if (e.key === \'Escape\' && modal.style.display === \'block\') {',
' modal.style.display = \'none\';',
' }',
' });',
2025-01-14 18:20:37 +08:00
' // 左侧历史记录边栏开关',
' historyToggle.addEventListener(\'click\', () => {',
' historySidebar.classList.toggle(\'open\');',
' });',
2025-01-14 18:20:37 +08:00
' const copyBtn = document.getElementById(\'copyBtn\');',
' // 修改复制结果功能,保持完整的 LaTeX 格式',
2025-01-14 18:20:37 +08:00
' copyBtn.addEventListener(\'click\', async () => {',
' // 获取原始文本(包含完整的 LaTeX 格式)',
' const result = resultDiv.getAttribute(\'data-original-text\');',
' if (!result) return;',
2025-01-14 18:20:37 +08:00
' try {',
' // 直接复制包含 LaTeX 标记的文本',
2025-01-14 18:20:37 +08:00
' await navigator.clipboard.writeText(result);',
' copyBtn.textContent = \'已复制\';',
' copyBtn.classList.add(\'copied\');',
' setTimeout(() => {',
' copyBtn.textContent = \'复制结果\';',
' copyBtn.classList.remove(\'copied\');',
' }, 2000);',
' } catch (err) {',
' console.error(\'复制失败:\', err);',
' }',
' });',
2025-01-14 18:20:37 +08:00
' // 添加关闭侧边栏的功能',
' document.getElementById("closeSidebar").addEventListener("click", () => {',
' sidebar.classList.remove("open");',
' });',
2025-01-14 18:20:37 +08:00
' // Base64 输入相关功能',
' const base64Input = document.getElementById(\'base64Input\');',
' const toggleBase64 = document.getElementById(\'toggleBase64\');',
2025-01-14 18:20:37 +08:00
' // 切换 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输入\';',
' }',
' });',
2025-01-14 18:20:37 +08:00
' // 为 base64Input 添加阻止事件冒泡',
' document.getElementById(\'base64Input\').addEventListener(\'click\', (e) => {',
' e.stopPropagation(); // 阻止事件冒泡到 uploadArea',
' });',
2025-01-14 18:20:37 +08:00
' // 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;',
' }',
2025-01-14 18:20:37 +08:00
' // 验证Base64是否为有效图片',
' const img = new Image();',
' img.src = imageData;',
' await new Promise((resolve, reject) => {',
' img.onload = resolve;',
' img.onerror = reject;',
' });',
2025-01-14 18:20:37 +08:00
' // 转换Base64为Blob',
' const response = await fetch(imageData);',
' const blob = await response.blob();',
' const file = new File([blob], "image.png", { type: "image/png" });',
2025-01-14 18:20:37 +08:00
' // 显示预览',
' previewImage.src = imageData;',
' previewImage.style.display = \'block\';',
2025-01-14 18:20:37 +08:00
' // 处理图片',
' 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);',
2025-01-14 18:20:37 +08:00
' btn.textContent = \'已复制\';',
' btn.classList.add(\'copied\');',
2025-01-14 18:20:37 +08:00
' setTimeout(() => {',
' btn.textContent = \'复制结果\';',
' btn.classList.remove(\'copied\');',
' }, 2000);',
' return true;',
2025-01-14 18:20:37 +08:00
' } catch (err) {',
' console.error(\'复制失败:\', err);',
' alert(\'复制失败: \' + err.message);',
' return false;',
2025-01-14 18:20:37 +08:00
' }',
' }',
2025-01-14 18:20:37 +08:00
' // 删除历史记录项',
' function deleteHistoryItem(index) {',
' const history = historyManager.loadHistory(currentToken);',
' if (!history[index]) {',
' alert(\'该记录不存在\');',
' return;',
' }',
2025-01-14 18:20:37 +08:00
' 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\');',
' }',
' }',
2025-01-14 18:20:37 +08:00
'</script>',
'</body>',
'</html>'
].join('\n');
return html;
}