├── .github └── workflows │ └── build.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── LICENSE ├── __init__.py ├── api │ ├── __init__.py │ ├── html.py │ └── translate.py ├── config │ ├── config.default.json │ └── redis.conf ├── main.py ├── middleware │ ├── __init__.py │ └── req_limit.py ├── routers │ ├── __init__.py │ └── routers.py ├── static │ ├── css │ │ └── style.css │ ├── element-ui │ │ ├── fonts │ │ │ ├── element-icons.ttf │ │ │ └── element-icons.woff │ │ ├── index.css │ │ └── index.js │ ├── images │ │ ├── favicon.ico │ │ ├── icon128.png │ │ └── logo.png │ └── js │ │ ├── axios.min.js │ │ ├── manifest.json │ │ └── vue.min.js ├── templates │ ├── index.html │ ├── index_custom.html │ └── index_old.html └── utils │ ├── __init__.py │ ├── helper.py │ └── version.py ├── install.sh ├── other_deps.txt ├── requirements.txt └── run.sh /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Push Docker Image 2 | 3 | on: 4 | workflow_dispatch: # 只保留手动触发的方式 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v4 13 | 14 | - name: Remove .git directory 15 | run: rm -rf .git 16 | 17 | - name: Login to Docker Hub 18 | uses: docker/login-action@v3 19 | with: 20 | username: ${{ secrets.DOCKERHUB_USERNAME }} 21 | password: ${{ secrets.DOCKERHUB_TOKEN }} 22 | 23 | - name: Build and push Docker image 24 | uses: docker/build-push-action@v5 25 | with: 26 | context: . 27 | push: true 28 | tags: ${{ secrets.DOCKERHUB_USERNAME }}/transmute:latest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | venv 3 | app/data/* 4 | testrun.sh 5 | depends.sh -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 基于redis镜像构建 2 | FROM redis:7-alpine 3 | # 工作目录 4 | WORKDIR /opt/transmute 5 | # 把当前目录下的所有文件拷贝到工作目录 6 | COPY . . 7 | # 执行安装脚本 8 | RUN sh install.sh 9 | # 暴露端口和目录 10 | EXPOSE 2082 11 | VOLUME /opt/transmute/app/data 12 | # 启动命令 13 | CMD ["sh", "run.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Transmute 2 | 3 | Transmute是一款使用AI大模型驱动的智能翻译工具,可以同时对接多个大模型,比如OpenAI、DeepSeek、通义千问、豆包等。Transmute使用Python 3 + FastAPI技术开发。 4 | 5 | ## 主要特点 6 | 7 | * 支持接入多种AI大模型,只要兼容OpenAI API接口均可。 8 | * 支持多种AI模型切换 9 | * AI智能翻译:智能纠错、智能识别、语意优化等。 10 | * 支持流式传输 11 | * 支持限制IP请求频率 12 | * 支持限制输入字符串长度 13 | * PWA支持 14 | 15 | ## 截图 16 | 17 | ![2f339be26c1f5bf9.png](https://img.rss.ink/imgs/2025/03/17/2f339be26c1f5bf9.png) 18 | 19 | ## 安装 20 | 21 | > 目前仅支持Docker安装,请确保您已经安装Docker和Docker Compose 22 | 23 | 新建`docker-compose.yaml`文件,内容如下: 24 | 25 | ```yaml 26 | version: '3.8' 27 | 28 | services: 29 | transmute: 30 | container_name: transmute 31 | image: helloz/transmute 32 | ports: 33 | - "2082:2082" 34 | restart: always 35 | volumes: 36 | - /opt/transmute/app/data:/opt/transmute/app/data 37 | ``` 38 | 39 | 然后输入`docker-compose up -d`启动。 40 | 41 | ## 使用 42 | 43 | Transmute配置文件位于挂载目录下的`config/config.json`,使用标准的json格式: 44 | 45 | ```json 46 | { 47 | "redis":{ 48 | "host":"127.0.0.1", 49 | "port":6379, 50 | "password":"transmute2082", 51 | "db":0 52 | }, 53 | "app":{ 54 | "req_limit":100, 55 | "word_limit":3000 56 | }, 57 | "site":{ 58 | "title":"Transmute", 59 | "keywords":"Transmute,北冥翻译,智能翻译,AI翻译,翻译,翻译工具,翻译软件,翻译器,翻译网站", 60 | "description":"Transmute是一款基于人工智能的翻译工具,支持多种语言互译,提供多种翻译模型。", 61 | "sub_title":"AI大模型驱动的智能翻译工具" 62 | }, 63 | "models":[ 64 | { 65 | "base_url":"https://api.openai.com/v1", 66 | "model":"gpt-4o", 67 | "api_key":"sk-xxx", 68 | "name":"GPT-4o" 69 | } 70 | ] 71 | } 72 | ``` 73 | 74 | 需要修改`models`,添加您自己的AI大模型接口,大模型接口需要兼容OpenAI API格式,同时只需要路径的前缀部分,比如完整的API地址为:`https://api.openai.com/v1/chat/completions`,您只需要填写`https://api.openai.com/v1`,不需要末尾的`/chat/completions`,参数含义如下: 75 | 76 | * `models.[0].base_url`:API前缀地址,不需要末尾的`/chat/completions` 77 | * `models.[0].model`:模型参数值 78 | * `models.[0].api_key`:密钥信息 79 | * `models.[0].name`:前端显示的模型名称 80 | 81 | 可以在`models`节点下添加多个模型,比如: 82 | 83 | ``` 84 | "models":[ 85 | { 86 | "base_url":"https://api.openai.com/v1", 87 | "model":"gpt-4o", 88 | "api_key":"sk-xxx", 89 | "name":"GPT-4o" 90 | }, 91 | { 92 | "base_url":"https://api.deepseek.com/v1", 93 | "model":"deepseek-chat", 94 | "api_key":"sk-xxx", 95 | "name":"DeepSeek" 96 | } 97 | ] 98 | ``` 99 | 100 | **注意事项:** 101 | 102 | 1. 参数修改完毕后请务必校验json格式正确,否则可能导致程序异常 103 | 2. 修改参数后需要重启容器`docker restart transmute`才会生效 104 | 3. 然后访问`http://IP:2082`测试 105 | 106 | **其他参数** 107 | 108 | * `app.req_limit`:单个访客请求频率限制,单位为24H,超出请求频率后将被限制 109 | * `app.word_limit`:最大可输入的字符串长度 110 | * `site`:站点相关的信息 111 | 112 | ### 快捷键 113 | 114 | * 支持`Ctrl + Enter` 或 `Command + Enter`提交翻译 115 | * 支持 `ESC` 清空输入内容和结果 116 | 117 | ## 问题反馈 118 | 119 | * 如果有任何问题可以在[Issues](https://github.com/helloxz/transmute/issues) 中提交。 120 | * 或者添加我的微信:`xiaozme`,请务必备注Transmute 121 | 122 | ## 其他产品 123 | 124 | 如果您有兴趣,还可以了解我们的其他产品。 125 | 126 | * [Zdir](https://www.zdir.pro/zh/) - 一款轻量级、多功能的文件分享程序。 127 | * [OneNav](https://www.onenav.top/) - 高效的浏览器书签管理工具,将您的书签集中式管理。 128 | * [ImgURL](https://www.imgurl.org/) - 2017年上线的免费图床。 -------------------------------------------------------------------------------- /app/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloxz/transmute/2b563f9844763373227020771d902e90bbcd5141/app/__init__.py -------------------------------------------------------------------------------- /app/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloxz/transmute/2b563f9844763373227020771d902e90bbcd5141/app/api/__init__.py -------------------------------------------------------------------------------- /app/api/html.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Request 2 | from fastapi.responses import HTMLResponse 3 | from fastapi.staticfiles import StaticFiles 4 | from fastapi.templating import Jinja2Templates 5 | 6 | 7 | class Html: 8 | def __init__(self): 9 | pass 10 | 11 | async def index(self,request: Request): 12 | templates = Jinja2Templates(directory="app/templates") 13 | template_name = request.app.state.config["app"].get("template_name", "index.html") 14 | # 获取站点信息 15 | siteInfo = request.app.state.config["site"] 16 | return templates.TemplateResponse( 17 | request=request, name=template_name,context={"siteInfo": siteInfo} 18 | ) -------------------------------------------------------------------------------- /app/api/translate.py: -------------------------------------------------------------------------------- 1 | 2 | from openai import OpenAI 3 | from fastapi import Request,HTTPException 4 | from fastapi.responses import StreamingResponse 5 | from app.utils.helper import * 6 | import json 7 | import os 8 | import langid 9 | from datetime import datetime 10 | from app.main import * 11 | from pydantic import BaseModel 12 | import httpx 13 | 14 | # 支持的目标语言 15 | support_languages = [ 16 | { 17 | "name": "自动检测", 18 | "value": "auto" 19 | }, 20 | { 21 | "name": "简体中文", 22 | "value": "zh-CN" 23 | }, 24 | { 25 | "name": "繁体中文", 26 | "value": "zh-HK" 27 | }, 28 | { 29 | "name": "英语", 30 | "value": "en-US" 31 | }, 32 | { 33 | "name": "日语", 34 | "value": "ja-JP" 35 | }, 36 | { 37 | "name": "韩语", 38 | "value": "ko-KR" 39 | }, 40 | { 41 | "name": "西班牙语", 42 | "value": "es-ES" 43 | }, 44 | { 45 | "name": "法语", 46 | "value": "fr-FR" 47 | }, 48 | { 49 | "name": "德语", 50 | "value": "de-DE" 51 | } 52 | ] 53 | 54 | 55 | # ZincSearch 配置,暂时没有使用 56 | ZINCSEARCH_URL = "xxx" # ZincSearch 的 URL 57 | INDEX_NAME = "transmute" # 索引名称 58 | AUTH = ("xxx", "xxx") # 认证信息(用户名和密码) 59 | 60 | async def add_document_to_zincsearch(document: dict): 61 | """ 62 | 将文档异步非阻塞地写入 ZincSearch。 63 | 64 | :param document: 符合 mappings 结构的文档字典。 65 | :return: 写入结果。 66 | """ 67 | # 构建 ZincSearch 的 API URL 68 | url = f"{ZINCSEARCH_URL}/api/{INDEX_NAME}/_doc" 69 | try: 70 | # 发送 POST 请求到 ZincSearch 71 | async with httpx.AsyncClient(auth=AUTH) as client: 72 | response = await client.post(url, json=document) 73 | response.raise_for_status() # 如果请求失败,抛出异常 74 | except httpx.HTTPStatusError as e: 75 | raise HTTPException(status_code=e.response.status_code, detail=str(e)) 76 | return {"status": "success", "response": response.json()} 77 | 78 | 79 | # 声明需要的参数 80 | class InputItem(BaseModel): 81 | target_language: str 82 | model: str 83 | input: str 84 | browser_lang:str = None 85 | 86 | 87 | class Translate: 88 | modelList = [] 89 | def __init__(self): 90 | # 获取当前工作路径 91 | current_path = os.getcwd() 92 | # 打印当前路径 93 | # print("当前路径:", current_path) 94 | # 从配置文件中获取模型列表 95 | with open('app/data/config/config.json', 'r') as file: 96 | data = json.load(file) 97 | 98 | self.modelList = data["models"] 99 | 100 | # 对话接口 101 | async def chat(self,item:InputItem,request: Request = None): 102 | # 解析json数据 103 | target_language = item.target_language 104 | model = item.model 105 | input = item.input 106 | browser_lang = item.browser_lang 107 | # 判断目标语言是否支持 108 | if not self._is_support_language(target_language): 109 | return show_json(400, "The target language is not supported", None) 110 | 111 | # 判断模型是否存在 112 | modelInfo = self._is_support_model(model) 113 | if not modelInfo: 114 | return show_json(400, "The model does not exist", None) 115 | 116 | # print("modelInfo:", modelInfo) 117 | word_limit = request.app.state.config["app"]["word_limit"] 118 | # 判断内容长度,必须2到1000个字符之间 119 | if len(input) < 2 or len(input) > word_limit: 120 | return show_json(400, f'The content length must be between 2 and {word_limit} characters', None) 121 | 122 | # 检测输入语言,然后自动设置目标语言 123 | if target_language == "auto": 124 | # 使用langid库检测语言 125 | lang, _ = langid.classify(input) 126 | if lang == "zh": 127 | target_language = "en-US" 128 | elif lang == "en": 129 | target_language = "zh-CN" 130 | else: 131 | # 如果既不是中文也不是英文,则默认翻译为英文 132 | target_language = "en-US" 133 | 134 | # 设置提示词 135 | prompt = f""" 136 | 你是一个专业的翻译官,请将用户输入的内容翻译为 {target_language} 语言。仅翻译尖括号 <<<>>> 之间的内容,直接返回翻译结果,不要包含 <<<>>> 或其他解释。 137 | 138 | 在进行翻译时,请遵循以下指南: 139 | 1. 仔细识别输入文本中的错别字并进行修正。 140 | 2. 仅翻译尖括号 <<<>>> 之间的内容,直接返回翻译结果,不要包含 <<<>>> 或其他解释。 141 | 3. 确保翻译后的内容在语法和语义上符合目标语言的表达习惯。 142 | 4. 尽量使翻译结果优美、流畅,避免生硬的表达。 143 | 5. 不要改变原文的意思。 144 | 6. 保持原文的段落和格式。 145 | """ 146 | 147 | 148 | # 初始化客户端 149 | client = OpenAI( 150 | api_key=modelInfo["api_key"], # 替换为你的密钥 151 | base_url=modelInfo["base_url"] # 替换为你的地址 152 | ) 153 | 154 | def generate(): 155 | stream = client.chat.completions.create( 156 | model=modelInfo["model"], 157 | temperature=0.5, 158 | messages=[ 159 | {"role": "assistant", "content": prompt}, 160 | { 161 | "role": "user", 162 | "content": f"<<<{input}>>>", 163 | } 164 | ], 165 | stream=True # 启用流式输出 166 | ) 167 | 168 | for chunk in stream: 169 | if chunk.choices and len(chunk.choices) > 0: 170 | content = chunk.choices[0].delta.content 171 | 172 | # print(content) 173 | if content is not None: 174 | value = { 175 | "value": content 176 | } 177 | 178 | 179 | # 按照SSE格式发送数据 180 | # yield f"data: {content}\n\n" 181 | # 使用json.dumps将字典转换为JSON格式的字符串 182 | yield f"data: {json.dumps(value)}\n\n" 183 | 184 | yield f"data: [DONE]\n\n" 185 | 186 | # 请求次数+1 187 | await self._add_req_count(request) 188 | 189 | # 写入日志 190 | """ 191 | asyncio.create_task(add_document_to_zincsearch({ 192 | "ip": get_client_ip(request), 193 | "user_agent": request.headers.get("user-agent"), 194 | "input": input, 195 | "output": self.output, 196 | "target_lang": target_language, 197 | "model": model, 198 | "browser_lang": browser_lang 199 | })) 200 | """ 201 | # 返回流式响应 202 | return StreamingResponse(generate(), media_type="text/event-stream") 203 | 204 | # 判断目标语言是否支持 205 | def _is_support_language(self,target_language: str): 206 | # 遍历support_languages,然后判断self.target_language是否在support_languages中 207 | for lang in support_languages: 208 | if lang["value"] == target_language: 209 | return True 210 | return False 211 | 212 | # 检查模型是否存在 213 | def _is_support_model(self,model: str): 214 | # 如果模型为auto 215 | if model == "auto": 216 | # 直接使用第一个模型 217 | return self.modelList[0] 218 | 219 | # 模型不为空,遍历模型列表 220 | for modelInfo in self.modelList: 221 | if modelInfo["model"] == model: 222 | return modelInfo 223 | 224 | # 如果不存在,返回False 225 | return False 226 | 227 | # 请求次数+1 228 | async def _add_req_count(self,request: Request): 229 | # 生成key 230 | # 获取当前日期并格式化为 YYYYMMDD 231 | current_date = datetime.now().strftime('%Y%m%d') 232 | # request = Request 233 | ip = get_client_ip(request) 234 | key = "transmute:" + current_date + ":" + ip 235 | await request.app.state.redis.incr(key) 236 | # 设置过期时间为24小时 237 | await request.app.state.redis.expire(key, 24 * 60 * 60) 238 | 239 | # 获取模型列表 240 | async def get_models(self,request:Request): 241 | newModelList = [] 242 | # 遍历并去除敏感数据 243 | for model in self.modelList: 244 | newModelList.append({ 245 | "model": model["model"], 246 | "name": model["name"] 247 | }) 248 | return show_json(200, "success", newModelList) 249 | 250 | # 获取支持的目标语言 251 | async def get_languages(self): 252 | return show_json(200, "success", support_languages) -------------------------------------------------------------------------------- /app/config/config.default.json: -------------------------------------------------------------------------------- 1 | { 2 | "redis":{ 3 | "host":"127.0.0.1", 4 | "port":6379, 5 | "password":"transmute2082", 6 | "db":0 7 | }, 8 | "app":{ 9 | "req_limit":100, 10 | "word_limit":3000, 11 | "template_name":"index.html" 12 | }, 13 | "site":{ 14 | "title":"Transmute", 15 | "keywords":"Transmute,北冥翻译,智能翻译,AI翻译,翻译,翻译工具,翻译软件,翻译器,翻译网站", 16 | "description":"Transmute是一款基于人工智能的翻译工具,支持多种语言互译,提供多种翻译模型。", 17 | "sub_title":"AI大模型驱动的智能翻译工具" 18 | }, 19 | "models":[ 20 | { 21 | "base_url":"https://api.openai.com/v1", 22 | "model":"gpt-4o", 23 | "api_key":"sk-xxx", 24 | "name":"GPT-4o" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /app/config/redis.conf: -------------------------------------------------------------------------------- 1 | # 绑定地址,默认只监听本地回环地址 2 | bind 127.0.0.1 3 | 4 | # 保护模式,开启后只允许本地访问 5 | protected-mode yes 6 | 7 | # 设置 Redis 监听端口 8 | port 6379 9 | 10 | # 设置 Redis 访问密码 11 | requirepass transmute2082 12 | 13 | # 最大内存限制,根据你的服务器内存设置 14 | maxmemory 1gb 15 | 16 | # 内存淘汰策略,当内存达到上限时的处理方式 17 | maxmemory-policy allkeys-lru 18 | 19 | # 禁用 AOF 持久化 20 | appendonly no 21 | 22 | # 后台保存 RDB 快照 23 | save 900 1 24 | save 300 10 25 | save 60 10000 26 | 27 | # RDB 文件名 28 | dbfilename dump.rdb 29 | 30 | # RDB 文件保存目录 31 | dir /opt/transmute/app/data 32 | 33 | # 客户端最大连接数 34 | maxclients 10000 35 | 36 | # 超时设置,客户端空闲超过指定时间后断开连接 37 | timeout 300 38 | 39 | # TCP keepalive 设置,检测客户端是否存活 40 | tcp-keepalive 60 41 | 42 | # 日志级别,可选 debug, verbose, notice, warning 43 | loglevel notice 44 | 45 | # 日志文件路径 46 | logfile /var/log/redis.log 47 | 48 | # 数据库数量 49 | databases 16 50 | 51 | # 慢查询日志,记录执行时间超过指定微秒的命令 52 | slowlog-log-slower-than 10000 53 | 54 | # 慢查询日志最大长度 55 | slowlog-max-len 128 56 | 57 | # 禁用危险命令,增强安全性 58 | rename-command FLUSHALL "" 59 | rename-command FLUSHDB "" 60 | rename-command CONFIG "" -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | from contextlib import asynccontextmanager 3 | from fastapi.staticfiles import StaticFiles 4 | from fastapi import FastAPI 5 | import redis.asyncio as redis 6 | from app.routers.routers import router 7 | from app.middleware.req_limit import req_limit # 直接导入函数,而不是整个模块 8 | import json 9 | 10 | # 声明全局变量 11 | config = None 12 | 13 | # 定义应用的生命周期 14 | @asynccontextmanager 15 | async def lifespan(app: FastAPI): 16 | # 在应用启动时执行的代码 17 | with open('app/data/config/config.json', 'r') as file: 18 | global config 19 | config = json.load(file) 20 | app.state.config = config 21 | 22 | redisConfig = config["redis"] 23 | host = redisConfig["host"] 24 | port = redisConfig["port"] 25 | password = redisConfig["password"] 26 | db = redisConfig["db"] 27 | 28 | app.state.redis = await redis.Redis(host=host, port=port, password=password, db=db, decode_responses=True) 29 | yield 30 | # 在应用关闭时执行的代码 31 | await app.state.redis.close() 32 | 33 | app = FastAPI(lifespan=lifespan) 34 | 35 | # 挂载静态文件 36 | app.mount("/static", StaticFiles(directory="app/static"), name="static") 37 | 38 | # 注册中间件 39 | app.middleware("http")(req_limit) 40 | 41 | # 将路由添加到应用中 42 | app.include_router(router) 43 | -------------------------------------------------------------------------------- /app/middleware/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloxz/transmute/2b563f9844763373227020771d902e90bbcd5141/app/middleware/__init__.py -------------------------------------------------------------------------------- /app/middleware/req_limit.py: -------------------------------------------------------------------------------- 1 | from fastapi import Request 2 | from app.utils.helper import get_client_ip, show_json 3 | from fastapi.responses import JSONResponse 4 | from app.main import * 5 | from datetime import datetime 6 | # 限制请求频率 7 | async def req_limit(request: Request, call_next): 8 | # 获取当前请求的路由路径 9 | path = request.url.path 10 | if path == "/api/translate": 11 | # 获取当前日期并格式化为 YYYYMMDD 12 | current_date = datetime.now().strftime('%Y%m%d') 13 | 14 | ip = get_client_ip(request) 15 | key = "transmute:" + current_date + ":" + ip 16 | # 读取 Redis 中的 key 值 17 | value = await request.app.state.redis.get(key) 18 | if value is None: 19 | # 放行请求,继续执行后续中间件或路由处理 20 | res = await call_next(request) 21 | return res 22 | else: 23 | # print(request.app.state.config["app"]["req_limit"]) 24 | # res = await call_next(request) 25 | # return res 26 | # 获取配置文件中的次数限制 27 | req_limit = int(request.app.state.config["app"]["req_limit"]) 28 | value_int = int(value) 29 | if value_int > req_limit: 30 | # 返回请求频率过快的错误信息 31 | # 返回请求频率过快的错误信息 32 | response_data = show_json(429, "Too Many Requests", None) 33 | return JSONResponse(status_code=200, content=response_data) 34 | else: 35 | res = await call_next(request) 36 | return res 37 | else: 38 | # 对于非 /api/translate 路径的请求,直接放行 39 | res = await call_next(request) 40 | return res -------------------------------------------------------------------------------- /app/routers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloxz/transmute/2b563f9844763373227020771d902e90bbcd5141/app/routers/__init__.py -------------------------------------------------------------------------------- /app/routers/routers.py: -------------------------------------------------------------------------------- 1 | from app.api.translate import Translate 2 | from app.api.html import Html 3 | from fastapi import APIRouter 4 | 5 | # 创建 APIRouter 实例 6 | router = APIRouter() 7 | 8 | 9 | translate = Translate() 10 | html = Html() 11 | 12 | router.post("/api/translate")(translate.chat) 13 | router.get("/api/get/models")(translate.get_models) 14 | router.get("/api/get/languages")(translate.get_languages) 15 | router.get("/")(html.index) 16 | -------------------------------------------------------------------------------- /app/static/css/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: #409EFF; 3 | --secondary-color: #67C23A; 4 | --dark-bg: #f5f7fa; 5 | --light-text: #303133; 6 | --border-radius: 12px; 7 | --box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 8 | --transition: all 0.3s ease; 9 | } 10 | 11 | * { 12 | box-sizing: border-box; 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | html, body { 18 | height: 100%; 19 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; 20 | background-color: #F5F9FF; 21 | color: var(--light-text); 22 | } 23 | 24 | #app { 25 | display: flex; 26 | flex-direction: column; 27 | min-height: 100vh; 28 | } 29 | 30 | .header { 31 | background-color: white; 32 | box-shadow: var(--box-shadow); 33 | padding: 0 24px; 34 | height: 68px; 35 | display: flex; 36 | align-items: center; 37 | justify-content: space-between; 38 | position: sticky; 39 | top: 0; 40 | z-index: 100; 41 | } 42 | 43 | /* .el-select .el-input__inner { 44 | border: none !important; 45 | box-shadow: none !important; 46 | } */ 47 | 48 | 49 | .btns{ 50 | margin-top: 20px; 51 | } 52 | 53 | .logo { 54 | display: flex; 55 | align-items: center; 56 | font-weight: bold; 57 | font-size: 22px; 58 | color: var(--primary-color); 59 | text-decoration: none; 60 | } 61 | 62 | .logo i { 63 | margin-right: 8px; 64 | } 65 | 66 | .github-link { 67 | display: flex; 68 | align-items: center; 69 | color: #606266; 70 | text-decoration: none; 71 | transition: var(--transition); 72 | } 73 | 74 | .github-link:hover { 75 | color: var(--primary-color); 76 | } 77 | 78 | .content { 79 | flex: 1; 80 | padding: 20px; 81 | max-width: 800px; 82 | width: 100%; 83 | margin: 0 auto; 84 | } 85 | 86 | .language-selector { 87 | /* background-color: white; 88 | border-radius: var(--border-radius); 89 | box-shadow: var(--box-shadow); */ 90 | /* padding: 20px; */ 91 | margin-bottom: 20px; 92 | } 93 | .language-selector .el-input__inner{ 94 | border: none !important; 95 | box-shadow: none !important; 96 | } 97 | 98 | .language-selector .el-select-dropdown{ 99 | border: none !important; 100 | box-shadow: none !important; 101 | } 102 | 103 | .el-textarea__inner{ 104 | border:none !important; 105 | box-shadow: none !important; 106 | } 107 | 108 | 109 | .translate-container { 110 | background-color: white; 111 | border-radius: var(--border-radius); 112 | box-shadow: var(--box-shadow); 113 | padding: 20px; 114 | margin-bottom: 20px; 115 | } 116 | 117 | .textarea-container { 118 | position: relative; 119 | margin: 10px 0; 120 | } 121 | 122 | .char-count { 123 | position: absolute; 124 | bottom: 10px; 125 | right: 10px; 126 | color: #909399; 127 | font-size: 12px; 128 | } 129 | 130 | .result-container { 131 | background-color: #F8F8FB; 132 | border-radius: var(--border-radius); 133 | box-shadow: var(--box-shadow); 134 | padding: 10px; 135 | margin-top: 20px; 136 | } 137 | 138 | .result-output { 139 | background-color: #f5f7fa; 140 | border-radius: var(--border-radius); 141 | padding: 20px; 142 | min-height: 150px; 143 | max-height: 400px; 144 | overflow-y: auto; 145 | border: 1px solid #EBEEF5; 146 | white-space: pre-wrap; 147 | line-height: 1.6; 148 | font-family: "Courier New", Courier, monospace; 149 | color: #333; 150 | } 151 | 152 | .footer { 153 | background-color: white; 154 | padding: 15px 20px; 155 | text-align: center; 156 | color: #909399; 157 | font-size: 14px; 158 | box-shadow: var(--box-shadow); 159 | margin-top: auto; 160 | } 161 | 162 | .loading-dot { 163 | display: inline-block; 164 | width: 6px; 165 | height: 6px; 166 | border-radius: 50%; 167 | background-color: var(--primary-color); 168 | margin: 0 2px; 169 | animation: loading 1.4s infinite ease-in-out both; 170 | } 171 | .switch{ 172 | display: flex; 173 | align-items: center; 174 | justify-content: center; 175 | width: 100%; 176 | height: 40px; 177 | } 178 | 179 | .footer a{ 180 | text-decoration: none; 181 | color:#F56C6C; 182 | } 183 | 184 | .loading-dot:nth-child(1) { animation-delay: -0.32s; } 185 | .loading-dot:nth-child(2) { animation-delay: -0.16s; } 186 | 187 | @keyframes loading { 188 | 0%, 80%, 100% { transform: scale(0); } 189 | 40% { transform: scale(1); } 190 | } 191 | 192 | /* 响应式设计 */ 193 | @media (max-width: 768px) { 194 | .header { 195 | padding: 0 24px; 196 | height: 60px; 197 | } 198 | 199 | .logo { 200 | font-size: 18px; 201 | } 202 | 203 | .content { 204 | padding: 15px; 205 | } 206 | 207 | .language-selector, .translate-container, .result-container { 208 | padding: 12px; 209 | } 210 | .language-selector{ 211 | margin-bottom: 0px; 212 | } 213 | 214 | .el-col { 215 | margin-bottom: 15px; 216 | } 217 | } 218 | .input-left-btns{ 219 | width: 140px; 220 | text-align: left; 221 | } 222 | .input-left-btns .el-select .el-input__inner { 223 | border: none !important; 224 | box-shadow: none !important; 225 | background-color: #DBEAFE; 226 | color:#606266; 227 | border-radius: 14px; 228 | } 229 | .input-right-btns{ 230 | /* width: 100%; */ 231 | text-align: right; 232 | } 233 | 234 | .result-container .el-textarea__inner{ 235 | background-color: #F8F8FB; 236 | } 237 | 238 | .result-btns{ 239 | margin-top: 20px; 240 | text-align: right; 241 | } -------------------------------------------------------------------------------- /app/static/element-ui/fonts/element-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloxz/transmute/2b563f9844763373227020771d902e90bbcd5141/app/static/element-ui/fonts/element-icons.ttf -------------------------------------------------------------------------------- /app/static/element-ui/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloxz/transmute/2b563f9844763373227020771d902e90bbcd5141/app/static/element-ui/fonts/element-icons.woff -------------------------------------------------------------------------------- /app/static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloxz/transmute/2b563f9844763373227020771d902e90bbcd5141/app/static/images/favicon.ico -------------------------------------------------------------------------------- /app/static/images/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloxz/transmute/2b563f9844763373227020771d902e90bbcd5141/app/static/images/icon128.png -------------------------------------------------------------------------------- /app/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloxz/transmute/2b563f9844763373227020771d902e90bbcd5141/app/static/images/logo.png -------------------------------------------------------------------------------- /app/static/js/axios.min.js: -------------------------------------------------------------------------------- 1 | /* axios v0.27.2 | (c) 2022 by Matt Zabriskie */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=13)}([function(e,t,n){"use strict";var r,o=n(4),i=Object.prototype.toString,s=(r=Object.create(null),function(e){var t=i.call(e);return r[t]||(r[t]=t.slice(8,-1).toLowerCase())});function a(e){return e=e.toLowerCase(),function(t){return s(t)===e}}function u(e){return Array.isArray(e)}function c(e){return void 0===e}var f=a("ArrayBuffer");function l(e){return null!==e&&"object"==typeof e}function p(e){if("object"!==s(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}var d=a("Date"),h=a("File"),m=a("Blob"),v=a("FileList");function y(e){return"[object Function]"===i.call(e)}var g=a("URLSearchParams");function E(e,t){if(null!=e)if("object"!=typeof e&&(e=[e]),u(e))for(var n=0,r=e.length;n0;)s[i=r[o]]||(t[i]=e[i],s[i]=!0);e=Object.getPrototypeOf(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},kindOf:s,kindOfTest:a,endsWith:function(e,t,n){e=String(e),(void 0===n||n>e.length)&&(n=e.length),n-=t.length;var r=e.indexOf(t,n);return-1!==r&&r===n},toArray:function(e){if(!e)return null;var t=e.length;if(c(t))return null;for(var n=new Array(t);t-- >0;)n[t]=e[t];return n},isTypedArray:O,isFileList:v}},function(e,t,n){"use strict";var r=n(0);function o(e,t,n,r,o){Error.call(this),this.message=e,this.name="AxiosError",t&&(this.code=t),n&&(this.config=n),r&&(this.request=r),o&&(this.response=o)}r.inherits(o,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code,status:this.response&&this.response.status?this.response.status:null}}});var i=o.prototype,s={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED"].forEach((function(e){s[e]={value:e}})),Object.defineProperties(o,s),Object.defineProperty(i,"isAxiosError",{value:!0}),o.from=function(e,t,n,s,a,u){var c=Object.create(i);return r.toFlatObject(e,c,(function(e){return e!==Error.prototype})),o.call(c,e.message,t,n,s,a),c.name=e.name,u&&Object.assign(c,u),c},e.exports=o},function(e,t,n){"use strict";var r=n(1);function o(e){r.call(this,null==e?"canceled":e,r.ERR_CANCELED),this.name="CanceledError"}n(0).inherits(o,r,{__CANCEL__:!0}),e.exports=o},function(e,t,n){"use strict";var r=n(0),o=n(19),i=n(1),s=n(6),a=n(7),u={"Content-Type":"application/x-www-form-urlencoded"};function c(e,t){!r.isUndefined(e)&&r.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}var f,l={transitional:s,adapter:(("undefined"!=typeof XMLHttpRequest||"undefined"!=typeof process&&"[object process]"===Object.prototype.toString.call(process))&&(f=n(8)),f),transformRequest:[function(e,t){if(o(t,"Accept"),o(t,"Content-Type"),r.isFormData(e)||r.isArrayBuffer(e)||r.isBuffer(e)||r.isStream(e)||r.isFile(e)||r.isBlob(e))return e;if(r.isArrayBufferView(e))return e.buffer;if(r.isURLSearchParams(e))return c(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString();var n,i=r.isObject(e),s=t&&t["Content-Type"];if((n=r.isFileList(e))||i&&"multipart/form-data"===s){var u=this.env&&this.env.FormData;return a(n?{"files[]":e}:e,u&&new u)}return i||"application/json"===s?(c(t,"application/json"),function(e,t,n){if(r.isString(e))try{return(t||JSON.parse)(e),r.trim(e)}catch(e){if("SyntaxError"!==e.name)throw e}return(n||JSON.stringify)(e)}(e)):e}],transformResponse:[function(e){var t=this.transitional||l.transitional,n=t&&t.silentJSONParsing,o=t&&t.forcedJSONParsing,s=!n&&"json"===this.responseType;if(s||o&&r.isString(e)&&e.length)try{return JSON.parse(e)}catch(e){if(s){if("SyntaxError"===e.name)throw i.from(e,i.ERR_BAD_RESPONSE,this,null,this.response);throw e}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:n(27)},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};r.forEach(["delete","get","head"],(function(e){l.headers[e]={}})),r.forEach(["post","put","patch"],(function(e){l.headers[e]=r.merge(u)})),e.exports=l},function(e,t,n){"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r=0)return;s[t]="set-cookie"===t?(s[t]?s[t]:[]).concat([n]):s[t]?s[t]+", "+n:n}})),s):s}},function(e,t,n){"use strict";var r=n(0);e.exports=r.isStandardBrowserEnv()?function(){var e,t=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");function o(e){var r=e;return t&&(n.setAttribute("href",r),r=n.href),n.setAttribute("href",r),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:"/"===n.pathname.charAt(0)?n.pathname:"/"+n.pathname}}return e=o(window.location.href),function(t){var n=r.isString(t)?o(t):t;return n.protocol===e.protocol&&n.host===e.host}}():function(){return!0}},function(e,t,n){"use strict";e.exports=function(e){var t=/^([-+\w]{1,25})(:?\/\/|:)/.exec(e);return t&&t[1]||""}},function(e,t){e.exports=null},function(e,t,n){"use strict";var r=n(12).version,o=n(1),i={};["object","boolean","number","function","string","symbol"].forEach((function(e,t){i[e]=function(n){return typeof n===e||"a"+(t<1?"n ":" ")+e}}));var s={};i.transitional=function(e,t,n){function i(e,t){return"[Axios v"+r+"] Transitional option '"+e+"'"+t+(n?". "+n:"")}return function(n,r,a){if(!1===e)throw new o(i(r," has been removed"+(t?" in "+t:"")),o.ERR_DEPRECATED);return t&&!s[r]&&(s[r]=!0,console.warn(i(r," has been deprecated since v"+t+" and will be removed in the near future"))),!e||e(n,r,a)}},e.exports={assertOptions:function(e,t,n){if("object"!=typeof e)throw new o("options must be an object",o.ERR_BAD_OPTION_VALUE);for(var r=Object.keys(e),i=r.length;i-- >0;){var s=r[i],a=t[s];if(a){var u=e[s],c=void 0===u||a(u,s,e);if(!0!==c)throw new o("option "+s+" must be "+c,o.ERR_BAD_OPTION_VALUE)}else if(!0!==n)throw new o("Unknown option "+s,o.ERR_BAD_OPTION)}},validators:i}},function(e,t,n){"use strict";var r=n(2);function o(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise((function(e){t=e}));var n=this;this.promise.then((function(e){if(n._listeners){var t,r=n._listeners.length;for(t=0;t=0&&Math.floor(t)===t&&isFinite(e)}function u(e){return n(e)&&"function"==typeof e.then&&"function"==typeof e.catch}function l(e){return null==e?"":Array.isArray(e)||s(e)&&e.toString===a?JSON.stringify(e,null,2):String(e)}function f(e){var t=parseFloat(e);return isNaN(t)?e:t}function p(e,t){for(var n=Object.create(null),r=e.split(","),i=0;i-1)return e.splice(n,1)}}var m=Object.prototype.hasOwnProperty;function y(e,t){return m.call(e,t)}function g(e){var t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}var _=/-(\w)/g,b=g(function(e){return e.replace(_,function(e,t){return t?t.toUpperCase():""})}),$=g(function(e){return e.charAt(0).toUpperCase()+e.slice(1)}),w=/\B([A-Z])/g,C=g(function(e){return e.replace(w,"-$1").toLowerCase()});var x=Function.prototype.bind?function(e,t){return e.bind(t)}:function(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n};function k(e,t){t=t||0;for(var n=e.length-t,r=new Array(n);n--;)r[n]=e[n+t];return r}function A(e,t){for(var n in t)e[n]=t[n];return e}function O(e){for(var t={},n=0;n0,Z=J&&J.indexOf("edge/")>0,G=(J&&J.indexOf("android"),J&&/iphone|ipad|ipod|ios/.test(J)||"ios"===K),X=(J&&/chrome\/\d+/.test(J),J&&/phantomjs/.test(J),J&&J.match(/firefox\/(\d+)/)),Y={}.watch,Q=!1;if(V)try{var ee={};Object.defineProperty(ee,"passive",{get:function(){Q=!0}}),window.addEventListener("test-passive",null,ee)}catch(e){}var te=function(){return void 0===B&&(B=!V&&!z&&"undefined"!=typeof global&&(global.process&&"server"===global.process.env.VUE_ENV)),B},ne=V&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function re(e){return"function"==typeof e&&/native code/.test(e.toString())}var ie,oe="undefined"!=typeof Symbol&&re(Symbol)&&"undefined"!=typeof Reflect&&re(Reflect.ownKeys);ie="undefined"!=typeof Set&&re(Set)?Set:function(){function e(){this.set=Object.create(null)}return e.prototype.has=function(e){return!0===this.set[e]},e.prototype.add=function(e){this.set[e]=!0},e.prototype.clear=function(){this.set=Object.create(null)},e}();var ae=S,se=0,ce=function(){this.id=se++,this.subs=[]};ce.prototype.addSub=function(e){this.subs.push(e)},ce.prototype.removeSub=function(e){h(this.subs,e)},ce.prototype.depend=function(){ce.target&&ce.target.addDep(this)},ce.prototype.notify=function(){for(var e=this.subs.slice(),t=0,n=e.length;t-1)if(o&&!y(i,"default"))a=!1;else if(""===a||a===C(e)){var c=Re(String,i.type);(c<0||s0&&(ct((u=e(u,(a||"")+"_"+c))[0])&&ct(f)&&(s[l]=he(f.text+u[0].text),u.shift()),s.push.apply(s,u)):i(u)?ct(f)?s[l]=he(f.text+u):""!==u&&s.push(he(u)):ct(u)&&ct(f)?s[l]=he(f.text+u.text):(r(o._isVList)&&n(u.tag)&&t(u.key)&&n(a)&&(u.key="__vlist"+a+"_"+c+"__"),s.push(u)));return s}(e):void 0}function ct(e){return n(e)&&n(e.text)&&!1===e.isComment}function ut(e,t){if(e){for(var n=Object.create(null),r=oe?Reflect.ownKeys(e):Object.keys(e),i=0;i0,a=t?!!t.$stable:!o,s=t&&t.$key;if(t){if(t._normalized)return t._normalized;if(a&&r&&r!==e&&s===r.$key&&!o&&!r.$hasNormal)return r;for(var c in i={},t)t[c]&&"$"!==c[0]&&(i[c]=vt(n,c,t[c]))}else i={};for(var u in n)u in i||(i[u]=ht(n,u));return t&&Object.isExtensible(t)&&(t._normalized=i),R(i,"$stable",a),R(i,"$key",s),R(i,"$hasNormal",o),i}function vt(e,t,n){var r=function(){var e=arguments.length?n.apply(null,arguments):n({}),t=(e=e&&"object"==typeof e&&!Array.isArray(e)?[e]:st(e))&&e[0];return e&&(!t||1===e.length&&t.isComment&&!pt(t))?void 0:e};return n.proxy&&Object.defineProperty(e,t,{get:r,enumerable:!0,configurable:!0}),r}function ht(e,t){return function(){return e[t]}}function mt(e,t){var r,i,a,s,c;if(Array.isArray(e)||"string"==typeof e)for(r=new Array(e.length),i=0,a=e.length;idocument.createEvent("Event").timeStamp&&(cn=function(){return un.now()})}function ln(){var e,t;for(sn=cn(),on=!0,en.sort(function(e,t){return e.id-t.id}),an=0;anan&&en[n].id>e.id;)n--;en.splice(n+1,0,e)}else en.push(e);rn||(rn=!0,Qe(ln))}}(this)},pn.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||o(e)||this.deep){var t=this.value;if(this.value=e,this.user){var n='callback for watcher "'+this.expression+'"';Be(this.cb,this.vm,[e,t],this.vm,n)}else this.cb.call(this.vm,e,t)}}},pn.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},pn.prototype.depend=function(){for(var e=this.deps.length;e--;)this.deps[e].depend()},pn.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||h(this.vm._watchers,this);for(var e=this.deps.length;e--;)this.deps[e].removeSub(this);this.active=!1}};var dn={enumerable:!0,configurable:!0,get:S,set:S};function vn(e,t,n){dn.get=function(){return this[t][n]},dn.set=function(e){this[t][n]=e},Object.defineProperty(e,n,dn)}function hn(e){e._watchers=[];var t=e.$options;t.props&&function(e,t){var n=e.$options.propsData||{},r=e._props={},i=e.$options._propKeys=[];e.$parent&&$e(!1);var o=function(o){i.push(o);var a=Ie(o,t,n,e);xe(r,o,a),o in e||vn(e,"_props",o)};for(var a in t)o(a);$e(!0)}(e,t.props),t.methods&&function(e,t){e.$options.props;for(var n in t)e[n]="function"!=typeof t[n]?S:x(t[n],e)}(e,t.methods),t.data?function(e){var t=e.$options.data;s(t=e._data="function"==typeof t?function(e,t){le();try{return e.call(t,t)}catch(e){return He(e,t,"data()"),{}}finally{fe()}}(t,e):t||{})||(t={});var n=Object.keys(t),r=e.$options.props,i=(e.$options.methods,n.length);for(;i--;){var o=n[i];r&&y(r,o)||(a=void 0,36!==(a=(o+"").charCodeAt(0))&&95!==a&&vn(e,"_data",o))}var a;Ce(t,!0)}(e):Ce(e._data={},!0),t.computed&&function(e,t){var n=e._computedWatchers=Object.create(null),r=te();for(var i in t){var o=t[i],a="function"==typeof o?o:o.get;r||(n[i]=new pn(e,a||S,S,mn)),i in e||yn(e,i,o)}}(e,t.computed),t.watch&&t.watch!==Y&&function(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var i=0;i-1:"string"==typeof e?e.split(",").indexOf(t)>-1:(n=e,"[object RegExp]"===a.call(n)&&e.test(t));var n}function On(e,t){var n=e.cache,r=e.keys,i=e._vnode;for(var o in n){var a=n[o];if(a){var s=a.name;s&&!t(s)&&Sn(n,o,r,i)}}}function Sn(e,t,n,r){var i=e[t];!i||r&&i.tag===r.tag||i.componentInstance.$destroy(),e[t]=null,h(n,t)}!function(t){t.prototype._init=function(t){var n=this;n._uid=$n++,n._isVue=!0,t&&t._isComponent?function(e,t){var n=e.$options=Object.create(e.constructor.options),r=t._parentVnode;n.parent=t.parent,n._parentVnode=r;var i=r.componentOptions;n.propsData=i.propsData,n._parentListeners=i.listeners,n._renderChildren=i.children,n._componentTag=i.tag,t.render&&(n.render=t.render,n.staticRenderFns=t.staticRenderFns)}(n,t):n.$options=De(wn(n.constructor),t||{},n),n._renderProxy=n,n._self=n,function(e){var t=e.$options,n=t.parent;if(n&&!t.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(e)}e.$parent=n,e.$root=n?n.$root:e,e.$children=[],e.$refs={},e._watcher=null,e._inactive=null,e._directInactive=!1,e._isMounted=!1,e._isDestroyed=!1,e._isBeingDestroyed=!1}(n),function(e){e._events=Object.create(null),e._hasHookEvent=!1;var t=e.$options._parentListeners;t&&Wt(e,t)}(n),function(t){t._vnode=null,t._staticTrees=null;var n=t.$options,r=t.$vnode=n._parentVnode,i=r&&r.context;t.$slots=lt(n._renderChildren,i),t.$scopedSlots=e,t._c=function(e,n,r,i){return Ht(t,e,n,r,i,!1)},t.$createElement=function(e,n,r,i){return Ht(t,e,n,r,i,!0)};var o=r&&r.data;xe(t,"$attrs",o&&o.attrs||e,null,!0),xe(t,"$listeners",n._parentListeners||e,null,!0)}(n),Qt(n,"beforeCreate"),function(e){var t=ut(e.$options.inject,e);t&&($e(!1),Object.keys(t).forEach(function(n){xe(e,n,t[n])}),$e(!0))}(n),hn(n),function(e){var t=e.$options.provide;t&&(e._provided="function"==typeof t?t.call(e):t)}(n),Qt(n,"created"),n.$options.el&&n.$mount(n.$options.el)}}(Cn),function(e){var t={get:function(){return this._data}},n={get:function(){return this._props}};Object.defineProperty(e.prototype,"$data",t),Object.defineProperty(e.prototype,"$props",n),e.prototype.$set=ke,e.prototype.$delete=Ae,e.prototype.$watch=function(e,t,n){if(s(t))return bn(this,e,t,n);(n=n||{}).user=!0;var r=new pn(this,e,t,n);if(n.immediate){var i='callback for immediate watcher "'+r.expression+'"';le(),Be(t,this,[r.value],this,i),fe()}return function(){r.teardown()}}}(Cn),function(e){var t=/^hook:/;e.prototype.$on=function(e,n){var r=this;if(Array.isArray(e))for(var i=0,o=e.length;i1?k(t):t;for(var n=k(arguments,1),r='event handler for "'+e+'"',i=0,o=t.length;iparseInt(this.max)&&Sn(e,t[0],t,this._vnode),this.vnodeToCache=null}}},created:function(){this.cache=Object.create(null),this.keys=[]},destroyed:function(){for(var e in this.cache)Sn(this.cache,e,this.keys)},mounted:function(){var e=this;this.cacheVNode(),this.$watch("include",function(t){On(e,function(e){return An(t,e)})}),this.$watch("exclude",function(t){On(e,function(e){return!An(t,e)})})},updated:function(){this.cacheVNode()},render:function(){var e=this.$slots.default,t=zt(e),n=t&&t.componentOptions;if(n){var r=kn(n),i=this.include,o=this.exclude;if(i&&(!r||!An(i,r))||o&&r&&An(o,r))return t;var a=this.cache,s=this.keys,c=null==t.key?n.Ctor.cid+(n.tag?"::"+n.tag:""):t.key;a[c]?(t.componentInstance=a[c].componentInstance,h(s,c),s.push(c)):(this.vnodeToCache=t,this.keyToCache=c),t.data.keepAlive=!0}return t||e&&e[0]}}};!function(e){var t={get:function(){return F}};Object.defineProperty(e,"config",t),e.util={warn:ae,extend:A,mergeOptions:De,defineReactive:xe},e.set=ke,e.delete=Ae,e.nextTick=Qe,e.observable=function(e){return Ce(e),e},e.options=Object.create(null),I.forEach(function(t){e.options[t+"s"]=Object.create(null)}),e.options._base=e,A(e.options.components,Nn),function(e){e.use=function(e){var t=this._installedPlugins||(this._installedPlugins=[]);if(t.indexOf(e)>-1)return this;var n=k(arguments,1);return n.unshift(this),"function"==typeof e.install?e.install.apply(e,n):"function"==typeof e&&e.apply(null,n),t.push(e),this}}(e),function(e){e.mixin=function(e){return this.options=De(this.options,e),this}}(e),xn(e),function(e){I.forEach(function(t){e[t]=function(e,n){return n?("component"===t&&s(n)&&(n.name=n.name||e,n=this.options._base.extend(n)),"directive"===t&&"function"==typeof n&&(n={bind:n,update:n}),this.options[t+"s"][e]=n,n):this.options[t+"s"][e]}})}(e)}(Cn),Object.defineProperty(Cn.prototype,"$isServer",{get:te}),Object.defineProperty(Cn.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(Cn,"FunctionalRenderContext",{value:Et}),Cn.version="2.6.14";var En=p("style,class"),jn=p("input,textarea,option,select,progress"),Dn=function(e,t,n){return"value"===n&&jn(e)&&"button"!==t||"selected"===n&&"option"===e||"checked"===n&&"input"===e||"muted"===n&&"video"===e},Ln=p("contenteditable,draggable,spellcheck"),In=p("events,caret,typing,plaintext-only"),Mn=function(e,t){return Bn(t)||"false"===t?"false":"contenteditable"===e&&In(t)?t:"true"},Fn=p("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible"),Pn="http://www.w3.org/1999/xlink",Rn=function(e){return":"===e.charAt(5)&&"xlink"===e.slice(0,5)},Hn=function(e){return Rn(e)?e.slice(6,e.length):""},Bn=function(e){return null==e||!1===e};function Un(e){for(var t=e.data,r=e,i=e;n(i.componentInstance);)(i=i.componentInstance._vnode)&&i.data&&(t=Vn(i.data,t));for(;n(r=r.parent);)r&&r.data&&(t=Vn(t,r.data));return function(e,t){if(n(e)||n(t))return zn(e,Kn(t));return""}(t.staticClass,t.class)}function Vn(e,t){return{staticClass:zn(e.staticClass,t.staticClass),class:n(e.class)?[e.class,t.class]:t.class}}function zn(e,t){return e?t?e+" "+t:e:t||""}function Kn(e){return Array.isArray(e)?function(e){for(var t,r="",i=0,o=e.length;i-1?mr(e,t,n):Fn(t)?Bn(n)?e.removeAttribute(t):(n="allowfullscreen"===t&&"EMBED"===e.tagName?"true":t,e.setAttribute(t,n)):Ln(t)?e.setAttribute(t,Mn(t,n)):Rn(t)?Bn(n)?e.removeAttributeNS(Pn,Hn(t)):e.setAttributeNS(Pn,t,n):mr(e,t,n)}function mr(e,t,n){if(Bn(n))e.removeAttribute(t);else{if(q&&!W&&"TEXTAREA"===e.tagName&&"placeholder"===t&&""!==n&&!e.__ieph){var r=function(t){t.stopImmediatePropagation(),e.removeEventListener("input",r)};e.addEventListener("input",r),e.__ieph=!0}e.setAttribute(t,n)}}var yr={create:vr,update:vr};function gr(e,r){var i=r.elm,o=r.data,a=e.data;if(!(t(o.staticClass)&&t(o.class)&&(t(a)||t(a.staticClass)&&t(a.class)))){var s=Un(r),c=i._transitionClasses;n(c)&&(s=zn(s,Kn(c))),s!==i._prevClass&&(i.setAttribute("class",s),i._prevClass=s)}}var _r,br,$r,wr,Cr,xr,kr={create:gr,update:gr},Ar=/[\w).+\-_$\]]/;function Or(e){var t,n,r,i,o,a=!1,s=!1,c=!1,u=!1,l=0,f=0,p=0,d=0;for(r=0;r=0&&" "===(h=e.charAt(v));v--);h&&Ar.test(h)||(u=!0)}}else void 0===i?(d=r+1,i=e.slice(0,r).trim()):m();function m(){(o||(o=[])).push(e.slice(d,r).trim()),d=r+1}if(void 0===i?i=e.slice(0,r).trim():0!==d&&m(),o)for(r=0;r-1?{exp:e.slice(0,wr),key:'"'+e.slice(wr+1)+'"'}:{exp:e,key:null};br=e,wr=Cr=xr=0;for(;!zr();)Kr($r=Vr())?qr($r):91===$r&&Jr($r);return{exp:e.slice(0,Cr),key:e.slice(Cr+1,xr)}}(e);return null===n.key?e+"="+t:"$set("+n.exp+", "+n.key+", "+t+")"}function Vr(){return br.charCodeAt(++wr)}function zr(){return wr>=_r}function Kr(e){return 34===e||39===e}function Jr(e){var t=1;for(Cr=wr;!zr();)if(Kr(e=Vr()))qr(e);else if(91===e&&t++,93===e&&t--,0===t){xr=wr;break}}function qr(e){for(var t=e;!zr()&&(e=Vr())!==t;);}var Wr,Zr="__r",Gr="__c";function Xr(e,t,n){var r=Wr;return function i(){null!==t.apply(null,arguments)&&ei(e,i,n,r)}}var Yr=Ke&&!(X&&Number(X[1])<=53);function Qr(e,t,n,r){if(Yr){var i=sn,o=t;t=o._wrapper=function(e){if(e.target===e.currentTarget||e.timeStamp>=i||e.timeStamp<=0||e.target.ownerDocument!==document)return o.apply(this,arguments)}}Wr.addEventListener(e,t,Q?{capture:n,passive:r}:n)}function ei(e,t,n,r){(r||Wr).removeEventListener(e,t._wrapper||t,n)}function ti(e,r){if(!t(e.data.on)||!t(r.data.on)){var i=r.data.on||{},o=e.data.on||{};Wr=r.elm,function(e){if(n(e[Zr])){var t=q?"change":"input";e[t]=[].concat(e[Zr],e[t]||[]),delete e[Zr]}n(e[Gr])&&(e.change=[].concat(e[Gr],e.change||[]),delete e[Gr])}(i),it(i,o,Qr,ei,Xr,r.context),Wr=void 0}}var ni,ri={create:ti,update:ti};function ii(e,r){if(!t(e.data.domProps)||!t(r.data.domProps)){var i,o,a=r.elm,s=e.data.domProps||{},c=r.data.domProps||{};for(i in n(c.__ob__)&&(c=r.data.domProps=A({},c)),s)i in c||(a[i]="");for(i in c){if(o=c[i],"textContent"===i||"innerHTML"===i){if(r.children&&(r.children.length=0),o===s[i])continue;1===a.childNodes.length&&a.removeChild(a.childNodes[0])}if("value"===i&&"PROGRESS"!==a.tagName){a._value=o;var u=t(o)?"":String(o);oi(a,u)&&(a.value=u)}else if("innerHTML"===i&&Wn(a.tagName)&&t(a.innerHTML)){(ni=ni||document.createElement("div")).innerHTML=""+o+"";for(var l=ni.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;l.firstChild;)a.appendChild(l.firstChild)}else if(o!==s[i])try{a[i]=o}catch(e){}}}}function oi(e,t){return!e.composing&&("OPTION"===e.tagName||function(e,t){var n=!0;try{n=document.activeElement!==e}catch(e){}return n&&e.value!==t}(e,t)||function(e,t){var r=e.value,i=e._vModifiers;if(n(i)){if(i.number)return f(r)!==f(t);if(i.trim)return r.trim()!==t.trim()}return r!==t}(e,t))}var ai={create:ii,update:ii},si=g(function(e){var t={},n=/:(.+)/;return e.split(/;(?![^(]*\))/g).forEach(function(e){if(e){var r=e.split(n);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t});function ci(e){var t=ui(e.style);return e.staticStyle?A(e.staticStyle,t):t}function ui(e){return Array.isArray(e)?O(e):"string"==typeof e?si(e):e}var li,fi=/^--/,pi=/\s*!important$/,di=function(e,t,n){if(fi.test(t))e.style.setProperty(t,n);else if(pi.test(n))e.style.setProperty(C(t),n.replace(pi,""),"important");else{var r=hi(t);if(Array.isArray(n))for(var i=0,o=n.length;i-1?t.split(gi).forEach(function(t){return e.classList.add(t)}):e.classList.add(t);else{var n=" "+(e.getAttribute("class")||"")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function bi(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(gi).forEach(function(t){return e.classList.remove(t)}):e.classList.remove(t),e.classList.length||e.removeAttribute("class");else{for(var n=" "+(e.getAttribute("class")||"")+" ",r=" "+t+" ";n.indexOf(r)>=0;)n=n.replace(r," ");(n=n.trim())?e.setAttribute("class",n):e.removeAttribute("class")}}function $i(e){if(e){if("object"==typeof e){var t={};return!1!==e.css&&A(t,wi(e.name||"v")),A(t,e),t}return"string"==typeof e?wi(e):void 0}}var wi=g(function(e){return{enterClass:e+"-enter",enterToClass:e+"-enter-to",enterActiveClass:e+"-enter-active",leaveClass:e+"-leave",leaveToClass:e+"-leave-to",leaveActiveClass:e+"-leave-active"}}),Ci=V&&!W,xi="transition",ki="animation",Ai="transition",Oi="transitionend",Si="animation",Ti="animationend";Ci&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(Ai="WebkitTransition",Oi="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Si="WebkitAnimation",Ti="webkitAnimationEnd"));var Ni=V?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(e){return e()};function Ei(e){Ni(function(){Ni(e)})}function ji(e,t){var n=e._transitionClasses||(e._transitionClasses=[]);n.indexOf(t)<0&&(n.push(t),_i(e,t))}function Di(e,t){e._transitionClasses&&h(e._transitionClasses,t),bi(e,t)}function Li(e,t,n){var r=Mi(e,t),i=r.type,o=r.timeout,a=r.propCount;if(!i)return n();var s=i===xi?Oi:Ti,c=0,u=function(){e.removeEventListener(s,l),n()},l=function(t){t.target===e&&++c>=a&&u()};setTimeout(function(){c0&&(n=xi,l=a,f=o.length):t===ki?u>0&&(n=ki,l=u,f=c.length):f=(n=(l=Math.max(a,u))>0?a>u?xi:ki:null)?n===xi?o.length:c.length:0,{type:n,timeout:l,propCount:f,hasTransform:n===xi&&Ii.test(r[Ai+"Property"])}}function Fi(e,t){for(;e.length1}function Vi(e,t){!0!==t.data.show&&Ri(t)}var zi=function(e){var o,a,s={},c=e.modules,u=e.nodeOps;for(o=0;ov?_(e,t(i[y+1])?null:i[y+1].elm,i,d,y,o):d>y&&$(r,p,v)}(p,h,y,o,l):n(y)?(n(e.text)&&u.setTextContent(p,""),_(p,null,y,0,y.length-1,o)):n(h)?$(h,0,h.length-1):n(e.text)&&u.setTextContent(p,""):e.text!==i.text&&u.setTextContent(p,i.text),n(v)&&n(d=v.hook)&&n(d=d.postpatch)&&d(e,i)}}}function k(e,t,i){if(r(i)&&n(e.parent))e.parent.data.pendingInsert=t;else for(var o=0;o-1,a.selected!==o&&(a.selected=o);else if(E(Zi(a),r))return void(e.selectedIndex!==s&&(e.selectedIndex=s));i||(e.selectedIndex=-1)}}function Wi(e,t){return t.every(function(t){return!E(t,e)})}function Zi(e){return"_value"in e?e._value:e.value}function Gi(e){e.target.composing=!0}function Xi(e){e.target.composing&&(e.target.composing=!1,Yi(e.target,"input"))}function Yi(e,t){var n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}function Qi(e){return!e.componentInstance||e.data&&e.data.transition?e:Qi(e.componentInstance._vnode)}var eo={model:Ki,show:{bind:function(e,t,n){var r=t.value,i=(n=Qi(n)).data&&n.data.transition,o=e.__vOriginalDisplay="none"===e.style.display?"":e.style.display;r&&i?(n.data.show=!0,Ri(n,function(){e.style.display=o})):e.style.display=r?o:"none"},update:function(e,t,n){var r=t.value;!r!=!t.oldValue&&((n=Qi(n)).data&&n.data.transition?(n.data.show=!0,r?Ri(n,function(){e.style.display=e.__vOriginalDisplay}):Hi(n,function(){e.style.display="none"})):e.style.display=r?e.__vOriginalDisplay:"none")},unbind:function(e,t,n,r,i){i||(e.style.display=e.__vOriginalDisplay)}}},to={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function no(e){var t=e&&e.componentOptions;return t&&t.Ctor.options.abstract?no(zt(t.children)):e}function ro(e){var t={},n=e.$options;for(var r in n.propsData)t[r]=e[r];var i=n._parentListeners;for(var o in i)t[b(o)]=i[o];return t}function io(e,t){if(/\d-keep-alive$/.test(t.tag))return e("keep-alive",{props:t.componentOptions.propsData})}var oo=function(e){return e.tag||pt(e)},ao=function(e){return"show"===e.name},so={name:"transition",props:to,abstract:!0,render:function(e){var t=this,n=this.$slots.default;if(n&&(n=n.filter(oo)).length){var r=this.mode,o=n[0];if(function(e){for(;e=e.parent;)if(e.data.transition)return!0}(this.$vnode))return o;var a=no(o);if(!a)return o;if(this._leaving)return io(e,o);var s="__transition-"+this._uid+"-";a.key=null==a.key?a.isComment?s+"comment":s+a.tag:i(a.key)?0===String(a.key).indexOf(s)?a.key:s+a.key:a.key;var c=(a.data||(a.data={})).transition=ro(this),u=this._vnode,l=no(u);if(a.data.directives&&a.data.directives.some(ao)&&(a.data.show=!0),l&&l.data&&!function(e,t){return t.key===e.key&&t.tag===e.tag}(a,l)&&!pt(l)&&(!l.componentInstance||!l.componentInstance._vnode.isComment)){var f=l.data.transition=A({},c);if("out-in"===r)return this._leaving=!0,ot(f,"afterLeave",function(){t._leaving=!1,t.$forceUpdate()}),io(e,o);if("in-out"===r){if(pt(a))return u;var p,d=function(){p()};ot(c,"afterEnter",d),ot(c,"enterCancelled",d),ot(f,"delayLeave",function(e){p=e})}}return o}}},co=A({tag:String,moveClass:String},to);function uo(e){e.elm._moveCb&&e.elm._moveCb(),e.elm._enterCb&&e.elm._enterCb()}function lo(e){e.data.newPos=e.elm.getBoundingClientRect()}function fo(e){var t=e.data.pos,n=e.data.newPos,r=t.left-n.left,i=t.top-n.top;if(r||i){e.data.moved=!0;var o=e.elm.style;o.transform=o.WebkitTransform="translate("+r+"px,"+i+"px)",o.transitionDuration="0s"}}delete co.mode;var po={Transition:so,TransitionGroup:{props:co,beforeMount:function(){var e=this,t=this._update;this._update=function(n,r){var i=Gt(e);e.__patch__(e._vnode,e.kept,!1,!0),e._vnode=e.kept,i(),t.call(e,n,r)}},render:function(e){for(var t=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,i=this.$slots.default||[],o=this.children=[],a=ro(this),s=0;s-1?Xn[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:Xn[e]=/HTMLUnknownElement/.test(t.toString())},A(Cn.options.directives,eo),A(Cn.options.components,po),Cn.prototype.__patch__=V?zi:S,Cn.prototype.$mount=function(e,t){return function(e,t,n){var r;return e.$el=t,e.$options.render||(e.$options.render=ve),Qt(e,"beforeMount"),r=function(){e._update(e._render(),n)},new pn(e,r,S,{before:function(){e._isMounted&&!e._isDestroyed&&Qt(e,"beforeUpdate")}},!0),n=!1,null==e.$vnode&&(e._isMounted=!0,Qt(e,"mounted")),e}(this,e=e&&V?Qn(e):void 0,t)},V&&setTimeout(function(){F.devtools&&ne&&ne.emit("init",Cn)},0);var vo=/\{\{((?:.|\r?\n)+?)\}\}/g,ho=/[-.*+?^${}()|[\]\/\\]/g,mo=g(function(e){var t=e[0].replace(ho,"\\$&"),n=e[1].replace(ho,"\\$&");return new RegExp(t+"((?:.|\\n)+?)"+n,"g")});var yo={staticKeys:["staticClass"],transformNode:function(e,t){t.warn;var n=Pr(e,"class");n&&(e.staticClass=JSON.stringify(n));var r=Fr(e,"class",!1);r&&(e.classBinding=r)},genData:function(e){var t="";return e.staticClass&&(t+="staticClass:"+e.staticClass+","),e.classBinding&&(t+="class:"+e.classBinding+","),t}};var go,_o={staticKeys:["staticStyle"],transformNode:function(e,t){t.warn;var n=Pr(e,"style");n&&(e.staticStyle=JSON.stringify(si(n)));var r=Fr(e,"style",!1);r&&(e.styleBinding=r)},genData:function(e){var t="";return e.staticStyle&&(t+="staticStyle:"+e.staticStyle+","),e.styleBinding&&(t+="style:("+e.styleBinding+"),"),t}},bo=function(e){return(go=go||document.createElement("div")).innerHTML=e,go.textContent},$o=p("area,base,br,col,embed,frame,hr,img,input,isindex,keygen,link,meta,param,source,track,wbr"),wo=p("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source"),Co=p("address,article,aside,base,blockquote,body,caption,col,colgroup,dd,details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,title,tr,track"),xo=/^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,ko=/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,Ao="[a-zA-Z_][\\-\\.0-9_a-zA-Z"+P.source+"]*",Oo="((?:"+Ao+"\\:)?"+Ao+")",So=new RegExp("^<"+Oo),To=/^\s*(\/?)>/,No=new RegExp("^<\\/"+Oo+"[^>]*>"),Eo=/^]+>/i,jo=/^",""":'"',"&":"&"," ":"\n"," ":"\t","'":"'"},Fo=/&(?:lt|gt|quot|amp|#39);/g,Po=/&(?:lt|gt|quot|amp|#39|#10|#9);/g,Ro=p("pre,textarea",!0),Ho=function(e,t){return e&&Ro(e)&&"\n"===t[0]};function Bo(e,t){var n=t?Po:Fo;return e.replace(n,function(e){return Mo[e]})}var Uo,Vo,zo,Ko,Jo,qo,Wo,Zo,Go=/^@|^v-on:/,Xo=/^v-|^@|^:|^#/,Yo=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,Qo=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,ea=/^\(|\)$/g,ta=/^\[.*\]$/,na=/:(.*)$/,ra=/^:|^\.|^v-bind:/,ia=/\.[^.\]]+(?=[^\]]*$)/g,oa=/^v-slot(:|$)|^#/,aa=/[\r\n]/,sa=/[ \f\t\r\n]+/g,ca=g(bo),ua="_empty_";function la(e,t,n){return{type:1,tag:e,attrsList:t,attrsMap:ya(t),rawAttrsMap:{},parent:n,children:[]}}function fa(e,t){Uo=t.warn||Tr,qo=t.isPreTag||T,Wo=t.mustUseProp||T,Zo=t.getTagNamespace||T;t.isReservedTag;zo=Nr(t.modules,"transformNode"),Ko=Nr(t.modules,"preTransformNode"),Jo=Nr(t.modules,"postTransformNode"),Vo=t.delimiters;var n,r,i=[],o=!1!==t.preserveWhitespace,a=t.whitespace,s=!1,c=!1;function u(e){if(l(e),s||e.processed||(e=pa(e,t)),i.length||e===n||n.if&&(e.elseif||e.else)&&va(n,{exp:e.elseif,block:e}),r&&!e.forbidden)if(e.elseif||e.else)a=e,(u=function(e){var t=e.length;for(;t--;){if(1===e[t].type)return e[t];e.pop()}}(r.children))&&u.if&&va(u,{exp:a.elseif,block:a});else{if(e.slotScope){var o=e.slotTarget||'"default"';(r.scopedSlots||(r.scopedSlots={}))[o]=e}r.children.push(e),e.parent=r}var a,u;e.children=e.children.filter(function(e){return!e.slotScope}),l(e),e.pre&&(s=!1),qo(e.tag)&&(c=!1);for(var f=0;f]*>)","i")),p=e.replace(f,function(e,n,r){return u=r.length,Lo(l)||"noscript"===l||(n=n.replace(//g,"$1").replace(//g,"$1")),Ho(l,n)&&(n=n.slice(1)),t.chars&&t.chars(n),""});c+=e.length-p.length,e=p,A(l,c-u,c)}else{var d=e.indexOf("<");if(0===d){if(jo.test(e)){var v=e.indexOf("--\x3e");if(v>=0){t.shouldKeepComment&&t.comment(e.substring(4,v),c,c+v+3),C(v+3);continue}}if(Do.test(e)){var h=e.indexOf("]>");if(h>=0){C(h+2);continue}}var m=e.match(Eo);if(m){C(m[0].length);continue}var y=e.match(No);if(y){var g=c;C(y[0].length),A(y[1],g,c);continue}var _=x();if(_){k(_),Ho(_.tagName,e)&&C(1);continue}}var b=void 0,$=void 0,w=void 0;if(d>=0){for($=e.slice(d);!(No.test($)||So.test($)||jo.test($)||Do.test($)||(w=$.indexOf("<",1))<0);)d+=w,$=e.slice(d);b=e.substring(0,d)}d<0&&(b=e),b&&C(b.length),t.chars&&b&&t.chars(b,c-b.length,c)}if(e===n){t.chars&&t.chars(e);break}}function C(t){c+=t,e=e.substring(t)}function x(){var t=e.match(So);if(t){var n,r,i={tagName:t[1],attrs:[],start:c};for(C(t[0].length);!(n=e.match(To))&&(r=e.match(ko)||e.match(xo));)r.start=c,C(r[0].length),r.end=c,i.attrs.push(r);if(n)return i.unarySlash=n[1],C(n[0].length),i.end=c,i}}function k(e){var n=e.tagName,c=e.unarySlash;o&&("p"===r&&Co(n)&&A(r),s(n)&&r===n&&A(n));for(var u=a(n)||!!c,l=e.attrs.length,f=new Array(l),p=0;p=0&&i[a].lowerCasedTag!==s;a--);else a=0;if(a>=0){for(var u=i.length-1;u>=a;u--)t.end&&t.end(i[u].tag,n,o);i.length=a,r=a&&i[a-1].tag}else"br"===s?t.start&&t.start(e,[],!0,n,o):"p"===s&&(t.start&&t.start(e,[],!1,n,o),t.end&&t.end(e,n,o))}A()}(e,{warn:Uo,expectHTML:t.expectHTML,isUnaryTag:t.isUnaryTag,canBeLeftOpenTag:t.canBeLeftOpenTag,shouldDecodeNewlines:t.shouldDecodeNewlines,shouldDecodeNewlinesForHref:t.shouldDecodeNewlinesForHref,shouldKeepComment:t.comments,outputSourceRange:t.outputSourceRange,start:function(e,o,a,l,f){var p=r&&r.ns||Zo(e);q&&"svg"===p&&(o=function(e){for(var t=[],n=0;nc&&(s.push(o=e.slice(c,i)),a.push(JSON.stringify(o)));var u=Or(r[1].trim());a.push("_s("+u+")"),s.push({"@binding":u}),c=i+r[0].length}return c-1"+("true"===o?":("+t+")":":_q("+t+","+o+")")),Mr(e,"change","var $$a="+t+",$$el=$event.target,$$c=$$el.checked?("+o+"):("+a+");if(Array.isArray($$a)){var $$v="+(r?"_n("+i+")":i)+",$$i=_i($$a,$$v);if($$el.checked){$$i<0&&("+Ur(t,"$$a.concat([$$v])")+")}else{$$i>-1&&("+Ur(t,"$$a.slice(0,$$i).concat($$a.slice($$i+1))")+")}}else{"+Ur(t,"$$c")+"}",null,!0)}(e,r,i);else if("input"===o&&"radio"===a)!function(e,t,n){var r=n&&n.number,i=Fr(e,"value")||"null";Er(e,"checked","_q("+t+","+(i=r?"_n("+i+")":i)+")"),Mr(e,"change",Ur(t,i),null,!0)}(e,r,i);else if("input"===o||"textarea"===o)!function(e,t,n){var r=e.attrsMap.type,i=n||{},o=i.lazy,a=i.number,s=i.trim,c=!o&&"range"!==r,u=o?"change":"range"===r?Zr:"input",l="$event.target.value";s&&(l="$event.target.value.trim()"),a&&(l="_n("+l+")");var f=Ur(t,l);c&&(f="if($event.target.composing)return;"+f),Er(e,"value","("+t+")"),Mr(e,u,f,null,!0),(s||a)&&Mr(e,"blur","$forceUpdate()")}(e,r,i);else if(!F.isReservedTag(o))return Br(e,r,i),!1;return!0},text:function(e,t){t.value&&Er(e,"textContent","_s("+t.value+")",t)},html:function(e,t){t.value&&Er(e,"innerHTML","_s("+t.value+")",t)}},isPreTag:function(e){return"pre"===e},isUnaryTag:$o,mustUseProp:Dn,canBeLeftOpenTag:wo,isReservedTag:Zn,getTagNamespace:Gn,staticKeys:function(e){return e.reduce(function(e,t){return e.concat(t.staticKeys||[])},[]).join(",")}($a)},ka=g(function(e){return p("type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap"+(e?","+e:""))});function Aa(e,t){e&&(wa=ka(t.staticKeys||""),Ca=t.isReservedTag||T,function e(t){t.static=function(e){if(2===e.type)return!1;if(3===e.type)return!0;return!(!e.pre&&(e.hasBindings||e.if||e.for||d(e.tag)||!Ca(e.tag)||function(e){for(;e.parent;){if("template"!==(e=e.parent).tag)return!1;if(e.for)return!0}return!1}(e)||!Object.keys(e).every(wa)))}(t);if(1===t.type){if(!Ca(t.tag)&&"slot"!==t.tag&&null==t.attrsMap["inline-template"])return;for(var n=0,r=t.children.length;n|^function(?:\s+[\w$]+)?\s*\(/,Sa=/\([^)]*?\);*$/,Ta=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,Na={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},Ea={esc:["Esc","Escape"],tab:"Tab",enter:"Enter",space:[" ","Spacebar"],up:["Up","ArrowUp"],left:["Left","ArrowLeft"],right:["Right","ArrowRight"],down:["Down","ArrowDown"],delete:["Backspace","Delete","Del"]},ja=function(e){return"if("+e+")return null;"},Da={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:ja("$event.target !== $event.currentTarget"),ctrl:ja("!$event.ctrlKey"),shift:ja("!$event.shiftKey"),alt:ja("!$event.altKey"),meta:ja("!$event.metaKey"),left:ja("'button' in $event && $event.button !== 0"),middle:ja("'button' in $event && $event.button !== 1"),right:ja("'button' in $event && $event.button !== 2")};function La(e,t){var n=t?"nativeOn:":"on:",r="",i="";for(var o in e){var a=Ia(e[o]);e[o]&&e[o].dynamic?i+=o+","+a+",":r+='"'+o+'":'+a+","}return r="{"+r.slice(0,-1)+"}",i?n+"_d("+r+",["+i.slice(0,-1)+"])":n+r}function Ia(e){if(!e)return"function(){}";if(Array.isArray(e))return"["+e.map(function(e){return Ia(e)}).join(",")+"]";var t=Ta.test(e.value),n=Oa.test(e.value),r=Ta.test(e.value.replace(Sa,""));if(e.modifiers){var i="",o="",a=[];for(var s in e.modifiers)if(Da[s])o+=Da[s],Na[s]&&a.push(s);else if("exact"===s){var c=e.modifiers;o+=ja(["ctrl","shift","alt","meta"].filter(function(e){return!c[e]}).map(function(e){return"$event."+e+"Key"}).join("||"))}else a.push(s);return a.length&&(i+=function(e){return"if(!$event.type.indexOf('key')&&"+e.map(Ma).join("&&")+")return null;"}(a)),o&&(i+=o),"function($event){"+i+(t?"return "+e.value+".apply(null, arguments)":n?"return ("+e.value+").apply(null, arguments)":r?"return "+e.value:e.value)+"}"}return t||n?e.value:"function($event){"+(r?"return "+e.value:e.value)+"}"}function Ma(e){var t=parseInt(e,10);if(t)return"$event.keyCode!=="+t;var n=Na[e],r=Ea[e];return"_k($event.keyCode,"+JSON.stringify(e)+","+JSON.stringify(n)+",$event.key,"+JSON.stringify(r)+")"}var Fa={on:function(e,t){e.wrapListeners=function(e){return"_g("+e+","+t.value+")"}},bind:function(e,t){e.wrapData=function(n){return"_b("+n+",'"+e.tag+"',"+t.value+","+(t.modifiers&&t.modifiers.prop?"true":"false")+(t.modifiers&&t.modifiers.sync?",true":"")+")"}},cloak:S},Pa=function(e){this.options=e,this.warn=e.warn||Tr,this.transforms=Nr(e.modules,"transformCode"),this.dataGenFns=Nr(e.modules,"genData"),this.directives=A(A({},Fa),e.directives);var t=e.isReservedTag||T;this.maybeComponent=function(e){return!!e.component||!t(e.tag)},this.onceId=0,this.staticRenderFns=[],this.pre=!1};function Ra(e,t){var n=new Pa(t);return{render:"with(this){return "+(e?"script"===e.tag?"null":Ha(e,n):'_c("div")')+"}",staticRenderFns:n.staticRenderFns}}function Ha(e,t){if(e.parent&&(e.pre=e.pre||e.parent.pre),e.staticRoot&&!e.staticProcessed)return Ba(e,t);if(e.once&&!e.onceProcessed)return Ua(e,t);if(e.for&&!e.forProcessed)return za(e,t);if(e.if&&!e.ifProcessed)return Va(e,t);if("template"!==e.tag||e.slotTarget||t.pre){if("slot"===e.tag)return function(e,t){var n=e.slotName||'"default"',r=Wa(e,t),i="_t("+n+(r?",function(){return "+r+"}":""),o=e.attrs||e.dynamicAttrs?Xa((e.attrs||[]).concat(e.dynamicAttrs||[]).map(function(e){return{name:b(e.name),value:e.value,dynamic:e.dynamic}})):null,a=e.attrsMap["v-bind"];!o&&!a||r||(i+=",null");o&&(i+=","+o);a&&(i+=(o?"":",null")+","+a);return i+")"}(e,t);var n;if(e.component)n=function(e,t,n){var r=t.inlineTemplate?null:Wa(t,n,!0);return"_c("+e+","+Ka(t,n)+(r?","+r:"")+")"}(e.component,e,t);else{var r;(!e.plain||e.pre&&t.maybeComponent(e))&&(r=Ka(e,t));var i=e.inlineTemplate?null:Wa(e,t,!0);n="_c('"+e.tag+"'"+(r?","+r:"")+(i?","+i:"")+")"}for(var o=0;o>>0}(a):"")+")"}(e,e.scopedSlots,t)+","),e.model&&(n+="model:{value:"+e.model.value+",callback:"+e.model.callback+",expression:"+e.model.expression+"},"),e.inlineTemplate){var o=function(e,t){var n=e.children[0];if(n&&1===n.type){var r=Ra(n,t.options);return"inlineTemplate:{render:function(){"+r.render+"},staticRenderFns:["+r.staticRenderFns.map(function(e){return"function(){"+e+"}"}).join(",")+"]}"}}(e,t);o&&(n+=o+",")}return n=n.replace(/,$/,"")+"}",e.dynamicAttrs&&(n="_b("+n+',"'+e.tag+'",'+Xa(e.dynamicAttrs)+")"),e.wrapData&&(n=e.wrapData(n)),e.wrapListeners&&(n=e.wrapListeners(n)),n}function Ja(e){return 1===e.type&&("slot"===e.tag||e.children.some(Ja))}function qa(e,t){var n=e.attrsMap["slot-scope"];if(e.if&&!e.ifProcessed&&!n)return Va(e,t,qa,"null");if(e.for&&!e.forProcessed)return za(e,t,qa);var r=e.slotScope===ua?"":String(e.slotScope),i="function("+r+"){return "+("template"===e.tag?e.if&&n?"("+e.if+")?"+(Wa(e,t)||"undefined")+":undefined":Wa(e,t)||"undefined":Ha(e,t))+"}",o=r?"":",proxy:true";return"{key:"+(e.slotTarget||'"default"')+",fn:"+i+o+"}"}function Wa(e,t,n,r,i){var o=e.children;if(o.length){var a=o[0];if(1===o.length&&a.for&&"template"!==a.tag&&"slot"!==a.tag){var s=n?t.maybeComponent(a)?",1":",0":"";return""+(r||Ha)(a,t)+s}var c=n?function(e,t){for(var n=0,r=0;r':'
',ns.innerHTML.indexOf(" ")>0}var as=!!V&&os(!1),ss=!!V&&os(!0),cs=g(function(e){var t=Qn(e);return t&&t.innerHTML}),us=Cn.prototype.$mount;return Cn.prototype.$mount=function(e,t){if((e=e&&Qn(e))===document.body||e===document.documentElement)return this;var n=this.$options;if(!n.render){var r=n.template;if(r)if("string"==typeof r)"#"===r.charAt(0)&&(r=cs(r));else{if(!r.nodeType)return this;r=r.innerHTML}else e&&(r=function(e){if(e.outerHTML)return e.outerHTML;var t=document.createElement("div");return t.appendChild(e.cloneNode(!0)),t.innerHTML}(e));if(r){var i=is(r,{outputSourceRange:!1,shouldDecodeNewlines:as,shouldDecodeNewlinesForHref:ss,delimiters:n.delimiters,comments:n.comments},this),o=i.render,a=i.staticRenderFns;n.render=o,n.staticRenderFns=a}}return us.call(this,e,t)},Cn.compile=is,Cn}); -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ siteInfo.title }} - {{siteInfo.sub_title}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |
19 | 23 |

