@nickyzj2023/utils 1.0.64 → 1.0.66
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 +1 -3
- package/dist/index.d.mts +120 -13
- package/dist/index.mjs +4 -3
- package/package.json +32 -33
package/README.md
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,117 @@
|
|
|
1
|
+
//#region src/ai/chatCompletions/types.d.ts
|
|
2
|
+
declare namespace ChatCompletions {
|
|
3
|
+
type Model = {
|
|
4
|
+
/** 模型名称(如果不传,会尝试从 /models 读取模型) */model?: string; /** API 基础地址 */
|
|
5
|
+
baseURL: string; /** API 密钥(本地模型可不传) */
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
};
|
|
8
|
+
type TextContent = {
|
|
9
|
+
type: "text";
|
|
10
|
+
text: string;
|
|
11
|
+
};
|
|
12
|
+
type ImageContent = {
|
|
13
|
+
type: "image_url";
|
|
14
|
+
image_url: {
|
|
15
|
+
url: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
type ContentPart = TextContent | ImageContent;
|
|
19
|
+
type Message = {
|
|
20
|
+
role: "system" | "user" | "assistant" | "tool" | "function";
|
|
21
|
+
content: string | ContentPart[];
|
|
22
|
+
name?: string;
|
|
23
|
+
tool_calls?: ToolCall[];
|
|
24
|
+
tool_call_id?: string;
|
|
25
|
+
};
|
|
26
|
+
type ToolCall = {
|
|
27
|
+
id: string;
|
|
28
|
+
type: "function";
|
|
29
|
+
function: {
|
|
30
|
+
name: string;
|
|
31
|
+
arguments: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
type ToolDefinition = {
|
|
35
|
+
type: "function";
|
|
36
|
+
function: {
|
|
37
|
+
name: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
parameters?: Record<string, any>;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
type Usage = {
|
|
43
|
+
prompt_tokens: number;
|
|
44
|
+
completion_tokens: number;
|
|
45
|
+
total_tokens: number;
|
|
46
|
+
};
|
|
47
|
+
type Response = {
|
|
48
|
+
id: string;
|
|
49
|
+
object: "chat.completion";
|
|
50
|
+
created: number;
|
|
51
|
+
model: string;
|
|
52
|
+
choices: Array<{
|
|
53
|
+
index: number;
|
|
54
|
+
message: Message;
|
|
55
|
+
finish_reason: "stop" | "length" | "tool_calls" | "content_filter" | null;
|
|
56
|
+
}>;
|
|
57
|
+
usage: Usage;
|
|
58
|
+
system_fingerprint?: string;
|
|
59
|
+
};
|
|
60
|
+
type ExtraBody = {
|
|
61
|
+
/** 工具列表 */tools?: ToolDefinition[]; /** 工具调用函数表,key 为工具名,value 为函数 */
|
|
62
|
+
toolHandlers?: Record<string, (args: any) => any | Promise<any>>; /** 其他额外参数 */
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
};
|
|
65
|
+
type Result = {
|
|
66
|
+
/** 模型的最终回复内容(多模态时取所有 text 拼接) */content: string; /** Token 消耗情况 */
|
|
67
|
+
usage: Usage; /** 原始响应中的其他字段 */
|
|
68
|
+
[key: string]: any;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/ai/chatCompletions/index.d.ts
|
|
73
|
+
/**
|
|
74
|
+
* 兼容 OpenAI API 的聊天补全函数
|
|
75
|
+
* - 自动处理工具调用
|
|
76
|
+
* - 返回最终回复内容和 token 消耗情况
|
|
77
|
+
*
|
|
78
|
+
* @param model 模型配置,包含 model、baseURL、apiKey
|
|
79
|
+
* @param messages OpenAI API 兼容的消息数组
|
|
80
|
+
* @param extraBody 可选的额外参数,如 tools、toolHandlers、temperature 等
|
|
81
|
+
* @returns 包含 content、usage 和其他原始字段的对象
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* // 最简调用
|
|
85
|
+
* // 未填写模型名,会自动使用/v1/models的第一个模型
|
|
86
|
+
* const { content, usage } = await chatCompletions(
|
|
87
|
+
* { baseURL: "http://127.0.0.1:11434/v1" },
|
|
88
|
+
* [{ role: "user", content: "你好" }],
|
|
89
|
+
* );
|
|
90
|
+
* console.log(content); // "你好!有什么我可以帮你的吗?"
|
|
91
|
+
* console.log(usage); // { prompt_tokens: 13, completion_tokens: 9, total_tokens: 22 }
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* // 工具调用
|
|
95
|
+
* const { content, usage } = await chatCompletions(
|
|
96
|
+
* { baseURL: "http://127.0.0.1:11434/v1", model: "model.gguf", apiKey: "sk-local-no-need-key" },
|
|
97
|
+
* [{ role: "user", content: "查询上海天气" }],
|
|
98
|
+
* {
|
|
99
|
+
* tools: [{
|
|
100
|
+
* type: "function",
|
|
101
|
+
* function: {
|
|
102
|
+
* name: "getWeather",
|
|
103
|
+
* description: "查询城市天气情况",
|
|
104
|
+
* parameters: { type: "object", properties: { city: { type: "string" } } },
|
|
105
|
+
* },
|
|
106
|
+
* }],
|
|
107
|
+
* toolHandlers: {
|
|
108
|
+
* getWeather: (args) => `${args.city}今日晴转多云,25°C`,
|
|
109
|
+
* },
|
|
110
|
+
* },
|
|
111
|
+
* );
|
|
112
|
+
*/
|
|
113
|
+
declare const chatCompletions: (model: ChatCompletions.Model, messages: ChatCompletions.Message[], extraBody?: ChatCompletions.ExtraBody) => Promise<ChatCompletions.Result>;
|
|
114
|
+
//#endregion
|
|
1
115
|
//#region src/dom/log.d.ts
|
|
2
116
|
/**
|
|
3
117
|
* log 配置选项
|
|
@@ -109,17 +223,6 @@ declare const withCache: <Args extends any[], Result>(fn: (this: {
|
|
|
109
223
|
updateTtl(seconds: number): void;
|
|
110
224
|
};
|
|
111
225
|
//#endregion
|
|
112
|
-
//#region src/is/is-falsy.d.ts
|
|
113
|
-
type Falsy = false | 0 | -0 | 0n | "" | null | undefined;
|
|
114
|
-
/**
|
|
115
|
-
* 检测传入的值是否为**假值**(false、0、''、null、undefined、NaN等)
|
|
116
|
-
*
|
|
117
|
-
* @example
|
|
118
|
-
* isFalsy(""); // true
|
|
119
|
-
* isFalsy(1); // false
|
|
120
|
-
*/
|
|
121
|
-
declare const isFalsy: (value: any) => value is Falsy;
|
|
122
|
-
//#endregion
|
|
123
226
|
//#region src/is/is-nil.d.ts
|
|
124
227
|
/**
|
|
125
228
|
* 检测传入的值是否为**空值**(null、undefined)
|
|
@@ -480,7 +583,7 @@ declare const decapitalize: <S extends string>(s: S) => Decapitalize<S>;
|
|
|
480
583
|
* !`);
|
|
481
584
|
*
|
|
482
585
|
* @example
|
|
483
|
-
* // "Hello...world"
|
|
586
|
+
* // "Hello...world"
|
|
484
587
|
* compactStr("Hello, beautiful world!", { maxLength: 15 });
|
|
485
588
|
*/
|
|
486
589
|
declare const compactStr: (text?: string, options?: {
|
|
@@ -501,6 +604,10 @@ declare const compactStr: (text?: string, options?: {
|
|
|
501
604
|
disableCollapse?: boolean;
|
|
502
605
|
}) => string;
|
|
503
606
|
//#endregion
|
|
607
|
+
//#region src/string/extract-error-message.d.ts
|
|
608
|
+
/** 从任意异常中提取类似 error.message 的可读文字 */
|
|
609
|
+
declare const extractErrorMessage: (error: unknown) => string;
|
|
610
|
+
//#endregion
|
|
504
611
|
//#region src/string/qs.d.ts
|
|
505
612
|
/**
|
|
506
613
|
* 针对 URL 查询字符串的解析和序列化
|
|
@@ -605,4 +712,4 @@ declare const sleep: (time?: number) => Promise<unknown>;
|
|
|
605
712
|
*/
|
|
606
713
|
declare const throttle: <T extends (...args: any[]) => any>(fn: T, delay?: number) => (this: any, ...args: Parameters<T>) => void;
|
|
607
714
|
//#endregion
|
|
608
|
-
export { CamelToSnake, Capitalize, Decapitalize, DeepMapKeys, DeepMapValues,
|
|
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
const e=(e,t)=>{let{
|
|
2
|
-
`)
|
|
3
|
-
`)
|
|
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)=>{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)}`}},T=e=>typeof e==`string`?e:e.filter(e=>e.type===`text`).map(e=>e.text).join(`
|
|
3
|
+
`),E=async(e,t,n={})=>{let{toolHandlers:r={},...i}=n;for(;;){let{choices:n,usage:a,...o}=await C(e,t,i),{message:s}=n[0]??{};if(!s)throw Error(`模型没有回复任何内容`);let{content:c=``,tool_calls:l=[],...u}=s;if(t.push(s),l.length>0&&Object.keys(r).length>0){for(let e of l){let n=await w(e,r);t.push({role:`tool`,content:n,tool_call_id:e.id})}continue}return{content:T(c),usage:a,...o,...u}}},D=(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(`
|
|
4
|
+
`)[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)},O=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})且未满足停止执行条件`)},k=(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},A=(e,t)=>Math.floor(Math.random()*(t-e+1))+e,j=(e,t=300)=>{let n=null;return(...r)=>{n&&clearTimeout(n),n=setTimeout(()=>{e(...r)},t)}};var M=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 N=async(e=150)=>new Promise(t=>{setTimeout(t,e)}),P=(e,t=300)=>{let n=null;return function(...r){n||=setTimeout(()=>{n=null,e.apply(this,r)},t)}};export{M as LockQueue,g as camelToSnake,_ as capitalize,E as chatCompletions,y as compactStr,j 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,D as log,O 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,A as randomInt,N as sleep,h as snakeToCamel,P as throttle,d as to,k as withCache};
|
package/package.json
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
2
|
+
"name": "@nickyzj2023/utils",
|
|
3
|
+
"version": "1.0.66",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.mjs",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.mts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
|
+
"import": "./dist/index.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/Nickyzj628/utils.git"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^25.6.0",
|
|
23
|
+
"tsdown": "^0.21.10",
|
|
24
|
+
"typedoc": "^0.28.19",
|
|
25
|
+
"typedoc-material-theme": "^1.4.1",
|
|
26
|
+
"typescript": "^6.0.3"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsdown",
|
|
30
|
+
"docs": "typedoc src/index.ts --plugin typedoc-material-theme",
|
|
31
|
+
"check": "biome check --diagnostic-level=error --write src/"
|
|
32
|
+
}
|
|
33
|
+
}
|