├── .gitignore ├── README.md ├── poetry.lock ├── pyproject.toml ├── requirements.txt ├── run.bat └── src ├── __init__.py ├── config.py ├── gui.py ├── main.py └── ocr.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | downloads/ 11 | eggs/ 12 | .eggs/ 13 | lib/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | wheels/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Virtual Environment 24 | venv/ 25 | env/ 26 | ENV/ 27 | 28 | # IDE 29 | .idea/ 30 | .vscode/ 31 | *.swp 32 | *.swo 33 | 34 | # Project specific 35 | config.json 36 | results_pdf/ 37 | results_image/ 38 | *.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mistral OCR 文档识别工具 2 | 3 | 基于 Mistral API 的文档识别工具,支持处理 PDF 和图片文件。 4 | 5 | ## 功能特点 6 | 7 | - 支持处理 PDF 文件和图片文件(JPG、JPEG、PNG) 8 | - 提供图形用户界面和命令行界面 9 | - 自动保存处理结果为 Markdown 格式 10 | - 支持配置文件管理 11 | - 支持批量处理文件 12 | 13 | ## 安装 14 | 15 | 1. 克隆项目代码: 16 | 17 | ```bash 18 | git clone https://github.com/yourusername/mistralOCR.git 19 | cd mistralOCR 20 | ``` 21 | 22 | 2. 安装依赖: 23 | 24 | ```bash 25 | pip install -r requirements.txt 26 | ``` 27 | 28 | 3. 安装tkinter(如果尚未安装): 29 | 30 | 在Mac上,您可以使用以下命令安装tkinter: 31 | 32 | ```bash 33 | brew install python-tk 34 | ``` 35 | 36 | ## 使用方法 37 | 38 | ### 图形界面 39 | 40 | 运行以下命令启动图形界面: 41 | 42 | ```bash 43 | python -m src.main --gui 44 | ``` 45 | 46 | ### 命令行界面 47 | 48 | 使用命令行处理文件: 49 | 50 | ```bash 51 | python -m src.main --file <文件路径> --api-key 52 | ``` 53 | 54 | ### Windows下运行 55 | 56 | 在Windows系统中,您可以直接双击运行 `run.bat` 文件来启动程序。 57 | 58 | ```bash 59 | # 直接双击 run.bat 文件 60 | ``` 61 | 62 | ## 项目结构 63 | 64 | ``` 65 | mistralOCR/ 66 | ├── src/ # 源代码目录 67 | │ ├── __init__.py # 包初始化文件 68 | │ ├── main.py # 主程序入口 69 | │ ├── ocr.py # OCR核心功能 70 | │ ├── gui.py # 图形界面 71 | │ └── config.py # 配置管理 72 | ├── tests/ # 测试目录 73 | ├── docs/ # 文档目录 74 | ├── results_pdf/ # PDF处理结果 75 | ├── results_image/ # 图片处理结果 76 | ├── README.md # 项目说明 77 | ├── requirements.txt # 依赖列表 78 | └── config.json # 配置文件 79 | ``` 80 | 81 | ## 配置文件 82 | 83 | 配置文件 `config.json` 用于存储常用设置: 84 | 85 | ```json 86 | { 87 | "api_key": "your-api-key" 88 | } 89 | ``` 90 | 91 | 请注意,您可以在以下网址获取Mistral API Key: [https://console.mistral.ai/](https://console.mistral.ai/) 92 | 93 | ## 开发说明 94 | 95 | ### 环境要求 96 | 97 | - Python 3.7+ 98 | - 依赖包见 requirements.txt 99 | 100 | ### 开发设置 101 | 102 | 1. 创建虚拟环境: 103 | 104 | ```bash 105 | python -m venv venv 106 | source venv/bin/activate # Linux/Mac 107 | venv\Scripts\activate # Windows 108 | ``` 109 | 110 | 2. 安装开发依赖: 111 | 112 | ```bash 113 | pip install -r requirements-dev.txt 114 | ``` 115 | 116 | ### 运行测试 117 | 118 | ```bash 119 | python -m pytest tests/ 120 | ``` 121 | 122 | ## 许可证 123 | 124 | MIT License 125 | 126 | ## 贡献指南 127 | 128 | 1. Fork 项目 129 | 2. 创建特性分支 130 | 3. 提交更改 131 | 4. 推送到分支 132 | 5. 创建 Pull Request 133 | 134 | ## 问题反馈 135 | 136 | 如果您在使用过程中遇到任何问题,请在 GitHub Issues 页面提交问题。 -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "annotated-types" 5 | version = "0.7.0" 6 | description = "Reusable constraint types to use with typing.Annotated" 7 | optional = false 8 | python-versions = ">=3.8" 9 | files = [ 10 | {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, 11 | {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, 12 | ] 13 | 14 | [package.source] 15 | type = "legacy" 16 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 17 | reference = "tsinghua" 18 | 19 | [[package]] 20 | name = "anyio" 21 | version = "4.8.0" 22 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 23 | optional = false 24 | python-versions = ">=3.9" 25 | files = [ 26 | {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, 27 | {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, 28 | ] 29 | 30 | [package.dependencies] 31 | idna = ">=2.8" 32 | sniffio = ">=1.1" 33 | typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} 34 | 35 | [package.extras] 36 | doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] 37 | test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] 38 | trio = ["trio (>=0.26.1)"] 39 | 40 | [package.source] 41 | type = "legacy" 42 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 43 | reference = "tsinghua" 44 | 45 | [[package]] 46 | name = "certifi" 47 | version = "2025.1.31" 48 | description = "Python package for providing Mozilla's CA Bundle." 49 | optional = false 50 | python-versions = ">=3.6" 51 | files = [ 52 | {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, 53 | {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, 54 | ] 55 | 56 | [package.source] 57 | type = "legacy" 58 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 59 | reference = "tsinghua" 60 | 61 | [[package]] 62 | name = "eval-type-backport" 63 | version = "0.2.2" 64 | description = "Like `typing._eval_type`, but lets older Python versions use newer typing features." 65 | optional = false 66 | python-versions = ">=3.8" 67 | files = [ 68 | {file = "eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a"}, 69 | {file = "eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1"}, 70 | ] 71 | 72 | [package.extras] 73 | tests = ["pytest"] 74 | 75 | [package.source] 76 | type = "legacy" 77 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 78 | reference = "tsinghua" 79 | 80 | [[package]] 81 | name = "h11" 82 | version = "0.14.0" 83 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 84 | optional = false 85 | python-versions = ">=3.7" 86 | files = [ 87 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, 88 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, 89 | ] 90 | 91 | [package.source] 92 | type = "legacy" 93 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 94 | reference = "tsinghua" 95 | 96 | [[package]] 97 | name = "httpcore" 98 | version = "1.0.7" 99 | description = "A minimal low-level HTTP client." 100 | optional = false 101 | python-versions = ">=3.8" 102 | files = [ 103 | {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, 104 | {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, 105 | ] 106 | 107 | [package.dependencies] 108 | certifi = "*" 109 | h11 = ">=0.13,<0.15" 110 | 111 | [package.extras] 112 | asyncio = ["anyio (>=4.0,<5.0)"] 113 | http2 = ["h2 (>=3,<5)"] 114 | socks = ["socksio (==1.*)"] 115 | trio = ["trio (>=0.22.0,<1.0)"] 116 | 117 | [package.source] 118 | type = "legacy" 119 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 120 | reference = "tsinghua" 121 | 122 | [[package]] 123 | name = "httpx" 124 | version = "0.28.1" 125 | description = "The next generation HTTP client." 126 | optional = false 127 | python-versions = ">=3.8" 128 | files = [ 129 | {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, 130 | {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, 131 | ] 132 | 133 | [package.dependencies] 134 | anyio = "*" 135 | certifi = "*" 136 | httpcore = "==1.*" 137 | idna = "*" 138 | 139 | [package.extras] 140 | brotli = ["brotli", "brotlicffi"] 141 | cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] 142 | http2 = ["h2 (>=3,<5)"] 143 | socks = ["socksio (==1.*)"] 144 | zstd = ["zstandard (>=0.18.0)"] 145 | 146 | [package.source] 147 | type = "legacy" 148 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 149 | reference = "tsinghua" 150 | 151 | [[package]] 152 | name = "idna" 153 | version = "3.10" 154 | description = "Internationalized Domain Names in Applications (IDNA)" 155 | optional = false 156 | python-versions = ">=3.6" 157 | files = [ 158 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 159 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 160 | ] 161 | 162 | [package.extras] 163 | all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 164 | 165 | [package.source] 166 | type = "legacy" 167 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 168 | reference = "tsinghua" 169 | 170 | [[package]] 171 | name = "jsonpath-python" 172 | version = "1.0.6" 173 | description = "A more powerful JSONPath implementation in modern python" 174 | optional = false 175 | python-versions = ">=3.6" 176 | files = [ 177 | {file = "jsonpath-python-1.0.6.tar.gz", hash = "sha256:dd5be4a72d8a2995c3f583cf82bf3cd1a9544cfdabf2d22595b67aff07349666"}, 178 | {file = "jsonpath_python-1.0.6-py3-none-any.whl", hash = "sha256:1e3b78df579f5efc23565293612decee04214609208a2335884b3ee3f786b575"}, 179 | ] 180 | 181 | [package.source] 182 | type = "legacy" 183 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 184 | reference = "tsinghua" 185 | 186 | [[package]] 187 | name = "mistralai" 188 | version = "1.5.1" 189 | description = "Python Client SDK for the Mistral AI API." 190 | optional = false 191 | python-versions = ">=3.8" 192 | files = [ 193 | {file = "mistralai-1.5.1-py3-none-any.whl", hash = "sha256:881f8a1b9a7966d15bd1eb4ed05df09483c261f826c1b9d153ceeca605dc79ac"}, 194 | {file = "mistralai-1.5.1.tar.gz", hash = "sha256:ce4b8c7aa587521c46dbc45d42e27575f6197c0229f47242e5b331875104707b"}, 195 | ] 196 | 197 | [package.dependencies] 198 | eval-type-backport = ">=0.2.0" 199 | httpx = ">=0.27.0" 200 | jsonpath-python = ">=1.0.6" 201 | pydantic = ">=2.9.0" 202 | python-dateutil = ">=2.8.2" 203 | typing-inspect = ">=0.9.0" 204 | 205 | [package.extras] 206 | gcp = ["google-auth (>=2.27.0)", "requests (>=2.32.3)"] 207 | 208 | [package.source] 209 | type = "legacy" 210 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 211 | reference = "tsinghua" 212 | 213 | [[package]] 214 | name = "mypy-extensions" 215 | version = "1.0.0" 216 | description = "Type system extensions for programs checked with the mypy type checker." 217 | optional = false 218 | python-versions = ">=3.5" 219 | files = [ 220 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 221 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 222 | ] 223 | 224 | [package.source] 225 | type = "legacy" 226 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 227 | reference = "tsinghua" 228 | 229 | [[package]] 230 | name = "pydantic" 231 | version = "2.10.6" 232 | description = "Data validation using Python type hints" 233 | optional = false 234 | python-versions = ">=3.8" 235 | files = [ 236 | {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, 237 | {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, 238 | ] 239 | 240 | [package.dependencies] 241 | annotated-types = ">=0.6.0" 242 | pydantic-core = "2.27.2" 243 | typing-extensions = ">=4.12.2" 244 | 245 | [package.extras] 246 | email = ["email-validator (>=2.0.0)"] 247 | timezone = ["tzdata"] 248 | 249 | [package.source] 250 | type = "legacy" 251 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 252 | reference = "tsinghua" 253 | 254 | [[package]] 255 | name = "pydantic-core" 256 | version = "2.27.2" 257 | description = "Core functionality for Pydantic validation and serialization" 258 | optional = false 259 | python-versions = ">=3.8" 260 | files = [ 261 | {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, 262 | {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, 263 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, 264 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, 265 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, 266 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, 267 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, 268 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, 269 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, 270 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, 271 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, 272 | {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, 273 | {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, 274 | {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, 275 | {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, 276 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, 277 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, 278 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, 279 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, 280 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, 281 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, 282 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, 283 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, 284 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, 285 | {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, 286 | {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, 287 | {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, 288 | {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, 289 | {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, 290 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, 291 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, 292 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, 293 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, 294 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, 295 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, 296 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, 297 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, 298 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, 299 | {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, 300 | {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, 301 | {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, 302 | {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, 303 | {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, 304 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, 305 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, 306 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, 307 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, 308 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, 309 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, 310 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, 311 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, 312 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, 313 | {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, 314 | {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, 315 | {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, 316 | {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, 317 | {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, 318 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, 319 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, 320 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, 321 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, 322 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, 323 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, 324 | {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, 325 | {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, 326 | {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, 327 | {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, 328 | {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, 329 | {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, 330 | {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, 331 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, 332 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, 333 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, 334 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, 335 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, 336 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, 337 | {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, 338 | {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, 339 | {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, 340 | {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, 341 | {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, 342 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, 343 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, 344 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, 345 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, 346 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, 347 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, 348 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, 349 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, 350 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, 351 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, 352 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, 353 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, 354 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, 355 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, 356 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, 357 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, 358 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, 359 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, 360 | {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, 361 | ] 362 | 363 | [package.dependencies] 364 | typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" 365 | 366 | [package.source] 367 | type = "legacy" 368 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 369 | reference = "tsinghua" 370 | 371 | [[package]] 372 | name = "python-dateutil" 373 | version = "2.9.0.post0" 374 | description = "Extensions to the standard Python datetime module" 375 | optional = false 376 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 377 | files = [ 378 | {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, 379 | {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, 380 | ] 381 | 382 | [package.dependencies] 383 | six = ">=1.5" 384 | 385 | [package.source] 386 | type = "legacy" 387 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 388 | reference = "tsinghua" 389 | 390 | [[package]] 391 | name = "six" 392 | version = "1.17.0" 393 | description = "Python 2 and 3 compatibility utilities" 394 | optional = false 395 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 396 | files = [ 397 | {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, 398 | {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, 399 | ] 400 | 401 | [package.source] 402 | type = "legacy" 403 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 404 | reference = "tsinghua" 405 | 406 | [[package]] 407 | name = "sniffio" 408 | version = "1.3.1" 409 | description = "Sniff out which async library your code is running under" 410 | optional = false 411 | python-versions = ">=3.7" 412 | files = [ 413 | {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, 414 | {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, 415 | ] 416 | 417 | [package.source] 418 | type = "legacy" 419 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 420 | reference = "tsinghua" 421 | 422 | [[package]] 423 | name = "typing-extensions" 424 | version = "4.12.2" 425 | description = "Backported and Experimental Type Hints for Python 3.8+" 426 | optional = false 427 | python-versions = ">=3.8" 428 | files = [ 429 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 430 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 431 | ] 432 | 433 | [package.source] 434 | type = "legacy" 435 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 436 | reference = "tsinghua" 437 | 438 | [[package]] 439 | name = "typing-inspect" 440 | version = "0.9.0" 441 | description = "Runtime inspection utilities for typing module." 442 | optional = false 443 | python-versions = "*" 444 | files = [ 445 | {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, 446 | {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, 447 | ] 448 | 449 | [package.dependencies] 450 | mypy-extensions = ">=0.3.0" 451 | typing-extensions = ">=3.7.4" 452 | 453 | [package.source] 454 | type = "legacy" 455 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" 456 | reference = "tsinghua" 457 | 458 | [metadata] 459 | lock-version = "2.0" 460 | python-versions = "^3.12" 461 | content-hash = "d34077ce4e832074fe1beccbaa2047a6a5d68cc8ee323f412d16c88a45760766" 462 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "mistralocr" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["andy"] 6 | readme = "README.md" 7 | package-mode = false 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.12" 11 | mistralai = "^1.5.1" 12 | 13 | 14 | [[tool.poetry.source]] 15 | name = "tsinghua" 16 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" 17 | priority = "primary" 18 | 19 | 20 | [[tool.poetry.source]] 21 | name = "PyPI" 22 | priority = "supplemental" 23 | 24 | [build-system] 25 | requires = ["poetry-core"] 26 | build-backend = "poetry.core.masonry.api" 27 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mistralai>=0.0.7 2 | tkinter>=8.6 3 | pathlib>=1.0.1 -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | chcp 65001 > nul 3 | echo [启动] Mistral OCR 文档识别工具... 4 | 5 | REM 尝试使用 poetry run 6 | poetry run python -m src.main --gui 2>nul 7 | if %errorlevel% equ 0 ( 8 | exit /b 0 9 | ) 10 | 11 | REM 如果 poetry 失败,尝试直接使用 python 12 | echo [信息] Poetry 未安装或运行失败,尝试直接使用 Python... 13 | python -m src.main --gui 14 | if %errorlevel% neq 0 ( 15 | echo [错误] 运行失败!请确保已安装 Python 并添加到系统环境变量中。 16 | echo [提示] 您也可以尝试手动运行:python -m src.main --gui 17 | pause 18 | exit /b 1 19 | ) 20 | 21 | exit /b 0 -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MistralOCR包初始化文件 3 | """ 4 | from .ocr import process_file 5 | from .gui import main as run_gui 6 | from .config import Config 7 | 8 | __version__ = "0.1.0" 9 | __all__ = ["process_file", "run_gui", "Config"] -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | 配置管理模块,提供配置文件读写功能 3 | """ 4 | import json 5 | import os 6 | from pathlib import Path 7 | from typing import Dict, Any, Optional 8 | 9 | class Config: 10 | """配置管理类""" 11 | 12 | def __init__(self, config_file: str = "config.json"): 13 | """ 14 | 初始化配置管理器 15 | 16 | Args: 17 | config_file: 配置文件路径 18 | """ 19 | self.config_file = config_file 20 | self.config: Dict[str, Any] = {} 21 | self.load() 22 | 23 | def load(self) -> None: 24 | """ 25 | 加载配置文件 26 | 如果配置文件不存在,将创建一个空配置 27 | """ 28 | try: 29 | if os.path.exists(self.config_file): 30 | with open(self.config_file, 'r', encoding='utf-8') as f: 31 | self.config = json.load(f) 32 | except Exception as e: 33 | print(f"加载配置文件失败: {e}") 34 | self.config = {} 35 | 36 | def save(self) -> None: 37 | """ 38 | 保存配置到文件 39 | """ 40 | try: 41 | with open(self.config_file, 'w', encoding='utf-8') as f: 42 | json.dump(self.config, f, indent=4, ensure_ascii=False) 43 | except Exception as e: 44 | print(f"保存配置文件失败: {e}") 45 | 46 | def get(self, key: str, default: Any = None) -> Any: 47 | """ 48 | 获取配置项 49 | 50 | Args: 51 | key: 配置项键名 52 | default: 默认值 53 | 54 | Returns: 55 | 配置项值 56 | """ 57 | return self.config.get(key, default) 58 | 59 | def set(self, key: str, value: Any) -> None: 60 | """ 61 | 设置配置项 62 | 63 | Args: 64 | key: 配置项键名 65 | value: 配置项值 66 | """ 67 | self.config[key] = value 68 | self.save() 69 | 70 | def delete(self, key: str) -> None: 71 | """ 72 | 删除配置项 73 | 74 | Args: 75 | key: 配置项键名 76 | """ 77 | if key in self.config: 78 | del self.config[key] 79 | self.save() 80 | 81 | def clear(self) -> None: 82 | """清空所有配置""" 83 | self.config = {} 84 | self.save() -------------------------------------------------------------------------------- /src/gui.py: -------------------------------------------------------------------------------- 1 | """ 2 | GUI模块,提供图形用户界面功能 3 | """ 4 | import tkinter as tk 5 | from tkinter import ttk, filedialog, messagebox 6 | from pathlib import Path 7 | import os 8 | import platform 9 | 10 | from .config import Config 11 | from .ocr import process_file 12 | 13 | class OCRGUI: 14 | """OCR图形界面类""" 15 | 16 | def __init__(self, root: tk.Tk): 17 | """ 18 | 初始化GUI 19 | 20 | Args: 21 | root: Tkinter根窗口 22 | """ 23 | self.root = root 24 | self.root.title("Mistral OCR 处理工具") 25 | self.root.geometry("600x400") 26 | 27 | # 在Mac系统上提升窗口 28 | if platform.system() == 'Darwin': 29 | self.root.createcommand('tk::mac::ReopenApplication', self.root.lift) 30 | 31 | self.config = Config() 32 | 33 | self._init_ui() 34 | self._load_saved_config() 35 | 36 | # 确保窗口被激活 37 | self.root.lift() 38 | self.root.attributes('-topmost', True) 39 | self.root.after_idle(self.root.attributes, '-topmost', False) 40 | 41 | def _init_ui(self) -> None: 42 | """初始化用户界面""" 43 | # 创建主框架 44 | self.main_frame = ttk.Frame(self.root, padding="10") 45 | self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) 46 | 47 | # API Key 输入 48 | ttk.Label(self.main_frame, text="API Key:").grid(row=0, column=0, sticky=tk.W, pady=5) 49 | self.api_key_var = tk.StringVar() 50 | self.api_key_entry = ttk.Entry(self.main_frame, textvariable=self.api_key_var, width=50) 51 | self.api_key_entry.grid(row=0, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5) 52 | 53 | # 文件选择 54 | ttk.Label(self.main_frame, text="选择文件:").grid(row=1, column=0, sticky=tk.W, pady=5) 55 | self.file_path_var = tk.StringVar() 56 | self.file_path_entry = ttk.Entry(self.main_frame, textvariable=self.file_path_var, width=40) 57 | self.file_path_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5) 58 | ttk.Button(self.main_frame, text="浏览", command=self._select_file).grid(row=1, column=2, padx=5, pady=5) 59 | 60 | # 支持的文件类型提示 61 | ttk.Label(self.main_frame, text="支持的文件类型: PDF, JPG, JPEG, PNG", 62 | font=("", 8)).grid(row=2, column=0, columnspan=3, sticky=tk.W, pady=(0,10)) 63 | 64 | # 处理按钮 65 | self.process_button = ttk.Button(self.main_frame, text="开始处理", command=self._process_document) 66 | self.process_button.grid(row=3, column=0, columnspan=3, pady=20) 67 | 68 | # 状态显示 69 | self.status_var = tk.StringVar() 70 | ttk.Label(self.main_frame, textvariable=self.status_var).grid(row=4, column=0, columnspan=3, pady=5) 71 | 72 | # 结果显示区域 73 | self.result_text = tk.Text(self.main_frame, height=8, width=50) 74 | self.result_text.grid(row=5, column=0, columnspan=3, pady=5) 75 | self.result_text.insert('1.0', '处理结果将显示在这里...\n') 76 | self.result_text.config(state='disabled') 77 | 78 | # 配置窗口大小调整 79 | self.root.columnconfigure(0, weight=1) 80 | self.root.rowconfigure(0, weight=1) 81 | self.main_frame.columnconfigure(1, weight=1) 82 | 83 | def _load_saved_config(self) -> None: 84 | """加载保存的配置""" 85 | api_key = self.config.get('api_key', '') 86 | self.api_key_var.set(api_key) 87 | 88 | def _select_file(self) -> None: 89 | """选择要处理的文件""" 90 | filetypes = [ 91 | ("所有支持的文件", "*.pdf *.jpg *.jpeg *.png"), 92 | ("PDF文件", "*.pdf"), 93 | ("图片文件", "*.jpg *.jpeg *.png"), 94 | ("所有文件", "*.*") 95 | ] 96 | 97 | # 确保对话框在主窗口之上 98 | self.root.lift() 99 | self.root.focus_force() 100 | 101 | try: 102 | filename = filedialog.askopenfilename( 103 | title="选择文件", 104 | filetypes=filetypes, 105 | parent=self.root 106 | ) 107 | 108 | if filename: 109 | self.file_path_var.set(filename) 110 | self.root.focus_force() # 重新获取焦点 111 | except Exception as e: 112 | messagebox.showerror("错误", f"选择文件时出错:{str(e)}") 113 | 114 | def _update_result_text(self, text: str) -> None: 115 | """ 116 | 更新结果显示区域的文本 117 | 118 | Args: 119 | text: 要显示的文本 120 | """ 121 | self.result_text.config(state='normal') 122 | self.result_text.delete('1.0', tk.END) 123 | self.result_text.insert('1.0', text) 124 | self.result_text.config(state='disabled') 125 | 126 | def _process_document(self) -> None: 127 | """处理文档""" 128 | api_key = self.api_key_var.get().strip() 129 | file_path = self.file_path_var.get().strip() 130 | 131 | if not api_key: 132 | messagebox.showerror("错误", "请输入API Key") 133 | return 134 | 135 | if not file_path: 136 | messagebox.showerror("错误", "请选择要处理的文件") 137 | return 138 | 139 | try: 140 | self.status_var.set("正在处理中...") 141 | self.process_button.state(['disabled']) 142 | self._update_result_text("处理中,请稍候...") 143 | self.root.update() 144 | 145 | # 保存API Key 146 | self.config.set('api_key', api_key) 147 | 148 | # 处理文档 149 | output_dir = process_file(file_path, api_key) 150 | 151 | # 获取文件类型和名称 152 | file = Path(file_path) 153 | is_pdf = file.suffix.lower() == '.pdf' 154 | result_dir = 'results_pdf' if is_pdf else 'results_image' 155 | 156 | if is_pdf: 157 | result_file = os.path.join(result_dir, file.stem, f"{file.stem}.md") 158 | else: 159 | result_file = os.path.join(result_dir, f"{file.stem}.md") 160 | 161 | if os.path.exists(result_file): 162 | with open(result_file, 'r', encoding='utf-8') as f: 163 | result_text = f"处理完成!\n\n结果保存在: {result_file}\n\n" 164 | result_text += "文件内容预览:\n" + "-" * 40 + "\n" 165 | result_text += f.read()[:500] + "..." # 只显示前500个字符 166 | self._update_result_text(result_text) 167 | 168 | self.status_var.set("处理完成!") 169 | messagebox.showinfo("成功", f"文档处理完成!\n结果保存在: {result_file}") 170 | except Exception as e: 171 | self.status_var.set("处理失败") 172 | error_message = f"处理过程中出现错误:{str(e)}" 173 | self._update_result_text(error_message) 174 | messagebox.showerror("错误", error_message) 175 | finally: 176 | self.process_button.state(['!disabled']) 177 | 178 | def main(): 179 | """启动GUI应用""" 180 | root = tk.Tk() 181 | app = OCRGUI(root) 182 | root.mainloop() 183 | 184 | if __name__ == "__main__": 185 | main() -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | 主程序入口文件 3 | """ 4 | import sys 5 | import argparse 6 | from pathlib import Path 7 | 8 | from .ocr import process_file 9 | from .gui import main as run_gui 10 | 11 | def parse_args(): 12 | """解析命令行参数""" 13 | parser = argparse.ArgumentParser(description="Mistral OCR 文档识别工具") 14 | parser.add_argument("--gui", action="store_true", help="启动图形界面") 15 | parser.add_argument("--file", type=str, help="要处理的文件路径") 16 | parser.add_argument("--api-key", type=str, help="Mistral API密钥") 17 | return parser.parse_args() 18 | 19 | def main(): 20 | """主程序入口""" 21 | args = parse_args() 22 | 23 | if args.gui: 24 | run_gui() 25 | elif args.file and args.api_key: 26 | try: 27 | output_dir = process_file(args.file, args.api_key) 28 | print(f"处理完成!结果保存在: {output_dir}") 29 | except Exception as e: 30 | print(f"处理失败: {e}", file=sys.stderr) 31 | sys.exit(1) 32 | else: 33 | print("请提供文件路径和API密钥,或使用--gui参数启动图形界面", file=sys.stderr) 34 | sys.exit(1) 35 | 36 | if __name__ == "__main__": 37 | main() -------------------------------------------------------------------------------- /src/ocr.py: -------------------------------------------------------------------------------- 1 | """ 2 | OCR核心功能模块,提供文档识别相关功能 3 | """ 4 | from mistralai import Mistral 5 | from pathlib import Path 6 | import os 7 | import base64 8 | from mistralai import DocumentURLChunk, ImageURLChunk 9 | from mistralai.models import OCRResponse 10 | from typing import Union, Literal 11 | 12 | def replace_images_in_markdown(markdown_str: str, images_dict: dict) -> str: 13 | """ 14 | 替换Markdown中的图片引用 15 | 16 | Args: 17 | markdown_str: Markdown文本 18 | images_dict: 图片映射字典 19 | 20 | Returns: 21 | 替换后的Markdown文本 22 | """ 23 | for img_name, img_path in images_dict.items(): 24 | markdown_str = markdown_str.replace(f"![{img_name}]({img_name})", f"![{img_name}]({img_path})") 25 | return markdown_str 26 | 27 | def save_ocr_results(ocr_response: OCRResponse, original_file: Path, file_type: Literal['pdf', 'image']) -> str: 28 | """ 29 | 保存OCR结果 30 | 31 | Args: 32 | ocr_response: OCR响应结果 33 | original_file: 原始文件路径 34 | file_type: 文件类型 ('pdf' 或 'image') 35 | 36 | Returns: 37 | 输出目录路径 38 | """ 39 | base_dir = 'results_pdf' if file_type == 'pdf' else 'results_image' 40 | os.makedirs(base_dir, exist_ok=True) 41 | 42 | original_name = original_file.stem 43 | 44 | if file_type == 'pdf': 45 | output_dir = os.path.join(base_dir, original_name) 46 | os.makedirs(output_dir, exist_ok=True) 47 | images_dir = os.path.join(output_dir, "images") 48 | os.makedirs(images_dir, exist_ok=True) 49 | 50 | all_markdowns = [] 51 | for page in ocr_response.pages: 52 | page_images = {} 53 | for img in page.images: 54 | img_data = base64.b64decode(img.image_base64.split(',')[1]) 55 | img_path = os.path.join(images_dir, f"{img.id}.png") 56 | with open(img_path, 'wb') as f: 57 | f.write(img_data) 58 | page_images[img.id] = f"images/{img.id}.png" 59 | 60 | page_markdown = replace_images_in_markdown(page.markdown, page_images) 61 | all_markdowns.append(page_markdown) 62 | 63 | output_file = os.path.join(output_dir, f"{original_name}.md") 64 | with open(output_file, 'w', encoding='utf-8') as f: 65 | f.write("\n\n".join(all_markdowns)) 66 | 67 | else: 68 | output_file = os.path.join(base_dir, f"{original_name}.md") 69 | with open(output_file, 'w', encoding='utf-8') as f: 70 | f.write(ocr_response.pages[0].markdown) 71 | 72 | return base_dir 73 | 74 | def process_image(file_path: str, client: Mistral) -> OCRResponse: 75 | """ 76 | 处理图片文件 77 | 78 | Args: 79 | file_path: 图片文件路径 80 | client: Mistral客户端实例 81 | 82 | Returns: 83 | OCR处理结果 84 | """ 85 | file = Path(file_path) 86 | 87 | uploaded_file = client.files.upload( 88 | file={ 89 | "file_name": file.name, 90 | "content": file.read_bytes(), 91 | }, 92 | purpose="ocr", 93 | ) 94 | 95 | signed_url = client.files.get_signed_url(file_id=uploaded_file.id, expiry=1) 96 | 97 | response = client.ocr.process( 98 | document=ImageURLChunk(image_url=signed_url.url), 99 | model="mistral-ocr-latest", 100 | include_image_base64=True 101 | ) 102 | 103 | return response 104 | 105 | def process_pdf(file_path: str, client: Mistral) -> OCRResponse: 106 | """ 107 | 处理PDF文件 108 | 109 | Args: 110 | file_path: PDF文件路径 111 | client: Mistral客户端实例 112 | 113 | Returns: 114 | OCR处理结果 115 | """ 116 | file = Path(file_path) 117 | 118 | uploaded_file = client.files.upload( 119 | file={ 120 | "file_name": file.name, 121 | "content": file.read_bytes(), 122 | }, 123 | purpose="ocr", 124 | ) 125 | 126 | signed_url = client.files.get_signed_url(file_id=uploaded_file.id, expiry=1) 127 | 128 | response = client.ocr.process( 129 | document=DocumentURLChunk(document_url=signed_url.url), 130 | model="mistral-ocr-latest", 131 | include_image_base64=True 132 | ) 133 | 134 | return response 135 | 136 | def process_file(file_path: str, api_key: str) -> str: 137 | """ 138 | 处理PDF或图片文件 139 | 140 | Args: 141 | file_path: 文件路径 142 | api_key: Mistral API密钥 143 | 144 | Returns: 145 | 输出目录路径 146 | """ 147 | client = Mistral(api_key=api_key) 148 | 149 | file = Path(file_path) 150 | if not file.is_file(): 151 | raise FileNotFoundError(f"文件不存在: {file_path}") 152 | 153 | file_extension = file.suffix.lower() 154 | supported_extensions = {'.pdf', '.jpg', '.jpeg', '.png'} 155 | 156 | if file_extension not in supported_extensions: 157 | raise ValueError(f"不支持的文件类型: {file_extension}。支持的类型: {', '.join(supported_extensions)}") 158 | 159 | if file_extension == '.pdf': 160 | response = process_pdf(file_path, client) 161 | output_dir = save_ocr_results(response, file, 'pdf') 162 | else: 163 | response = process_image(file_path, client) 164 | output_dir = save_ocr_results(response, file, 'image') 165 | 166 | return output_dir --------------------------------------------------------------------------------