diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 66341c8..c37a74f 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -8,9 +8,9 @@ body: value: | **非常感谢您的反馈!Thank you very much for your feedback!** - 有关翻译错误的内容请附在此 [issue](https://github.com/yetone/bob-plugin-openai-translator/issues/30) 中。 + 有关翻译错误的内容请附在此 [issue](https://github.com/openai-translator/bob-plugin-openai-translator/issues/30) 中。 - Please post any translation-related errors in this [issue](https://github.com/yetone/bob-plugin-openai-translator/issues/30). + Please post any translation-related errors in this [issue](https://github.com/openai-translator/bob-plugin-openai-translator/issues/30). - type: checkboxes attributes: @@ -28,10 +28,10 @@ body: attributes: label: Please read README description: | - 辛苦提 Bug 前,请确定是按照 README 中的 [使用方法](https://github.com/yetone/bob-plugin-openai-translator/tree/main#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95) 部分安装的。 - - Before reporting bugs, please carefully read the [usage instructions]((https://github.com/yetone/bob-plugin-openai-translator/tree/main#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95) section in README - + 辛苦提 Bug 前,请确定是按照 README 中的 [使用方法](https://github.com/openai-translator/bob-plugin-openai-translator/tree/main#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95) 部分安装的。 + + Before reporting bugs, please carefully read the [usage instructions]((https://github.com/openai-translator/bob-plugin-openai-translator/tree/main#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95) section in README + options: - label: I have read the usage instructions section in README in detail. required: true @@ -40,7 +40,7 @@ body: label: Please check your network and OpenAI API quota description: | 如果您遇到的报错是 Failed to fetch 或者 API quota exceed,则说明是网络问题或者 OpenAI API 使用量超过阈值,这些问题都与 OpenAI Translator Bob Plugin 无关,请自行解决。 - + If you encounter error messages such as 'Failed to fetch' or 'API quota exceed', it means there is a network issue or the usage limit of OpenAI API has been exceeded. These issues are not related to OpenAI Translator Bob Plugin, please resolve them on your own. options: @@ -51,7 +51,7 @@ body: label: OpenAI Translator Bob Plugin version description: | 请提供您正在使用的 OpenAI Translator Bob Plugin 的版本。 - + Please provide the version of OpenAI Translator Bob Plugin you are using. For example, `v0.1.0`. validations: required: true @@ -60,7 +60,7 @@ body: label: 系统版本 | System version description: | 请提供您正在使用的系统版本。 - + Please provide the version of the System you are using. For example, `macOS 11.2.3`. validations: required: true @@ -78,7 +78,7 @@ body: label: 复现步骤 | Reproduce step description: | 请提供完整且简明的复现步骤,以方便及时定位并解决问题。 - + Please provide complete and concise reproduction steps to facilitate timely identification and resolution of the issue. validations: required: true @@ -100,7 +100,7 @@ body: label: 你是否愿意提交一份 PR 来修改这个错误?Are you willing to submit a PR? description: | 我们期待开发人员和用户的帮助,以解决在 OpenAI Translator Bob Plugin 中发现的任何问题。 如果您愿意通过提交 PR 来解决此问题,请勾选。 - + We eagerly anticipate developers' and users' support and collaboration in resolving any issues found in OpenAI Translator Bob Plugin. If you are willing to offer a solution by submitting a PR to fix this matter, kindly mark the checkbox provided. options: - label: 我愿意提供 PR! I'm willing to submit a PR! diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5fac515..45270d0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,9 +1,14 @@ -name: Release +name: Bump Version and Release on: - push: - tags: [ v\d+\.\d+\.\d+ ] - + workflow_dispatch: + inputs: + version: + description: 'Tag version (e.g., 1.0.0 or v1.0.0)' + required: true + message: + description: 'Tag message' + required: true jobs: release: @@ -14,56 +19,77 @@ jobs: packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.PAT }} - - name: Get version - id: get_version - uses: battila7/get-version-action@v2 + - name: Set up Git + run: | + git config --global user.name 'GitHub Actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' - - name: Get tag message - id: tag + - name: Determine tag version + id: determine_version run: | - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - echo "message=$(git tag -l --format='%(contents:subject)' ${{ steps.get_version.outputs.version }})" >> $GITHUB_OUTPUT + VERSION="${{ github.event.inputs.version }}" + if [[ $VERSION != v* ]]; then + VERSION="v$VERSION" + fi + VERSION_NUMBER="${VERSION#v}" + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "VERSION_NUMBER=$VERSION_NUMBER" >> $GITHUB_ENV - - name: Change version + - name: Update info.json run: | - OLD_VERSION=$(grep '"version":' src/info.json | awk -F\" '{print $4}') - sed -i "s/$OLD_VERSION/${{ steps.get_version.outputs.version-without-v }}/" src/info.json + OLD_VERSION=$(grep '"version":' public/info.json | awk -F\" '{print $4}') + sed -i "s/$OLD_VERSION/${{ env.VERSION_NUMBER }}/" public/info.json - - name: Package plugin - run: mkdir release && zip -j -r release/openai-translator-${{ steps.get_version.outputs.version-without-v }}.bobplugin ./src/* + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: .node-version + cache: pnpm - - run: git checkout -- src/ + - run: pnpm install + - run: pnpm run build + + - name: Package plugin + run: | + zip -j -r dist/openai-translator-${{ env.VERSION_NUMBER }}.bobplugin ./dist/* + echo "Packaged file: dist/openai-translator-${{ env.VERSION_NUMBER }}.bobplugin" - name: Update appcast.json env: - VERSION: ${{ steps.get_version.outputs.version-without-v }} - MESSAGE: ${{ steps.tag.outputs.message }} + VERSION_NUMBER: ${{ env.VERSION_NUMBER }} + MESSAGE: ${{ github.event.inputs.message }} run: | - python3 scripts/update_release.py "$VERSION" "$MESSAGE" - + python3 scripts/update_appcast.py "$VERSION_NUMBER" "$MESSAGE" + - name: Commit files run: | - git config --global user.name 'Bryan Lee' - git config --global user.email '38807139+liby@users.noreply.github.com' - git commit -am 'chore: update appcast.json and info.json' + git commit -a -m 'chore: update appcast.json and info.json' + + - name: Create tag + env: + VERSION: ${{ env.VERSION }} + MESSAGE: ${{ github.event.inputs.message }} + run: | + git tag -a "$VERSION" -m "$MESSAGE" - name: Push changes uses: ad-m/github-push-action@master with: github_token: ${{ secrets.PAT }} + tags: true - name: Upload binaries to release uses: svenstaro/upload-release-action@v2 with: - release_name: ${{ steps.get_version.outputs.version }} + release_name: ${{ env.VERSION }} repo_token: ${{ secrets.GITHUB_TOKEN }} - file: release/openai-translator-${{ steps.get_version.outputs.version-without-v }}.bobplugin - asset_name: openai-translator-${{ steps.get_version.outputs.version-without-v }}.bobplugin - tag: ${{ github.ref }} + file: dist/openai-translator-${{ env.VERSION_NUMBER }}.bobplugin + asset_name: openai-translator-${{ env.VERSION_NUMBER }}.bobplugin + tag: ${{ env.VERSION }} overwrite: true - body: ${{ steps.tag.outputs.message }} + body: ${{ github.event.inputs.message }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index a8b324e..3367fdb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ *.bobplugin +*.log* +dist +node_modules \ No newline at end of file diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..593cb75 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +20.16.0 \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index 132ebf9..b4874ba 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,173 +1 @@ -# Attribution-NonCommercial-ShareAlike 4.0 International - -Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. - -### Using Creative Commons Public Licenses - -Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. - -* __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors). - -* __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees). - -## Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License - -By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. - -### Section 1 – Definitions. - -a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. - -b. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. - -c. __BY-NC-SA Compatible License__ means a license listed at [creativecommons.org/compatiblelicenses](http://creativecommons.org/compatiblelicenses), approved by Creative Commons as essentially the equivalent of this Public License. - -d. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. - -e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. - -f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. - -g. __License Elements__ means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution, NonCommercial, and ShareAlike. - -h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License. - -i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. - -h. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License. - -i. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. - -j. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. - -k. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. - -l. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. - -### Section 2 – Scope. - -a. ___License grant.___ - - 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: - - A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and - - B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only. - - 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. - - 3. __Term.__ The term of this Public License is specified in Section 6(a). - - 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. - - 5. __Downstream recipients.__ - - A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. - - B. __Additional offer from the Licensor – Adapted Material.__ Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply. - - C. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. - - 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). - -b. ___Other rights.___ - - 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this Public License. - - 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. - -### Section 3 – License Conditions. - -Your exercise of the Licensed Rights is expressly made subject to the following conditions. - -a. ___Attribution.___ - - 1. If You Share the Licensed Material (including in modified form), You must: - - A. retain the following if it is supplied by the Licensor with the Licensed Material: - - i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); - - ii. a copyright notice; - - iii. a notice that refers to this Public License; - - iv. a notice that refers to the disclaimer of warranties; - - v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; - - B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and - - C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. - - 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. - - 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. - -b. ___ShareAlike.___ - -In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. - -1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-NC-SA Compatible License. - -2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. - -3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. - -### Section 4 – Sui Generis Database Rights. - -Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: - -a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only; - -b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and - -c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. - -For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. - -### Section 5 – Disclaimer of Warranties and Limitation of Liability. - -a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__ - -b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__ - -c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. - -### Section 6 – Term and Termination. - -a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. - -b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or - - 2. upon express reinstatement by the Licensor. - - For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. - -c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. - -d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. - -### Section 7 – Other Terms and Conditions. - -a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. - -b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. - -### Section 8 – Interpretation. - -a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. - -b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. - -c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. - -d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. - -> Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. -> -> Creative Commons may be contacted at creativecommons.org +This work is licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/). \ No newline at end of file diff --git a/README.md b/README.md index 5e6c485..a1b9480 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@

- 简体中文 | English + 简体中文 | English

OpenAI Translator Bob Plugin

- - release + + release - - GitHub Repo stars + + GitHub Repo stars - + GitHub Repo stars - + GitHub Repo stars

@@ -22,7 +22,7 @@ > **Note** > -> 重要更新:非 macOS 用户可以使用我开发的基于 ChatGPT API 的划词翻译浏览器插件 [openai-translator](https://github.com/yetone/openai-translator) 以解燃眉之急。 +> 重要更新:非 macOS 用户可以使用我开发的基于 OpenAI API 的划词翻译浏览器插件 [openai-translator](https://github.com/yetone/openai-translator) 以解燃眉之急。 ## 演示 @@ -34,7 +34,7 @@ ChatGPT 向我们展示了 GPT 模型的伟大之处,所以我使用 OpenAI ### 润色功能 -此插件已支持使用 ChatGPT API 对句子进行润色和语法修改,只需要把目标语言设置为与源语言一样即可,全面替代 Grammarly!而且理论上任何语言都可以润色,不仅仅是英语。 +此插件已支持使用 OpenAI API 对句子进行润色和语法修改,只需要把目标语言设置为与源语言一样即可,全面替代 Grammarly!而且理论上任何语言都可以润色,不仅仅是英语。 如果你不喜欢将翻译功能和文本润色功能放在一起,这里单独拆分出了一个专门用来文本润色和语法纠错的插件: [bob-plugin-openai-polisher](https://github.com/yetone/bob-plugin-openai-polisher),这个润色插件具有更高级的润色功能,比如解释修改原因等。 @@ -42,13 +42,13 @@ ChatGPT 向我们展示了 GPT 模型的伟大之处,所以我使用 OpenAI 要使用 ChatGPT 的 API 需要在 Bob 的设置页面把此插件的模型改为 `gpt-3.5-turbo-0301` 或者 `gpt-3.5-turbo`: -![how to use ChatGPT API](https://user-images.githubusercontent.com/1206493/222339607-d8f05042-4b65-495c-af58-849891de7434.png) +![how to use OpenAI API](https://user-images.githubusercontent.com/1206493/222339607-d8f05042-4b65-495c-af58-849891de7434.png) ## 使用方法 1. 安装 [Bob](https://bobtranslate.com/guide/#%E5%AE%89%E8%A3%85) (版本 >= 0.50),一款 macOS 平台的翻译和 OCR 软件 -2. 下载此插件: [openai-translator.bobplugin](https://github.com/yetone/bob-plugin-openai-translator/releases/latest) +2. 下载此插件: [openai-translator.bobplugin](https://github.com/openai-translator/bob-plugin-openai-translator/releases/latest) 3. 安装此插件: @@ -58,7 +58,7 @@ ChatGPT 向我们展示了 GPT 模型的伟大之处,所以我使用 OpenAI 5. 把 API KEY 填入 Bob 偏好设置 > 服务 > 此插件配置界面的 API KEY 的输入框中 - 如果你想了解关于其他设置的更多信息,请查看[配置手册](./docs/configuration_manual_CN.md) - + ![设置步骤](https://user-images.githubusercontent.com/1206493/219937398-8e5bb8d2-7dc8-404a-96e7-a937e08c939f.gif) 6. 安装 [PopClip](https://bobtranslate.com/guide/integration/popclip.html) 实现划词后鼠标附近出现悬浮图标 diff --git a/appcast.json b/appcast.json index ac157e3..ba77c7f 100644 --- a/appcast.json +++ b/appcast.json @@ -5,322 +5,322 @@ "version": "2.3.2", "desc": "Add support for GPT-4o mini", "sha256": "8990a720c38c8e23f91cacc6e320845c27f12abffefe95ca51472507ff28a7d2", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v2.3.2/openai-translator-2.3.2.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v2.3.2/openai-translator-2.3.2.bobplugin", "minBobVersion": "1.8.0" }, { "version": "2.3.1", "desc": "Added new models to provide more options for users", "sha256": "fd7e6da0062d907d27aaf932ab5c93c5daac3056eea68be2fe960390aa7e7c14", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v2.3.1/openai-translator-2.3.1.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v2.3.1/openai-translator-2.3.1.bobplugin", "minBobVersion": "1.8.0" }, { "version": "2.3.0", "desc": "Add temperature configuration", "sha256": "b82396b6f6c72e946325388ec0c93a683d5c1330a7635d7e5149d16c608533be", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v2.3.0/openai-translator-2.3.0.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v2.3.0/openai-translator-2.3.0.bobplugin", "minBobVersion": "1.8.0" }, { "version": "2.2.4", "desc": "Update 429 error code message", "sha256": "8f81063cc9962b01f30b0f5f0e5c481a235b55e695dc29d5ce7618611298ac5e", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v2.2.4/openai-translator-2.2.4.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v2.2.4/openai-translator-2.2.4.bobplugin", "minBobVersion": "1.8.0" }, { "version": "2.2.3", "desc": "Replace Completions with Chat in Azure OpenAI key validation", "sha256": "1b072b4ee23cec060e748d8e9cc5cea4345246de5661d09c2ad2d714d6e0a9fa", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v2.2.3/openai-translator-2.2.3.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v2.2.3/openai-translator-2.2.3.bobplugin", "minBobVersion": "1.8.0" }, { "version": "2.2.2", "desc": "Update API version query in Azure OpenAI key validation", "sha256": "f7b0b7a756d54b9ee97889ddde16e1d332e6382e49d6c75096acdd09a40e9f57", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v2.2.2/openai-translator-2.2.2.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v2.2.2/openai-translator-2.2.2.bobplugin", "minBobVersion": "1.8.0" }, { "version": "2.2.0", "desc": "Added Custom API Key Validation and Stream Output Settings", "sha256": "f34651fdead53927d366af2f0968c2d78b5f96992a480256adf249fc1f6fb612", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v2.2.0/openai-translator-2.2.0.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v2.2.0/openai-translator-2.2.0.bobplugin", "minBobVersion": "1.8.0" }, { "version": "2.1.3", "desc": "Simplify JSON parsing and response handling in the streaming process.", "sha256": "526074b41a5296f2b0cd450ecb6ce726c5dab611f6f5de44cdc80eb4e2bfcf94", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v2.1.3/openai-translator-2.1.3.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v2.1.3/openai-translator-2.1.3.bobplugin", "minBobVersion": "1.8.0" }, { "version": "2.1.2", "desc": "Compress the logo", "sha256": "98cc3116f451050792ce2338e819641f55c78146f3c92b1a23c8f6b23c6803cb", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v2.1.2/openai-translator-2.1.2.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v2.1.2/openai-translator-2.1.2.bobplugin", "minBobVersion": "1.8.0" }, { "version": "2.1.1", "desc": "Update logo", "sha256": "38d58027894be6df22a9e90286a67c918c23bc90ad901d5b102585d688addc9f", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v2.1.1/openai-translator-2.1.1.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v2.1.1/openai-translator-2.1.1.bobplugin", "minBobVersion": "1.8.0" }, { "version": "2.1.0", "desc": "Support Cloudflare AI Gateway and update default Azure API version", "sha256": "8724ab8305ab09e241e60cab072cefb8d93385863e9ecdd62d7606ea089f8d71", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v2.1.0/openai-translator-2.1.0.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v2.1.0/openai-translator-2.1.0.bobplugin", "minBobVersion": "1.8.0" }, { "version": "2.0.0", "desc": "Support for custom models, drop of support for v1/completions", "sha256": "c515fe5ee1e6602a2f879fc8ae76ef586b85d854badad87143c1c7cde639b754", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v2.0.0/openai-translator-2.0.0.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v2.0.0/openai-translator-2.0.0.bobplugin", "minBobVersion": "1.8.0" }, { "version": "1.1.4", "desc": "Fixed a bad judgment about a model issue", "sha256": "6408b89c3e1f5406616716ca743c2dd8231c5326c5b40179bac8771a571bda36", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v1.1.4/openai-translator-1.1.4.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v1.1.4/openai-translator-1.1.4.bobplugin", "minBobVersion": "1.8.0" }, { "version": "1.1.3", "desc": "Supports the latest GPT-3.5 Turbo model", "sha256": "830554d6751a759aab65d6358452e9e2a98cc0a0e1d65d3b8599a182ddd12ee2", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v1.1.3/openai-translator-1.1.3.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v1.1.3/openai-translator-1.1.3.bobplugin", "minBobVersion": "1.8.0" }, { "version": "1.1.2", "desc": "优化 HTTP Error 429 的错误信息", "sha256": "8fa7ec7e5686ac1940ac4caf165dfeb3857cb417105d6b1b6533c42f06993f37", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v1.1.2/openai-translator-1.1.2.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v1.1.2/openai-translator-1.1.2.bobplugin", "minBobVersion": "1.8.0" }, { "version": "1.1.1", "desc": "Supports the latest GPT-4 and 3.5 Turbo models", "sha256": "eac85470b36bf3766ab225859198f0d3c8245d4e32695bf99db091eb7ee48cac", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v1.1.1/openai-translator-1.1.1.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v1.1.1/openai-translator-1.1.1.bobplugin", "minBobVersion": "1.8.0" }, { "version": "1.1.0", "desc": "Handle fragmented stream data from OpenAI API", "sha256": "4dd2f5f30c6571c82134d48f2502bd524e768480ba5b2c99bb32d8ced7ad090e", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v1.1.0/openai-translator-1.1.0.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v1.1.0/openai-translator-1.1.0.bobplugin", "minBobVersion": "1.8.0" }, { "version": "1.0.1", "desc": "Update the error handling logic", "sha256": "9b2c2f0e2afe844f0fd19952fbebdb41a3715f838e701f25ee58860fc7f367d0", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v1.0.1/openai-translator-1.0.1.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v1.0.1/openai-translator-1.0.1.bobplugin", "minBobVersion": "1.8.0" }, { "version": "1.0.0", "desc": "Supports streaming output, requires Bob version >= 1.8.0", "sha256": "6a5843cd08244554f2e07f6ac5a57c6ff508fce6e70f0fc0f635e0a25055fecf", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v1.0.0/openai-translator-1.0.0.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v1.0.0/openai-translator-1.0.0.bobplugin", "minBobVersion": "1.8.0" }, { "version": "0.4.0", "desc": "Support custom prompts", "sha256": "74791057b4d804137105ecdf0e26abe25f972db1372416ba8ff1cc8a0f8debc0", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.4.0/openai-translator-0.4.0.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.4.0/openai-translator-0.4.0.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.3.3", "desc": "If you have not set API domain, use api.openai.com", "sha256": "dd8f54b35daf214749c9174c9d01e7f3b2cf600101ab2e0fc7b32ddb16a3eea1", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.3.3/openai-translator-0.3.3.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.3.3/openai-translator-0.3.3.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.3.2", "desc": "Remove trailing comma and handle empty API keys error", "sha256": "87ad8574c9663595eefabe3c5497b19aeaee13f5353986557262f21345e023df", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.3.2/openai-translator-0.3.2.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.3.2/openai-translator-0.3.2.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.3.1", "desc": "Improve compatibility handling for API URL", "sha256": "94e250270acaf70d3cf12e339e1dbd843ce470dc2a1ed78257bee2dd03f6f552", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.3.1/openai-translator-0.3.1.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.3.1/openai-translator-0.3.1.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.3.0", "desc": "Remove extra symbols from translation results", "sha256": "ba6d79293c9091c368d5b720479f4729403c1a71486320ed6df36818f947621a", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.3.0/openai-translator-0.3.0.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.3.0/openai-translator-0.3.0.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.2.9", "desc": "Updated the format of Prompt", "sha256": "49f3d84f5831c745bdf28abe16baacf77c35ff796e81f055167a35a43cacb2c8", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.2.9/openai-translator-0.2.9.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.2.9/openai-translator-0.2.9.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.2.8", "desc": "Fix the issue of incorrect release version", "sha256": "71df49c99dfa3a26cf615088144d67371086d449a636d23c969c83ef4468d918", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.2.8/openai-translator-0.2.8.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.2.8/openai-translator-0.2.8.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.2.7", "desc": "Add support for gpt-4 and Azure OpenAI service", "sha256": "3e0a7b41ae117f95f3b2f9070bb59fc3a1bce4fd5ff7ac7e749617013ad026bd", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.2.7/openai-translator-0.2.7.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.2.7/openai-translator-0.2.7.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.2.6", "desc": "Fix incorrect polishing result", "sha256": "3cfbcd66d47ef41a9842d3d4e8bf156e9879842b8134f5d075aaa1cb8cc45b87", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.2.6/openai-translator-0.2.6.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.2.6/openai-translator-0.2.6.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.2.5", "desc": "Improve the prompt to avoid many failed translation cases.", "sha256": "dd846ab0e9ec23fb23fc0829174d0bb8006da60dbc2742cd09da73b3f0d54d38", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.2.5/openai-translator-0.2.5.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.2.5/openai-translator-0.2.5.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.2.4", "desc": "Support specify the OpenAI domain", "sha256": "0b46246db77c3f714b9fed703204e4d558d275805e61d6a369b94ac3a0b3461f", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.2.4/openai-translator-0.2.4.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.2.4/openai-translator-0.2.4.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.2.3", "desc": "optimize: save token count", "sha256": "31823ea32290783939ad5f69ea48137816479c127278664d741bfbc352ab2182", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.2.3/openai-translator-0.2.3.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.2.3/openai-translator-0.2.3.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.2.2", "desc": "Remove Chinese quotation marks prefix and suffix.", "sha256": "eecf1248874f3b71a5884405d17c82320e0a3770af6dafe7249bad33defb567c", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.2.2/openai-translator-0.2.2.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.2.2/openai-translator-0.2.2.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.2.1", "desc": "Fixed an issue with some sentences not translating", "sha256": "8b847e9a33b2278d6ee8894e99c767f53166ab683e075a8ea5f6b43c5de7ef1f", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.2.1/openai-translator-0.2.1.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.2.1/openai-translator-0.2.1.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.2.0", "desc": "With the ability to use Grammarly, you can polish your sentences and correct grammar errors!", "sha256": "ee2165c49e82ffe553c66dff732b3ff8f2e9807b6e08629bba91f0827975147b", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.2.0/openai-translator-0.2.0.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.2.0/openai-translator-0.2.0.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.1.4", "desc": "Recommend gpt-3.5-turbo model", "sha256": "56c56ff74e8902317b5554a29ecbb906f54bd0b48cc08e48d44ec3425efc7592", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.1.4/openai-translator-0.1.4.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.1.4/openai-translator-0.1.4.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.1.3", "desc": "Fine-tune the prompt to eliminate unnecessary symbols in the translation result.", "sha256": "99a41cd9cb660c0389463dde5bf3c1a3077c3d45a54eec94b48f90927902fe25", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.1.3/openai-translator-0.1.3.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.1.3/openai-translator-0.1.3.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.1.2", "desc": "Support Classical Chinese to Simple Chinese", "sha256": "61f270491fd8b46cb67ae62c7fdec8aaa8d2e5942364b9ddb051621e18caecf9", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.1.2/openai-translator-0.1.2.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.1.2/openai-translator-0.1.2.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.1.1", "desc": "Fix buggy ChatGPT model detection logic", "sha256": "a2831405a2c2e5381ef89a9af2636a396645159b8f7e40294d77c209683b4e8d", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.1.1/openai-translator-0.1.1.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.1.1/openai-translator-0.1.1.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.1.0", "desc": "feat: support ChatGPT API!!! Please change the model to \"gpt-3.5-turbo\" or \"gpt-3.5-turbo-0301\"", "sha256": "9f08257b46f864f514b1963f27fc240db654c355eb9bae3c25feef8b177202c4", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.1.0/openai-translator-0.1.0.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.1.0/openai-translator-0.1.0.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.0.12", - "desc": "https://github.com/yetone/bob-plugin-openai-translator/releases/tag/v0.0.12", + "desc": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/tag/v0.0.12", "sha256": "ce9f60c9a9282c083459af55719521cb7abd9b618681674290fe26f3d78b2b75", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.0.12/openai-translator-0.0.12.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.0.12/openai-translator-0.0.12.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.0.11", - "desc": "https://github.com/yetone/bob-plugin-openai-translator/releases/tag/v0.0.11", + "desc": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/tag/v0.0.11", "sha256": "a274fe29119ce4a5a4fb522f79f71db5a0867befd12d807a7f14d15643dc3481", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.0.11/openai-translator-0.0.11.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.0.11/openai-translator-0.0.11.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.0.10", - "desc": "https://github.com/yetone/bob-plugin-openai-translator/releases/tag/v0.0.10", + "desc": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/tag/v0.0.10", "sha256": "784c9c304a2b4b76aa6abd023dd897c29924331a9b29ecc7cadb60a18d48f8b5", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.0.10/openai-translator-0.0.10.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.0.10/openai-translator-0.0.10.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.0.9", - "desc": "https://github.com/yetone/bob-plugin-openai-translator/releases/tag/v0.0.9", + "desc": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/tag/v0.0.9", "sha256": "57e07d15a5654abf41ade482b0f76d024b371dc40de4df8cb80f3d93530b1811", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.0.9/openai-translator-0.0.9.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.0.9/openai-translator-0.0.9.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.0.8", - "desc": "https://github.com/yetone/bob-plugin-openai-translator/releases/tag/v0.0.8", + "desc": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/tag/v0.0.8", "sha256": "a5acb2971f31fd5989092ead4ed21757d409256ad5c3a0ca80d9ee1db8450a09", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.0.8/openai-translator-0.0.8.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.0.8/openai-translator-0.0.8.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.0.7", - "desc": "https://github.com/yetone/bob-plugin-openai-translator/releases/tag/v0.0.7", + "desc": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/tag/v0.0.7", "sha256": "a2738659384fde997ce222655a841ac7f2184e702a1eb7230071c7edbb4d7214", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.0.7/openai-translator-0.0.7.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.0.7/openai-translator-0.0.7.bobplugin", "minBobVersion": "0.5.0" }, { "version": "0.0.6", - "desc": "https://github.com/yetone/bob-plugin-openai-translator/releases/tag/v0.0.6", + "desc": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/tag/v0.0.6", "sha256": "979a3db1a4ebde0475a79b267f8d05f7f940f38325daabdf0cecb73030102ca6", - "url": "https://github.com/yetone/bob-plugin-openai-translator/releases/download/v0.0.6/openai-translator-0.0.6.bobplugin", + "url": "https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v0.0.6/openai-translator-0.0.6.bobplugin", "minBobVersion": "0.5.0" } ] diff --git a/docs/README_EN.md b/docs/README_EN.md index 22da343..f7cfb88 100644 --- a/docs/README_EN.md +++ b/docs/README_EN.md @@ -1,20 +1,20 @@

- 简体中文 | English + 简体中文 | English

OpenAI Translator Bob Plugin

- - release + + release - - GitHub Repo stars + + GitHub Repo stars - + GitHub Repo stars - + GitHub Repo stars

@@ -22,7 +22,7 @@ > **Note** > -> Important update: Non-macOS users can use my browser extension based on ChatGPT API for word translation [openai-translator](https://github.com/yetone/openai-translator) to solve urgent needs. +> Important update: Non-macOS users can use my browser extension based on OpenAI API for word translation [openai-translator](https://github.com/yetone/openai-translator) to solve urgent needs. ## Demonstration @@ -36,21 +36,21 @@ ChatGPT showcases the greatness of GPT models, so I have implemented the Bob tra ### Polishing Feature -This plugin supports polishing sentences and modifying grammar using the ChatGPT API. To do so, just set the target language to be the same as the source language. It's a comprehensive alternative to Grammarly! And in theory, any language can be polished, not just English. +This plugin supports polishing sentences and modifying grammar using the OpenAI API. To do so, just set the target language to be the same as the source language. It's a comprehensive alternative to Grammarly! And in theory, any language can be polished, not just English. If you don't like combining translation functionality and text polishing, a separate plugin specifically for text polishing and grammar correction is available: [bob-plugin-openai-polisher](https://github.com/yetone/bob-plugin-openai-polisher). This polishing plugin has more advanced polishing features, such as explaining the modification reasons, etc. ### Language Model -To use the ChatGPT API, go to Bob's settings page and change the plugin model to `gpt-3.5-turbo-0301` or `gpt-3.5-turbo`: +To use the OpenAI API, go to Bob's settings page and change the plugin model to `gpt-3.5-turbo-0301` or `gpt-3.5-turbo`: -![how to use ChatGPT API](https://user-images.githubusercontent.com/1206493/222339607-d8f05042-4b65-495c-af58-849891de7434.png) +![how to use OpenAI API](https://user-images.githubusercontent.com/1206493/222339607-d8f05042-4b65-495c-af58-849891de7434.png) ## Usage 1. Install [Bob](https://bobtranslate.com/guide/#%E5%AE%89%E8%A3%85) (version >= 0.50), a macOS translation and OCR software -2. Download this plugin: [openai-translator.bobplugin](https://github.com/yetone/bob-plugin-openai-translator/releases/latest) +2. Download this plugin: [openai-translator.bobplugin](https://github.com/openai-translator/bob-plugin-openai-translator/releases/latest) 3. Install this plugin: diff --git a/global.d.ts b/global.d.ts deleted file mode 100644 index 251d94e..0000000 --- a/global.d.ts +++ /dev/null @@ -1,427 +0,0 @@ -declare namespace Bob { - // https://ripperhe.gitee.io/bob/#/plugin/addtion/language - enum LanguagesEnum { - 'auto' = '自动', - 'zh-Hans' = '中文简体', - 'zh-Hant' = '中文繁体', - 'yue' = '粤语', - 'wyw' = '文言文', - 'pysx' = '拼音缩写', - 'en' = '英语', - 'ja' = '日语', - 'ko' = '韩语', - 'fr' = '法语', - 'de' = '德语', - 'es' = '西班牙语', - 'it' = '意大利语', - 'ru' = '俄语', - 'pt' = '葡萄牙语', - 'nl' = '荷兰语', - 'pl' = '波兰语', - 'ar' = '阿拉伯语', - 'af' = '南非语', - 'am' = '阿姆哈拉语', - 'az' = '阿塞拜疆语', - 'be' = '白俄罗斯语', - 'bg' = '保加利亚语', - 'bn' = '孟加拉语', - 'bo' = '藏语', - 'bs' = '波斯尼亚语', - 'ca' = '加泰隆语', - 'ceb' = '宿务语', - 'chr' = '切罗基语', - 'co' = '科西嘉语', - 'cs' = '捷克语', - 'cy' = '威尔士语', - 'da' = '丹麦语', - 'el' = '希腊语', - 'eo' = '世界语', - 'et' = '爱沙尼亚语', - 'eu' = '巴斯克语', - 'fa' = '波斯语', - 'fi' = '芬兰语', - 'fj' = '斐济语', - 'fy' = '弗里西语', - 'ga' = '爱尔兰语', - 'gd' = '苏格兰盖尔语', - 'gl' = '加利西亚语', - 'gu' = '古吉拉特语', - 'ha' = '豪萨语', - 'haw' = '夏威夷语', - 'he' = '希伯来语', - 'hi' = '印地语', - 'hmn' = '苗语', - 'hr' = '克罗地亚语', - 'ht' = '海地克里奥尔语', - 'hu' = '匈牙利语', - 'hy' = '亚美尼亚语', - 'id' = '印尼语', - 'ig' = '伊博语', - 'is' = '冰岛语', - 'jw' = '爪哇语', - 'ka' = '格鲁吉亚语', - 'kk' = '哈萨克语', - 'km' = '高棉语', - 'kn' = '卡纳达语', - 'ku' = '库尔德语', - 'ky' = '柯尔克孜语', - 'la' = '老挝语', - 'lb' = '卢森堡语', - 'lo' = '老挝语', - 'lt' = '立陶宛语', - 'lv' = '拉脱维亚语', - 'mg' = '马尔加什语', - 'mi' = '毛利语', - 'mk' = '马其顿语', - 'ml' = '马拉雅拉姆语', - 'mn' = '蒙古语', - 'mr' = '马拉地语', - 'ms' = '马来语', - 'mt' = '马耳他语', - 'mww' = '白苗语', - 'my' = '缅甸语', - 'ne' = '尼泊尔语', - 'no' = '挪威语', - 'ny' = '齐切瓦语', - 'or' = '奥里亚语', - 'otq' = '克雷塔罗奥托米语', - 'pa' = '旁遮普语', - 'ps' = '普什图语', - 'ro' = '罗马尼亚语', - 'rw' = '卢旺达语', - 'sd' = '信德语', - 'si' = '僧伽罗语', - 'sk' = '斯洛伐克语', - 'sl' = '斯洛文尼亚语', - 'sm' = '萨摩亚语', - 'sn' = '修纳语', - 'so' = '索马里语', - 'sq' = '阿尔巴尼亚语', - 'sr' = '塞尔维亚语', - 'sr-Cyrl' = '塞尔维亚语-西里尔文', - 'sr-Latn' = '塞尔维亚语-拉丁文', - 'st' = '塞索托语', - 'su' = '巽他语', - 'sv' = '瑞典语', - 'sw' = '斯瓦希里语', - 'ta' = '泰米尔语', - 'te' = '泰卢固语', - 'tg' = '塔吉克语', - 'th' = '泰语', - 'tk' = '土库曼语', - 'tl' = '菲律宾语', - 'tlh' = '克林贡语', - 'to' = '汤加语', - 'tr' = '土耳其语', - 'tt' = '鞑靼语', - 'ty' = '塔希提语', - 'ug' = '维吾尔语', - 'uk' = '乌克兰语', - 'ur' = '乌尔都语', - 'uz' = '乌兹别克语', - 'vi' = '越南语', - 'xh' = '科萨语', - 'yi' = '意第绪语', - 'yo' = '约鲁巴语', - 'yua' = '尤卡坦玛雅语', - 'zu' = '祖鲁语', - } - - type Languages = Array; - type supportLanguages = Languages; - type Language = keyof typeof LanguagesEnum; - - - interface DataPayload { - message: string; -} - - interface Disposable { - dispose: () => void; - } - - interface Signal { - send: (data?: DataPayload) => void; - subscribe: (callback: (data?: DataPayload) => void) => Disposable; - removeAllSubscriber: () => void; - } - - // https://ripperhe.gitee.io/bob/#/plugin/quickstart/translate - type Translate = (query: TranslateQuery, completion: Completion) => void; - type completionResult = { result: Result }; - type CompletionError = { error: ServiceError }; - type Completion = (args: completionResult | CompletionError) => void; - type HandleStream = (args: completionResult) => void; - type ValidateCompletion = (args: { result: boolean, error?: ServiceError }) => void; - type PluginValidate = (completion: ValidateCompletion) => void; - interface TranslateQuery { - text: string; // 需要翻译的文本 - from: Language; // 用户选中的源语种标准码 - to: Language; // 用户选中的目标语种标准码 - detectFrom: Exclude; // 检测过后的源语种 - detectTo: Exclude; // 检测过后的目标语种 - cancelSignal: Signal, - onStream: HandleStream, - onCompletion: Completion; // 用于回调翻译结果的函数 - } - interface OcrQuery { - from: Language; // 目前用户选中的源语言 - image: Data; // 需要识别的图片数据 - detectFrom: Exclude; // 图片中最可能的语言,如果插件不具备检测语种的能力,可直接使用该属性。 - } - interface TTSQuery { - text: string; // 需要合成的文本 - lang: Exclude; // 当前文本的语种。 - } - type Query = TranslateQuery | OcrQuery | TTSQuery; - - // https://ripperhe.gitee.io/bob/#/plugin/quickstart/info - interface Info { - identifier: string; // 插件的唯一标识符,必须由数字、小写字母和 . 组成。 - category: 'translate' | 'ocr' | 'tts'; // 插件类别,分别对应文本翻译、文本识别和语音合成。 - version: string; // 插件版本号,必须由数字、小写字母和 . 组成。 - name: string; // 插件名称,无限制,建议别太长。 - summary?: string; // 插件描述信息。 - icon?: string; // 插件图标标识符,如果插件根目录有 icon.png 文件,则会将其作为插件图标,不会读取该字段;如果没有,会读取该字段,值可以为 这个图标列表 中所包含的任意一个ID。 - author?: string; // 插件作者。 - homepage?: string; // 插件主页网址。 - appcast?: string; // 插件发布信息 URL。 - minBobVersion?: string; // 最低支持本插件的 Bob 版本,建议填写您开发插件时候的调试插件的 Bob 版本,目前应该是 1.8.0。 - options?: OptionObject[]; - } - interface MenuObject { - title: string; // 菜单选项名称,用于展示。 - value: string; // 当前菜单被选中时的值。 - } - interface OptionObject { - identifier: string; // 选项唯一标识符,取值时使用。 - type: 'text' | 'menu'; // 选项类型,分别对应输入框和菜单。 - title: string; // 选项名称,用于展示。 - defaultValue?: string; // 默认值。 - menuValues?: MenuObject[]; // type 为 menu 时必须有菜单选项数组,详情见 menu object。 - } - - // https://ripperhe.gitee.io/bob/#/plugin/quickstart/publish - interface Appcast { - identifier: string; // 插件的唯一标识符,需和插件 info.json 文件中的唯一标识符一致。 - versions: Array; // 版本信息数组,请倒序排列,新版本放前面。具体结构看 version object。 - } - interface VersionObject { - version: string; // 版本号,请与对应插件包 info.json 中的信息一致。 - desc: string; // 插件的更新内容。 - sha256: string; // 插件包 SHA256 哈希值,会和从 url 中下载的插件包进行校验。 - url: string; // 插件包下载地址。 - minBobVersion?: string; // 最低支持本插件的 Bob 版本,请与对应插件包 info.json 中的信息一致。 - } - - // https://ripperhe.gitee.io/bob/#/plugin/api/option - type Option = { - [propName: string]: string; - }; - - // https://ripperhe.gitee.io/bob/#/plugin/api/log - interface Log { - info: (msg: string) => void; // 用于打印一些常规的信息 - error: (msg: string) => void; // 用于打印错误信息 - } - - // https://ripperhe.gitee.io/bob/#/plugin/api/http - interface Http { - request>(config: HttpRequestConfig): Promise; - get>(config: HttpRequestConfig): Promise; - post>(config: HttpRequestConfig): Promise; - streamRequest>(config: HttpStreamRequestConfig): Promise; - } - type HttpMethod = - | 'get' - | 'GET' - | 'delete' - | 'DELETE' - | 'head' - | 'HEAD' - | 'options' - | 'OPTIONS' - | 'post' - | 'POST' - | 'put' - | 'PUT'; - - interface HttpRequestConfig { - url: string; - method?: HttpMethod; - header?: any; - params?: any; - body?: any; - files?: HttpRequestFiles; - handler?: (resp: HttpResponse) => void; - timeout?: number; - } - - interface HttpStreamRequestConfig { - url: string; - method?: HttpMethod; - header?: any; - params?: any; - body?: any; - files?: HttpRequestFiles; - handler?: (resp: HttpResponse) => void; - cancelSignal?: Signal; - streamHandler?: (stream: { text: string, rawData: Data }) => void - timeout?: number; - } - - interface HttpRequestFiles { - data: DataObject; // 二进制数据 - name: string; // 上传表单中的名称 - filename: string; // 上传之后的文件名 - 'content-type': string; // 文件格式 - } - interface HttpResponse { - data: T; // object / string / $data 解析过后的数据 - rawData: DataObject; - response: HttpResponseInfo; // 请求响应信息 - error: HttpResponseError; - } - interface HttpResponseInfo { - url: string; // url - MIMEType: string; // MIME 类型 - expectedContentLength: number; // 长度 - textEncodingName: string; // 编码 - suggestedFilename: string; // 建议的文件名 - statusCode: number; // HTTP 状态码 - headers: any; // HTTP header - } - interface HttpResponseError { - domain: string; - code: number; - userInfo: any; - localizedDescription: string; // 描述 - localizedFailureReason: string; // 原因 - localizedRecoverySuggestion: string; // 建议 - } - type HttpResponsePromise = Promise>; - - // https://ripperhe.gitee.io/bob/#/plugin/api/file - interface File { - read(path: string): DataObject; - write(object: { data: DataObject; path: string }): boolean; - delete(path: string): boolean; - list(path: string): string[]; - copy(object: { src: string; dst: string }): boolean; - move(object: { src: string; dst: string }): boolean; - mkdir(path: string): boolean; - exists(path: string): boolean; - isDirectory(path: string): boolean; - } - - // https://ripperhe.gitee.io/bob/#/plugin/api/data - interface Data { - fromUTF8: (data: string) => DataObject; - fromHex: (data: string) => DataObject; - fromBase64: (data: string) => DataObject; - fromByteArray(data: number[]): DataObject; - fromData: (data: DataObject) => DataObject; - isData: (data: any) => boolean; - } - interface DataObject { - length: number; - toUTF8(): string | undefined; - toHex(useUpper?: boolean): string; - toBase64(): string; - toByteArray(): number[]; - readUInt8(index: number): number; - writeUInt8(value: number, index: number): void; - subData(start: number, end: number): DataObject; - appendData(data: this): this; - } - - // https://ripperhe.gitee.io/bob/#/plugin/object/serviceerror - interface ServiceError { - type: ServiceErrorType; // 错误类型 - message: string; // 错误描述,用于展示给用户看 - addition?: string; // 附加信息,可以是任何可 json 序列化的数据类型,用于 debug - troubleshootingLink?: string; // 附加的故障排除链接,用于指导用户解决问题 - } - enum ServiceErrorEnum { - unknown = '未知错误', - param = '参数错误', - unsupportLanguage = '不支持的语种', - secretKey = '缺少秘钥', - network = '网络异常,网络请失败', - api = '服务接口异常', - } - type ServiceErrorType = keyof typeof ServiceErrorEnum; - - // https://ripperhe.gitee.io/bob/#/plugin/object/translateresult - interface TranslateResult { - from: Language; // 由翻译接口提供的源语种,可以与查询时的 from 不同。 - to: Language; // 由翻译接口提供的目标语种,可以与查询时的 to 不同。 - toParagraphs: string[]; // 译文分段拆分过后的 string 数组。 - fromParagraphs?: string[]; // 原文分段拆分过后的 string 数组。 - toDict?: ToDictObject; // 词典结果,见 to dict object。 - fromTTS?: TTSResult; // result原文的语音合成数据。 - toTTS?: TTSResult; // result译文的语音合成数据。 - raw?: any; // 如果插件内部调用了某翻译接口,可将接口原始数据传回,方便定位问题。 - } - interface ToDictObject { - phonetics: Array; // 音标数据数组,一般英文查词会有,见 phonetic object。 - parts: Array; // 词性词义数组,一般英文查词会有,见 part object。 - exchanges?: Array; // 其他形式数组,一般英文查词会有,见 exchange object。 - relatedWordParts?: Array; // 相关的单词数组,一般中文查词会有,表示和该中文对应的英文单词有哪些,见 related word part object。 - addtions?: Array; // 附加内容数组,考虑到以上字段无法覆盖所有词典内容,比如例句、记忆技巧等,可将相应数据添加到该数组,最终也会显示到翻译结果中,见 addtion object。 - } - interface PhoneticObject { - type: 'us' | 'uk'; // 音标类型,值可以是 us 或 uk,分别对应美式音标和英式音标。 - value?: string; // 音标字符串。例如 ɡʊd。 - tts?: TTSResult; // result音标发音数据。 - } - interface PartObject { - part: string; // 单词词性,例如 n.、vi.... - means: string[]; // 词义 string 数组。 - } - interface ExchangeObject { - name: string; // 形式的名字,例如 比较级、最高级... - words: string[]; // 该形式对于的单词 string 数组,一般只有一个 - } - interface RelatedWordPartObject { - part?: string; // 词性。 - words: Array; // 相关的单词数组,见 related word object。 - } - interface RelatedWordObject { - word: string; // 单词本身。 - means?: string[]; // 词义 string 数组。 - } - interface AddtionObject { - name: string; // 附加内容名称。 - value: string; // 附加内容。 - } - - // https://ripperhe.gitee.io/bob/#/plugin/object/ttsresult - interface TTSResult { - type: 'url' | 'base64'; // 数据类型,必传。 - value: string; // 值,必传。 - raw?: any; // 如果插件内部调用了某语音合成接口,可将接口原始数据传回,方便定位问题,可不传。 - } - - // https://ripperhe.gitee.io/bob/#/plugin/object/ocrresult - interface OcrResult { - from?: Language; // 图片中的文字的主要语种,可与查询参数中传入的 from 不一致,可不传。 - texts: Array; // 文本识别结果数组,按照段落分割,见 ocr text,必传。 - raw?: any; // 如果插件内部调用了某文本识别接口,可将接口原始数据传回,方便定位问题,可不传。 - } - interface OcrText { - text: string; - } - - type Result = TranslateResult | OcrResult | TTSResult; -} - -declare var $http: Bob.Http; -declare var $info: Bob.Info; -declare var $option: Bob.Option; -declare var $log: Bob.Log; -declare var $data: Bob.Data; -declare var $file: Bob.File; -declare var $signal: { - new: () => Bob.Signal; -}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..36c219d --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "bob-plugin-openai-translator", + "version": "1.0.0", + "description": "基于 OpenAI API 的文本翻译、文本润色、语法纠错 Bob 插件,让我们一起迎接不需要巴别塔的新时代!", + "type": "module", + "license": "CC BY-NC-SA 4.0", + "main": "dist/main.js", + "keywords": [ + "bob-plugin", + "openai", + "translator" + ], + "homepage": "https://github.com/openai-translator/bob-plugin-openai-translator", + "scripts": { + "build": "rollup --config rollup.config.ts --configPlugin typescript" + }, + "devDependencies": { + "typescript": "5.5.4", + "@rollup/plugin-terser": "0.4.4", + "@rollup/plugin-typescript": "11.1.6", + "rollup": "4.20.0", + "rollup-plugin-copy": "3.5.0", + "tslib": "2.6.3" + }, + "engines": { + "node": ">=20" + }, + "packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e" +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..f2bba1b --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,764 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@rollup/plugin-terser': + specifier: 0.4.4 + version: 0.4.4(rollup@4.20.0) + '@rollup/plugin-typescript': + specifier: 11.1.6 + version: 11.1.6(rollup@4.20.0)(tslib@2.6.3)(typescript@5.5.4) + rollup: + specifier: 4.20.0 + version: 4.20.0 + rollup-plugin-copy: + specifier: 3.5.0 + version: 3.5.0 + tslib: + specifier: 2.6.3 + version: 2.6.3 + typescript: + specifier: 5.5.4 + version: 5.5.4 + +packages: + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@rollup/plugin-terser@0.4.4': + resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-typescript@11.1.6': + resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.14.0||^3.0.0||^4.0.0 + tslib: '*' + typescript: '>=3.7.0' + peerDependenciesMeta: + rollup: + optional: true + tslib: + optional: true + + '@rollup/pluginutils@5.1.0': + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.20.0': + resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.20.0': + resolution: {integrity: sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.20.0': + resolution: {integrity: sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.20.0': + resolution: {integrity: sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.20.0': + resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.20.0': + resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.20.0': + resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.20.0': + resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': + resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.20.0': + resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.20.0': + resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.20.0': + resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.20.0': + resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.20.0': + resolution: {integrity: sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.20.0': + resolution: {integrity: sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.20.0': + resolution: {integrity: sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/fs-extra@8.1.5': + resolution: {integrity: sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==} + + '@types/glob@7.2.0': + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} + + '@types/minimatch@5.1.2': + resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} + + '@types/node@22.1.0': + resolution: {integrity: sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==} + + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globby@10.0.1: + resolution: {integrity: sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==} + engines: {node: '>=8'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-core-module@2.15.0: + resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-object@3.0.1: + resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==} + engines: {node: '>=0.10.0'} + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup-plugin-copy@3.5.0: + resolution: {integrity: sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==} + engines: {node: '>=8.3'} + + rollup@4.20.0: + resolution: {integrity: sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + smob@1.5.0: + resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + terser@5.31.3: + resolution: {integrity: sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==} + engines: {node: '>=10'} + hasBin: true + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.13.0: + resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + +snapshots: + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@rollup/plugin-terser@0.4.4(rollup@4.20.0)': + dependencies: + serialize-javascript: 6.0.2 + smob: 1.5.0 + terser: 5.31.3 + optionalDependencies: + rollup: 4.20.0 + + '@rollup/plugin-typescript@11.1.6(rollup@4.20.0)(tslib@2.6.3)(typescript@5.5.4)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.20.0) + resolve: 1.22.8 + typescript: 5.5.4 + optionalDependencies: + rollup: 4.20.0 + tslib: 2.6.3 + + '@rollup/pluginutils@5.1.0(rollup@4.20.0)': + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + optionalDependencies: + rollup: 4.20.0 + + '@rollup/rollup-android-arm-eabi@4.20.0': + optional: true + + '@rollup/rollup-android-arm64@4.20.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.20.0': + optional: true + + '@rollup/rollup-darwin-x64@4.20.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.20.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.20.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.20.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.20.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.20.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.20.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.20.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.20.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.20.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.20.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.20.0': + optional: true + + '@types/estree@1.0.5': {} + + '@types/fs-extra@8.1.5': + dependencies: + '@types/node': 22.1.0 + + '@types/glob@7.2.0': + dependencies: + '@types/minimatch': 5.1.2 + '@types/node': 22.1.0 + + '@types/minimatch@5.1.2': {} + + '@types/node@22.1.0': + dependencies: + undici-types: 6.13.0 + + acorn@8.12.1: {} + + array-union@2.1.0: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + buffer-from@1.1.2: {} + + colorette@1.4.0: {} + + commander@2.20.3: {} + + concat-map@0.0.1: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + estree-walker@2.0.2: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.7 + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globby@10.0.1: + dependencies: + '@types/glob': 7.2.0 + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + glob: 7.2.3 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + + graceful-fs@4.2.11: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + ignore@5.3.1: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-core-module@2.15.0: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-plain-object@3.0.1: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + merge2@1.4.1: {} + + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + path-is-absolute@1.0.1: {} + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + picomatch@2.3.1: {} + + queue-microtask@1.2.3: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + resolve@1.22.8: + dependencies: + is-core-module: 2.15.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.0.4: {} + + rollup-plugin-copy@3.5.0: + dependencies: + '@types/fs-extra': 8.1.5 + colorette: 1.4.0 + fs-extra: 8.1.0 + globby: 10.0.1 + is-plain-object: 3.0.1 + + rollup@4.20.0: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.20.0 + '@rollup/rollup-android-arm64': 4.20.0 + '@rollup/rollup-darwin-arm64': 4.20.0 + '@rollup/rollup-darwin-x64': 4.20.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.20.0 + '@rollup/rollup-linux-arm-musleabihf': 4.20.0 + '@rollup/rollup-linux-arm64-gnu': 4.20.0 + '@rollup/rollup-linux-arm64-musl': 4.20.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.20.0 + '@rollup/rollup-linux-riscv64-gnu': 4.20.0 + '@rollup/rollup-linux-s390x-gnu': 4.20.0 + '@rollup/rollup-linux-x64-gnu': 4.20.0 + '@rollup/rollup-linux-x64-musl': 4.20.0 + '@rollup/rollup-win32-arm64-msvc': 4.20.0 + '@rollup/rollup-win32-ia32-msvc': 4.20.0 + '@rollup/rollup-win32-x64-msvc': 4.20.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.2.1: {} + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + slash@3.0.0: {} + + smob@1.5.0: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + supports-preserve-symlinks-flag@1.0.0: {} + + terser@5.31.3: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.12.1 + commander: 2.20.3 + source-map-support: 0.5.21 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tslib@2.6.3: {} + + typescript@5.5.4: {} + + undici-types@6.13.0: {} + + universalify@0.1.2: {} + + wrappy@1.0.2: {} diff --git a/src/icon.png b/public/icon.png similarity index 100% rename from src/icon.png rename to public/icon.png diff --git a/src/info.json b/public/info.json similarity index 96% rename from src/info.json rename to public/info.json index 847a55e..ef64dcf 100644 --- a/src/info.json +++ b/public/info.json @@ -6,8 +6,8 @@ "summary": "GPT powered translator", "icon": "", "author": "yetone ", - "homepage": "https://github.com/yetone/bob-plugin-openai-translator", - "appcast": "https://raw.githubusercontent.com/yetone/bob-plugin-openai-translator/main/appcast.json", + "homepage": "https://github.com/openai-translator/bob-plugin-openai-translator", + "appcast": "https://raw.githubusercontent.com/openai-translator/bob-plugin-openai-translator/main/appcast.json", "minBobVersion": "1.8.0", "options": [ { diff --git a/rollup.config.ts b/rollup.config.ts new file mode 100644 index 0000000..6f52657 --- /dev/null +++ b/rollup.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from "rollup"; +import copy from 'rollup-plugin-copy' +import typescript from "@rollup/plugin-typescript"; + +export default defineConfig({ + input: "src/main.ts", + output: { + file: "dist/main.js", + format: "cjs", + }, + cache: true, + plugins: [ + copy({ + targets: [ + { src: 'public/icon.png', dest: 'dist' }, + { src: 'public/info.json', dest: 'dist' } + ] + }), + typescript(), + ], +}); \ No newline at end of file diff --git a/scripts/update_release.py b/scripts/update_appcast.py similarity index 62% rename from scripts/update_release.py rename to scripts/update_appcast.py index 83aca03..91b9d74 100644 --- a/scripts/update_release.py +++ b/scripts/update_appcast.py @@ -3,9 +3,8 @@ import json from pathlib import Path - def update_appcast(version, desc): - release_file = Path(f'release/openai-translator-{version}.bobplugin') + release_file = Path(f'dist/openai-translator-{version}.bobplugin') assert release_file.is_file(), 'Release file not exist' with open(release_file, 'rb') as f: c = f.read() @@ -14,7 +13,7 @@ def update_appcast(version, desc): 'version': version, 'desc': desc, 'sha256': file_hash, - 'url': f'https://github.com/yetone/bob-plugin-openai-translator/releases/download/v{version}/{release_file.name}', + 'url': f'https://github.com/openai-translator/bob-plugin-openai-translator/releases/download/v{version}/{release_file.name}', 'minBobVersion': '1.8.0' } appcast_file = Path('appcast.json') @@ -27,16 +26,7 @@ def update_appcast(version, desc): with open(appcast_file, 'w') as f: json.dump(appcast, f, ensure_ascii=False, indent=2) -def update_info_json(version): - info_file = Path('src/info.json') - with open(info_file, 'r') as f: - info = json.load(f) - info['version'] = version - with open(info_file, 'w') as f: - json.dump(info, f, ensure_ascii=False, indent=2) - if __name__ == '__main__': version = sys.argv[1] desc = sys.argv[2] - update_appcast(version, desc) - update_info_json(version) \ No newline at end of file + update_appcast(version, desc) \ No newline at end of file diff --git a/src/const.js b/src/const.js deleted file mode 100644 index 7b18d6b..0000000 --- a/src/const.js +++ /dev/null @@ -1,48 +0,0 @@ - -var HttpErrorCodes = { - "400": "Bad Request", - "401": "Unauthorized", - "402": "Payment Required", - "403": "Forbidden", - "404": "Not Found", - "405": "Method Not Allowed", - "406": "Not Acceptable", - "407": "Proxy Authentication Required", - "408": "Request Timeout", - "409": "Conflict", - "410": "Gone", - "411": "Length Required", - "412": "Precondition Failed", - "413": "Payload Too Large", - "414": "URI Too Long", - "415": "Unsupported Media Type", - "416": "Range Not Satisfiable", - "417": "Expectation Failed", - "418": "I'm a teapot", - "421": "Misdirected Request", - "422": "Unprocessable Entity", - "423": "Locked", - "424": "Failed Dependency", - "425": "Too Early", - "426": "Upgrade Required", - "428": "Precondition Required", - "429": "请求过于频繁,请慢一点。OpenAI 对您在 API 上的请求实施速率限制。或是您的 API credits 已超支,需要充值。好消息是您仍然可以使用官方的 Web 端聊天页面", - "431": "Request Header Fields Too Large", - "451": "Unavailable For Legal Reasons", - "500": "Internal Server Error", - "501": "Not Implemented", - "502": "Bad Gateway", - "503": "Service Unavailable", - "504": "Gateway Timeout", - "505": "HTTP Version Not Supported", - "506": "Variant Also Negotiates", - "507": "Insufficient Storage", - "508": "Loop Detected", - "510": "Not Extended", - "511": "Network Authentication Required" -} - -var SYSTEM_PROMPT = "You are a translation engine that can only translate text and cannot interpret it." - -exports.SYSTEM_PROMPT = SYSTEM_PROMPT; -exports.HttpErrorCodes = HttpErrorCodes; \ No newline at end of file diff --git a/src/const.ts b/src/const.ts new file mode 100644 index 0000000..2bf7767 --- /dev/null +++ b/src/const.ts @@ -0,0 +1,47 @@ + +export const HTTP_ERROR_CODES = { + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Payload Too Large", + 414: "URI Too Long", + 415: "Unsupported Media Type", + 416: "Range Not Satisfiable", + 417: "Expectation Failed", + 418: "I'm a teapot", + 421: "Misdirected Request", + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 425: "Too Early", + 426: "Upgrade Required", + 428: "Precondition Required", + 429: "请求过于频繁,请慢一点。OpenAI 对您在 API 上的请求实施速率限制。或是您的 API credits 已超支,需要充值。好消息是您仍然可以使用官方的 Web 端聊天页面", + 431: "Request Header Fields Too Large", + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", + 507: "Insufficient Storage", + 508: "Loop Detected", + 510: "Not Extended", + 511: "Network Authentication Required" +} as const; + +export type HttpErrorCode = keyof typeof HTTP_ERROR_CODES; + +export const SYSTEM_PROMPT = "You are a translation engine that can only translate text and cannot interpret it." as const; \ No newline at end of file diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..44f1cab --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,59 @@ +type BobHttpResponse = import('./types').BobHttpResponse; +type DataObject = import('./types').DataObject; + +type HttpMethod = + | 'GET' + | 'DELETE' + | 'HEAD' + | 'OPTIONS' + | 'POST' + | 'PUT'; + +interface HttpRequestFiles { + data: DataObject; // Binary data + name: string; // Name in the upload form + filename: string; // Filename after upload + contentType: string; // File format +} + +interface HttpRequestConfig { + url: string; + method?: HttpMethod; + header?: Record; // Define as a record for headers + params?: Record; // Define as a record for query parameters + body?: any; // Specify a more detailed type if possible + files?: HttpRequestFiles; + handler?: (resp: BobHttpResponse) => void; + timeout?: number; // Timeout in milliseconds +} + +interface HttpStreamRequestConfig extends HttpRequestConfig { + cancelSignal?: Signal; // AbortSignal if using the Fetch API + streamHandler?: (stream: { text: string; rawData: DataObject }) => void; +} + +type HttpResponsePromise = Promise>; + +interface Http { + request>(config: HttpRequestConfig): Promise; + get>(config: HttpRequestConfig): Promise; + post>(config: HttpRequestConfig): Promise; + streamRequest>(config: HttpStreamRequestConfig): Promise; +} + +declare const $http: Http; + +interface Options { + apiKeys: string; + apiUrl: string; + apiVersion: string; + customModel: string; + customSystemPrompt: string; + customUserPrompt: string; + deploymentName: string; + model: string; + stream: string; + temperature: string; +} + +declare const $option: Options; \ No newline at end of file diff --git a/src/lang.js b/src/lang.ts similarity index 87% rename from src/lang.js rename to src/lang.ts index 886f14a..2058a21 100644 --- a/src/lang.js +++ b/src/lang.ts @@ -1,4 +1,4 @@ -const supportLanguages = [ +export const supportLanguageList = [ ["auto", "auto"], ["zh-Hans", "zh-CN"], ["zh-Hant", "zh-TW"], @@ -114,10 +114,8 @@ const supportLanguages = [ ["yi", "yi"], ["yo", "yo"], ["zu", "zu"], -]; +] as const; -exports.supportLanguages = supportLanguages; -exports.langMap = new Map(supportLanguages.map(([key, value]) => [key, value])); -exports.langMapReverse = new Map( - supportLanguages.map(([standardLang, lang]) => [lang, standardLang]) -); +export type Language = typeof supportLanguageList[number][0]; + +export const langMap = new Map(supportLanguageList.map(([key, value]) => [key, value])); diff --git a/src/main.js b/src/main.js deleted file mode 100644 index a67e628..0000000 --- a/src/main.js +++ /dev/null @@ -1,432 +0,0 @@ -//@ts-check - -var lang = require("./lang.js"); -var SYSTEM_PROMPT = require("./const.js").SYSTEM_PROMPT; - -var { - buildHeader, - ensureHttpsAndNoTrailingSlash, - getApiKey, - handleGeneralError, - handleValidateError, - replacePromptKeywords -} = require("./utils.js"); - - -/** - * @param {Bob.TranslateQuery} query - * @returns {{ - * generatedSystemPrompt: string, - * generatedUserPrompt: string - * }} -*/ -function generatePrompts(query) { - let generatedSystemPrompt = SYSTEM_PROMPT; - const { detectFrom, detectTo } = query; - const sourceLang = lang.langMap.get(detectFrom) || detectFrom; - const targetLang = lang.langMap.get(detectTo) || detectTo; - let generatedUserPrompt = `translate from ${sourceLang} to ${targetLang}`; - - if (detectTo === "wyw" || detectTo === "yue") { - generatedUserPrompt = `翻译成${targetLang}`; - } - - if ( - detectFrom === "wyw" || - detectFrom === "zh-Hans" || - detectFrom === "zh-Hant" - ) { - if (detectTo === "zh-Hant") { - generatedUserPrompt = "翻译成繁体白话文"; - } else if (detectTo === "zh-Hans") { - generatedUserPrompt = "翻译成简体白话文"; - } else if (detectTo === "yue") { - generatedUserPrompt = "翻译成粤语白话文"; - } - } - if (detectFrom === detectTo) { - generatedSystemPrompt = - "You are a text embellisher, you can only embellish the text, don't interpret it."; - if (detectTo === "zh-Hant" || detectTo === "zh-Hans") { - generatedUserPrompt = "润色此句"; - } else { - generatedUserPrompt = "polish this sentence"; - } - } - - generatedUserPrompt = `${generatedUserPrompt}:\n\n${query.text}` - - return { generatedSystemPrompt, generatedUserPrompt }; -} - -/** - * @param {string} model - * @param {Bob.TranslateQuery} query - * @returns {{ - * model: string; - * temperature: number; - * max_tokens: number; - * top_p: number; - * frequency_penalty: number; - * presence_penalty: number; - * messages?: { - * role: "system" | "user"; - * content: string; - * }[]; - * prompt?: string; - * }} -*/ -function buildRequestBody(model, query) { - let { customSystemPrompt, customUserPrompt, temperature } = $option; - const { generatedSystemPrompt, generatedUserPrompt } = generatePrompts(query); - - customSystemPrompt = replacePromptKeywords(customSystemPrompt, query); - customUserPrompt = replacePromptKeywords(customUserPrompt, query); - - const systemPrompt = customSystemPrompt || generatedSystemPrompt; - const userPrompt = customUserPrompt || generatedUserPrompt; - - const modelTemperature = Number(temperature ?? 0.2); - - const standardBody = { - model: model, - temperature: modelTemperature, - max_tokens: 1000, - top_p: 1, - frequency_penalty: 1, - presence_penalty: 1, - }; - - return { - ...standardBody, - model: model, - messages: [ - { - role: "system", - content: systemPrompt, - }, - { - role: "user", - content: userPrompt, - }, - ], - }; -} - -/** - * @param {Bob.TranslateQuery} query - * @param {string} targetText - * @param {string} textFromResponse - * @returns {string} -*/ -function handleStreamResponse(query, targetText, textFromResponse) { - if (textFromResponse !== '[DONE]') { - try { - const dataObj = JSON.parse(textFromResponse); - // https://github.com/openai/openai-node/blob/master/src/resources/chat/completions.ts#L190 - const { choices } = dataObj; - const delta = choices[0]?.delta?.content; - if (delta) { - targetText += delta; - query.onStream({ - result: { - from: query.detectFrom, - to: query.detectTo, - toParagraphs: [targetText], - }, - }); - } - } catch (err) { - handleGeneralError(query, { - type: err.type || "param", - message: err.message || "Failed to parse JSON", - addition: err.addition, - }); - } - } - return targetText; -} - -/** - * @param {Bob.TranslateQuery} query - * @param {Bob.HttpResponse} result - * @returns {void} -*/ -function handleGeneralResponse( query, result) { - const { choices } = result.data; - - if (!choices || choices.length === 0) { - handleGeneralError(query, { - type: "api", - message: "接口未返回结果", - addition: JSON.stringify(result), - }); - return; - } - - let targetText = choices[0].message.content.trim(); - - // 使用正则表达式删除字符串开头和结尾的特殊字符 - targetText = targetText.replace(/^(『|「|"|“)|(』|」|"|”)$/g, ""); - - // 判断并删除字符串末尾的 `" =>` - if (targetText.endsWith('" =>')) { - targetText = targetText.slice(0, -4); - } - - query.onCompletion({ - result: { - from: query.detectFrom, - to: query.detectTo, - toParagraphs: targetText.split("\n"), - }, - }); -} - -/** - * @type {Bob.Translate} - */ -function translate(query) { - if (!lang.langMap.get(query.detectTo)) { - handleGeneralError(query, { - type: "unsupportLanguage", - message: "不支持该语种", - addition: "不支持该语种", - }); - } - - const { - apiKeys, - apiUrl, - apiVersion, - customModel, - deploymentName, - model, - stream, - } = $option; - - const isCustomModelRequired = model === "custom"; - if (isCustomModelRequired && !customModel) { - handleGeneralError(query, { - type: "param", - message: "配置错误 - 请确保您在插件配置中填入了正确的自定义模型名称", - addition: "请在插件配置中填写自定义模型名称", - }); - } - - if (!apiKeys) { - handleGeneralError(query, { - type: "secretKey", - message: "配置错误 - 请确保您在插件配置中填入了正确的 API Keys", - addition: "请在插件配置中填写 API Keys", - }); - } - - const modelValue = isCustomModelRequired ? customModel : model; - - const apiKey = getApiKey($option.apiKeys); - - const baseUrl = ensureHttpsAndNoTrailingSlash(apiUrl || "https://api.openai.com"); - let apiUrlPath = baseUrl.includes("gateway.ai.cloudflare.com") ? "/chat/completions" : "/v1/chat/completions"; - const apiVersionQuery = apiVersion ? `?api-version=${apiVersion}` : "?api-version=2023-03-15-preview"; - - const isAzureServiceProvider = baseUrl.includes("openai.azure.com"); - if (isAzureServiceProvider) { - if (deploymentName) { - apiUrlPath = `/openai/deployments/${deploymentName}/chat/completions${apiVersionQuery}`; - } else { - handleGeneralError(query,{ - type: "secretKey", - message: "配置错误 - 未填写 Deployment Name", - addition: "请在插件配置中填写 Deployment Name", - troubleshootingLink: "https://bobtranslate.com/service/translate/azureopenai.html" - }); - } - } - - const header = buildHeader(isAzureServiceProvider, apiKey); - const body = buildRequestBody(modelValue, query); - - let targetText = ""; // 初始化拼接结果变量 - let buffer = ""; // 新增 buffer 变量 - (async () => { - if (stream) { - await $http.streamRequest({ - method: "POST", - url: baseUrl + apiUrlPath, - header, - body: { - ...body, - stream: true, - }, - cancelSignal: query.cancelSignal, - streamHandler: (streamData) => { - if (streamData.text?.includes("Invalid token")) { - handleGeneralError(query, { - type: "secretKey", - message: "配置错误 - 请确保您在插件配置中填入了正确的 API Keys", - addition: "请在插件配置中填写正确的 API Keys", - troubleshootingLink: "https://bobtranslate.com/service/translate/openai.html" - }); - } else if (streamData.text !== undefined) { - // 将新的数据添加到缓冲变量中 - buffer += streamData.text; - // 检查缓冲变量是否包含一个完整的消息 - while (true) { - const match = buffer.match(/data: (.*?})\n/); - if (match) { - // 如果是一个完整的消息,处理它并从缓冲变量中移除 - const textFromResponse = match[1].trim(); - targetText = handleStreamResponse(query, targetText, textFromResponse); - buffer = buffer.slice(match[0].length); - } else { - // 如果没有完整的消息,等待更多的数据 - break; - } - } - } - }, - handler: (result) => { - if (result.response.statusCode >= 400) { - handleGeneralError(query, result); - } else { - query.onCompletion({ - result: { - from: query.detectFrom, - to: query.detectTo, - toParagraphs: [targetText], - }, - }); - } - } - }); - } else { - const result = await $http.request({ - method: "POST", - url: baseUrl + apiUrlPath, - header, - body, - }); - - if (result.error) { - handleGeneralError(query, result); - } else { - handleGeneralResponse(query, result); - } - } - })().catch((err) => { - handleGeneralError(query, err); - }); -} - -function supportLanguages() { - return lang.supportLanguages.map(([standardLang]) => standardLang); -} - - -/** - * @type {Bob.PluginValidate} - */ -function pluginValidate(completion) { - const { apiKeys, apiUrl, deploymentName } = $option; - if (!apiKeys) { - handleValidateError(completion, { - type: "secretKey", - message: "配置错误 - 请确保您在插件配置中填入了正确的 API Keys", - addition: "请在插件配置中填写正确的 API Keys", - troubleshootingLink: "https://bobtranslate.com/service/translate/openai.html" - }); - return; - } - - const apiKey = getApiKey(apiKeys); - const baseUrl = ensureHttpsAndNoTrailingSlash(apiUrl || "https://api.openai.com"); - let apiUrlPath = baseUrl.includes("gateway.ai.cloudflare.com") ? "/models" : "/v1/models"; - - const isAzureServiceProvider = apiUrl.includes("openai.azure.com"); - if (isAzureServiceProvider) { - if (!deploymentName) { - handleValidateError(completion, { - type: "secretKey", - message: "配置错误 - 未填写 Deployment Name", - addition: "请在插件配置中填写 Deployment Name", - troubleshootingLink: "https://bobtranslate.com/service/translate/azureopenai.html" - }); - return; - } - apiUrlPath = `/openai/deployments/${deploymentName}/chat/completions?api-version=2023-05-15`; - } - - const header = buildHeader(isAzureServiceProvider, apiKey); - (async () => { - if (isAzureServiceProvider) { - $http.request({ - method: "POST", - url: baseUrl+apiUrlPath, - header: header, - body: { - "messages":[{ - "content": "You are a helpful assistant.", - "role": "system", - }, { - "content": "Test connection.", - "role": "user", - }], - max_tokens: 5 - }, - handler: function(resp) { - if (resp.data.error) { - const { statusCode } = resp.response; - const reason = (statusCode >= 400 && statusCode < 500) ? "param" : "api"; - handleValidateError(completion, { - type: reason, - message: resp.data.error, - troubleshootingLink: "https://bobtranslate.com/service/translate/azureopenai.html" - }); - return; - } - if (resp.data.choices.length > 0) { - completion({ - result: true, - }) - } - } - }); - } else { - $http.request({ - method: "GET", - url: baseUrl+apiUrlPath, - header: header, - handler: function(resp) { - if (resp.data.error) { - const { statusCode } = resp.response; - const reason = (statusCode >= 400 && statusCode < 500) ? "param" : "api"; - handleValidateError(completion, { - type: reason, - message: resp.data.error, - troubleshootingLink: "https://bobtranslate.com/service/translate/openai.html" - }); - return; - } - const modelList = resp.data - if (modelList.data?.length > 0) { - completion({ - result: true, - }) - } - } - }); - } - })().catch((err) => { - handleValidateError(completion, err); - }); -} - -function pluginTimeoutInterval() { - return 60; -} - -exports.pluginTimeoutInterval = pluginTimeoutInterval; -exports.pluginValidate = pluginValidate; -exports.supportLanguages = supportLanguages; -exports.translate = translate; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..4129a4a --- /dev/null +++ b/src/main.ts @@ -0,0 +1,404 @@ +import { SYSTEM_PROMPT } from "./const"; +import { langMap, supportLanguageList } from "./lang"; +import type { BobHttpResponse, BobServiceError, BobTranslateQuery, BobValidateCompletion } from "./types"; +import { buildHeader, ensureHttpsAndNoTrailingSlash, getApiKey, handleGeneralError, handleValidateError, replacePromptKeywords } from "./utils"; + +function isBobServiceError(error: unknown): error is BobServiceError { + return ( + typeof error === 'object' && + error !== null && + 'message' in error && + typeof (error as BobServiceError).message === 'string' + ); +} + +function generatePrompts(query: BobTranslateQuery): { + generatedSystemPrompt: string, + generatedUserPrompt: string +} { + let generatedSystemPrompt = null; + const { detectFrom, detectTo } = query; + const sourceLang = langMap.get(detectFrom) || detectFrom; + const targetLang = langMap.get(detectTo) || detectTo; + let generatedUserPrompt = `translate from ${sourceLang} to ${targetLang}`; + + if (detectTo === "wyw" || detectTo === "yue") { + generatedUserPrompt = `翻译成${targetLang}`; + } + + if ( + detectFrom === "wyw" || + detectFrom === "zh-Hans" || + detectFrom === "zh-Hant" + ) { + if (detectTo === "zh-Hant") { + generatedUserPrompt = "翻译成繁体白话文"; + } else if (detectTo === "zh-Hans") { + generatedUserPrompt = "翻译成简体白话文"; + } else if (detectTo === "yue") { + generatedUserPrompt = "翻译成粤语白话文"; + } + } + if (detectFrom === detectTo) { + generatedSystemPrompt = + "You are a text embellisher, you can only embellish the text, don't interpret it."; + if (detectTo === "zh-Hant" || detectTo === "zh-Hans") { + generatedUserPrompt = "润色此句"; + } else { + generatedUserPrompt = "polish this sentence"; + } + } + + generatedUserPrompt = `${generatedUserPrompt}:\n\n${query.text}` + + return { + generatedSystemPrompt: generatedSystemPrompt ?? SYSTEM_PROMPT, + generatedUserPrompt + }; +} + +function buildRequestBody(model: string, query: BobTranslateQuery) { + let { customSystemPrompt, customUserPrompt, temperature } = $option; + const { generatedSystemPrompt, generatedUserPrompt } = generatePrompts(query); + + customSystemPrompt = replacePromptKeywords(customSystemPrompt, query); + customUserPrompt = replacePromptKeywords(customUserPrompt, query); + + const systemPrompt = customSystemPrompt || generatedSystemPrompt; + const userPrompt = customUserPrompt || generatedUserPrompt; + + const modelTemperature = Number(temperature ?? 0.2); + + const standardBody = { + model: model, + temperature: modelTemperature, + max_tokens: 1000, + top_p: 1, + frequency_penalty: 1, + presence_penalty: 1, + }; + + return { + ...standardBody, + model: model, + messages: [ + { + role: "system", + content: systemPrompt, + }, + { + role: "user", + content: userPrompt, + }, + ], + }; +} + +function handleStreamResponse(query: BobTranslateQuery, targetText: string, textFromResponse: string) { + if (textFromResponse !== '[DONE]') { + try { + const dataObj = JSON.parse(textFromResponse); + // https://github.com/openai/openai-node/blob/master/src/resources/chat/completions#L190 + const { choices } = dataObj; + const delta = choices[0]?.delta?.content; + if (delta) { + targetText += delta; + query.onStream({ + result: { + from: query.detectFrom, + to: query.detectTo, + toParagraphs: [targetText], + }, + }); + } + } catch (error) { + if (isBobServiceError(error)) { + handleGeneralError(query, { + type: error.type || 'param', + message: error.message || 'Failed to parse JSON', + addition: error.addition, + }); + } else { + handleGeneralError(query, { + type: 'param', + message: 'An unknown error occurred', + }); + } + } + } + return targetText; +} + +function handleGeneralResponse(query: BobTranslateQuery, result: BobHttpResponse) { + const { choices } = result.data; + + if (!choices || choices.length === 0) { + handleGeneralError(query, { + type: "api", + message: "接口未返回结果", + addition: JSON.stringify(result), + }); + return; + } + + let targetText = choices[0].message.content.trim(); + + // 使用正则表达式删除字符串开头和结尾的特殊字符 + targetText = targetText.replace(/^(『|「|"|“)|(』|」|"|”)$/g, ""); + + // 判断并删除字符串末尾的 `" =>` + if (targetText.endsWith('" =>')) { + targetText = targetText.slice(0, -4); + } + + query.onCompletion({ + result: { + from: query.detectFrom, + to: query.detectTo, + toParagraphs: targetText.split("\n"), + }, + }); +} + +function translate(query: BobTranslateQuery) { + if (!langMap.get(query.detectTo)) { + handleGeneralError(query, { + type: "unsupportedLanguage", + message: "不支持该语种", + addition: "不支持该语种", + }); + } + + const { + apiKeys, + apiUrl, + apiVersion, + customModel, + deploymentName, + model, + stream, + } = $option; + + const isCustomModelRequired = model === "custom"; + if (isCustomModelRequired && !customModel) { + handleGeneralError(query, { + type: "param", + message: "配置错误 - 请确保您在插件配置中填入了正确的自定义模型名称", + addition: "请在插件配置中填写自定义模型名称", + }); + } + + if (!apiKeys) { + handleGeneralError(query, { + type: "secretKey", + message: "配置错误 - 请确保您在插件配置中填入了正确的 API Keys", + addition: "请在插件配置中填写 API Keys", + }); + } + + const modelValue = isCustomModelRequired ? customModel : model; + + const apiKey = getApiKey($option.apiKeys); + + const baseUrl = ensureHttpsAndNoTrailingSlash(apiUrl || "https://api.openai.com"); + let apiUrlPath = baseUrl.includes("gateway.ai.cloudflare.com") ? "/chat/completions" : "/v1/chat/completions"; + const apiVersionQuery = apiVersion ? `?api-version=${apiVersion}` : "?api-version=2023-03-15-preview"; + + const isAzureServiceProvider = baseUrl.includes("openai.azure.com"); + if (isAzureServiceProvider) { + if (deploymentName) { + apiUrlPath = `/openai/deployments/${deploymentName}/chat/completions${apiVersionQuery}`; + } else { + handleGeneralError(query, { + type: "secretKey", + message: "配置错误 - 未填写 Deployment Name", + addition: "请在插件配置中填写 Deployment Name", + troubleshootingLink: "https://bobtranslate.com/service/translate/azureopenai.html" + }); + } + } + + const header = buildHeader(isAzureServiceProvider, apiKey); + const body = buildRequestBody(modelValue, query); + + let targetText = ""; // 初始化拼接结果变量 + let buffer = ""; // 新增 buffer 变量 + (async () => { + if (stream) { + await $http.streamRequest({ + method: "POST", + url: baseUrl + apiUrlPath, + header, + body: { + ...body, + stream: true, + }, + cancelSignal: query.cancelSignal, + streamHandler: (streamData) => { + if (streamData.text?.includes("Invalid token")) { + handleGeneralError(query, { + type: "secretKey", + message: "配置错误 - 请确保您在插件配置中填入了正确的 API Keys", + addition: "请在插件配置中填写正确的 API Keys", + troubleshootingLink: "https://bobtranslate.com/service/translate/openai.html" + }); + } else if (streamData.text !== undefined) { + // 将新的数据添加到缓冲变量中 + buffer += streamData.text; + // 检查缓冲变量是否包含一个完整的消息 + while (true) { + const match = buffer.match(/data: (.*?})\n/); + if (match) { + // 如果是一个完整的消息,处理它并从缓冲变量中移除 + const textFromResponse = match[1].trim(); + targetText = handleStreamResponse(query, targetText, textFromResponse); + buffer = buffer.slice(match[0].length); + } else { + // 如果没有完整的消息,等待更多的数据 + break; + } + } + } + }, + handler: (result) => { + if (result.response.statusCode >= 400) { + handleGeneralError(query, result); + } else { + query.onCompletion({ + result: { + from: query.detectFrom, + to: query.detectTo, + toParagraphs: [targetText], + }, + }); + } + } + }); + } else { + const result = await $http.request({ + method: "POST", + url: baseUrl + apiUrlPath, + header, + body, + }); + + if (result.error) { + handleGeneralError(query, result); + } else { + handleGeneralResponse(query, result); + } + } + })().catch((error) => { + handleGeneralError(query, error); + }); +} + +function pluginValidate(completion: BobValidateCompletion) { + const { apiKeys, apiUrl, deploymentName } = $option; + if (!apiKeys) { + handleValidateError(completion, { + type: "secretKey", + message: "配置错误 - 请确保您在插件配置中填入了正确的 API Keys", + addition: "请在插件配置中填写正确的 API Keys", + troubleshootingLink: "https://bobtranslate.com/service/translate/openai.html" + }); + return; + } + + const apiKey = getApiKey(apiKeys); + const baseUrl = ensureHttpsAndNoTrailingSlash(apiUrl || "https://api.openai.com"); + let apiUrlPath = baseUrl.includes("gateway.ai.cloudflare.com") ? "/models" : "/v1/models"; + + const isAzureServiceProvider = apiUrl.includes("openai.azure.com"); + if (isAzureServiceProvider) { + if (!deploymentName) { + handleValidateError(completion, { + type: "secretKey", + message: "配置错误 - 未填写 Deployment Name", + addition: "请在插件配置中填写 Deployment Name", + troubleshootingLink: "https://bobtranslate.com/service/translate/azureopenai.html" + }); + return; + } + apiUrlPath = `/openai/deployments/${deploymentName}/chat/completions?api-version=2023-05-15`; + } + + const header = buildHeader(isAzureServiceProvider, apiKey); + (async () => { + if (isAzureServiceProvider) { + $http.request({ + method: "POST", + url: baseUrl + apiUrlPath, + header: header, + body: { + "messages": [{ + "content": "You are a helpful assistant.", + "role": "system", + }, { + "content": "Test connection.", + "role": "user", + }], + max_tokens: 5 + }, + handler: function (resp) { + if (resp.data.error) { + const { statusCode } = resp.response; + const reason = (statusCode >= 400 && statusCode < 500) ? "param" : "api"; + handleValidateError(completion, { + type: reason, + message: resp.data.error, + troubleshootingLink: "https://bobtranslate.com/service/translate/azureopenai.html" + }); + return; + } + if (resp.data.choices.length > 0) { + completion({ + result: true, + }) + } + } + }); + } else { + $http.request({ + method: "GET", + url: baseUrl + apiUrlPath, + header: header, + handler: function (resp) { + if (resp.data.error) { + const { statusCode } = resp.response; + const reason = (statusCode >= 400 && statusCode < 500) ? "param" : "api"; + handleValidateError(completion, { + type: reason, + message: resp.data.error, + troubleshootingLink: "https://bobtranslate.com/service/translate/openai.html" + }); + return; + } + const modelList = resp.data + if (modelList.data?.length > 0) { + completion({ + result: true, + }) + } + } + }); + } + })().catch((error) => { + handleValidateError(completion, error); + }); +} + +function pluginTimeoutInterval() { + return 60; +} + +const supportLanguages = () => { + return supportLanguageList.map(([standardLang]) => standardLang); +} + +export { + pluginTimeoutInterval, + pluginValidate, + supportLanguages, + translate, +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..26b168f --- /dev/null +++ b/src/types.ts @@ -0,0 +1,154 @@ +import { HttpErrorCode } from "./const"; +import { Language } from "./lang"; + +// https://bobtranslate.com/plugin/object/serviceerror.html#service-error +type BobServiceErrorType = + | 'unknown' + | 'param' + | 'unsupportedLanguage' + | 'secretKey' + | 'network' + | 'api' + | 'notFound'; + +export interface BobServiceError { + type: BobServiceErrorType; // 错误类型 + message: string; // 错误描述,用于展示给用户看 + addition?: string; // 附加信息,可以是任何可 json 序列化的数据类型,用于 debug + troubleshootingLink?: string; // 附加的故障排除链接,用于指导用户解决问题 +} + +export type BobValidateCompletion = (args: { result: boolean, error?: BobServiceError }) => void; + +export interface DataObject { + length: number; + toUTF8(): string | undefined; + toHex(useUpper?: boolean): string; + toBase64(): string; + toByteArray(): number[]; + readUInt8(index: number): number; + writeUInt8(value: number, index: number): void; + subData(start: number, end: number): DataObject; + appendData(data: this): this; +} + +interface BobHttpResponseInfo { + url: string; // url + MIMEType: string; // MIME 类型 + expectedContentLength: number; // 长度 + textEncodingName: string; // 编码 + suggestedFilename: string; // 建议的文件名 + statusCode: HttpErrorCode; // HTTP 状态码 + headers: any; // HTTP header +} + +interface BobHttpResponseError { + domain: string; + code: number; + userInfo: any; + localizedDescription: string; // 描述 + localizedFailureReason: string; // 原因 + localizedRecoverySuggestion: string; // 建议 +} + +export interface BobHttpResponse { + data: T; // object / string / $data 解析过后的数据 + rawData: DataObject; + response: BobHttpResponseInfo; // 请求响应信息 + error: BobHttpResponseError; +} + +interface DataPayload { + message: string; +} + +interface Signal { + send: (data?: DataPayload) => void; + subscribe: (callback: (data?: DataPayload) => void) => Disposable; + removeAllSubscriber: () => void; +} + +interface PhoneticObject { + type: 'us' | 'uk'; // 音标类型,值可以是 us 或 uk,分别对应美式音标和英式音标。 + value?: string; // 音标字符串。例如 ɡʊd。 + tts?: TTSResult; // result音标发音数据。 +} + +interface PartObject { + part: string; // 单词词性,例如 n.、vi.... + means: string[]; // 词义 string 数组。 +} + +interface ExchangeObject { + name: string; // 形式的名字,例如 比较级、最高级... + words: string[]; // 该形式对于的单词 string 数组,一般只有一个 +} + +interface RelatedWordObject { + word: string; // 单词本身。 + means?: string[]; // 词义 string 数组。 +} + +interface RelatedWordPartObject { + part?: string; // 词性。 + words: Array; // 相关的单词数组,见 related word object。 +} + +interface AdditionObject { + name: string; // 附加内容名称。 + value: string; // 附加内容。 +} + +interface ToDictObject { + phonetics: Array; // 音标数据数组,一般英文查词会有,见 phonetic object。 + parts: Array; // 词性词义数组,一般英文查词会有,见 part object。 + exchanges?: Array; // 其他形式数组,一般英文查词会有,见 exchange object。 + relatedWordParts?: Array; // 相关的单词数组,一般中文查词会有,表示和该中文对应的英文单词有哪些,见 related word part object。 + additions?: Array; // 附加内容数组,考虑到以上字段无法覆盖所有词典内容,比如例句、记忆技巧等,可将相应数据添加到该数组,最终也会显示到翻译结果中,见 additions object。 +} + +interface TranslateResult { + from: Language; // 由翻译接口提供的源语种,可以与查询时的 from 不同。 + to: Language; // 由翻译接口提供的目标语种,可以与查询时的 to 不同。 + toParagraphs: string[]; // 译文分段拆分过后的 string 数组。 + fromParagraphs?: string[]; // 原文分段拆分过后的 string 数组。 + toDict?: ToDictObject; // 词典结果,见 to dict object。 + fromTTS?: TTSResult; // result原文的语音合成数据。 + toTTS?: TTSResult; // result译文的语音合成数据。 + raw?: any; // 如果插件内部调用了某翻译接口,可将接口原始数据传回,方便定位问题。 +} + +interface OcrText { + text: string; +} + +interface OcrResult { + from?: Language; // 图片中的文字的主要语种,可与查询参数中传入的 from 不一致,可不传。 + texts: Array; // 文本识别结果数组,按照段落分割,见 ocr text,必传。 + raw?: any; // 如果插件内部调用了某文本识别接口,可将接口原始数据传回,方便定位问题,可不传。 +} + +interface TTSResult { + type: 'url' | 'base64'; // 数据类型,必传。 + value: string; // 值,必传。 + raw?: any; // 如果插件内部调用了某语音合成接口,可将接口原始数据传回,方便定位问题,可不传。 +} + +type Result = TranslateResult | OcrResult | TTSResult; + +type completionResult = { result: Result }; +type CompletionError = { error: BobServiceError }; + +type HandleStream = (args: completionResult) => void; +type Completion = (args: completionResult | CompletionError) => void; + +export interface BobTranslateQuery { + text: string; // 需要翻译的文本 + from: Language; // 用户选中的源语种标准码 + to: Language; // 用户选中的目标语种标准码 + detectFrom: Exclude; // 检测过后的源语种 + detectTo: Exclude; // 检测过后的目标语种 + cancelSignal: Signal, + onStream: HandleStream, + onCompletion: Completion; // 用于回调翻译结果的函数 +} diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index dd7e41d..0000000 --- a/src/utils.js +++ /dev/null @@ -1,101 +0,0 @@ -var HttpErrorCodes = require("./const.js").HttpErrorCodes; - -/** -* @param {boolean} isAzureServiceProvider - Indicates if the service provider is Azure. -* @param {string} apiKey - The authentication API key. -* @returns {{ - * "Content-Type": string; - * "api-key"?: string; - * "Authorization"?: string; - * }} The header object. - */ - function buildHeader(isAzureServiceProvider, apiKey) { - return { - "Content-Type": "application/json", - [isAzureServiceProvider ? "api-key" : "Authorization"]: isAzureServiceProvider ? apiKey : `Bearer ${apiKey}` - }; - } - -/** - * @param {string} url - * @returns {string} -*/ -function ensureHttpsAndNoTrailingSlash(url) { - const hasProtocol = /^[a-z]+:\/\//i.test(url); - const modifiedUrl = hasProtocol ? url : 'https://' + url; - - return modifiedUrl.endsWith('/') ? modifiedUrl.slice(0, -1) : modifiedUrl; -} - -/** - * @param {string} apiKeys - * @returns {string} -*/ -function getApiKey(apiKeys) { - const trimmedApiKeys = apiKeys.endsWith(",") ? apiKeys.slice(0, -1) : apiKeys; - const apiKeySelection = trimmedApiKeys.split(",").map(key => key.trim()); - return apiKeySelection[Math.floor(Math.random() * apiKeySelection.length)]; -} - -/** - * @param {Bob.TranslateQuery} query - * @param {Bob.ServiceError | Bob.HttpResponse} error - */ -function handleGeneralError(query, error) { - if ('response' in error) { - // 处理 HTTP 响应错误 - const { statusCode } = error.response; - const reason = (statusCode >= 400 && statusCode < 500) ? "param" : "api"; - query.onCompletion({ - error: { - type: reason, - message: `接口响应错误 - ${HttpErrorCodes[statusCode]}`, - addition: `${JSON.stringify(error)}`, - }, - }); - } else { - // 处理一般错误 - query.onCompletion({ - error: { - ...error, - type: error.type || "unknown", - message: error.message || "Unknown error", - }, - }); - } -} - - -/** - * @param {Bob.ValidateCompletion} completion - * @param {Bob.ServiceError} error - */ -function handleValidateError(completion, error) { - completion({ - result: false, - error: { - ...error, - type: error.type || 'unknown', - message: error.message || "Unknown error", - } - }); -} - -/** -* @param {string} prompt -* @param {Bob.TranslateQuery} query -* @returns {string} -*/ -function replacePromptKeywords(prompt, query) { - if (!prompt) return prompt; - return prompt.replace("$text", query.text) - .replace("$sourceLang", query.detectFrom) - .replace("$targetLang", query.detectTo); -} - -exports.buildHeader = buildHeader; -exports.ensureHttpsAndNoTrailingSlash = ensureHttpsAndNoTrailingSlash; -exports.getApiKey = getApiKey; -exports.handleGeneralError = handleGeneralError; -exports.handleValidateError = handleValidateError; -exports.replacePromptKeywords = replacePromptKeywords; \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..88a66d0 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,91 @@ +import { HTTP_ERROR_CODES } from "./const"; +import type { BobHttpResponse, BobServiceError, BobTranslateQuery, BobValidateCompletion } from "./types"; + + +function buildHeader(isAzureServiceProvider: boolean, apiKey: string): { + "Content-Type": string; + "api-key"?: string; + Authorization?: string; +} { + return { + "Content-Type": "application/json", + [isAzureServiceProvider ? "api-key" : "Authorization"]: + isAzureServiceProvider ? apiKey : `Bearer ${apiKey}`, + }; +} + +function ensureHttpsAndNoTrailingSlash(url: string): string { + const hasProtocol = /^[a-z]+:\/\//i.test(url); + const modifiedUrl = hasProtocol ? url : "https://" + url; + + return modifiedUrl.endsWith("/") ? modifiedUrl.slice(0, -1) : modifiedUrl; +} + +function getApiKey(apiKeys: string): string { + const trimmedApiKeys = apiKeys.endsWith(",") + ? apiKeys.slice(0, -1) + : apiKeys; + const apiKeySelection = trimmedApiKeys.split(",").map((key) => key.trim()); + return apiKeySelection[Math.floor(Math.random() * apiKeySelection.length)]; +} + +function handleGeneralError( + query: BobTranslateQuery, + error: BobServiceError | BobHttpResponse +) { + if ("response" in error) { + // Handle HTTP response error + const { statusCode } = error.response; + const reason = statusCode >= 400 && statusCode < 500 ? "param" : "api"; + query.onCompletion({ + error: { + type: reason, + message: `接口响应错误 - ${HTTP_ERROR_CODES[statusCode]}`, + addition: `${JSON.stringify(error)}`, + }, + }); + } else { + // Handle general error + query.onCompletion({ + error: { + ...error, + type: error.type || "unknown", + message: error.message || "Unknown error", + }, + }); + } +} + +function handleValidateError( + completion: BobValidateCompletion, + error: BobServiceError +) { + completion({ + result: false, + error: { + ...error, + type: error.type || "unknown", + message: error.message || "Unknown error", + }, + }); +} + +function replacePromptKeywords( + prompt: string, + query: BobTranslateQuery +): string { + if (!prompt) return prompt; + return prompt + .replace("$text", query.text) + .replace("$sourceLang", query.detectFrom) + .replace("$targetLang", query.detectTo); +} + +export { + buildHeader, + ensureHttpsAndNoTrailingSlash, + getApiKey, + handleGeneralError, + handleValidateError, + replacePromptKeywords, +}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 746b6d2..0782bfc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,20 @@ { "compilerOptions": { - "allowJs": true, - "checkJs": true, - "outDir": "dist", - "lib": ["ESNext", "DOM"], + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmitOnError": true, + "noUnusedParameters": true, + "pretty": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ESNext", + "useDefineForClassFields": false, }, - "exclude": ["node_modules", "**/node_modules/*", "dist/**"], + "exclude": [ + "dist", + "node_modules", + ] } \ No newline at end of file