This commit is contained in:
Cunninger 2025-01-14 18:25:37 +08:00
commit 657f37a90e
3 changed files with 308 additions and 137 deletions

32
LICENSE Normal file
View File

@ -0,0 +1,32 @@
重要声明
由于本项目是对 QwenLM 的逆向工程实现,仅供学习和研究使用。任何商业用途或滥用行为均与作者无关。请遵守相关法律法规和平台的使用条款。
开源协议 (LICENSE)
本项目采用 MIT 许可证,明确限制仅用于学习和研究目的。以下是 LICENSE 文件内容:
```
MIT License
Copyright (c) 2025 [cunninger]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. The Software is provided for educational and research purposes only.
Commercial use is strictly prohibited.
2. The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

138
README.md Normal file
View File

@ -0,0 +1,138 @@
# 🖼️ QwenLM OCR 逆向工程项目
本项目是对 [QwenLM](https://chat.qwenlm.ai/) 的 OCR 功能进行逆向工程的实现。通过调用 QwenLM 的 API你可以从图片中提取文字内容并且该项目支持一键部署到 **Cloudflare Workers** (CF) 上。
## 项目展示
![image](https://github.com/user-attachments/assets/6a292a81-98e4-4f6d-8759-a310c95499b1)
## 🚀 功能特性
- **图片 OCR**:使用 QwenLM 强大的 OCR 功能从图片中提取文字。
- **拖拽上传**:直接将图片拖拽到页面即可识别。
- **复制粘贴**:支持从剪贴板直接粘贴图片进行识别。
- **Token 管理**:支持多 Token 轮询使用,提升稳定性。
- **历史记录**:保存每次识别的结果和图片,方便查看。
- **一键复制**:轻松复制识别结果到剪贴板。
- **数学公式识别**:特别优化了对数学公式的提取,支持 LaTeX 格式输出。
- **API 支持**:提供 `curl` 接口调用,支持 base64 和图片 URL 两种方式。
- **验证码识别**:新增验证码识别功能,支持常见类型的验证码(如数字、字母、混合字符等),提升自动化处理能力。
## 🛠️ 部署指南
### 1. 部署到 Cloudflare Workers
1. **配置 Cloudflare Workers**
- 登录 [Cloudflare Dashboard](https://dash.cloudflare.com/)。
- 创建一个新的 Worker。
- 将 `worker.js` 中的代码复制到 Worker 编辑器中。
2. **部署**
- 保存并部署 Worker。
- 获取 Worker 的访问地址,即可使用。
## 🧩 使用说明
1. **设置 Token**
- 前往 [QwenLM](https://chat.qwenlm.ai/) 获取 Token。
- 点击右上角的 **⚙️ Token设置** 按钮。
- 输入你的 QwenLM API Token多个 Token 用英文逗号分隔)。
- 点击 **保存**
2. **上传图片**
- 拖拽图片到页面,或点击上传区域选择图片。
- 支持直接粘贴图片。
3. **查看结果**
- 识别结果会显示在页面下方。
- 点击 **复制结果** 按钮,将识别内容复制到剪贴板。
4. **查看历史记录**
- 点击左侧的 **📋 识别历史** 按钮,查看历史识别记录。
- 点击历史记录中的图片,可以查看大图。
5. **API 调用**
- **支持 base64**
```bash
curl --location 'https://ocr.doublefenzhuan.me/api/recognize/base64' \
--header 'Content-Type: application/json' \
--data '{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjUzZTk0Nzg4LWMwM2QtNDY4Mi05OTNhLWE0ZDNjNGUyZDY0OSIsImV4cCI6MTczOTA3NTE0MX0.FtwG6xDLYd2rngWUhuldg56WXCiLSTL0RI6xJJQ4vHM",
"base64Image": "xxx"
}'
```
- **支持图片 URL**:
```bash
curl --location 'https://ocr.doublefenzhuan.me/api/recognize/url' \
--header 'Content-Type: application/json' \
--data '{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjUzZTk0Nzg4LWMwM2QtNDY4Mi05OTNhLWE0ZDNjNGUyZDY0OSIsImV4cCI6MTczOTA3NTE0MX0.FtwG6xDLYd2rngWUhuldg56WXCiLSTL0RI6xJJQ4vHM",
"imageUrl": "xxxx"
}'
```
6. **验证码识别**
![image](https://github.com/user-attachments/assets/66f24d52-6263-446c-b371-cc2e65c9277c)
## 📜 许可证
本项目基于 MIT 许可证开源。详情请查看 [LICENSE](LICENSE) 文件。
## 🙏 致谢
- 感谢 [QwenLM](https://chat.qwenlm.ai/) 提供的 OCR 功能。
- 感谢 Cloudflare 提供的 Workers 服务。
---
🌟 如果觉得这个项目对你有帮助,欢迎点个 Star 支持一下!🌟
**体验地址**[智能图片识别 (doublefenzhuan.me)](https://ocr.doublefenzhuan.me/)
**GitHub 仓库**[Cunninger/ocr-based-qwen](https://github.com/Cunninger/ocr-based-qwen)
---
#### 后续计划
- 优化数学公式识别精度;
- 增加更多 API 功能支持;
- 提升识别速度和稳定性。
快来体验吧!如果有任何问题或建议,欢迎在 GitHub 上提 Issue 或直接联系我!
## 更新
### 2025/01/13 应佬友需求,优化了对数学公式的识别,效果如下图
- 原图:
![image](https://github.com/user-attachments/assets/9841509d-be56-4eb9-aafa-4d4ca5555c2e)
- 识别效果图:
![image](https://github.com/user-attachments/assets/2340dc6d-9156-4866-aa53-cdfd1911a651)
### 2025/01/13 18点34分 支持`curl`接口调用
- **支持base64**
```
curl --location 'https://ocr.doublefenzhuan.me/api/recognize/base64' \
--header 'Content-Type: application/json' \
--data '{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjUzZTk0Nzg4LWMwM2QtNDY4Mi05OTNhLWE0ZDNjNGUyZDY0OSIsImV4cCI6MTczOTA3NTE0MX0.FtwG6xDLYd2rngWUhuldg56WXCiLSTL0RI6xJJQ4vHM",
"base64Image": "xxx"
}'
```
- 效果图:
![image](https://github.com/user-attachments/assets/ef160aae-e741-49d3-96f0-a0969b883f1a)
- **支持图片URL**:
```bash
curl --location 'https://ocr.doublefenzhuan.me/api/recognize/url' \
--header 'Content-Type: application/json' \
--data '{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjUzZTk0Nzg4LWMwM2QtNDY4Mi05OTNhLWE0ZDNjNGUyZDY0OSIsImV4cCI6MTczOTA3NTE0MX0.FtwG6xDLYd2rngWUhuldg56WXCiLSTL0RI6xJJQ4vHM",
"imageUrl": "xxxx"
}'
```
- 效果图:
![image](https://github.com/user-attachments/assets/db0c89f9-96f1-45b1-b1e9-88ac3d01e196)
## 趋势
[![Star History Chart](https://api.star-history.com/svg?repos=cunninger/ocr-based-qwen&type=Date&v=20231001)](https://star-history.com/#Cunninger/ocr-based-qwen&Date)

275
worker.js
View File

@ -55,8 +55,8 @@ async function handleImageUrlRecognition(request) {
const { token, imageUrl } = await request.json(); const { token, imageUrl } = await request.json();
if (!token || !imageUrl) { if (!token || !imageUrl) {
return new Response(JSON.stringify({ return new Response(JSON.stringify({
error: 'Missing token or imageUrl' error: 'Missing token or imageUrl'
}), { }), {
status: 400, status: 400,
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -86,8 +86,8 @@ async function handleImageUrlRecognition(request) {
// 调用识别API // 调用识别API
return await recognizeImage(token, uploadData.id); return await recognizeImage(token, uploadData.id);
} catch (error) { } catch (error) {
return new Response(JSON.stringify({ return new Response(JSON.stringify({
error: error.message || 'Internal Server Error' error: error.message || 'Internal Server Error'
}), { }), {
status: 500, status: 500,
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -101,8 +101,8 @@ async function handleBase64Recognition(request) {
const { token, base64Image } = await request.json(); const { token, base64Image } = await request.json();
if (!token || !base64Image) { if (!token || !base64Image) {
return new Response(JSON.stringify({ return new Response(JSON.stringify({
error: 'Missing token or base64Image' error: 'Missing token or base64Image'
}), { }), {
status: 400, status: 400,
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -110,10 +110,10 @@ async function handleBase64Recognition(request) {
} }
// 转换Base64为Blob // 转换Base64为Blob
const imageData = base64Image.startsWith('data:') ? const imageData = base64Image.startsWith('data:') ?
base64Image : base64Image :
'data:image/png;base64,' + base64Image; 'data:image/png;base64,' + base64Image;
const response = await fetch(imageData); const response = await fetch(imageData);
const blob = await response.blob(); const blob = await response.blob();
@ -136,8 +136,8 @@ async function handleBase64Recognition(request) {
// 调用识别API // 调用识别API
return await recognizeImage(token, uploadData.id); return await recognizeImage(token, uploadData.id);
} catch (error) { } catch (error) {
return new Response(JSON.stringify({ return new Response(JSON.stringify({
error: error.message || 'Internal Server Error' error: error.message || 'Internal Server Error'
}), { }), {
status: 500, status: 500,
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -151,8 +151,8 @@ async function handleFileRecognition(request) {
const { token, imageId } = await request.json(); const { token, imageId } = await request.json();
if (!token || !imageId) { if (!token || !imageId) {
return new Response(JSON.stringify({ return new Response(JSON.stringify({
error: 'Missing token or imageId' error: 'Missing token or imageId'
}), { }), {
status: 400, status: 400,
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -161,8 +161,8 @@ async function handleFileRecognition(request) {
return await recognizeImage(token, imageId); return await recognizeImage(token, imageId);
} catch (error) { } catch (error) {
return new Response(JSON.stringify({ return new Response(JSON.stringify({
error: error.message || 'Internal Server Error' error: error.message || 'Internal Server Error'
}), { }), {
status: 500, status: 500,
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -186,8 +186,8 @@ async function recognizeImage(token, imageId) {
{ {
role: 'user', role: 'user',
content: [ content: [
{ {
type: 'text', type: 'text',
text: '请识别图片中的内容,注意以下要求:\n' + text: '请识别图片中的内容,注意以下要求:\n' +
'对于数学公式和普通文本:\n' + '对于数学公式和普通文本:\n' +
'1. 所有数学公式和数学符号都必须使用标准的LaTeX格式\n' + '1. 所有数学公式和数学符号都必须使用标准的LaTeX格式\n' +
@ -216,10 +216,10 @@ async function recognizeImage(token, imageId) {
const data = await response.json(); const data = await response.json();
let result = data.choices[0]?.message?.content || '识别失败'; let result = data.choices[0]?.message?.content || '识别失败';
// 如果结果长度小于10且只包含字母数字很可能是验证码 // 如果结果长度小于10且只包含字母数字很可能是验证码
if (result.length <= 10 && /^[A-Za-z0-9]+$/.test(result)) { if (result.length <= 10 && /^[A-Za-z0-9]+$/.test(result)) {
return new Response(JSON.stringify({ return new Response(JSON.stringify({
success: true, success: true,
result: result.toUpperCase(), // 验证码统一转大写 result: result.toUpperCase(), // 验证码统一转大写
type: 'captcha' type: 'captcha'
@ -242,7 +242,7 @@ async function recognizeImage(token, imageId) {
.replace(/\$\$/g, '$$') .replace(/\$\$/g, '$$')
.trim(); .trim();
return new Response(JSON.stringify({ return new Response(JSON.stringify({
success: true, success: true,
result: result, result: result,
type: 'text' type: 'text'
@ -262,7 +262,7 @@ function getHTML() {
'<meta charset="UTF-8">', '<meta charset="UTF-8">',
'<meta name="viewport" content="width=device-width, initial-scale=1.0">', '<meta name="viewport" content="width=device-width, initial-scale=1.0">',
'<title>Qwen VL 智能识别系统</title>', '<title>Qwen VL 智能识别系统</title>',
// 添加 MathJax 支持 // 添加 MathJax 支持
'<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>', '<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>',
'<script>', '<script>',
@ -301,14 +301,14 @@ function getHTML() {
' checkMathJax();', ' checkMathJax();',
'}', '}',
'</script>', '</script>',
'<style>', '<style>',
' * {', ' * {',
' box-sizing: border-box;', ' box-sizing: border-box;',
' margin: 0;', ' margin: 0;',
' padding: 0;', ' padding: 0;',
' }', ' }',
' body {', ' body {',
' font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Oxygen, Ubuntu, sans-serif;', ' font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Oxygen, Ubuntu, sans-serif;',
' background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);', ' background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);',
@ -318,7 +318,7 @@ function getHTML() {
' align-items: center;', ' align-items: center;',
' padding: 20px;', ' padding: 20px;',
' }', ' }',
' .container {', ' .container {',
' background: rgba(255, 255, 255, 0.95);', ' background: rgba(255, 255, 255, 0.95);',
' padding: 2.5rem;', ' padding: 2.5rem;',
@ -329,7 +329,7 @@ function getHTML() {
' max-width: 800px;', ' max-width: 800px;',
' transition: all 0.3s ease;', ' transition: all 0.3s ease;',
' }', ' }',
' h1 {', ' h1 {',
' color: #2c3e50;', ' color: #2c3e50;',
' margin-bottom: 0.5rem;', ' margin-bottom: 0.5rem;',
@ -346,7 +346,7 @@ function getHTML() {
' padding-bottom: 10px;', ' padding-bottom: 10px;',
' animation: titleFadeIn 1s ease-out;', ' animation: titleFadeIn 1s ease-out;',
' }', ' }',
' @keyframes titleFadeIn {', ' @keyframes titleFadeIn {',
' from {', ' from {',
' opacity: 0;', ' opacity: 0;',
@ -357,7 +357,7 @@ function getHTML() {
' transform: translateY(0);', ' transform: translateY(0);',
' }', ' }',
' }', ' }',
' h1::after {', ' h1::after {',
' content: "";', ' content: "";',
' position: absolute;', ' position: absolute;',
@ -368,7 +368,7 @@ function getHTML() {
' height: 3px;', ' height: 3px;',
' background: linear-gradient(90deg, transparent, #3498db, transparent);', ' background: linear-gradient(90deg, transparent, #3498db, transparent);',
' }', ' }',
' .subtitle {', ' .subtitle {',
' color: #7f8c8d;', ' color: #7f8c8d;',
' text-align: center;', ' text-align: center;',
@ -379,7 +379,7 @@ function getHTML() {
' opacity: 0.8;', ' opacity: 0.8;',
' animation: subtitleFadeIn 1s ease-out 0.3s both;', ' animation: subtitleFadeIn 1s ease-out 0.3s both;',
' }', ' }',
' @keyframes subtitleFadeIn {', ' @keyframes subtitleFadeIn {',
' from {', ' from {',
' opacity: 0;', ' opacity: 0;',
@ -390,7 +390,7 @@ function getHTML() {
' transform: translateY(0);', ' transform: translateY(0);',
' }', ' }',
' }', ' }',
' .upload-area {', ' .upload-area {',
' border: 2px dashed #8e9eab;', ' border: 2px dashed #8e9eab;',
' border-radius: 12px;', ' border-radius: 12px;',
@ -402,29 +402,29 @@ function getHTML() {
' position: relative;', ' position: relative;',
' overflow: hidden;', ' overflow: hidden;',
' }', ' }',
' .upload-area:hover {', ' .upload-area:hover {',
' border-color: #3498db;', ' border-color: #3498db;',
' background: rgba(52, 152, 219, 0.05);', ' background: rgba(52, 152, 219, 0.05);',
' }', ' }',
' .upload-area.dragover {', ' .upload-area.dragover {',
' border-color: #3498db;', ' border-color: #3498db;',
' background: rgba(52, 152, 219, 0.1);', ' background: rgba(52, 152, 219, 0.1);',
' transform: scale(1.02);', ' transform: scale(1.02);',
' }', ' }',
' .upload-area i {', ' .upload-area i {',
' font-size: 2rem;', ' font-size: 2rem;',
' color: #8e9eab;', ' color: #8e9eab;',
' margin-bottom: 1rem;', ' margin-bottom: 1rem;',
' }', ' }',
' .upload-text {', ' .upload-text {',
' color: #7f8c8d;', ' color: #7f8c8d;',
' font-size: 0.9rem;', ' font-size: 0.9rem;',
' }', ' }',
' #tokens {', ' #tokens {',
' width: 100%;', ' width: 100%;',
' padding: 0.8rem;', ' padding: 0.8rem;',
@ -434,19 +434,19 @@ function getHTML() {
' font-size: 0.9rem;', ' font-size: 0.9rem;',
' resize: none;', ' resize: none;',
' }', ' }',
' .result-container {', ' .result-container {',
' margin-top: 1.5rem;', ' margin-top: 1.5rem;',
' opacity: 0;', ' opacity: 0;',
' transform: translateY(20px);', ' transform: translateY(20px);',
' transition: all 0.3s ease;', ' transition: all 0.3s ease;',
' }', ' }',
' .result-container.show {', ' .result-container.show {',
' opacity: 1;', ' opacity: 1;',
' transform: translateY(0);', ' transform: translateY(0);',
' }', ' }',
' .result {', ' .result {',
' background: #f8f9fa;', ' background: #f8f9fa;',
' padding: 1.2rem;', ' padding: 1.2rem;',
@ -456,13 +456,13 @@ function getHTML() {
' line-height: 1.6;', ' line-height: 1.6;',
' white-space: pre-wrap;', ' white-space: pre-wrap;',
' }', ' }',
' .loading {', ' .loading {',
' display: none;', ' display: none;',
' text-align: center;', ' text-align: center;',
' margin: 1rem 0;', ' margin: 1rem 0;',
' }', ' }',
' .loading::after {', ' .loading::after {',
' content: \'\';', ' content: \'\';',
' display: inline-block;', ' display: inline-block;',
@ -473,11 +473,11 @@ function getHTML() {
' border-top-color: transparent;', ' border-top-color: transparent;',
' animation: spin 0.8s linear infinite;', ' animation: spin 0.8s linear infinite;',
' }', ' }',
' @keyframes spin {', ' @keyframes spin {',
' to { transform: rotate(360deg); }', ' to { transform: rotate(360deg); }',
' }', ' }',
' .preview-image {', ' .preview-image {',
' max-width: 100%;', ' max-width: 100%;',
' max-height: 200px;', ' max-height: 200px;',
@ -485,7 +485,7 @@ function getHTML() {
' border-radius: 8px;', ' border-radius: 8px;',
' display: none;', ' display: none;',
' }', ' }',
' /* 侧边栏样式 */', ' /* 侧边栏样式 */',
' .sidebar {', ' .sidebar {',
' position: fixed;', ' position: fixed;',
@ -499,11 +499,11 @@ function getHTML() {
' padding: 20px;', ' padding: 20px;',
' z-index: 1000;', ' z-index: 1000;',
' }', ' }',
' .sidebar.open {', ' .sidebar.open {',
' right: 0;', ' right: 0;',
' }', ' }',
' .sidebar-toggle {', ' .sidebar-toggle {',
' position: fixed;', ' position: fixed;',
' right: 20px;', ' right: 20px;',
@ -516,11 +516,11 @@ function getHTML() {
' cursor: pointer;', ' cursor: pointer;',
' z-index: 1001;', ' z-index: 1001;',
' }', ' }',
' .token-list {', ' .token-list {',
' margin-top: 20px;', ' margin-top: 20px;',
' }', ' }',
' .token-item {', ' .token-item {',
' background: #f8f9fa;', ' background: #f8f9fa;',
' padding: 10px;', ' padding: 10px;',
@ -529,11 +529,11 @@ function getHTML() {
' cursor: pointer;', ' cursor: pointer;',
' word-break: break-all;', ' word-break: break-all;',
' }', ' }',
' .token-item:hover {', ' .token-item:hover {',
' background: #e9ecef;', ' background: #e9ecef;',
' }', ' }',
' #tokenInput {', ' #tokenInput {',
' width: 100%;', ' width: 100%;',
' padding: 10px;', ' padding: 10px;',
@ -541,7 +541,7 @@ function getHTML() {
' border: 1px solid #dcdde1;', ' border: 1px solid #dcdde1;',
' border-radius: 5px;', ' border-radius: 5px;',
' }', ' }',
' .save-btn {', ' .save-btn {',
' background: #3498db;', ' background: #3498db;',
' color: white;', ' color: white;',
@ -551,20 +551,20 @@ function getHTML() {
' cursor: pointer;', ' cursor: pointer;',
' width: 100%;', ' width: 100%;',
' }', ' }',
' /* 历史记录样式 */', ' /* 历史记录样式 */',
' .history-container {', ' .history-container {',
' margin-top: 2rem;', ' margin-top: 2rem;',
' border-top: 1px solid #eee;', ' border-top: 1px solid #eee;',
' padding-top: 1rem;', ' padding-top: 1rem;',
' }', ' }',
' .history-title {', ' .history-title {',
' color: #2c3e50;', ' color: #2c3e50;',
' font-size: 1.2rem;', ' font-size: 1.2rem;',
' margin-bottom: 1rem;', ' margin-bottom: 1rem;',
' }', ' }',
' .history-item {', ' .history-item {',
' display: flex;', ' display: flex;',
' align-items: flex-start;', ' align-items: flex-start;',
@ -573,7 +573,7 @@ function getHTML() {
' border-radius: 8px;', ' border-radius: 8px;',
' margin-bottom: 1rem;', ' margin-bottom: 1rem;',
' }', ' }',
' .history-image {', ' .history-image {',
' width: 100px;', ' width: 100px;',
' height: 100px;', ' height: 100px;',
@ -581,29 +581,29 @@ function getHTML() {
' border-radius: 4px;', ' border-radius: 4px;',
' margin-right: 1rem;', ' margin-right: 1rem;',
' }', ' }',
' .history-content {', ' .history-content {',
' flex: 1;', ' flex: 1;',
' }', ' }',
' .history-text {', ' .history-text {',
' color: #2c3e50;', ' color: #2c3e50;',
' font-size: 0.9rem;', ' font-size: 0.9rem;',
' line-height: 1.4;', ' line-height: 1.4;',
' }', ' }',
' .history-time {', ' .history-time {',
' color: #7f8c8d;', ' color: #7f8c8d;',
' font-size: 0.8rem;', ' font-size: 0.8rem;',
' margin-top: 0.5rem;', ' margin-top: 0.5rem;',
' }', ' }',
' .no-history {', ' .no-history {',
' text-align: center;', ' text-align: center;',
' color: #7f8c8d;', ' color: #7f8c8d;',
' padding: 1rem;', ' padding: 1rem;',
' }', ' }',
' .modal {', ' .modal {',
' display: none;', ' display: none;',
' position: fixed;', ' position: fixed;',
@ -615,7 +615,7 @@ function getHTML() {
' z-index: 2000;', ' z-index: 2000;',
' cursor: pointer;', ' cursor: pointer;',
' }', ' }',
' .modal-content {', ' .modal-content {',
' max-width: 90%;', ' max-width: 90%;',
' max-height: 90vh;', ' max-height: 90vh;',
@ -625,13 +625,13 @@ function getHTML() {
' top: 50%;', ' top: 50%;',
' transform: translateY(-50%);', ' transform: translateY(-50%);',
' }', ' }',
' /* 修改侧边栏样式 */', ' /* 修改侧边栏样式 */',
' .sidebar {', ' .sidebar {',
' position: fixed;', ' position: fixed;',
' right: -400px;', ' right: -400px;',
' top: 0;', ' top: 0;',
' width: 400px;', ' width: 400px;',
' height: 100vh;', ' height: 100vh;',
' background: rgba(255, 255, 255, 0.95);', ' background: rgba(255, 255, 255, 0.95);',
' backdrop-filter: blur(10px);', ' backdrop-filter: blur(10px);',
@ -747,11 +747,11 @@ function getHTML() {
' z-index: 1000;', ' z-index: 1000;',
' overflow-y: auto;', ' overflow-y: auto;',
' }', ' }',
' .history-sidebar.open {', ' .history-sidebar.open {',
' left: 0;', ' left: 0;',
' }', ' }',
' .history-toggle {', ' .history-toggle {',
' position: fixed;', ' position: fixed;',
' left: 20px;', ' left: 20px;',
@ -764,7 +764,7 @@ function getHTML() {
' cursor: pointer;', ' cursor: pointer;',
' z-index: 1001;', ' z-index: 1001;',
' }', ' }',
' /* 添加复制按钮样式 */', ' /* 添加复制按钮样式 */',
' .result-header {', ' .result-header {',
' display: flex;', ' display: flex;',
@ -772,7 +772,7 @@ function getHTML() {
' align-items: center;', ' align-items: center;',
' margin-bottom: 10px;', ' margin-bottom: 10px;',
' }', ' }',
' .copy-btn {', ' .copy-btn {',
' background: #3498db;', ' background: #3498db;',
' color: white;', ' color: white;',
@ -783,11 +783,11 @@ function getHTML() {
' font-size: 0.9rem;', ' font-size: 0.9rem;',
' transition: background 0.3s ease;', ' transition: background 0.3s ease;',
' }', ' }',
' .copy-btn:hover {', ' .copy-btn:hover {',
' background: #2980b9;', ' background: #2980b9;',
' }', ' }',
' .copy-btn.copied {', ' .copy-btn.copied {',
' background: #27ae60;', ' background: #27ae60;',
' }', ' }',
@ -1018,7 +1018,7 @@ function getHTML() {
'</div>', '</div>',
'<div class="token-list" id="tokenList"></div>', '<div class="token-list" id="tokenList"></div>',
'</div>', '</div>',
'<div class="container">', '<div class="container">',
'<h1>Qwen VL 智能识别系统</h1>', '<h1>Qwen VL 智能识别系统</h1>',
'<div class="subtitle">基于通义千问大模型的多模态智能识别引擎</div>', '<div class="subtitle">基于通义千问大模型的多模态智能识别引擎</div>',
@ -1051,31 +1051,31 @@ function getHTML() {
'</div>', '</div>',
'</div>', '</div>',
'</div>', '</div>',
'<div id="imageModal" class="modal">', '<div id="imageModal" class="modal">',
'<img class="modal-content" id="modalImage">', '<img class="modal-content" id="modalImage">',
'</div>', '</div>',
'<script>', '<script>',
' // 首先定义类', ' // 首先定义类',
' function HistoryManager() {', ' function HistoryManager() {',
' this.maxHistory = 10;', ' this.maxHistory = 10;',
' }', ' }',
' // 添加原型方法', ' // 添加原型方法',
' HistoryManager.prototype.getHistoryKey = function(token) {', ' HistoryManager.prototype.getHistoryKey = function(token) {',
' return \'imageRecognition_history_\' + token;', ' return \'imageRecognition_history_\' + token;',
' };', ' };',
' HistoryManager.prototype.loadHistory = function(token) {', ' HistoryManager.prototype.loadHistory = function(token) {',
' const history = localStorage.getItem(this.getHistoryKey(token));', ' const history = localStorage.getItem(this.getHistoryKey(token));',
' return history ? JSON.parse(history) : [];', ' return history ? JSON.parse(history) : [];',
' };', ' };',
' HistoryManager.prototype.saveHistory = function(token, history) {', ' HistoryManager.prototype.saveHistory = function(token, history) {',
' localStorage.setItem(this.getHistoryKey(token), JSON.stringify(history));', ' localStorage.setItem(this.getHistoryKey(token), JSON.stringify(history));',
' };', ' };',
' HistoryManager.prototype.addHistory = function(token, imageData, result) {', ' HistoryManager.prototype.addHistory = function(token, imageData, result) {',
' const history = this.loadHistory(token);', ' const history = this.loadHistory(token);',
' const newRecord = {', ' const newRecord = {',
@ -1083,24 +1083,24 @@ function getHTML() {
' result: result,', ' result: result,',
' timestamp: new Date().toISOString()', ' timestamp: new Date().toISOString()',
' };', ' };',
' history.unshift(newRecord);', ' history.unshift(newRecord);',
' if (history.length > this.maxHistory) {', ' if (history.length > this.maxHistory) {',
' history.pop();', ' history.pop();',
' }', ' }',
' this.saveHistory(token, history);', ' this.saveHistory(token, history);',
' this.displayHistory(token);', ' this.displayHistory(token);',
' };', ' };',
' HistoryManager.prototype.displayHistory = function(token) {', ' HistoryManager.prototype.displayHistory = function(token) {',
' const history = this.loadHistory(token);', ' const history = this.loadHistory(token);',
' if (history.length === 0) {', ' if (history.length === 0) {',
' historyList.innerHTML = \'<div class="no-history">暂无识别历史</div>\';', ' historyList.innerHTML = \'<div class="no-history">暂无识别历史</div>\';',
' return;', ' return;',
' }', ' }',
' var html = \'\';', ' var html = \'\';',
' history.forEach((record, i) => {', ' history.forEach((record, i) => {',
' // 确保 image 数据存在且格式正确', ' // 确保 image 数据存在且格式正确',
@ -1109,7 +1109,7 @@ function getHTML() {
' record.image : ', ' record.image : ',
' `data:image/png;base64,${record.image}`', ' `data:image/png;base64,${record.image}`',
' );', ' );',
' const timestamp = new Date(record.timestamp);', ' const timestamp = new Date(record.timestamp);',
' const timeStr = timestamp.toLocaleString(\'zh-CN\', {', ' const timeStr = timestamp.toLocaleString(\'zh-CN\', {',
' year: \'numeric\',', ' year: \'numeric\',',
@ -1118,7 +1118,7 @@ function getHTML() {
' hour: \'2-digit\',', ' hour: \'2-digit\',',
' minute: \'2-digit\'', ' minute: \'2-digit\'',
' });', ' });',
' html += `', ' html += `',
' <div class="history-item" data-index="${i}">', ' <div class="history-item" data-index="${i}">',
' <div class="history-image-container">', ' <div class="history-image-container">',
@ -1144,9 +1144,9 @@ function getHTML() {
' </div>', ' </div>',
' `;', ' `;',
' });', ' });',
' historyList.innerHTML = html;', ' historyList.innerHTML = html;',
' // 使用 waitForMathJax 函数处理公式渲染', ' // 使用 waitForMathJax 函数处理公式渲染',
' waitForMathJax(() => {', ' waitForMathJax(() => {',
' try {', ' try {',
@ -1157,7 +1157,7 @@ function getHTML() {
' }', ' }',
' });', ' });',
' };', ' };',
' // 初始化变量', ' // 初始化变量',
' const uploadArea = document.getElementById(\'uploadArea\');', ' const uploadArea = document.getElementById(\'uploadArea\');',
' const tokensInput = document.getElementById(\'tokenInput\');', ' const tokensInput = document.getElementById(\'tokenInput\');',
@ -1173,11 +1173,11 @@ function getHTML() {
' const tokenList = document.getElementById(\'tokenList\');', ' const tokenList = document.getElementById(\'tokenList\');',
' const historySidebar = document.getElementById(\'historySidebar\');', ' const historySidebar = document.getElementById(\'historySidebar\');',
' const historyToggle = document.getElementById(\'historyToggle\');', ' const historyToggle = document.getElementById(\'historyToggle\');',
' let currentToken = \'\';', ' let currentToken = \'\';',
' let tokens = [];', ' let tokens = [];',
' const historyManager = new HistoryManager();', ' const historyManager = new HistoryManager();',
' // 从localStorage加载保存的tokens', ' // 从localStorage加载保存的tokens',
' function loadTokens() {', ' function loadTokens() {',
' const savedTokens = localStorage.getItem(\'imageRecognitionTokens\');', ' const savedTokens = localStorage.getItem(\'imageRecognitionTokens\');',
@ -1189,7 +1189,7 @@ function getHTML() {
' }', ' }',
' }', ' }',
' }', ' }',
' // 修改 updateTokenList 函数', ' // 修改 updateTokenList 函数',
' function updateTokenList() {', ' function updateTokenList() {',
' tokenList.innerHTML = "";', ' tokenList.innerHTML = "";',
@ -1208,7 +1208,7 @@ function getHTML() {
' });', ' });',
' tokenInput.value = tokens.join(",");', ' tokenInput.value = tokens.join(",");',
' }', ' }',
' // 保存tokens', ' // 保存tokens',
' saveTokensBtn.addEventListener(\'click\', () => {', ' saveTokensBtn.addEventListener(\'click\', () => {',
' const inputTokens = tokenInput.value.split(\',\').map(t => t.trim()).filter(t => t);', ' const inputTokens = tokenInput.value.split(\',\').map(t => t.trim()).filter(t => t);',
@ -1222,12 +1222,12 @@ function getHTML() {
' alert(\'请至少输入一个有效的Token\');', ' alert(\'请至少输入一个有效的Token\');',
' }', ' }',
' });', ' });',
' // 侧边栏开关', ' // 侧边栏开关',
' sidebarToggle.addEventListener(\'click\', () => {', ' sidebarToggle.addEventListener(\'click\', () => {',
' sidebar.classList.toggle(\'open\');', ' sidebar.classList.toggle(\'open\');',
' });', ' });',
' // 处理文件上传和识别', ' // 处理文件上传和识别',
' async function processImage(file) {', ' async function processImage(file) {',
' if (!currentToken) {', ' if (!currentToken) {',
@ -1235,7 +1235,7 @@ function getHTML() {
' sidebar.classList.add(\'open\');', ' sidebar.classList.add(\'open\');',
' return;', ' return;',
' }', ' }',
' // 显示图片预览', ' // 显示图片预览',
' const reader = new FileReader();', ' const reader = new FileReader();',
' let imageData;', ' let imageData;',
@ -1245,16 +1245,16 @@ function getHTML() {
' previewImage.style.display = \'block\';', ' previewImage.style.display = \'block\';',
' };', ' };',
' reader.readAsDataURL(file);', ' reader.readAsDataURL(file);',
' // 显示加载动画', ' // 显示加载动画',
' loading.style.display = \'block\';', ' loading.style.display = \'block\';',
' resultContainer.classList.remove(\'show\');', ' resultContainer.classList.remove(\'show\');',
' try {', ' try {',
' // 上传文件', ' // 上传文件',
' const formData = new FormData();', ' const formData = new FormData();',
' formData.append(\'file\', file);', ' formData.append(\'file\', file);',
' const uploadResponse = await fetch(\'https://chat.qwenlm.ai/api/v1/files/\', {', ' const uploadResponse = await fetch(\'https://chat.qwenlm.ai/api/v1/files/\', {',
' method: \'POST\',', ' method: \'POST\',',
' headers: {', ' headers: {',
@ -1263,27 +1263,27 @@ function getHTML() {
' },', ' },',
' body: formData,', ' body: formData,',
' });', ' });',
' const uploadData = await uploadResponse.json();', ' const uploadData = await uploadResponse.json();',
' if (!uploadData.id) throw new Error(\'文件上传失败\');', ' if (!uploadData.id) throw new Error(\'文件上传失败\');',
' // 识别图片', ' // 识别图片',
' const recognizeResponse = await fetch(\'/recognize\', {', ' const recognizeResponse = await fetch(\'/recognize\', {',
' method: \'POST\',', ' method: \'POST\',',
' headers: { \'Content-Type\': \'application/json\' },', ' headers: { \'Content-Type\': \'application/json\' },',
' body: JSON.stringify({ ', ' body: JSON.stringify({ ',
' token: currentToken, ', ' token: currentToken, ',
' imageId: uploadData.id ', ' imageId: uploadData.id ',
' }),', ' }),',
' });', ' });',
' const recognizeData = await recognizeResponse.json();', ' const recognizeData = await recognizeResponse.json();',
' // 修改这里:使用新的响应格式', ' // 修改这里:使用新的响应格式',
' if (!recognizeData.success) {', ' if (!recognizeData.success) {',
' throw new Error(recognizeData.error || \'识别失败\');', ' throw new Error(recognizeData.error || \'识别失败\');',
' }', ' }',
' const result = recognizeData.result || \'识别失败\';', ' const result = recognizeData.result || \'识别失败\';',
' resultDiv.innerHTML = result;', ' resultDiv.innerHTML = result;',
' waitForMathJax(() => {', ' waitForMathJax(() => {',
@ -1301,7 +1301,7 @@ function getHTML() {
' resultContainer.classList.add(\'show\');', ' resultContainer.classList.add(\'show\');',
' }', ' }',
' });', ' });',
' // 添加到历史记录', ' // 添加到历史记录',
' historyManager.addHistory(currentToken, imageData, result);', ' historyManager.addHistory(currentToken, imageData, result);',
' } catch (error) {', ' } catch (error) {',
@ -1313,17 +1313,17 @@ function getHTML() {
' loading.style.display = \'none\';', ' loading.style.display = \'none\';',
' }', ' }',
' }', ' }',
' // 文件拖放处理', ' // 文件拖放处理',
' uploadArea.addEventListener(\'dragover\', (e) => {', ' uploadArea.addEventListener(\'dragover\', (e) => {',
' e.preventDefault();', ' e.preventDefault();',
' uploadArea.classList.add(\'dragover\');', ' uploadArea.classList.add(\'dragover\');',
' });', ' });',
' uploadArea.addEventListener(\'dragleave\', () => {', ' uploadArea.addEventListener(\'dragleave\', () => {',
' uploadArea.classList.remove(\'dragover\');', ' uploadArea.classList.remove(\'dragover\');',
' });', ' });',
' uploadArea.addEventListener(\'drop\', (e) => {', ' uploadArea.addEventListener(\'drop\', (e) => {',
' e.preventDefault();', ' e.preventDefault();',
' uploadArea.classList.remove(\'dragover\');', ' uploadArea.classList.remove(\'dragover\');',
@ -1332,7 +1332,7 @@ function getHTML() {
' processImage(file);', ' processImage(file);',
' }', ' }',
' });', ' });',
' // 点击上传', ' // 点击上传',
' uploadArea.addEventListener(\'click\', (e) => {', ' uploadArea.addEventListener(\'click\', (e) => {',
' // 如果点击的是 base64Input 或 toggleBase64 按钮,不触发文件上传', ' // 如果点击的是 base64Input 或 toggleBase64 按钮,不触发文件上传',
@ -1342,7 +1342,7 @@ function getHTML() {
' e.target.closest(\'#toggleBase64\')) {', ' e.target.closest(\'#toggleBase64\')) {',
' return;', ' return;',
' }', ' }',
' const input = document.createElement(\'input\');', ' const input = document.createElement(\'input\');',
' input.type = \'file\';', ' input.type = \'file\';',
' input.accept = \'image/*\';', ' input.accept = \'image/*\';',
@ -1352,7 +1352,7 @@ function getHTML() {
' };', ' };',
' input.click();', ' input.click();',
' });', ' });',
' // 粘贴处理', ' // 粘贴处理',
' document.addEventListener(\'paste\', (e) => {', ' document.addEventListener(\'paste\', (e) => {',
' const file = e.clipboardData.files[0];', ' const file = e.clipboardData.files[0];',
@ -1360,60 +1360,60 @@ function getHTML() {
' processImage(file);', ' processImage(file);',
' }', ' }',
' });', ' });',
' // 初始化', ' // 初始化',
' loadTokens();', ' loadTokens();',
' if (currentToken) {', ' if (currentToken) {',
' historyManager.displayHistory(currentToken);', ' historyManager.displayHistory(currentToken);',
' }', ' }',
' const modal = document.getElementById(\'imageModal\');', ' const modal = document.getElementById(\'imageModal\');',
' const modalImg = document.getElementById(\'modalImage\');', ' const modalImg = document.getElementById(\'modalImage\');',
' function showFullImage(src) {', ' function showFullImage(src) {',
' const modal = document.getElementById(\'imageModal\');', ' const modal = document.getElementById(\'imageModal\');',
' const modalImg = document.getElementById(\'modalImage\');', ' const modalImg = document.getElementById(\'modalImage\');',
' if (!src) {', ' if (!src) {',
' console.error(\'图片源为空\');', ' console.error(\'图片源为空\');',
' return;', ' return;',
' }', ' }',
' modal.style.display = \'block\';', ' modal.style.display = \'block\';',
' modalImg.src = src;', ' modalImg.src = src;',
' // 添加加载错误处理', ' // 添加加载错误处理',
' modalImg.onerror = function() {', ' modalImg.onerror = function() {',
' alert(\'图片加载失败\');', ' alert(\'图片加载失败\');',
' modal.style.display = \'none\';', ' modal.style.display = \'none\';',
' };', ' };',
' modalImg.style.opacity = \'0\';', ' modalImg.style.opacity = \'0\';',
' setTimeout(() => {', ' setTimeout(() => {',
' modalImg.style.transition = \'opacity 0.3s ease\';', ' modalImg.style.transition = \'opacity 0.3s ease\';',
' modalImg.style.opacity = \'1\';', ' modalImg.style.opacity = \'1\';',
' }, 50);', ' }, 50);',
' }', ' }',
' // 点击模态框关闭', ' // 点击模态框关闭',
' modal.onclick = function() {', ' modal.onclick = function() {',
' modal.style.display = "none";', ' modal.style.display = "none";',
' }', ' }',
' // ESC 键关闭模态框', ' // ESC 键关闭模态框',
' document.addEventListener(\'keydown\', function(e) {', ' document.addEventListener(\'keydown\', function(e) {',
' if (e.key === \'Escape\' && modal.style.display === \'block\') {', ' if (e.key === \'Escape\' && modal.style.display === \'block\') {',
' modal.style.display = \'none\';', ' modal.style.display = \'none\';',
' }', ' }',
' });', ' });',
' // 左侧历史记录边栏开关', ' // 左侧历史记录边栏开关',
' historyToggle.addEventListener(\'click\', () => {', ' historyToggle.addEventListener(\'click\', () => {',
' historySidebar.classList.toggle(\'open\');', ' historySidebar.classList.toggle(\'open\');',
' });', ' });',
' const copyBtn = document.getElementById(\'copyBtn\');', ' const copyBtn = document.getElementById(\'copyBtn\');',
' // 复制结果功能', ' // 复制结果功能',
' copyBtn.addEventListener(\'click\', async () => {', ' copyBtn.addEventListener(\'click\', async () => {',
' const result = resultDiv.textContent;', ' const result = resultDiv.textContent;',
@ -1437,7 +1437,7 @@ function getHTML() {
' // Base64 输入相关功能', ' // Base64 输入相关功能',
' const base64Input = document.getElementById(\'base64Input\');', ' const base64Input = document.getElementById(\'base64Input\');',
' const toggleBase64 = document.getElementById(\'toggleBase64\');', ' const toggleBase64 = document.getElementById(\'toggleBase64\');',
' // 切换 Base64 输入框显示', ' // 切换 Base64 输入框显示',
' toggleBase64.addEventListener(\'click\', (e) => {', ' toggleBase64.addEventListener(\'click\', (e) => {',
' e.stopPropagation(); // 阻止事件冒泡到 uploadArea', ' e.stopPropagation(); // 阻止事件冒泡到 uploadArea',
@ -1449,12 +1449,12 @@ function getHTML() {
' toggleBase64.textContent = \'切换Base64输入\';', ' toggleBase64.textContent = \'切换Base64输入\';',
' }', ' }',
' });', ' });',
' // 为 base64Input 添加阻止事件冒泡', ' // 为 base64Input 添加阻止事件冒泡',
' document.getElementById(\'base64Input\').addEventListener(\'click\', (e) => {', ' document.getElementById(\'base64Input\').addEventListener(\'click\', (e) => {',
' e.stopPropagation(); // 阻止事件冒泡到 uploadArea', ' e.stopPropagation(); // 阻止事件冒泡到 uploadArea',
' });', ' });',
' // base64Input 的 input 事件处理也需要阻止冒泡', ' // base64Input 的 input 事件处理也需要阻止冒泡',
' base64Input.addEventListener(\'input\', async (e) => {', ' base64Input.addEventListener(\'input\', async (e) => {',
' e.stopPropagation();', ' e.stopPropagation();',
@ -1468,7 +1468,7 @@ function getHTML() {
' } else {', ' } else {',
' imageData = \'data:image/png;base64,\' + base64Content;', ' imageData = \'data:image/png;base64,\' + base64Content;',
' }', ' }',
' // 验证Base64是否为有效图片', ' // 验证Base64是否为有效图片',
' const img = new Image();', ' const img = new Image();',
' img.src = imageData;', ' img.src = imageData;',
@ -1476,16 +1476,16 @@ function getHTML() {
' img.onload = resolve;', ' img.onload = resolve;',
' img.onerror = reject;', ' img.onerror = reject;',
' });', ' });',
' // 转换Base64为Blob', ' // 转换Base64为Blob',
' const response = await fetch(imageData);', ' const response = await fetch(imageData);',
' const blob = await response.blob();', ' const blob = await response.blob();',
' const file = new File([blob], "image.png", { type: "image/png" });', ' const file = new File([blob], "image.png", { type: "image/png" });',
' // 显示预览', ' // 显示预览',
' previewImage.src = imageData;', ' previewImage.src = imageData;',
' previewImage.style.display = \'block\';', ' previewImage.style.display = \'block\';',
' // 处理图片', ' // 处理图片',
' await processImage(file);', ' await processImage(file);',
' } catch (error) {', ' } catch (error) {',
@ -1495,23 +1495,23 @@ function getHTML() {
' }', ' }',
' }', ' }',
' });', ' });',
' // 复制历史记录结果', ' // 复制历史记录结果',
' async function copyHistoryResult(index) {', ' async function copyHistoryResult(index) {',
' const history = historyManager.loadHistory(currentToken);', ' const history = historyManager.loadHistory(currentToken);',
' const result = history[index]?.result;', ' const result = history[index]?.result;',
' if (!result) {', ' if (!result) {',
' alert(\'无法复制:结果为空\');', ' alert(\'无法复制:结果为空\');',
' return;', ' return;',
' }', ' }',
' try {', ' try {',
' await navigator.clipboard.writeText(result);', ' await navigator.clipboard.writeText(result);',
' const btn = event.target;', ' const btn = event.target;',
' btn.textContent = \'已复制\';', ' btn.textContent = \'已复制\';',
' btn.classList.add(\'copied\');', ' btn.classList.add(\'copied\');',
' setTimeout(() => {', ' setTimeout(() => {',
' btn.textContent = \'复制结果\';', ' btn.textContent = \'复制结果\';',
' btn.classList.remove(\'copied\');', ' btn.classList.remove(\'copied\');',
@ -1521,7 +1521,7 @@ function getHTML() {
' alert(\'复制失败,请手动复制\');', ' alert(\'复制失败,请手动复制\');',
' }', ' }',
' }', ' }',
' // 删除历史记录项', ' // 删除历史记录项',
' function deleteHistoryItem(index) {', ' function deleteHistoryItem(index) {',
' const history = historyManager.loadHistory(currentToken);', ' const history = historyManager.loadHistory(currentToken);',
@ -1529,7 +1529,7 @@ function getHTML() {
' alert(\'该记录不存在\');', ' alert(\'该记录不存在\');',
' return;', ' return;',
' }', ' }',
' if (confirm(\'确定要删除这条历史记录吗?\')) {', ' if (confirm(\'确定要删除这条历史记录吗?\')) {',
' history.splice(index, 1);', ' history.splice(index, 1);',
' historyManager.saveHistory(currentToken, history);', ' historyManager.saveHistory(currentToken, history);',
@ -1542,4 +1542,5 @@ function getHTML() {
].join('\n'); ].join('\n');
return html; return html;
} }