Merge branch 'main' of https://github.com/Cunninger/ocr-based-qwen
This commit is contained in:
commit
657f37a90e
32
LICENSE
Normal file
32
LICENSE
Normal 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
138
README.md
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
# 🖼️ QwenLM OCR 逆向工程项目
|
||||||
|
|
||||||
|
本项目是对 [QwenLM](https://chat.qwenlm.ai/) 的 OCR 功能进行逆向工程的实现。通过调用 QwenLM 的 API,你可以从图片中提取文字内容,并且该项目支持一键部署到 **Cloudflare Workers** (CF) 上。
|
||||||
|
|
||||||
|
## 项目展示
|
||||||
|

|
||||||
|
|
||||||
|
## 🚀 功能特性
|
||||||
|
|
||||||
|
- **图片 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. **验证码识别**
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## 📜 许可证
|
||||||
|
|
||||||
|
本项目基于 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 应佬友需求,优化了对数学公式的识别,效果如下图
|
||||||
|
- 原图:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 识别效果图:
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### 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"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
- 效果图:
|
||||||
|

|
||||||
|
|
||||||
|
- **支持图片URL**:
|
||||||
|
```bash
|
||||||
|
curl --location 'https://ocr.doublefenzhuan.me/api/recognize/url' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjUzZTk0Nzg4LWMwM2QtNDY4Mi05OTNhLWE0ZDNjNGUyZDY0OSIsImV4cCI6MTczOTA3NTE0MX0.FtwG6xDLYd2rngWUhuldg56WXCiLSTL0RI6xJJQ4vHM",
|
||||||
|
|
||||||
|
"imageUrl": "xxxx"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
- 效果图:
|
||||||
|

|
||||||
|
|
||||||
|
## 趋势
|
||||||
|
[](https://star-history.com/#Cunninger/ocr-based-qwen&Date)
|
275
worker.js
275
worker.js
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user