@nickyzj2023/utils 1.0.66 → 1.0.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -31
- package/dist/index.d.mts +55 -6
- package/dist/index.mjs +4 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,31 +1,35 @@
|
|
|
1
|
-
男生自用全新前端工具库,0依赖
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
# npm
|
|
7
|
-
npm install @nickyzj2023/utils
|
|
8
|
-
|
|
9
|
-
# yarn
|
|
10
|
-
yarn add @nickyzj2023/utils
|
|
11
|
-
|
|
12
|
-
# pnpm
|
|
13
|
-
pnpm add @nickyzj2023/utils
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
import { fetcher, to } from "@nickyzj2023/utils";
|
|
21
|
-
|
|
22
|
-
const api = fetcher("https://api.example.com");
|
|
23
|
-
|
|
24
|
-
const [error, data] = await to(api.get<Blog>("/blogs/hello-world"));
|
|
25
|
-
if (error) {
|
|
26
|
-
console.error(error);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
console.log(data);
|
|
31
|
-
```
|
|
1
|
+
男生自用全新前端工具库,0依赖
|
|
2
|
+
|
|
3
|
+
## 安装到你的项目里
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# npm
|
|
7
|
+
npm install @nickyzj2023/utils
|
|
8
|
+
|
|
9
|
+
# yarn
|
|
10
|
+
yarn add @nickyzj2023/utils
|
|
11
|
+
|
|
12
|
+
# pnpm
|
|
13
|
+
pnpm add @nickyzj2023/utils
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 使用方式
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { fetcher, to } from "@nickyzj2023/utils";
|
|
21
|
+
|
|
22
|
+
const api = fetcher("https://api.example.com");
|
|
23
|
+
|
|
24
|
+
const [error, data] = await to(api.get<Blog>("/blogs/hello-world"));
|
|
25
|
+
if (error) {
|
|
26
|
+
console.error(error);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(data);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 完整文档
|
|
34
|
+
|
|
35
|
+
[点我](https://nickyzj628.github.io/utils/)
|
package/dist/index.d.mts
CHANGED
|
@@ -59,7 +59,8 @@ declare namespace ChatCompletions {
|
|
|
59
59
|
};
|
|
60
60
|
type ExtraBody = {
|
|
61
61
|
/** 工具列表 */tools?: ToolDefinition[]; /** 工具调用函数表,key 为工具名,value 为函数 */
|
|
62
|
-
toolHandlers?: Record<string, (args: any) => any | Promise<any>>; /**
|
|
62
|
+
toolHandlers?: Record<string, (args: any) => any | Promise<any>>; /** 是否使用流式传输,启用后函数返回异步迭代器 */
|
|
63
|
+
stream?: boolean; /** 其他额外参数 */
|
|
63
64
|
[key: string]: any;
|
|
64
65
|
};
|
|
65
66
|
type Result = {
|
|
@@ -67,18 +68,48 @@ declare namespace ChatCompletions {
|
|
|
67
68
|
usage: Usage; /** 原始响应中的其他字段 */
|
|
68
69
|
[key: string]: any;
|
|
69
70
|
};
|
|
71
|
+
/** 流式响应中的单个 SSE 数据块(OpenAI 原始格式) */
|
|
72
|
+
type StreamResponse = {
|
|
73
|
+
id: string;
|
|
74
|
+
object: "chat.completion.chunk";
|
|
75
|
+
created: number;
|
|
76
|
+
model: string;
|
|
77
|
+
choices: Array<{
|
|
78
|
+
index: number;
|
|
79
|
+
delta: {
|
|
80
|
+
role?: Message["role"];
|
|
81
|
+
content?: string | null;
|
|
82
|
+
tool_calls?: Array<{
|
|
83
|
+
index: number;
|
|
84
|
+
id?: string;
|
|
85
|
+
type?: "function";
|
|
86
|
+
function?: {
|
|
87
|
+
name?: string;
|
|
88
|
+
arguments?: string;
|
|
89
|
+
};
|
|
90
|
+
}>;
|
|
91
|
+
};
|
|
92
|
+
finish_reason: "stop" | "length" | "tool_calls" | "content_filter" | null;
|
|
93
|
+
}>;
|
|
94
|
+
usage?: Usage;
|
|
95
|
+
};
|
|
96
|
+
/** 流式调用 chatCompletions 时迭代器产出的数据块 */
|
|
97
|
+
type StreamChunk = {
|
|
98
|
+
/** 模型流式返回的内容增量(仅在生成过程中出现) */content?: string; /** Token 消耗情况(仅在最后一帧出现) */
|
|
99
|
+
usage?: Usage;
|
|
100
|
+
};
|
|
70
101
|
}
|
|
71
102
|
//#endregion
|
|
72
103
|
//#region src/ai/chatCompletions/index.d.ts
|
|
73
104
|
/**
|
|
74
105
|
* 兼容 OpenAI API 的聊天补全函数
|
|
75
106
|
* - 自动处理工具调用
|
|
76
|
-
* -
|
|
107
|
+
* - 同时支持普通响应和流式响应
|
|
77
108
|
*
|
|
78
109
|
* @param model 模型配置,包含 model、baseURL、apiKey
|
|
79
110
|
* @param messages OpenAI API 兼容的消息数组
|
|
80
|
-
* @param extraBody 可选的额外参数,如 tools、toolHandlers、temperature 等
|
|
81
|
-
* @returns
|
|
111
|
+
* @param extraBody 可选的额外参数,如 tools、toolHandlers、temperature、stream 等
|
|
112
|
+
* @returns 普通模式下返回 `{ content, usage, ... }`;`stream: true` 时返回异步迭代器
|
|
82
113
|
*
|
|
83
114
|
* @example
|
|
84
115
|
* // 最简调用
|
|
@@ -109,8 +140,26 @@ declare namespace ChatCompletions {
|
|
|
109
140
|
* },
|
|
110
141
|
* },
|
|
111
142
|
* );
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* // 流式传输
|
|
146
|
+
* const result = await chatCompletions(
|
|
147
|
+
* { baseURL: "http://127.0.0.1:11434/v1" },
|
|
148
|
+
* [{ role: "user", content: "你好" }],
|
|
149
|
+
* { stream: true },
|
|
150
|
+
* );
|
|
151
|
+
* for await (const { content, usage } of result) {
|
|
152
|
+
* if (content) {
|
|
153
|
+
* console.log("流式传输中:", content);
|
|
154
|
+
* } else if (usage) {
|
|
155
|
+
* console.log("对话结束,消耗:", usage);
|
|
156
|
+
* }
|
|
157
|
+
* }
|
|
112
158
|
*/
|
|
113
|
-
declare
|
|
159
|
+
declare function chatCompletions(model: ChatCompletions.Model, messages: ChatCompletions.Message[], extraBody: ChatCompletions.ExtraBody & {
|
|
160
|
+
stream: true;
|
|
161
|
+
}): Promise<AsyncGenerator<ChatCompletions.StreamChunk>>;
|
|
162
|
+
declare function chatCompletions(model: ChatCompletions.Model, messages: ChatCompletions.Message[], extraBody?: ChatCompletions.ExtraBody): Promise<ChatCompletions.Result>;
|
|
114
163
|
//#endregion
|
|
115
164
|
//#region src/dom/log.d.ts
|
|
116
165
|
/**
|
|
@@ -712,4 +761,4 @@ declare const sleep: (time?: number) => Promise<unknown>;
|
|
|
712
761
|
*/
|
|
713
762
|
declare const throttle: <T extends (...args: any[]) => any>(fn: T, delay?: number) => (this: any, ...args: Parameters<T>) => void;
|
|
714
763
|
//#endregion
|
|
715
|
-
export { CamelToSnake, Capitalize, Decapitalize, DeepMapKeys, DeepMapValues, ImageCompressionOptions, LockQueue, LogOptions, Primitive, RequestInit, SetTtl, SnakeToCamel, camelToSnake, capitalize, chatCompletions, compactStr, debounce, decapitalize, extractErrorMessage, fetcher, getRealURL, imageUrlToBase64, isNil, isObject, isPrimitive, log, loopUntil, mapKeys, mapValues, mergeObjects, omit, omitBy, pick, pickBy, qs, randomInt, sleep, snakeToCamel, throttle, to, withCache };
|
|
764
|
+
export { CamelToSnake, Capitalize, type ChatCompletions, Decapitalize, DeepMapKeys, DeepMapValues, ImageCompressionOptions, LockQueue, LogOptions, Primitive, RequestInit, SetTtl, SnakeToCamel, camelToSnake, capitalize, chatCompletions, compactStr, debounce, decapitalize, extractErrorMessage, fetcher, getRealURL, imageUrlToBase64, isNil, isObject, isPrimitive, log, loopUntil, mapKeys, mapValues, mergeObjects, omit, omitBy, pick, pickBy, qs, randomInt, sleep, snakeToCamel, throttle, to, withCache };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const e=e=>e==null,t=e=>e?.constructor===Object,n=e=>e==null||typeof e!=`object`&&typeof e!=`function`,r=(e,n)=>Array.isArray(e)?e.map(e=>r(e,n)):t(e)?Object.keys(e).reduce((t,i)=>{let a=n(i),o=e[i];return t[a]=r(o,n),t},{}):e,i=(e,n,r)=>{let{filter:a}=r??{};if(Array.isArray(e)){let o=e.map((e,a)=>t(e)?i(e,n,r):n(e,a));return a?o.filter((e,t)=>a(e,t)):o}return t(e)?Object.keys(e).reduce((o,s)=>{let c=e[s],l;return l=t(c)||Array.isArray(c)?i(c,n,r):n(c,s),(!a||a(l,s))&&(o[s]=l),o},{}):e},a=(e,r)=>{let i={...e};for(let e of Object.keys(r)){let o=i[e],s=r[e];if(n(o)&&n(s)){i[e]=s;continue}if(Array.isArray(o)&&Array.isArray(s)){i[e]=o.concat(s);continue}if(t(o)&&t(s)){i[e]=a(o,s);continue}i[e]=s}return i},o=(e,t)=>{let n={...e};for(let e of t)delete n[e];return n},s=(e,t)=>{let n={};for(let[r,i]of Object.entries(e))t(r,i)||(n[r]=i);return n},c=(e,t)=>t.reduce((t,n)=>(Object.hasOwn(e,n)&&(t[n]=e[n]),t),{}),l=(e,t)=>{let n={};for(let[r,i]of Object.entries(e))t(r,i)&&(n[r]=i);return n},u=(n=``,r={})=>{let i=async(i,o={})=>{let s=new URL(n?`${n}${i}`:i),{params:c,parser:l,...u}=a(r,o);t(c)&&Object.entries(c).forEach(([t,n])=>{e(n)||s.searchParams.append(t,n.toString())}),(t(u.body)||Array.isArray(u.body))&&(u.body=JSON.stringify(u.body),u.headers={...u.headers,"Content-Type":`application/json`});let d=await fetch(s,u);if(!d.ok){if(d.headers.get(`Content-Type`)?.startsWith(`application/json`)){let e=await d.json(),t=Error(e.error?.message||d.statusText);throw t.data=e,t}throw Error(d.statusText)}return await(l?.(d)??d.json())};return{get:(e,t)=>i(e,{...t,method:`GET`}),post:(e,t,n)=>i(e,{...n,method:`POST`,body:t}),put:(e,t,n)=>i(e,{...n,method:`PUT`,body:t}),delete:(e,t)=>i(e,{...t,method:`DELETE`})}},d=async e=>{try{return[null,await e]}catch(e){return[e,void 0]}},f=async e=>{let[t,n]=await d(fetch(e,{method:`HEAD`,redirect:`manual`}));return t?e:n.headers.get(`location`)||e},p=e=>{let t=new Uint8Array(e),n=``;for(let e=0;e<t.byteLength;e++)n+=String.fromCharCode(t[e]);return btoa(n)},m=async(e,t={})=>{let{quality:n=.92,compressor:r,fetcher:i=fetch}=t;if(!e.startsWith(`http`))throw Error(`图片地址必须以http或https开头`);let a=await i(e);if(!a.ok)throw Error(`获取图片失败: ${a.statusText}`);let o=a.headers.get(`Content-Type`)||`image/jpeg`,s=await a.arrayBuffer();if(o!==`image/jpeg`&&o!==`image/png`)return`data:${o};base64,${p(s)}`;if(r)return await r(s,o,n);if(typeof OffscreenCanvas<`u`){let e=null;try{let t=new Blob([s],{type:o});e=await createImageBitmap(t);let r=new OffscreenCanvas(e.width,e.height),i=r.getContext(`2d`);if(!i)throw Error(`无法获取 OffscreenCanvas context`);return i.drawImage(e,0,0),e.close(),e=null,`data:${o};base64,${p(await(await r.convertToBlob({type:o,quality:n})).arrayBuffer())}`}catch{return e?.close(),`data:${o};base64,${p(s)}`}}return`data:${o};base64,${p(s)}`},h=e=>e.replace(/_([a-zA-Z])/g,(e,t)=>t.toUpperCase()),g=e=>e.replace(/([A-Z])/g,(e,t)=>`_${t.toLowerCase()}`),_=e=>e.charAt(0).toUpperCase()+e.slice(1),v=e=>e.charAt(0).toLowerCase()+e.slice(1),y=(e=``,t)=>{if(!e)return``;let{maxLength:n=1/0,disableNewLineReplace:r=!1,disableCollapse:i=!1}=t??{},a=e;return i||(a=a.replace(/[\n\t]+/g,`
|
|
2
|
-
`)),a=r?a.replace(/\r?\n/g,` `):a.replace(/\r?\n/g,`\\n`),a=a.replace(/\s+/g,` `).trim(),n>0&&a.length>n?`${a.slice(0,n)}...`:a},b=e=>{if(e instanceof Error)return e.message;if(typeof e==`string`)return e;if(t(e)){let t=e.message||e.msg;if(t)return t;for(let t of Object.values(e)){let e=b(t);if(e)return e}}return JSON.stringify(e,null,2)},x={parse:e=>{let t=new URLSearchParams(e),n={};for(let[e,r]of t)Number.isNaN(Number(r))?n[e]=r:n[e]=Number(r);return n},stringify:(e,t)=>{let{addQueryPrefix:n=!1}=t??{},r=new URLSearchParams(e).toString();return r?n?`?${r}`:r:``}},S=async e=>{let t=(await e.get(`/models`)).data[0]?.id;if(!t)throw Error(`无法从 /models 获取模型名称`);return t},C=async(e,t,n={})=>{let{model:r,baseURL:i,apiKey:a=``}=e,o=u(i,{headers:{Authorization:`Bearer ${a}`}}),s={model:r??await S(o),messages:t,...n};return o.post(`/chat/completions`,s)},w=async(e,t)
|
|
3
|
-
`)
|
|
4
|
-
`)
|
|
2
|
+
`)),a=r?a.replace(/\r?\n/g,` `):a.replace(/\r?\n/g,`\\n`),a=a.replace(/\s+/g,` `).trim(),n>0&&a.length>n?`${a.slice(0,n)}...`:a},b=e=>{if(e instanceof Error)return e.message;if(typeof e==`string`)return e;if(t(e)){let t=e.message||e.msg;if(t)return t;for(let t of Object.values(e)){let e=b(t);if(e)return e}}return JSON.stringify(e,null,2)},x={parse:e=>{let t=new URLSearchParams(e),n={};for(let[e,r]of t)Number.isNaN(Number(r))?n[e]=r:n[e]=Number(r);return n},stringify:(e,t)=>{let{addQueryPrefix:n=!1}=t??{},r=new URLSearchParams(e).toString();return r?n?`?${r}`:r:``}},S=async e=>{let t=(await e.get(`/models`)).data[0]?.id;if(!t)throw Error(`无法从 /models 获取模型名称`);return t},C=async(e,t,n={})=>{let{model:r,baseURL:i,apiKey:a=``}=e,o=u(i,{headers:{Authorization:`Bearer ${a}`}}),s={model:r??await S(o),messages:t,...n};return o.post(`/chat/completions`,s)},w=async function*(e,t,n={}){let{model:r,baseURL:i,apiKey:a=``}=e,o=u(i,{headers:{Authorization:`Bearer ${a}`}}),s={model:r??await S(o),messages:t,...n,stream:!0,stream_options:{include_usage:!0,...n.stream_options??{}}},c=await o.post(`/chat/completions`,s,{parser:async e=>e});if(!c.body)throw Error(`响应没有 body,无法读取流式数据`);let l=c.body.getReader(),d=new TextDecoder,f=``;try{for(;;){let{done:e,value:t}=await l.read();if(e)break;f+=d.decode(t,{stream:!0});let n=f.split(`
|
|
3
|
+
`);f=n.pop()??``;for(let e of n){let t=e.trim();if(!t.startsWith(`data:`))continue;let n=t.slice(5).trim();if(n===`[DONE]`)return;try{yield JSON.parse(n)}catch{}}}}finally{l.releaseLock()}},T=async(e,t)=>{let n=t[e.function.name];if(!n)return`没有找到工具“${e.function.name}”的处理函数`;try{let t=await n(JSON.parse(e.function.arguments));return typeof t==`string`?t:JSON.stringify(t)}catch(t){return`工具“${e.function.name}”处理失败:${b(t)}`}},E=e=>typeof e==`string`?e:e.filter(e=>e.type===`text`).map(e=>e.text).join(`
|
|
4
|
+
`),D=async(e,t,n,r)=>{for(;;){let{choices:i,usage:a,...o}=await C(e,t,r),{message:s}=i[0]??{};if(!s)throw Error(`模型没有回复任何内容`);t.push(s);let{content:c=``,tool_calls:l=[],...u}=s;if(l.length>0&&Object.keys(n).length>0){for(let e of l){let r=await T(e,n);t.push({role:`tool`,content:r,tool_call_id:e.id})}continue}return{content:E(c),usage:a,...o,...u}}},O=async function*(e,t,n,r){for(;;){let i=``,a=new Map,o=null,s;for await(let n of w(e,t,r)){n.usage&&(s=n.usage);let e=n.choices?.[0];if(!e)continue;let{delta:t}=e,{content:r,tool_calls:c}=t;if(r&&(i+=r,yield{content:r}),c)for(let e of c){let t=a.get(e.index)??{id:``,type:`function`,function:{name:``,arguments:``}};e.id&&(t.id=e.id),e.function?.name&&(t.function.name+=e.function.name),e.function?.arguments&&(t.function.arguments+=e.function.arguments),a.set(e.index,t)}e.finish_reason&&(o=e.finish_reason)}let c=Array.from(a.values());if(o===`tool_calls`&&c.length>0&&Object.keys(n).length>0){t.push({role:`assistant`,content:i,tool_calls:c});for(let e of c){let r=await T(e,n);t.push({role:`tool`,content:r,tool_call_id:e.id})}continue}s&&(yield{usage:s});return}};async function k(e,t,n={}){let{stream:r,toolHandlers:i={},...a}=n;return r?O(e,t,i,a):D(e,t,i,a)}const A=(e,t)=>{let{time:n=!0,fileName:r=!0}=t??{},i=[];if(n&&i.push(`[${new Date().toLocaleTimeString()}]`),r){let{stack:e}=Error(),t=(e?.split(`
|
|
5
|
+
`)[2]?.trim())?.match(/at\s+(.*):(\d+)/);if(t?.[1]){let e=t[1].split(/[/\\]/).pop();i.push(`[${e}:${t[2]}]`)}}Array.isArray(e)?i.push(...e):i.push(e),console.log(...i)},j=async(e,t)=>{let{maxRetries:n=5,shouldStop:r}=t??{},i;for(let t=0;t<n;t++)if(i=await e(t),r?.(i)===!0)return i;if(!r)return i;throw Error(`超过了最大循环次数(${n})且未满足停止执行条件`)},M=(e,t=-1)=>{let n=new Map,r=(...r)=>{let i=JSON.stringify(r),a=Date.now(),o=n.get(i);if(o&&a<o.expiresAt)return o.value;let s=t===-1?1/0:a+t*1e3,c=e.apply({setTtl:e=>{s=a+e*1e3}},r);if(c instanceof Promise){let e=c.then(e=>(n.set(i,{value:e,expiresAt:s}),e));return n.set(i,{value:e,expiresAt:s}),e}return n.set(i,{value:c,expiresAt:s}),c};return r.clear=()=>n.clear(),r.updateTtl=e=>{t=e;let r=Date.now(),i=r+e*1e3;for(let[e,t]of n.entries())t.expiresAt>r&&(t.expiresAt=i,n.set(e,t))},r},N=(e,t)=>Math.floor(Math.random()*(t-e+1))+e,P=(e,t=300)=>{let n=null;return(...r)=>{n&&clearTimeout(n),n=setTimeout(()=>{e(...r)},t)}};var F=class{queue;constructor(){this.queue=Promise.resolve()}waitInQueue(){let e,t=new Promise(t=>{e=t}),n=this.queue.then(()=>e);return this.queue=t,n}};const I=async(e=150)=>new Promise(t=>{setTimeout(t,e)}),L=(e,t=300)=>{let n=null;return function(...r){n||=setTimeout(()=>{n=null,e.apply(this,r)},t)}};export{F as LockQueue,g as camelToSnake,_ as capitalize,k as chatCompletions,y as compactStr,P as debounce,v as decapitalize,b as extractErrorMessage,u as fetcher,f as getRealURL,m as imageUrlToBase64,e as isNil,t as isObject,n as isPrimitive,A as log,j as loopUntil,r as mapKeys,i as mapValues,a as mergeObjects,o as omit,s as omitBy,c as pick,l as pickBy,x as qs,N as randomInt,I as sleep,h as snakeToCamel,L as throttle,d as to,M as withCache};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nickyzj2023/utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.67",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsdown",
|
|
30
|
-
"docs": "typedoc
|
|
30
|
+
"docs": "typedoc",
|
|
31
31
|
"check": "biome check --diagnostic-level=error --write src/"
|
|
32
32
|
}
|
|
33
33
|
}
|