├── .node-version ├── .gitignore ├── public ├── icon.png └── info.json ├── LICENSE.md ├── rollup.config.ts ├── tsconfig.json ├── package.json ├── scripts └── update_appcast.py ├── src ├── types.ts ├── const.ts ├── lang.ts ├── utils.ts └── main.ts ├── README.md ├── .github └── workflows │ └── release.yaml ├── docs └── README_EN.md ├── appcast.json └── pnpm-lock.yaml /.node-version: -------------------------------------------------------------------------------- 1 | 20.16.0 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bobplugin 2 | *.log* 3 | dist 4 | node_modules -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openai-translator/bob-plugin-openai-polisher/HEAD/public/icon.png -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This work is licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/). -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "rollup"; 2 | import copy from 'rollup-plugin-copy' 3 | import typescript from "@rollup/plugin-typescript"; 4 | 5 | export default defineConfig({ 6 | input: "src/main.ts", 7 | output: { 8 | file: "dist/main.js", 9 | format: "cjs", 10 | }, 11 | cache: true, 12 | plugins: [ 13 | copy({ 14 | targets: [ 15 | { src: 'public/icon.png', dest: 'dist' }, 16 | { src: 'public/info.json', dest: 'dist' } 17 | ] 18 | }), 19 | typescript(), 20 | ], 21 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "forceConsistentCasingInFileNames": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "noEmitOnError": true, 8 | "noUnusedParameters": true, 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "target": "ES5", 13 | "types": [ 14 | "@bob-translate/types" 15 | ], 16 | "useDefineForClassFields": false, 17 | }, 18 | "exclude": [ 19 | "dist", 20 | "node_modules", 21 | ] 22 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bob-plugin-openai-polisher", 3 | "version": "1.0.0", 4 | "description": "使用 OpenAI API 给文本进行润色和语法纠错的 Bob 插件!完美代替 Grammarly!", 5 | "type": "module", 6 | "license": "CC BY-NC-SA 4.0", 7 | "main": "dist/main.js", 8 | "keywords": [ 9 | "bob-plugin", 10 | "openai", 11 | "polisher" 12 | ], 13 | "homepage": "https://github.com/openai-translator/bob-plugin-openai-polisher", 14 | "scripts": { 15 | "build": "rollup --config rollup.config.ts --configPlugin typescript" 16 | }, 17 | "devDependencies": { 18 | "@bob-translate/types": "1.0.2", 19 | "@rollup/plugin-terser": "0.4.4", 20 | "@rollup/plugin-typescript": "11.1.6", 21 | "rollup": "4.20.0", 22 | "rollup-plugin-copy": "3.5.0", 23 | "tslib": "2.6.3", 24 | "typescript": "5.5.4" 25 | }, 26 | "engines": { 27 | "node": ">=20" 28 | }, 29 | "packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e" 30 | } 31 | -------------------------------------------------------------------------------- /scripts/update_appcast.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import hashlib 3 | import json 4 | from pathlib import Path 5 | 6 | def update_appcast(version, desc): 7 | release_file = Path(f'dist/openai-polisher-{version}.bobplugin') 8 | assert release_file.is_file(), 'Release file not exist' 9 | with open(release_file, 'rb') as f: 10 | c = f.read() 11 | file_hash = hashlib.sha256(c).hexdigest() 12 | version_info = { 13 | 'version': version, 14 | 'desc': desc, 15 | 'sha256': file_hash, 16 | 'url': f'https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v{version}/{release_file.name}', 17 | 'minBobVersion': '1.8.0' 18 | } 19 | appcast_file = Path('appcast.json') 20 | if appcast_file.is_file(): 21 | with open(appcast_file, 'r') as f: 22 | appcast = json.load(f) 23 | else: 24 | appcast = dict(identifier='yetone.openai.polisher', versions=[]) 25 | appcast['versions'].insert(0, version_info) 26 | with open(appcast_file, 'w') as f: 27 | json.dump(appcast, f, ensure_ascii=False, indent=2) 28 | 29 | if __name__ == '__main__': 30 | version = sys.argv[1] 31 | desc = sys.argv[2] 32 | update_appcast(version, desc) -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type PolishingMode = "detailed" | "simplicity"; 2 | // https://github.com/openai/openai-node/blob/master/src/resources/chat/completions.ts 3 | 4 | interface ChatCompletionDelta { 5 | role: 'assistant'; 6 | content: string; 7 | /** 8 | * The refusal message generated by the model. 9 | */ 10 | refusal: string | null; 11 | } 12 | 13 | /** 14 | * A chat completion message generated by the model. 15 | */ 16 | export interface ChatCompletionMessage { 17 | /** 18 | * The contents of the message. 19 | */ 20 | content: string | null; 21 | 22 | /** 23 | * The refusal message generated by the model. 24 | */ 25 | refusal: string | null; 26 | 27 | /** 28 | * The role of the author of this message. 29 | */ 30 | role: 'assistant'; 31 | } 32 | 33 | 34 | interface ChatCompletionChoice { 35 | index: number; 36 | delta: ChatCompletionDelta; 37 | /** 38 | * The refusal message generated by the model. 39 | */ 40 | refusal: string | null; 41 | /** 42 | * The reason the model stopped generating tokens. This will be `stop` if the model 43 | * hit a natural stop point or a provided stop sequence, `length` if the maximum 44 | * number of tokens specified in the request was reached, `content_filter` if 45 | * content was omitted due to a flag from our content filters, `tool_calls` if the 46 | * model called a tool, or `function_call` (deprecated) if the model called a 47 | * function. 48 | */ 49 | finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter' | 'function_call'; 50 | 51 | /** 52 | * A chat completion message generated by the model. 53 | */ 54 | message: ChatCompletionMessage; 55 | } 56 | 57 | export interface ChatCompletion { 58 | id: string; 59 | object: string; 60 | created: number; 61 | model: string; 62 | system_fingerprint?: string; 63 | choices: ChatCompletionChoice[]; 64 | } 65 | 66 | /** 67 | * Describes an OpenAI model offering that can be used with the API. 68 | */ 69 | interface Model { 70 | /** 71 | * The model identifier, which can be referenced in the API endpoints. 72 | */ 73 | id: string; 74 | 75 | /** 76 | * The Unix timestamp (in seconds) when the model was created. 77 | */ 78 | created: number; 79 | 80 | /** 81 | * The object type, which is always "model". 82 | */ 83 | object: 'model'; 84 | 85 | /** 86 | * The organization that owns the model. 87 | */ 88 | owned_by: string; 89 | } 90 | 91 | export interface LanguagePrompt { 92 | prompt: string; 93 | detailed: string; 94 | } 95 | 96 | export interface ModelList { 97 | object: string, 98 | data: Model[] 99 | } -------------------------------------------------------------------------------- /src/const.ts: -------------------------------------------------------------------------------- 1 | import { Language } from "./lang"; 2 | import { LanguagePrompt } from "./types"; 3 | 4 | export const HTTP_ERROR_CODES = { 5 | 400: "Bad Request", 6 | 401: "Unauthorized", 7 | 402: "Payment Required", 8 | 403: "Forbidden", 9 | 404: "Not Found", 10 | 405: "Method Not Allowed", 11 | 406: "Not Acceptable", 12 | 407: "Proxy Authentication Required", 13 | 408: "Request Timeout", 14 | 409: "Conflict", 15 | 410: "Gone", 16 | 411: "Length Required", 17 | 412: "Precondition Failed", 18 | 413: "Payload Too Large", 19 | 414: "URI Too Long", 20 | 415: "Unsupported Media Type", 21 | 416: "Range Not Satisfiable", 22 | 417: "Expectation Failed", 23 | 418: "I'm a teapot", 24 | 421: "Misdirected Request", 25 | 422: "Unprocessable Entity", 26 | 423: "Locked", 27 | 424: "Failed Dependency", 28 | 425: "Too Early", 29 | 426: "Upgrade Required", 30 | 428: "Precondition Required", 31 | 429: "请求过于频繁,请慢一点。OpenAI 对您在 API 上的请求实施速率限制。或是您的 API credits 已超支,需要充值。好消息是您仍然可以使用官方的 Web 端聊天页面", 32 | 431: "Request Header Fields Too Large", 33 | 451: "Unavailable For Legal Reasons", 34 | 500: "Internal Server Error", 35 | 501: "Not Implemented", 36 | 502: "Bad Gateway", 37 | 503: "Service Unavailable", 38 | 504: "Gateway Timeout", 39 | 505: "HTTP Version Not Supported", 40 | 506: "Variant Also Negotiates", 41 | 507: "Insufficient Storage", 42 | 508: "Loop Detected", 43 | 510: "Not Extended", 44 | 511: "Network Authentication Required" 45 | } as const; 46 | 47 | export type HttpErrorCode = keyof typeof HTTP_ERROR_CODES; 48 | 49 | export const DEFAULT_PROMPT = { 50 | simplicity: "Revise the following sentences to make them more clear, concise, and coherent.", 51 | detailed: ". Please note that you need to list the changes and briefly explain why", 52 | } as const; 53 | 54 | export const languageMapping: Partial> = { 55 | "zh-Hant": { 56 | prompt: "潤色此句", 57 | detailed: "。請列出修改項目,並簡述修改原因", 58 | }, 59 | "zh-Hans": { 60 | prompt: "润色此句", 61 | detailed: "。请注意要列出更改以及简要解释一下为什么这么修改", 62 | }, 63 | "ja": { 64 | prompt: "この文章を装飾する", 65 | detailed: "。変更点をリストアップし、なぜそのように変更したかを簡単に説明することに注意してください", 66 | }, 67 | "ru": { 68 | prompt: "Переформулируйте следующие предложения, чтобы они стали более ясными, краткими и связными", 69 | detailed: ". Пожалуйста, обратите внимание на необходимость перечисления изменений и краткого объяснения причин таких изменений", 70 | }, 71 | "wyw": { 72 | prompt: "润色此句古文", 73 | detailed: "。请注意要列出更改以及简要解释一下为什么这么修改", 74 | }, 75 | "yue": { 76 | prompt: "潤色呢句粵語", 77 | detailed: "。記住要列出修改嘅內容同簡單解釋下點解要做呢啲更改", 78 | }, 79 | }; 80 | -------------------------------------------------------------------------------- /src/lang.ts: -------------------------------------------------------------------------------- 1 | export const supportLanguageList = [ 2 | ["auto", "auto"], 3 | ["zh-Hans", "zh-CN"], 4 | ["zh-Hant", "zh-TW"], 5 | ["en", "en"], 6 | ["yue", "粤语"], 7 | ["wyw", "古文"], 8 | ["en", "en"], 9 | ["ja", "ja"], 10 | ["ko", "ko"], 11 | ["fr", "fr"], 12 | ["de", "de"], 13 | ["es", "es"], 14 | ["it", "it"], 15 | ["ru", "ru"], 16 | ["pt", "pt"], 17 | ["nl", "nl"], 18 | ["pl", "pl"], 19 | ["ar", "ar"], 20 | ["af", "af"], 21 | ["am", "am"], 22 | ["az", "az"], 23 | ["be", "be"], 24 | ["bg", "bg"], 25 | ["bn", "bn"], 26 | ["bs", "bs"], 27 | ["ca", "ca"], 28 | ["ceb", "ceb"], 29 | ["co", "co"], 30 | ["cs", "cs"], 31 | ["cy", "cy"], 32 | ["da", "da"], 33 | ["el", "el"], 34 | ["eo", "eo"], 35 | ["et", "et"], 36 | ["eu", "eu"], 37 | ["fa", "fa"], 38 | ["fi", "fi"], 39 | ["fj", "fj"], 40 | ["fy", "fy"], 41 | ["ga", "ga"], 42 | ["gd", "gd"], 43 | ["gl", "gl"], 44 | ["gu", "gu"], 45 | ["ha", "ha"], 46 | ["haw", "haw"], 47 | ["he", "he"], 48 | ["hi", "hi"], 49 | ["hmn", "hmn"], 50 | ["hr", "hr"], 51 | ["ht", "ht"], 52 | ["hu", "hu"], 53 | ["hy", "hy"], 54 | ["id", "id"], 55 | ["ig", "ig"], 56 | ["is", "is"], 57 | ["jw", "jw"], 58 | ["ka", "ka"], 59 | ["kk", "kk"], 60 | ["km", "km"], 61 | ["kn", "kn"], 62 | ["ku", "ku"], 63 | ["ky", "ky"], 64 | ["la", "lo"], 65 | ["lb", "lb"], 66 | ["lo", "lo"], 67 | ["lt", "lt"], 68 | ["lv", "lv"], 69 | ["mg", "mg"], 70 | ["mi", "mi"], 71 | ["mk", "mk"], 72 | ["ml", "ml"], 73 | ["mn", "mn"], 74 | ["mr", "mr"], 75 | ["ms", "ms"], 76 | ["mt", "mt"], 77 | ["my", "my"], 78 | ["ne", "ne"], 79 | ["no", "no"], 80 | ["ny", "ny"], 81 | ["or", "or"], 82 | ["pa", "pa"], 83 | ["ps", "ps"], 84 | ["ro", "ro"], 85 | ["rw", "rw"], 86 | ["si", "si"], 87 | ["sk", "sk"], 88 | ["sl", "sl"], 89 | ["sm", "sm"], 90 | ["sn", "sn"], 91 | ["so", "so"], 92 | ["sq", "sq"], 93 | ["sr", "sr"], 94 | ["sr-Cyrl", "sr"], 95 | ["sr-Latn", "sr"], 96 | ["st", "st"], 97 | ["su", "su"], 98 | ["sv", "sv"], 99 | ["sw", "sw"], 100 | ["ta", "ta"], 101 | ["te", "te"], 102 | ["tg", "tg"], 103 | ["th", "th"], 104 | ["tk", "tk"], 105 | ["tl", "tl"], 106 | ["tr", "tr"], 107 | ["tt", "tt"], 108 | ["ug", "ug"], 109 | ["uk", "uk"], 110 | ["ur", "ur"], 111 | ["uz", "uz"], 112 | ["vi", "vi"], 113 | ["xh", "xh"], 114 | ["yi", "yi"], 115 | ["yo", "yo"], 116 | ["zu", "zu"], 117 | ] as const; 118 | 119 | export type Language = typeof supportLanguageList[number][0]; 120 | 121 | export const langMap = new Map(supportLanguageList.map(([key, value]) => [key, value])); 122 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { HttpResponse, ServiceError, TextTranslateQuery, ValidationCompletion } from "@bob-translate/types"; 2 | import { HTTP_ERROR_CODES } from "./const"; 3 | 4 | function buildHeader(isAzureServiceProvider: boolean, apiKey: string): { 5 | "Content-Type": string; 6 | "api-key"?: string; 7 | Authorization?: string; 8 | } { 9 | return { 10 | "Content-Type": "application/json", 11 | [isAzureServiceProvider ? "api-key" : "Authorization"]: 12 | isAzureServiceProvider ? apiKey : `Bearer ${apiKey}`, 13 | }; 14 | } 15 | 16 | function ensureHttpsAndNoTrailingSlash(url: string): string { 17 | const hasProtocol = /^[a-z]+:\/\//i.test(url); 18 | const modifiedUrl = hasProtocol ? url : "https://" + url; 19 | 20 | return modifiedUrl.endsWith("/") ? modifiedUrl.slice(0, -1) : modifiedUrl; 21 | } 22 | 23 | function getApiKey(apiKeys: string): string { 24 | const trimmedApiKeys = apiKeys.endsWith(",") 25 | ? apiKeys.slice(0, -1) 26 | : apiKeys; 27 | const apiKeySelection = trimmedApiKeys.split(",").map((key) => key.trim()); 28 | return apiKeySelection[Math.floor(Math.random() * apiKeySelection.length)]; 29 | } 30 | 31 | function handleGeneralError( 32 | query: TextTranslateQuery, 33 | error: ServiceError | HttpResponse 34 | ) { 35 | if ("response" in error) { 36 | // Handle HTTP response error 37 | const { statusCode } = error.response; 38 | const reason = statusCode >= 400 && statusCode < 500 ? "param" : "api"; 39 | query.onCompletion({ 40 | error: { 41 | type: reason, 42 | message: `接口响应错误 - ${HTTP_ERROR_CODES[statusCode]}`, 43 | addition: `${JSON.stringify(error)}`, 44 | }, 45 | }); 46 | } else { 47 | // Handle general error 48 | query.onCompletion({ 49 | error: { 50 | ...error, 51 | type: error.type || "unknown", 52 | message: error.message || "Unknown error", 53 | }, 54 | }); 55 | } 56 | } 57 | 58 | function handleValidateError( 59 | completion: ValidationCompletion, 60 | error: ServiceError 61 | ) { 62 | completion({ 63 | result: false, 64 | error: { 65 | ...error, 66 | type: error.type || "unknown", 67 | message: error.message || "Unknown error", 68 | }, 69 | }); 70 | } 71 | 72 | function replacePromptKeywords( 73 | prompt: string, 74 | query: TextTranslateQuery 75 | ): string { 76 | if (!prompt) return prompt; 77 | return prompt 78 | .replace("$text", query.text) 79 | .replace("$sourceLang", query.detectFrom) 80 | .replace("$targetLang", query.detectTo); 81 | } 82 | 83 | export { 84 | buildHeader, 85 | ensureHttpsAndNoTrailingSlash, 86 | getApiKey, 87 | handleGeneralError, 88 | handleValidateError, 89 | replacePromptKeywords, 90 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 简体中文 | English 3 |

4 | 5 |
6 |

OpenAI Polisher Bob Plugin

7 |

8 | 9 | release 10 | 11 | 12 | GitHub Repo stars 13 | 14 | 15 | GitHub Repo stars 16 | 17 | 18 | GitHub Repo stars 19 | 20 |

21 |
22 | 23 | 24 | > **Note** 25 | > 26 | > 重要更新:非 macOS 用户可以使用我开发的基于 ChatGPT API 的划词翻译浏览器插件(支持语法润色) [openai-translator](https://github.com/yetone/openai-translator) 以解燃眉之急。 27 | 28 | ## 演示 29 | 30 | ![demo](https://user-images.githubusercontent.com/1206493/222710761-bbd5ce10-2b12-42c0-abfa-5a3152157cb2.gif) 31 | 32 | ## 简介 33 | 34 | ChatGPT 向我们展示了 GPT 模型的伟大之处,所以我使用 ChatGPT 的 API 实现了这个用来给语言润色和语法纠错的 Bob 插件,效果拔群!完美替代 Grammarly! 35 | 36 | ## 使用方法 37 | 38 | 1. 安装 [Bob](https://bobtranslate.com/guide/#%E5%AE%89%E8%A3%85),一款 macOS 平台的翻译和 OCR 软件;[openai-translator.bobplugin](https://github.com/yetone/bob-plugin-openai-translator/releases/latest) >= **1.0.0** 以后默认开启流式输出,需要 Bob 版本 >= **1.8.0** 39 | 40 | 2. 下载此插件: [openai-polisher.bobplugin](https://github.com/openai-translator/bob-plugin-openai-polisher/releases) 41 | 42 | 3. 安装此插件: 43 | ![安装步骤](https://user-images.githubusercontent.com/1206493/222712959-4a4b27e2-b129-408a-a8af-24a3a89df2dd.gif) 44 | 45 | 4. 去 [OpenAI](https://platform.openai.com/account/api-keys) 获取你的 API KEY 46 | 47 | 5. 把 API KEY 填入 Bob 偏好设置 > 服务 > 此插件配置界面的 API KEY 的输入框中 48 | ![设置步骤](https://user-images.githubusercontent.com/1206493/222712982-5c5598b0-8560-422f-837f-3ffd08a39f81.gif) 49 | 50 | 6. 安装 [PopClip](https://bobtranslate.com/guide/integration/popclip.html) 实现划词后鼠标附近出现悬浮图标 51 | ![PopClip](https://user-images.githubusercontent.com/1206493/219933584-d0c2b6cf-8fa0-40a6-858f-8f4bf05f38ef.gif) 52 | 53 | ## 感谢 54 | 55 | 我这只是个小小的 Bob 插件,强大的是 Bob 本身,向它的开发者 [ripperhe](https://github.com/ripperhe) 致敬! 56 | 57 |
58 | 59 | [![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source") 60 | 61 |
62 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Bump Version and Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Tag version (e.g., 1.0.0 or v1.0.0)' 8 | required: true 9 | message: 10 | description: 'Tag message' 11 | required: true 12 | 13 | jobs: 14 | release: 15 | runs-on: ubuntu-latest 16 | 17 | permissions: 18 | contents: write 19 | packages: write 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Set up Git 27 | run: | 28 | git config --global user.name 'GitHub Actions[bot]' 29 | git config --global user.email 'github-actions[bot]@users.noreply.github.com' 30 | 31 | - name: Determine tag version 32 | id: determine_version 33 | run: | 34 | VERSION="${{ github.event.inputs.version }}" 35 | if [[ $VERSION != v* ]]; then 36 | VERSION="v$VERSION" 37 | fi 38 | VERSION_NUMBER="${VERSION#v}" 39 | echo "VERSION=$VERSION" >> $GITHUB_ENV 40 | echo "VERSION_NUMBER=$VERSION_NUMBER" >> $GITHUB_ENV 41 | 42 | - name: Update info.json 43 | run: | 44 | OLD_VERSION=$(grep '"version":' public/info.json | awk -F\" '{print $4}') 45 | sed -i "s/$OLD_VERSION/${{ env.VERSION_NUMBER }}/" public/info.json 46 | 47 | - uses: pnpm/action-setup@v4 48 | - uses: actions/setup-node@v4 49 | with: 50 | node-version-file: .node-version 51 | cache: pnpm 52 | 53 | - run: pnpm install 54 | - run: pnpm run build 55 | 56 | - name: Package plugin 57 | run: | 58 | zip -j -r dist/openai-polisher-${{ env.VERSION_NUMBER }}.bobplugin ./dist/* 59 | echo "Packaged file: dist/openai-polisher-${{ env.VERSION_NUMBER }}.bobplugin" 60 | 61 | - name: Update appcast.json 62 | env: 63 | VERSION_NUMBER: ${{ env.VERSION_NUMBER }} 64 | MESSAGE: ${{ github.event.inputs.message }} 65 | run: | 66 | python3 scripts/update_appcast.py "$VERSION_NUMBER" "$MESSAGE" 67 | 68 | - name: Commit files 69 | run: | 70 | git commit -a -m 'chore: update appcast.json and info.json' 71 | 72 | - name: Create tag 73 | env: 74 | VERSION: ${{ env.VERSION }} 75 | MESSAGE: ${{ github.event.inputs.message }} 76 | run: | 77 | git tag -a "$VERSION" -m "$MESSAGE" 78 | 79 | - name: Push changes 80 | uses: ad-m/github-push-action@master 81 | with: 82 | github_token: ${{ github.token }} 83 | tags: true 84 | 85 | - name: Upload binaries to release 86 | uses: svenstaro/upload-release-action@v2 87 | with: 88 | release_name: ${{ env.VERSION }} 89 | repo_token: ${{ secrets.GITHUB_TOKEN }} 90 | file: dist/openai-polisher-${{ env.VERSION_NUMBER }}.bobplugin 91 | asset_name: openai-polisher-${{ env.VERSION_NUMBER }}.bobplugin 92 | tag: ${{ env.VERSION }} 93 | overwrite: true 94 | body: ${{ github.event.inputs.message }} -------------------------------------------------------------------------------- /docs/README_EN.md: -------------------------------------------------------------------------------- 1 |

2 | 简体中文 | English 3 |

4 | 5 |
6 |

OpenAI Polisher Bob Plugin

7 |

8 | 9 | release 10 | 11 | 12 | GitHub Repo stars 13 | 14 | 15 | GitHub Repo stars 16 | 17 | 18 | GitHub Repo stars 19 | 20 |

21 |
22 | 23 | > **Note** 24 | > 25 | > Important update: Non-macOS users can use my browser extension based on ChatGPT API for language polishing [openai-translator](https://github.com/openai-translator/openai-translator) to solve urgent needs. 26 | 27 | 28 | ## Demonstration 29 | 30 | ![demo](https://user-images.githubusercontent.com/1206493/222710761-bbd5ce10-2b12-42c0-abfa-5a3152157cb2.gif) 31 | 32 | ## Introduction 33 | 34 | ChatGPT has shown us the greatness of the GPT model, so I used ChatGPT's API to create this Bob plugin for language refinement and grammar correction. The effect is outstanding! It's a perfect replacement for Grammarly! 35 | 36 | ## Usage 37 | 38 | 1. Install [Bob](https://bobtranslate.com/guide/#%E5%AE%89%E8%A3%85), a translation and OCR software for macOS platform; [openai-translator.bobplugin](https://github.com/yetone/bob-plugin-openai-translator/releases/latest) >= **1.0.0** enables streaming output by default after installation, requiring Bob version >= **1.8.0**. 39 | 40 | 2. Download this plugin: [openai-polisher.bobplugin](https://github.com/openai-translator/bob-plugin-openai-polisher/releases/latest) 41 | 42 | 3. Install this plugin: 43 | ![Installation Steps](https://user-images.githubusercontent.com/1206493/222712959-4a4b27e2-b129-408a-a8af-24a3a89df2dd.gif) 44 | 45 | 4. Get your API KEY from [OpenAI](https://platform.openai.com/account/api-keys) 46 | 47 | 5. Enter the API KEY in Bob Preferences > Services > This plugin configuration interface's API KEY input box: 48 | ![Settings Steps](https://user-images.githubusercontent.com/1206493/222712982-5c5598b0-8560-422f-837f-3ffd08a39f81.gif) 49 | 50 | 6. Install [PopClip](https://bobtranslate.com/guide/integration/popclip.html) for highlighted text mouse proximity floating icon: 51 | ![PopClip](https://user-images.githubusercontent.com/1206493/219933584-d0c2b6cf-8fa0-40a6-858f-8f4bf05f38ef.gif) 52 | 53 | ## Thanks 54 | 55 | I'm just a small Bob plugin, and the powerful part is Bob itself. I pay tribute to its developer [ripperhe](https://github.com/ripperhe)! 56 | -------------------------------------------------------------------------------- /appcast.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "xyz.yetone.openai.polisher", 3 | "versions": [ 4 | { 5 | "version": "3.0.1", 6 | "desc": "Update model options", 7 | "sha256": "d406228bbfb8b64fb42d1a29407f14ad60c202a4efb089d231f3d3f128f28d2f", 8 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v3.0.1/openai-polisher-3.0.1.bobplugin", 9 | "minBobVersion": "1.8.0" 10 | }, 11 | { 12 | "version": "3.0.0", 13 | "desc": "Refactor OpenAI Polisher Bob Plugin to improve performance and maintainability", 14 | "sha256": "ba3fe4a954e57c4754e09bcf17adba40de205bfa35b4ac341277c231fca4b336", 15 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v3.0.0/openai-polisher-3.0.0.bobplugin", 16 | "minBobVersion": "1.8.0" 17 | }, 18 | { 19 | "version": "2.0.0", 20 | "desc": "Support for custom models, drop of support for `v1/completions`", 21 | "sha256": "520985c6b10de2ae55651ddc94f5123fe9655ceb3962a276fd814ebad2facc2f", 22 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v2.0.0/openai-polisher-2.0.0.bobplugin", 23 | "minBobVersion": "1.8.0" 24 | }, 25 | { 26 | "version": "1.2.2", 27 | "desc": "优化 HTTP Error 429 的错误信息", 28 | "sha256": "25c10ebdd3133c37407740511f096a171d5219ad0d35c84d5bb00bf81bc205aa", 29 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v1.2.2/openai-polisher-1.2.2.bobplugin", 30 | "minBobVersion": "1.8.0" 31 | }, 32 | { 33 | "version": "1.2.1", 34 | "desc": "Supports the latest GPT-4 and 3.5 Turbo models", 35 | "sha256": "efd43255e1014e92405fb1521c4d61fea4d50f8fb6a00a292790a49b1102420a", 36 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v1.2.1/openai-polisher-1.2.1.bobplugin", 37 | "minBobVersion": "1.8.0" 38 | }, 39 | { 40 | "version": "1.2.0", 41 | "desc": "Fix invalid custom system prompt", 42 | "sha256": "26d21851dcf5af88e44768d524874932c099238f91ae4114ddf78f80caf090ad", 43 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v1.2.0/openai-polisher-1.2.0.bobplugin", 44 | "minBobVersion": "1.8.0" 45 | }, 46 | { 47 | "version": "1.1.0", 48 | "desc": "Handle fragmented stream data from OpenAI API", 49 | "sha256": "2cefc695cb1ab9f8b2b93440d105a6f0fcbadc0198a0d785cd1d9920bd3f3fbf", 50 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v1.1.0/openai-polisher-1.1.0.bobplugin", 51 | "minBobVersion": "1.8.0" 52 | }, 53 | { 54 | "version": "1.0.0", 55 | "desc": "Supports streaming output, requires Bob version >= 1.8.0", 56 | "sha256": "e540344dfd152d40779455301cc38750fdcf8db2e3d3e2f7ef89329d607be3c2", 57 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v1.0.0/openai-polisher-1.0.0.bobplugin", 58 | "minBobVersion": "1.8.0" 59 | }, 60 | { 61 | "version": "0.2.0", 62 | "desc": "fix(ci): add permissions", 63 | "sha256": "1138da2e03ab8209722cc8bb4c7ecddb7aedc3acd77e7b3ca573400ad801e1a8", 64 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v0.2.0/openai-polisher-0.2.0.bobplugin", 65 | "minBobVersion": "0.5.0" 66 | }, 67 | { 68 | "version": "0.1.0", 69 | "desc": "Support custom prompt", 70 | "sha256": "9dd8dd4d52f24df0bbf946e48f9e57cc417ef18be4bc37639fc3308aa5e7b98b", 71 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v0.1.0/openai-polisher-0.1.0.bobplugin", 72 | "minBobVersion": "0.5.0" 73 | }, 74 | { 75 | "version": "0.0.3", 76 | "desc": "Add an option to display the reason for polishing or not.", 77 | "sha256": "31cdc98bcd9f5bf473c67f5bc2e60da6d3f984b0ac97fdfebce981eb8504981b", 78 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v0.0.3/openai-polisher-0.0.3.bobplugin", 79 | "minBobVersion": "0.5.0" 80 | }, 81 | { 82 | "version": "0.0.2", 83 | "desc": "Support the polishing of Japanese and other languages", 84 | "sha256": "1945ca77329079b550571b329461ae50933f8da70aa9d1fbe176dba288548896", 85 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v0.0.2/openai-polisher-0.0.2.bobplugin", 86 | "minBobVersion": "0.5.0" 87 | }, 88 | { 89 | "version": "0.0.1", 90 | "desc": "release v0.0.1!", 91 | "sha256": "0b3f7dc3f8bfcb08c090d0abd3809421172ac7e04a95c602f7bae2fa4d37160b", 92 | "url": "https://github.com/openai-translator/bob-plugin-openai-polisher/releases/download/v0.0.1/openai-polisher-0.0.1.bobplugin", 93 | "minBobVersion": "0.5.0" 94 | } 95 | ] 96 | } -------------------------------------------------------------------------------- /public/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "xyz.yetone.openai.polisher", 3 | "version": "3.0.1", 4 | "category": "translate", 5 | "name": "OpenAI Polisher", 6 | "summary": "GPT powered polisher", 7 | "icon": "", 8 | "author": "yetone ", 9 | "homepage": "https://github.com/openai-translator/bob-plugin-openai-polisher", 10 | "appcast": "https://raw.githubusercontent.com/openai-translator/bob-plugin-openai-polisher/main/appcast.json", 11 | "minBobVersion": "1.8.0", 12 | "options": [ 13 | { 14 | "identifier": "apiUrl", 15 | "type": "text", 16 | "title": "API URL", 17 | "defaultValue": "https://api.openai.com", 18 | "desc": "可选项。如果您的网络环境需要代理才能访问 OpenAI API, 可在这里修改为反代 API 的地址", 19 | "textConfig": { 20 | "type": "visible", 21 | "placeholderText": "https://api.openai.com" 22 | } 23 | }, 24 | { 25 | "identifier": "deploymentName", 26 | "type": "text", 27 | "title": "Dep. Name", 28 | "desc": "可选项。此值为在部署 Azure 模型时为部署选择的自定义名称,可在 Azure 门户中的 “资源管理”>“部署” 下查看", 29 | "textConfig": { 30 | "type": "visible" 31 | } 32 | }, 33 | { 34 | "identifier": "apiVersion", 35 | "type": "text", 36 | "title": "API Version", 37 | "defaultValue": "2023-03-15-preview", 38 | "desc": "可选项。此值为在使用 Azure 模型时采用的 Chat completions API 版本,不支持 2023-03-15-preview 之前的版本", 39 | "textConfig": { 40 | "type": "visible", 41 | "placeholderText": "2023-03-15-preview" 42 | } 43 | }, 44 | { 45 | "identifier": "apiKeys", 46 | "type": "text", 47 | "title": "API KEY", 48 | "desc": "必填项。可以用英文逗号分割多个 API KEY 以实现额度加倍及负载均衡", 49 | "textConfig": { 50 | "type": "secure", 51 | "height": "40", 52 | "placeholderText": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 53 | } 54 | }, 55 | { 56 | "identifier": "model", 57 | "type": "menu", 58 | "title": "模型", 59 | "defaultValue": "gpt-4.1-nano", 60 | "menuValues": [ 61 | { 62 | "title": "Custom", 63 | "value": "custom" 64 | }, 65 | { 66 | "title": "GPT-4.1", 67 | "value": "gpt-4.1" 68 | }, 69 | { 70 | "title": "GPT-4.1 mini", 71 | "value": "gpt-4.1-mini" 72 | }, 73 | { 74 | "title": "GPT-4.1 nano", 75 | "value": "gpt-4.1-nano" 76 | }, 77 | { 78 | "title": "GPT-4o", 79 | "value": "gpt-4o" 80 | }, 81 | { 82 | "title": "o4-mini", 83 | "value": "o4-mini" 84 | } 85 | ] 86 | }, 87 | { 88 | "identifier": "customModel", 89 | "type": "text", 90 | "title": "自定义模型", 91 | "desc": "可选项。当 Model 选择为 custom 时,此项为必填项。请填写有效的模型名称", 92 | "textConfig": { 93 | "type": "visible", 94 | "placeholderText": "gpt-3.5-turbo" 95 | } 96 | }, 97 | { 98 | "identifier": "customSystemPrompt", 99 | "type": "text", 100 | "title": "系统指令", 101 | "defaultValue": "Revise the following sentences to make them more clear, concise, and coherent.", 102 | "desc": "可选项。自定义 System Prompt,填写则会覆盖默认的 System Prompt。自定义 Prompt可使用以下变量:\n\n`$text` - 需要润色的文本,即翻译窗口输入框内的文本 `$sourceLang` - 原文语言,即翻译窗口输入框内文本的语言,比如「简体中文」\n\n`$targetLang` - 目标语言,可以在翻译窗口中手动选择或自动检测,比如「English」", 103 | "textConfig": { 104 | "type": "visible", 105 | "height": "100", 106 | "placeholderText": "Revise the following sentences to make them more clear, concise, and coherent.", 107 | "keyWords": [ 108 | "$text", 109 | "$sourceLang", 110 | "$targetLang" 111 | ] 112 | } 113 | }, 114 | { 115 | "identifier": "customUserPrompt", 116 | "type": "text", 117 | "title": "用户指令", 118 | "defaultValue": "$text", 119 | "desc": "可选项。自定义 User Prompt,填写则会覆盖默认的 User Prompt,默认值为`$text`(即翻译窗口输入框内的文本)\n\n自定义 Prompt 中可以使用与系统指令中相同的变量", 120 | "textConfig": { 121 | "type": "visible", 122 | "height": "100", 123 | "placeholderText": "$text", 124 | "keyWords": [ 125 | "$text", 126 | "$sourceLang", 127 | "$targetLang" 128 | ] 129 | } 130 | }, 131 | { 132 | "identifier": "stream", 133 | "type": "menu", 134 | "title": "流式输出", 135 | "defaultValue": "1", 136 | "menuValues": [ 137 | { 138 | "title": "Enable", 139 | "value": "1" 140 | }, 141 | { 142 | "title": "Disable", 143 | "value": "0" 144 | } 145 | ] 146 | }, 147 | { 148 | "identifier": "polishingMode", 149 | "type": "menu", 150 | "title": "润色模式", 151 | "defaultValue": "simplicity", 152 | "menuValues": [ 153 | { 154 | "title": "简洁(只输出润色后的文本)", 155 | "value": "simplicity" 156 | }, 157 | { 158 | "title": "详尽(附加解释修改原因)", 159 | "value": "detailed" 160 | } 161 | ] 162 | } 163 | ] 164 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | HttpResponse, 3 | PluginValidate, 4 | ServiceError, 5 | TextTranslate, 6 | TextTranslateQuery 7 | } from "@bob-translate/types"; 8 | import { DEFAULT_PROMPT, languageMapping } from "./const"; 9 | import { langMap, supportLanguageList } from "./lang"; 10 | import type { 11 | ChatCompletion, 12 | ModelList, 13 | PolishingMode, 14 | } from "./types"; 15 | import { 16 | buildHeader, 17 | ensureHttpsAndNoTrailingSlash, 18 | getApiKey, 19 | handleGeneralError, 20 | handleValidateError, 21 | replacePromptKeywords 22 | } from "./utils"; 23 | 24 | const pluginTimeoutInterval = () => 60; 25 | 26 | const pluginValidate: PluginValidate = (completion) => { 27 | const { apiKeys, apiUrl, deploymentName } = $option; 28 | if (!apiKeys) { 29 | handleValidateError(completion, { 30 | type: "secretKey", 31 | message: "配置错误 - 请确保您在插件配置中填入了正确的 API Keys", 32 | addition: "请在插件配置中填写正确的 API Keys", 33 | troubleshootingLink: "https://bobtranslate.com/service/translate/openai.html" 34 | }); 35 | return; 36 | } 37 | 38 | const apiKey = getApiKey(apiKeys); 39 | const baseUrl = ensureHttpsAndNoTrailingSlash(apiUrl || "https://api.openai.com"); 40 | let apiUrlPath = baseUrl.includes("gateway.ai.cloudflare.com") ? "/models" : "/v1/models"; 41 | 42 | const isAzureServiceProvider = apiUrl.includes("openai.azure.com"); 43 | if (isAzureServiceProvider) { 44 | if (!deploymentName) { 45 | handleValidateError(completion, { 46 | type: "secretKey", 47 | message: "配置错误 - 未填写 Deployment Name", 48 | addition: "请在插件配置中填写 Deployment Name", 49 | troubleshootingLink: "https://bobtranslate.com/service/translate/azureopenai.html" 50 | }); 51 | return; 52 | } 53 | apiUrlPath = `/openai/deployments/${deploymentName}/chat/completions?api-version=2023-05-15`; 54 | } 55 | 56 | const header = buildHeader(isAzureServiceProvider, apiKey); 57 | (async () => { 58 | if (isAzureServiceProvider) { 59 | $http.request({ 60 | method: "POST", 61 | url: baseUrl + apiUrlPath, 62 | header: header, 63 | body: { 64 | "messages": [{ 65 | "content": "You are a helpful assistant.", 66 | "role": "system", 67 | }, { 68 | "content": "Test connection.", 69 | "role": "user", 70 | }], 71 | max_tokens: 5 72 | }, 73 | handler: function (resp) { 74 | const data = resp.data as { 75 | error: string; 76 | } 77 | if (data.error) { 78 | const { statusCode } = resp.response; 79 | const reason = (statusCode >= 400 && statusCode < 500) ? "param" : "api"; 80 | handleValidateError(completion, { 81 | type: reason, 82 | message: data.error, 83 | troubleshootingLink: "https://bobtranslate.com/service/translate/azureopenai.html" 84 | }); 85 | return; 86 | } 87 | if ((resp.data as ChatCompletion).choices.length > 0) { 88 | completion({ 89 | result: true, 90 | }) 91 | } 92 | } 93 | }); 94 | } else { 95 | $http.request({ 96 | method: "GET", 97 | url: baseUrl + apiUrlPath, 98 | header: header, 99 | handler: function (resp) { 100 | const data = resp.data as { 101 | error: string; 102 | } 103 | if (data.error) { 104 | const { statusCode } = resp.response; 105 | const reason = (statusCode >= 400 && statusCode < 500) ? "param" : "api"; 106 | handleValidateError(completion, { 107 | type: reason, 108 | message: data.error, 109 | troubleshootingLink: "https://bobtranslate.com/service/translate/openai.html" 110 | }); 111 | return; 112 | } 113 | const modelList = resp.data as ModelList; 114 | if (modelList.data?.length > 0) { 115 | completion({ 116 | result: true, 117 | }) 118 | } 119 | } 120 | }); 121 | } 122 | })().catch((error) => { 123 | handleValidateError(completion, error); 124 | }); 125 | } 126 | 127 | function supportLanguages() { 128 | return supportLanguageList.map(([standardLang]) => standardLang); 129 | } 130 | 131 | const isServiceError = (error: unknown): error is ServiceError => { 132 | return ( 133 | typeof error === 'object' && 134 | error !== null && 135 | 'message' in error && 136 | typeof (error as ServiceError).message === 'string' 137 | ); 138 | } 139 | 140 | const generateSystemPrompt = ( 141 | basePrompt: string | null, 142 | polishingMode: PolishingMode, 143 | query: TextTranslateQuery 144 | ): string => { 145 | const isDetailedPolishingMode = polishingMode === "detailed"; 146 | 147 | const promptInfo = languageMapping[query.detectFrom] || { 148 | prompt: DEFAULT_PROMPT.simplicity, 149 | detailed: DEFAULT_PROMPT.detailed, 150 | }; 151 | 152 | let systemPrompt = basePrompt || promptInfo.prompt; 153 | if (isDetailedPolishingMode) { 154 | systemPrompt += promptInfo.detailed; 155 | } 156 | 157 | return systemPrompt; 158 | } 159 | 160 | const buildRequestBody = ( 161 | model: string, 162 | query: TextTranslateQuery 163 | ) => { 164 | const { customSystemPrompt, customUserPrompt, polishingMode } = $option; 165 | 166 | const systemPrompt = generateSystemPrompt( 167 | replacePromptKeywords(customSystemPrompt, query), 168 | polishingMode as PolishingMode, 169 | query 170 | ); 171 | 172 | const userPrompt = customUserPrompt 173 | ? `${replacePromptKeywords(customUserPrompt, query)}:\n\n"${query.text}"` 174 | : query.text; 175 | 176 | const standardBody = { 177 | model: model, 178 | temperature: 0.2, 179 | max_tokens: 1000, 180 | top_p: 1, 181 | frequency_penalty: 1, 182 | presence_penalty: 1, 183 | }; 184 | 185 | return { 186 | ...standardBody, 187 | model: model, 188 | messages: [ 189 | { 190 | role: "system", 191 | content: systemPrompt, 192 | }, 193 | { 194 | role: "user", 195 | content: userPrompt, 196 | }, 197 | ], 198 | }; 199 | } 200 | 201 | const handleStreamResponse = ( 202 | query: TextTranslateQuery, 203 | targetText: string, 204 | textFromResponse: string 205 | ) => { 206 | if (textFromResponse !== '[DONE]') { 207 | try { 208 | const dataObj = JSON.parse(textFromResponse); 209 | // https://github.com/openai/openai-node/blob/master/src/resources/chat/completions#L190 210 | const { choices } = dataObj; 211 | const delta = choices[0]?.delta?.content; 212 | if (delta) { 213 | targetText += delta; 214 | query.onStream({ 215 | result: { 216 | from: query.detectFrom, 217 | to: query.detectTo, 218 | toParagraphs: [targetText], 219 | }, 220 | }); 221 | } 222 | } catch (error) { 223 | if (isServiceError(error)) { 224 | handleGeneralError(query, { 225 | type: error.type || 'param', 226 | message: error.message || 'Failed to parse JSON', 227 | addition: error.addition, 228 | }); 229 | } else { 230 | handleGeneralError(query, { 231 | type: 'param', 232 | message: 'An unknown error occurred', 233 | }); 234 | } 235 | } 236 | } 237 | return targetText; 238 | } 239 | 240 | const handleGeneralResponse = (query: TextTranslateQuery, result: HttpResponse) => { 241 | const { choices } = result.data as ChatCompletion; 242 | 243 | if (!choices || choices.length === 0) { 244 | handleGeneralError(query, { 245 | type: "api", 246 | message: "接口未返回结果", 247 | addition: JSON.stringify(result), 248 | }); 249 | return; 250 | } 251 | 252 | let targetText = choices[0].message.content?.trim(); 253 | 254 | // 使用正则表达式删除字符串开头和结尾的特殊字符 255 | targetText = targetText?.replace(/^(『|「|"|“)|(』|」|"|”)$/g, ""); 256 | 257 | // 判断并删除字符串末尾的 `" =>` 258 | if (targetText?.endsWith('" =>')) { 259 | targetText = targetText.slice(0, -4); 260 | } 261 | 262 | query.onCompletion({ 263 | result: { 264 | from: query.detectFrom, 265 | to: query.detectTo, 266 | toParagraphs: targetText!.split("\n"), 267 | }, 268 | }); 269 | } 270 | 271 | const translate: TextTranslate = (query) => { 272 | if (!langMap.get(query.detectTo)) { 273 | handleGeneralError(query, { 274 | type: "unsupportedLanguage", 275 | message: "不支持该语种", 276 | addition: "不支持该语种", 277 | }); 278 | } 279 | 280 | const { 281 | apiKeys, 282 | apiUrl, 283 | apiVersion, 284 | customModel, 285 | deploymentName, 286 | model, 287 | stream, 288 | } = $option; 289 | 290 | const isCustomModelRequired = model === "custom"; 291 | if (isCustomModelRequired && !customModel) { 292 | handleGeneralError(query, { 293 | type: "param", 294 | message: "配置错误 - 请确保您在插件配置中填入了正确的自定义模型名称", 295 | addition: "请在插件配置中填写自定义模型名称", 296 | }); 297 | } 298 | 299 | if (!apiKeys) { 300 | handleGeneralError(query, { 301 | type: "secretKey", 302 | message: "配置错误 - 请确保您在插件配置中填入了正确的 API Keys", 303 | addition: "请在插件配置中填写 API Keys", 304 | }); 305 | } 306 | 307 | const modelValue = isCustomModelRequired ? customModel : model; 308 | 309 | const apiKey = getApiKey($option.apiKeys); 310 | 311 | const baseUrl = ensureHttpsAndNoTrailingSlash(apiUrl || "https://api.openai.com"); 312 | let apiUrlPath = baseUrl.includes("gateway.ai.cloudflare.com") ? "/chat/completions" : "/v1/chat/completions"; 313 | const apiVersionQuery = apiVersion ? `?api-version=${apiVersion}` : "?api-version=2023-03-15-preview"; 314 | 315 | const isAzureServiceProvider = baseUrl.includes("openai.azure.com"); 316 | if (isAzureServiceProvider) { 317 | if (deploymentName) { 318 | apiUrlPath = `/openai/deployments/${deploymentName}/chat/completions${apiVersionQuery}`; 319 | } else { 320 | handleGeneralError(query, { 321 | type: "secretKey", 322 | message: "配置错误 - 未填写 Deployment Name", 323 | addition: "请在插件配置中填写 Deployment Name", 324 | troubleshootingLink: "https://bobtranslate.com/service/translate/azureopenai.html" 325 | }); 326 | } 327 | } 328 | 329 | const header = buildHeader(isAzureServiceProvider, apiKey); 330 | const body = buildRequestBody(modelValue, query); 331 | 332 | let targetText = ""; // 初始化拼接结果变量 333 | let buffer = ""; // 新增 buffer 变量 334 | (async () => { 335 | if (Number(stream)) { 336 | await $http.streamRequest({ 337 | method: "POST", 338 | url: baseUrl + apiUrlPath, 339 | header, 340 | body: { 341 | ...body, 342 | stream: true, 343 | }, 344 | cancelSignal: query.cancelSignal, 345 | streamHandler: (streamData) => { 346 | if (streamData.text?.includes("Invalid token")) { 347 | handleGeneralError(query, { 348 | type: "secretKey", 349 | message: "配置错误 - 请确保您在插件配置中填入了正确的 API Keys", 350 | addition: "请在插件配置中填写正确的 API Keys", 351 | troubleshootingLink: "https://bobtranslate.com/service/translate/openai.html" 352 | }); 353 | } else if (streamData.text !== undefined) { 354 | // 将新的数据添加到缓冲变量中 355 | buffer += streamData.text; 356 | // 检查缓冲变量是否包含一个完整的消息 357 | while (true) { 358 | const match = buffer.match(/data: (.*?})\n/); 359 | if (match) { 360 | // 如果是一个完整的消息,处理它并从缓冲变量中移除 361 | const textFromResponse = match[1].trim(); 362 | targetText = handleStreamResponse(query, targetText, textFromResponse); 363 | buffer = buffer.slice(match[0].length); 364 | } else { 365 | // 如果没有完整的消息,等待更多的数据 366 | break; 367 | } 368 | } 369 | } 370 | }, 371 | handler: (result) => { 372 | if (result.response.statusCode >= 400) { 373 | handleGeneralError(query, result); 374 | } else { 375 | query.onCompletion({ 376 | result: { 377 | from: query.detectFrom, 378 | to: query.detectTo, 379 | toParagraphs: [targetText], 380 | }, 381 | }); 382 | } 383 | } 384 | }); 385 | } else { 386 | const result = await $http.request({ 387 | method: "POST", 388 | url: baseUrl + apiUrlPath, 389 | header, 390 | body, 391 | }); 392 | 393 | if (result.error) { 394 | handleGeneralError(query, result); 395 | } else { 396 | handleGeneralResponse(query, result); 397 | } 398 | } 399 | })().catch((error) => { 400 | handleGeneralError(query, error); 401 | }); 402 | } 403 | 404 | export { 405 | pluginTimeoutInterval, 406 | pluginValidate, 407 | supportLanguages, 408 | translate, 409 | } -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | '@bob-translate/types': 12 | specifier: 1.0.2 13 | version: 1.0.2 14 | '@rollup/plugin-terser': 15 | specifier: 0.4.4 16 | version: 0.4.4(rollup@4.20.0) 17 | '@rollup/plugin-typescript': 18 | specifier: 11.1.6 19 | version: 11.1.6(rollup@4.20.0)(tslib@2.6.3)(typescript@5.5.4) 20 | rollup: 21 | specifier: 4.20.0 22 | version: 4.20.0 23 | rollup-plugin-copy: 24 | specifier: 3.5.0 25 | version: 3.5.0 26 | tslib: 27 | specifier: 2.6.3 28 | version: 2.6.3 29 | typescript: 30 | specifier: 5.5.4 31 | version: 5.5.4 32 | 33 | packages: 34 | 35 | '@bob-translate/types@1.0.2': 36 | resolution: {integrity: sha512-eYy10j8iJ5o6xq0wknH4RUVhnRQJQA+qRMV83LxuAOB289s0qSyxh/LAXmE8RjJanN1ZItiqb5s0aTkbPHPzZA==} 37 | 38 | '@jridgewell/gen-mapping@0.3.5': 39 | resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} 40 | engines: {node: '>=6.0.0'} 41 | 42 | '@jridgewell/resolve-uri@3.1.2': 43 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 44 | engines: {node: '>=6.0.0'} 45 | 46 | '@jridgewell/set-array@1.2.1': 47 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 48 | engines: {node: '>=6.0.0'} 49 | 50 | '@jridgewell/source-map@0.3.6': 51 | resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} 52 | 53 | '@jridgewell/sourcemap-codec@1.5.0': 54 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 55 | 56 | '@jridgewell/trace-mapping@0.3.25': 57 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 58 | 59 | '@nodelib/fs.scandir@2.1.5': 60 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 61 | engines: {node: '>= 8'} 62 | 63 | '@nodelib/fs.stat@2.0.5': 64 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 65 | engines: {node: '>= 8'} 66 | 67 | '@nodelib/fs.walk@1.2.8': 68 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 69 | engines: {node: '>= 8'} 70 | 71 | '@rollup/plugin-terser@0.4.4': 72 | resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} 73 | engines: {node: '>=14.0.0'} 74 | peerDependencies: 75 | rollup: ^2.0.0||^3.0.0||^4.0.0 76 | peerDependenciesMeta: 77 | rollup: 78 | optional: true 79 | 80 | '@rollup/plugin-typescript@11.1.6': 81 | resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==} 82 | engines: {node: '>=14.0.0'} 83 | peerDependencies: 84 | rollup: ^2.14.0||^3.0.0||^4.0.0 85 | tslib: '*' 86 | typescript: '>=3.7.0' 87 | peerDependenciesMeta: 88 | rollup: 89 | optional: true 90 | tslib: 91 | optional: true 92 | 93 | '@rollup/pluginutils@5.1.0': 94 | resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} 95 | engines: {node: '>=14.0.0'} 96 | peerDependencies: 97 | rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 98 | peerDependenciesMeta: 99 | rollup: 100 | optional: true 101 | 102 | '@rollup/rollup-android-arm-eabi@4.20.0': 103 | resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==} 104 | cpu: [arm] 105 | os: [android] 106 | 107 | '@rollup/rollup-android-arm64@4.20.0': 108 | resolution: {integrity: sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==} 109 | cpu: [arm64] 110 | os: [android] 111 | 112 | '@rollup/rollup-darwin-arm64@4.20.0': 113 | resolution: {integrity: sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==} 114 | cpu: [arm64] 115 | os: [darwin] 116 | 117 | '@rollup/rollup-darwin-x64@4.20.0': 118 | resolution: {integrity: sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==} 119 | cpu: [x64] 120 | os: [darwin] 121 | 122 | '@rollup/rollup-linux-arm-gnueabihf@4.20.0': 123 | resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==} 124 | cpu: [arm] 125 | os: [linux] 126 | 127 | '@rollup/rollup-linux-arm-musleabihf@4.20.0': 128 | resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==} 129 | cpu: [arm] 130 | os: [linux] 131 | 132 | '@rollup/rollup-linux-arm64-gnu@4.20.0': 133 | resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==} 134 | cpu: [arm64] 135 | os: [linux] 136 | 137 | '@rollup/rollup-linux-arm64-musl@4.20.0': 138 | resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==} 139 | cpu: [arm64] 140 | os: [linux] 141 | 142 | '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': 143 | resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==} 144 | cpu: [ppc64] 145 | os: [linux] 146 | 147 | '@rollup/rollup-linux-riscv64-gnu@4.20.0': 148 | resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==} 149 | cpu: [riscv64] 150 | os: [linux] 151 | 152 | '@rollup/rollup-linux-s390x-gnu@4.20.0': 153 | resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==} 154 | cpu: [s390x] 155 | os: [linux] 156 | 157 | '@rollup/rollup-linux-x64-gnu@4.20.0': 158 | resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==} 159 | cpu: [x64] 160 | os: [linux] 161 | 162 | '@rollup/rollup-linux-x64-musl@4.20.0': 163 | resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==} 164 | cpu: [x64] 165 | os: [linux] 166 | 167 | '@rollup/rollup-win32-arm64-msvc@4.20.0': 168 | resolution: {integrity: sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==} 169 | cpu: [arm64] 170 | os: [win32] 171 | 172 | '@rollup/rollup-win32-ia32-msvc@4.20.0': 173 | resolution: {integrity: sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==} 174 | cpu: [ia32] 175 | os: [win32] 176 | 177 | '@rollup/rollup-win32-x64-msvc@4.20.0': 178 | resolution: {integrity: sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==} 179 | cpu: [x64] 180 | os: [win32] 181 | 182 | '@types/estree@1.0.5': 183 | resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} 184 | 185 | '@types/fs-extra@8.1.5': 186 | resolution: {integrity: sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==} 187 | 188 | '@types/glob@7.2.0': 189 | resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} 190 | 191 | '@types/minimatch@5.1.2': 192 | resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} 193 | 194 | '@types/node@22.1.0': 195 | resolution: {integrity: sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==} 196 | 197 | acorn@8.12.1: 198 | resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} 199 | engines: {node: '>=0.4.0'} 200 | hasBin: true 201 | 202 | array-union@2.1.0: 203 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 204 | engines: {node: '>=8'} 205 | 206 | balanced-match@1.0.2: 207 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 208 | 209 | brace-expansion@1.1.11: 210 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 211 | 212 | braces@3.0.3: 213 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 214 | engines: {node: '>=8'} 215 | 216 | buffer-from@1.1.2: 217 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 218 | 219 | colorette@1.4.0: 220 | resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} 221 | 222 | commander@2.20.3: 223 | resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} 224 | 225 | concat-map@0.0.1: 226 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 227 | 228 | dir-glob@3.0.1: 229 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 230 | engines: {node: '>=8'} 231 | 232 | estree-walker@2.0.2: 233 | resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 234 | 235 | fast-glob@3.3.2: 236 | resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} 237 | engines: {node: '>=8.6.0'} 238 | 239 | fastq@1.17.1: 240 | resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} 241 | 242 | fill-range@7.1.1: 243 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 244 | engines: {node: '>=8'} 245 | 246 | fs-extra@8.1.0: 247 | resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} 248 | engines: {node: '>=6 <7 || >=8'} 249 | 250 | fs.realpath@1.0.0: 251 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 252 | 253 | fsevents@2.3.3: 254 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 255 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 256 | os: [darwin] 257 | 258 | function-bind@1.1.2: 259 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 260 | 261 | glob-parent@5.1.2: 262 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 263 | engines: {node: '>= 6'} 264 | 265 | glob@7.2.3: 266 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 267 | deprecated: Glob versions prior to v9 are no longer supported 268 | 269 | globby@10.0.1: 270 | resolution: {integrity: sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==} 271 | engines: {node: '>=8'} 272 | 273 | graceful-fs@4.2.11: 274 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 275 | 276 | hasown@2.0.2: 277 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 278 | engines: {node: '>= 0.4'} 279 | 280 | ignore@5.3.1: 281 | resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} 282 | engines: {node: '>= 4'} 283 | 284 | inflight@1.0.6: 285 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 286 | 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. 287 | 288 | inherits@2.0.4: 289 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 290 | 291 | is-core-module@2.15.0: 292 | resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==} 293 | engines: {node: '>= 0.4'} 294 | 295 | is-extglob@2.1.1: 296 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 297 | engines: {node: '>=0.10.0'} 298 | 299 | is-glob@4.0.3: 300 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 301 | engines: {node: '>=0.10.0'} 302 | 303 | is-number@7.0.0: 304 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 305 | engines: {node: '>=0.12.0'} 306 | 307 | is-plain-object@3.0.1: 308 | resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==} 309 | engines: {node: '>=0.10.0'} 310 | 311 | jsonfile@4.0.0: 312 | resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} 313 | 314 | merge2@1.4.1: 315 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 316 | engines: {node: '>= 8'} 317 | 318 | micromatch@4.0.7: 319 | resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} 320 | engines: {node: '>=8.6'} 321 | 322 | minimatch@3.1.2: 323 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 324 | 325 | once@1.4.0: 326 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 327 | 328 | path-is-absolute@1.0.1: 329 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 330 | engines: {node: '>=0.10.0'} 331 | 332 | path-parse@1.0.7: 333 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 334 | 335 | path-type@4.0.0: 336 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 337 | engines: {node: '>=8'} 338 | 339 | picomatch@2.3.1: 340 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 341 | engines: {node: '>=8.6'} 342 | 343 | queue-microtask@1.2.3: 344 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 345 | 346 | randombytes@2.1.0: 347 | resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} 348 | 349 | resolve@1.22.8: 350 | resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} 351 | hasBin: true 352 | 353 | reusify@1.0.4: 354 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 355 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 356 | 357 | rollup-plugin-copy@3.5.0: 358 | resolution: {integrity: sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==} 359 | engines: {node: '>=8.3'} 360 | 361 | rollup@4.20.0: 362 | resolution: {integrity: sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==} 363 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 364 | hasBin: true 365 | 366 | run-parallel@1.2.0: 367 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 368 | 369 | safe-buffer@5.2.1: 370 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 371 | 372 | serialize-javascript@6.0.2: 373 | resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} 374 | 375 | slash@3.0.0: 376 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 377 | engines: {node: '>=8'} 378 | 379 | smob@1.5.0: 380 | resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} 381 | 382 | source-map-support@0.5.21: 383 | resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} 384 | 385 | source-map@0.6.1: 386 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 387 | engines: {node: '>=0.10.0'} 388 | 389 | supports-preserve-symlinks-flag@1.0.0: 390 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 391 | engines: {node: '>= 0.4'} 392 | 393 | terser@5.31.3: 394 | resolution: {integrity: sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==} 395 | engines: {node: '>=10'} 396 | hasBin: true 397 | 398 | to-regex-range@5.0.1: 399 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 400 | engines: {node: '>=8.0'} 401 | 402 | tslib@2.6.3: 403 | resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} 404 | 405 | typescript@5.5.4: 406 | resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} 407 | engines: {node: '>=14.17'} 408 | hasBin: true 409 | 410 | undici-types@6.13.0: 411 | resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} 412 | 413 | universalify@0.1.2: 414 | resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} 415 | engines: {node: '>= 4.0.0'} 416 | 417 | wrappy@1.0.2: 418 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 419 | 420 | snapshots: 421 | 422 | '@bob-translate/types@1.0.2': {} 423 | 424 | '@jridgewell/gen-mapping@0.3.5': 425 | dependencies: 426 | '@jridgewell/set-array': 1.2.1 427 | '@jridgewell/sourcemap-codec': 1.5.0 428 | '@jridgewell/trace-mapping': 0.3.25 429 | 430 | '@jridgewell/resolve-uri@3.1.2': {} 431 | 432 | '@jridgewell/set-array@1.2.1': {} 433 | 434 | '@jridgewell/source-map@0.3.6': 435 | dependencies: 436 | '@jridgewell/gen-mapping': 0.3.5 437 | '@jridgewell/trace-mapping': 0.3.25 438 | 439 | '@jridgewell/sourcemap-codec@1.5.0': {} 440 | 441 | '@jridgewell/trace-mapping@0.3.25': 442 | dependencies: 443 | '@jridgewell/resolve-uri': 3.1.2 444 | '@jridgewell/sourcemap-codec': 1.5.0 445 | 446 | '@nodelib/fs.scandir@2.1.5': 447 | dependencies: 448 | '@nodelib/fs.stat': 2.0.5 449 | run-parallel: 1.2.0 450 | 451 | '@nodelib/fs.stat@2.0.5': {} 452 | 453 | '@nodelib/fs.walk@1.2.8': 454 | dependencies: 455 | '@nodelib/fs.scandir': 2.1.5 456 | fastq: 1.17.1 457 | 458 | '@rollup/plugin-terser@0.4.4(rollup@4.20.0)': 459 | dependencies: 460 | serialize-javascript: 6.0.2 461 | smob: 1.5.0 462 | terser: 5.31.3 463 | optionalDependencies: 464 | rollup: 4.20.0 465 | 466 | '@rollup/plugin-typescript@11.1.6(rollup@4.20.0)(tslib@2.6.3)(typescript@5.5.4)': 467 | dependencies: 468 | '@rollup/pluginutils': 5.1.0(rollup@4.20.0) 469 | resolve: 1.22.8 470 | typescript: 5.5.4 471 | optionalDependencies: 472 | rollup: 4.20.0 473 | tslib: 2.6.3 474 | 475 | '@rollup/pluginutils@5.1.0(rollup@4.20.0)': 476 | dependencies: 477 | '@types/estree': 1.0.5 478 | estree-walker: 2.0.2 479 | picomatch: 2.3.1 480 | optionalDependencies: 481 | rollup: 4.20.0 482 | 483 | '@rollup/rollup-android-arm-eabi@4.20.0': 484 | optional: true 485 | 486 | '@rollup/rollup-android-arm64@4.20.0': 487 | optional: true 488 | 489 | '@rollup/rollup-darwin-arm64@4.20.0': 490 | optional: true 491 | 492 | '@rollup/rollup-darwin-x64@4.20.0': 493 | optional: true 494 | 495 | '@rollup/rollup-linux-arm-gnueabihf@4.20.0': 496 | optional: true 497 | 498 | '@rollup/rollup-linux-arm-musleabihf@4.20.0': 499 | optional: true 500 | 501 | '@rollup/rollup-linux-arm64-gnu@4.20.0': 502 | optional: true 503 | 504 | '@rollup/rollup-linux-arm64-musl@4.20.0': 505 | optional: true 506 | 507 | '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': 508 | optional: true 509 | 510 | '@rollup/rollup-linux-riscv64-gnu@4.20.0': 511 | optional: true 512 | 513 | '@rollup/rollup-linux-s390x-gnu@4.20.0': 514 | optional: true 515 | 516 | '@rollup/rollup-linux-x64-gnu@4.20.0': 517 | optional: true 518 | 519 | '@rollup/rollup-linux-x64-musl@4.20.0': 520 | optional: true 521 | 522 | '@rollup/rollup-win32-arm64-msvc@4.20.0': 523 | optional: true 524 | 525 | '@rollup/rollup-win32-ia32-msvc@4.20.0': 526 | optional: true 527 | 528 | '@rollup/rollup-win32-x64-msvc@4.20.0': 529 | optional: true 530 | 531 | '@types/estree@1.0.5': {} 532 | 533 | '@types/fs-extra@8.1.5': 534 | dependencies: 535 | '@types/node': 22.1.0 536 | 537 | '@types/glob@7.2.0': 538 | dependencies: 539 | '@types/minimatch': 5.1.2 540 | '@types/node': 22.1.0 541 | 542 | '@types/minimatch@5.1.2': {} 543 | 544 | '@types/node@22.1.0': 545 | dependencies: 546 | undici-types: 6.13.0 547 | 548 | acorn@8.12.1: {} 549 | 550 | array-union@2.1.0: {} 551 | 552 | balanced-match@1.0.2: {} 553 | 554 | brace-expansion@1.1.11: 555 | dependencies: 556 | balanced-match: 1.0.2 557 | concat-map: 0.0.1 558 | 559 | braces@3.0.3: 560 | dependencies: 561 | fill-range: 7.1.1 562 | 563 | buffer-from@1.1.2: {} 564 | 565 | colorette@1.4.0: {} 566 | 567 | commander@2.20.3: {} 568 | 569 | concat-map@0.0.1: {} 570 | 571 | dir-glob@3.0.1: 572 | dependencies: 573 | path-type: 4.0.0 574 | 575 | estree-walker@2.0.2: {} 576 | 577 | fast-glob@3.3.2: 578 | dependencies: 579 | '@nodelib/fs.stat': 2.0.5 580 | '@nodelib/fs.walk': 1.2.8 581 | glob-parent: 5.1.2 582 | merge2: 1.4.1 583 | micromatch: 4.0.7 584 | 585 | fastq@1.17.1: 586 | dependencies: 587 | reusify: 1.0.4 588 | 589 | fill-range@7.1.1: 590 | dependencies: 591 | to-regex-range: 5.0.1 592 | 593 | fs-extra@8.1.0: 594 | dependencies: 595 | graceful-fs: 4.2.11 596 | jsonfile: 4.0.0 597 | universalify: 0.1.2 598 | 599 | fs.realpath@1.0.0: {} 600 | 601 | fsevents@2.3.3: 602 | optional: true 603 | 604 | function-bind@1.1.2: {} 605 | 606 | glob-parent@5.1.2: 607 | dependencies: 608 | is-glob: 4.0.3 609 | 610 | glob@7.2.3: 611 | dependencies: 612 | fs.realpath: 1.0.0 613 | inflight: 1.0.6 614 | inherits: 2.0.4 615 | minimatch: 3.1.2 616 | once: 1.4.0 617 | path-is-absolute: 1.0.1 618 | 619 | globby@10.0.1: 620 | dependencies: 621 | '@types/glob': 7.2.0 622 | array-union: 2.1.0 623 | dir-glob: 3.0.1 624 | fast-glob: 3.3.2 625 | glob: 7.2.3 626 | ignore: 5.3.1 627 | merge2: 1.4.1 628 | slash: 3.0.0 629 | 630 | graceful-fs@4.2.11: {} 631 | 632 | hasown@2.0.2: 633 | dependencies: 634 | function-bind: 1.1.2 635 | 636 | ignore@5.3.1: {} 637 | 638 | inflight@1.0.6: 639 | dependencies: 640 | once: 1.4.0 641 | wrappy: 1.0.2 642 | 643 | inherits@2.0.4: {} 644 | 645 | is-core-module@2.15.0: 646 | dependencies: 647 | hasown: 2.0.2 648 | 649 | is-extglob@2.1.1: {} 650 | 651 | is-glob@4.0.3: 652 | dependencies: 653 | is-extglob: 2.1.1 654 | 655 | is-number@7.0.0: {} 656 | 657 | is-plain-object@3.0.1: {} 658 | 659 | jsonfile@4.0.0: 660 | optionalDependencies: 661 | graceful-fs: 4.2.11 662 | 663 | merge2@1.4.1: {} 664 | 665 | micromatch@4.0.7: 666 | dependencies: 667 | braces: 3.0.3 668 | picomatch: 2.3.1 669 | 670 | minimatch@3.1.2: 671 | dependencies: 672 | brace-expansion: 1.1.11 673 | 674 | once@1.4.0: 675 | dependencies: 676 | wrappy: 1.0.2 677 | 678 | path-is-absolute@1.0.1: {} 679 | 680 | path-parse@1.0.7: {} 681 | 682 | path-type@4.0.0: {} 683 | 684 | picomatch@2.3.1: {} 685 | 686 | queue-microtask@1.2.3: {} 687 | 688 | randombytes@2.1.0: 689 | dependencies: 690 | safe-buffer: 5.2.1 691 | 692 | resolve@1.22.8: 693 | dependencies: 694 | is-core-module: 2.15.0 695 | path-parse: 1.0.7 696 | supports-preserve-symlinks-flag: 1.0.0 697 | 698 | reusify@1.0.4: {} 699 | 700 | rollup-plugin-copy@3.5.0: 701 | dependencies: 702 | '@types/fs-extra': 8.1.5 703 | colorette: 1.4.0 704 | fs-extra: 8.1.0 705 | globby: 10.0.1 706 | is-plain-object: 3.0.1 707 | 708 | rollup@4.20.0: 709 | dependencies: 710 | '@types/estree': 1.0.5 711 | optionalDependencies: 712 | '@rollup/rollup-android-arm-eabi': 4.20.0 713 | '@rollup/rollup-android-arm64': 4.20.0 714 | '@rollup/rollup-darwin-arm64': 4.20.0 715 | '@rollup/rollup-darwin-x64': 4.20.0 716 | '@rollup/rollup-linux-arm-gnueabihf': 4.20.0 717 | '@rollup/rollup-linux-arm-musleabihf': 4.20.0 718 | '@rollup/rollup-linux-arm64-gnu': 4.20.0 719 | '@rollup/rollup-linux-arm64-musl': 4.20.0 720 | '@rollup/rollup-linux-powerpc64le-gnu': 4.20.0 721 | '@rollup/rollup-linux-riscv64-gnu': 4.20.0 722 | '@rollup/rollup-linux-s390x-gnu': 4.20.0 723 | '@rollup/rollup-linux-x64-gnu': 4.20.0 724 | '@rollup/rollup-linux-x64-musl': 4.20.0 725 | '@rollup/rollup-win32-arm64-msvc': 4.20.0 726 | '@rollup/rollup-win32-ia32-msvc': 4.20.0 727 | '@rollup/rollup-win32-x64-msvc': 4.20.0 728 | fsevents: 2.3.3 729 | 730 | run-parallel@1.2.0: 731 | dependencies: 732 | queue-microtask: 1.2.3 733 | 734 | safe-buffer@5.2.1: {} 735 | 736 | serialize-javascript@6.0.2: 737 | dependencies: 738 | randombytes: 2.1.0 739 | 740 | slash@3.0.0: {} 741 | 742 | smob@1.5.0: {} 743 | 744 | source-map-support@0.5.21: 745 | dependencies: 746 | buffer-from: 1.1.2 747 | source-map: 0.6.1 748 | 749 | source-map@0.6.1: {} 750 | 751 | supports-preserve-symlinks-flag@1.0.0: {} 752 | 753 | terser@5.31.3: 754 | dependencies: 755 | '@jridgewell/source-map': 0.3.6 756 | acorn: 8.12.1 757 | commander: 2.20.3 758 | source-map-support: 0.5.21 759 | 760 | to-regex-range@5.0.1: 761 | dependencies: 762 | is-number: 7.0.0 763 | 764 | tslib@2.6.3: {} 765 | 766 | typescript@5.5.4: {} 767 | 768 | undici-types@6.13.0: {} 769 | 770 | universalify@0.1.2: {} 771 | 772 | wrappy@1.0.2: {} 773 | --------------------------------------------------------------------------------