├── .gitignore ├── LICENSE ├── README.md ├── cjs ├── fetchSSE.js └── index.js ├── index.d.ts ├── index.js ├── package.json ├── test ├── .DS_Store ├── demo.wav └── node.js ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/*.mp3 3 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Easy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 中文说明见后边,以下英文由 GPT3.5 友情翻译。感谢 [cdswyda](https://github.com/cdswyda) 的 PR,已经可以在 Node 环境运行。 2 | 3 | # A Simple Pure Browser SDK for Api2d and OpenAI 4 | 5 | For some reason, I couldn't find a pure browser OpenAI SDK, they were all implemented in Node. So I wrote one myself, which is compatible with both OpenAI and [API2d](https://api2d.com/) keys. 6 | 7 | ## Change Log 8 | 9 | - 0.1.41 添加 onReasoning 方法,用于输出推理过程中的内容 10 | - 0.1.39 添加 imageGenerate 方法,用于调用 DALL 生成图片 11 | 12 | - 0.1.37:Azure默认模型名称采用去掉小数点的版本 13 | 14 | ``` 15 | { 16 | 'gpt-3.5-turbo':'gpt-35-turbo', 17 | 'gpt-3.5-turbo-0301':'gpt-35-turbo-0301', 18 | 'gpt-3.5-turbo-0613':'gpt-35-turbo-0613', 19 | 'gpt-3.5-16k':'gpt-35-16k', 20 | 'gpt-3.5-16k-0613':'gpt-35-16k-0613', 21 | 'gpt-4':'gpt-4', 22 | 'text-embedding-ada-002':'text-embedding-ada-002', 23 | } 24 | ``` 25 | 26 | - 0.1.36:修正chunk同时包含content和stop时,无法显示内容的问题,支持event action(清屏) 27 | - 0.1.33:强制关闭 `@microsoft/fetch-event-source` 默认配置导致的请求重发 28 | - 0.1.31:从api层删除发送给OpenAI的moderation的header以避免400错误 29 | 30 | - 0.1.28:兼容Azure OpenAI 接口 31 | 32 | 使用方式: 33 | 34 | 1. apiBaseUrl 填入部署模型的域名部分如 `https://ai2co.openai.azure.com` 35 | 2. 按 deployments 参数中 `模型→部署名` 的对应关系在 Azure 上部署模型。如果你已经部署过模型,也可以修改 deployments 对象的值,并作为参数传递 36 | 37 | ``` 38 | (key = null, apiBaseUrl = null, timeout = 60000, version = '2023-07-01-preview', deployments = { 39 | 'gpt-3.5-turbo':'GPT35', 40 | 'gpt-3.5-turbo-0301':'GPT35', 41 | 'gpt-3.5-turbo-0613':'GPT35', 42 | 'gpt-3.5-16k':'GPT35-16K', 43 | 'gpt-3.5-16k-0613':'GPT35-16K', 44 | 'gpt-4':'GPT4', 45 | 'text-embedding-ada-002':'EBD002', 46 | }) 47 | ``` 48 | - 0.1.25:添加带重试的请求方法 completionWithRetry,添加 request 方法以支持自定义接口 49 | 50 | - 0.1.22:tts支持speed参数 51 | 52 | ## Usage 53 | 54 | ```js 55 | import Api2d from 'api2d'; 56 | 57 | const timeout = 1000 * 60; // 60 seconds timeout 58 | const api = new Api2d(key, apiBaseUrl, timeout); 59 | 60 | // chat completion 61 | const ret = await api.completion({ 62 | model: 'gpt-3.5-turbo', 63 | messages: [ 64 | { 65 | role: 'user', 66 | content: 'Hello', 67 | }, 68 | ], 69 | stream: true, // supports stream, note that when stream is true, the return value is undefined 70 | onMessage: (string) => { 71 | console.log('SSE returns, here is the complete string received', string); 72 | }, 73 | onEnd: (string) => { 74 | console.log('end', string); 75 | }, 76 | }); 77 | 78 | // embeddings 79 | const ret = await api.embeddings({ 80 | input: 'hello world', 81 | }); 82 | console.log(ret); 83 | 84 | api.setKey('newkey'); // set key 85 | api.setApiBaseUrl('https://...your openai proxy address'); 86 | api.setTimeout(1000 * 60 * 5); 87 | api.abort(); // cancel the request actively 88 | ``` 89 | 90 | ### Example of using in Node environment 91 | 92 | ```js 93 | const api2d = require('api2d-js/cjs/index.js'); 94 | const forward_key = 'FK...'; 95 | async function doit() { 96 | const api2d_instance = new api2d(forward_key); 97 | const response = await api2d_instance.completion({ 98 | messages: [ 99 | { 100 | role: 'user', 101 | content: '来首唐诗', 102 | }, 103 | ], 104 | stream: true, 105 | onMessage: (message) => { 106 | console.log(message); 107 | }, 108 | }); 109 | console.log(response); 110 | } 111 | 112 | doit(); 113 | ``` 114 | 115 | [More examples](https://github.com/easychen/api2d-js/pull/3#issuecomment-1498753640) 116 | 117 | # 一个简单的纯浏览器 SDK for Api2d 和 OpenAI 118 | 119 | 不知道为啥,找了半天没有找到纯 Browser 的 OpenAI SDK,都是 Node 实现的。于是自己写了一个,同时兼容 OpenAI 和 [API2d](https://api2d.com/) 的 key。 120 | 121 | ## 使用方法 122 | 123 | ```js 124 | import Api2d from 'api2d'; 125 | 126 | const timeout = 1000 * 60; // 60秒超时 127 | const api = new Api2d(key, apiBaseUrl, timeout); 128 | 129 | // chat 补全 130 | const ret = await api.completion({ 131 | model: 'gpt-3.5-turbo', 132 | messages: [ 133 | { 134 | role: 'user', 135 | content: 'Hello', 136 | }, 137 | ], 138 | stream: true, // 支持 stream,注意stream为 true 的时候,返回值为undefined 139 | onMessage: (string) => { 140 | console.log('SSE返回,这里返回的是已经接收到的完整字符串', string); 141 | }, 142 | onEnd: (string) => { 143 | console.log('end', string); 144 | }, 145 | }); 146 | 147 | // embeddings 148 | const ret = await api.embeddings({ 149 | input: 'hello world', 150 | }); 151 | console.log(ret); 152 | 153 | api.setKey('newkey'); // set key 154 | api.setApiBaseUrl('https://...your openai proxy address'); 155 | api.setTimeout(1000 * 60 * 5); 156 | api.abort(); // 主动取消请求 157 | ``` 158 | 159 | ### Node 环境使用示例 160 | 161 | ```js 162 | const api2d = require('api2d-js/cjs/index.js'); 163 | const forward_key = 'FK...'; 164 | async function doit() { 165 | const api2d_instance = new api2d(forward_key); 166 | const response = await api2d_instance.completion({ 167 | messages: [ 168 | { 169 | role: 'user', 170 | content: '来首唐诗', 171 | }, 172 | ], 173 | stream: true, 174 | onMessage: (message) => { 175 | console.log(message); 176 | }, 177 | }); 178 | console.log(response); 179 | } 180 | 181 | doit(); 182 | ``` 183 | 184 | ## Azure 语音 <=> 文字 185 | 186 | Azure 这两个 API 涉及到文件操作,稍微有点复杂,所以单独拿出来说明。 187 | 188 | 注意,Azure API 只能使用 API2D 地址。 189 | 190 | ### 浏览器环境 191 | 192 | #### 语音 => 文字 193 | 194 | ```js 195 | import Api2d from 'api2d'; 196 | 197 | const timeout = 1000 * 60; // 60秒超时 198 | const api = new Api2d(key, apiBaseUrl, timeout); 199 | 200 | // stt 201 | const ret = await api.speechToText({ 202 | file: document.querySelector('input').files[0], // 这里可以使用用户本地选择的文件,也可以通过各种形式构建 File 对象传入 203 | language: 'zh-CN', // 文字对应的语言,Azure 支持的语言列表:https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support?tabs=stt 204 | moderation: false, // 如果设置为 true,会使用腾讯云的文本审核 205 | moderation_stop: false, // 如果设置为 true,当内容违规会自动清除 206 | }); 207 | console.log(ret); // {text: '这里是转换好的文字内容'} 208 | 209 | api.setKey('newkey'); // set key 210 | api.setApiBaseUrl('https://openai.api2d.net'); // 只能使用 api2d 211 | api.setTimeout(1000 * 60 * 5); 212 | api.abort(); // 主动取消请求 213 | ``` 214 | 215 | 注意输入的文件只能是 `.wav` 格式。 216 | 217 | #### 文字 => 语音 218 | 219 | 首先,文字转语音支持三种返回类型: 220 | 221 | - `file`:指定文件名,会直接调用浏览器把生成好的文件下载到本地 222 | - `blob`:返回文件的 blob,可以做进一步处理 223 | - `blob-url`:返回一个 blob-url,可以直接调用浏览器的 `Audio` 接口播放声音 224 | 225 | 下面分别举例。 226 | 227 | ##### file 228 | 229 | ```js 230 | import Api2d from 'api2d'; 231 | 232 | const timeout = 1000 * 60; // 60秒超时 233 | const api = new Api2d(key, apiBaseUrl, timeout); 234 | 235 | // tts 236 | api.textToSpeech({ 237 | text: '你好', 238 | voiceName: 'zh-CN-XiaochenNeural', // Azure 支持的声音列表:https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support?tabs=tts#supported-languages 239 | responseType: 'file', 240 | speed: 1.5, // 语速,默认为 1,范围是 0.5~2,超出范围会自动改为最近的合法值 241 | moderation: false, // 如果设置为 true,会使用腾讯云的文本审核,在【转换音频之前】对文字进行审核 242 | moderation_stop: false, // 如果设置为 true,当内容违规会直接返回,不生成音频文件 243 | }); 244 | 245 | api.setKey('newkey'); // set key 246 | api.setApiBaseUrl('https://openai.api2d.net'); // 只能使用 api2d 247 | api.setTimeout(1000 * 60 * 5); 248 | api.abort(); // 主动取消请求 249 | ``` 250 | 251 | 这里我们不需要 `await`,因为生成好之后会直接通过浏览器下载,我们不需要什么返回值。当然如果你想要等待这个过程完成,也可以 `await`,只是返回值为空,单纯用来判断是否生成完毕。 252 | 253 | ##### blob 254 | 255 | ```js 256 | import Api2d from 'api2d'; 257 | 258 | const timeout = 1000 * 60; // 60秒超时 259 | const api = new Api2d(key, apiBaseUrl, timeout); 260 | 261 | // tts 262 | const blob = await api.textToSpeech({ 263 | text: '你好', 264 | voiceName: 'zh-CN-XiaochenNeural', // Azure 支持的声音列表:https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support?tabs=tts#supported-languages 265 | responseType: 'blob', 266 | speed: 1.5, // 语速,默认为 1,范围是 0.5~2,超出范围会自动改为最近的合法值 267 | moderation: false, // 如果设置为 true,会使用腾讯云的文本审核,在【转换音频之前】对文字进行审核 268 | moderation_stop: false, // 如果设置为 true,当内容违规会直接返回,不生成音频文件 269 | }); 270 | 271 | api.setKey('newkey'); // set key 272 | api.setApiBaseUrl('https://openai.api2d.net'); // 只能使用 api2d 273 | api.setTimeout(1000 * 60 * 5); 274 | api.abort(); // 主动取消请求 275 | ``` 276 | 277 | 拿到 blob 之后可以进行各种处理。如果你只是想播放声音,可以使用 `blob-url`。 278 | 279 | ##### blob-url 280 | 281 | ```js 282 | import Api2d from 'api2d'; 283 | 284 | const timeout = 1000 * 60; // 60秒超时 285 | const api = new Api2d(key, apiBaseUrl, timeout); 286 | 287 | // tts 288 | const blob_url = await api.textToSpeech({ 289 | text: '你好', 290 | voiceName: 'zh-CN-XiaochenNeural', // Azure 支持的声音列表:https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support?tabs=tts#supported-languages 291 | responseType: 'blob-url', 292 | speed: 1.5, // 语速,默认为 1,范围是 0.5~2,超出范围会自动改为最近的合法值 293 | moderation: false, // 如果设置为 true,会使用腾讯云的文本审核,在【转换音频之前】对文字进行审核 294 | moderation_stop: false, // 如果设置为 true,当内容违规会直接返回,不生成音频文件 295 | }); 296 | 297 | var audio0 = new Audio(blob_url); 298 | audio0.play(); // 这里会直接播放声音 299 | 300 | api.setKey('newkey'); // set key 301 | api.setApiBaseUrl('https://openai.api2d.net'); // 只能使用 api2d 302 | api.setTimeout(1000 * 60 * 5); 303 | api.abort(); // 主动取消请求 304 | ``` 305 | 306 | ### NodeJS 环境 307 | 308 | NodeJS 环境因为可以操作本地文件,也可以对流做更多处理,所以接口和返回类型稍有不同。 309 | 310 | #### 语音 => 文字 311 | 312 | ```js 313 | import Api2d from 'api2d'; 314 | 315 | const timeout = 1000 * 60; // 60秒超时 316 | const api = new Api2d(key, apiBaseUrl, timeout); 317 | 318 | // stt 319 | const ret = await api.speechToText({ 320 | file: 'demo.wav', // 可以是一个完整路径 321 | language: 'zh-CN', // 文字对应的语言,Azure 支持的语言列表:https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support?tabs=stt 322 | moderation: false, // 如果设置为 true,会使用腾讯云的文本审核 323 | moderation_stop: false, // 如果设置为 true,当内容违规会自动清除 324 | }); 325 | console.log(ret); // {text: '这里是转换好的文字内容'} 326 | 327 | api.setKey('newkey'); // set key 328 | api.setApiBaseUrl('https://openai.api2d.net'); // 只能使用 api2d 329 | api.setTimeout(1000 * 60 * 5); 330 | api.abort(); // 主动取消请求 331 | ``` 332 | 333 | #### 文字 => 语音 334 | 335 | NodeJS 环境下支持两种返回值: 336 | 337 | - file 338 | - stream 339 | 340 | ##### file 341 | 342 | ```js 343 | import Api2d from 'api2d'; 344 | 345 | const timeout = 1000 * 60; // 60秒超时 346 | const api = new Api2d(key, apiBaseUrl, timeout); 347 | 348 | // tts 349 | await api.textToSpeech({ 350 | text: '你好', 351 | voiceName: 'zh-CN-XiaochenNeural', // Azure 支持的声音列表:https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support?tabs=tts#supported-languages 352 | responseType: 'file', 353 | speed: 1.5, // 语速,默认为 1,范围是 0.5~2,超出范围会自动改为最近的合法值 354 | output: 'output.mp3', // 可以是一个完整路径 355 | moderation: false, // 如果设置为 true,会使用腾讯云的文本审核,在【转换音频之前】对文字进行审核 356 | moderation_stop: false, // 如果设置为 true,当内容违规会直接返回,不生成音频文件 357 | }); 358 | 359 | api.setKey('newkey'); // set key 360 | api.setApiBaseUrl('https://openai.api2d.net'); // 只能使用 api2d 361 | api.setTimeout(1000 * 60 * 5); 362 | api.abort(); // 主动取消请求 363 | ``` 364 | 365 | 执行完毕后会直接把音频存入本地文件中。 366 | 367 | ##### stream 368 | 369 | ```js 370 | import Api2d from 'api2d'; 371 | 372 | const timeout = 1000 * 60; // 60秒超时 373 | const api = new Api2d(key, apiBaseUrl, timeout); 374 | 375 | // tts 376 | await api.textToSpeech({ 377 | text: '你好', 378 | voiceName: 'zh-CN-XiaochenNeural', // Azure 支持的声音列表:https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support?tabs=tts#supported-languages 379 | responseType: 'stream', 380 | speed: 1.5, // 语速,默认为 1,范围是 0.5~2,超出范围会自动改为最近的合法值 381 | output: fs.createWriteStream('outputStream.mp3'), 382 | moderation: false, // 如果设置为 true,会使用腾讯云的文本审核,在【转换音频之前】对文字进行审核 383 | moderation_stop: false, // 如果设置为 true,当内容违规会直接返回,不生成音频文件 384 | }); 385 | 386 | api.setKey('newkey'); // set key 387 | api.setApiBaseUrl('https://openai.api2d.net'); // 只能使用 api2d 388 | api.setTimeout(1000 * 60 * 5); 389 | api.abort(); // 主动取消请求 390 | ``` 391 | 392 | 输出是一个 stream,这里我们只是把它写入本地文件,你也可以自行处理实现更多功能,比如一边生成一边播放。 393 | 394 | [更多例子](https://github.com/easychen/api2d-js/pull/3#issuecomment-1498753640) 395 | -------------------------------------------------------------------------------- /cjs/fetchSSE.js: -------------------------------------------------------------------------------- 1 | const { createParser } = require('eventsource-parser'); 2 | 3 | module.exports = async function fetchSSE(url, options, fetch) { 4 | const { onmessage, onError, ...fetchOptions } = options; 5 | const res = await fetch(url, fetchOptions); 6 | if (!res.ok) { 7 | let reason; 8 | 9 | try { 10 | reason = await res.text(); 11 | } catch (err) { 12 | reason = res.statusText; 13 | } 14 | 15 | const msg = `ChatGPT error ${res.status}: ${reason}`; 16 | const error = new Error(msg, { cause: res }); 17 | error.statusCode = res.status; 18 | error.statusText = res.statusText; 19 | throw error; 20 | } 21 | 22 | const parser = createParser((event) => { 23 | if (event.type === 'event') { 24 | onmessage(event.data); 25 | } 26 | }); 27 | 28 | // handle special response errors 29 | const feed = (chunk) => { 30 | let response = null; 31 | 32 | try { 33 | response = JSON.parse(chunk); 34 | } catch { 35 | // ignore 36 | } 37 | 38 | if (response?.detail?.type === 'invalid_request_error') { 39 | const msg = `ChatGPT error ${response.detail.message}: ${response.detail.code} (${response.detail.type})`; 40 | const error = new Error(msg, { cause: response }); 41 | error.statusCode = response.detail.code; 42 | error.statusText = response.detail.message; 43 | 44 | if (onError) { 45 | onError(error); 46 | } else { 47 | console.error(error); 48 | } 49 | 50 | // don't feed to the event parser 51 | return; 52 | } 53 | 54 | parser.feed(chunk); 55 | }; 56 | 57 | if (!res.body.getReader) { 58 | // Vercel polyfills `fetch` with `node-fetch`, which doesn't conform to 59 | // web standards, so this is a workaround... 60 | const body = res.body; 61 | 62 | if (!body.on || !body.read) { 63 | throw new Error('unsupported "fetch" implementation'); 64 | } 65 | 66 | body.on('readable', () => { 67 | let chunk; 68 | while (null !== (chunk = body.read())) { 69 | feed(chunk.toString()); 70 | } 71 | }); 72 | } else { 73 | for await (const chunk of streamAsyncIterable(res.body)) { 74 | const str = new TextDecoder().decode(chunk); 75 | feed(str); 76 | } 77 | } 78 | }; 79 | 80 | async function* streamAsyncIterable(stream) { 81 | const reader = stream.getReader(); 82 | try { 83 | while (true) { 84 | const { done, value } = await reader.read(); 85 | if (done) { 86 | return; 87 | } 88 | yield value; 89 | } 90 | } finally { 91 | reader.releaseLock(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /cjs/index.js: -------------------------------------------------------------------------------- 1 | const fetchSSE = require('./fetchSSE.js'); 2 | const fetch = require('node-fetch'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const axios = require('axios'); 6 | 7 | module.exports = class Api2d { 8 | // 设置key和apiBaseUrl 9 | constructor(key = null, apiBaseUrl = null, timeout = 60000, version = '2023-07-01-preview', deployments = { 10 | 'gpt-3.5-turbo':'gpt-35-turbo', 11 | 'gpt-3.5-turbo-0301':'gpt-35-turbo-0301', 12 | 'gpt-3.5-turbo-0613':'gpt-35-turbo-0613', 13 | 'gpt-3.5-16k':'gpt-35-16k', 14 | 'gpt-3.5-16k-0613':'gpt-35-16k-0613', 15 | 'gpt-4':'gpt-4', 16 | 'text-embedding-ada-002':'text-embedding-ada-002', 17 | }) { 18 | this.key = key || ''; 19 | this.apiBaseUrl = apiBaseUrl || (key && key.startsWith('fk') ? 'https://oa.api2d.net' : 'https://api.openai.com'); 20 | this.deployments = deployments; 21 | this.version = version; 22 | 23 | this._updateHeaders() 24 | this.timeout = timeout; 25 | this.controller = new AbortController(); 26 | this.apiVersion = 1; 27 | } 28 | 29 | // 根据 key 和 apiBaseUrl,更新请求 headers 30 | _updateHeaders() { 31 | // 如果 apiBaseUrl 包含 openai.azure.com 32 | if( this.apiBaseUrl.includes('openai.azure.com') ) 33 | { 34 | this.by = 'azure'; 35 | this.authHeader = {'api-key': this.key}; 36 | this.refHeader = {}; 37 | }else 38 | { 39 | // openai 默认配置 40 | this.by = this.key.startsWith('fk') ? 'api2d' : 'openai'; 41 | this.authHeader = {"Authorization": "Bearer " + this.key}; 42 | 43 | if( this.key.startsWith('sk-or-') ) 44 | { 45 | this.refHeader = {"HTTP-Referer":"https://ai0c.com"}; 46 | }else 47 | { 48 | this.refHeader = {}; 49 | } 50 | } 51 | } 52 | 53 | // set key 54 | setKey(key) { 55 | this.key = key || ''; 56 | this._updateHeaders() 57 | } 58 | 59 | // set apiBaseUrl 60 | setApiBaseUrl(apiBaseUrl) { 61 | this.apiBaseUrl = apiBaseUrl; 62 | this._updateHeaders() 63 | } 64 | 65 | // set apiVersion 66 | setApiVersion(apiVersion) { 67 | this.apiVersion = apiVersion; 68 | } 69 | 70 | setTimeout(timeout) { 71 | this.timeout = parseInt(timeout) || 60 * 1000; 72 | } 73 | 74 | abort() { 75 | this.controller.abort(); 76 | this.controller = new AbortController(); 77 | } 78 | 79 | api2dOnly( openaiOk = false ) 80 | { 81 | if( openaiOk ) 82 | { 83 | if( this.by != 'api2d'&& this.by != 'openai' ) 84 | { 85 | throw new Error('Only support api2d'); 86 | } 87 | }else 88 | { 89 | if( this.by != 'api2d' ) 90 | { 91 | throw new Error('Only support api2d'); 92 | } 93 | } 94 | 95 | } 96 | 97 | buildUrlByModel(model) 98 | { 99 | // console.log( "model", model ); 100 | if( this.by == 'azure' ) 101 | { 102 | const deployment = this.deployments[model]||"GPT35"; 103 | if( String(model).toLowerCase().startsWith('text-embedding') ) 104 | { 105 | return this.apiBaseUrl + '/openai/deployments/'+deployment+'/embeddings?api-version='+this.version; 106 | }else 107 | { 108 | // if( model.toLowerCase().startsWith('gpt') ) 109 | // { 110 | return this.apiBaseUrl + '/openai/deployments/'+deployment+'/chat/completions?api-version='+this.version; 111 | // } 112 | } 113 | } 114 | else 115 | { 116 | const trimmedUrl = this.apiBaseUrl.replace(/\/*$/, ''); 117 | if( String(model).toLowerCase().startsWith('text-embedding') || String(model).toLowerCase().endsWith('bge-m3') ) 118 | { 119 | if (trimmedUrl.match(/\/v\d+$/)) 120 | return `${trimmedUrl}/embeddings`; 121 | return `${trimmedUrl}/v${this.apiVersion}/embeddings`; 122 | }else 123 | { 124 | if (trimmedUrl.match(/\/v\d+$/)) 125 | return `${trimmedUrl}/chat/completions`; 126 | return `${trimmedUrl}/v${this.apiVersion}/chat/completions`; 127 | } 128 | } 129 | } 130 | 131 | 132 | 133 | // Completion 134 | async completion(options) { 135 | // 拼接headers 136 | const headers = { 137 | "Content-Type": "application/json", 138 | ...this.authHeader,...this.refHeader, 139 | }; 140 | 141 | const {onMessage, onReasoning, onEnd, model, noCache, ...otherOptions} = options; 142 | 143 | // 拼接目标URL 144 | const url = this.buildUrlByModel(model || 'gpt-3.5-turbo'); 145 | const modelObj = this.by == 'azure' ? {} : {model: model || 'gpt-3.5-turbo'}; 146 | 147 | const { moderation, moderation_stop, ...optionsWithoutModeration } = otherOptions; 148 | 149 | const restOptions = this.by == 'api2d' ? otherOptions : optionsWithoutModeration; 150 | 151 | if (noCache) headers['x-api2d-no-cache'] = 1; 152 | 153 | // 如果是流式返回,且有回调函数 154 | if (restOptions.stream && onMessage) { 155 | // 返回一个 Promise 156 | return new Promise(async (resolve, reject) => { 157 | try { 158 | let chars = ''; 159 | // console.log('in stream'); 160 | // 使用 fetchEventSource 发送请求 161 | const timeout_handle = setTimeout(() => { 162 | this.controller.abort(); 163 | this.controller = new AbortController(); 164 | // throw new Error( "Timeout "+ this.timeout ); 165 | reject(new Error(`[408]:Timeout by ${this.timeout} ms`)); 166 | }, this.timeout); 167 | 168 | // console.log( "url", url, "opt", JSON.stringify({...restOptions, ...modelObj})); 169 | 170 | const response = await fetchSSE(url, { 171 | signal: this.controller.signal, 172 | method: 'POST', 173 | openWhenHidden: true, 174 | fetch: fetch, 175 | headers: {...headers, Accept: 'text/event-stream'}, 176 | body: JSON.stringify({...restOptions, ...modelObj }), 177 | async onopen(response) { 178 | if (response.status != 200) { 179 | const info = await response.text(); 180 | throw new Error(`[${response.status}]:${response.statusText} ${info}`); 181 | } 182 | }, 183 | onmessage: (data) => { 184 | if (timeout_handle) { 185 | clearTimeout(timeout_handle); 186 | } 187 | if (data == '[DONE]') { 188 | // console.log( 'DONE' ); 189 | if (onEnd) onEnd(chars); 190 | resolve(chars); 191 | } else { 192 | if( !isJSON(data) ) return; 193 | const event = JSON.parse(data); 194 | if( event.error ) 195 | { 196 | throw new Error(event.error.message); 197 | }else 198 | { 199 | if( event.choices && event.choices.length > 0 ) 200 | { 201 | const char = event.choices[0].delta.content; 202 | const reasoning_char = event.choices[0].delta.reasoning_content || ''; 203 | if (char) 204 | { 205 | chars += char; 206 | if (onMessage) onMessage(chars,char); 207 | }else if(reasoning_char) 208 | { 209 | if(onReasoning) onReasoning(reasoning_char); 210 | } 211 | 212 | if( event.action && event.action === 'clean' ) 213 | { 214 | chars = ""; 215 | } 216 | 217 | // azure 不返回 [DONE],而是返回 finish_reason 218 | if( event.choices[0].finish_reason ) 219 | { 220 | // end 221 | if (onEnd) onEnd(chars); 222 | resolve(chars); 223 | } 224 | } 225 | } 226 | } 227 | }, 228 | onerror: error => { 229 | console.log(error); 230 | let error_string = String(error); 231 | if (error_string && error_string.match(/\[(\d+)\]/)) { 232 | const matchs = error_string.match(/\[(\d+)\]/); 233 | error_string = `[${matchs[1]}]:${error_string}`; 234 | } 235 | throw new Error(error_string); 236 | } 237 | }, global.fetch || fetch); 238 | 239 | // const ret = await response.json(); 240 | } catch (error) { 241 | console.log(error); 242 | reject(error); 243 | } 244 | }); 245 | } else { 246 | // 使用 fetch 发送请求 247 | const timeout_handle = setTimeout(() => { 248 | this.controller.abort(); 249 | this.controller = new AbortController(); 250 | }, this.timeout); 251 | const response = await fetch(url, { 252 | signal: this.controller.signal, 253 | method: 'POST', 254 | headers: headers, 255 | body: JSON.stringify({...restOptions,...modelObj}) 256 | }); 257 | const ret = await response.json(); 258 | clearTimeout(timeout_handle); 259 | return ret; 260 | } 261 | } 262 | 263 | async completionWithRetry ( data, retry = 2 ) 264 | { 265 | return new Promise( (resolve, reject) => { 266 | try { 267 | this.completion(data).then( resolve ).catch( (error) => { 268 | console.log( "error in completion", error ); 269 | if( retry > 0 && String(error).includes("retry") ) 270 | { 271 | setTimeout( () => { 272 | this.completionWithRetry( data, retry-1 ).then( resolve ).catch( reject ); 273 | }, 1000 ); 274 | } 275 | else 276 | { 277 | console.log( "error in completion", error ); 278 | reject(error); 279 | } 280 | }); 281 | 282 | } catch (error) { 283 | console.log( "err in completion", error ); 284 | } 285 | 286 | }); 287 | } 288 | 289 | async embeddings(options) { 290 | 291 | // 拼接headers 292 | const headers = { 293 | 'Content-Type': 'application/json', 294 | ...this.authHeader,...this.refHeader, 295 | }; 296 | const {model, ...restOptions} = options; 297 | const modelObj = this.by == 'azure' ? {} : {model: model || 'text-embedding-ada-002'}; 298 | 299 | // 拼接目标URL 300 | const url = this.buildUrlByModel(model || 'text-embedding-ada-002'); 301 | // 使用 fetch 发送请求 302 | const timeout_handle = setTimeout(() => { 303 | this.controller.abort(); 304 | this.controller = new AbortController(); 305 | }, this.timeout); 306 | const response = await fetch(url, { 307 | signal: this.controller.signal, 308 | method: 'POST', 309 | headers: headers, 310 | body: JSON.stringify({...restOptions, ...modelObj}) 311 | }); 312 | const ret = await response.json(); 313 | clearTimeout(timeout_handle); 314 | return ret; 315 | } 316 | 317 | async billing() { 318 | this.api2dOnly(true); 319 | const url = this.apiBaseUrl + '/dashboard/billing/credit_grants'; 320 | const headers = { 321 | 'Content-Type': 'application/json', 322 | Authorization: 'Bearer ' + this.key 323 | }; 324 | const timeout_handle = setTimeout(() => { 325 | this.controller.abort(); 326 | this.controller = new AbortController(); 327 | }, this.timeout); 328 | const response = await fetch(url, { 329 | signal: this.controller.signal, 330 | method: 'GET', 331 | headers: headers 332 | }); 333 | const ret = await response.json(); 334 | clearTimeout(timeout_handle); 335 | return ret; 336 | } 337 | 338 | async vectorSave(options) { 339 | this.api2dOnly(); 340 | // text, embedding, uuid = "", meta = "" 341 | const {text, embedding, uuid, meta} = options; 342 | // 拼接目标URL 343 | const url = this.apiBaseUrl + "/vector"; 344 | // 拼接headers 345 | const headers = { 346 | "Content-Type": "application/json", 347 | ...this.authHeader,...this.refHeader, 348 | }; 349 | const timeout_handle = setTimeout(() => { 350 | this.controller.abort(); 351 | this.controller = new AbortController(); 352 | }, this.timeout); 353 | // 使用 fetch 发送请求 354 | const response = await fetch(url, { 355 | signal: this.controller.signal, 356 | method: "POST", 357 | headers: headers, 358 | body: JSON.stringify({ 359 | text: text, 360 | uuid: uuid || "", 361 | embedding: embedding, 362 | meta: meta || "" 363 | }) 364 | }); 365 | const ret = await response.json(); 366 | clearTimeout(timeout_handle); 367 | return ret; 368 | } 369 | 370 | async vectorSearch(options) { 371 | this.api2dOnly(); 372 | const {searchable_id, embedding, topk} = options; 373 | // 拼接目标URL 374 | const url = this.apiBaseUrl + "/vector/search"; 375 | // 拼接headers 376 | const headers = { 377 | "Content-Type": "application/json", 378 | ...this.authHeader,...this.refHeader, 379 | }; 380 | // 使用 fetch 发送请求 381 | const timeout_handle = setTimeout(() => { 382 | this.controller.abort(); 383 | this.controller = new AbortController(); 384 | }, this.timeout); 385 | const response = await fetch(url, { 386 | signal: this.controller.signal, 387 | method: "POST", 388 | headers: headers, 389 | body: JSON.stringify({ 390 | searchable_id, 391 | embedding, 392 | topk: topk || 1 393 | }) 394 | }); 395 | const ret = await response.json(); 396 | clearTimeout(timeout_handle); 397 | return ret; 398 | } 399 | 400 | async vectorDelete(options) { 401 | this.api2dOnly(); 402 | const {uuid} = options; 403 | // 拼接目标URL 404 | const url = this.apiBaseUrl + "/vector/delete"; 405 | // 拼接headers 406 | const headers = { 407 | "Content-Type": "application/json", 408 | ...this.authHeader,...this.refHeader, 409 | }; 410 | // 使用 fetch 发送请求 411 | const timeout_handle = setTimeout(() => { 412 | this.controller.abort(); 413 | this.controller = new AbortController(); 414 | }, this.timeout); 415 | const response = await fetch(url, { 416 | signal: this.controller.signal, 417 | method: "POST", 418 | headers: headers, 419 | body: JSON.stringify({ 420 | uuid 421 | }) 422 | }); 423 | const ret = await response.json(); 424 | clearTimeout(timeout_handle); 425 | return ret; 426 | } 427 | 428 | async vectorDeleteAll() { 429 | this.api2dOnly(); 430 | // 拼接目标URL 431 | const url = this.apiBaseUrl + "/vector/delete-all"; 432 | // 拼接headers 433 | const headers = { 434 | "Content-Type": "application/json", 435 | ...this.authHeader,...this.refHeader, 436 | }; 437 | // 使用 fetch 发送请求 438 | const timeout_handle = setTimeout(() => { 439 | this.controller.abort(); 440 | this.controller = new AbortController(); 441 | }, this.timeout); 442 | const response = await fetch(url, { 443 | signal: this.controller.signal, 444 | method: "POST", 445 | headers: headers, 446 | body: JSON.stringify({}) 447 | }); 448 | const ret = await response.json(); 449 | clearTimeout(timeout_handle); 450 | return ret; 451 | } 452 | 453 | async textToSpeech(options) { 454 | this.api2dOnly(); 455 | const {text, voiceName, responseType, output, speed} = options; 456 | // 拼接目标URL 457 | const url = this.apiBaseUrl + "/azure/tts"; 458 | // 拼接headers 459 | const headers = { 460 | "Content-Type": "application/json", 461 | ...this.authHeader,...this.refHeader, 462 | }; 463 | // 使用 fetch 发送请求 464 | const timeout_handle = setTimeout(() => { 465 | this.controller.abort(); 466 | this.controller = new AbortController(); 467 | }, this.timeout); 468 | const responseToFile = response => { 469 | const file_stream = fs.createWriteStream(output, {autoClose: true}); 470 | const p = new Promise((resolve, reject) => { 471 | response.body.on('data', data => { 472 | file_stream.write(data); 473 | }); 474 | 475 | response.body.on('end', () => { 476 | file_stream.close(); 477 | resolve(true); 478 | }) 479 | 480 | response.body.on('error', err => { 481 | reject(err); 482 | }); 483 | }); 484 | 485 | return p; 486 | }; 487 | const responseToStream = async response => { 488 | const p = new Promise((resolve, reject) => { 489 | response.body.on('data', data => { 490 | output.write(data); 491 | }); 492 | 493 | response.body.on('end', () => { 494 | output.close(); 495 | resolve(); 496 | }) 497 | 498 | response.body.on('error', err => { 499 | reject(err); 500 | }); 501 | }); 502 | 503 | return p; 504 | }; 505 | const response_promise = fetch(url, { 506 | signal: this.controller.signal, 507 | method: "POST", 508 | headers: headers, 509 | body: JSON.stringify({ 510 | text, 511 | voiceName, 512 | speed 513 | }) 514 | }); 515 | 516 | if (responseType === 'file') { 517 | 518 | const res = await response_promise; 519 | const ret = await responseToFile(res); 520 | clearTimeout(timeout_handle); 521 | return ret; 522 | } else if (responseType === 'stream') { 523 | const ret = response_promise.then(response => responseToStream(response)); 524 | clearTimeout(timeout_handle); 525 | return ret; 526 | 527 | } else { 528 | throw new Error('responseType must be file, blob or blob-url'); 529 | } 530 | } 531 | 532 | async speechToText(options) { 533 | this.api2dOnly(); 534 | const {file, language, moderation, moderation_stop} = options; 535 | // 拼接目标URL 536 | const url = this.apiBaseUrl + "/azure/stt"; 537 | // 拼接headers 538 | const headers = { 539 | "Content-Type": "multipart/form-data", 540 | ...this.authHeader,...this.refHeader, 541 | }; 542 | const {FormData, File} = await import("formdata-node"); 543 | 544 | const formData = new FormData(); 545 | 546 | formData.set('language', language); 547 | formData.set('moderation', moderation); 548 | formData.set('moderation_stop', moderation_stop); 549 | formData.set('file', new File([fs.readFileSync(file)], path.parse(file).base)); 550 | 551 | // 使用 fetch 发送请求 552 | const timeout_handle = setTimeout(() => { 553 | this.controller.abort(); 554 | this.controller = new AbortController(); 555 | }, this.timeout); 556 | 557 | // node-fetch 处理 formdata 有问题,用 axios 558 | const response = await axios.post(url, formData, { 559 | headers, 560 | signal: this.controller.signal 561 | }); 562 | 563 | clearTimeout(timeout_handle); 564 | 565 | return response.data; 566 | } 567 | 568 | async imageGenerate(options) { 569 | let { model, prompt, n, size, response_format} = options; 570 | if( !n ) n = 1; 571 | if( !size ) size = '1024x1024'; 572 | if( !response_format ) response_format = 'url'; 573 | if( !['dall-e-2','dall-e-3'].includes(model) ) model = 'dall-e-3'; 574 | 575 | const ret = await this.request({ 576 | path: 'v1/images/generations', 577 | method: 'POST', 578 | data: { 579 | prompt, 580 | n, 581 | size, 582 | model, 583 | response_format 584 | } 585 | }, false); 586 | return ret; 587 | } 588 | 589 | async request( options, api2dOnly = ture ) 590 | { 591 | if(api2dOnly) this.api2dOnly(); 592 | const {url, method, headers, body, path, data} = options; 593 | const timeout_handle = setTimeout(() => { 594 | this.controller.abort(); 595 | this.controller = new AbortController(); 596 | }, this.timeout); 597 | 598 | const final_url = path ? this.apiBaseUrl +'/'+ path : url; 599 | const final_data = data ? JSON.stringify(data) : body; 600 | let option = { 601 | signal: this.controller.signal, 602 | method: method || 'GET', 603 | headers: {...( headers ? headers : {} ), ...{ 604 | "Content-Type": "application/json", 605 | ...this.authHeader,...this.refHeader, 606 | }} 607 | } 608 | if( !['GET','HEAD'].includes( method.toUpperCase() ) ) option.body = final_data; 609 | 610 | const response = await fetch( final_url, option ); 611 | // console.log( final_url, option, response ); 612 | const ret = await response.json(); 613 | clearTimeout(timeout_handle); 614 | return ret; 615 | } 616 | }; 617 | 618 | // 一个测试能否被JSON parse的函数 619 | function isJSON(str) { 620 | if (typeof str == 'string') { 621 | try { 622 | const obj = JSON.parse(str); 623 | if (typeof obj == 'object' && obj) { 624 | return true; 625 | } else { 626 | return false; 627 | } 628 | 629 | } catch (e) { 630 | console.log('error:' + str + '!!!' + e); 631 | return false; 632 | } 633 | } 634 | console.log('It is not a string!') 635 | return false; 636 | } 637 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "api2d" 2 | 3 | declare class Api2d { 4 | constructor( 5 | key?: null | string, 6 | apiBaseUrl?: null | string, 7 | timeout?: number, 8 | ); 9 | 10 | setKey(key: string): void; 11 | 12 | setApiBaseUrl(apiBaseUrl: string): void; 13 | setApiVersion(apiVersion: number): void; 14 | 15 | setTimeout(timeout: number): void; 16 | 17 | abort(): void; 18 | 19 | completion(options: { 20 | onMessage?: (message: string) => void; 21 | onReasoning?: (message: string) => void; 22 | onEnd?: (message: string) => void; 23 | model?: string; 24 | stream?: boolean; 25 | noCache?: boolean; 26 | [key: string]: any; 27 | }): Promise | Object; 28 | 29 | embeddings(options: { 30 | model?: string; 31 | [key: string]: any; 32 | }): Promise; 33 | 34 | billing(): Promise; 35 | 36 | vectorSave(options:{ 37 | text: string, 38 | embedding: number[], 39 | uuid?: string, 40 | meta?: string 41 | } 42 | ): Promise; 43 | 44 | vectorSearch(options:{ 45 | searchable_id: string, 46 | embedding: number[], // 1536维的向量 47 | topk?: number 48 | } 49 | ): Promise; 50 | 51 | vectorDelete(options:{ 52 | uuid: string 53 | }): Promise; 54 | vectorDeleteAll(): Promise; 55 | 56 | textToSpeech(options: { 57 | text: string; 58 | voiceName: string; 59 | responseType: 'file' | 'stream'; 60 | output: string | any; 61 | }): Promise | Promise; 62 | 63 | 64 | speechToText(options: { 65 | file: string; 66 | language: string; 67 | moderation?: boolean; 68 | moderation_stop?: boolean; 69 | }): Promise; 70 | 71 | imageGenerate(options: { 72 | model?: string; 73 | prompt?: string; 74 | n?: number; 75 | size?: string; 76 | response_format?: string; 77 | }): Promise; 78 | 79 | } 80 | 81 | export default Api2d 82 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import {fetchEventSource} from '@microsoft/fetch-event-source'; 2 | 3 | export default class Api2d { 4 | // 设置key和apiBaseUrl 5 | constructor(key = null, apiBaseUrl = null, timeout = 60000, version = '2023-07-01-preview', deployments = { 6 | 'gpt-3.5-turbo':'gpt-35-turbo', 7 | 'gpt-3.5-turbo-0301':'gpt-35-turbo-0301', 8 | 'gpt-3.5-turbo-0613':'gpt-35-turbo-0613', 9 | 'gpt-3.5-16k':'gpt-35-16k', 10 | 'gpt-3.5-16k-0613':'gpt-35-16k-0613', 11 | 'gpt-4':'gpt-4', 12 | 'text-embedding-ada-002':'text-embedding-ada-002', 13 | }) { 14 | this.key = key || ''; 15 | this.apiBaseUrl = apiBaseUrl || (key && key.startsWith('fk') ? 'https://oa.api2d.net' : 'https://api.openai.com'); 16 | this.deployments = deployments; 17 | this.version = version; 18 | 19 | this._updateHeaders() 20 | this.timeout = timeout; 21 | this.controller = new AbortController(); 22 | this.apiVersion = 1; 23 | } 24 | 25 | // 根据 key 和 apiBaseUrl,更新请求 headers 26 | _updateHeaders() { 27 | // 如果 apiBaseUrl 包含 openai.azure.com 28 | if( this.apiBaseUrl.includes('openai.azure.com') ) 29 | { 30 | this.by = 'azure'; 31 | this.authHeader = {'api-key': this.key}; 32 | this.refHeader = {}; 33 | }else 34 | { 35 | // openai 默认配置 36 | this.by = this.key.startsWith('fk') ? 'api2d' : 'openai'; 37 | this.authHeader = {"Authorization": "Bearer " + this.key}; 38 | 39 | if( this.key.startsWith('sk-or-') ) 40 | { 41 | this.refHeader = {"HTTP-Referer":"https://ai0c.com"}; 42 | }else 43 | { 44 | this.refHeader = {}; 45 | } 46 | } 47 | } 48 | 49 | // set key 50 | setKey(key) { 51 | this.key = key || ''; 52 | this._updateHeaders() 53 | } 54 | 55 | // set apiBaseUrl 56 | setApiBaseUrl(apiBaseUrl) { 57 | this.apiBaseUrl = apiBaseUrl; 58 | this._updateHeaders() 59 | } 60 | // set apiVersion 61 | setApiVersion(apiVersion) { 62 | this.apiVersion = apiVersion; 63 | } 64 | 65 | setTimeout(timeout) { 66 | this.timeout = parseInt(timeout) || 60 * 1000; 67 | } 68 | 69 | abort() { 70 | this.controller.abort(); 71 | this.controller = new AbortController(); 72 | } 73 | 74 | api2dOnly( openaiOk = false ) 75 | { 76 | if( openaiOk ) 77 | { 78 | if( this.by != 'api2d'&& this.by != 'openai' ) 79 | { 80 | throw new Error('Only support api2d'); 81 | } 82 | }else 83 | { 84 | if( this.by != 'api2d' ) 85 | { 86 | throw new Error('Only support api2d'); 87 | } 88 | } 89 | 90 | } 91 | 92 | buildUrlByModel(model) 93 | { 94 | // console.log( "model", model ); 95 | if( this.by == 'azure' ) 96 | { 97 | const deployment = this.deployments[model]||"GPT35"; 98 | if( String(model).toLowerCase().startsWith('text-embedding') ) 99 | { 100 | return this.apiBaseUrl + '/openai/deployments/'+deployment+'/embeddings?api-version='+this.version; 101 | }else 102 | { 103 | // if( model.toLowerCase().startsWith('gpt') ) 104 | // { 105 | return this.apiBaseUrl + '/openai/deployments/'+deployment+'/chat/completions?api-version='+this.version; 106 | // } 107 | } 108 | } 109 | else 110 | { 111 | const trimmedUrl = this.apiBaseUrl.replace(/\/*$/, ''); 112 | if( String(model).toLowerCase().startsWith('text-embedding') || String(model).toLowerCase().endsWith('bge-m3') ) 113 | { 114 | if (trimmedUrl.match(/\/v\d+$/)) 115 | return `${trimmedUrl}/embeddings`; 116 | return `${trimmedUrl}/v${this.apiVersion}/embeddings`; 117 | }else 118 | { 119 | if (trimmedUrl.match(/\/v\d+$/)) 120 | return `${trimmedUrl}/chat/completions`; 121 | return `${trimmedUrl}/v${this.apiVersion}/chat/completions`; 122 | } 123 | } 124 | } 125 | 126 | // Completion 127 | async completion(options) { 128 | // 拼接headers 129 | const headers = { 130 | "Content-Type": "application/json", 131 | ...this.authHeader,...this.refHeader, 132 | }; 133 | 134 | const {onMessage, onReasoning, onEnd, model, noCache, ...otherOptions} = options; 135 | 136 | // 拼接目标URL 137 | const url = this.buildUrlByModel(model || 'gpt-3.5-turbo'); 138 | const modelObj = this.by == 'azure' ? {} : {model: model || 'gpt-3.5-turbo'}; 139 | 140 | const { moderation, moderation_stop, ...optionsWithoutModeration } = otherOptions; 141 | 142 | const restOptions = this.by == 'api2d' ? otherOptions : optionsWithoutModeration; 143 | 144 | if (noCache) headers['x-api2d-no-cache'] = 1; 145 | 146 | // 如果是流式返回,且有回调函数 147 | if (restOptions.stream && onMessage) { 148 | // 返回一个 Promise 149 | return new Promise(async (resolve, reject) => { 150 | try { 151 | let chars = ""; 152 | // console.log("in stream"); 153 | // 使用 fetchEventSource 发送请求 154 | const timeout_handle = setTimeout(() => { 155 | this.controller.abort(); 156 | this.controller = new AbortController(); 157 | // throw new Error( "Timeout "+ this.timeout ); 158 | reject(new Error(`[408]:Timeout by ${this.timeout} ms`)); 159 | }, this.timeout); 160 | const response = await fetchEventSource(url, { 161 | signal: this.controller.signal, 162 | openWhenHidden: true, 163 | method: "POST", 164 | headers: {...headers, "Accept": "text/event-stream"}, 165 | body: JSON.stringify({...restOptions, ...modelObj}), 166 | async onopen(response) { 167 | if (response.status != 200) { 168 | const info = await response.text(); 169 | throw new Error(`[${response.status}]:${response.statusText} ${info}`); 170 | } 171 | }, 172 | onmessage: e => { 173 | if (timeout_handle) { 174 | clearTimeout(timeout_handle); 175 | } 176 | if (e.data == '[DONE]') { 177 | // console.log( 'DONE' ); 178 | if (onEnd) onEnd(chars); 179 | resolve(chars); 180 | } else { 181 | // 忽略所有非JSON的数据 182 | if( !isJSON(e.data) ) return; 183 | const event = JSON.parse(e.data); 184 | if( event.error ) 185 | { 186 | throw new Error(event.error.message); 187 | }else 188 | { 189 | if( event.choices && event.choices.length > 0 ) 190 | { 191 | // azure 不返回 [DONE],而是返回 finish_reason 192 | const char = event.choices[0].delta.content; 193 | const reasoning_char = event.choices[0].delta.reasoning_content || ''; 194 | if (char) 195 | { 196 | chars += char; 197 | if (onMessage) onMessage(chars,char); 198 | }else if(reasoning_char) 199 | { 200 | if(onReasoning) onReasoning(reasoning_char); 201 | } 202 | 203 | if( event.action && event.action === 'clean' ) 204 | { 205 | chars = ""; 206 | } 207 | 208 | if( event.choices[0].finish_reason ) 209 | { 210 | // end 211 | if (onEnd) onEnd(chars); 212 | resolve(chars); 213 | } 214 | } 215 | } 216 | 217 | 218 | } 219 | 220 | }, 221 | onerror: error => { 222 | console.log(error); 223 | let error_string = String(error); 224 | if (error_string && error_string.match(/\[(\d+)\]/)) { 225 | const matchs = error_string.match(/\[(\d+)\]/); 226 | error_string = `[${matchs[1]}]:${error_string}`; 227 | } 228 | throw new Error(error_string); 229 | } 230 | }); 231 | 232 | // const ret = await response.json(); 233 | 234 | } catch (error) { 235 | console.log(error); 236 | reject(error); 237 | } 238 | }); 239 | } else { 240 | // 使用 fetch 发送请求 241 | const timeout_handle = setTimeout(() => { 242 | this.controller.abort(); 243 | this.controller = new AbortController(); 244 | }, this.timeout); 245 | 246 | const response = await fetch(url, { 247 | signal: this.controller.signal, 248 | method: "POST", 249 | headers: headers, 250 | body: JSON.stringify({...restOptions, ...modelObj}) 251 | }); 252 | 253 | const ret = await response.json(); 254 | clearTimeout(timeout_handle); 255 | return ret; 256 | } 257 | } 258 | 259 | async completionWithRetry ( data, retry = 2 ) 260 | { 261 | return new Promise( (resolve, reject) => { 262 | try { 263 | this.completion(data).then( resolve ).catch( (error) => { 264 | console.log( "error in completion", error ); 265 | if( retry > 0 && String(error).includes("retry") ) 266 | { 267 | setTimeout( () => { 268 | this.completionWithRetry( data, retry-1 ).then( resolve ).catch( reject ); 269 | }, 1000 ); 270 | } 271 | else 272 | { 273 | console.log( "error in completion", error ); 274 | reject(error); 275 | } 276 | }); 277 | 278 | } catch (error) { 279 | console.log( "error in completion", error ); 280 | } 281 | 282 | }); 283 | } 284 | 285 | async embeddings(options) { 286 | 287 | // 拼接headers 288 | const headers = { 289 | "Content-Type": "application/json", 290 | ...this.authHeader,...this.refHeader, 291 | }; 292 | const {model, ...restOptions} = options; 293 | const modelObj = this.by == 'azure' ? {} : {model: model || 'text-embedding-ada-002'}; 294 | 295 | // 拼接目标URL 296 | const url = this.buildUrlByModel(model || 'text-embedding-ada-002'); 297 | // 使用 fetch 发送请求 298 | const timeout_handle = setTimeout(() => { 299 | this.controller.abort(); 300 | this.controller = new AbortController(); 301 | }, this.timeout); 302 | const response = await fetch(url, { 303 | signal: this.controller.signal, 304 | method: "POST", 305 | headers: headers, 306 | body: JSON.stringify({...restOptions, ...modelObj}) 307 | }); 308 | const ret = await response.json(); 309 | clearTimeout(timeout_handle); 310 | return ret; 311 | } 312 | 313 | async billing() { 314 | this.api2dOnly(true); 315 | const url = this.apiBaseUrl + "/dashboard/billing/credit_grants"; 316 | const headers = { 317 | "Content-Type": "application/json", 318 | "Authorization": "Bearer " + this.key 319 | }; 320 | const response = await fetch(url, { 321 | signal: this.controller.signal, 322 | method: "GET", 323 | headers: headers 324 | }); 325 | const timeout_handle = setTimeout(() => { 326 | this.controller.abort(); 327 | this.controller = new AbortController(); 328 | }, this.timeout); 329 | const ret = await response.json(); 330 | clearTimeout(timeout_handle); 331 | return ret; 332 | } 333 | 334 | async vectorSave(options) { 335 | this.api2dOnly(); 336 | // text, embedding, uuid = "", meta = "" 337 | const {text, embedding, uuid, meta} = options; 338 | // 拼接目标URL 339 | const url = this.apiBaseUrl + "/vector"; 340 | // 拼接headers 341 | const headers = { 342 | "Content-Type": "application/json", 343 | "Authorization": "Bearer " + this.key 344 | }; 345 | 346 | // 使用 fetch 发送请求 347 | const response = await fetch(url, { 348 | signal: this.controller.signal, 349 | method: "POST", 350 | headers: headers, 351 | body: JSON.stringify({ 352 | text: text, 353 | uuid: uuid || "", 354 | embedding: embedding, 355 | meta: meta || "" 356 | }) 357 | }); 358 | const timeout_handle = setTimeout(() => { 359 | this.controller.abort(); 360 | this.controller = new AbortController(); 361 | }, this.timeout); 362 | const ret = await response.json(); 363 | clearTimeout(timeout_handle); 364 | return ret; 365 | } 366 | 367 | async vectorSearch(options) { 368 | this.api2dOnly(); 369 | const {searchable_id, embedding, topk} = options; 370 | // 拼接目标URL 371 | const url = this.apiBaseUrl + "/vector/search"; 372 | // 拼接headers 373 | const headers = { 374 | "Content-Type": "application/json", 375 | "Authorization": "Bearer " + this.key 376 | }; 377 | // 使用 fetch 发送请求 378 | const timeout_handle = setTimeout(() => { 379 | this.controller.abort(); 380 | this.controller = new AbortController(); 381 | }, this.timeout); 382 | const response = await fetch(url, { 383 | signal: this.controller.signal, 384 | method: "POST", 385 | headers: headers, 386 | body: JSON.stringify({ 387 | searchable_id, 388 | embedding, 389 | topk: topk || 1 390 | }) 391 | }); 392 | const ret = await response.json(); 393 | clearTimeout(timeout_handle); 394 | return ret; 395 | } 396 | 397 | async vectorDelete(options) { 398 | this.api2dOnly(); 399 | const {uuid} = options; 400 | // 拼接目标URL 401 | const url = this.apiBaseUrl + "/vector/delete"; 402 | // 拼接headers 403 | const headers = { 404 | "Content-Type": "application/json", 405 | "Authorization": "Bearer " + this.key 406 | }; 407 | // 使用 fetch 发送请求 408 | const timeout_handle = setTimeout(() => { 409 | this.controller.abort(); 410 | this.controller = new AbortController(); 411 | }, this.timeout); 412 | const response = await fetch(url, { 413 | signal: this.controller.signal, 414 | method: "POST", 415 | headers: headers, 416 | body: JSON.stringify({ 417 | uuid 418 | }) 419 | }); 420 | const ret = await response.json(); 421 | clearTimeout(timeout_handle); 422 | return ret; 423 | } 424 | 425 | async vectorDeleteAll() { 426 | this.api2dOnly(); 427 | // 拼接目标URL 428 | const url = this.apiBaseUrl + "/vector/delete-all"; 429 | // 拼接headers 430 | const headers = { 431 | "Content-Type": "application/json", 432 | "Authorization": "Bearer " + this.key 433 | }; 434 | // 使用 fetch 发送请求 435 | const timeout_handle = setTimeout(() => { 436 | this.controller.abort(); 437 | this.controller = new AbortController(); 438 | }, this.timeout); 439 | const response = await fetch(url, { 440 | signal: this.controller.signal, 441 | method: "POST", 442 | headers: headers, 443 | body: JSON.stringify({}) 444 | }); 445 | 446 | const ret = await response.json(); 447 | clearTimeout(timeout_handle); 448 | return ret; 449 | } 450 | 451 | async textToSpeech(options) { 452 | this.api2dOnly(); 453 | const {text, voiceName, responseType, output, speed} = options; 454 | // 拼接目标URL 455 | const url = this.apiBaseUrl + "/azure/tts"; 456 | // 拼接headers 457 | const headers = { 458 | "Content-Type": "application/json", 459 | "Authorization": "Bearer " + this.key, 460 | }; 461 | // 使用 fetch 发送请求 462 | const timeout_handle = setTimeout(() => { 463 | this.controller.abort(); 464 | this.controller = new AbortController(); 465 | }, this.timeout); 466 | const response_promise = fetch(url, { 467 | signal: this.controller.signal, 468 | method: "POST", 469 | headers: headers, 470 | body: JSON.stringify({ 471 | text, 472 | voiceName, 473 | speed 474 | }) 475 | }) 476 | .then((response) => { 477 | const reader = response.body.getReader(); 478 | return new ReadableStream({ 479 | start(controller) { 480 | return pump(); 481 | function pump() { 482 | return reader.read().then(({done, value}) => { 483 | // When no more data needs to be consumed, close the stream 484 | if (done) { 485 | controller.close(); 486 | return; 487 | } 488 | // Enqueue the next data chunk into our target stream 489 | controller.enqueue(value); 490 | return pump(); 491 | }); 492 | } 493 | }, 494 | }); 495 | }) 496 | // Create a new response out of the stream 497 | .then((stream) => new Response(stream)) 498 | // Create an object URL for the response 499 | .then((response) => response.blob()); 500 | const saveData = (function () { 501 | var a = document.createElement("a"); 502 | document.body.appendChild(a); 503 | a.style = "display: none"; 504 | return function (data, fileName) { 505 | var url = window.URL.createObjectURL(data); 506 | a.href = url; 507 | a.download = fileName; 508 | a.click(); 509 | window.URL.revokeObjectURL(url); 510 | }; 511 | }()); 512 | 513 | if (responseType === 'file') { 514 | const ret = response_promise.then((blob) => saveData(blob, output)); 515 | clearTimeout(timeout_handle); 516 | return ret; 517 | 518 | } else if (responseType === 'blob') { 519 | const ret = response_promise; 520 | clearTimeout(timeout_handle); 521 | return ret; 522 | } else if (responseType === 'blob-url') { 523 | const ret = response_promise.then((blob) => window.URL.createObjectURL(blob)); 524 | clearTimeout(timeout_handle); 525 | return ret; 526 | } else { 527 | throw new Error('responseType must be file, blob or blob-url'); 528 | } 529 | } 530 | 531 | async speechToText(options) { 532 | this.api2dOnly(); 533 | const {file, language, moderation, moderation_stop} = options; 534 | // 拼接目标URL 535 | const url = this.apiBaseUrl + "/azure/stt"; 536 | // 拼接headers 537 | const headers = { 538 | "Content-Type": "multipart/form-data", 539 | "Authorization": "Bearer " + this.key, 540 | }; 541 | 542 | const formData = new FormData(); 543 | 544 | formData.set('language', language); 545 | formData.set('moderation', moderation); 546 | formData.set('moderation_stop', moderation_stop); 547 | formData.set('file', file); 548 | 549 | const timeout_handle = setTimeout(() => { 550 | this.controller.abort(); 551 | this.controller = new AbortController(); 552 | }, this.timeout); 553 | 554 | // node-fetch 处理 formdata 有问题,用 axios 555 | const response = await axios.post(url, formData, { 556 | headers, 557 | signal: this.controller.signal 558 | }); 559 | 560 | clearTimeout(timeout_handle); 561 | 562 | return response.data; 563 | } 564 | 565 | async imageGenerate(options) { 566 | let { model, prompt, n, size, response_format} = options; 567 | if( !n ) n = 1; 568 | if( !size ) size = '1024x1024'; 569 | if( !response_format ) response_format = 'url'; 570 | if( !['dall-e-2','dall-e-3'].includes(model) ) model = 'dall-e-3'; 571 | 572 | const ret = await this.request({ 573 | path: 'v1/images/generations', 574 | method: 'POST', 575 | data: { 576 | prompt, 577 | n, 578 | size, 579 | model, 580 | response_format 581 | } 582 | }, false); 583 | return ret; 584 | } 585 | 586 | async request( options, api2dOnly = ture ) 587 | { 588 | if(api2dOnly) this.api2dOnly(); 589 | const {url, method, headers, body, path, data} = options; 590 | const timeout_handle = setTimeout(() => { 591 | this.controller.abort(); 592 | this.controller = new AbortController(); 593 | }, this.timeout); 594 | 595 | const final_url = path ? this.apiBaseUrl +'/'+ path : url; 596 | const final_data = data ? JSON.stringify(data) : body; 597 | let option = { 598 | signal: this.controller.signal, 599 | method: method || 'GET', 600 | headers: {...( headers ? headers : {} ), ...{ 601 | "Content-Type": "application/json", 602 | "Authorization": "Bearer " + this.key 603 | }} 604 | } 605 | if( !['GET','HEAD'].includes( method.toUpperCase() ) ) option.body = final_data; 606 | 607 | const response = await fetch( final_url, option ); 608 | // console.log( final_url, option, response ); 609 | const ret = await response.json(); 610 | clearTimeout(timeout_handle); 611 | return ret; 612 | } 613 | } 614 | 615 | // 一个测试能否被JSON parse的函数 616 | function isJSON(str) { 617 | if (typeof str == 'string') { 618 | try { 619 | const obj = JSON.parse(str); 620 | if (typeof obj == 'object' && obj) { 621 | return true; 622 | } else { 623 | return false; 624 | } 625 | 626 | } catch (e) { 627 | console.log('error:' + str + '!!!' + e); 628 | return false; 629 | } 630 | } 631 | console.log('It is not a string!') 632 | return false; 633 | } 634 | 635 | 636 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api2d", 3 | "version": "0.1.42", 4 | "description": "pure browser sdk for api2d and openai", 5 | "main": "cjs/index.js", 6 | "module": "index.js", 7 | "types": "index.d.ts", 8 | "repository": "https://github.com/easychen/api2d-js", 9 | "author": "EasyChen", 10 | "license": "MIT", 11 | "private": false, 12 | "dependencies": { 13 | "@microsoft/fetch-event-source": "^2.0.1", 14 | "axios": "^1.3.6", 15 | "eventsource-parser": "^1.0.0", 16 | "formdata-node": "^5.0.0", 17 | "https-proxy-agent": "^7.0.1", 18 | "node-fetch": "2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/api2d-js/98261733a27895ba3568223915332a32ff70fd2b/test/.DS_Store -------------------------------------------------------------------------------- /test/demo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/api2d-js/98261733a27895ba3568223915332a32ff70fd2b/test/demo.wav -------------------------------------------------------------------------------- /test/node.js: -------------------------------------------------------------------------------- 1 | const api2d = require('../cjs/index.js'); 2 | const fs = require('fs'); 3 | const nodeFetch = require('node-fetch'); 4 | const { HttpsProxyAgent } = require('https-proxy-agent'); 5 | 6 | global.fetch = (url, options) => { 7 | const data = { 8 | ...options, 9 | ...( process.env.http_proxy? { "agent": new HttpsProxyAgent(process.env.http_proxy) } : {}) 10 | }; 11 | return nodeFetch(url, data); 12 | }; 13 | // 从环境变量取得key 14 | const forward_key = process.env.FORWARD_KEY || "fk..."; 15 | async function chat() { 16 | const api2d_instance = new api2d(forward_key, forward_key.startsWith("sk")? 'https://openrouter.ai/api/' : 'https://oa.api2d.net'); 17 | const response = await api2d_instance.completion({ 18 | messages: [ 19 | { 20 | 'role': 'user', 21 | 'content': '来首唐诗,杜甫的', 22 | } 23 | ], 24 | model: "gpt-3.5-turbo", 25 | noCache: true, 26 | stream: true, 27 | onMessage: (message,char) => { 28 | console.log(char); 29 | } 30 | }); 31 | console.log(response); 32 | } 33 | 34 | async function vector() { 35 | const api2d_instance = new api2d(forward_key); 36 | const text = '一纸淋漓漫点方圆透,记我 长风万里绕指未相勾,形生意成 此意 逍遥不游'; 37 | // 调用 embedding 接口,将文本转为向量 38 | const response = await api2d_instance.embeddings({ 39 | input: text, 40 | }); 41 | console.log(response); 42 | if (response.data[0].embedding) { 43 | console.log("获取到向量", response.data[0].embedding); 44 | 45 | // 将向量和文本保存到数据库 46 | const response2 = await api2d_instance.vectorSave({ 47 | text, 48 | embedding: response.data[0].embedding, 49 | }); 50 | 51 | console.log(response2); 52 | 53 | if (response2.searchable_id) { 54 | console.log("保存成功,searchable_id=" + response2.searchable_id); 55 | 56 | // 开始搜索 57 | const response3 = await api2d_instance.vectorSearch({ 58 | embedding: response.data[0].embedding, 59 | topk: 1, 60 | searchable_id: response2.searchable_id, 61 | }); 62 | console.log(response3, response3.data.Get.Text[0].text); 63 | 64 | // 删除 65 | const response4 = await api2d_instance.vectorDelete({ 66 | uuid: response2.uuid, 67 | }); 68 | console.log(response4); 69 | } 70 | 71 | } 72 | 73 | 74 | } 75 | 76 | async function tts() { 77 | const api2d_instance = new api2d(forward_key); 78 | const text = '一纸淋漓漫点方圆透'; 79 | // 调用 embedding 接口,将文本转为向量 80 | const ret1 = await api2d_instance.textToSpeech({ 81 | text, 82 | voiceName: 'zh-CN-XiaochenNeural', 83 | responseType: 'file', 84 | output: 'output.mp3' 85 | }); 86 | console.log("ret1", ret1); 87 | } 88 | 89 | async function ttsStream() { 90 | const api2d_instance = new api2d(forward_key); 91 | const text = '一纸淋漓漫点方圆透'; 92 | // 调用 embedding 接口,将文本转为向量 93 | await api2d_instance.textToSpeech({ 94 | text, 95 | voiceName: 'zh-CN-XiaochenNeural', 96 | responseType: 'stream', 97 | output: fs.createWriteStream('outputStream.mp3'), 98 | speed: 2 99 | }); 100 | } 101 | 102 | async function stt() { 103 | const api2d_instance = new api2d(forward_key); 104 | // 调用 embedding 接口,将文本转为向量 105 | const response = await api2d_instance.speechToText({ 106 | file: 'demo.wav', 107 | language: 'zh-CN' 108 | }); 109 | console.log(response); 110 | } 111 | 112 | async function api() 113 | { 114 | const api2d_instance = new api2d(forward_key); 115 | const ret = await api2d_instance.request({ 116 | path: 'custom_api/186008/fetch?url='+encodeURIComponent('https://ftqq.com'), 117 | method: 'GET' 118 | }); 119 | console.log("ret=", ret); 120 | } 121 | 122 | async function azure() { 123 | const api2d_instance = new api2d(forward_key, 'https://ai2co.openai.azure.com'); 124 | const response = await api2d_instance.completionWithRetry({ 125 | messages: [ 126 | { 127 | 'role': 'user', 128 | 'content': '来首唐诗,杜甫的', 129 | } 130 | ], 131 | model:'gpt-3.5-turbo-0613', 132 | noCache: true, 133 | stream: true, 134 | onMessage: (message,char) => { 135 | console.log(char); 136 | }, 137 | onEnd: () => { 138 | console.log("onEnd"); 139 | } 140 | }); 141 | 142 | console.log("await end", response); 143 | } 144 | 145 | async function image() 146 | { 147 | const api2d_instance = new api2d(forward_key); 148 | const ret = await api2d_instance.imageGenerate( 149 | { 150 | "prompt": "A painting of a cat" 151 | }); 152 | console.log(ret); 153 | // { 154 | // created: 1706192857, 155 | // data: [ 156 | // { 157 | // revised_prompt: 'A decapturing the beauty of the natural world.', 158 | // url: 'https://oaidalleapiprodscus.blob.core.windows.net/private/org-KRG1eKn1nbXZ7vixuyJhpE6u/user-FQRBcGpSkM35GekObogjwLrc/img-ejom6IucUTN9l7fN6ms4aTuK.png?st=2024-01-25T13%3A27%3A37Z&se=2024-01-25T15%3A27%3A37Z&sp=r&sv=2021-08-06&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2024-01-24T22%3A00%3A38Z&ske=2024-01-25T22%3A00%3A38Z&sks=b&skv=2021-08-06&sig=Jv6wGPUdsRCgOSmu348UfeIzaOkSwMjDVdP6MwuXCDU%3D' 159 | // } 160 | // ] 161 | // } 162 | } 163 | 164 | async function deepChat() { 165 | const api2d_instance = new api2d(forward_key, 'https://api.siliconflow.cn'); 166 | const response = await api2d_instance.completion({ 167 | messages: [ 168 | { 169 | 'role': 'user', 170 | 'content': '为加班的Easy同学写一首青玉案,要求幽默风趣', 171 | } 172 | ], 173 | model: "Pro/deepseek-ai/DeepSeek-R1", 174 | noCache: true, 175 | stream: true, 176 | onReasoning: (message) => { 177 | console.log("🤔"+message); 178 | }, 179 | onMessage: (message,char) => { 180 | console.log(char); 181 | } 182 | }); 183 | console.log(response); 184 | } 185 | 186 | async function deepChatVol() { 187 | const api2d_instance = new api2d(forward_key, 'https://oa.api2d.net'); 188 | // api2d_instance.setApiVersion(3); 189 | const response = await api2d_instance.completion({ 190 | messages: [ 191 | { 192 | 'role': 'user', 193 | 'content': '为加班的Easy同学写一首破阵子,要求幽默风趣', 194 | } 195 | ], 196 | model: "gemini-2.0-flash", 197 | stream: true, 198 | onReasoning: (message) => { 199 | console.log("🤔"+message); 200 | }, 201 | onMessage: (message,char) => { 202 | console.log(char); 203 | } 204 | }); 205 | console.log(response); 206 | } 207 | 208 | deepChatVol(); 209 | // deepChat(); 210 | // image(); 211 | // chat(); 212 | // vector(); 213 | // tts(); 214 | // ttsStream(); 215 | // stt(); 216 | // api(); 217 | // azure(); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "strict": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@microsoft/fetch-event-source@^2.0.1": 6 | version "2.0.1" 7 | resolved "https://registry.npmmirror.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d" 8 | integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA== 9 | 10 | agent-base@^7.0.2: 11 | version "7.1.0" 12 | resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz#536802b76bc0b34aa50195eb2442276d613e3434" 13 | integrity sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg== 14 | dependencies: 15 | debug "^4.3.4" 16 | 17 | asynckit@^0.4.0: 18 | version "0.4.0" 19 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 20 | integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== 21 | 22 | axios@^1.3.6: 23 | version "1.3.6" 24 | resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.6.tgz#1ace9a9fb994314b5f6327960918406fa92c6646" 25 | integrity sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg== 26 | dependencies: 27 | follow-redirects "^1.15.0" 28 | form-data "^4.0.0" 29 | proxy-from-env "^1.1.0" 30 | 31 | combined-stream@^1.0.8: 32 | version "1.0.8" 33 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 34 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 35 | dependencies: 36 | delayed-stream "~1.0.0" 37 | 38 | debug@4, debug@^4.3.4: 39 | version "4.3.4" 40 | resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 41 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 42 | dependencies: 43 | ms "2.1.2" 44 | 45 | delayed-stream@~1.0.0: 46 | version "1.0.0" 47 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 48 | integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== 49 | 50 | eventsource-parser@^1.0.0: 51 | version "1.0.0" 52 | resolved "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.0.0.tgz#6332e37fd5512e3c8d9df05773b2bf9e152ccc04" 53 | integrity sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g== 54 | 55 | follow-redirects@^1.15.0: 56 | version "1.15.2" 57 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" 58 | integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== 59 | 60 | form-data@^4.0.0: 61 | version "4.0.0" 62 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" 63 | integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== 64 | dependencies: 65 | asynckit "^0.4.0" 66 | combined-stream "^1.0.8" 67 | mime-types "^2.1.12" 68 | 69 | formdata-node@^5.0.0: 70 | version "5.0.0" 71 | resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-5.0.0.tgz#7b4d23f8d823b88d29130366f33ff9ac58dfa7fe" 72 | integrity sha512-zrGsVVS56jJo+htsVv7ffXuzie91a2NrU1cPamvtPaSyRX++SH+4KXlGoOt+ncgDJ4bFA2SAQ+QGA+p4l1vciw== 73 | dependencies: 74 | node-domexception "1.0.0" 75 | web-streams-polyfill "4.0.0-beta.3" 76 | 77 | https-proxy-agent@^7.0.1: 78 | version "7.0.1" 79 | resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz#0277e28f13a07d45c663633841e20a40aaafe0ab" 80 | integrity sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ== 81 | dependencies: 82 | agent-base "^7.0.2" 83 | debug "4" 84 | 85 | mime-db@1.52.0: 86 | version "1.52.0" 87 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 88 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 89 | 90 | mime-types@^2.1.12: 91 | version "2.1.35" 92 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 93 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 94 | dependencies: 95 | mime-db "1.52.0" 96 | 97 | ms@2.1.2: 98 | version "2.1.2" 99 | resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 100 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 101 | 102 | node-domexception@1.0.0: 103 | version "1.0.0" 104 | resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" 105 | integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== 106 | 107 | node-fetch@2: 108 | version "2.6.12" 109 | resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba" 110 | integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== 111 | dependencies: 112 | whatwg-url "^5.0.0" 113 | 114 | proxy-from-env@^1.1.0: 115 | version "1.1.0" 116 | resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" 117 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== 118 | 119 | tr46@~0.0.3: 120 | version "0.0.3" 121 | resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" 122 | integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== 123 | 124 | web-streams-polyfill@4.0.0-beta.3: 125 | version "4.0.0-beta.3" 126 | resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" 127 | integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== 128 | 129 | webidl-conversions@^3.0.0: 130 | version "3.0.1" 131 | resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" 132 | integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== 133 | 134 | whatwg-url@^5.0.0: 135 | version "5.0.0" 136 | resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" 137 | integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== 138 | dependencies: 139 | tr46 "~0.0.3" 140 | webidl-conversions "^3.0.0" 141 | --------------------------------------------------------------------------------