{{ siteInfo.sub_title }}

24 |
25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 | 35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 |
48 |
49 | 50 | 51 | 52 | 53 | 54 |
55 |
56 | 57 | 58 |
59 | 69 | 70 |
71 | 72 | 73 | 74 | 75 |
76 | 77 | 78 | 79 |
80 |
81 | 82 |
83 | 92 | 93 | 94 | 103 | 翻译 104 | 105 |
106 |
107 |
108 |
109 | 110 | 111 |
112 | 119 | 120 | 121 |
122 | 123 | 124 |
125 | 126 |
127 | 128 | 129 |
130 | 131 | 132 | 135 |
136 | 137 | 138 | 139 | 140 | 141 | 374 | 375 | -------------------------------------------------------------------------------- /app/templates/index_custom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ siteInfo.title }} - {{siteInfo.sub_title}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 |
21 | 25 |

{{ siteInfo.sub_title }}

26 |
27 |
28 | 29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 | 37 | 38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | 49 |
50 |
51 | 52 | 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 |
61 | 71 | 72 |
73 | 74 | 75 | 76 | 77 |
78 | 79 | 80 | 81 |
82 |
83 | 84 |
85 | 94 | 95 | 96 | 105 | 翻译 106 | 107 |
108 |
109 |
110 |
111 | 112 | 113 |
114 | 121 | 122 | 123 |
124 | 125 | 126 |
127 | 128 |
129 | 130 | 131 |
132 | 133 | 134 | 137 |
138 | 139 | 140 | 141 | 142 | 143 | 376 | 377 | -------------------------------------------------------------------------------- /app/templates/index_old.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Transmute - 智能翻译 7 | 8 | 9 | 10 | 11 | 171 | 172 | 173 |
174 | 175 | 184 | 185 | 186 |
187 |
188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 |
204 | 211 |
{{ charCount }}/5000
212 |
213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 229 | 翻译 230 | 231 | 232 | 233 | 234 | 235 |
236 | 242 |
243 | 248 | 249 |
250 |
251 |
252 |
253 | 254 | 255 | 258 |
259 | 260 | 261 | 262 | 263 | 431 | 432 | -------------------------------------------------------------------------------- /app/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloxz/transmute/2b563f9844763373227020771d902e90bbcd5141/app/utils/__init__.py -------------------------------------------------------------------------------- /app/utils/helper.py: -------------------------------------------------------------------------------- 1 | from fastapi import Request,Header 2 | from typing import Annotated 3 | 4 | from ipaddress import ip_address, IPv4Address, IPv6Address 5 | 6 | # 返回json信息 7 | def show_json(code:int, msg:str, data=None): 8 | return { 9 | "code": code, 10 | "msg": msg, 11 | "data": data 12 | } 13 | 14 | 15 | 16 | def get_client_ip(request:Request) -> str: 17 | """ 18 | 获取客户端 IP 地址,处理 X-Forwarded-For 和 X-Real-IP 头的情况。 19 | 如果 IP 地址格式不正确,则返回 "0.0.0.0"。 20 | """ 21 | # headers = Headers(raw=request.headers.raw) 22 | # 尝试通过 X-Forwarded-For 获取 23 | xff = request.headers.get("X-Forwarded-For") 24 | if xff: 25 | # 取第一个 IP 地址(通常是客户端原始 IP) 26 | ip = xff.split(",")[0].strip() 27 | else: 28 | # 如果 X-Forwarded-For 不存在,则尝试通过 X-Real-IP 获取 29 | ip = request.headers.get("X_Real_IP") 30 | # 取第一个 31 | if ip: 32 | ip = ip.split(",")[0].strip() 33 | 34 | # 如果以上两个头都不存在,则直接从请求中获取客户端 IP 35 | if not ip: 36 | ip = request.client.host 37 | 38 | # 验证 IP 地址格式是否正确 39 | try: 40 | # 使用 ipaddress 模块验证 IP 地址 41 | parsed_ip = ip_address(ip) 42 | if not isinstance(parsed_ip, (IPv4Address, IPv6Address)): 43 | ip = "0.0.0.0" 44 | except ValueError: 45 | # 如果 IP 地址格式不正确,则返回默认值 46 | ip = "0.0.0.0" 47 | 48 | return ip 49 | -------------------------------------------------------------------------------- /app/utils/version.py: -------------------------------------------------------------------------------- 1 | VERSION="1.0.1" 2 | VERSION_DATE="20250318" -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 安装依赖 4 | install_deps(){ 5 | akp update 6 | apk add python3 7 | apk add py3-pip 8 | mkdir -p /opt/transmute && cd /opt/transmute 9 | } 10 | 11 | # 安装 Python 依赖 12 | install_python_deps(){ 13 | python3 -m venv venv 14 | source venv/bin/activate 15 | pip3 install -r requirements.txt 16 | } 17 | 18 | 19 | # 清理缓存,缩小镜像体积 20 | clean(){ 21 | apk del py3-pip 22 | rm -rf /var/cache/apk/* 23 | rm -rf /root/.cache 24 | #rm -rf /opt/transmute/venv 25 | } 26 | 27 | install_deps && install_python_deps && clean -------------------------------------------------------------------------------- /other_deps.txt: -------------------------------------------------------------------------------- 1 | uvicorn==0.34.0 2 | Cython==3.0.12 3 | uvloop==0.21.0 4 | Jinja2==3.1.6 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.115.11 2 | httpx==0.28.1 3 | langid==1.1.6 4 | openai==1.66.3 5 | pydantic==2.10.6 6 | redis==5.2.1 7 | uvicorn==0.34.0 8 | Cython==3.0.12 9 | uvloop==0.21.0 10 | Jinja2==3.1.6 -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | # 复制配置文件 5 | copyConfig(){ 6 | # 检查app/config/config.json文件是否存在 7 | if [ ! -f "app/data/config/config.json" ]; then 8 | # 递归创建目录 9 | mkdir -p app/data/config 10 | # 复制配置文件 11 | cp app/config/config.default.json app/data/config/config.json 12 | fi 13 | } 14 | 15 | # 启动redis 16 | runRedis(){ 17 | redis-server app/config/redis.conf --daemonize yes 18 | # 检查 Redis 是否启动成功 19 | if [ $? -eq 0 ]; then 20 | echo "Redis started successfully." 21 | else 22 | echo "Failed to start Redis." 23 | exit 1 24 | fi 25 | } 26 | 27 | # 启动主进程 28 | runMain(){ 29 | # 获取环境变量WORKERS 30 | WORKERS=${WORKERS} 31 | # 判断变量是否存在 32 | if [ -z "$WORKERS" ]; then 33 | WORKERS=1 34 | fi 35 | # 启动主进程 36 | source venv/bin/activate 37 | uvicorn app.main:app --workers ${WORKERS} --host 0.0.0.0 --port 2082 38 | } 39 | 40 | copyConfig && runRedis && runMain 41 | --------------------------------------------------------------------------------