lanhu-design-mcp 0.2.2 → 0.3.0
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/index.js +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ Additional focus from the caller (business context / priorities \u2014 reflect t
|
|
|
57
57
|
"${e.trim()}"`:"";return CE+n+o}var m_=null;async function AE(t,e,r){return new Promise((n,o)=>{let s=new URL(t),i=s.protocol==="https:"?yE:_E,a=JSON.stringify(e),c=i({hostname:s.hostname,port:s.port||(s.protocol==="https:"?443:80),path:s.pathname+s.search,method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(a),...r?{Authorization:`Bearer ${r}`}:{}}},l=>{let d=[];l.on("data",f=>d.push(f)),l.on("end",()=>{let f=Buffer.concat(d).toString("utf8");try{n({status:l.statusCode||0,json:JSON.parse(f)})}catch{o(new Error(`\u89C6\u89C9\u6A21\u578B API \u8FD4\u56DE HTTP ${l.statusCode}\uFF0C\u54CD\u5E94\u975E JSON\uFF1A${f.slice(0,120)}`))}})}),u=Number(process.env.LANHU_VISION_TIMEOUT_MS)||12e4;c.setTimeout(u,()=>{c.destroy(new Error(`\u89C6\u89C9\u6A21\u578B API \u8BF7\u6C42\u8D85\u65F6\uFF08${u}ms\uFF09\uFF0C\u53EF\u8BBE LANHU_VISION_TIMEOUT_MS \u8C03\u6574`))}),c.on("error",o),c.write(a),c.end()})}var Rd=new Map;async function Jr({images:t=[],text:e,detail:r="auto"}){EE(t);let n=RE(t,e,r),o=NE(n);if(o)return o;let s=Rd.get(n);if(s)return console.error("[vision] \u76F8\u540C\u8BF7\u6C42\u5728\u9014\uFF0C\u590D\u7528\u5176\u7ED3\u679C"),s;let i=jE({images:t,text:e,detail:r},n).finally(()=>Rd.delete(n));return Rd.set(n,i),i}async function jE({images:t=[],text:e,detail:r="auto"},n){let o=[{type:"text",text:IE(e)},...t.map(l=>({type:"image_url",image_url:{url:l.startsWith("data:")?l:`data:image/png;base64,${l}`,detail:r}}))],s={model:Ho,messages:[{role:"user",content:o}],response_format:{type:"json_object"},max_tokens:SE,temperature:0,top_p:.95};/glm/i.test(Ho)&&(s.max_tokens=Number(process.env.LANHU_VISION_MAX_TOKENS)||16384,s.thinking={type:"enabled",clear_thinking:!1},s.reasoning_effort="max");let i=null,a=m_??kE,c=!1,u=!1;for(let l=1;l<=Vo;l++){let d=Date.now(),f=0,m=null;try{let y=await AE(f_(a),s,g_);f=y.status,m=y.json}catch(y){f=0,i=y instanceof Error?y:new Error(String(y))}if(f===404&&!c){c=!0,a=!a,m_=a,l--,console.error(`[vision] HTTP 404 \u2192 \u5207\u6362\u7AEF\u70B9\u4E3A ${f_(a)}`);continue}if(f===400&&s.response_format&&!u&&/response_format|json_object/i.test(JSON.stringify(m?.error||""))){u=!0,delete s.response_format,l--,console.error("[vision] \u6A21\u578B\u62D2\u7EDD response_format \u2192 \u964D\u7EA7\u4E3A\u63D0\u793A\u8BCD\u7EA6\u675F\u91CD\u8BD5");continue}if((f===0||f===429||f>=500)&&l<Vo){let y=1e3*l,_=f===0?"\u7F51\u7EDC/\u8D85\u65F6":`HTTP ${f}`;i==null&&(i=new Error(`\u89C6\u89C9\u6A21\u578B API HTTP ${f}\uFF1A${m?.error?.message||m?.message||JSON.stringify(m).slice(0,150)}`)),console.error(`[vision] ${_}\uFF0C${y}ms \u540E\u91CD\u8BD5\uFF08attempt=${l}/${Vo}\uFF09\uFF1A${i.message}`),await new Promise(w=>setTimeout(w,y));continue}if(f<200||f>=300){let y=m?.error?.message||m?.message||JSON.stringify(m).slice(0,150);throw new Error(`\u89C6\u89C9\u6A21\u578B API HTTP ${f}\uFF1A${y}`)}let h=m?.choices?.[0],g=h?.message?.content||"";if(console.error(`[vision] ${Ho} ${t.length}\u56FE detail=${r} attempt=${l} \u2192 HTTP ${f} \u8017\u65F6${Date.now()-d}ms finish=${h?.finish_reason} tokens=${m?.usage?.total_tokens} contentLen=${g.length}`),g.trim()){let y=g.replace(/^```(?:json)?\s*/i,"").replace(/\s*```$/,"");try{let _=JSON.parse(y);return OE(n,_),_}catch{return{_raw:g}}}i=new Error(`\u89C6\u89C9\u6A21\u578B\u8FD4\u56DE\u7A7A content\uFF08HTTP ${f}\uFF0Cfinish_reason=${h?.finish_reason??"\u65E0choices\u5B57\u6BB5"}\uFF0C\u54CD\u5E94\u4F53=${JSON.stringify(m).slice(0,120)}\uFF09`),l<Vo&&(console.error(`[vision] \u7A7A\u54CD\u5E94\uFF0C${l}s \u540E\u91CD\u8BD5\u2026`),await new Promise(y=>setTimeout(y,1e3*l)))}throw new Error(`${i.message}\u3002\u91CD\u8BD5 ${Vo} \u6B21\u4ECD\u4E3A\u7A7A\uFF0C\u591A\u4E3A\u6A21\u578B\u7AEF/\u4EE3\u7406\u5F02\u5E38\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002`)}function ME(){let t=r=>{let n=[],o=r;for(;o&&o!==document.body;){let s=o.tagName.toLowerCase(),i=o.getAttribute("id");if(i)s+=`#${i}`;else{let a=(o.getAttribute("class")||"").trim().split(/\s+/).filter(Boolean)[0];a&&(s+=`.${a}`)}n.unshift(s),o=o.parentElement}return n.join(" > ")||r.tagName.toLowerCase()},e=[];for(let r of Array.from(document.querySelectorAll("body *"))){let n=r.getBoundingClientRect();if(n.width<=0||n.height<=0||r.tagName==="SVG")continue;let o=getComputedStyle(r);if(o.display==="none"||o.visibility==="hidden"||Number(o.opacity)===0)continue;let s=Array.from(r.childNodes).filter(u=>u.nodeType===3).map(u=>(u.textContent||"").trim()).join(" ").trim();if(r.children.length>0&&!s)continue;let i=parseFloat(o.lineHeight),a="",c=r.parentElement;for(let u=0;u<3&&c;u++){let l=getComputedStyle(c).backgroundColor;if(l&&l!=="rgba(0, 0, 0, 0)"&&l!=="transparent"){a=l;break}c=c.parentElement}e.push({selector:t(r),text:s,x:Math.round((n.x+window.scrollX)*100)/100,y:Math.round((n.y+window.scrollY)*100)/100,width:Math.round(n.width*100)/100,height:Math.round(n.height*100)/100,color:o.color,backgroundColor:o.backgroundColor,bgAncestor:a,fontSize:parseFloat(o.fontSize)||0,fontWeight:Number(o.fontWeight)||0,lineHeight:Number.isFinite(i)?Math.round(i*100)/100:null})}return e}function Bo(t){let e=String(t||"").trim(),r=/^#([0-9a-f]{3})$/i.exec(e);if(r){let n=r[1].split("").map(o=>parseInt(o+o,16));return{r:n[0],g:n[1],b:n[2],a:1}}if(r=/^#([0-9a-f]{6})$/i.exec(e),r)return{r:parseInt(r[1].slice(0,2),16),g:parseInt(r[1].slice(2,4),16),b:parseInt(r[1].slice(4,6),16),a:1};if(r=/^rgba?\(([^)]+)\)$/i.exec(e),r){let n=r[1].split(",").map(o=>parseFloat(o));return n.length<3||n.some(o=>!Number.isFinite(o))?null:{r:n[0],g:n[1],b:n[2],a:n.length>3?n[3]:1}}return null}function ZE(t,e){return t<=e[0]?null:t<=e[1]?"minor":t<=e[2]?"major":"critical"}var Kr={geometry:[1,3,8],fontSize:[.5,1,2],lineHeight:[1,2,4]};function DE(t,e,r,n){let o=[],s={layer:t.name||"(\u672A\u547D\u540D\u56FE\u5C42)",path:t.parentPath||"",selector:e.selector,matchBy:r},i=t.type==="text",a=(d,f,m,p)=>{let h=Math.abs(f-m),g=ZE(h,p);g&&o.push({...s,field:d,expected:f,actual:m,delta:Math.round(h*100)/100,severity:g})},c=(d,f,m)=>{let p=Bo(f),h=Bo(m);if(!p||!h||p.a<.01&&h.a<.01)return;let g=Math.max(Math.abs(p.r-h.r),Math.abs(p.g-h.g),Math.abs(p.b-h.b)),y=Math.abs(p.a-h.a);if(g<=2&&y<=.02)return;let _=y>.02||g>30?"critical":g>10?"major":"minor";h.a<.01&&_==="critical"&&(_="major"),o.push({...s,field:d,expected:f,actual:m,delta:Math.round(g*100)/100,severity:_})};if(a("x",t.x+n.dx,e.x,Kr.geometry),i){let d=Math.abs(t.w-e.width);d>Kr.geometry[0]&&o.push({...s,field:"width",expected:t.w,actual:e.width,delta:Math.round(d*100)/100,severity:"minor"})}else a("width",t.w,e.width,Kr.geometry);let u=Math.abs(t.y+n.dy-e.y);u>Kr.geometry[0]&&o.push({...s,field:"y",expected:Math.round((t.y+n.dy)*100)/100,actual:e.y,delta:Math.round(u*100)/100,severity:"minor"});let l=Math.abs(t.h-e.height);if(l>Kr.geometry[0]&&o.push({...s,field:"height",expected:t.h,actual:e.height,delta:Math.round(l*100)/100,severity:"minor"}),t.color&&c("color",t.color,e.color),t.fill){let d=Bo(t.fill);if(e.backgroundColor==="rgba(0, 0, 0, 0)"&&e.bgAncestor&&d){let f=Bo(e.bgAncestor);f&&Math.max(Math.abs(f.r-d.r),Math.abs(f.g-d.g),Math.abs(f.b-d.b))<=30&&Math.abs(f.a-d.a)<=.02||c("fill",t.fill,e.backgroundColor)}else c("fill",t.fill,e.backgroundColor)}if(t.fontSize&&a("fontSize",t.fontSize,e.fontSize,Kr.fontSize),t.fontWeight&&e.fontWeight&&t.fontWeight!==e.fontWeight){let d=t.fontWeight>=600&&e.fontWeight===700;o.push({...s,field:"fontWeight",expected:t.fontWeight,actual:e.fontWeight,delta:Math.abs(t.fontWeight-e.fontWeight),severity:d?"minor":"major"})}return typeof t.lineHeight=="number"&&e.lineHeight!=null&&a("lineHeight",t.lineHeight,e.lineHeight,Kr.lineHeight),t.text&&t.text.trim()&&e.text&&e.text.trim()&&t.text.trim()!==e.text.trim()&&o.push({...s,field:"text",expected:t.text.trim(),actual:e.text.trim(),delta:0,severity:"minor"}),o}function $_(t,e){let r=Math.max(t.x,e.x),n=Math.max(t.y,e.y),o=Math.min(t.x+t.w,e.x+e.width),s=Math.min(t.y+t.h,e.y+e.height),i=o-r,a=s-n;if(i<=0||a<=0)return 0;let c=i*a,u=t.w*t.h+e.width*e.height-c;return u>0?c/u:0}function qi(t){if(!t.length)return 0;let e=[...t].sort((n,o)=>n-o),r=e.length>>1;return e.length%2?e[r]:(e[r-1]+e[r])/2}var LE=/(状态栏|status\s*bar|system\s*bar|safe\s*area|刘海|notch|蒙版|mask|guide|标注|备份|backup|副本|copy|_old|占位|placeholder|草稿|draft|切图|slice|导出|export|temp|tmp|ref\b)/i;function qE(t,e){let r={},n=s=>{r[s]=(r[s]||0)+1},o=[];for(let s of t){if(s.opacity===0){n("opacity0");continue}if(s.w<=0||s.h<=0){n("zeroSize");continue}let i=s.name||"";if(LE.test(i)){n("noiseName");continue}if(s.y<=1&&s.x<=1&&s.w>=e*.9&&s.h>=18&&s.h<=80){n("statusBar(geom)");continue}o.push(s)}return{kept:o,skipped:t.length-o.length,reasons:r}}function UE(t,e){let r=i=>{let a=new Map;return i.forEach((c,u)=>{let l=c.text?.trim();l&&(a.has(l)||a.set(l,[]),a.get(l).push(u))}),a},n=r(t),o=r(e),s=[];for(let[i,a]of n){let c=o.get(i);!c||a.length!==1||c.length!==1||s.push({layer:t[a[0]],sample:e[c[0]],matchBy:"text"})}return s}function FE(t,e,r){let n=[];t.forEach((a,c)=>{let u=a.text?.trim()||"",l={x:a.x+r.dx,y:a.y+r.dy,w:a.w,h:a.h};e.forEach((d,f)=>{if(u&&d.text===u){let m=Math.hypot(d.x-l.x,d.y-l.y);n.push({li:c,si:f,score:10-m/1e3+$_(l,d),matchBy:"text"})}})}),n.sort((a,c)=>c.score-a.score);let o=new Set,s=new Set,i=[];for(let a of n)o.has(a.li)||s.has(a.si)||(o.add(a.li),s.add(a.si),i.push({layer:t[a.li],sample:e[a.si],matchBy:a.matchBy}));return{pairs:i,rest:t.filter((a,c)=>!o.has(c)),usedSample:s}}function VE(t){if(t.length<3)return{off:{dx:0,dy:0},inliers:t,outliers:[]};let e=t.map(i=>({dx:i.sample.x-i.layer.x,dy:i.sample.y-i.layer.y,pair:i})),r=[],n=1/0;for(let i of e){let a=e.filter(u=>Math.abs(u.dx-i.dx)<=3&&Math.abs(u.dy-i.dy)<=3),c=Math.hypot(i.dx,i.dy);(a.length>r.length||r.length>0&&a.length===r.length&&c<n)&&(r=a,n=c)}let o={dx:qi(r.map(i=>i.dx)),dy:qi(r.map(i=>i.dy))},s=new Set(r.map(i=>i.pair));return{off:o,inliers:[...s],outliers:t.filter(i=>!s.has(i))}}function x_(t,e,r,n){let o=[];t.forEach((a,c)=>{let u={x:a.x+n.dx,y:a.y+n.dy,w:a.w,h:a.h};e.forEach((l,d)=>{if(r.has(d))return;let f=$_(u,l);f>=.5&&o.push({li:c,si:d,v:f})})}),o.sort((a,c)=>c.v-a.v);let s=new Set,i=[];for(let a of o)s.has(a.li)||r.has(a.si)||(s.add(a.li),r.add(a.si),i.push({layer:t[a.li],sample:e[a.si],matchBy:"position"}));return{pairs:i,unmatched:t.filter((a,c)=>!s.has(c))}}var HE=['You are a design acceptance assistant. For each pair of "design copy" vs "actual page copy", judge whether they are semantically equivalent.',"","Rules (by priority):","1. Dynamic-value exemption: both sides are pure numbers/points/dates/counters (e.g. *30D, Lv.5, \xA529.9, 08:30), same unit and magnitude difference <2x \u2192 same=true","2. Cross-language translation equivalence \u2192 same=true","3. Synonymous paraphrase / abbreviation \u2192 same=true","4. Otherwise \u2192 same=false","","Samples (8 pairs; copy strings stay in their original languages):","\u2705 \u7ACB\u5373\u62A2\u8D2D | Buy Now (translation)","\u2705 *30D | *30\u5929 (dynamic_value_format)","\u2705 \xA529.90 | 29.9\u5143 (dynamic_value_format)","\u2705 Lv.5 | \u7B49\u7EA75 (dynamic_value_format)","\u2705 \u786E\u8BA4 | OK (abbreviation)","\u274C \u652F\u4ED8 | \u7ACB\u5373\u8D2D\u4E70 (different_meaning)","\u274C \u5DF2\u7ED3\u675F | \u8FDB\u884C\u4E2D (different_meaning)","\u274C \u514D\u8D39 | \xA59.9 (different_meaning)","",'Output JSON only: {"result":[{"same":true,"equivalence_class":"translation|synonym|abbreviation|dynamic_value_format|different_meaning","reason":"<=15 words, cite the rule number"}]}, strictly valid json for program parsing.'].join(`
|
|
58
58
|
`);async function BE(t){if(!t.length)return[];if(!An())return t.map(()=>({same:!0,cls:"",reason:"vision \u672A\u914D\u7F6E\uFF0C\u6309\u8BED\u4E49\u63A5\u8FD1\u9ED8\u8BA4\u653E\u884C"}));let e=t.map((n,o)=>`[${o}] \u8BBE\u8BA1:${n.expected} | \u9875\u9762:${n.actual}`).join(`
|
|
59
59
|
`),r=new Set(["translation","synonym","abbreviation","dynamic_value_format","different_meaning"]);try{let n=await Jr({text:HE+`
|
|
60
|
-
`+e}),o=Array.isArray(n?.result)?n.result:[];return t.map((s,i)=>{let a=o[i];if(!a||typeof a.same!="boolean")return{same:!0,cls:"",reason:"vision \u89E3\u6790\u5931\u8D25\uFF0C\u6309\u8BED\u4E49\u63A5\u8FD1\u9ED8\u8BA4\u653E\u884C"};let c=r.has(a.equivalence_class)?a.equivalence_class:"";return{same:a.same,cls:c,reason:String(a.reason||"")}})}catch{return t.map(()=>({same:!0,cls:"",reason:"vision \u8C03\u7528\u5931\u8D25\uFF0C\u6309\u8BED\u4E49\u63A5\u8FD1\u9ED8\u8BA4\u653E\u884C"}))}}function w_(t,e,r){let n=Math.round(t*2)/2;if(n<=0)return null;let o=(Number(e)||0)>=900?700:Number(e)||0,s=Bo(r);return s?{key:`${n}|${o}|rgb(${s.r},${s.g},${s.b})`,fontSize:n,fontWeight:o,color:`rgb(${s.r},${s.g},${s.b})`}:null}function JE(t,e){let r=()=>({fontSize:0,fontWeight:0,color:"",count:0,designLayers:[],samples:[],selectors:[]}),n=(c,u,l)=>{let d=c.get(u.key);d||(d=r(),d.fontSize=u.fontSize,d.fontWeight=u.fontWeight,d.color=u.color,c.set(u.key,d)),d.count++,l(d)},o=new Map;for(let c of t){if(c.type!=="text"||!c.fontSize||(c.y??0)<44)continue;let u=w_(c.fontSize,c.fontWeight||0,c.color||"");u&&n(o,u,l=>{l.designLayers.length<4&&c.name&&l.designLayers.push(c.name),l.samples.length<3&&c.text&&l.samples.push(String(c.text).slice(0,18))})}let s=new Map;for(let c of e){if(!c.text||!c.fontSize)continue;let u=w_(c.fontSize,c.fontWeight||0,c.color);u&&n(s,u,l=>{l.selectors.length<4&&c.selector&&l.selectors.push(c.selector),l.samples.length<3&&c.text&&l.samples.push(c.text.slice(0,18))})}let i=[...o.entries()].filter(([c])=>!s.has(c)).map(([,c])=>c).sort((c,u)=>u.count-c.count),a=[...s.entries()].filter(([c])=>!o.has(c)).map(([,c])=>c).sort((c,u)=>u.count-c.count);return{designStyleCount:o.size,pageStyleCount:s.size,missingOnPage:i,notInDesign:a,missingOnPageTotal:i.length,notInDesignTotal:a.length}}async function k_(t){let e=await Li(t.designUrl,{...t.credentials,needCover:!1}),r;try{r=await import("playwright")}catch{throw new Error("\u7F3A\u5C11 playwright\uFF1A\u8BF7\u5728 mcp/lanhu-design-mcp \u4E0B\u6267\u884C npm i playwright && npx playwright install chromium")}let n=await r.chromium.launch({headless:!0});try{let o={width:e.viewport.width||375,height:e.viewport.height||812},s=await n.newPage({viewport:o,deviceScaleFactor:1});await s.goto(t.pageUrl,{waitUntil:"load",timeout:3e4}),t.waitFor&&await s.waitForSelector(t.waitFor,{timeout:1e4}).catch(()=>{}),await new Promise(A=>setTimeout(A,500));let i=await s.evaluate(ME),a=qE(e.layers,o.width),c=a.kept,u=UE(c,i),l=u.length>=3?{dx:qi(u.map(A=>A.sample.x-A.layer.x)),dy:qi(u.map(A=>A.sample.y-A.layer.y))}:null,d=l??{dx:0,dy:0},f=FE(c,i,d),m=f.pairs.filter(A=>Math.abs(A.sample.x-A.layer.x-d.dx)<=8&&Math.abs(A.sample.y-A.layer.y-d.dy)<=8),p=f.pairs.filter(A=>!m.includes(A)).map(A=>A.layer),h=new Set(m.map(A=>i.indexOf(A.sample))),g=x_([...f.rest,...p],i,h,d),y=VE([...m,...g.pairs]),_=y.inliers.length>=3?y.off:l??{dx:0,dy:0},w=new Set(m.map(A=>i.indexOf(A.sample))),x=x_([...f.rest,...p],i,w,_),$=[...m,...x.pairs],P=[],J=[];for(let{layer:A,sample:oe,matchBy:Ee}of $)for(let He of DE(A,oe,Ee,_))He.field==="text"&&J.push(P.length),P.push(He);let H=!1;J.length&&An()&&(H=!0,(await BE(J.map(oe=>({expected:String(P[oe].expected),actual:String(P[oe].actual)})))).forEach((oe,Ee)=>{let He=P[J[Ee]];oe.cls&&(He.equivalenceClass=oe.cls),oe.reason&&(He.textReason=oe.reason),oe.same||(He.severity="major")}));let k=t.maxDiffs??50,O=[];if(a.skipped){let A=Object.entries(a.reasons).map(([oe,Ee])=>`${oe}\xD7${Ee}`).join("\uFF0C");O.push(`\u6BD4\u5BF9\u524D\u5254\u9664 ${a.skipped} \u4E2A\u65E0\u6548/\u72B6\u6001\u680F\u56FE\u5C42\uFF08${A}\uFF09\uFF1B\u8FD9\u4E9B\u5C42\u5728 webview \u4E2D\u4E0D\u6E32\u67D3\uFF0C\u4E0D\u53C2\u4E0E\u9A8C\u6536`)}(Math.abs(_.dx)>=2||Math.abs(_.dy)>=2)&&O.push(`\u68C0\u6D4B\u5230\u9875\u9762\u6574\u4F53\u504F\u79FB (dx=${Math.round(_.dx)}, dy=${Math.round(_.dy)})\uFF0C\u4F4D\u7F6E\u5B57\u6BB5\u5DF2\u6309\u6B64\u6821\u6B63\u540E\u6BD4\u5BF9\uFF1B\u5E38\u89C1\u539F\u56E0\u662F\u8BBE\u8BA1\u7A3F\u542B\u72B6\u6001\u680F\u5360\u4F4D\u800C\u9875\u9762\u7528\u539F\u751F\u5B89\u5168\u533A`),p.length&&O.push(`${p.length} \u4E2A\u6587\u6848\u951A\u70B9\u4E0E\u6574\u4F53\u504F\u79FB\u4E0D\u4E00\u81F4\uFF08\u591A\u4E3A *30D/x5/Lv.5 \u8FD9\u7C7B\u91CD\u590D\u77ED\u4E32\u4E32\u4F4D\uFF09\uFF0C\u5DF2\u5254\u9664\u5E76\u9000\u56DE\u51E0\u4F55\u91CD\u914D`);let E=x.unmatched.filter(A=>A.text).length;x.unmatched.length&&O.push(`${x.unmatched.length} \u4E2A\u56FE\u5C42\u672A\u5339\u914D\u5230\u9875\u9762\u5143\u7D20\uFF08\u5176\u4E2D ${E} \u4E2A\u542B\u6587\u6848\uFF09\uFF1B\u88C5\u9970\u5C42\u7528 CSS/\u80CC\u666F\u5B9E\u73B0\u4E0D\u4EA7\u751F DOM \u5143\u7D20\u5C5E\u6B63\u5E38\uFF0C\u4F46\u542B\u6587\u6848\u7684\u672A\u5339\u914D\u9700\u4EBA\u5DE5\u786E\u8BA4\u662F\u5426\u6F0F\u505A`),P.length>0&&O.push(`\u5B57\u6BB5\u504F\u5DEE\u5171 ${P.length} \u6761\uFF08\u6309\u56FE\u5C42\u5206\u7EC4\u89C1 diffs\uFF0Ccritical \u7EC4\u5728\u524D\uFF09\uFF1B\u6587\u6848\u5DEE\u5F02\u9ED8\u8BA4 minor\uFF0C\u7ECF\u89C6\u89C9\u6A21\u578B\u5224\u4E3A\u4E0D\u540C\u542B\u4E49\u624D\u5347 major`);let F={critical:0,major:1,minor:2},re=new Map;for(let A of P){let oe=`${A.layer}\0${A.path}\0${A.selector}\0${A.matchBy}`,Ee=re.get(oe);Ee||(Ee={layer:A.layer,path:A.path,selector:A.selector,matchBy:A.matchBy,diffs:[]},re.set(oe,Ee));let{layer:He,path:zt,selector:Fi,matchBy:Vi,...z_}=A;Ee.diffs.push(z_)}let K=[...re.values()].map(A=>({...A,diffs:[...A.diffs].sort((oe,Ee)=>F[oe.severity]-F[Ee.severity])})).sort((A,oe)=>{let Ee=Math.min(...A.diffs.map(zt=>F[zt.severity])),He=Math.min(...oe.diffs.map(zt=>F[zt.severity]));return Ee-He||oe.diffs.length-A.diffs.length}),Ve=K.slice(0,k);K.length>k&&O.push(`\u504F\u5DEE\u5171 ${K.length} \u7EC4 / ${P.length} \u6761\u5B57\u6BB5\u504F\u5DEE\uFF0C\u5DF2\u8FBE\u4E0A\u9650 ${k} \u7EC4\uFF0C\u5EFA\u8BAE\u5148\u4FEE critical \u518D\u590D\u68C0`);let ce=JE(c,i);return(ce.missingOnPage.length||ce.notInDesign.length)&&O.push(`\u6837\u5F0F\u6E05\u5355\u6BD4\u5BF9\uFF08\u96F6\u6807\u6CE8\u3001\u4E0D\u6BD4\u6587\u6848\uFF09\uFF1A\u8BBE\u8BA1\u7A3F ${ce.designStyleCount} \u79CD / \u9875\u9762 ${ce.pageStyleCount} \u79CD\u6587\u5B57\u6837\u5F0F\uFF1B\u8BBE\u8BA1\u6709\u9875\u9762\u65E0 ${ce.missingOnPage.length} \u79CD\u3001\u9875\u9762\u6709\u8BBE\u8BA1\u65E0 ${ce.notInDesign.length} \u79CD \u2192 \u89C1 inventory \u5B57\u6BB5`),{design:{name:e.name||"(\u672A\u547D\u540D\u8BBE\u8BA1\u7A3F)",viewport:e.viewport,layerCount:e.layers.length},page:{url:t.pageUrl,viewport:o,sampled:i.length},matched:$.length,offset:{dx:Math.round(_.dx*100)/100,dy:Math.round(_.dy*100)/100,anchors:m.length,rejected:p.length},denoised:a,diffs:Ve,diffCount:P.length,diffGroupCount:K.length,diffsTruncated:K.length>k,unmatchedLayers:x.unmatched.slice(0,50).map(A=>A.name||"(\u672A\u547D\u540D\u56FE\u5C42)"),notes:O,textCheckedByVision:H,inventory:{...ce,missingOnPage:ce.missingOnPage.slice(0,20),notInDesign:ce.notInDesign.slice(0,20),missingOnPageTotal:ce.missingOnPage.length,notInDesignTotal:ce.notInDesign.length}}}finally{await n.close()}}var GE={source:"mock",viewport:{width:390,height:844},layers:[{id:"bg",type:"rect",x:0,y:0,w:390,h:844,fill:"#0E0B1A"},{id:"title",type:"text",x:24,y:64,w:342,h:32,text:"Masked Ball",fontSize:24,fontWeight:700,color:"#F5F1FF",fontFamily:"Inter"},{id:"cta",type:"rect",x:24,y:720,w:342,h:48,fill:"#7C5CFF",radius:12},{id:"card",type:"rect",x:24,y:120,w:342,h:200,fill:"#1A1530",radius:16},{id:"badge",type:"rect",x:24,y:340,w:61,h:16,fill:"#771E00",borderRadius:{topLeft:8,topRight:12,bottomLeft:0,bottomRight:12},border:{color:"rgba(239,216,185,1)",width:1,alignment:"inside"}}],meta:{rawLayerCount:4,totalLayerCount:4}};function st(t){return{content:[{type:"text",text:JSON.stringify(t)}]}}function YE(t){if(t)try{return KE(t,"utf8").trim()||void 0}catch{return}}function Wr(t){return{cookie:t.cookie||process.env.LANHU_COOKIE||YE(process.env.LANHU_COOKIE_FILE)}}async function XE(t){try{return`data:image/jpeg;base64,${(await Ed(Buffer.from(t,"base64"))).toString("base64")}`}catch{return t}}async function Ui(t){if(t.path)try{return`data:image/jpeg;base64,${(await Ed(await WE(t.path))).toString("base64")}`}catch(e){throw new Error(`\u8BFB\u53D6\u56FE\u7247\u5931\u8D25\uFF08${t.path}\uFF09\uFF1A${e.message}`)}if(t.b64)return XE(t.b64);throw new Error(`${t.what}\uFF1A\u8BF7\u4F20 imagePath\uFF08\u672C\u5730\u622A\u56FE\u6587\u4EF6\u8DEF\u5F84\uFF0C\u63A8\u8350\uFF09\u6216 imageBase64 \u4E4B\u4E00`)}function S_(t){t.registerTool("lanhu_fetch_design",{description:"\u8BFB\u53D6\u84DD\u6E56\u8BBE\u8BA1\u7A3F\u7684\u7ED3\u6784\u5316\u56FE\u5C42\u6811\uFF08\u7CBE\u786E x/y/\u5BBD\u9AD8/\u8272\u503C/\u5B57\u53F7/\u5706\u89D2/\u63CF\u8FB9/\u6587\u672C\uFF09\u3002mode\uFF1Aapi=\u5B98\u65B9Cookie\u63A5\u53E3(\u9ED8\u8BA4,\u65E0\u9700\u6D4F\u89C8\u5668) / mock=\u5185\u7F6E\u793A\u4F8B\u3002analyze=true \u65F6\u7528\u914D\u7F6E\u7684\u89C6\u89C9\u6A21\u578B\u7406\u89E3\u8BBE\u8BA1\u7A3F\u5C01\u9762\u56FE\u3002analyze \u9ED8\u8BA4\u503C\uFF1A\u5DF2\u914D\u7F6E\u89C6\u89C9\u6A21\u578B\uFF08VLM_MODEL + VLM_API_KEY\uFF09\u65F6\u9ED8\u8BA4 true\uFF0C\u672A\u914D\u7F6E\u5219\u9ED8\u8BA4 false\uFF1B\u663E\u5F0F\u4F20 true/false \u59CB\u7EC8\u4F18\u5148\u3002\u4F7F\u7528\u7EAA\u5F8B\uFF1A\u4E00\u6B21\u53EA\u8BFB\u5F53\u524D\u8981\u5B9E\u73B0\u7684\u90A3 1 \u5F20\u7A3F\uFF1B\u4E0D\u8981\u4E3A\u300C\u4E86\u89E3\u5168\u8C8C\u300D\u6279\u91CF\u8BFB\u7A3F\u2014\u2014\u5206\u7EC4\u7A3F\u76EE\u5F55\u7528 lanhu_read_sector\uFF0C\u5B83\u8DB3\u591F\u5B9A\u4F4D\uFF1B\u8FD4\u56DE\u7684 layers \u542B\u7CBE\u786E\u6570\u503C\uFF0C\u8272\u503C/\u5B57\u53F7\u4ECE\u6570\u636E\u53D6\uFF0C\u7981\u6B62\u9760\u89C6\u89C9\u6A21\u578B OCR \u5C0F\u5B57\u3002",inputSchema:{mode:q.enum(["api","mock"]).default("api").describe("\u62BD\u53D6\u540E\u7AEF"),url:q.string().optional().describe("\u84DD\u6E56\u8BBE\u8BA1\u7A3F\u94FE\u63A5\uFF0C\u5982 https://lanhuapp.com/web/#/item/project/detailDetach?pid=xxx&image_id=yyy"),imageId:q.string().optional().describe("\u8BBE\u8BA1\u7A3F id\uFF08\u6765\u81EA lanhu_read_sector\uFF09\uFF0C\u4E0E projectId \u642D\u914D\uFF1Burl \u4E0E imageId \u4E8C\u9009\u4E00\uFF0C\u514D\u62FC URL"),projectId:q.string().optional().describe("\u9879\u76EE UUID\uFF08imageId \u6A21\u5F0F\u5FC5\u586B\uFF1Burl \u6A21\u5F0F\u4E0D\u9700\u8981\uFF09"),cookie:q.string().optional().describe("\u767B\u5F55 cookie \u4E32\uFF08\u4E5F\u53EF\u7528 LANHU_COOKIE / LANHU_COOKIE_FILE\uFF09"),analyze:q.boolean().optional().describe("\u7528\u89C6\u89C9\u6A21\u578B\u7406\u89E3\u5C01\u9762\u56FE\uFF0C\u8FD4\u56DE visionAnalysis\uFF1B\u4E0D\u4F20\u65F6\u6309\u662F\u5426\u914D\u7F6E\u4E86\u89C6\u89C9\u6A21\u578B\u81EA\u52A8\u51B3\u5B9A\uFF08\u914D\u4E86\u5C31 true\uFF09"),analyzeFocus:q.string().optional().describe("\u6CE8\u5165\u89C6\u89C9\u6A21\u578B\u7684\u989D\u5916\u5173\u6CE8\u70B9/\u4E1A\u52A1\u80CC\u666F\uFF08\u5982\u300C\u91CD\u70B9\u5206\u6790\u7B7E\u5230\u5F39\u7A97\uFF0C\u5FFD\u7565\u906E\u7F69\u5C42\u5E95\u4E0B\u7684\u5185\u5BB9\u300D\u300C\u5173\u6CE8\u6309\u94AE\u7684\u7981\u7528\u6001\u300D\uFF09\uFF0C\u8BA9\u5206\u6790\u66F4\u8D34\u5408\u5F53\u524D\u4EFB\u52A1\uFF1B\u4E0D\u5F71\u54CD\u8FD4\u56DE JSON \u7ED3\u6784\uFF0C\u4EC5 analyze \u6267\u884C\u65F6\u751F\u6548")}},async e=>{if(process.env.LANHU_MOCK==="1"||e.mode==="mock")return st(GE);if(!e.url&&!e.imageId)throw new Error("api \u6A21\u5F0F\u9700\u8981 url \u6216 imageId+projectId");if(e.imageId&&!e.projectId&&!e.url)throw new Error("imageId \u6A21\u5F0F\u9700\u8981\u540C\u65F6\u4F20 projectId");let r=e.analyze??__();e.analyze===void 0&&r&&console.error(`[fetch_design] analyze \u672A\u6307\u5B9A\u4E14\u89C6\u89C9\u6A21\u578B\u5DF2\u914D\u7F6E \u2192 \u81EA\u52A8\u5F00\u542F\uFF08${process.env.VLM_MODEL||"\u9ED8\u8BA4\u6A21\u578B"}\uFF09`);let n={...Wr(e),needCover:r},o=e.url?await Li(e.url,n):await c_(e.imageId,e.projectId,n);if(o.slices&&(o.slices=o.slices.map(({imageUrl:a,...c})=>c)),!r)return st(o);let{coverImageBase64:s,...i}=o;if(!s)return st({...i,visionError:"\u8BBE\u8BA1\u7A3F\u65E0\u5C01\u9762\u56FE\uFF08coverImageBase64 \u4E3A\u7A7A\uFF09\uFF0C\u5DF2\u8DF3\u8FC7\u89C6\u89C9\u5206\u6790"});try{let a=await Jr({images:[`data:image/jpeg;base64,${s}`],text:b_(i.meta?.docName??i.name,e.analyzeFocus),detail:"high"});return st({...i,visionAnalysis:a})}catch(a){console.error(`[fetch_design] \u89C6\u89C9\u5206\u6790\u5931\u8D25\uFF0C\u964D\u7EA7\u8FD4\u56DE\u56FE\u5C42\u6811\uFF1A${a?.message}`);let c=An()?"":"\u672A\u68C0\u6D4B\u5230\u89C6\u89C9\u6A21\u578B\u914D\u7F6E\uFF1A\u9700\u540C\u65F6\u8BBE\u7F6E VLM_MODEL \u4E0E VLM_API_KEY\u3002";return st({...i,visionError:`${a?.message||String(a)}${c}`})}}),t.registerTool("lanhu_verify_render",{description:"\u628A\u6E32\u67D3\u9875\u622A\u56FE\uFF08\u53EF\u9009\uFF1A\u8BBE\u8BA1\u7A3F\u622A\u56FE\uFF09\u8C03\u89C6\u89C9\u6A21\u578B\u505A\u8BED\u4E49\u5BF9\u6BD4\uFF0C\u8FD4\u56DE matchScore / verdict / diffs\u3002\u622A\u56FE\u4F20 imagePath\uFF08\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF0C\u63A8\u8350\uFF09\u6216 imageBase64 \u515C\u5E95\u3002\u4F20 context \u53EF\u58F0\u660E\u5DF2\u77E5\u523B\u610F\u5DEE\u5F02\uFF0C\u6A21\u578B\u5C06\u8DF3\u8FC7\u8FD9\u4E9B\u533A\u57DF\u3002\u7ED3\u8BBA\u4EC5\u662F\u89C6\u89C9\u7EBF\u7D22\uFF0C\u4E0E lanhu_verify_spec \u6570\u636E\u6BD4\u5BF9\u51B2\u7A81\u65F6\u4EE5\u6570\u636E\u6BD4\u5BF9\u4E3A\u51C6\u3002",inputSchema:{actualImagePath:q.string().optional().describe("\u6E32\u67D3\u9875\u622A\u56FE\u7684\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF08\u63A8\u8350\uFF0C\u907F\u514D base64 \u8FDB\u4E0A\u4E0B\u6587\uFF09"),actualImageBase64:q.string().optional().describe("\u6E32\u67D3\u9875\u622A\u56FE base64\uFF08\u515C\u5E95\uFF1B\u4E0E actualImagePath \u4E8C\u9009\u4E00\uFF09"),designImagePath:q.string().optional().describe("\u8BBE\u8BA1\u7A3F\u622A\u56FE\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF08\u53EF\u9009\uFF0C\u53CC\u56FE\u5BF9\u6BD4\u7528\uFF09"),designImageBase64:q.string().optional().describe("\u8BBE\u8BA1\u7A3F\u622A\u56FE base64\uFF08\u515C\u5E95\uFF09"),context:q.string().optional().describe("\u5DF2\u77E5\u523B\u610F\u5DEE\u5F02\u8BF4\u660E\uFF0C\u6A21\u578B\u5C06\u8DF3\u8FC7\u8FD9\u4E9B\u533A\u57DF\u7684\u62A5\u9519\u3002\u5982\u300C\u9876\u90E844px\u662F\u7CFB\u7EDF\u72B6\u6001\u680F\uFF0C\u9875\u9762\u7531\u539F\u751F\u6E32\u67D3\u300D\u300C\u5E95\u90E8\u6309\u94AE\u523B\u610F\u52A0\u9AD8\u523056px\u300D"),detail:q.enum(["auto","low","high"]).optional()}},async e=>{let r=!!(e.designImagePath||e.designImageBase64),n=r?`You are a senior frontend reviewer. Compare the RENDERED screenshot (first image) against the DESIGN reference (second image). Output a JSON: {"matchScore":<0-100>,"verdict":"pass|need_fix|fail","diffs":[{"location":"","issue":"","severity":"minor|major|critical"}],"suggestions":["..."]}. Ignore: JPEG compression noise, font anti-aliasing differences, resolution differences between the two images (compare by relative proportions, do not misreport size due to pixel density). Counter-rule: structural problems (misalignment/truncation/color deviation/missing elements) MUST be reported even if the image looks blurry. Known intentional differences (do NOT report these): ${e.context||"none"}. Arbitration: your verdict is a visual hint only; if it conflicts with the lanhu_verify_spec data comparison, the data comparison wins. Output discipline: at most 15 diffs, most important first. Only output JSON.`:`You are a senior frontend reviewer. No design reference provided \u2014 inspect this rendered screenshot for internal consistency only: layout sanity, alignment, contrast, truncation, overlap. Output JSON: {"matchScore":<0-100>,"verdict":"pass|need_fix|fail","diffs":[{"location":"","issue":"","severity":"minor|major|critical"}],"suggestions":["..."]}. Known intentional differences (do NOT report these): ${e.context||"none"}. Output discipline: at most 15 diffs, most important first. Only output JSON.`,o=[await Ui({b64:e.actualImageBase64,path:e.actualImagePath,what:"\u6E32\u67D3\u9875\u622A\u56FE"})];return r&&o.push(await Ui({b64:e.designImageBase64,path:e.designImagePath,what:"\u8BBE\u8BA1\u7A3F\u622A\u56FE"})),st(await Jr({images:o,text:n,detail:e.detail||"high"}))}),t.registerTool("vision_defect_check",{description:"\u6574\u9875/\u5C40\u90E8 UI \u7F3A\u9677\u68C0\u6D4B\uFF1A\u91CD\u53E0\u3001\u6EA2\u51FA\u3001\u7F3A\u56FE\u3001\u5BF9\u6BD4\u5EA6\u3001\u9519\u4F4D\u3001\u622A\u65AD\u3001\u7834\u56FE\u3001\u5360\u4F4D\u6B8B\u7559\u7B49 12 \u7C7B\u3002\u622A\u56FE\u4F20 imagePath\uFF08\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF0C\u63A8\u8350\uFF09\u6216 imageBase64 \u515C\u5E95\u3002\u8FD4\u56DE defects \u6570\u7EC4\u4E0E pass\u3002\u4F20 context \u53EF\u58F0\u660E\u5DF2\u77E5\u523B\u610F\u5DEE\u5F02\uFF0C\u6A21\u578B\u5C06\u8DF3\u8FC7\u8FD9\u4E9B\u533A\u57DF\u3002",inputSchema:{imagePath:q.string().optional().describe("\u622A\u5C4F\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF08\u63A8\u8350\uFF0C\u907F\u514D base64 \u8FDB\u4E0A\u4E0B\u6587\uFF09"),imageBase64:q.string().optional().describe("\u622A\u5C4F base64\uFF08\u515C\u5E95\uFF1B\u4E0E imagePath \u4E8C\u9009\u4E00\uFF09"),context:q.string().optional().describe("\u5DF2\u77E5\u523B\u610F\u5DEE\u5F02\u8BF4\u660E\uFF0C\u6A21\u578B\u5C06\u8DF3\u8FC7\u8FD9\u4E9B\u533A\u57DF\u7684\u62A5\u9519\u3002\u5982\u300C\u9876\u90E844px\u662F\u7CFB\u7EDF\u72B6\u6001\u680F\uFF0C\u9875\u9762\u7531\u539F\u751F\u6E32\u67D3\u300D"),language:q.string().optional().describe("\u8BED\u8A00\uFF0C\u9ED8\u8BA4 zh-CN"),detail:q.enum(["auto","low","high"]).optional()}},async e=>{let n=`Inspect this UI screenshot for visual defects; write all descriptions in ${e.language||"zh-CN"}. Output JSON: {"defects":[{"type":"overlap|overflow|missing_asset|contrast|misalign|truncation|broken_image|placeholder|stale_state|typo|alignment|other","severity":"minor|major|critical","location":"","description":""}],"summary":"","pass":<true|false>}. type guide: overlap / overflow / missing_asset / contrast(insufficient) / misalign / truncation / broken_image / placeholder(unreplaced, e.g. lorem or test images) / stale_state(e.g. loading never dismissed) / typo / alignment / other. severity (qualitative): critical = unreadable content / non-clickable element / blocked interaction; major = clearly visible, hurts consistency; minor = nitpick level. For contrast defects, always include the estimated ratio in description as "ratio:x.x". Known intentional differences (do NOT report these): ${e.context||"none"}. Output discipline: at most 15 defects, most severe first. Only output JSON.`;return st(await Jr({images:[await Ui({b64:e.imageBase64,path:e.imagePath,what:"\u622A\u5C4F"})],text:n,detail:e.detail||"auto"}))}),t.registerTool("vision_e2e_triage",{description:"E2E \u6D4B\u8BD5\u5931\u8D25\u65F6\uFF0C\u5206\u6790\u622A\u56FE+DOM \u5FEB\u7167+\u9519\u8BEF\u6587\u672C\uFF0C\u7ED9\u51FA\u6839\u56E0\u3001\u7C7B\u522B\u3001\u7F6E\u4FE1\u5EA6\u4E0E\u4E0B\u4E00\u6B65\u52A8\u4F5C\u5EFA\u8BAE\u3002\u622A\u56FE\u4F20 screenshotPath\uFF08\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF0C\u63A8\u8350\uFF09\u6216 screenshotBase64 \u515C\u5E95\u3002\u5F3A\u70C8\u5EFA\u8BAE\u4F20 expectedBehavior\uFF08\u6D4B\u8BD5\u9884\u671F\u884C\u4E3A\uFF09\u2014\u2014\u5F52\u56E0\u8D28\u91CF\u53D6\u51B3\u4E8E\u300C\u9884\u671F vs \u5B9E\u9645\u300D\u7684\u5DEE\u5F02\u5206\u6790\uFF0C\u53EA\u7ED9\u5931\u8D25\u73B0\u573A\u6A21\u578B\u53EA\u80FD\u731C\u3002",inputSchema:{expectedBehavior:q.string().optional().describe("\u6D4B\u8BD5\u7684\u9884\u671F\u884C\u4E3A\uFF0C\u5982\u300C\u70B9\u51FB\u63D0\u4EA4\u6309\u94AE\u540E 2s \u5185\u51FA\u73B0\u652F\u4ED8\u6210\u529F\u5F39\u7A97\u300D\u2014\u2014\u5F52\u56E0\u7684\u57FA\u51C6\u7EBF\uFF0C\u5F3A\u70C8\u5EFA\u8BAE\u4F20\u5165"),testSteps:q.string().optional().describe("\u5931\u8D25\u524D\u7684\u64CD\u4F5C\u6B65\u9AA4\u5E8F\u5217\uFF0C\u5982\u300C\u6253\u5F00\u9875\u9762 \u2192 \u586B\u5199\u8868\u5355 \u2192 \u70B9\u51FB\u63D0\u4EA4\u300D"),screenshotPath:q.string().optional().describe("\u5931\u8D25\u622A\u5C4F\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF08\u63A8\u8350\uFF0C\u907F\u514D base64 \u8FDB\u4E0A\u4E0B\u6587\uFF09"),screenshotBase64:q.string().optional().describe("\u5931\u8D25\u622A\u5C4F base64\uFF08\u515C\u5E95\uFF1B\u4E0E screenshotPath \u4E8C\u9009\u4E00\uFF09"),domSnapshot:q.string().optional().describe("\u5931\u8D25\u65F6\u7684 DOM \u5FEB\u7167\u6587\u672C"),errorText:q.string().optional().describe("\u9519\u8BEF\u6D88\u606F/\u6808")}},async e=>{let r=`You are an E2E test triage assistant. You are given: the EXPECTED behavior, (optional) steps taken before failure, and failure artifacts (screenshot/DOM/error). Judge the root cause by comparing expected vs actual. Output JSON: {"rootCause":"<=30 words","evidence":"cite concrete evidence: DOM snippet / error text / screenshot phenomenon, <=60 words","category":"selector_not_found|selector_changed|timing|layout_css|data|auth|environment|other","confidence":<0-1>,"next_action":"wait_and_retry|check_selector|refresh|fix_data|report_bug","fixSuggestion":"<=30 words"}. category guide: selector_not_found = target element never rendered; selector_changed = DOM structure changed but function still exists; timing = sequencing / dynamic rendering not finished; layout_css = style-caused anomaly; data = missing or wrong data; auth = auth/login-state problem; environment = network/timeout/proxy issues. confidence must be backed by evidence; confidence without evidence is meaningless. Expected behavior: ${e.expectedBehavior||"(not provided, base your analysis on the failure artifacts only)"}`+(e.testSteps?`
|
|
60
|
+
`+e}),o=Array.isArray(n?.result)?n.result:[];return t.map((s,i)=>{let a=o[i];if(!a||typeof a.same!="boolean")return{same:!0,cls:"",reason:"vision \u89E3\u6790\u5931\u8D25\uFF0C\u6309\u8BED\u4E49\u63A5\u8FD1\u9ED8\u8BA4\u653E\u884C"};let c=r.has(a.equivalence_class)?a.equivalence_class:"";return{same:a.same,cls:c,reason:String(a.reason||"")}})}catch{return t.map(()=>({same:!0,cls:"",reason:"vision \u8C03\u7528\u5931\u8D25\uFF0C\u6309\u8BED\u4E49\u63A5\u8FD1\u9ED8\u8BA4\u653E\u884C"}))}}function w_(t,e,r){let n=Math.round(t*2)/2;if(n<=0)return null;let o=(Number(e)||0)>=900?700:Number(e)||0,s=Bo(r);return s?{key:`${n}|${o}|rgb(${s.r},${s.g},${s.b})`,fontSize:n,fontWeight:o,color:`rgb(${s.r},${s.g},${s.b})`}:null}function JE(t,e){let r=()=>({fontSize:0,fontWeight:0,color:"",count:0,designLayers:[],samples:[],selectors:[]}),n=(c,u,l)=>{let d=c.get(u.key);d||(d=r(),d.fontSize=u.fontSize,d.fontWeight=u.fontWeight,d.color=u.color,c.set(u.key,d)),d.count++,l(d)},o=new Map;for(let c of t){if(c.type!=="text"||!c.fontSize||(c.y??0)<44)continue;let u=w_(c.fontSize,c.fontWeight||0,c.color||"");u&&n(o,u,l=>{l.designLayers.length<4&&c.name&&l.designLayers.push(c.name),l.samples.length<3&&c.text&&l.samples.push(String(c.text).slice(0,18))})}let s=new Map;for(let c of e){if(!c.text||!c.fontSize)continue;let u=w_(c.fontSize,c.fontWeight||0,c.color);u&&n(s,u,l=>{l.selectors.length<4&&c.selector&&l.selectors.push(c.selector),l.samples.length<3&&c.text&&l.samples.push(c.text.slice(0,18))})}let i=[...o.entries()].filter(([c])=>!s.has(c)).map(([,c])=>c).sort((c,u)=>u.count-c.count),a=[...s.entries()].filter(([c])=>!o.has(c)).map(([,c])=>c).sort((c,u)=>u.count-c.count);return{designStyleCount:o.size,pageStyleCount:s.size,missingOnPage:i,notInDesign:a,missingOnPageTotal:i.length,notInDesignTotal:a.length}}async function k_(t){let e=await Li(t.designUrl,{...t.credentials,needCover:!1}),r;try{r=await import("playwright")}catch{throw new Error("\u7F3A\u5C11 playwright\uFF1A\u8BF7\u5728 mcp/lanhu-design-mcp \u4E0B\u6267\u884C npm i playwright && npx playwright install chromium")}let n=await r.chromium.launch({headless:!0});try{let o={width:e.viewport.width||375,height:e.viewport.height||812},s=await n.newPage({viewport:o,deviceScaleFactor:1});await s.goto(t.pageUrl,{waitUntil:"load",timeout:3e4}),t.waitFor&&await s.waitForSelector(t.waitFor,{timeout:1e4}).catch(()=>{}),await new Promise(A=>setTimeout(A,500));let i=await s.evaluate(ME),a=qE(e.layers,o.width),c=a.kept,u=UE(c,i),l=u.length>=3?{dx:qi(u.map(A=>A.sample.x-A.layer.x)),dy:qi(u.map(A=>A.sample.y-A.layer.y))}:null,d=l??{dx:0,dy:0},f=FE(c,i,d),m=f.pairs.filter(A=>Math.abs(A.sample.x-A.layer.x-d.dx)<=8&&Math.abs(A.sample.y-A.layer.y-d.dy)<=8),p=f.pairs.filter(A=>!m.includes(A)).map(A=>A.layer),h=new Set(m.map(A=>i.indexOf(A.sample))),g=x_([...f.rest,...p],i,h,d),y=VE([...m,...g.pairs]),_=y.inliers.length>=3?y.off:l??{dx:0,dy:0},w=new Set(m.map(A=>i.indexOf(A.sample))),x=x_([...f.rest,...p],i,w,_),$=[...m,...x.pairs],P=[],J=[];for(let{layer:A,sample:oe,matchBy:Ee}of $)for(let He of DE(A,oe,Ee,_))He.field==="text"&&J.push(P.length),P.push(He);let H=!1;J.length&&An()&&(H=!0,(await BE(J.map(oe=>({expected:String(P[oe].expected),actual:String(P[oe].actual)})))).forEach((oe,Ee)=>{let He=P[J[Ee]];oe.cls&&(He.equivalenceClass=oe.cls),oe.reason&&(He.textReason=oe.reason),oe.same||(He.severity="major")}));let k=t.maxDiffs??50,O=[];if(a.skipped){let A=Object.entries(a.reasons).map(([oe,Ee])=>`${oe}\xD7${Ee}`).join("\uFF0C");O.push(`\u6BD4\u5BF9\u524D\u5254\u9664 ${a.skipped} \u4E2A\u65E0\u6548/\u72B6\u6001\u680F\u56FE\u5C42\uFF08${A}\uFF09\uFF1B\u8FD9\u4E9B\u5C42\u5728 webview \u4E2D\u4E0D\u6E32\u67D3\uFF0C\u4E0D\u53C2\u4E0E\u9A8C\u6536`)}(Math.abs(_.dx)>=2||Math.abs(_.dy)>=2)&&O.push(`\u68C0\u6D4B\u5230\u9875\u9762\u6574\u4F53\u504F\u79FB (dx=${Math.round(_.dx)}, dy=${Math.round(_.dy)})\uFF0C\u4F4D\u7F6E\u5B57\u6BB5\u5DF2\u6309\u6B64\u6821\u6B63\u540E\u6BD4\u5BF9\uFF1B\u5E38\u89C1\u539F\u56E0\u662F\u8BBE\u8BA1\u7A3F\u542B\u72B6\u6001\u680F\u5360\u4F4D\u800C\u9875\u9762\u7528\u539F\u751F\u5B89\u5168\u533A`),p.length&&O.push(`${p.length} \u4E2A\u6587\u6848\u951A\u70B9\u4E0E\u6574\u4F53\u504F\u79FB\u4E0D\u4E00\u81F4\uFF08\u591A\u4E3A *30D/x5/Lv.5 \u8FD9\u7C7B\u91CD\u590D\u77ED\u4E32\u4E32\u4F4D\uFF09\uFF0C\u5DF2\u5254\u9664\u5E76\u9000\u56DE\u51E0\u4F55\u91CD\u914D`);let E=x.unmatched.filter(A=>A.text).length;x.unmatched.length&&O.push(`${x.unmatched.length} \u4E2A\u56FE\u5C42\u672A\u5339\u914D\u5230\u9875\u9762\u5143\u7D20\uFF08\u5176\u4E2D ${E} \u4E2A\u542B\u6587\u6848\uFF09\uFF1B\u88C5\u9970\u5C42\u7528 CSS/\u80CC\u666F\u5B9E\u73B0\u4E0D\u4EA7\u751F DOM \u5143\u7D20\u5C5E\u6B63\u5E38\uFF0C\u4F46\u542B\u6587\u6848\u7684\u672A\u5339\u914D\u9700\u4EBA\u5DE5\u786E\u8BA4\u662F\u5426\u6F0F\u505A`),P.length>0&&O.push(`\u5B57\u6BB5\u504F\u5DEE\u5171 ${P.length} \u6761\uFF08\u6309\u56FE\u5C42\u5206\u7EC4\u89C1 diffs\uFF0Ccritical \u7EC4\u5728\u524D\uFF09\uFF1B\u6587\u6848\u5DEE\u5F02\u9ED8\u8BA4 minor\uFF0C\u7ECF\u89C6\u89C9\u6A21\u578B\u5224\u4E3A\u4E0D\u540C\u542B\u4E49\u624D\u5347 major`);let F={critical:0,major:1,minor:2},re=new Map;for(let A of P){let oe=`${A.layer}\0${A.path}\0${A.selector}\0${A.matchBy}`,Ee=re.get(oe);Ee||(Ee={layer:A.layer,path:A.path,selector:A.selector,matchBy:A.matchBy,diffs:[]},re.set(oe,Ee));let{layer:He,path:zt,selector:Fi,matchBy:Vi,...z_}=A;Ee.diffs.push(z_)}let K=[...re.values()].map(A=>({...A,diffs:[...A.diffs].sort((oe,Ee)=>F[oe.severity]-F[Ee.severity])})).sort((A,oe)=>{let Ee=Math.min(...A.diffs.map(zt=>F[zt.severity])),He=Math.min(...oe.diffs.map(zt=>F[zt.severity]));return Ee-He||oe.diffs.length-A.diffs.length}),Ve=K.slice(0,k);K.length>k&&O.push(`\u504F\u5DEE\u5171 ${K.length} \u7EC4 / ${P.length} \u6761\u5B57\u6BB5\u504F\u5DEE\uFF0C\u5DF2\u8FBE\u4E0A\u9650 ${k} \u7EC4\uFF0C\u5EFA\u8BAE\u5148\u4FEE critical \u518D\u590D\u68C0`);let ce=JE(c,i);return(ce.missingOnPage.length||ce.notInDesign.length)&&O.push(`\u6837\u5F0F\u6E05\u5355\u6BD4\u5BF9\uFF08\u96F6\u6807\u6CE8\u3001\u4E0D\u6BD4\u6587\u6848\uFF09\uFF1A\u8BBE\u8BA1\u7A3F ${ce.designStyleCount} \u79CD / \u9875\u9762 ${ce.pageStyleCount} \u79CD\u6587\u5B57\u6837\u5F0F\uFF1B\u8BBE\u8BA1\u6709\u9875\u9762\u65E0 ${ce.missingOnPage.length} \u79CD\u3001\u9875\u9762\u6709\u8BBE\u8BA1\u65E0 ${ce.notInDesign.length} \u79CD \u2192 \u89C1 inventory \u5B57\u6BB5`),{design:{name:e.name||"(\u672A\u547D\u540D\u8BBE\u8BA1\u7A3F)",viewport:e.viewport,layerCount:e.layers.length},page:{url:t.pageUrl,viewport:o,sampled:i.length},matched:$.length,offset:{dx:Math.round(_.dx*100)/100,dy:Math.round(_.dy*100)/100,anchors:m.length,rejected:p.length},denoised:a,diffs:Ve,diffCount:P.length,diffGroupCount:K.length,diffsTruncated:K.length>k,unmatchedLayers:x.unmatched.slice(0,50).map(A=>A.name||"(\u672A\u547D\u540D\u56FE\u5C42)"),notes:O,textCheckedByVision:H,inventory:{...ce,missingOnPage:ce.missingOnPage.slice(0,20),notInDesign:ce.notInDesign.slice(0,20),missingOnPageTotal:ce.missingOnPage.length,notInDesignTotal:ce.notInDesign.length}}}finally{await n.close()}}var GE={source:"mock",viewport:{width:390,height:844},layers:[{id:"bg",type:"rect",x:0,y:0,w:390,h:844,fill:"#0E0B1A"},{id:"title",type:"text",x:24,y:64,w:342,h:32,text:"Masked Ball",fontSize:24,fontWeight:700,color:"#F5F1FF",fontFamily:"Inter"},{id:"cta",type:"rect",x:24,y:720,w:342,h:48,fill:"#7C5CFF",radius:12},{id:"card",type:"rect",x:24,y:120,w:342,h:200,fill:"#1A1530",radius:16},{id:"badge",type:"rect",x:24,y:340,w:61,h:16,fill:"#771E00",borderRadius:{topLeft:8,topRight:12,bottomLeft:0,bottomRight:12},border:{color:"rgba(239,216,185,1)",width:1,alignment:"inside"}}],meta:{rawLayerCount:4,totalLayerCount:4}};function st(t){return{content:[{type:"text",text:JSON.stringify(t)}]}}function YE(t){if(t)try{return KE(t,"utf8").trim()||void 0}catch{return}}function Wr(t){return{cookie:t.cookie||process.env.LANHU_COOKIE||YE(process.env.LANHU_COOKIE_FILE)}}async function XE(t){try{return`data:image/jpeg;base64,${(await Ed(Buffer.from(t,"base64"))).toString("base64")}`}catch{return t}}async function Ui(t){if(t.path)try{return`data:image/jpeg;base64,${(await Ed(await WE(t.path))).toString("base64")}`}catch(e){throw new Error(`\u8BFB\u53D6\u56FE\u7247\u5931\u8D25\uFF08${t.path}\uFF09\uFF1A${e.message}`)}if(t.b64)return XE(t.b64);throw new Error(`${t.what}\uFF1A\u8BF7\u4F20 imagePath\uFF08\u672C\u5730\u622A\u56FE\u6587\u4EF6\u8DEF\u5F84\uFF0C\u63A8\u8350\uFF09\u6216 imageBase64 \u4E4B\u4E00`)}function S_(t){t.registerTool("lanhu_fetch_design",{description:"\u8BFB\u53D6\u84DD\u6E56\u8BBE\u8BA1\u7A3F\u7684\u7ED3\u6784\u5316\u56FE\u5C42\u6811\uFF08\u7CBE\u786E x/y/\u5BBD\u9AD8/\u8272\u503C/\u5B57\u53F7/\u5706\u89D2/\u63CF\u8FB9/\u6587\u672C\uFF09\u3002mode\uFF1Aapi=\u5B98\u65B9Cookie\u63A5\u53E3(\u9ED8\u8BA4,\u65E0\u9700\u6D4F\u89C8\u5668) / mock=\u5185\u7F6E\u793A\u4F8B\u3002analyze=true \u65F6\u7528\u914D\u7F6E\u7684\u89C6\u89C9\u6A21\u578B\u7406\u89E3\u8BBE\u8BA1\u7A3F\u5C01\u9762\u56FE\u3002analyze \u9ED8\u8BA4\u503C\uFF1A\u5DF2\u914D\u7F6E\u89C6\u89C9\u6A21\u578B\uFF08VLM_MODEL + VLM_API_KEY\uFF09\u65F6\u9ED8\u8BA4 true\uFF0C\u672A\u914D\u7F6E\u5219\u9ED8\u8BA4 false\uFF1B\u663E\u5F0F\u4F20 true/false \u59CB\u7EC8\u4F18\u5148\u3002\u4F7F\u7528\u7EAA\u5F8B\uFF1A\u4E00\u6B21\u53EA\u8BFB\u5F53\u524D\u8981\u5B9E\u73B0\u7684\u90A3 1 \u5F20\u7A3F\uFF1B\u4E0D\u8981\u4E3A\u300C\u4E86\u89E3\u5168\u8C8C\u300D\u6279\u91CF\u8BFB\u7A3F\u2014\u2014\u5206\u7EC4\u7A3F\u76EE\u5F55\u7528 lanhu_read_sector\uFF0C\u5B83\u8DB3\u591F\u5B9A\u4F4D\uFF1B\u8FD4\u56DE\u7684 layers \u542B\u7CBE\u786E\u6570\u503C\uFF0C\u8272\u503C/\u5B57\u53F7\u4ECE\u6570\u636E\u53D6\uFF0C\u7981\u6B62\u9760\u89C6\u89C9\u6A21\u578B OCR \u5C0F\u5B57\u3002",inputSchema:{mode:q.enum(["api","mock"]).default("api").describe("\u62BD\u53D6\u540E\u7AEF"),url:q.string().optional().describe("\u84DD\u6E56\u8BBE\u8BA1\u7A3F\u94FE\u63A5\uFF0C\u5982 https://lanhuapp.com/web/#/item/project/detailDetach?pid=xxx&image_id=yyy"),imageId:q.string().optional().describe("\u8BBE\u8BA1\u7A3F id\uFF08\u6765\u81EA lanhu_read_sector\uFF09\uFF0C\u4E0E projectId \u642D\u914D\uFF1Burl \u4E0E imageId \u4E8C\u9009\u4E00\uFF0C\u514D\u62FC URL"),projectId:q.string().optional().describe("\u9879\u76EE UUID\uFF08imageId \u6A21\u5F0F\u5FC5\u586B\uFF1Burl \u6A21\u5F0F\u4E0D\u9700\u8981\uFF09"),cookie:q.string().optional().describe("\u767B\u5F55 cookie \u4E32\uFF08\u4E5F\u53EF\u7528 LANHU_COOKIE / LANHU_COOKIE_FILE\uFF09"),analyze:q.boolean().optional().describe("\u7528\u89C6\u89C9\u6A21\u578B\u7406\u89E3\u5C01\u9762\u56FE\uFF0C\u8FD4\u56DE visionAnalysis\uFF1B\u4E0D\u4F20\u65F6\u6309\u662F\u5426\u914D\u7F6E\u4E86\u89C6\u89C9\u6A21\u578B\u81EA\u52A8\u51B3\u5B9A\uFF08\u914D\u4E86\u5C31 true\uFF09"),analyzeFocus:q.string().optional().describe("\u6CE8\u5165\u89C6\u89C9\u6A21\u578B\u7684\u989D\u5916\u5173\u6CE8\u70B9/\u4E1A\u52A1\u80CC\u666F\uFF08\u5982\u300C\u91CD\u70B9\u5206\u6790\u5F39\u7A97\u5185\u7684\u5956\u52B1\u89C4\u5219\uFF0C\u5FFD\u7565\u906E\u7F69\u5C42\u5E95\u4E0B\u7684\u5185\u5BB9\u300D\u300C\u5173\u6CE8\u6309\u94AE\u7684\u7981\u7528\u6001\u300D\uFF09\uFF0C\u8BA9\u5206\u6790\u66F4\u8D34\u5408\u5F53\u524D\u4EFB\u52A1\uFF1B\u4E0D\u5F71\u54CD\u8FD4\u56DE JSON \u7ED3\u6784\uFF0C\u4EC5 analyze \u6267\u884C\u65F6\u751F\u6548")}},async e=>{if(process.env.LANHU_MOCK==="1"||e.mode==="mock")return st(GE);if(!e.url&&!e.imageId)throw new Error("api \u6A21\u5F0F\u9700\u8981 url \u6216 imageId+projectId");if(e.imageId&&!e.projectId&&!e.url)throw new Error("imageId \u6A21\u5F0F\u9700\u8981\u540C\u65F6\u4F20 projectId");let r=e.analyze??__();e.analyze===void 0&&r&&console.error(`[fetch_design] analyze \u672A\u6307\u5B9A\u4E14\u89C6\u89C9\u6A21\u578B\u5DF2\u914D\u7F6E \u2192 \u81EA\u52A8\u5F00\u542F\uFF08${process.env.VLM_MODEL||"\u9ED8\u8BA4\u6A21\u578B"}\uFF09`);let n={...Wr(e),needCover:r},o=e.url?await Li(e.url,n):await c_(e.imageId,e.projectId,n);if(o.slices&&(o.slices=o.slices.map(({imageUrl:a,...c})=>c)),!r)return st(o);let{coverImageBase64:s,...i}=o;if(!s)return st({...i,visionError:"\u8BBE\u8BA1\u7A3F\u65E0\u5C01\u9762\u56FE\uFF08coverImageBase64 \u4E3A\u7A7A\uFF09\uFF0C\u5DF2\u8DF3\u8FC7\u89C6\u89C9\u5206\u6790"});try{let a=await Jr({images:[`data:image/jpeg;base64,${s}`],text:b_(i.meta?.docName??i.name,e.analyzeFocus),detail:"high"});return st({...i,visionAnalysis:a})}catch(a){console.error(`[fetch_design] \u89C6\u89C9\u5206\u6790\u5931\u8D25\uFF0C\u964D\u7EA7\u8FD4\u56DE\u56FE\u5C42\u6811\uFF1A${a?.message}`);let c=An()?"":"\u672A\u68C0\u6D4B\u5230\u89C6\u89C9\u6A21\u578B\u914D\u7F6E\uFF1A\u9700\u540C\u65F6\u8BBE\u7F6E VLM_MODEL \u4E0E VLM_API_KEY\u3002";return st({...i,visionError:`${a?.message||String(a)}${c}`})}}),t.registerTool("lanhu_verify_render",{description:"\u628A\u6E32\u67D3\u9875\u622A\u56FE\uFF08\u53EF\u9009\uFF1A\u8BBE\u8BA1\u7A3F\u622A\u56FE\uFF09\u8C03\u89C6\u89C9\u6A21\u578B\u505A\u8BED\u4E49\u5BF9\u6BD4\uFF0C\u8FD4\u56DE matchScore / verdict / diffs\u3002\u622A\u56FE\u4F20 imagePath\uFF08\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF0C\u63A8\u8350\uFF09\u6216 imageBase64 \u515C\u5E95\u3002\u4F20 context \u53EF\u58F0\u660E\u5DF2\u77E5\u523B\u610F\u5DEE\u5F02\uFF0C\u6A21\u578B\u5C06\u8DF3\u8FC7\u8FD9\u4E9B\u533A\u57DF\u3002\u7ED3\u8BBA\u4EC5\u662F\u89C6\u89C9\u7EBF\u7D22\uFF0C\u4E0E lanhu_verify_spec \u6570\u636E\u6BD4\u5BF9\u51B2\u7A81\u65F6\u4EE5\u6570\u636E\u6BD4\u5BF9\u4E3A\u51C6\u3002",inputSchema:{actualImagePath:q.string().optional().describe("\u6E32\u67D3\u9875\u622A\u56FE\u7684\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF08\u63A8\u8350\uFF0C\u907F\u514D base64 \u8FDB\u4E0A\u4E0B\u6587\uFF09"),actualImageBase64:q.string().optional().describe("\u6E32\u67D3\u9875\u622A\u56FE base64\uFF08\u515C\u5E95\uFF1B\u4E0E actualImagePath \u4E8C\u9009\u4E00\uFF09"),designImagePath:q.string().optional().describe("\u8BBE\u8BA1\u7A3F\u622A\u56FE\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF08\u53EF\u9009\uFF0C\u53CC\u56FE\u5BF9\u6BD4\u7528\uFF09"),designImageBase64:q.string().optional().describe("\u8BBE\u8BA1\u7A3F\u622A\u56FE base64\uFF08\u515C\u5E95\uFF09"),context:q.string().optional().describe("\u5DF2\u77E5\u523B\u610F\u5DEE\u5F02\u8BF4\u660E\uFF0C\u6A21\u578B\u5C06\u8DF3\u8FC7\u8FD9\u4E9B\u533A\u57DF\u7684\u62A5\u9519\u3002\u5982\u300C\u9876\u90E844px\u662F\u7CFB\u7EDF\u72B6\u6001\u680F\uFF0C\u9875\u9762\u7531\u539F\u751F\u6E32\u67D3\u300D\u300C\u5E95\u90E8\u6309\u94AE\u523B\u610F\u52A0\u9AD8\u523056px\u300D"),detail:q.enum(["auto","low","high"]).optional()}},async e=>{let r=!!(e.designImagePath||e.designImageBase64),n=r?`You are a senior frontend reviewer. Compare the RENDERED screenshot (first image) against the DESIGN reference (second image). Output a JSON: {"matchScore":<0-100>,"verdict":"pass|need_fix|fail","diffs":[{"location":"","issue":"","severity":"minor|major|critical"}],"suggestions":["..."]}. Ignore: JPEG compression noise, font anti-aliasing differences, resolution differences between the two images (compare by relative proportions, do not misreport size due to pixel density). Counter-rule: structural problems (misalignment/truncation/color deviation/missing elements) MUST be reported even if the image looks blurry. Known intentional differences (do NOT report these): ${e.context||"none"}. Arbitration: your verdict is a visual hint only; if it conflicts with the lanhu_verify_spec data comparison, the data comparison wins. Output discipline: at most 15 diffs, most important first. Only output JSON.`:`You are a senior frontend reviewer. No design reference provided \u2014 inspect this rendered screenshot for internal consistency only: layout sanity, alignment, contrast, truncation, overlap. Output JSON: {"matchScore":<0-100>,"verdict":"pass|need_fix|fail","diffs":[{"location":"","issue":"","severity":"minor|major|critical"}],"suggestions":["..."]}. Known intentional differences (do NOT report these): ${e.context||"none"}. Output discipline: at most 15 diffs, most important first. Only output JSON.`,o=[await Ui({b64:e.actualImageBase64,path:e.actualImagePath,what:"\u6E32\u67D3\u9875\u622A\u56FE"})];return r&&o.push(await Ui({b64:e.designImageBase64,path:e.designImagePath,what:"\u8BBE\u8BA1\u7A3F\u622A\u56FE"})),st(await Jr({images:o,text:n,detail:e.detail||"high"}))}),t.registerTool("vision_defect_check",{description:"\u6574\u9875/\u5C40\u90E8 UI \u7F3A\u9677\u68C0\u6D4B\uFF1A\u91CD\u53E0\u3001\u6EA2\u51FA\u3001\u7F3A\u56FE\u3001\u5BF9\u6BD4\u5EA6\u3001\u9519\u4F4D\u3001\u622A\u65AD\u3001\u7834\u56FE\u3001\u5360\u4F4D\u6B8B\u7559\u7B49 12 \u7C7B\u3002\u622A\u56FE\u4F20 imagePath\uFF08\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF0C\u63A8\u8350\uFF09\u6216 imageBase64 \u515C\u5E95\u3002\u8FD4\u56DE defects \u6570\u7EC4\u4E0E pass\u3002\u4F20 context \u53EF\u58F0\u660E\u5DF2\u77E5\u523B\u610F\u5DEE\u5F02\uFF0C\u6A21\u578B\u5C06\u8DF3\u8FC7\u8FD9\u4E9B\u533A\u57DF\u3002",inputSchema:{imagePath:q.string().optional().describe("\u622A\u5C4F\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF08\u63A8\u8350\uFF0C\u907F\u514D base64 \u8FDB\u4E0A\u4E0B\u6587\uFF09"),imageBase64:q.string().optional().describe("\u622A\u5C4F base64\uFF08\u515C\u5E95\uFF1B\u4E0E imagePath \u4E8C\u9009\u4E00\uFF09"),context:q.string().optional().describe("\u5DF2\u77E5\u523B\u610F\u5DEE\u5F02\u8BF4\u660E\uFF0C\u6A21\u578B\u5C06\u8DF3\u8FC7\u8FD9\u4E9B\u533A\u57DF\u7684\u62A5\u9519\u3002\u5982\u300C\u9876\u90E844px\u662F\u7CFB\u7EDF\u72B6\u6001\u680F\uFF0C\u9875\u9762\u7531\u539F\u751F\u6E32\u67D3\u300D"),language:q.string().optional().describe("\u8BED\u8A00\uFF0C\u9ED8\u8BA4 zh-CN"),detail:q.enum(["auto","low","high"]).optional()}},async e=>{let n=`Inspect this UI screenshot for visual defects; write all descriptions in ${e.language||"zh-CN"}. Output JSON: {"defects":[{"type":"overlap|overflow|missing_asset|contrast|misalign|truncation|broken_image|placeholder|stale_state|typo|alignment|other","severity":"minor|major|critical","location":"","description":""}],"summary":"","pass":<true|false>}. type guide: overlap / overflow / missing_asset / contrast(insufficient) / misalign / truncation / broken_image / placeholder(unreplaced, e.g. lorem or test images) / stale_state(e.g. loading never dismissed) / typo / alignment / other. severity (qualitative): critical = unreadable content / non-clickable element / blocked interaction; major = clearly visible, hurts consistency; minor = nitpick level. For contrast defects, always include the estimated ratio in description as "ratio:x.x". Known intentional differences (do NOT report these): ${e.context||"none"}. Output discipline: at most 15 defects, most severe first. Only output JSON.`;return st(await Jr({images:[await Ui({b64:e.imageBase64,path:e.imagePath,what:"\u622A\u5C4F"})],text:n,detail:e.detail||"auto"}))}),t.registerTool("vision_e2e_triage",{description:"E2E \u6D4B\u8BD5\u5931\u8D25\u65F6\uFF0C\u5206\u6790\u622A\u56FE+DOM \u5FEB\u7167+\u9519\u8BEF\u6587\u672C\uFF0C\u7ED9\u51FA\u6839\u56E0\u3001\u7C7B\u522B\u3001\u7F6E\u4FE1\u5EA6\u4E0E\u4E0B\u4E00\u6B65\u52A8\u4F5C\u5EFA\u8BAE\u3002\u622A\u56FE\u4F20 screenshotPath\uFF08\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF0C\u63A8\u8350\uFF09\u6216 screenshotBase64 \u515C\u5E95\u3002\u5F3A\u70C8\u5EFA\u8BAE\u4F20 expectedBehavior\uFF08\u6D4B\u8BD5\u9884\u671F\u884C\u4E3A\uFF09\u2014\u2014\u5F52\u56E0\u8D28\u91CF\u53D6\u51B3\u4E8E\u300C\u9884\u671F vs \u5B9E\u9645\u300D\u7684\u5DEE\u5F02\u5206\u6790\uFF0C\u53EA\u7ED9\u5931\u8D25\u73B0\u573A\u6A21\u578B\u53EA\u80FD\u731C\u3002",inputSchema:{expectedBehavior:q.string().optional().describe("\u6D4B\u8BD5\u7684\u9884\u671F\u884C\u4E3A\uFF0C\u5982\u300C\u70B9\u51FB\u63D0\u4EA4\u6309\u94AE\u540E 2s \u5185\u51FA\u73B0\u652F\u4ED8\u6210\u529F\u5F39\u7A97\u300D\u2014\u2014\u5F52\u56E0\u7684\u57FA\u51C6\u7EBF\uFF0C\u5F3A\u70C8\u5EFA\u8BAE\u4F20\u5165"),testSteps:q.string().optional().describe("\u5931\u8D25\u524D\u7684\u64CD\u4F5C\u6B65\u9AA4\u5E8F\u5217\uFF0C\u5982\u300C\u6253\u5F00\u9875\u9762 \u2192 \u586B\u5199\u8868\u5355 \u2192 \u70B9\u51FB\u63D0\u4EA4\u300D"),screenshotPath:q.string().optional().describe("\u5931\u8D25\u622A\u5C4F\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF08\u63A8\u8350\uFF0C\u907F\u514D base64 \u8FDB\u4E0A\u4E0B\u6587\uFF09"),screenshotBase64:q.string().optional().describe("\u5931\u8D25\u622A\u5C4F base64\uFF08\u515C\u5E95\uFF1B\u4E0E screenshotPath \u4E8C\u9009\u4E00\uFF09"),domSnapshot:q.string().optional().describe("\u5931\u8D25\u65F6\u7684 DOM \u5FEB\u7167\u6587\u672C"),errorText:q.string().optional().describe("\u9519\u8BEF\u6D88\u606F/\u6808")}},async e=>{let r=`You are an E2E test triage assistant. You are given: the EXPECTED behavior, (optional) steps taken before failure, and failure artifacts (screenshot/DOM/error). Judge the root cause by comparing expected vs actual. Output JSON: {"rootCause":"<=30 words","evidence":"cite concrete evidence: DOM snippet / error text / screenshot phenomenon, <=60 words","category":"selector_not_found|selector_changed|timing|layout_css|data|auth|environment|other","confidence":<0-1>,"next_action":"wait_and_retry|check_selector|refresh|fix_data|report_bug","fixSuggestion":"<=30 words"}. category guide: selector_not_found = target element never rendered; selector_changed = DOM structure changed but function still exists; timing = sequencing / dynamic rendering not finished; layout_css = style-caused anomaly; data = missing or wrong data; auth = auth/login-state problem; environment = network/timeout/proxy issues. confidence must be backed by evidence; confidence without evidence is meaningless. Expected behavior: ${e.expectedBehavior||"(not provided, base your analysis on the failure artifacts only)"}`+(e.testSteps?`
|
|
61
61
|
Steps before failure: ${e.testSteps}`:""),n=e.screenshotPath||e.screenshotBase64?[await Ui({b64:e.screenshotBase64,path:e.screenshotPath,what:"\u5931\u8D25\u622A\u5C4F"})]:[],o=n.length?r:`No screenshot provided. Base your analysis on DOM + error text only.
|
|
62
62
|
`+r,s=e.domSnapshot?`
|
|
63
63
|
|
|
@@ -65,5 +65,5 @@ DOM snapshot:
|
|
|
65
65
|
${e.domSnapshot}`:"",i=e.errorText?`
|
|
66
66
|
|
|
67
67
|
Error text:
|
|
68
|
-
${e.errorText}`:"";return st(await Jr({images:n,text:o+s+i,detail:"auto"}))}),t.registerTool("lanhu_check_auth",{description:"\u63A2\u6D3B\u5F53\u524D\u84DD\u6E56 cookie \u662F\u5426\u6709\u6548\u3002\u8C03\u4E00\u6B21 user_teams \u63A5\u53E3\uFF1A\u8FD4\u56DE { ok:true, teamCount, teams } \u8868\u793A cookie \u6709\u6548\uFF1B\u8FD4\u56DE { ok:false, reason, hint } \u8868\u793A\u5931\u6548\uFF08reason=cookie_expired/http_xxx/network_error\uFF09\u3002\u7528\u6CD5\uFF1A\u2460\u4EFB\u4F55\u84DD\u6E56\u5DE5\u5177\u62A5 401 \u6216\u8FD4\u56DE\u7A7A\u6570\u636E\u65F6\uFF0C\u5148\u8C03\u672C\u5DE5\u5177\u786E\u8BA4\u662F\u5426 cookie \u8FC7\u671F\uFF1B\u2461ok=true \u4F46\u67D0\u6B21\u8C03\u7528\u4ECD 401 \u2192 \u662F\u90A3\u4E2A\u5177\u4F53\u8D44\u6E90\u65E0\u6743\u8BBF\u95EE\uFF0C\u91CD\u65B0\u767B\u5F55\u65E0\u6548\uFF0C\u9700\u8054\u7CFB\u8BBE\u8BA1\u8005\u5F00\u6743\u9650\uFF1B\u2462ok=false(reason=cookie_expired) \u6216\u9996\u6B21\u4F7F\u7528\u65E0 cookie \u2192 \u771F\u8FC7\u671F/\u672A\u914D\u7F6E\uFF0C\u63D0\u793A\u7528\u6237\u4E8C\u9009\u4E00\u7EED\u671F\uFF1A\u65B9\u5F0F1 \u6D4F\u89C8\u5668 F12 \u2192 Network \u2192 \u590D\u5236 Cookie \u5934\u5199\u5165 .mcp-local/lanhu.cookie\uFF1B\u65B9\u5F0F2 \u53CC\u51FB lanhu-login.bat \u6216\u8DD1 npm run login \u81EA\u52A8\u5199\u5165\u3002\u5B8C\u6210\u540E\u91CD\u8BD5\u3002",inputSchema:{cookie:q.string().optional()}},async e=>st(await a_(Wr(e)))),t.registerTool("lanhu_list_teams",{description:"\u5217\u51FA\u5F53\u524D\u8D26\u53F7\u52A0\u5165\u7684\u5168\u90E8\u84DD\u6E56\u56E2\u961F\uFF08teamId/\u540D\u79F0/\u89D2\u8272/\u662F\u5426\u6240\u6709\u8005/\u6210\u5458\u6570\uFF09\u3002\u591A\u56E2\u961F\u573A\u666F\u5148\u7528\u5B83\u53D1\u73B0\u56E2\u961F\uFF0C\u62FF\u5230 teamId \u4F20\u7ED9 lanhu_list_directory\u3002\u4E0D\u4F20\u4EFB\u4F55\u53C2\u6570\u5373\u53EF\uFF1B\u8FD4\u56DE\u7CBE\u7B80\u5B57\u6BB5\uFF0C\u7701\u7565\u654F\u611F\u9879\u3002",inputSchema:{cookie:q.string().optional()}},async e=>st(await o_(Wr(e)))),t.registerTool("lanhu_list_directory",{description:"\u4E00\u6B21\u62C9\u56E2\u961F\u76EE\u5F55\uFF08\u9879\u76EE \u2192 \u5206\u7EC4\u5C42\uFF09\u3002\u7528\u4E8E\u300C\u60F3\u627E\u67D0\u6D3B\u52A8\u6709\u51E0\u4E2A\u8BBE\u8BA1\u7A3F\u300D\u2014\u2014AI \u5728\u8FD9\u4EFD\u76EE\u5F55\u91CC\u6309\u5206\u7EC4\u540D\u5339\u914D\uFF0C\u62FF\u5230 projectId \u540E\u4F20\u7ED9 lanhu_read_sector\u3002\u53EA\u5230\u5206\u7EC4\u5C42\uFF08\u542B designCount\uFF09\uFF0C\u4E0D\u5C55\u5F00\u8BBE\u8BA1\u7A3F\u540D\u3002\u56E2\u961F\u5B9A\u4F4D\u4F18\u5148\u7EA7\uFF1A\u6709\u84DD\u6E56\u94FE\u63A5\u4F20 url\uFF08\u4ECE tid \u63D0\u53D6\uFF0C\u6700\u51C6\uFF09\uFF1B\u65E0\u94FE\u63A5\u4F20 teamId\uFF08\u6765\u81EA lanhu_list_teams\uFF09\uFF1B\u4E24\u8005\u90FD\u6CA1\u6709\u5219\u62A5\u9519\uFF08\u65E0\u9ED8\u8BA4\u56E2\u961F\u56DE\u9000\uFF09\u3002\u5E76\u884C\u62C9\u53D6\u7EA6 2 \u79D2\uFF0C\u7EA6 1.6k tokens\u3002",inputSchema:{url:q.string().optional().describe("\u84DD\u6E56\u94FE\u63A5\uFF08\u4EFB\u610F\u7A3F\u94FE\u63A5\u5373\u53EF\uFF0C\u4ECE\u4E2D\u63D0\u53D6 tid \u5B9A\u4F4D\u56E2\u961F\uFF09\uFF1B\u6BD4 teamId \u66F4\u51C6"),teamId:q.string().optional().describe("\u56E2\u961F id\uFF08\u6765\u81EA lanhu_list_teams\uFF09\uFF1B\u65E0 url \u65F6\u7528"),cookie:q.string().optional()}},async e=>st(await u_({...Wr(e),...e.url?{url:e.url}:{},...e.teamId?{teamId:e.teamId}:{}}))),t.registerTool("lanhu_read_sector",{description:"\u5217\u51FA\u84DD\u6E56\u9879\u76EE\u4E0B\u67D0\u4E2A\u5206\u7EC4\uFF08\u9700\u6C42\uFF09\u7684\u6240\u6709\u8BBE\u8BA1\u7A3F\u76EE\u5F55\uFF1A\u7A3F\u540D/\u5C3A\u5BF8/\u5C42\u6570\uFF0C\u4E0D\u542B\u56FE\u5C42\u6811\uFF08\u5168\u91CF layers \u4F1A\u6491\u7206\u4E0A\u4E0B\u6587\uFF0C\u6545\u610F\u4E0D\u8FD4\u56DE\uFF09\u3002\u7528\u9014\uFF1A\u5148\u7528 lanhu_list_directory \u5B9A\u4F4D\u5206\u7EC4\uFF0C\u518D\u7528\u672C\u5DE5\u5177\u770B\u5206\u7EC4\u91CC\u6709\u54EA\u51E0\u5F20\u7A3F\uFF0C\u6309\u7A3F\u540D\u6311\u51FA\u8981\u5B9E\u73B0\u7684\u76EE\u6807\uFF0C\u6700\u540E\u7528 lanhu_fetch_design \u9010\u5F20\u8BFB\u56FE\u5C42\u6811\u5B9E\u73B0\u3002",inputSchema:{url:q.string().describe("\u84DD\u6E56\u8BBE\u8BA1\u7A3F\u94FE\u63A5 \u6216 \u9879\u76EE UUID\uFF08\u6765\u81EA lanhu_list_directory \u7684 projectId\uFF09"),sector:q.string().describe("\u5206\u7EC4\u540D\u6216\u5206\u7EC4 id\uFF08\u4ECE lanhu_list_directory \u770B\u5230\uFF09"),cookie:q.string().optional()}},async e=>st(await l_(e.url,e.sector,Wr(e)))),t.registerTool("lanhu_download_slices",{description:"\u4E0B\u8F7D\u84DD\u6E56\u8BBE\u8BA1\u7A3F\u5207\u56FE\u5230\u672C\u5730\u76EE\u5F55\u3002\u8303\u56F4\u4E8C\u9009\u4E00\uFF1A\u4F20 url \u4E0B\u5355\u7A3F\u5207\u56FE\uFF1B\u4F20 sector + url\uFF08\u9879\u76EE UUID \u6216\u8BE5\u5206\u7EC4\u4EFB\u4E00\u7A3F\u94FE\u63A5\uFF09\u4E0B\u6574\u4E2A\u5206\u7EC4\u7684\u5207\u56FE\uFF08\u8DE8\u7A3F\u516C\u5171 icon \u53EA\u4E0B\u4E00\u6B21\uFF09\u3002\u53BB\u91CD\uFF1AURL \u53BB\u91CD\uFF08\u540C\u56FE\u53EA\u4E0B\u4E00\u6B21\uFF0C\u9ED8\u8BA4\u5F00\uFF09\u3001skipExisting\uFF08\u672C\u5730\u5DF2\u5B58\u5728\u5219\u8DF3\u8FC7\uFF0C\u9ED8\u8BA4\u5F00\uFF09\u3001sliceNames\uFF08\u53EA\u4E0B\u6307\u5B9A\u540D\u5B57\u7684\u5207\u56FE\uFF09\u3002\u500D\u7387\u4E0E\u683C\u5F0F\u7531\u84DD\u6E56 OSS \u5728\u7EBF\u51FA\u56FE\uFF1Ascale \u9ED8\u8BA4 2x\uFF08\u8BBE\u8BA1\u5C3A\u5BF8\xD72\uFF09\uFF0C\u53EF\u9009 1x/3x/original\uFF08CDN \u5B58\u50A8\u539F\u56FE\uFF0C\u5B9E\u6D4B 4x\uFF09\uFF1Bformat \u9ED8\u8BA4 png\uFF0C\u53EF\u9009 webp\uFF08\u4F53\u79EF\u66F4\u5C0F\uFF0C\u4FDD\u900F\u660E\uFF09\u3002\u6BCF\u4E2A\u6587\u4EF6\u4E0B\u8F7D\u540E\u505A\u5B57\u8282\u7EA7\u9A8C\u771F\uFF08HTML \u4F2A\u88C5/\u683C\u5F0F/\u5B9E\u9645\u50CF\u7D20\uFF09\uFF0C\u9A8C\u771F\u4E0D\u8FC7\u4E0D\u843D\u76D8\u5E76\u8BB0\u5165 failed.reason\uFF1B\u4E0B\u8F7D\u7ECF host \u767D\u540D\u5355\u4E0E\u91CD\u5B9A\u5411\u9010\u8DF3\u6821\u9A8C\u3002withScaleUrls=true \u65F6\u8FD4\u56DE\u6BCF\u4E2A\u5207\u56FE\u7684\u5168\u5E73\u53F0\u500D\u7387 URL\uFF081x/2x/3x/iOS/Android\uFF09\u3002\u8FD4\u56DE\u4E0B\u8F7D\u660E\u7EC6\uFF08\u771F\u5B9E\u683C\u5F0F/\u50CF\u7D20/sha256\uFF09\u3001\u8DF3\u8FC7\u7EDF\u8BA1\u3001\u5931\u8D25\u5217\u8868\u3002",inputSchema:{url:q.string().describe("\u8BBE\u8BA1\u7A3F URL\uFF08\u542B image_id\uFF09\u6216\u7EAF image_id\uFF1B\u5206\u7EC4\u6A21\u5F0F\u4F20\u9879\u76EE UUID \u6216\u8BE5\u5206\u7EC4\u4EFB\u4E00\u7A3F\u94FE\u63A5"),outputPath:q.string().describe("\u672C\u5730\u8F93\u51FA\u76EE\u5F55\uFF0C\u5982 src/assets/activity-xxx/"),projectId:q.string().optional().describe("\u9879\u76EE UUID\uFF08\u4F20\u7EAF image_id \u65F6\u5FC5\u586B\uFF1B\u4F20 URL \u81EA\u52A8\u63D0\u53D6\uFF09"),sector:q.string().optional().describe("\u5206\u7EC4\u540D\u6216\u5206\u7EC4 id\uFF1A\u4F20\u4E86\u5C31\u4E0B\u8F7D\u8BE5\u5206\u7EC4\u6240\u6709\u7A3F\u7684\u5207\u56FE\uFF08\u8DE8\u7A3F\u5408\u5E76\u53BB\u91CD\uFF09"),sliceNames:q.array(q.string()).optional().describe("\u53EA\u4E0B\u8F7D\u6307\u5B9A\u540D\u5B57\u7684\u5207\u56FE\uFF08\u540C\u540D\u4E0D\u540C URL \u90FD\u4E0B\uFF0C\u56E0\u4E3A\u5B83\u4EEC\u662F\u4E0D\u540C\u7684\u56FE\uFF09"),skipExisting:q.boolean().optional().describe("\u672C\u5730\u5DF2\u5B58\u5728\u540C\u540D\u6587\u4EF6\u5219\u8DF3\u8FC7\uFF0C\u9ED8\u8BA4 true\uFF08\u907F\u514D\u91CD\u4E0B\u516C\u5171 icon\uFF09"),scale:q.enum(["1x","2x","3x","original"]).optional().describe("\u843D\u76D8\u500D\u7387\uFF0C\u9ED8\u8BA4 2x\uFF08\u8BBE\u8BA1\u5C3A\u5BF8\xD72\uFF0COSS \u5728\u7EBF\u51FA\u56FE\uFF09\u3002original=CDN \u5B58\u50A8\u539F\u56FE\uFF08\u5B9E\u6D4B 4x\uFF0C\u4F9B iOS @3x/\u9AD8\u6E05\u7D20\u6750\u573A\u666F\uFF09"),format:q.enum(["png","webp"]).optional().describe("\u8F93\u51FA\u683C\u5F0F\uFF0C\u9ED8\u8BA4 png\uFF1Bwebp \u7531 OSS \u5728\u7EBF\u8F6C\u6362\uFF08\u4F53\u79EF\u66F4\u5C0F\uFF0C\u652F\u6301\u900F\u660E\uFF0CiOS 14+/Android \u4E0E\u6240\u6709\u73B0\u4EE3\u6D4F\u89C8\u5668\u53EF\u7528\uFF09"),withScaleUrls:q.boolean().optional().describe("\u7ED3\u679C\u9644\u5E26\u6BCF\u4E2A\u5207\u56FE\u7684\u5168\u5E73\u53F0\u500D\u7387\u4E0B\u8F7D URL\uFF081x/2x/3x/iOS/Android \u5BC6\u5EA6\uFF0C\u4E0E format \u540C\u683C\u5F0F\uFF09\uFF0C\u9700\u8981\u5176\u5B83\u500D\u7387\u65F6\u4E0D\u5FC5\u91CD\u65B0\u8C03\u7528"),cookie:q.string().optional()}},async e=>st(await d_(e.url,e.outputPath,{...Wr(e),...e.projectId?{projectId:e.projectId}:{},...e.sector?{sector:e.sector}:{},...e.sliceNames?{sliceNames:e.sliceNames}:{},...e.skipExisting!==void 0?{skipExisting:e.skipExisting}:{},...e.scale?{scale:e.scale}:{},...e.withScaleUrls!==void 0?{withScaleUrls:e.withScaleUrls}:{}}))),t.registerTool("lanhu_verify_spec",{description:"\u8BBE\u8BA1\u7A3F\u9A8C\u6536\uFF08L3 \u6570\u636E\u6BD4\u5BF9\uFF09\uFF1A\u53D6\u8BBE\u8BA1\u7A3F\u56FE\u5C42\u6811\u7684\u7CBE\u786E\u6570\u503C\u4F5C\u4E3A\u671F\u671B\u503C\uFF0C\u7528 Playwright \u6253\u5F00\u9875\u9762\u91C7 getComputedStyle \u4F5C\u4E3A\u5B9E\u9645\u503C\uFF0C\u9010\u5B57\u6BB5 diff\uFF0C\u8F93\u51FA\u5C3A\u5BF8/\u4F4D\u7F6E/\u8272\u503C/\u5B57\u53F7\u56DB\u7C7B\u504F\u5DEE\u6E05\u5355\uFF08\u542B delta \u4E0E minor/major/critical \u5206\u7EA7\uFF09\u3002\u4E0D\u542B\u4E3B\u89C2\u5224\u65AD\uFF0C\u7ED3\u8BBA\u53EF\u56DE\u5F52\u3001\u53EF\u590D\u73B0\uFF0C\u662F\u66FF\u4EE3\u4EBA\u5DE5\u8D70\u67E5\u7684\u4E3B\u624B\u6BB5\uFF1Blanhu_verify_render \u7684\u89C6\u89C9\u8BED\u4E49\u6BD4\u5BF9\u53EA\u80FD\u5F53\u8865\u5145\u7EBF\u7D22\u3002\u5143\u7D20\u5339\u914D\uFF1A\u6587\u6848\u7CBE\u786E\u76F8\u7B49 > \u51E0\u4F55 IoU\uFF08\u4E0E\u8BED\u8A00\u65E0\u5173\uFF09\u3002\u300C\u6837\u5F0F\u6E05\u5355\u6BD4\u5BF9\u300D\u5B89\u5168\u7F51\uFF1A\u53EA\u6BD4\u8BBE\u8BA1\u7A3F\u4E0E\u9875\u9762\u7684\u6587\u5B57\u6837\u5F0F\u96C6\u5408\uFF08fontSize/\u5B57\u91CD/\u8272\u503C\uFF09\uFF0C\u96F6\u6807\u6CE8\u3001\u4E0D\u6BD4\u6587\u6848\uFF0C\u6C38\u8FDC\u5728\u7EBF\u2014\u2014\u8DE8\u8BED\u8A00\u6216\u8DE8\u8FED\u4EE3\u6587\u6848\u5DEE\u5F02\u90FD\u4E0D\u4F1A\u8BA9\u5B83\u5931\u76F2\uFF08\u89C1\u8FD4\u56DE inventory \u5B57\u6BB5\uFF09\u3002\u5F53\u524D\u4E3A\u6700\u5C0F\u53EF\u7528\u7248\u672C\uFF1A\u53EA\u8DD1\u9ED8\u8BA4\u6001\u3001\u5355\u4E00\u89C6\u53E3\uFF08\u6309\u8BBE\u8BA1\u7A3F viewport \u5C3A\u5BF8\uFF09\u3001\u4E0D\u8BC4\u5206\u3002",inputSchema:{designUrl:q.string().describe("\u84DD\u6E56\u8BBE\u8BA1\u7A3F URL\uFF08\u671F\u671B\u503C\u6765\u6E90\uFF09"),pageUrl:q.string().describe("\u5DF2\u5B9E\u73B0\u9875\u9762 URL\uFF08\u5B9E\u9645\u503C\u6765\u6E90\uFF09\uFF0C\u9700\u53EF\u8BBF\u95EE"),waitFor:q.string().optional().describe("\u9875\u9762\u52A0\u8F7D\u540E\u7B49\u5F85\u51FA\u73B0\u7684\u9009\u62E9\u5668\uFF08\u5982 .task-list\uFF09\uFF0C\u7528\u4E8E\u7B49\u5F85\u63A5\u53E3\u6570\u636E\u6E32\u67D3"),maxDiffs:q.number().optional().describe("\u8FD4\u56DE\u504F\u5DEE\u5206\u7EC4\u6570\u4E0A\u9650\uFF0C\u9ED8\u8BA4 50\uFF08\u6309\u4E25\u91CD\u5EA6\u6392\u5E8F\uFF0Ccritical \u7EC4\u5728\u524D\uFF09"),cookie:q.string().optional()}},async e=>st(await k_({designUrl:e.designUrl,pageUrl:e.pageUrl,credentials:Wr(e),...e.waitFor?{waitFor:e.waitFor}:{},...e.maxDiffs!==void 0?{maxDiffs:e.maxDiffs}:{}})))}async function QE(){process.on("unhandledRejection",n=>{console.error("[unhandledRejection]",n instanceof Error?n.stack??n.message:String(n))}),process.on("uncaughtException",n=>{console.error("[uncaughtException]",n.stack??n.message),process.exit(1)});let t=new Ni({name:"lanhu-design-mcp",version:"2.0.0"},{instructions:["\u84DD\u6E56\u8BBE\u8BA1\u7A3F\u8BFB\u53D6\u4E0E\u9A8C\u6536\u5DE5\u4F5C\u6D41\uFF1A","1. \u627E\u7A3F\uFF08\u65E0\u94FE\u63A5\u65F6\uFF09\uFF1Alanhu_list_teams \u62FF teamId\uFF08\u591A\u56E2\u961F\uFF09\u2192 lanhu_list_directory \u6309\u5206\u7EC4\u540D\u5B9A\u4F4D projectId \u2192 lanhu_read_sector \u5217\u5206\u7EC4\u5185\u7A3F\u76EE\u5F55\uFF08\u7A3F\u540D/\u5C3A\u5BF8/\u5C42\u6570\uFF09\u2192 \u6309\u7A3F\u540D\u6311\u51FA\u76EE\u6807\u3002","2. \u8BFB\u7A3F\uFF1Alanhu_fetch_design \u4E00\u6B21\u53EA\u8BFB\u5F53\u524D\u8981\u5B9E\u73B0\u7684\u90A3 1 \u5F20\uFF1Banalyze \u9ED8\u8BA4\u4F1A\u8C03\u89C6\u89C9\u6A21\u578B\u7406\u89E3\u8BED\u4E49\uFF0C\u53EA\u8981\u5750\u6807\u6570\u503C\u65F6\u4F20 analyze:false\uFF1B\u8272\u503C/\u5B57\u53F7\u7B49\u7CBE\u786E\u6570\u503C\u4E00\u5F8B\u53D6\u81EA layers \u5B57\u6BB5\uFF0C\u7981\u6B62\u9760\u622A\u56FE OCR\u3002","3. \u7D20\u6750\uFF1Alanhu_download_slices \u4E0B\u8F7D\u5207\u56FE\uFF08sliceNames \u6309\u540D\u8FC7\u6EE4\uFF09\uFF0C\u4EE3\u7801\u5F15\u7528\u8FD4\u56DE\u7684 file \u8DEF\u5F84\u3002","4. \u9A8C\u6536\uFF1Alanhu_verify_spec \u6570\u503C\u6BD4\u5BF9\u4E3A\u4E3B\uFF08\u5BA2\u89C2\u53EF\u56DE\u5F52\uFF09\uFF1Blanhu_verify_render / vision_defect_check \u4F20\u622A\u56FE\u7684 imagePath \u6587\u4EF6\u8DEF\u5F84\u505A\u89C6\u89C9\u8F85\u52A9\u3002","5. \u62A5 401 \u6216\u7A7A\u6570\u636E\u65F6\u5148 lanhu_check_auth\uFF1Aok:true = \u8BE5\u8D44\u6E90\u65E0\u6743\u9650\uFF08\u627E\u8BBE\u8BA1\u8005\u5F00\u6743\u9650\uFF0C\u91CD\u65B0\u767B\u5F55\u65E0\u6548\uFF09\uFF1Bcookie_expired = \u771F\u8FC7\u671F\uFF08\u8DD1 lanhu-login.bat \u6216 npm run login \u7EED\u671F\u540E\u91CD\u8BD5\uFF09\u3002"].join(`
|
|
68
|
+
${e.errorText}`:"";return st(await Jr({images:n,text:o+s+i,detail:"auto"}))}),t.registerTool("lanhu_check_auth",{description:"\u63A2\u6D3B\u5F53\u524D\u84DD\u6E56 cookie \u662F\u5426\u6709\u6548\u3002\u8C03\u4E00\u6B21 user_teams \u63A5\u53E3\uFF1A\u8FD4\u56DE { ok:true, teamCount, teams } \u8868\u793A cookie \u6709\u6548\uFF1B\u8FD4\u56DE { ok:false, reason, hint } \u8868\u793A\u5931\u6548\uFF08reason=cookie_expired/http_xxx/network_error\uFF09\u3002\u7528\u6CD5\uFF1A\u2460\u4EFB\u4F55\u84DD\u6E56\u5DE5\u5177\u62A5 401 \u6216\u8FD4\u56DE\u7A7A\u6570\u636E\u65F6\uFF0C\u5148\u8C03\u672C\u5DE5\u5177\u786E\u8BA4\u662F\u5426 cookie \u8FC7\u671F\uFF1B\u2461ok=true \u4F46\u67D0\u6B21\u8C03\u7528\u4ECD 401 \u2192 \u662F\u90A3\u4E2A\u5177\u4F53\u8D44\u6E90\u65E0\u6743\u8BBF\u95EE\uFF0C\u91CD\u65B0\u767B\u5F55\u65E0\u6548\uFF0C\u9700\u8054\u7CFB\u8BBE\u8BA1\u8005\u5F00\u6743\u9650\uFF1B\u2462ok=false(reason=cookie_expired) \u6216\u9996\u6B21\u4F7F\u7528\u65E0 cookie \u2192 \u771F\u8FC7\u671F/\u672A\u914D\u7F6E\uFF0C\u63D0\u793A\u7528\u6237\u4E8C\u9009\u4E00\u7EED\u671F\uFF1A\u65B9\u5F0F1 \u6D4F\u89C8\u5668 F12 \u2192 Network \u2192 \u590D\u5236 Cookie \u5934\u5199\u5165 .mcp-local/lanhu.cookie\uFF1B\u65B9\u5F0F2 \u53CC\u51FB lanhu-login.bat \u6216\u8DD1 npm run login \u81EA\u52A8\u5199\u5165\u3002\u5B8C\u6210\u540E\u91CD\u8BD5\u3002",inputSchema:{cookie:q.string().optional()}},async e=>st(await a_(Wr(e)))),t.registerTool("lanhu_list_teams",{description:"\u5217\u51FA\u5F53\u524D\u8D26\u53F7\u52A0\u5165\u7684\u5168\u90E8\u84DD\u6E56\u56E2\u961F\uFF08teamId/\u540D\u79F0/\u89D2\u8272/\u662F\u5426\u6240\u6709\u8005/\u6210\u5458\u6570\uFF09\u3002\u591A\u56E2\u961F\u573A\u666F\u5148\u7528\u5B83\u53D1\u73B0\u56E2\u961F\uFF0C\u62FF\u5230 teamId \u4F20\u7ED9 lanhu_list_directory\u3002\u4E0D\u4F20\u4EFB\u4F55\u53C2\u6570\u5373\u53EF\uFF1B\u8FD4\u56DE\u7CBE\u7B80\u5B57\u6BB5\uFF0C\u7701\u7565\u654F\u611F\u9879\u3002",inputSchema:{cookie:q.string().optional()}},async e=>st(await o_(Wr(e)))),t.registerTool("lanhu_list_directory",{description:"\u4E00\u6B21\u62C9\u56E2\u961F\u76EE\u5F55\uFF08\u9879\u76EE \u2192 \u5206\u7EC4\u5C42\uFF09\u3002\u7528\u4E8E\u300C\u60F3\u627E\u67D0\u6D3B\u52A8\u6709\u51E0\u4E2A\u8BBE\u8BA1\u7A3F\u300D\u2014\u2014AI \u5728\u8FD9\u4EFD\u76EE\u5F55\u91CC\u6309\u5206\u7EC4\u540D\u5339\u914D\uFF0C\u62FF\u5230 projectId \u540E\u4F20\u7ED9 lanhu_read_sector\u3002\u53EA\u5230\u5206\u7EC4\u5C42\uFF08\u542B designCount\uFF09\uFF0C\u4E0D\u5C55\u5F00\u8BBE\u8BA1\u7A3F\u540D\u3002\u56E2\u961F\u5B9A\u4F4D\u4F18\u5148\u7EA7\uFF1A\u6709\u84DD\u6E56\u94FE\u63A5\u4F20 url\uFF08\u4ECE tid \u63D0\u53D6\uFF0C\u6700\u51C6\uFF09\uFF1B\u65E0\u94FE\u63A5\u4F20 teamId\uFF08\u6765\u81EA lanhu_list_teams\uFF09\uFF1B\u4E24\u8005\u90FD\u6CA1\u6709\u5219\u62A5\u9519\uFF08\u65E0\u9ED8\u8BA4\u56E2\u961F\u56DE\u9000\uFF09\u3002\u5E76\u884C\u62C9\u53D6\u7EA6 2 \u79D2\uFF0C\u7EA6 1.6k tokens\u3002",inputSchema:{url:q.string().optional().describe("\u84DD\u6E56\u94FE\u63A5\uFF08\u4EFB\u610F\u7A3F\u94FE\u63A5\u5373\u53EF\uFF0C\u4ECE\u4E2D\u63D0\u53D6 tid \u5B9A\u4F4D\u56E2\u961F\uFF09\uFF1B\u6BD4 teamId \u66F4\u51C6"),teamId:q.string().optional().describe("\u56E2\u961F id\uFF08\u6765\u81EA lanhu_list_teams\uFF09\uFF1B\u65E0 url \u65F6\u7528"),cookie:q.string().optional()}},async e=>st(await u_({...Wr(e),...e.url?{url:e.url}:{},...e.teamId?{teamId:e.teamId}:{}}))),t.registerTool("lanhu_read_sector",{description:"\u5217\u51FA\u84DD\u6E56\u9879\u76EE\u4E0B\u67D0\u4E2A\u5206\u7EC4\uFF08\u9700\u6C42\uFF09\u7684\u6240\u6709\u8BBE\u8BA1\u7A3F\u76EE\u5F55\uFF1A\u7A3F\u540D/\u5C3A\u5BF8/\u5C42\u6570\uFF0C\u4E0D\u542B\u56FE\u5C42\u6811\uFF08\u5168\u91CF layers \u4F1A\u6491\u7206\u4E0A\u4E0B\u6587\uFF0C\u6545\u610F\u4E0D\u8FD4\u56DE\uFF09\u3002\u7528\u9014\uFF1A\u5148\u7528 lanhu_list_directory \u5B9A\u4F4D\u5206\u7EC4\uFF0C\u518D\u7528\u672C\u5DE5\u5177\u770B\u5206\u7EC4\u91CC\u6709\u54EA\u51E0\u5F20\u7A3F\uFF0C\u6309\u7A3F\u540D\u6311\u51FA\u8981\u5B9E\u73B0\u7684\u76EE\u6807\uFF0C\u6700\u540E\u7528 lanhu_fetch_design \u9010\u5F20\u8BFB\u56FE\u5C42\u6811\u5B9E\u73B0\u3002",inputSchema:{url:q.string().describe("\u84DD\u6E56\u8BBE\u8BA1\u7A3F\u94FE\u63A5 \u6216 \u9879\u76EE UUID\uFF08\u6765\u81EA lanhu_list_directory \u7684 projectId\uFF09"),sector:q.string().describe("\u5206\u7EC4\u540D\u6216\u5206\u7EC4 id\uFF08\u4ECE lanhu_list_directory \u770B\u5230\uFF09"),cookie:q.string().optional()}},async e=>st(await l_(e.url,e.sector,Wr(e)))),t.registerTool("lanhu_download_slices",{description:"\u4E0B\u8F7D\u84DD\u6E56\u8BBE\u8BA1\u7A3F\u5207\u56FE\u5230\u672C\u5730\u76EE\u5F55\u3002\u8303\u56F4\u4E8C\u9009\u4E00\uFF1A\u4F20 url \u4E0B\u5355\u7A3F\u5207\u56FE\uFF1B\u4F20 sector + url\uFF08\u9879\u76EE UUID \u6216\u8BE5\u5206\u7EC4\u4EFB\u4E00\u7A3F\u94FE\u63A5\uFF09\u4E0B\u6574\u4E2A\u5206\u7EC4\u7684\u5207\u56FE\uFF08\u8DE8\u7A3F\u516C\u5171 icon \u53EA\u4E0B\u4E00\u6B21\uFF09\u3002\u53BB\u91CD\uFF1AURL \u53BB\u91CD\uFF08\u540C\u56FE\u53EA\u4E0B\u4E00\u6B21\uFF0C\u9ED8\u8BA4\u5F00\uFF09\u3001skipExisting\uFF08\u672C\u5730\u5DF2\u5B58\u5728\u5219\u8DF3\u8FC7\uFF0C\u9ED8\u8BA4\u5F00\uFF09\u3001sliceNames\uFF08\u53EA\u4E0B\u6307\u5B9A\u540D\u5B57\u7684\u5207\u56FE\uFF09\u3002\u500D\u7387\u4E0E\u683C\u5F0F\u7531\u84DD\u6E56 OSS \u5728\u7EBF\u51FA\u56FE\uFF1Ascale \u9ED8\u8BA4 2x\uFF08\u8BBE\u8BA1\u5C3A\u5BF8\xD72\uFF09\uFF0C\u53EF\u9009 1x/3x/original\uFF08CDN \u5B58\u50A8\u539F\u56FE\uFF0C\u5B9E\u6D4B 4x\uFF09\uFF1Bformat \u9ED8\u8BA4 png\uFF0C\u53EF\u9009 webp\uFF08\u4F53\u79EF\u66F4\u5C0F\uFF0C\u4FDD\u900F\u660E\uFF09\u3002\u6BCF\u4E2A\u6587\u4EF6\u4E0B\u8F7D\u540E\u505A\u5B57\u8282\u7EA7\u9A8C\u771F\uFF08HTML \u4F2A\u88C5/\u683C\u5F0F/\u5B9E\u9645\u50CF\u7D20\uFF09\uFF0C\u9A8C\u771F\u4E0D\u8FC7\u4E0D\u843D\u76D8\u5E76\u8BB0\u5165 failed.reason\uFF1B\u4E0B\u8F7D\u7ECF host \u767D\u540D\u5355\u4E0E\u91CD\u5B9A\u5411\u9010\u8DF3\u6821\u9A8C\u3002withScaleUrls=true \u65F6\u8FD4\u56DE\u6BCF\u4E2A\u5207\u56FE\u7684\u5168\u5E73\u53F0\u500D\u7387 URL\uFF081x/2x/3x/iOS/Android\uFF09\u3002\u8FD4\u56DE\u4E0B\u8F7D\u660E\u7EC6\uFF08\u771F\u5B9E\u683C\u5F0F/\u50CF\u7D20/sha256\uFF09\u3001\u8DF3\u8FC7\u7EDF\u8BA1\u3001\u5931\u8D25\u5217\u8868\u3002",inputSchema:{url:q.string().describe("\u8BBE\u8BA1\u7A3F URL\uFF08\u542B image_id\uFF09\u6216\u7EAF image_id\uFF1B\u5206\u7EC4\u6A21\u5F0F\u4F20\u9879\u76EE UUID \u6216\u8BE5\u5206\u7EC4\u4EFB\u4E00\u7A3F\u94FE\u63A5"),outputPath:q.string().describe("\u672C\u5730\u8F93\u51FA\u76EE\u5F55\uFF0C\u5982 src/assets/activity-xxx/"),projectId:q.string().optional().describe("\u9879\u76EE UUID\uFF08\u4F20\u7EAF image_id \u65F6\u5FC5\u586B\uFF1B\u4F20 URL \u81EA\u52A8\u63D0\u53D6\uFF09"),sector:q.string().optional().describe("\u5206\u7EC4\u540D\u6216\u5206\u7EC4 id\uFF1A\u4F20\u4E86\u5C31\u4E0B\u8F7D\u8BE5\u5206\u7EC4\u6240\u6709\u7A3F\u7684\u5207\u56FE\uFF08\u8DE8\u7A3F\u5408\u5E76\u53BB\u91CD\uFF09"),sliceNames:q.array(q.string()).optional().describe("\u53EA\u4E0B\u8F7D\u6307\u5B9A\u540D\u5B57\u7684\u5207\u56FE\uFF08\u540C\u540D\u4E0D\u540C URL \u90FD\u4E0B\uFF0C\u56E0\u4E3A\u5B83\u4EEC\u662F\u4E0D\u540C\u7684\u56FE\uFF09"),skipExisting:q.boolean().optional().describe("\u672C\u5730\u5DF2\u5B58\u5728\u540C\u540D\u6587\u4EF6\u5219\u8DF3\u8FC7\uFF0C\u9ED8\u8BA4 true\uFF08\u907F\u514D\u91CD\u4E0B\u516C\u5171 icon\uFF09"),scale:q.enum(["1x","2x","3x","original"]).optional().describe("\u843D\u76D8\u500D\u7387\uFF0C\u9ED8\u8BA4 2x\uFF08\u8BBE\u8BA1\u5C3A\u5BF8\xD72\uFF0COSS \u5728\u7EBF\u51FA\u56FE\uFF09\u3002original=CDN \u5B58\u50A8\u539F\u56FE\uFF08\u5B9E\u6D4B 4x\uFF0C\u4F9B iOS @3x/\u9AD8\u6E05\u7D20\u6750\u573A\u666F\uFF09"),format:q.enum(["png","webp"]).optional().describe("\u8F93\u51FA\u683C\u5F0F\uFF0C\u9ED8\u8BA4 png\uFF1Bwebp \u7531 OSS \u5728\u7EBF\u8F6C\u6362\uFF08\u4F53\u79EF\u66F4\u5C0F\uFF0C\u652F\u6301\u900F\u660E\uFF0CiOS 14+/Android \u4E0E\u6240\u6709\u73B0\u4EE3\u6D4F\u89C8\u5668\u53EF\u7528\uFF09"),withScaleUrls:q.boolean().optional().describe("\u7ED3\u679C\u9644\u5E26\u6BCF\u4E2A\u5207\u56FE\u7684\u5168\u5E73\u53F0\u500D\u7387\u4E0B\u8F7D URL\uFF081x/2x/3x/iOS/Android \u5BC6\u5EA6\uFF0C\u4E0E format \u540C\u683C\u5F0F\uFF09\uFF0C\u9700\u8981\u5176\u5B83\u500D\u7387\u65F6\u4E0D\u5FC5\u91CD\u65B0\u8C03\u7528"),cookie:q.string().optional()}},async e=>st(await d_(e.url,e.outputPath,{...Wr(e),...e.projectId?{projectId:e.projectId}:{},...e.sector?{sector:e.sector}:{},...e.sliceNames?{sliceNames:e.sliceNames}:{},...e.skipExisting!==void 0?{skipExisting:e.skipExisting}:{},...e.scale?{scale:e.scale}:{},...e.withScaleUrls!==void 0?{withScaleUrls:e.withScaleUrls}:{}}))),t.registerTool("lanhu_verify_spec",{description:"\u8BBE\u8BA1\u7A3F\u9A8C\u6536\uFF08L3 \u6570\u636E\u6BD4\u5BF9\uFF09\uFF1A\u53D6\u8BBE\u8BA1\u7A3F\u56FE\u5C42\u6811\u7684\u7CBE\u786E\u6570\u503C\u4F5C\u4E3A\u671F\u671B\u503C\uFF0C\u7528 Playwright \u6253\u5F00\u9875\u9762\u91C7 getComputedStyle \u4F5C\u4E3A\u5B9E\u9645\u503C\uFF0C\u9010\u5B57\u6BB5 diff\uFF0C\u8F93\u51FA\u5C3A\u5BF8/\u4F4D\u7F6E/\u8272\u503C/\u5B57\u53F7\u56DB\u7C7B\u504F\u5DEE\u6E05\u5355\uFF08\u542B delta \u4E0E minor/major/critical \u5206\u7EA7\uFF09\u3002\u4E0D\u542B\u4E3B\u89C2\u5224\u65AD\uFF0C\u7ED3\u8BBA\u53EF\u56DE\u5F52\u3001\u53EF\u590D\u73B0\uFF0C\u662F\u66FF\u4EE3\u4EBA\u5DE5\u8D70\u67E5\u7684\u4E3B\u624B\u6BB5\uFF1Blanhu_verify_render \u7684\u89C6\u89C9\u8BED\u4E49\u6BD4\u5BF9\u53EA\u80FD\u5F53\u8865\u5145\u7EBF\u7D22\u3002\u5143\u7D20\u5339\u914D\uFF1A\u6587\u6848\u7CBE\u786E\u76F8\u7B49 > \u51E0\u4F55 IoU\uFF08\u4E0E\u8BED\u8A00\u65E0\u5173\uFF09\u3002\u300C\u6837\u5F0F\u6E05\u5355\u6BD4\u5BF9\u300D\u5B89\u5168\u7F51\uFF1A\u53EA\u6BD4\u8BBE\u8BA1\u7A3F\u4E0E\u9875\u9762\u7684\u6587\u5B57\u6837\u5F0F\u96C6\u5408\uFF08fontSize/\u5B57\u91CD/\u8272\u503C\uFF09\uFF0C\u96F6\u6807\u6CE8\u3001\u4E0D\u6BD4\u6587\u6848\uFF0C\u6C38\u8FDC\u5728\u7EBF\u2014\u2014\u8DE8\u8BED\u8A00\u6216\u8DE8\u8FED\u4EE3\u6587\u6848\u5DEE\u5F02\u90FD\u4E0D\u4F1A\u8BA9\u5B83\u5931\u76F2\uFF08\u89C1\u8FD4\u56DE inventory \u5B57\u6BB5\uFF09\u3002\u5F53\u524D\u4E3A\u6700\u5C0F\u53EF\u7528\u7248\u672C\uFF1A\u53EA\u8DD1\u9ED8\u8BA4\u6001\u3001\u5355\u4E00\u89C6\u53E3\uFF08\u6309\u8BBE\u8BA1\u7A3F viewport \u5C3A\u5BF8\uFF09\u3001\u4E0D\u8BC4\u5206\u3002",inputSchema:{designUrl:q.string().describe("\u84DD\u6E56\u8BBE\u8BA1\u7A3F URL\uFF08\u671F\u671B\u503C\u6765\u6E90\uFF09"),pageUrl:q.string().describe("\u5DF2\u5B9E\u73B0\u9875\u9762 URL\uFF08\u5B9E\u9645\u503C\u6765\u6E90\uFF09\uFF0C\u9700\u53EF\u8BBF\u95EE"),waitFor:q.string().optional().describe("\u9875\u9762\u52A0\u8F7D\u540E\u7B49\u5F85\u51FA\u73B0\u7684\u9009\u62E9\u5668\uFF08\u5982 .task-list\uFF09\uFF0C\u7528\u4E8E\u7B49\u5F85\u63A5\u53E3\u6570\u636E\u6E32\u67D3"),maxDiffs:q.number().optional().describe("\u8FD4\u56DE\u504F\u5DEE\u5206\u7EC4\u6570\u4E0A\u9650\uFF0C\u9ED8\u8BA4 50\uFF08\u6309\u4E25\u91CD\u5EA6\u6392\u5E8F\uFF0Ccritical \u7EC4\u5728\u524D\uFF09"),cookie:q.string().optional()}},async e=>st(await k_({designUrl:e.designUrl,pageUrl:e.pageUrl,credentials:Wr(e),...e.waitFor?{waitFor:e.waitFor}:{},...e.maxDiffs!==void 0?{maxDiffs:e.maxDiffs}:{}})))}async function QE(){process.on("unhandledRejection",n=>{console.error("[unhandledRejection]",n instanceof Error?n.stack??n.message:String(n))}),process.on("uncaughtException",n=>{console.error("[uncaughtException]",n.stack??n.message),process.exit(1)});let t=new Ni({name:"lanhu-design-mcp",version:"0.3.0"},{instructions:["\u84DD\u6E56\u8BBE\u8BA1\u7A3F\u8BFB\u53D6\u4E0E\u9A8C\u6536\u5DE5\u4F5C\u6D41\uFF1A","1. \u627E\u7A3F\uFF08\u65E0\u94FE\u63A5\u65F6\uFF09\uFF1Alanhu_list_teams \u62FF teamId\uFF08\u591A\u56E2\u961F\uFF09\u2192 lanhu_list_directory \u6309\u5206\u7EC4\u540D\u5B9A\u4F4D projectId \u2192 lanhu_read_sector \u5217\u5206\u7EC4\u5185\u7A3F\u76EE\u5F55\uFF08\u7A3F\u540D/\u5C3A\u5BF8/\u5C42\u6570\uFF09\u2192 \u6309\u7A3F\u540D\u6311\u51FA\u76EE\u6807\u3002","2. \u8BFB\u7A3F\uFF1Alanhu_fetch_design \u4E00\u6B21\u53EA\u8BFB\u5F53\u524D\u8981\u5B9E\u73B0\u7684\u90A3 1 \u5F20\uFF1Banalyze \u9ED8\u8BA4\u4F1A\u8C03\u89C6\u89C9\u6A21\u578B\u7406\u89E3\u8BED\u4E49\uFF0C\u53EA\u8981\u5750\u6807\u6570\u503C\u65F6\u4F20 analyze:false\uFF1B\u8272\u503C/\u5B57\u53F7\u7B49\u7CBE\u786E\u6570\u503C\u4E00\u5F8B\u53D6\u81EA layers \u5B57\u6BB5\uFF0C\u7981\u6B62\u9760\u622A\u56FE OCR\u3002","3. \u7D20\u6750\uFF1Alanhu_download_slices \u4E0B\u8F7D\u5207\u56FE\uFF08sliceNames \u6309\u540D\u8FC7\u6EE4\uFF09\uFF0C\u4EE3\u7801\u5F15\u7528\u8FD4\u56DE\u7684 file \u8DEF\u5F84\u3002","4. \u9A8C\u6536\uFF1Alanhu_verify_spec \u6570\u503C\u6BD4\u5BF9\u4E3A\u4E3B\uFF08\u5BA2\u89C2\u53EF\u56DE\u5F52\uFF09\uFF1Blanhu_verify_render / vision_defect_check \u4F20\u622A\u56FE\u7684 imagePath \u6587\u4EF6\u8DEF\u5F84\u505A\u89C6\u89C9\u8F85\u52A9\u3002","5. \u62A5 401 \u6216\u7A7A\u6570\u636E\u65F6\u5148 lanhu_check_auth\uFF1Aok:true = \u8BE5\u8D44\u6E90\u65E0\u6743\u9650\uFF08\u627E\u8BBE\u8BA1\u8005\u5F00\u6743\u9650\uFF0C\u91CD\u65B0\u767B\u5F55\u65E0\u6548\uFF09\uFF1Bcookie_expired = \u771F\u8FC7\u671F\uFF08\u8DD1 lanhu-login.bat \u6216 npm run login \u7EED\u671F\u540E\u91CD\u8BD5\uFF09\u3002"].join(`
|
|
69
69
|
`)});S_(t);let e=new Ci;await t.connect(e);let r=async n=>{console.error(`\u6536\u5230 ${n}\uFF0C\u6B63\u5728\u5173\u95ED lanhu-design-mcp\u2026`);try{await t.close()}catch{}process.exit(0)};process.on("SIGINT",()=>{r("SIGINT")}),process.on("SIGTERM",()=>{r("SIGTERM")}),process.on("SIGHUP",()=>{r("SIGHUP")}),console.error("lanhu-design-mcp \u5DF2\u542F\u52A8\uFF0C\u7B49\u5F85\u8FDE\u63A5\u2026")}QE().catch(t=>{console.error("\u542F\u52A8\u5931\u8D25:",t?.message||t),process.exit(1)});
|