├── .gitignore ├── image.png ├── ocr.png ├── LICENSE ├── README.md └── worker.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cunninger/ocr-based-qwen/HEAD/image.png -------------------------------------------------------------------------------- /ocr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cunninger/ocr-based-qwen/HEAD/ocr.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 重要声明 2 | 项目性质:本项目是对 QwenLM 的逆向工程实现,仅供学习和研究使用。 3 | 免责声明:任何商业用途或滥用行为均与作者无关。 4 | 法律合规:使用者需遵守相关法律法规和平台的使用条款。 5 | 6 | 开源协议 (LICENSE) 7 | 本项目采用 MIT 许可证,明确限制仅用于学习和研究目的。以下是 LICENSE 文件内容的补充说明: 8 | 9 | MIT License 10 | Copyright (c) 2025 [cunninger] 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | 1. The Software is provided for educational and research purposes only. 19 | Commercial use is strictly prohibited. 20 | 21 | 2. The above copyright notice and this permission notice shall be included in all 22 | copies or substantial portions of the Software. 23 | 24 | 3. If you modify or distribute the Software, you must include a reference to the original project's open-source address: https://github.com/Cunninger/ocr-based-qwen. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | 34 | 补充说明 35 | 二次开发声明:如果您对项目进行二次开发或分发,必须声明本项目的开源地址:https://github.com/Cunninger/ocr-based-qwen。 36 | 请确保在使用或修改本项目时遵守上述条款和条件。 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | # QwenLM OCR 6 | 7 | 8 | 本项目基于 [QwenLM](https://chat.qwenlm.ai/) 。通过调用 QwenLM 的 ”API“,你可以从图片中提取文字内容,并且该项目支持一键部署到 **Cloudflare Workers** (CF) 上。 9 | 10 | ## 项目展示 11 | ![screely-1743379867339](https://github.com/user-attachments/assets/ad25719c-0504-4bc1-926f-4bffeb6b745d) 12 | 13 | 14 | ## 测试cookie 15 | - 如果出现**处理失败: 文件上传失败的错误**,说明测试Cookie 上传文件过多, 尝试获取自己账号的Cookie 使用 16 | ``` 17 | { 18 | "code": "RateLimited", 19 | "detail": "Reached file upload limited: too many files uploaded in (86400.0) seconds." 20 | } 21 | ``` 22 | 23 | ![image](https://github.com/user-attachments/assets/5251feec-3fdc-4003-82b5-a273a3abb9f2) 24 | - cookie1: 25 | ```txt 26 | _gcl_au=1.1.1128300923.1746849193; _bl_uid=m1mL2atCh8Cot5wsRoIFbqdhm7tg; xlly_s=1; cna=qrulIGTI1xgCAbZmOSlWpOi2; cnaui=53e94788-c03d-4682-993a-a4d3c4e2d649; aui=53e94788-c03d-4682-993a-a4d3c4e2d649; acw_tc=0a03e55a17471050174934469e5698d16b1ed504f9e9c68f410d550889e99b; x-ap=ap-southeast-1; sca=54887197; atpsida=3cac89c5d9360e40b62eb508_1747105020_1; ssxmod_itna=eqIxgDnQKYqDqOD4kxjxYKD5i=m6l40QDXDUqAjdGgDYq7=GF7DChzw1CDBKeZxv=t7iiHP4gRxGXxkKxiNDAxq0iDC8eQeOCmL+tGqFjmhtqqRZayb5zHF2Ghm85mnrZpswVMZYKwDCPDExGkeBvhwDiiax0rD0eDPxDYDGbaD7PDoxDrOIYDjYoICSEmQz4DKx0kDY5Dw1+mDYPDWxDFb+DktYEwbDDCDivfV83DixiaaSDDBg0u4pxmtDi3bUOGjz+LxT0IhooD9E4Ds6xgl/kgvMiAkS17Wvewff3DvxDk2IgGUtTpwfTo+8ZxxohxY7eChxO7ek7orYqoD4aYhoA4ti517qoldaGQQDwolQChmw073QpCxxY+zyTsb+s/ke=eKn2tBjtwGYheYaYqpxoMA5VYYqr5OBQkDtaW5Q7q/2x4D; ssxmod_itna2=eqIxgDnQKYqDqOD4kxjxYKD5i=m6l40QDXDUqAjdGgDYq7=GF7DChzw1CDBKeZxv=t7iiHP4gKxDfrtK2nguI=5mwvNG4oIhfnAQPD; SERVERID=8d1f4d10c35f5d9c380a0c17580aeca9|1747105025|1747105017; token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjUzZTk0Nzg4LWMwM2QtNDY4Mi05OTNhLWE0ZDNjNGUyZDY0OSIsImV4cCI6MTc0OTY5NzAyNX0.MZ-Nmyq08Oy0vDR_PdWDpvq8bAfuA371KPA2fez8Qz0; isg=BNnZ9u4y1WqSC4nCppf6kGHW6MWzZs0YnUIs8PuOVYB_AvmUQ7bd6EcUBMZ0jmVQ; tfstk=gySjtnM1FjcXkNttCE2ydgAaio-_L8rehA9OKOnqBnKv1A1NpFpq0-b1P15XM16gnddO61ONuzrFntxMXWP_YkWc0MUDzitt_TKDQGHyzvEFntDRrOQ1nkk6PJ-6XCC9kU3JpQpxM1LvwUdkIdhvBme7FQvM6Kn9XULJ3pJ9XhCOe89MwKK9k1B-pmGeCRRfh-x3JMyTHBXvNcnO2lYXOt3ZXcIWhE6dHQ6PUg9XlBL9I3NdD_WO0iYuOXKGUw11W14Ern6OknpFnoiWvtQPfLSaQVOAig6R2UMEYh_AGNLhPk0HkFOfyiLSBcBkfaLd5sNjoIQVNevJyAZHU6RRniQ7IbBATItveUrLdTLOziYh07nXvwX20NCg84xfBZK54MmeOK3-5YTnfLOUF8giSdRn6tlqKjZvkLvXV8wSG3LvELOUF8giSEpkU4w7FjtR. 27 | ``` 28 | - cookie2: 29 | ```txt 30 | cna=jVVkIH8WwiECAWrgu3Rh49BZ; _bl_uid=69m7O83qin4t1v50n9b33n9oge32; cnaui=1df8319d-47e8-4859-b77f-810584faec0f; aui=1df8319d-47e8-4859-b77f-810584faec0f; _gcl_au=1.1.505594525.1742563213.867342246.1745928027.1745928034; acw_tc=0a03e55f17471053364778166e659f6b4859f09675a78f141a3e9cc6228e4a; x-ap=ap-southeast-1; sca=73b49ddd; xlly_s=1; ssxmod_itna=GqGx9DyDuiiQYY5i7=YAKG7qDOG2ikDnQDBP01DphxQyK08D6DYqGdcFkmNhY=eF34oqrYqSnxGXKFYxiNDAxq0iDC+ejrenpBHw3354EhteeiAl0g5Ir825b40aFK48qiYoweGLDmKDyWiNxoxGGA4GwDGoD34DiDDPfD03Db4D+fjiD72r32WT5rePDQ4GyDiUti2rDm4DfDDd5qgD4CWroDDtDAuW1PPDADAfYoDDlYiO4mODKoDGWClR/iPptBahdZRrDjkPD/R40Xay05vGtXWkXGKoe=cWDtqD976RVSGIYVQu4htNhDoRYemid=weFio3AxqD4cbxKAGNnDIxYFAGqgt+EOeDNwrdGmexuGpWdhGi0kz9urBTsmAe0AxRotcme3DxRA+F0DqY45O5ZB5xrw3YhiD4bQvFiNeiDD; ssxmod_itna2=GqGx9DyDuiiQYY5i7=YAKG7qDOG2ikDnQDBP01DphxQyK08D6DYqGdcFkmNhY=eF34oqrYqSYxD3beY6jETIQkAw7Xsx58ixWUeeD; token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjFkZjgzMTlkLTQ3ZTgtNDg1OS1iNzdmLTgxMDU4NGZhZWMwZiIsImV4cCI6MTc0OTY5NzM0NH0.GCGFR6NI85QMmXtG6OjLxY0F1fze0XPdVBPRNzpTSh8; atpsida=005f535629829fb74bf7df34_1747105344_2; SERVERID=b98d56744b338696d8c13af9b400879a|1747105345|1747105336; tfstk=gU3jT81sNtXjChzO1oAzO4XwlWUsCQ8UG1NttfQV6rUYC1MZ9lFVuImsVAkbHAGMi5etBAwZ038Eij4gWpJ68el0KkXUGqaOQbU0_VCz46LEijf-qfnsie5_VBg_DRHTDuB8_8FY6PHTyaF_eZFOk5BJN82RkOeT6zE8_SIYBAU9Nbeu6PFxBodSwvQRL5OahjO0-hpQzn2jM8_OW4LgcJGAbNQtPSZj9jeSabu7GowLq1p3hqHr6qu0qp6LSboICcHMYg4j6mUYTjYfPyhx4VNr7CS_Hx0Sw4E5TwzsDxiTx0ThxoE_h03YV1QtZPi8vcMXhi2x0YZ3wuC6-7kUej0xVCX-Mvybk7EyJBG8X0u0xqJRlynngrotIHSulfnYPg5c8JNA2G17xNN7LQO5jGA7LQ7uzuiwwoFuGzOWNta8D7V7LQO5jGqYZ7SWNQ6by; isg=BIqKadcq1ueGLlW0I70ajFfB23Asew7VpeYBkBTDNl1oxyqB_Ate5dAx1zMbA4Zt 31 | ``` 32 | ## 🚀 功能特性 33 | 34 | - **图片 OCR**:使用 QwenLM 强大的 OCR 功能从图片中提取文字。 35 | - **拖拽上传**:直接将图片拖拽到页面即可识别。 36 | - **复制粘贴**:支持从剪贴板直接粘贴图片进行识别。 37 | - **Token 管理**:支持多 Token 轮询使用,提升稳定性。 38 | - **历史记录**:保存每次识别的结果和图片,方便查看。 39 | - **一键复制**:轻松复制识别结果到剪贴板。 40 | - **数学公式识别**:特别优化了对数学公式的提取,支持 LaTeX 格式输出。 41 | - **API 支持**:提供 `curl` 接口调用,支持通过图片文件、base64 和图片 URL 3种方式。(Apifox调用文档示例(**仅作为代码示例,这个网页调试有问题**):https://0vkh6v4ad8.apifox.cn/) 42 | - **验证码识别**:新增验证码识别功能,支持常见类型的验证码(如数字、字母、混合字符等),提升自动化处理能力。 43 | - **自定义prompt**: 在高级模式下(v1.1.0支持),用户可以自定义 prompt,跳过格式化处理,直接返回原始结果,而在普通模式下,使用默认的 prompt 并保持现有的格式化处理逻辑。 44 | ## qwen模型接口: 45 | https://chat.qwenlm.ai/api/models 46 | ## 提示词工程 47 | ``` 48 | const defaultPrompt = 49 | '不要输出任何额外的解释或说明,禁止输出例如:识别内容、以上内容已严格按照要求进行格式化和转换等相关无意义的文字!' + '请识别图片中的内容,注意以下要求:\n' + 50 | '对于数学公式和普通文本:\n' + 51 | '1. 所有数学公式和数学符号都必须使用标准的LaTeX格式\n' + 52 | '2. 行内公式使用单个$符号包裹,如:$x^2$\n' + 53 | '3. 独立公式块使用两个$$符号包裹,如:$$\\sum_{i=1}^n i^2$$\n' + 54 | '4. 普通文本保持原样,不要使用LaTeX格式\n' + 55 | '5. 保持原文的段落格式和换行\n' + 56 | '6. 明显的换行使用\\n表示\n' + 57 | '7. 确保所有数学符号都被正确包裹在$或$$中\n\n' + 58 | '对于验证码图片:\n' + 59 | '1. 只输出验证码字符,不要加任何额外解释\n' + 60 | '2. 忽略干扰线和噪点\n' + 61 | '3. 注意区分相似字符,如0和O、1和l、2和Z等\n' + 62 | '4. 验证码通常为4-6位字母数字组合\n\n' + 63 | ''; 64 | ``` 65 | ## 🛠️ 部署指南 66 | 67 | ### 1. 部署到 Cloudflare Workers 68 | 69 | 1. **配置 Cloudflare Workers**: 70 | - 登录 [Cloudflare Dashboard](https://dash.cloudflare.com/)。 71 | - 创建一个新的 Worker。 72 | - 将 `worker.js` 中的代码复制到 Worker 编辑器中。 73 | 74 | 2. **部署**: 75 | - 保存并部署 Worker。 76 | - 获取 Worker 的访问地址,即可使用。 77 | ### 2. Docker 一键部署 78 | 1. 使用以下命令拉取并运行Docker镜像。 79 | ``` 80 | docker pull sexgirls/qwen-ocr-app:latest 81 | docker run -p 3000:3000 sexgirls/qwen-ocr-app:latest 82 | ``` 83 | 2. 然后在浏览器中访问应用: 84 | ``` 85 | http://localhost:3000 86 | ``` 87 | 详情可见:https://github.com/Cunninger/ocr-based-qwen/tree/docker-version 88 | ## 🧩 使用说明 89 | 90 | 1. **设置 Cookie**: 91 | - 前往 [QwenLM](https://chat.qwenlm.ai/) 获取对话请求中的 Cookie。 92 | ![alt text](image.png) 93 | 94 | - 点击右上角的 **⚙️ Cookie设置** 按钮。 95 | - 输入你的 Cookie(或者使用测试Cookie)。 96 | - 点击 **保存**。 97 | 98 | 2. **上传图片**: 99 | - 拖拽图片到页面,或点击上传区域选择图片。 100 | - 支持直接粘贴图片。 101 | 102 | 3. **查看结果**: 103 | - 识别结果会显示在页面下方。 104 | - 点击 **复制结果** 按钮,将识别内容复制到剪贴板。 105 | 106 | 4. **查看历史记录**: 107 | - 点击左侧的 **📋 识别历史** 按钮,查看历史识别记录。 108 | - 点击历史记录中的图片,可以查看大图。 109 | 110 | 5. **API 调用**: 111 | - **支持 base64**: 112 | ```bash 113 | curl -X POST \ 114 | 'https://test-qwen-cor.aughumes8.workers.dev/api/recognize/base64' \ 115 | -H 'Content-Type: application/json' \ 116 | -H 'x-custom-cookie: YOUR_COOKIE_STRING' \ 117 | -d '{ 118 | "base64Image": "YOUR_BASE64_IMAGE_STRING" 119 | }' 120 | ``` 121 | - **支持图片 URL**: 122 | ```bash 123 | curl -X POST \ 124 | 'https://test-qwen-cor.aughumes8.workers.dev/api/recognize/url' \ 125 | -H 'Content-Type: application/json' \ 126 | -H 'x-custom-cookie: YOUR_COOKIE_STRING' \ 127 | -d '{ 128 | "imageUrl": "YOUR_IMAGE_URL" 129 | }' 130 | ``` 131 | 6. **验证码识别** 132 | ![image](https://github.com/user-attachments/assets/66f24d52-6263-446c-b371-cc2e65c9277c) 133 | 134 | 135 | ## 📜 许可证 136 | 137 | 本项目基于 MIT 许可证开源。详情请查看 [LICENSE](LICENSE) 文件。 138 | 139 | ## 🙏 致谢 140 | 141 | - 感谢 [QwenLM](https://chat.qwenlm.ai/) 提供的 OCR 功能。 142 | - 感谢 Cloudflare 提供的 Workers 服务。 143 | 144 | --- 145 | 146 | 🌟 如果觉得这个项目对你有帮助,欢迎点个 Star 支持一下!🌟 147 | 148 | **体验地址**:[智能图片识别 (doublefenzhuan.me)](https://ocr.doublefenzhuan.me/) 149 | 150 | **GitHub 仓库**:[Cunninger/ocr-based-qwen](https://github.com/Cunninger/ocr-based-qwen) 151 | 152 | --- 153 | 154 | #### 后续计划 155 | - 优化数学公式识别精度; 156 | - 增加更多 API 功能支持; 157 | - 提升识别速度和稳定性。 158 | 159 | 快来体验吧!如果有任何问题或建议,欢迎在 GitHub 上提 Issue 或直接联系我! 160 | 161 | ## 更新 162 | ### 2025/01/13 应佬友需求,优化了对数学公式的识别,效果如下图 163 | - 原图: 164 | 165 | ![image](https://github.com/user-attachments/assets/9841509d-be56-4eb9-aafa-4d4ca5555c2e) 166 | 167 | - 识别效果图: 168 | ![image](https://github.com/user-attachments/assets/2340dc6d-9156-4866-aa53-cdfd1911a651) 169 | 170 | 171 | ### 2025/01/13 18点34分 支持`curl`接口调用 172 | - **支持base64**: 173 | ```bash 174 | curl -X POST \ 175 | 'https://test-qwen-cor.aughumes8.workers.dev/api/recognize/base64' \ 176 | -H 'Content-Type: application/json' \ 177 | -H 'x-custom-cookie: YOUR_COOKIE_STRING' \ 178 | -d '{ 179 | "base64Image": "YOUR_BASE64_IMAGE_STRING" 180 | }' 181 | ``` 182 | - 效果图: 183 | ![image](https://github.com/user-attachments/assets/363afb39-444b-4308-a2bd-55831df81b6f) 184 | 185 | - **支持图片URL**: 186 | ```bash 187 | curl -X POST \ 188 | 'https://test-qwen-cor.aughumes8.workers.dev/api/recognize/url' \ 189 | -H 'Content-Type: application/json' \ 190 | -H 'x-custom-cookie: YOUR_COOKIE_STRING' \ 191 | -d '{ 192 | "imageUrl": "YOUR_IMAGE_URL" 193 | }' 194 | ``` 195 | - 效果图: 196 | ![image](https://github.com/user-attachments/assets/a816085e-8a52-4425-b02c-94ea543bf95b) 197 | 198 | - **通过图片文件识别(需要先上传获取imageId)** 199 | ```bash 200 | # 1. 先上传文件 201 | curl -X POST \ 202 | 'https://test-qwen-cor.aughumes8.workers.dev/proxy/upload' \ 203 | -H 'x-custom-cookie: YOUR_COOKIE_STRING' \ 204 | -F 'file=@/path/to/your/image.jpg' 205 | 206 | # 2. 使用返回的imageId进行识别 207 | curl -X POST \ 208 | 'https://test-qwen-cor.aughumes8.workers.dev/recognize' \ 209 | -H 'Content-Type: application/json' \ 210 | -H 'x-custom-cookie: YOUR_COOKIE_STRING' \ 211 | -d '{ 212 | "imageId": "RETURNED_IMAGE_ID" 213 | }' 214 | ``` 215 | ## Cloudflare访问数据 216 | ![image](https://github.com/user-attachments/assets/bb456075-6107-47ee-a361-a0edba532c38) 217 | ## 致谢 218 | ![image](https://github.com/user-attachments/assets/cfc04290-6400-483d-b0a9-4dd4b409bab9) 219 | ## 趋势 220 | [![Star History Chart](https://api.star-history.com/svg?repos=cunninger/ocr-based-qwen&type=Date)](https://star-history.com/#Cunninger/ocr-based-qwen&Date) 221 | -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | addEventListener('fetch', event => { 2 | event.respondWith(handleRequest(event.request)); 3 | }); 4 | 5 | async function handleRequest(request) { 6 | const url = new URL(request.url); 7 | const cookie = request.headers.get('x-custom-cookie') || ''; // 从自定义请求头获取 cookie 8 | 9 | // 处理 CORS 预检请求 10 | if (request.method === 'OPTIONS') { 11 | return new Response(null, { 12 | headers: { 13 | 'Access-Control-Allow-Origin': '*', 14 | 'Access-Control-Allow-Methods': 'POST, OPTIONS', 15 | 'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-custom-cookie', // 添加自定义请求头 16 | }, 17 | }); 18 | } 19 | 20 | // API路由处理 21 | switch (url.pathname) { 22 | // 1. 通过图片URL识别 23 | case '/api/recognize/url': 24 | if (request.method === 'POST') { 25 | return handleImageUrlRecognition(request); 26 | } 27 | break; 28 | 29 | // 2. 通过Base64识别 30 | case '/api/recognize/base64': 31 | if (request.method === 'POST') { 32 | return handleBase64Recognition(request); 33 | } 34 | break; 35 | 36 | // 3. 通过图片文件识别 (原有的/recognize端点) 37 | case '/recognize': 38 | if (request.method === 'POST') { 39 | return handleFileRecognition(request); 40 | } 41 | break; 42 | 43 | // 添加代理路由 44 | case '/proxy/upload': 45 | if (request.method === 'POST') { 46 | return handleProxyUpload(request); 47 | } 48 | break; 49 | 50 | // 返回前端界面 51 | case '/': 52 | return new Response(getHTML(), { 53 | headers: { 'Content-Type': 'text/html' }, 54 | }); 55 | } 56 | 57 | return new Response('Not Found', { status: 404 }); 58 | } 59 | 60 | // 处理图片URL识别 61 | async function handleImageUrlRecognition(request) { 62 | try { 63 | const { imageUrl } = await request.json(); 64 | const cookie = request.headers.get('x-custom-cookie'); 65 | 66 | if (!cookie || !imageUrl) { 67 | return new Response(JSON.stringify({ 68 | error: 'Missing cookie or imageUrl' 69 | }), { 70 | status: 400, 71 | headers: { 'Content-Type': 'application/json' }, 72 | }); 73 | } 74 | 75 | // 从cookie中提取token 76 | const tokenMatch = cookie.match(/token=([^;]+)/); 77 | if (!tokenMatch) { 78 | return new Response(JSON.stringify({ 79 | error: 'Invalid cookie format: missing token' 80 | }), { 81 | status: 400, 82 | headers: { 'Content-Type': 'application/json' }, 83 | }); 84 | } 85 | const token = tokenMatch[1]; 86 | 87 | // 下载图片 88 | const imageResponse = await fetch(imageUrl); 89 | const imageBlob = await imageResponse.blob(); 90 | 91 | // 上传到QwenLM 92 | const formData = new FormData(); 93 | formData.append('file', imageBlob); 94 | 95 | const uploadResponse = await fetch('https://chat.qwenlm.ai/api/v1/files/', { 96 | method: 'POST', 97 | headers: { 98 | 'accept': 'application/json', 99 | 'authorization': `Bearer ${token}`, 100 | 'cookie': cookie 101 | }, 102 | body: formData, 103 | }); 104 | 105 | const uploadData = await uploadResponse.json(); 106 | if (!uploadData.id) throw new Error('File upload failed'); 107 | 108 | // 调用通用识别函数 109 | return await recognizeImage(token, uploadData.id, request); 110 | } catch (error) { 111 | return new Response(JSON.stringify({ 112 | error: error.message || 'Internal Server Error' 113 | }), { 114 | status: 500, 115 | headers: { 'Content-Type': 'application/json' }, 116 | }); 117 | } 118 | } 119 | 120 | // 处理Base64识别 121 | async function handleBase64Recognition(request) { 122 | try { 123 | const { base64Image } = await request.json(); 124 | const cookie = request.headers.get('x-custom-cookie'); 125 | 126 | if (!cookie || !base64Image) { 127 | return new Response(JSON.stringify({ 128 | error: 'Missing cookie or base64Image' 129 | }), { 130 | status: 400, 131 | headers: { 'Content-Type': 'application/json' }, 132 | }); 133 | } 134 | 135 | // 从cookie中提取token 136 | const tokenMatch = cookie.match(/token=([^;]+)/); 137 | if (!tokenMatch) { 138 | return new Response(JSON.stringify({ 139 | error: 'Invalid cookie format: missing token' 140 | }), { 141 | status: 400, 142 | headers: { 'Content-Type': 'application/json' }, 143 | }); 144 | } 145 | const token = tokenMatch[1]; 146 | 147 | // 转换Base64为Blob 148 | const imageData = base64Image.startsWith('data:') ? base64Image : 'data:image/png;base64,' + base64Image; 149 | const response = await fetch(imageData); 150 | const blob = await response.blob(); 151 | 152 | // 上传到QwenLM 153 | const formData = new FormData(); 154 | formData.append('file', blob); 155 | 156 | const uploadResponse = await fetch('https://chat.qwenlm.ai/api/v1/files/', { 157 | method: 'POST', 158 | headers: { 159 | 'accept': 'application/json', 160 | 'authorization': `Bearer ${token}`, 161 | 'cookie': cookie 162 | }, 163 | body: formData, 164 | }); 165 | 166 | const uploadData = await uploadResponse.json(); 167 | if (!uploadData.id) throw new Error('File upload failed'); 168 | 169 | // 调用通用识别函数 170 | return await recognizeImage(token, uploadData.id, request); 171 | } catch (error) { 172 | return new Response(JSON.stringify({ 173 | error: error.message || 'Internal Server Error' 174 | }), { 175 | status: 500, 176 | headers: { 'Content-Type': 'application/json' }, 177 | }); 178 | } 179 | } 180 | 181 | // 处理文件识别 (原有功能) 182 | async function handleFileRecognition(request) { 183 | try { 184 | const { imageId } = await request.json(); 185 | const cookie = request.headers.get('x-custom-cookie') || ''; 186 | 187 | if (!cookie || !imageId) { 188 | return new Response(JSON.stringify({ 189 | error: 'Missing cookie or imageId' 190 | }), { 191 | status: 400, 192 | headers: { 'Content-Type': 'application/json' }, 193 | }); 194 | } 195 | 196 | // 从cookie中提取token 197 | const tokenMatch = cookie.match(/token=([^;]+)/); 198 | const token = tokenMatch ? tokenMatch[1] : ''; 199 | 200 | return await recognizeImage(token, imageId, request); 201 | } catch (error) { 202 | return new Response(JSON.stringify({ 203 | error: error.message || 'Internal Server Error' 204 | }), { 205 | status: 500, 206 | headers: { 'Content-Type': 'application/json' }, 207 | }); 208 | } 209 | } 210 | 211 | // 添加代理处理函数 212 | async function handleProxyUpload(request) { 213 | try { 214 | const formData = await request.formData(); 215 | const cookie = request.headers.get('x-custom-cookie') || ''; 216 | 217 | // 从cookie中提取token 218 | const tokenMatch = cookie.match(/token=([^;]+)/); 219 | const token = tokenMatch ? tokenMatch[1] : ''; 220 | 221 | const response = await fetch('https://chat.qwenlm.ai/api/v1/files/', { 222 | method: 'POST', 223 | headers: { 224 | 'accept': 'application/json', 225 | 'authorization': `Bearer ${token}`, 226 | 'cookie': cookie 227 | }, 228 | body: formData, 229 | }); 230 | 231 | const data = await response.json(); 232 | 233 | return new Response(JSON.stringify(data), { 234 | headers: { 235 | 'Content-Type': 'application/json', 236 | 'Access-Control-Allow-Origin': '*', 237 | }, 238 | }); 239 | } catch (error) { 240 | return new Response(JSON.stringify({ 241 | error: error.message || 'Proxy upload failed' 242 | }), { 243 | status: 500, 244 | headers: { 245 | 'Content-Type': 'application/json', 246 | 'Access-Control-Allow-Origin': '*', 247 | }, 248 | }); 249 | } 250 | } 251 | 252 | // 通用的识别函数 253 | async function recognizeImage(token, imageId, request) { 254 | const cookie = request.headers.get('x-custom-cookie') || ''; 255 | 256 | // 从请求头中获取高级模式状态和自定义prompt 257 | const advancedMode = request.headers.get('x-advanced-mode') === 'true'; 258 | 259 | // 解码自定义prompt 260 | let customPrompt = ''; 261 | try { 262 | const encodedPrompt = request.headers.get('x-custom-prompt'); 263 | if (encodedPrompt) { 264 | customPrompt = decodeURIComponent(atob(encodedPrompt)); 265 | } 266 | } catch (error) { 267 | console.error('Prompt解码错误:', error); 268 | } 269 | 270 | const defaultPrompt = 271 | '不要输出任何额外的解释或说明,禁止输出例如:识别内容、以上内容已严格按照要求进行格式化和转换等相关无意义的文字!' + '请识别图片中的内容,注意以下要求:\n' + 272 | '对于数学公式和普通文本:\n' + 273 | '1. 所有数学公式和数学符号都必须使用标准的LaTeX格式\n' + 274 | '2. 行内公式使用单个$符号包裹,如:$x^2$\n' + 275 | '3. 独立公式块使用两个$$符号包裹,如:$$\\sum_{i=1}^n i^2$$\n' + 276 | '4. 普通文本保持原样,不要使用LaTeX格式\n' + 277 | '5. 保持原文的段落格式和换行\n' + 278 | '6. 明显的换行使用\\n表示\n' + 279 | '7. 确保所有数学符号都被正确包裹在$或$$中\n\n' + 280 | '对于验证码图片:\n' + 281 | '1. 只输出验证码字符,不要加任何额外解释\n' + 282 | '2. 忽略干扰线和噪点\n' + 283 | '3. 注意区分相似字符,如0和O、1和l、2和Z等\n' + 284 | '4. 验证码通常为4-6位字母数字组合\n\n' + 285 | ''; 286 | const response = await fetch('https://chat.qwenlm.ai/api/chat/completions', { 287 | method: 'POST', 288 | headers: { 289 | 'Content-Type': 'application/json', 290 | 'User-Agent': 'PostmanRuntime/7.43.0', 291 | 'accept': '*/*', 292 | 'authorization': `Bearer ${token}`, 293 | 'cookie': cookie, 294 | }, 295 | body: JSON.stringify({ 296 | stream: false, 297 | chat_type: "t2t", 298 | model: 'qwen-max-latest', 299 | messages: [ 300 | { 301 | role: 'user', 302 | content: [ 303 | { 304 | type: 'text', 305 | text: advancedMode ? customPrompt : defaultPrompt, 306 | chat_type: "t2t" 307 | }, 308 | { 309 | type: 'image', 310 | image: imageId, 311 | chat_type: "t2t" 312 | }, 313 | ], 314 | }, 315 | ], 316 | session_id: '1', 317 | chat_id: '2', 318 | id: '3', 319 | }), 320 | }); 321 | 322 | const data = await response.json(); 323 | let result = data.choices[0]?.message?.content || '识别失败'; 324 | 325 | // 只在非高级模式下进行格式化处理 326 | if (!advancedMode) { 327 | // 如果结果长度小于10且只包含字母数字,很可能是验证码 328 | if (result.length <= 10 && /^[A-Za-z0-9]+$/.test(result)) { 329 | return new Response(JSON.stringify({ 330 | success: true, 331 | result: result.toUpperCase(), 332 | type: 'captcha' 333 | }), { 334 | headers: { 335 | 'Content-Type': 'application/json', 336 | 'Access-Control-Allow-Origin': '*', 337 | }, 338 | }); 339 | } 340 | 341 | // 其他情况(数学公式和普通文本)的处理 342 | result = result 343 | .replace(/\\(/g, '\\(') 344 | .replace(/\\)/g, '\\)') 345 | .replace(/\n{3,}/g, '\n\n') 346 | .replace(/([^\n])\n([^\n])/g, '$1\n$2') 347 | .replace(/\$\s+/g, '$') 348 | .replace(/\s+\$/g, '$') 349 | .replace(/\$\$/g, '$$') 350 | .trim(); 351 | } 352 | 353 | return new Response(JSON.stringify({ 354 | success: true, 355 | result: result, 356 | type: 'text' 357 | }), { 358 | headers: { 359 | 'Content-Type': 'application/json', 360 | 'Access-Control-Allow-Origin': '*', 361 | }, 362 | }); 363 | } 364 | 365 | function getHTML() { 366 | const html = [ 367 | '', 368 | '', 369 | '', 370 | '', 371 | '', 372 | '', 373 | 'Qwen VL 智能识别系统', 374 | 375 | // 添加 MathJax 支持 376 | '', 377 | '', 398 | '', 399 | '', 413 | 414 | '', 1267 | '', 1268 | '', 1269 | '', 1270 | ' 获取Cookie', 1271 | '', 1272 | '', 1273 | ' ', 1274 | ' ', 1275 | ' ', 1276 | '', 1277 | '', 1278 | '', 1305 | 1306 | '
', 1307 | '

Qwen VL 智能识别系统

', 1308 | '
基于通义千问大模型的多模态智能识别引擎
', 1309 | '
', 1310 | '📸', 1311 | '
', 1312 | '拖拽图片到这里,点击上传,或粘贴图片/Base64/URL
', 1313 | '支持多种输入方式', 1314 | '
', 1315 | '
', 1316 | '', 1317 | '', 1318 | '
', 1319 | '', 1320 | '', 1321 | '
', 1322 | '
', 1323 | '', 1324 | '
', 1325 | '
', 1326 | '
', 1327 | '
', 1328 | '识别结果', 1329 | '', 1330 | '
', 1331 | '
', 1332 | '
', 1333 | '', 1334 | '
', 1335 | '
', 1336 | '

识别历史

', 1337 | '', 1338 | '
', 1339 | '
', 1340 | '
', 1341 | '', 1346 | '
', 1347 | 1348 | '', 1351 | 1352 | '', 2089 | '', 2090 | '' 2091 | ].join('\n'); 2092 | 2093 | return html; 2094 | } --------------------------------------------------------------------------------