ms-vite-plugin 1.4.73 → 1.4.75
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/dist/luaBundle.js +1 -1
- package/dist/luaObfuscator/addVararg.js +1 -1
- package/dist/luaObfuscator/flatten.js +1 -1
- package/dist/luaObfuscator/rename.js +1 -1
- package/dist/luaObfuscator/scope.js +1 -1
- package/docs/api/ai.md +524 -0
- package/docs/api/opencv.md +9 -2
- package/docs/apicn/ai.md +491 -0
- package/docs/apilua/ai.md +492 -0
- package/docs/apilua/opencv.md +8 -1
- package/docs/apipython/ai.md +463 -0
- package/docs/apipython/overview.md +0 -39
- package/docs/quick/vscode/createProject.md +0 -1
- package/package.json +1 -1
package/dist/luaBundle.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var b=exports&&exports.__createBinding||(Object.create?(function(t,e,i,n){n===void 0&&(n=i);var a=Object.getOwnPropertyDescriptor(e,i);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[i]}}),Object.defineProperty(t,n,a)}):(function(t,e,i,n){n===void 0&&(n=i),t[n]=e[i]})),_=exports&&exports.__setModuleDefault||(Object.create?(function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}):function(t,e){t.default=e}),m=exports&&exports.__importStar||(function(){var t=function(e){return t=Object.getOwnPropertyNames||function(i){var n=[];for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[n.length]=a);return n},t(e)};return function(e){if(e&&e.__esModule)return e;var i={};if(e!=null)for(var n=t(e),a=0;a<n.length;a++)n[a]!=="default"&&b(i,e,n[a]);return _(i,e),i}})();Object.defineProperty(exports,"__esModule",{value:!0}),exports.bundleLuaScripts=v;const c=m(require("fs-extra")),d=m(require("path")),w=new Set(["action","appleocr","cloud","config","cryptoUtils","cv","device","dotocr","file","hid","hotupdate","http","image","ime","logger","media","mysql","netcard","node","paddleocr","pip","system","tomatoocr","tts","ui","utils","yolo","yolocls"]),F=new Set(["coroutine","debug","io","math","os","package","string","table","utf8"]);async function v(t){const e=d.join(t,"main.lua");await c.pathExists(e)||(console.error("\u274C \u672A\u627E\u5230 scripts/main.lua\uFF0C\u65E0\u6CD5\u5408\u5E76 Lua"),process.exit(1));const i=await j(t),n=new Map;for(const o of i){const u=p(t,o),l=h(u);if(!l)continue;if(w.has(l)||F.has(l)){const g=w.has(l)?"\u5BBF\u4E3B":"\u6807\u51C6\u5E93";console.warn(`\u26A0\uFE0F \u8DF3\u8FC7 ${u}\uFF1A\u6A21\u5757\u540D ${l} \u4E0E${g}\u5168\u5C40\u540D\u51B2\u7A81\uFF0C\u4E0D\u8981\u5360\u7528\u5BBF\u4E3B\u6216\u6807\u51C6\u5E93\u540D\u79F0`);continue}if(n.has(l))continue;let f=await c.readFile(o,"utf8");f=y(f),n.set(l,{rel:u,source:f})}const a=y(await c.readFile(e,"utf8"));if(n.size===0)return console.log("\u2139\uFE0F Lua \u4EC5\u6709 main.lua\uFF0C\u65E0\u9700\u5408\u5E76"),0;const r=[];r.push("-- KuaiJS Lua bundle: package.preload + main.lua");const s=[...n.keys()].sort((o,u)=>o.localeCompare(u));for(const o of s){const u=n.get(o);r.push(`package.preload[${O(o)}] = function(...)`),r.push(u.source.replace(/\s+$/,"")),r.push("end")}r.push(a.replace(/^\uFEFF/,"")),await c.writeFile(e,`${r.join(`
|
|
1
|
+
"use strict";var b=exports&&exports.__createBinding||(Object.create?(function(t,e,i,n){n===void 0&&(n=i);var a=Object.getOwnPropertyDescriptor(e,i);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[i]}}),Object.defineProperty(t,n,a)}):(function(t,e,i,n){n===void 0&&(n=i),t[n]=e[i]})),_=exports&&exports.__setModuleDefault||(Object.create?(function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}):function(t,e){t.default=e}),m=exports&&exports.__importStar||(function(){var t=function(e){return t=Object.getOwnPropertyNames||function(i){var n=[];for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[n.length]=a);return n},t(e)};return function(e){if(e&&e.__esModule)return e;var i={};if(e!=null)for(var n=t(e),a=0;a<n.length;a++)n[a]!=="default"&&b(i,e,n[a]);return _(i,e),i}})();Object.defineProperty(exports,"__esModule",{value:!0}),exports.bundleLuaScripts=v;const c=m(require("fs-extra")),d=m(require("path")),w=new Set(["action","ai","appleocr","cloud","config","cryptoUtils","cv","device","dotocr","file","hid","hotupdate","http","image","ime","logger","media","mysql","netcard","node","paddleocr","pip","system","tomatoocr","tts","ui","utils","yolo","yolocls"]),F=new Set(["coroutine","debug","io","math","os","package","string","table","utf8"]);async function v(t){const e=d.join(t,"main.lua");await c.pathExists(e)||(console.error("\u274C \u672A\u627E\u5230 scripts/main.lua\uFF0C\u65E0\u6CD5\u5408\u5E76 Lua"),process.exit(1));const i=await j(t),n=new Map;for(const o of i){const u=p(t,o),l=h(u);if(!l)continue;if(w.has(l)||F.has(l)){const g=w.has(l)?"\u5BBF\u4E3B":"\u6807\u51C6\u5E93";console.warn(`\u26A0\uFE0F \u8DF3\u8FC7 ${u}\uFF1A\u6A21\u5757\u540D ${l} \u4E0E${g}\u5168\u5C40\u540D\u51B2\u7A81\uFF0C\u4E0D\u8981\u5360\u7528\u5BBF\u4E3B\u6216\u6807\u51C6\u5E93\u540D\u79F0`);continue}if(n.has(l))continue;let f=await c.readFile(o,"utf8");f=y(f),n.set(l,{rel:u,source:f})}const a=y(await c.readFile(e,"utf8"));if(n.size===0)return console.log("\u2139\uFE0F Lua \u4EC5\u6709 main.lua\uFF0C\u65E0\u9700\u5408\u5E76"),0;const r=[];r.push("-- KuaiJS Lua bundle: package.preload + main.lua");const s=[...n.keys()].sort((o,u)=>o.localeCompare(u));for(const o of s){const u=n.get(o);r.push(`package.preload[${O(o)}] = function(...)`),r.push(u.source.replace(/\s+$/,"")),r.push("end")}r.push(a.replace(/^\uFEFF/,"")),await c.writeFile(e,`${r.join(`
|
|
2
2
|
`)}
|
|
3
3
|
`,"utf8");for(const o of i)p(t,o)!=="main.lua"&&await c.remove(o);return await $(t),console.log(`\u2705 Lua \u5DF2\u5408\u5E76\u4E3A scripts/main.lua\uFF08${n.size} \u4E2A\u6A21\u5757\uFF09`),n.size}async function j(t){const e=[],i=async n=>{const a=await c.readdir(n,{withFileTypes:!0});for(const r of a){const s=d.join(n,r.name);r.isDirectory()?await i(s):r.isFile()&&r.name.endsWith(".lua")&&e.push(s)}};return await i(t),e.sort((n,a)=>{const r=p(t,n),s=p(t,a),o=h(r)??"",u=h(s)??"";if(o===u&&o){const l=r.endsWith("/init.lua")?1:0,f=s.endsWith("/init.lua")?1:0;return l-f}return r.localeCompare(s)}),e}function h(t){const e=t.replace(/\\/g,"/");if(e!=="main.lua"){if(e.endsWith("/init.lua")){const i=e.slice(0,-9);return i?i.replace(/\//g,"."):"init"}if(e.endsWith(".lua"))return e.slice(0,-4).replace(/\//g,".")}}function p(t,e){return d.relative(t,e).split(d.sep).join("/")}function y(t){let e=t.replace(/^\uFEFF/,"");if(e.startsWith("#!")){const i=e.indexOf(`
|
|
4
4
|
`);e=i>=0?e.slice(i+1):""}return e}function O(t){if(!/["\\\n\r]/.test(t))return`"${t}"`;let e="";for(;t.includes(`]${e}]`);)e+="=";return`[${e}[${t}]${e}]`}async function $(t){const e=async i=>{const n=await c.readdir(i,{withFileTypes:!0});let a=!0;for(const r of n){const s=d.join(i,r.name);r.isDirectory()&&await e(s)?await c.remove(s):a=!1}return a};await e(t)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.addVararg=c;function s(
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.addVararg=c;function s(r){r.vararg=!0,t(r.body.body)}function a(r){switch(r.type){case"FuncExpr":s(r);break;case"UnaryExpr":a(r.arg);break;case"BinaryExpr":a(r.left),a(r.right);break;case"TableExpr":for(const e of r.fields)e.kind==="index"&&a(e.key),a(e.value);break;case"IndexExpr":a(r.object),a(r.index);break;case"MemberExpr":a(r.object);break;case"CallExpr":a(r.callee);for(const e of r.args)a(e);break;case"MethodCallExpr":a(r.object);for(const e of r.args)a(e);break;case"ParenExpr":a(r.exp);break;default:break}}function t(r){for(const e of r)switch(e.type){case"DoStat":case"WhileStat":case"RepeatStat":case"ForNumStat":case"ForInStat":if(e.type==="WhileStat"&&a(e.cond),e.type==="RepeatStat"&&a(e.cond),e.type==="ForNumStat"&&(a(e.start),a(e.end),e.step&&a(e.step)),e.type==="ForInStat")for(const o of e.exps)a(o);t(e.body.body);break;case"IfStat":for(const o of e.clauses)a(o.cond),t(o.body.body);e.elseBody&&t(e.elseBody.body);break;case"FuncStat":case"LocalFuncStat":s(e.func);break;case"LocalStat":if(e.exps)for(const o of e.exps)a(o);break;case"AssignStat":for(const o of e.vars)a(o);for(const o of e.exps)a(o);break;case"CallStat":a(e.exp);break;case"ReturnStat":for(const o of e.exps)a(o);break;default:break}}function c(r){t(r.body)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.flattenControlFlow=C;function m(t){return{type:"NumberLiteral",raw:String(t),value:t}}function p(t){return{type:"NameExpr",name:t}}function f(t,
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.flattenControlFlow=C;function m(t){return{type:"NumberLiteral",raw:String(t),value:t}}function p(t){return{type:"NameExpr",name:t}}function f(t,o){return{type:"AssignStat",vars:t.map(e=>p(e)),exps:o}}function u(t,o){return f([t.stateName],[typeof o=="number"?m(o):o])}function k(t,o,e){return{type:"BinaryExpr",op:"or",left:{type:"BinaryExpr",op:"and",left:p(t),right:m(o)},right:m(e)}}function E(t,o,e){return{type:"BinaryExpr",op:t,left:o,right:e}}function S(t){const o=`__t${t.nextTemp}`;return t.nextTemp+=1,t.temps.push(o),o}function x(t){const o=t.nextState;return t.nextState+=1,t.states.set(o,[]),o}function s(t,o,e){t.states.get(o).push(e)}function F(t,o){for(const e of t)if(e.type==="LocalStat")for(const a of e.names)o.push(a.name);else if(e.type==="LocalFuncStat")o.push(e.name);else if(e.type==="ForNumStat")o.push(e.name),F(e.body.body,o);else if(e.type==="ForInStat")o.push(...e.names),F(e.body.body,o);else if(e.type==="DoStat"||e.type==="WhileStat"||e.type==="RepeatStat")F(e.body.body,o);else if(e.type==="IfStat"){for(const a of e.clauses)F(a.body.body,o);e.elseBody&&F(e.elseBody.body,o)}}function h(t,o,e,a){if(t.length===0)return o;const y=t.map(()=>x(e));for(let n=0;n<t.length;n+=1){const r=t[n];r.type==="LabelStat"&&e.userLabels.set(r.name,y[n])}for(let n=0;n<t.length;n+=1){const r=n+1<t.length?y[n+1]:o;I(e,y[n],t[n],r,a)}return y[0]}function I(t,o,e,a,y){switch(e.type){case"EmptyStat":case"LabelStat":s(t,o,u(t,a));return;case"AssignStat":case"CallStat":case"FuncStat":s(t,o,e),s(t,o,u(t,a));return;case"LocalStat":e.exps&&e.exps.length>0&&s(t,o,f(e.names.map(n=>n.name),e.exps)),s(t,o,u(t,a));return;case"LocalFuncStat":s(t,o,f([e.name],[e.func])),s(t,o,u(t,a));return;case"ReturnStat":s(t,o,e);return;case"BreakStat":s(t,o,u(t,y));return;case"GotoStat":{const n=t.userLabels.get(e.name);if(n===void 0)throw new Error(`goto \u672A\u5B9A\u4E49\u6807\u7B7E ::${e.name}::`);s(t,o,u(t,n));return}case"DoStat":s(t,o,u(t,h(e.body.body,a,t,y)));return;case"IfStat":{const n=S(t);let r=o;for(let l=0;l<e.clauses.length;l+=1){const b=l===e.clauses.length-1,d=h(e.clauses[l].body.body,a,t,y);let i=a;b?e.elseBody&&(i=h(e.elseBody.body,a,t,y)):i=x(t),s(t,r,f([n],[e.clauses[l].cond])),s(t,r,u(t,k(n,d,i))),b||(r=i)}e.clauses.length===0&&s(t,o,u(t,a));return}case"WhileStat":{const n=S(t),r=h(e.body.body,o,t,a);s(t,o,f([n],[e.cond])),s(t,o,u(t,k(n,r,a)));return}case"RepeatStat":{const n=S(t),r=x(t),l=h(e.body.body,r,t,a);s(t,o,u(t,l)),s(t,r,f([n],[e.cond])),s(t,r,u(t,k(n,a,l)));return}case"ForNumStat":{const n=e.name,r=S(t),l=S(t),b=S(t),d=x(t),i=x(t),L=x(t),g=x(t),N=h(e.body.body,g,t,a);s(t,o,f([n],[e.start])),s(t,o,f([r],[e.end])),s(t,o,f([l],[e.step??m(1)])),s(t,o,u(t,d)),s(t,d,f([b],[E(">=",p(l),m(0))])),s(t,d,u(t,k(b,i,L))),s(t,i,f([b],[E("<=",p(n),p(r))])),s(t,i,u(t,k(b,N,a))),s(t,L,f([b],[E(">=",p(n),p(r))])),s(t,L,u(t,k(b,N,a))),s(t,g,f([n],[E("+",p(n),p(l))])),s(t,g,u(t,d));return}case"ForInStat":{const n=S(t),r=S(t),l=S(t),b=S(t),d=x(t),i=[f([l],[p(e.names[0])]),...e.body.body],L=h(i,d,t,a),g=e.exps.slice();for(;g.length<3;)g.push({type:"NilLiteral"});s(t,o,f([n,r,l],g.slice(0,3))),s(t,o,u(t,d)),s(t,d,{type:"AssignStat",vars:e.names.map(N=>p(N)),exps:[{type:"CallExpr",callee:p(n),args:[p(r),p(l)]}]}),s(t,d,f([b],[E("~=",p(e.names[0]),{type:"NilLiteral"})])),s(t,d,u(t,k(b,L,a)));return}default:s(t,o,u(t,a))}}function v(t,o){const e=t.slice();let a=o>>>0||1;for(let y=e.length-1;y>0;y-=1){a=Math.imul(a,1664525)+1013904223>>>0;const n=a%(y+1),r=e[y];e[y]=e[n],e[n]=r}return e}function w(t,o){if(t.length===0)return t;const e={states:new Map,temps:[],nextState:1,nextTemp:1,stateName:"__s",userLabels:new Map},a=[];F(t,a);const y=h(t,0,e,0),n=[...new Set([...a,...e.temps,e.stateName])],r=v([...e.states.keys()],o).map(l=>({cond:E("==",p(e.stateName),m(l)),body:{type:"Block",body:e.states.get(l)}}));return[{type:"LocalStat",names:n.map(l=>({name:l}))},f([e.stateName],[m(y)]),{type:"WhileStat",cond:E("~=",p(e.stateName),m(0)),body:{type:"Block",body:[{type:"IfStat",clauses:r}]}}]}function c(t,o){switch(t.type){case"FuncExpr":B(t.body.body,o),t.body.body=w(t.body.body,o);break;case"UnaryExpr":c(t.arg,o);break;case"BinaryExpr":c(t.left,o),c(t.right,o);break;case"TableExpr":for(const e of t.fields)e.kind==="index"&&c(e.key,o),c(e.value,o);break;case"IndexExpr":c(t.object,o),c(t.index,o);break;case"MemberExpr":c(t.object,o);break;case"CallExpr":c(t.callee,o);for(const e of t.args)c(e,o);break;case"MethodCallExpr":c(t.object,o);for(const e of t.args)c(e,o);break;case"ParenExpr":c(t.exp,o);break;default:break}}function B(t,o){for(const e of t)switch(e.type){case"DoStat":case"WhileStat":case"RepeatStat":case"ForNumStat":case"ForInStat":if((e.type==="WhileStat"||e.type==="RepeatStat")&&c(e.cond,o),e.type==="ForNumStat"&&(c(e.start,o),c(e.end,o),e.step&&c(e.step,o)),e.type==="ForInStat")for(const a of e.exps)c(a,o);B(e.body.body,o);break;case"IfStat":for(const a of e.clauses)c(a.cond,o),B(a.body.body,o);e.elseBody&&B(e.elseBody.body,o);break;case"FuncStat":case"LocalFuncStat":B(e.func.body.body,o),e.func.body.body=w(e.func.body.body,o);break;case"LocalStat":if(e.exps)for(const a of e.exps)c(a,o);break;case"AssignStat":for(const a of e.vars)c(a,o);for(const a of e.exps)c(a,o);break;case"CallStat":c(e.exp,o);break;case"ReturnStat":for(const a of e.exps)c(a,o);break;default:break}}function C(t,o){B(t.body,o),t.body=w(t.body,o)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.shortName=
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.shortName=a,exports.renameLocals=c;const u=require("./reserved"),d=require("./scope"),l="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";function a(r){let e=r,t="";do t=l[e%l.length]+t,e=Math.floor(e/l.length)-1;while(e>=0);return u.luaKeywords.has(t)?`_${t}`:t}function c(r){let e=0;const t=new Set;(0,d.collectAndRename)(r,(o,s)=>{if(o.newName!==o.original)return;let n=a(e);for(e+=1;s.has(n)||t.has(n)||u.luaKeywords.has(n);)n=a(e),e+=1;o.newName=n,t.add(n),s.add(n)})}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.resolveLocal=i,exports.collectAndRename=x;function i(t,
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.resolveLocal=i,exports.collectAndRename=x;function i(t,o){let r=t;for(;r;){const e=r.locals.get(o);if(e)return e;r=r.parent}}function s(t){return{parent:t,locals:new Map}}function f(t,o){const r=t.locals.get(o);if(r)return r;const e={original:o,newName:o};return t.locals.set(o,e),e}function x(t,o){const r=new Set,e={locals:new Map};d(t.body,e,r,o)}function l(t,o,r,e){d(t.body,o,r,e)}function d(t,o,r,e){for(const c of t)b(c,o,r,e)}function m(t,o,r,e){const c=s(o),a=t.params.slice();for(let u=0;u<a.length;u+=1){const p=f(c,a[u]);e(p,r),t.params[u]=p.newName}l(t.body,c,r,e)}function b(t,o,r,e){switch(t.type){case"EmptyStat":case"BreakStat":case"GotoStat":case"LabelStat":return;case"DoStat":l(t.body,s(o),r,e);return;case"WhileStat":n(t.cond,o,r,e),l(t.body,s(o),r,e);return;case"RepeatStat":{const c=s(o);l(t.body,c,r,e),n(t.cond,c,r,e);return}case"IfStat":for(const c of t.clauses)n(c.cond,o,r,e),l(c.body,s(o),r,e);t.elseBody&&l(t.elseBody,s(o),r,e);return;case"ForNumStat":{n(t.start,o,r,e),n(t.end,o,r,e),t.step&&n(t.step,o,r,e);const c=s(o),a=f(c,t.name);e(a,r),t.name=a.newName,l(t.body,c,r,e);return}case"ForInStat":{for(const a of t.exps)n(a,o,r,e);const c=s(o);for(let a=0;a<t.names.length;a+=1){const u=f(c,t.names[a]);e(u,r),t.names[a]=u.newName}l(t.body,c,r,e);return}case"FuncStat":{const c=i(o,t.name.base);c&&(t.name.base=c.newName),m(t.func,o,r,e);return}case"LocalFuncStat":{const c=f(o,t.name);e(c,r),t.name=c.newName,m(t.func,o,r,e);return}case"LocalStat":if(t.exps)for(const c of t.exps)n(c,o,r,e);for(let c=0;c<t.names.length;c+=1){const a=f(o,t.names[c].name);e(a,r),t.names[c].name=a.newName}return;case"AssignStat":for(const c of t.vars)n(c,o,r,e);for(const c of t.exps)n(c,o,r,e);return;case"CallStat":n(t.exp,o,r,e);return;case"ReturnStat":for(const c of t.exps)n(c,o,r,e);return}}function n(t,o,r,e){switch(t.type){case"NilLiteral":case"BoolLiteral":case"NumberLiteral":case"StringLiteral":case"VarargExpr":return;case"NameExpr":{const c=i(o,t.name);c&&(t.name=c.newName);return}case"UnaryExpr":n(t.arg,o,r,e);return;case"BinaryExpr":n(t.left,o,r,e),n(t.right,o,r,e);return;case"FuncExpr":m(t,o,r,e);return;case"TableExpr":for(const c of t.fields)c.kind==="index"&&n(c.key,o,r,e),n(c.value,o,r,e);return;case"IndexExpr":n(t.object,o,r,e),n(t.index,o,r,e);return;case"MemberExpr":n(t.object,o,r,e);return;case"CallExpr":n(t.callee,o,r,e);for(const c of t.args)n(c,o,r,e);return;case"MethodCallExpr":n(t.object,o,r,e);for(const c of t.args)n(c,o,r,e);return;case"ParenExpr":n(t.exp,o,r,e);return}}
|
package/docs/api/ai.md
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
# AI 模块 (AI)
|
|
2
|
+
|
|
3
|
+
AI 模块让脚本直接在手机上调用大模型:看图回答问题、做是非判断、从屏幕提取结构化数据、多轮对话。
|
|
4
|
+
|
|
5
|
+
支持三种接口协议,覆盖绝大多数模型服务:
|
|
6
|
+
|
|
7
|
+
| `protocol` | 适用服务 |
|
|
8
|
+
| ----------- | ------------------------------------------------------------------------------------- |
|
|
9
|
+
| `openai` | OpenAI、DeepSeek、通义千问、Kimi、智谱、SiliconFlow、OpenRouter、本机 Ollama / vLLM 等 |
|
|
10
|
+
| `anthropic` | Anthropic Claude |
|
|
11
|
+
| `gemini` | Google Gemini |
|
|
12
|
+
|
|
13
|
+
模型配置(地址、Key、模型名)直接写在脚本里,脚本开头 `ai.setProvider` 一次即可;脚本停止后配置自动清空,不会存到手机上。
|
|
14
|
+
|
|
15
|
+
所有方法同步阻塞,失败时返回 `null` / `false`,原因用 `ai.lastError()` 查看。
|
|
16
|
+
|
|
17
|
+
## 快速开始
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
// 1. 脚本开头登记模型(只需一次)。通义千问 Qwen3.5 起原生多模态,看图、问字都能用。
|
|
21
|
+
ai.setProvider({
|
|
22
|
+
name: "通义千问",
|
|
23
|
+
protocol: "openai",
|
|
24
|
+
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
25
|
+
apiKey: "sk-xxxxxxxx",
|
|
26
|
+
model: "qwen3.5-flash",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 2. 直接问
|
|
30
|
+
logi(ai.ask("用一句话介绍你自己"));
|
|
31
|
+
|
|
32
|
+
// 3. 看着屏幕判断
|
|
33
|
+
if (ai.judge("当前是否已经登录成功?", { image: "screen" })) {
|
|
34
|
+
logi("已登录");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 4. 从屏幕某个区域提取数据
|
|
38
|
+
const data = ai.extract("提取验证码", {
|
|
39
|
+
image: "screen",
|
|
40
|
+
x: 100,
|
|
41
|
+
y: 800,
|
|
42
|
+
ex: 600,
|
|
43
|
+
ey: 900,
|
|
44
|
+
schema: { code: "string" },
|
|
45
|
+
});
|
|
46
|
+
if (data) logi(data.code);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 图片参数
|
|
50
|
+
|
|
51
|
+
图片参数和 image 模块**完全一样**,没有单独的截图开关:
|
|
52
|
+
|
|
53
|
+
| 参数 | 含义 |
|
|
54
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------ |
|
|
55
|
+
| `image` | `imageId`,与所有 `image.*` 函数的 `imageId` 参数一样:`image.captureScreen()` 等返回的图片 ID、`"screen"`(当前屏幕)、res 目录或手机绝对路径的图片文件 |
|
|
56
|
+
| `x`, `y`, `ex`, `ey` | 对 `image` 裁剪,同 `image.clip(imageId, x, y, ex, ey)`:左上角 / 右下角坐标,全 0 不裁剪。只写区域不写 `image` 时按 `"screen"` |
|
|
57
|
+
| `images` | 多张图的数组,元素同 `image`,不裁剪 |
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// 当前全屏
|
|
61
|
+
ai.ask("屏幕上有几个按钮?", { image: "screen" });
|
|
62
|
+
|
|
63
|
+
// 屏幕区域:左上 (0, 200) 到右下 (750, 600),与 image.clip 的坐标写法相同
|
|
64
|
+
ai.ask("这一块显示的价格是多少?", { image: "screen", x: 0, y: 200, ex: 750, ey: 600 });
|
|
65
|
+
|
|
66
|
+
// 已有的图片 ID(image 模块产出的都可以)
|
|
67
|
+
const id = image.captureScreen();
|
|
68
|
+
ai.ask("描述这张图", { image: id, x: 0, y: 0, ex: 750, ey: 400 });
|
|
69
|
+
image.release(id);
|
|
70
|
+
|
|
71
|
+
// 图片文件
|
|
72
|
+
ai.ask("这张图里是什么", { image: "sample.png" });
|
|
73
|
+
|
|
74
|
+
// 一次传多张:模板图 + 当前屏幕
|
|
75
|
+
ai.judge("第二张图里有没有第一张图的图标?", { images: ["icon.png", "screen"] });
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 费用说明
|
|
79
|
+
|
|
80
|
+
模型按 token 计费,其中图片是大头:一张 iPhone 全屏截图约 1400 ~ 1500 token,文字提问通常只有几十个。
|
|
81
|
+
|
|
82
|
+
- **能裁区域就不发全屏。** 只发需要看的那一块,token 通常能少 5 ~ 10 倍。
|
|
83
|
+
- **循环里保持提示词不变。** 三家服务都有「前缀缓存」:连续请求里开头相同的部分按约 1/10 计费。AI 模块已经把 `system` 排最前、文字排在图片之前,循环里每次只有图片变,`system` 和问题文字都能命中缓存。不要往 `system` 或问题里拼时间、序号这类每次都变的内容。
|
|
84
|
+
- **不同场景用不同模型。** 便宜的文本模型做默认,视觉模型只在需要看图时用(见 [setProvider](#setprovider---登记或更新模型供应商))。
|
|
85
|
+
|
|
86
|
+
## 数据类型
|
|
87
|
+
|
|
88
|
+
### AiProvider
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
interface AiProvider {
|
|
92
|
+
id: string; // 供应商 id,不传时自动生成
|
|
93
|
+
name: string; // 展示名,例如 "DeepSeek",调用时可用它选模型
|
|
94
|
+
protocol: "openai" | "anthropic" | "gemini";
|
|
95
|
+
baseUrl: string; // 接口根地址,例如 https://api.openai.com/v1
|
|
96
|
+
apiKey: string; // API Key;openai 协议连本机模型时可为空
|
|
97
|
+
model: string; // 默认模型名
|
|
98
|
+
headers: Record<string, string>; // 额外请求头,某些网关需要
|
|
99
|
+
temperature?: number; // 默认温度;不传则不发该字段,用模型自己的默认值
|
|
100
|
+
maxTokens?: number; // 默认最大输出 token;不传则不发,用模型默认值(Anthropic 必填,缺省 2048)
|
|
101
|
+
supportsVision: boolean; // 是否支持图片输入,默认 true
|
|
102
|
+
isDefault: boolean; // 是否为默认供应商
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### AiOptions
|
|
107
|
+
|
|
108
|
+
`ask` / `judge` / `extract` / `chat` 的第二个参数,所有字段可选:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
interface AiOptions {
|
|
112
|
+
provider?: string; // 用哪个供应商:id 或 name;不传用默认
|
|
113
|
+
model?: string; // 临时换模型,覆盖供应商默认模型
|
|
114
|
+
temperature?: number; // 覆盖默认温度
|
|
115
|
+
maxTokens?: number; // 覆盖默认最大输出 token
|
|
116
|
+
timeout?: number; // 超时毫秒,默认 90000
|
|
117
|
+
json?: boolean; // 要求模型输出 JSON(extract 自动开启)
|
|
118
|
+
system?: string; // 系统提示(chat 不用这个,写在 messages 里)
|
|
119
|
+
image?: string; // imageId,同 image 模块:图片 ID / "screen" / 图片路径
|
|
120
|
+
x?: number; // 裁剪区域左上角 X,同 image.clip
|
|
121
|
+
y?: number; // 裁剪区域左上角 Y
|
|
122
|
+
ex?: number; // 裁剪区域右下角 X,全 0 不裁剪
|
|
123
|
+
ey?: number; // 裁剪区域右下角 Y
|
|
124
|
+
images?: string[]; // 多张图,元素同 image,不裁剪
|
|
125
|
+
schema?: string | object; // 仅 extract:期望的 JSON 结构说明
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### AiMessage
|
|
130
|
+
|
|
131
|
+
`chat` 的消息项,图片参数与 `AiOptions` 相同:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
interface AiMessage {
|
|
135
|
+
role: "system" | "user" | "assistant";
|
|
136
|
+
content: string;
|
|
137
|
+
image?: string;
|
|
138
|
+
x?: number;
|
|
139
|
+
y?: number;
|
|
140
|
+
ex?: number;
|
|
141
|
+
ey?: number;
|
|
142
|
+
images?: string[];
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## API 参考
|
|
147
|
+
|
|
148
|
+
### setProvider - 登记或更新模型供应商。
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
function setProvider(config: Partial<AiProvider>): AiProvider | null;
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**参数:**
|
|
155
|
+
|
|
156
|
+
| 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
|
|
157
|
+
| -------- | --------------------- | -------- | ------ | ----------------------------------------------------------------------------------- |
|
|
158
|
+
| `config` | `Partial<AiProvider>` | 是 | - | 新建至少要有 `baseUrl`、`model`(`protocol` 缺省 openai);同 `id` 更新时没传的字段沿用旧值 |
|
|
159
|
+
|
|
160
|
+
**返回值:**
|
|
161
|
+
|
|
162
|
+
| 类型 | 描述 |
|
|
163
|
+
| ------------ | ------------------------------------------------------------------------------- |
|
|
164
|
+
| `AiProvider` | 保存后的完整配置;`protocol` 不是三者之一或缺 `baseUrl` / `model` 时返回 `null` |
|
|
165
|
+
|
|
166
|
+
第一个登记的供应商自动成为默认;`isDefault: true` 会把默认切到它。
|
|
167
|
+
|
|
168
|
+
**示例:**
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
// 只用一个模型(通义能看图)
|
|
172
|
+
ai.setProvider({
|
|
173
|
+
name: "通义千问",
|
|
174
|
+
protocol: "openai",
|
|
175
|
+
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
176
|
+
apiKey: "sk-xxxxxxxx",
|
|
177
|
+
model: "qwen3.5-flash",
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// 多个模型省钱:便宜的文本模型做默认,视觉模型单独一个,用到时按 name 选
|
|
181
|
+
ai.setProvider({
|
|
182
|
+
name: "文本",
|
|
183
|
+
protocol: "openai",
|
|
184
|
+
baseUrl: "https://api.deepseek.com",
|
|
185
|
+
apiKey: "sk-xxxxxxxx",
|
|
186
|
+
model: "deepseek-v4-flash",
|
|
187
|
+
supportsVision: false,
|
|
188
|
+
isDefault: true,
|
|
189
|
+
});
|
|
190
|
+
ai.setProvider({
|
|
191
|
+
name: "视觉",
|
|
192
|
+
protocol: "openai",
|
|
193
|
+
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
194
|
+
apiKey: "sk-xxxxxxxx",
|
|
195
|
+
model: "qwen3.5-flash",
|
|
196
|
+
});
|
|
197
|
+
ai.ask("把这段话翻译成英文:你好"); // 走默认的「文本」
|
|
198
|
+
ai.judge("是否出现弹窗?", { image: "screen", provider: "视觉" }); // 看图时才用视觉模型
|
|
199
|
+
|
|
200
|
+
// 同一供应商只换模型,不必再登记
|
|
201
|
+
ai.ask("复杂推理题……", { model: "deepseek-v4-pro" });
|
|
202
|
+
|
|
203
|
+
// Anthropic(国内多数网关走 openai 兼容口即可,只有官方 Claude 才用这条协议)
|
|
204
|
+
ai.setProvider({
|
|
205
|
+
name: "Claude",
|
|
206
|
+
protocol: "anthropic",
|
|
207
|
+
baseUrl: "https://api.anthropic.com",
|
|
208
|
+
apiKey: "sk-ant-...",
|
|
209
|
+
model: "claude-haiku-4-5",
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// 本机 Ollama,不需要 Key
|
|
213
|
+
ai.setProvider({
|
|
214
|
+
name: "本机",
|
|
215
|
+
protocol: "openai",
|
|
216
|
+
baseUrl: "http://192.168.1.10:11434/v1",
|
|
217
|
+
model: "qwen3:8b",
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### getProviders - 列出已登记的供应商。
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
function getProviders(): AiProvider[];
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**返回值:**
|
|
228
|
+
|
|
229
|
+
| 类型 | 描述 |
|
|
230
|
+
| -------------- | -------------------------- |
|
|
231
|
+
| `AiProvider[]` | 供应商数组,默认项排在最前 |
|
|
232
|
+
|
|
233
|
+
**示例:**
|
|
234
|
+
|
|
235
|
+
```javascript
|
|
236
|
+
ai.getProviders().forEach((p) => logi(`${p.name} -> ${p.model}`));
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### getDefaultProvider - 获取默认供应商。
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
function getDefaultProvider(): AiProvider | null;
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**返回值:**
|
|
246
|
+
|
|
247
|
+
| 类型 | 描述 |
|
|
248
|
+
| ------------ | ------------------------------------- |
|
|
249
|
+
| `AiProvider` | 默认供应商;一个都没登记时返回 `null` |
|
|
250
|
+
|
|
251
|
+
**示例:**
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
const p = ai.getDefaultProvider();
|
|
255
|
+
if (!p) loge("还没配置模型");
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### setDefaultProvider - 切换默认供应商。
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
function setDefaultProvider(id: string): boolean;
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**参数:**
|
|
265
|
+
|
|
266
|
+
| 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
|
|
267
|
+
| ------ | ------ | -------- | ------ | --------- |
|
|
268
|
+
| `id` | string | 是 | - | 供应商 id 或 name |
|
|
269
|
+
|
|
270
|
+
**返回值:**
|
|
271
|
+
|
|
272
|
+
| 类型 | 描述 |
|
|
273
|
+
| --------- | ------------------ |
|
|
274
|
+
| `boolean` | id 或 name 存在返回 `true` |
|
|
275
|
+
|
|
276
|
+
**示例:**
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
ai.setProvider({ id: "cheap", protocol: "openai", baseUrl: "https://api.deepseek.com", apiKey: "sk-...", model: "deepseek-v4-flash" });
|
|
280
|
+
ai.setDefaultProvider("cheap");
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### removeProvider - 删除供应商。
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
function removeProvider(id: string): boolean;
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**参数:**
|
|
290
|
+
|
|
291
|
+
| 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
|
|
292
|
+
| ------ | ------ | -------- | ------ | --------- |
|
|
293
|
+
| `id` | string | 是 | - | 供应商 id 或 name |
|
|
294
|
+
|
|
295
|
+
**返回值:**
|
|
296
|
+
|
|
297
|
+
| 类型 | 描述 |
|
|
298
|
+
| --------- | --------------------- |
|
|
299
|
+
| `boolean` | 存在并删除返回 `true` |
|
|
300
|
+
|
|
301
|
+
删掉的是默认供应商时,默认自动转给剩下的第一个。
|
|
302
|
+
|
|
303
|
+
### ask - 单轮提问,可附带图片。
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
function ask(prompt: string, options?: AiOptions): string | null;
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**参数:**
|
|
310
|
+
|
|
311
|
+
| 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
|
|
312
|
+
| --------- | ----------- | -------- | ------ | ------------------------------------------ |
|
|
313
|
+
| `prompt` | string | 是 | - | 问题 |
|
|
314
|
+
| `options` | `AiOptions` | 否 | `{}` | 见 [AiOptions](#aioptions),图片参数同 image 模块 |
|
|
315
|
+
|
|
316
|
+
**返回值:**
|
|
317
|
+
|
|
318
|
+
| 类型 | 描述 |
|
|
319
|
+
| -------- | --------------------------------------------------- |
|
|
320
|
+
| `string` | 模型回复文本;失败返回 `null`,原因看 `lastError()` |
|
|
321
|
+
|
|
322
|
+
**示例:**
|
|
323
|
+
|
|
324
|
+
```javascript
|
|
325
|
+
// 纯文本
|
|
326
|
+
const t = ai.ask("北京到上海高铁大概几小时?");
|
|
327
|
+
|
|
328
|
+
// 带系统提示 + 当前屏幕
|
|
329
|
+
const desc = ai.ask("描述当前页面", {
|
|
330
|
+
system: "你是手机自动化助手,回答尽量简短。",
|
|
331
|
+
image: "screen",
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// 只看屏幕某个区域
|
|
335
|
+
const price = ai.ask("这个区域里的价格是多少?只回答数字", { image: "screen", x: 0, y: 300, ex: 750, ey: 500 });
|
|
336
|
+
|
|
337
|
+
// 失败处理
|
|
338
|
+
const r = ai.ask("你好");
|
|
339
|
+
if (r === null) loge(ai.lastError());
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### judge - 是非判断。
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
function judge(question: string, options?: AiOptions): boolean;
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
让模型只回答 yes / no,并解析成布尔值。适合做流程分支:「有没有弹窗」「是否到了首页」。
|
|
349
|
+
|
|
350
|
+
**参数:**
|
|
351
|
+
|
|
352
|
+
| 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
|
|
353
|
+
| ---------- | ----------- | -------- | ------ | ------------ |
|
|
354
|
+
| `question` | string | 是 | - | 要判断的问题 |
|
|
355
|
+
| `options` | `AiOptions` | 否 | `{}` | 同 `ask` |
|
|
356
|
+
|
|
357
|
+
**返回值:**
|
|
358
|
+
|
|
359
|
+
| 类型 | 描述 |
|
|
360
|
+
| --------- | ---------------------------------------------------------------------------- |
|
|
361
|
+
| `boolean` | 模型回答肯定为 `true`;否定或请求失败为 `false`,失败时 `lastError()` 有内容 |
|
|
362
|
+
|
|
363
|
+
**示例:**
|
|
364
|
+
|
|
365
|
+
```javascript
|
|
366
|
+
if (ai.judge("屏幕上是否出现了「领取成功」?", { image: "screen" })) {
|
|
367
|
+
logi("领取成功");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 只看顶部区域,省 token
|
|
371
|
+
while (!ai.judge("顶部是否显示「首页」?", { image: "screen", x: 0, y: 0, ex: 0, ey: 200 })) {
|
|
372
|
+
action.click(375, 1500);
|
|
373
|
+
sleep(1000);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// 区分「回答否」和「请求失败」
|
|
377
|
+
const ok = ai.judge("是否已登录?", { image: "screen" });
|
|
378
|
+
if (!ok && ai.lastError()) loge("请求失败: " + ai.lastError());
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### extract - 从图片或文本里提取结构化数据。
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
function extract(prompt: string, options?: AiOptions): any | null;
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
要求模型输出 JSON 并自动解析成对象或数组。`schema` 用来告诉模型你想要的结构。
|
|
388
|
+
|
|
389
|
+
**参数:**
|
|
390
|
+
|
|
391
|
+
| 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
|
|
392
|
+
| --------- | ----------- | -------- | ------ | ----------------------------------------- |
|
|
393
|
+
| `prompt` | string | 是 | - | 提取要求 |
|
|
394
|
+
| `options` | `AiOptions` | 否 | `{}` | 同 `ask`,另支持 `schema`(字符串或对象) |
|
|
395
|
+
|
|
396
|
+
**返回值:**
|
|
397
|
+
|
|
398
|
+
| 类型 | 描述 |
|
|
399
|
+
| -------------- | --------------------------------------------------------- |
|
|
400
|
+
| `object/array` | 解析后的数据;模型没有输出合法 JSON 或请求失败返回 `null` |
|
|
401
|
+
|
|
402
|
+
**示例:**
|
|
403
|
+
|
|
404
|
+
```javascript
|
|
405
|
+
// 提取验证码
|
|
406
|
+
const d = ai.extract("提取图中的验证码", {
|
|
407
|
+
image: "screen",
|
|
408
|
+
x: 100,
|
|
409
|
+
y: 800,
|
|
410
|
+
ex: 600,
|
|
411
|
+
ey: 900,
|
|
412
|
+
schema: { code: "string" },
|
|
413
|
+
});
|
|
414
|
+
if (d) ime.input(d.code);
|
|
415
|
+
|
|
416
|
+
// 提取列表
|
|
417
|
+
const list = ai.extract("列出屏幕上所有商品的名称和价格", {
|
|
418
|
+
image: "screen",
|
|
419
|
+
schema: [{ name: "string", price: "number" }],
|
|
420
|
+
});
|
|
421
|
+
if (list) list.forEach((it) => logi(`${it.name}: ${it.price}`));
|
|
422
|
+
|
|
423
|
+
// 让模型给坐标(配合 action.click)
|
|
424
|
+
const pos = ai.extract("找到「立即购买」按钮,给出它的中心点像素坐标", {
|
|
425
|
+
image: "screen",
|
|
426
|
+
schema: { x: "number", y: "number" },
|
|
427
|
+
});
|
|
428
|
+
if (pos) action.click(pos.x, pos.y);
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### chat - 多轮对话。
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
function chat(messages: (AiMessage | string)[], options?: AiOptions): string | null;
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
自己维护完整对话历史时用它;`ask` 是它的单轮封装。模块不记会话,每次都要把到目前为止的消息整表传进来。
|
|
438
|
+
|
|
439
|
+
图片写在**那一条 user 消息**上,不要用 `options.image`。`options.system` 也不生效,系统提示写成第一条 `{ role: "system", content: "..." }`。
|
|
440
|
+
|
|
441
|
+
失败返回 `null`:不要把空回复推进历史,否则下一轮变成 `user → assistant(null) → user`,Anthropic 会 400。Anthropic 协议下模块会自动合并连续同角色、并在首条是 assistant 时垫一条占位 user;OpenAI 兼容口更宽松,不必为此改脚本。
|
|
442
|
+
|
|
443
|
+
**参数:**
|
|
444
|
+
|
|
445
|
+
| 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
|
|
446
|
+
| ---------- | ------------------------- | -------- | ------ | ----------------------------------------------------------------------- |
|
|
447
|
+
| `messages` | `(AiMessage \| string)[]` | 是 | - | 消息数组;直接写字符串等于 `user` 消息;图片参数写在消息里 |
|
|
448
|
+
| `options` | `AiOptions` | 否 | `{}` | `provider` / `model` / `temperature` / `maxTokens` / `json` / `timeout` |
|
|
449
|
+
|
|
450
|
+
**返回值:**
|
|
451
|
+
|
|
452
|
+
| 类型 | 描述 |
|
|
453
|
+
| -------- | ------------------------- |
|
|
454
|
+
| `string` | 回复文本;失败返回 `null` |
|
|
455
|
+
|
|
456
|
+
**示例:**
|
|
457
|
+
|
|
458
|
+
```javascript
|
|
459
|
+
const history = [{ role: "system", content: "你是手机自动化助手" }];
|
|
460
|
+
|
|
461
|
+
function talk(content, extra) {
|
|
462
|
+
history.push({ role: "user", content, ...extra });
|
|
463
|
+
const reply = ai.chat(history);
|
|
464
|
+
if (reply == null) {
|
|
465
|
+
history.pop();
|
|
466
|
+
loge(ai.lastError());
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
history.push({ role: "assistant", content: reply });
|
|
470
|
+
return reply;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
talk("看下现在在哪个页面", { image: "screen" });
|
|
474
|
+
talk("那下一步该点哪里?");
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### parseJson - 从模型文本里解析 JSON。
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
function parseJson(text: string): any | null;
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
自动剥掉 Markdown 代码块和前后的解释文字。`extract` 内部就是用它解析;自己用 `ask` 要 JSON 时也可以调它。
|
|
484
|
+
|
|
485
|
+
**参数:**
|
|
486
|
+
|
|
487
|
+
| 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
|
|
488
|
+
| ------ | ------ | -------- | ------ | -------- |
|
|
489
|
+
| `text` | string | 是 | - | 模型输出 |
|
|
490
|
+
|
|
491
|
+
**返回值:**
|
|
492
|
+
|
|
493
|
+
| 类型 | 描述 |
|
|
494
|
+
| -------------- | ------------------------- |
|
|
495
|
+
| `object/array` | 解析结果;失败返回 `null` |
|
|
496
|
+
|
|
497
|
+
**示例:**
|
|
498
|
+
|
|
499
|
+
```javascript
|
|
500
|
+
const raw = ai.ask('用 JSON 输出三个水果名,格式 {"fruits": []}');
|
|
501
|
+
const obj = ai.parseJson(raw);
|
|
502
|
+
if (obj) logi(obj.fruits.join(", "));
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### lastError - 最近一次调用失败的原因。
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
function lastError(): string | null;
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**返回值:**
|
|
512
|
+
|
|
513
|
+
| 类型 | 描述 |
|
|
514
|
+
| -------- | ------------------------------------- |
|
|
515
|
+
| `string` | 错误文案;上一次调用成功时返回 `null` |
|
|
516
|
+
|
|
517
|
+
常见错误:未登记供应商、Key 错误 / 余额不足(接口返回的原文)、模型不支持图片、图片无效或裁剪越界、超时、模型没有输出 JSON。
|
|
518
|
+
|
|
519
|
+
**示例:**
|
|
520
|
+
|
|
521
|
+
```javascript
|
|
522
|
+
const t = ai.ask("你好");
|
|
523
|
+
if (t === null) loge("AI 调用失败: " + ai.lastError());
|
|
524
|
+
```
|
package/docs/api/opencv.md
CHANGED
|
@@ -186,6 +186,8 @@ function imwrite(path: string, mat: Mat): boolean;
|
|
|
186
186
|
| `path` | string | 是 | - | 本地绝对路径(写入磁盘,不是 msbundle) |
|
|
187
187
|
| `mat` | Mat | 是 | - | Mat 句柄 |
|
|
188
188
|
|
|
189
|
+
**说明:** 格式由 `path` 的扩展名决定,可用 `png` / `apng` / `jpg` / `jpeg` / `jpe` / `bmp` / `dib` / `webp` / `ppm` / `pgm` / `pbm` / `pnm` / `pam` / `sr` / `ras`;其它扩展名(如 `tiff`)或通道数不是 1 / 3 / 4 的 Mat 会直接返回 `false`。
|
|
190
|
+
|
|
189
191
|
**返回值:** 成功 `true`,失败 `false`。
|
|
190
192
|
|
|
191
193
|
**示例:**
|
|
@@ -208,14 +210,16 @@ function imencode(ext: string, mat: Mat, quality?: number): string | null;
|
|
|
208
210
|
| --- | --- | --- | --- | --- |
|
|
209
211
|
| `ext` | string | 是 | - | 如 `.png` / `.jpg` / `png` |
|
|
210
212
|
| `mat` | Mat | 是 | - | Mat 句柄 |
|
|
211
|
-
| `quality` | number | 否 | 90 | JPEG
|
|
213
|
+
| `quality` | number | 否 | 90 | JPEG 质量,取值 1–100(仅 jpg 有效) |
|
|
214
|
+
|
|
215
|
+
**说明:** 可用扩展名与通道限制同 `imwrite`,不支持的格式返回 `null`。
|
|
212
216
|
|
|
213
217
|
**返回值:** 纯 base64 字符串(无 data URI 前缀);失败 `null`。
|
|
214
218
|
|
|
215
219
|
**示例:**
|
|
216
220
|
|
|
217
221
|
```javascript
|
|
218
|
-
const b64 = cv.imencode(".jpg", mat,
|
|
222
|
+
const b64 = cv.imencode(".jpg", mat, 85);
|
|
219
223
|
```
|
|
220
224
|
|
|
221
225
|
### fromImageId / toImageId - 与 image 互通
|
|
@@ -567,6 +571,9 @@ cv.release(out);
|
|
|
567
571
|
|
|
568
572
|
就地修改并返回**同一句柄**。`color` 为 BGR 数组或 `#RRGGBB`。
|
|
569
573
|
|
|
574
|
+
`thickness` 上限为 32767。`rectangle` / `circle` 的负值表示填充;`line` / `putText` / `polylines` /
|
|
575
|
+
`arrowedLine` / `drawMarker` 只能描边,传 0 或负值按 1 处理。
|
|
576
|
+
|
|
570
577
|
```typescript
|
|
571
578
|
function rectangle(mat: Mat, options: RectangleOptions): Mat | null;
|
|
572
579
|
function circle(mat: Mat, options: CircleOptions): Mat | null;
|