cardbuddy 0.13.0 → 0.13.1
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/mcp.js
CHANGED
|
@@ -18,10 +18,13 @@
|
|
|
18
18
|
* 根本找不到 src/cards;
|
|
19
19
|
* - CARD_API_BASE 不给,client 回退 devApiBase() 即 5180(仓库里由 Vite 把 /__* 代理到
|
|
20
20
|
* 8787),而消费者工程通常只有 cardbuddy 的 8787、没有 Vite,于是连不上,
|
|
21
|
-
*
|
|
21
|
+
* 且不支持离线降级的 6 个工具(render_status / node_models / import_meta / sync /
|
|
22
|
+
* save / slice_upload)直接报 dev-server-required。
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
24
|
+
* 本地服务没起也能用大部分:route_input / scaffold / add_component 纯本地;list / read / write /
|
|
25
|
+
* validate / history / rollback 降级为直接读写盘(validate 跑 Python 校验器);screenshot 降级到
|
|
26
|
+
* 包内独立离线渲染器。只有上述 6 个必须另起一个 cardbuddy(sync / save / slice_upload 还需已登录
|
|
27
|
+
* FinMall)。完整分档表见 README「MCP:把 card_* 工具挂给外部 AI agent」一节。
|
|
25
28
|
*/
|
|
26
29
|
import fs from 'node:fs'
|
|
27
30
|
import { pathToFileURL } from 'node:url'
|
|
@@ -248,7 +248,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
248
248
|
|
|
249
249
|
`:``}[已挂载本地文件(仅本机路径,未上传;需要内容请用工具读取)]\n${e.join(`
|
|
250
250
|
`)}`}return{role:`user`,content:n}}let t=(e.toolCalls||[]).filter(e=>e.result!==void 0||e.error).map(e=>({toolCallId:e.toolCallId,toolName:e.toolName,args:e.args||{},result:typeof e.result==`string`?e.result.slice(0,4e3):e.result,error:e.error||``})),n=e.content||``;return e.status===`aborted`&&(n=n?`${n}\n\n[本轮生成被用户手动中止,以上为已完成部分;未完成的步骤没有执行,请从中断处继续。]`:`[上一轮被用户手动中止于思考/工具阶段,没有产出可用内容;请重新规划并继续完成用户的目标。]`),{role:`assistant`,content:n,toolCalls:t}})},onScroll(){let e=this.$refs.listEl;e&&(this.pinned=e.scrollHeight-e.scrollTop-e.clientHeight<48)},scrollToBottom(e=!1){let t=this.$refs.listEl;t&&(!e&&!this.pinned||(t.scrollTop=t.scrollHeight))},stopTickers(){this._saveTimer&&clearTimeout(this._saveTimer),this._saveTimer=0},notify(e,t){this.notice={type:e,text:t},this._noticeTimer&&clearTimeout(this._noticeTimer),this._noticeTimer=setTimeout(()=>{this.notice=null},6e3)}}},Bk={class:`aichat__head`},Vk={class:`aichat__titles`},Hk={class:`aichat__sub`},Uk={class:`aichat__acts`},Wk=[`disabled`],Gk={key:0,class:`aichat__banner`},Kk={class:`aichat__scroll`},qk={key:0,class:`aichat__loading`},Jk={key:1,class:`aichat__empty`},Yk={class:`aichat__suggests`},Xk=[`onClick`],Zk={viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},Qk=[`href`],$k={key:0,class:`aichat__live`};function eA(e,t,n,r,i,a){let o=L(`ChatMessage`),s=L(`ChatComposer`),c=L(`AiSettings`),l=L(`ConfirmDialog`);return B(),V(`aside`,{class:j([`aichat`,{"is-open":n.visible}]),style:A({width:a.panelWidth+`px`}),role:`complementary`,"aria-label":`AI 开发助手`,onKeydown:t[11]||=vl((...e)=>a.onEscape&&a.onEscape(...e),[`esc`])},[K(` 左缘拖拽把手:调宽(双击恢复默认 33%) `),U(`div`,{class:j([`aichat__grip`,{"is-dragging":i.resizing}]),title:`拖拽调整宽度(双击恢复默认)`,onPointerdown:t[0]||=q((...e)=>a.startResize&&a.startResize(...e),[`prevent`]),onDblclick:t[1]||=q((...e)=>a.resetWidth&&a.resetWidth(...e),[`prevent`])},null,34),K(` 头部:标题 + 当前卡片 / 模型 + 清空 / 设置 / 关闭 `),U(`header`,Bk,[t[16]||=U(`span`,{class:`aichat__logo`},[U(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-sparkles`})])],-1),U(`span`,Vk,[t[12]||=U(`span`,{class:`aichat__title`},`AI 开发助手`,-1),U(`span`,Hk,[n.cardId?(B(),V(z,{key:0},[G(M(n.cardName||n.cardId),1)],64)):(B(),V(z,{key:1},[G(`未选择卡片`)],64)),i.config.model?(B(),V(z,{key:2},[G(` · `+M(i.config.model),1)],64)):K(`v-if`,!0)])]),U(`span`,Uk,[U(`button`,{type:`button`,class:`aichat__iconbtn`,title:`清空本卡片对话记录`,disabled:!i.messages.length||i.busy,onClick:t[2]||=(...e)=>a.askClear&&a.askClear(...e)},[...t[13]||=[U(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-trash`})],-1)]],8,Wk),U(`button`,{type:`button`,class:`aichat__iconbtn`,title:`模型设置`,onClick:t[3]||=(...e)=>a.openSettings&&a.openSettings(...e)},[...t[14]||=[U(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-settings`})],-1)]]),U(`button`,{type:`button`,class:`aichat__iconbtn`,title:`关闭(Esc)`,onClick:t[4]||=t=>e.$emit(`close`)},[...t[15]||=[U(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-x`})],-1)]])])]),K(` 未配置模型:常驻提示条,一键打开设置 `),a.configReady?K(`v-if`,!0):(B(),V(`div`,Gk,[t[17]||=U(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-alert-triangle`})],-1),t[18]||=U(`span`,null,`尚未配置模型,无法对话。`,-1),U(`button`,{type:`button`,class:`aichat__banner-btn`,onClick:t[5]||=(...e)=>a.openSettings&&a.openSettings(...e)},`去设置`)])),K(` 消息区:滚动容器与悬浮「回到底部」共处一个定位上下文,按钮只贴消息区底缘,
|
|
251
|
-
输入区拖高 / 反馈条出现都不会把它盖住或悬空 `),U(`div`,Kk,[U(`div`,{ref:`listEl`,class:`aichat__list`,onScroll:t[6]||=(...e)=>a.onScroll&&a.onScroll(...e)},[i.loadingHistory?(B(),V(`div`,qk,`正在加载对话记录…`)):i.messages.length?(B(),V(z,{key:2},[(B(!0),V(z,null,R(i.messages,(e,t)=>(B(),H(o,{key:e.id,msg:e,streaming:i.busy&&t===i.messages.length-1,"can-regenerate":e.role===`assistant`&&t===i.messages.length-1,onRegenerate:a.regenerate,onApproval:a.onApproval,onContinue:a.onContinue},null,8,[`msg`,`streaming`,`can-regenerate`,`onRegenerate`,`onApproval`,`onContinue`]))),128)),i.busy?(B(),V(`div`,$k,`生成中… 可随时终止`)):K(`v-if`,!0)],64)):(B(),V(`div`,Jk,[t[19]||=as(`<span class="aichat__empty-logo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-sparkles"></use></svg></span><p class="aichat__empty-title">让 AI 直接改这张卡</p><p class="aichat__empty-desc"> 描述需求即可,AI 会调用卡片工具读改校验并截图自检;也可以把设计稿标注目录、参考图拖进来,只传本机路径不上传。 </p>`,3),U(`div`,Yk,[(B(!0),V(z,null,R(i.suggestions,e=>(B(),V(`button`,{key:e.text,type:`button`,class:`aichat__suggest`,onClick:t=>a.useSuggestion(e)},[(B(),V(`svg`,Zk,[U(`use`,{href:e.icon},null,8,Qk)])),U(`span`,null,M(e.label),1)],8,Xk))),128))])]))],544),K(` 回到底部:脱离底部吸附时出现 `),I(U(`button`,{type:`button`,class:`aichat__jump`,title:`回到底部`,onClick:t[7]||=e=>a.scrollToBottom(!0)},[...t[20]||=[U(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-arrow-down`})],-1)]],512),[[Sc,!i.pinned&&i.messages.length]])]),K(` 操作反馈(附件解析失败 / 历史保存失败等) `),i.notice?(B(),V(`div`,{key:1,class:j([`aichat__notice`,`aichat__notice--${i.notice.type}`])},[U(`span`,null,M(i.notice.text),1),U(`button`,{type:`button`,class:`aichat__notice-x`,title:`关闭`,onClick:t[8]||=e=>i.notice=null},[...t[21]||=[U(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-x`})],-1)]])],2)):K(`v-if`,!0),W(s,{ref:`composerEl`,busy:i.busy,model:i.config.model,attachments:i.draftAttachments,placeholder:a.configReady?`描述你要改的卡片,Enter 发送 / Shift+Enter 换行`:`请先在设置中配置模型…`,context:i.contextUsage,onSend:a.onSend,onStop:a.stop,onPick:a.onPick,onAddPath:a.onAddPath,onRemoveAttachment:a.removeAttachment,onDropFiles:a.onDropFiles,onPasteImage:a.onPasteImage,onEscape:a.onEscape},null,8,[`busy`,`model`,`attachments`,`placeholder`,`context`,`onSend`,`onStop`,`onPick`,`onAddPath`,`onRemoveAttachment`,`onDropFiles`,`onPasteImage`,`onEscape`]),K(` 模型设置(面板内浮层,不额外抬 z-index) `),i.settingsOpen?(B(),H(c,{key:2,config:i.config,onClose:t[9]||=e=>i.settingsOpen=!1,onSaved:a.onConfigSaved},null,8,[`config`,`onSaved`])):K(`v-if`,!0),W(l,{visible:i.confirmClear,title:`清空对话记录`,message:`将删除卡片「${n.cardName||n.cardId||`当前卡片`}」的全部对话记录,此操作不可撤销。`,"confirm-text":`清空`,severity:`danger`,onConfirm:a.doClear,onCancel:t[10]||=e=>i.confirmClear=!1},null,8,[`visible`,`message`,`onConfirm`])],38)}var tA=X(zk,[[`render`,eA],[`__file`,`/Users/owlaford/Workspace/dsl-card/card-editor/components/ChatPanel.vue`]]),nA=`/assets/site-icon-CFMif8z4.png`,rA={includeMocks:!0},iA=2500,aA=new eu,oA=au({},aA);typeof window<`u`&&(window.__rt=oA);var sA=188,cA=320,lA=200,uA=160,dA={name:`App`,components:{RenderTree:ap,DebugPanel:wy,ApiCallPanel:By,CardSelect:ib,AlertCardLayer:mb,DeviceOverlay:Yb,PickerOverlay:ix,DatePickerOverlay:yx,SelectionOverlay:Cx,HoverGuides:kx,RemotePreview:_S,AppToast:TS,ConfirmDialog:_p,CardGallery:KS,NewCardDialog:uC,ComponentPalette:TC,Switch:kC,ChatPanel:tA},provide(){return{adapter:aA,cardRuntime:oA,customComponents:{}}},data(){return{adapter:aA,rt:oA,logoUrl:nA,appVersion:`0.16.2`,cardList:[],cardCount:0,galleryOpen:!1,newCardOpen:!1,selectedId:``,externalReloadNote:``,externalReloadTimer:null,selfSaveId:``,selfSaveAt:0,preview:null,chromeOff:!1,theme:`dark`,cardReady:!1,reloadToken:0,moveCollapseToken:0,refreshing:!1,loadingCard:!1,currentSchema:null,currentCard:null,cardImportMeta:null,localSchema:null,localData:null,selectedNodeId:``,activeTab:`vars`,apiPanelOpen:!0,addNodePanelOpen:!1,addNodeTarget:null,validateBadge:{state:`idle`,errors:0,warnings:0,renderOk:null,ts:0},validateTimer:null,copiedSchema:!1,copyTimer:null,savedCard:!1,savingCard:!1,saveTimer:null,savingRemote:!1,savedRemote:!1,verifiedRemote:null,saveRemoteTimer:null,syncingRemote:!1,syncedRemote:!1,remotePreviewOpen:!1,syncTarget:null,syncTimer:null,uploadingSlices:!1,loginModal:!1,loginName:``,loginPassword:``,loginSubmitting:!1,loginError:``,confirm:{show:!1,title:``,message:``,confirmText:`继续`,cancelText:`取消`,severity:`primary`,onConfirm:null},past:[],future:[],baseline:null,_commitTimer:null,viewportHeight:700,canvasBg:`gray`,refreshing:!1,bgOptions:[{key:`checker`,label:`棋盘`},{key:`white`,label:`白色`},{key:`gray`,label:`浅灰`},{key:`black`,label:`黑色`}],_previewWidthPx:372,_apiPanelWidthPx:360,presetSizes:[{key:375,value:`375`},{key:430,value:`430`},{key:188,value:`188`}],stageWidth:typeof window<`u`?window.innerWidth:1280,colsWidth:0,stageHeight:typeof window<`u`?window.innerHeight-52:748,dragging:!1,apiDragging:!1,_stageObserver:null,devServiceOpen:!1,aiChatOpen:(()=>{try{return sessionStorage.getItem(`cardbuddy:ai-chat-open`)===`1`}catch{return!1}})(),devServiceForm:Nl(),apiBaseResolved:Fl(),devSvcLive:`unknown`,devSvcDown:!1,_unsubCardsChanged:null}},computed:{currentBg(){return this.bgOptions.find(e=>e.key===this.canvasBg)||this.bgOptions[0]},cardIds(){return this.cardList.map(e=>e&&e.id).filter(Boolean)},canUndo(){return this.past.length>0},canRedo(){return this.future.length>0},devSvcBlockReason(){let e=this.devServiceForm||{};return String(e.baseUrl||``).trim()?e.enabled?null:`disabled`:`empty`},canPaste(){return!!Hu()},hasCardSource(){return!!(this.cardImportMeta&&this.cardImportMeta.source)},historyDirty(){return!this.localSchema||!this.currentSchema?!1:!this.structuralEqual(this.currentSchema,this.localSchema)||!this.dataEqual(this.rt&&this.rt.rootScope,this.localData)},devSvcConn(){return this.apiBaseResolved===null?`unconfigured`:this.devSvcLive===`open`?`open`:this.devSvcDown?`closed`:`connecting`},devSvcTitle(){if(this.apiBaseResolved===null)return this.devSvcBlockReason===`disabled`?`已填地址但未开启用本地服务`:`未配置本地开发服务(部署态必填)`;let e=this.apiBaseResolved===``?`同源开发服务`:`本地开发服务`;return this.devSvcLive===`open`?e+`已连接`:this.devSvcDown?e+`已断开(连接失败或服务已停止,重连中…)`:e+`连接中 / 重连中…`},devSvcClass(){return this.apiBaseResolved===null?`app__btn--warn`:this.devSvcLive===`open`?`app__btn--connected`:this.devSvcDown?`app__btn--disconnected`:`app__btn--reconnecting`},devSvcLabel(){return this.apiBaseResolved===null?`未配置本地服务`:this.devSvcLive===`open`?`本地服务已连接`:this.devSvcDown?`本地服务已断开`:`本地服务连接中…`},badgeText(){return{idle:`待校验`,pending:`校验中…`,ok:`校验通过`,error:`校验未通过`,nopython:`未配置校验`,fail:`校验服务异常`}[this.validateBadge.state]||`待校验`},badgeTitle(){let e=this.validateBadge;if(e.state===`ok`)return`结构合法 + 渲染不抛错(不保证画得对,视觉正确性见截图)`;if(e.state===`error`){let t=[];return e.errors&&t.push(`契约错误 ${e.errors} 个`),e.renderOk===!1&&t.push(`渲染抛出异常`),`校验未通过:`+(t.join(`;`)||`详见 /__validate 与 /__render-status`)}return e.state===`nopython`?`未找到 python3/python,无法跑契约校验(请安装 Python 或设置 DSL_PYTHON)`:e.state===`fail`?`校验服务异常(dev server 未启动或端点不可用)`:``},previewWidth(){if(this.chromeOff)return this.stageWidth;let{min:e,max:t}=this.previewBounds,n=this._previewWidthPx||sA;return Math.max(e,Math.min(t,n))},viewportWidth(){return this.stageWidth,typeof window<`u`?window.innerWidth:1280},debugSoftWidth(){return Math.max(lA,Math.floor(this.viewportWidth/3))},previewBounds(){let e=this.stageWidth||0,t=Math.floor(e/2),n=e-cA-this.debugSoftWidth;return{min:sA,max:Math.max(sA,Math.min(t,n))}},debugMinWidth(){let e=this.colsWidth||Math.max(0,(this.stageWidth||0)-this.previewWidth);return Math.max(lA,Math.min(this.debugSoftWidth,Math.floor(e)-cA))},apiPanelBounds(){let e=this.colsWidth||Math.max(0,(this.stageWidth||0)-this.previewWidth),t=Math.max(uA,Math.floor(e)-this.debugMinWidth);return{min:Math.min(cA,t),max:t}},canvasW(){return Math.round(this.previewWidth||375)},selectedCardName(){let e=this.cardList.find(e=>e.id===this.selectedId);return e&&e.name||this.selectedId||``},apiPanelWidth(){let{min:e,max:t}=this.apiPanelBounds,n=this._apiPanelWidthPx||360;return Math.max(e,Math.min(t,n))},frameStyle(){return{}}},watch:{activeTab(e){e!==`structure`&&(this.selectedNodeId=``,Ex())},aiChatOpen(e){try{sessionStorage.setItem(`cardbuddy:ai-chat-open`,e?`1`:`0`)}catch{}},theme(e){document.documentElement.dataset.theme=e},apiPanelOpen(e){try{localStorage.setItem(`cardbuddy:api-panel-open`,e?`1`:`0`)}catch{}}},beforeUnmount(){this.viewportObserver&&this.viewportObserver.disconnect(),this._stageObserver&&this._stageObserver.disconnect(),this.saveTimer&&clearTimeout(this.saveTimer),this.syncTimer&&clearTimeout(this.syncTimer),this.saveRemoteTimer&&clearTimeout(this.saveRemoteTimer),window.removeEventListener(`keydown`,this.onKeydown)},mounted(){this.viewportObserver=new ResizeObserver(e=>{for(let t of e)this.viewportHeight=Math.round(t.contentRect.height)}),this.$refs.phoneViewport&&this.viewportObserver.observe(this.$refs.phoneViewport),this.loadPreviewWidth(),this.loadApiPanelWidth(),this._stageObserver=new ResizeObserver(e=>{for(let t of e){if(t.target===this.$refs.colsEl){this.colsWidth=Math.round(t.contentRect.width);continue}this.stageWidth=Math.round(t.contentRect.width),this.stageHeight=Math.round(t.contentRect.height)}}),this.$refs.stageEl&&this._stageObserver.observe(this.$refs.stageEl),this.$refs.colsEl&&this._stageObserver.observe(this.$refs.colsEl),window.addEventListener(`keydown`,this.onKeydown)},async created(){Ib(oA);let e=new URLSearchParams(location.search).get(`chrome`);this.chromeOff=e===`off`||e===``;try{let e=localStorage.getItem(`cardbuddy:theme`);(e===`light`||e===`dark`)&&(this.theme=e)}catch{}document.documentElement.dataset.theme=this.theme;try{localStorage.getItem(`cardbuddy:api-panel-open`)===`0`&&(this.apiPanelOpen=!1)}catch{}await this.fetchCardList();let t=new URLSearchParams(location.search).get(`card`),n=null;try{let e=localStorage.getItem(`cardbuddy:selected-card`);e&&this.cardList.some(t=>t.id===e)&&(n=e)}catch{}let r=t&&this.cardList.some(e=>e.id===t)?t:n||this.cardList[0]&&this.cardList[0].id;r&&(this.selectedId=r,await this.loadCard(this.selectedId)),this.$watch(()=>this.rt.rootScope,()=>{this.syncDataToSchema()},{deep:!0}),this.setupCardsChanged()},methods:{setupCardsChanged(){if(this._unsubCardsChanged){try{this._unsubCardsChanged()}catch{}this._unsubCardsChanged=null}this.devSvcLive=`connecting`,this._unsubCardsChanged=Vl(e=>this.onCardsChanged(e),e=>this.onDevServiceState(e))},onDevServiceState(e){let t=this.devSvcLive===`connecting`;this.devSvcLive=e,e===`open`?this.devSvcDown=!1:e===`closed`&&(this.devSvcDown=!0),e===`connecting`?!t&&!this._svcWatchdog&&(this._svcWatchdog=setTimeout(()=>{this.devSvcLive===`connecting`&&(this.devSvcLive=`closed`,this.devSvcDown=!0),this._svcWatchdog=null},8e3)):this._svcWatchdog&&=(clearTimeout(this._svcWatchdog),null)},saveDevService(){Pl(this.devServiceForm),this.apiBaseResolved=Fl(),this.setupCardsChanged(),this.devServiceOpen=!1,Z(`本地开发服务配置已保存`,`ok`),this.fetchCardList()},beginColumnDrag(e,t,n){let r=e&&e.currentTarget;if(!r)return;let i=e.pointerId;try{r.setPointerCapture(i)}catch{}let a=()=>{r.removeEventListener(`pointermove`,t),r.removeEventListener(`pointerup`,a),r.removeEventListener(`pointercancel`,a),window.removeEventListener(`blur`,a);try{r.hasPointerCapture?.(i)&&r.releasePointerCapture(i)}catch{}document.body.style.cursor=``,document.body.style.userSelect=``,n()};document.body.style.cursor=`col-resize`,document.body.style.userSelect=`none`,r.addEventListener(`pointermove`,t),r.addEventListener(`pointerup`,a),r.addEventListener(`pointercancel`,a),window.addEventListener(`blur`,a)},startDrag(e){if(this.chromeOff)return;let t=this.$refs.stageEl;if(!t)return;let n=t.getBoundingClientRect().left;this.dragging=!0,this.beginColumnDrag(e,e=>{let{min:t,max:r}=this.previewBounds;this._previewWidthPx=Math.max(t,Math.min(r,Math.round(e.clientX-n)))},()=>{this.dragging=!1,this.savePreviewWidth()})},startApiDrag(e){if(this.chromeOff||!this.apiPanelOpen)return;let t=this.$refs.colsEl;if(!t)return;let n=t.getBoundingClientRect().right;this.apiDragging=!0,this.beginColumnDrag(e,e=>{let{min:t,max:r}=this.apiPanelBounds;this._apiPanelWidthPx=Math.max(t,Math.min(r,Math.round(n-e.clientX)))},()=>{this.apiDragging=!1,this.saveApiPanelWidth()})},loadPreviewWidth(){try{let e=localStorage.getItem(`cardbuddy:preview-width`);if(e!=null){let t=parseInt(e,10);!Number.isNaN(t)&&t>=188&&(this._previewWidthPx=t)}}catch{}},savePreviewWidth(){try{localStorage.setItem(`cardbuddy:preview-width`,String(Math.round(this._previewWidthPx)))}catch{}},setPresetWidth(e){if(this.chromeOff)return;let{min:t,max:n}=this.previewBounds;this._previewWidthPx=Math.max(t,Math.min(n,e)),this.savePreviewWidth()},loadApiPanelWidth(){try{let e=localStorage.getItem(`cardbuddy:api-panel-width`);if(e!=null){let t=parseInt(e,10);!Number.isNaN(t)&&t>=320&&(this._apiPanelWidthPx=t)}}catch{}},saveApiPanelWidth(){try{localStorage.setItem(`cardbuddy:api-panel-width`,String(Math.round(this.apiPanelWidth)))}catch{}},chooseBg(e){this.canvasBg=e},onSelect(e){this.loadingCard=!0,this.selectedId=e,this.loadCard(e,{clearAll:!0})},selectCard(e){this.selectedId=e;try{localStorage.setItem(`cardbuddy:selected-card`,e)}catch{}},openGallery(){this.cardList.length&&(this.galleryOpen=!0)},openNewCard(){this.newCardOpen=!0,this.fetchCardList()},closeGallery(){this.galleryOpen=!1},async openCardFolder(){if(this.selectedId)try{let e=await J(`/__open-folder`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({id:this.selectedId})}),t=(e.headers.get(`content-type`)||``).includes(`application/json`)?await e.json().catch(()=>({})):{};!e.ok||!t.ok?Z(t.error||`打开文件夹失败`,`err`):Z(`已打开卡片文件夹`,`ok`)}catch{Z(`无法连接本地开发服务,请确认已启动`,`err`)}},async refreshCard(){if(!(!this.selectedId||this.refreshing)){this.refreshing=!0;try{await this.loadCard(this.selectedId)}finally{this.refreshing=!1}}},toggleTheme(){this.theme=this.theme===`light`?`dark`:`light`;try{localStorage.setItem(`cardbuddy:theme`,this.theme)}catch{}},toggleApiPanel(){this.apiPanelOpen=!this.apiPanelOpen},async refreshValidateBadge(){let e=this.selectedId;if(!(!e||!this.preview)&&(this.validateTimer&&clearTimeout(this.validateTimer),this.validateBadge={...this.validateBadge,state:`pending`},await new Promise(e=>{this.validateTimer=setTimeout(e,120)}),this.selectedId===e))try{let[t,n]=await Promise.all([J(`/__validate/${encodeURIComponent(e)}`),J(`/__render-status/${encodeURIComponent(e)}`)]);if(!t.ok){this.validateBadge={...this.validateBadge,state:t.status===503?`nopython`:`fail`};return}let r=await t.json().catch(()=>null),i=n.ok?await n.json().catch(()=>null):null;if(!r){this.validateBadge={...this.validateBadge,state:`fail`};return}let a=!!r.ok,o=!i||!!i.renderOk,s=a&&o?`ok`:`error`,c=this.validateBadge.state!==s;this.validateBadge={state:s,errors:(r.errors||[]).length,warnings:(r.warnings||[]).length,renderOk:o,ts:Date.now()},c&&(s===`ok`||s===`error`)&&this.celebrateValidate(s)}catch{this.validateBadge={...this.validateBadge,state:`fail`}}},celebrateValidate(e=`ok`){let t=this.$refs.validateBadgeEl;if(!t||window.matchMedia&&window.matchMedia(`(prefers-reduced-motion: reduce)`).matches)return;t.classList.remove(`is-celebrating`),t.offsetWidth,t.classList.add(`is-celebrating`),setTimeout(()=>t.classList.remove(`is-celebrating`),600);let n=t.getBoundingClientRect(),r={ok:`--ok`,error:`--danger`,pending:`--warn`,fail:`--text-faint`,nopython:`--text-faint`}[e]||`--ok`,i={ok:`#52c41a`,error:`#F5222D`,pending:`#FAAD14`,fail:`#8c8c8c`,nopython:`#8c8c8c`}[e]||`#52c41a`,a=getComputedStyle(t).getPropertyValue(r).trim()||i,o=document.createElement(`div`);o.className=`app__confetti`,o.style.left=n.left+n.width/2+`px`,o.style.top=n.top+n.height/2+`px`;let s=[`polygon(50% 0%, 0% 100%, 100% 100%)`,`polygon(62% 0%, 100% 40%, 45% 100%, 0% 52%)`,`polygon(0% 0%, 100% 18%, 82% 100%, 12% 70%)`,`polygon(22% 0%, 100% 8%, 88% 100%, 0% 76%)`,`polygon(0% 32%, 100% 0%, 76% 100%, 18% 86%)`,`polygon(8% 0%, 100% 22%, 70% 100%, 0% 64%)`,`polygon(0% 12%, 100% 0%, 100% 78%, 30% 100%, 12% 55%)`],c=n.width/2,l=n.height/2;for(let e=0;e<22;e++){let e=document.createElement(`span`);e.className=`app__confetti-bit`;let t=Math.random()*4|0,n,r;t===0?(n=(Math.random()*2-1)*c,r=-l):t===1?(n=c,r=(Math.random()*2-1)*l):t===2?(n=(Math.random()*2-1)*c,r=l):(n=-c,r=(Math.random()*2-1)*l);let i=Math.hypot(n,r)||1,u=Math.atan2(r/i,n/i)+(Math.random()-.5)*.5,d=16+Math.random()*26;e.style.setProperty(`--sx`,n.toFixed(1)+`px`),e.style.setProperty(`--sy`,r.toFixed(1)+`px`),e.style.setProperty(`--tx`,(Math.cos(u)*d).toFixed(1)+`px`),e.style.setProperty(`--ty`,(Math.sin(u)*d).toFixed(1)+`px`),e.style.setProperty(`--rot`,(Math.random()*720-360).toFixed(0)+`deg`),e.style.setProperty(`--clip`,s[Math.random()*s.length|0]),e.style.setProperty(`--c`,a),e.style.setProperty(`--o`,(.68+Math.random()*.27).toFixed(2));let f=Math.random(),p,m;f<.35?(p=1.5+Math.random()*2,m=10+Math.random()*10):f<.75?(p=4+Math.random()*5,m=p*(.6+Math.random()*.8)):(p=2+Math.random()*2.5,m=p),e.style.width=p.toFixed(0)+`px`,e.style.height=m.toFixed(0)+`px`,o.appendChild(e)}document.body.appendChild(o),setTimeout(()=>o.remove(),800)},async loadCard(e,t={}){if(e){this.loadingCard=!0;try{let n=await J(`/__card/${encodeURIComponent(e)}`);if(!n.ok){this.rt.pushError(`loadCard`,`HTTP `+n.status);return}let r=await n.json();if(!r.schema){this.rt.pushError(`loadCard`,`schema 读取失败`);return}this.selectCard(e),t.clearAll?this.rt.clearErrors():this.rt.clearBindingErrors(),this.rt.clearApiCalls(),t.keepScope||this.rt.setRootScope(Xg(r.schema,rA)),this.currentCard={schema:r.schema},this.currentSchema=r.schema,this.cardImportMeta=null,this.fetchImportMeta(e),this.localSchema=this.cloneSchema(r.schema),this.localData=this.cloneData(Xg(r.schema,rA)),this.preview=Pu(r.schema),typeof window<`u`&&(window.__cardModel=Bu(r.schema)),this.cardReady=!1,this.$nextTick(()=>{this.cardReady=!0}),this.syncDataToSchema(),this.selectedNodeId=``,this.baseline={schema:this.cloneSchema(this.currentSchema)},this.reloadToken+=1,ub(),Lb(),this.refreshValidateBadge(),await this.loadHistory(e)}finally{this.loadingCard=!1}}},cloneSchema(e){return e?JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t)):null},normalizeForCompare(e){let t=this.cloneSchema(e);if(t&&t.dictionary&&Array.isArray(t.dictionary.inputParams))for(let e of t.dictionary.inputParams)e&&typeof e==`object`&&(e.defaultValue=void 0);return t},structuralEqual(e,t){return!e||!t?e===t:JSON.stringify(this.normalizeForCompare(e))===JSON.stringify(this.normalizeForCompare(t))},cloneData(e){return e==null?e:JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t))},dataEqual(e,t){return JSON.stringify(e)===JSON.stringify(t)},applySchemaSnapshot(e){if(!this.selectedId||!e)return;let t=e.schema||e,n=e.data==null?Xg(t,rA):e.data;this.currentSchema=this.cloneSchema(t),this.rt.setRootScope(this.cloneData(n)),this.preview=Pu(this.currentSchema),typeof window<`u`&&(window.__cardModel=Bu(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``,this.reloadToken+=1,this.refreshValidateBadge()},commitHistory(){if(!this.currentSchema||!this.baseline)return;let e=this.structuralEqual(this.currentSchema,this.baseline.schema),t=this.dataEqual(this.rt.rootScope,Xg(this.baseline.schema,rA));if(e&&t)return;let n={schema:this.baseline.schema};this.past.push(n),this.past.length>30&&this.past.shift(),this.future=[],this.baseline={schema:this.cloneSchema(this.currentSchema)},this.persistSnapshot(n)},scheduleCommit(){this._commitTimer&&clearTimeout(this._commitTimer),this._commitTimer=setTimeout(()=>{this._commitTimer=null,this.commitHistory()},500)},persistSnapshot(e){let t=this.selectedId;if(!t)return;let n=e.schema||e;J(`/__history/${encodeURIComponent(t)}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({schema:n},(e,t)=>e===`_schemaNode`?void 0:t,2)}).catch(e=>console.warn(`[历史快照落盘失败]`,e))},async loadHistory(e){this.past=[],this.future=[];try{let t=await J(`/__history/${encodeURIComponent(e)}`);if(!t.ok)return;let n=await t.json().catch(()=>null),r=n&&n.snapshots||[];this.past=r.slice(-30).map(e=>({schema:e.schema,data:e.data==null?Xg(e.schema,rA):e.data}))}catch(e){console.warn(`[加载编辑历史失败]`,e)}},undo(){if(!this.canUndo)return;let e=this.past.pop();this.future.push(this.baseline),this.future.length>30&&this.future.shift(),this.baseline=e,this.applySchemaSnapshot(e)},redo(){if(!this.canRedo)return;let e=this.future.pop();this.past.push(this.baseline),this.past.length>30&&this.past.shift(),this.baseline=e,this.applySchemaSnapshot(e)},async applyHistoryToLocal(){this.loadingCard||!this.historyDirty||!this.preview||!this.selectedId||await this.saveCard()&&Z(`已将该版本保存到本地`,`ok`)},onSchemaChanged(){!this.selectedId||!this.currentSchema||(this.reloadToken+=1,this.preview=Pu(this.currentSchema),typeof window<`u`&&(window.__cardModel=Bu(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``)},onSchemaCommit(){!this.selectedId||!this.currentSchema||(this.reloadToken+=1,this.preview=Pu(this.currentSchema),typeof window<`u`&&(window.__cardModel=Bu(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``,this.commitHistory())},syncDataToSchema(){let e=this.currentSchema;if(!e||!e.dictionary||!Array.isArray(e.dictionary.inputParams))return;let t=this.rt&&this.rt.rootScope;if(!t||typeof t!=`object`)return;let n=e.dictionary.inputParams,r=Zg(e);for(let e of Object.keys(t)){let i=n.find(t=>t&&(t.key===e||t.name===e));if(!i)continue;let a=JSON.parse(JSON.stringify(t[e],(e,t)=>e===`_schemaNode`?void 0:t));r&&Object.prototype.hasOwnProperty.call(r,e)?r[e]=a:i.defaultValue=a}},onSelectNode(e){this.selectedNodeId=e||``},findPreviewNodeById(e,t){if(!e||t==null)return null;let n=String(t),r=e=>{if(!e)return null;if(String(e.id)===n)return e;let t=[];if(e.components&&t.push(...e.components),e.slots)for(let n of e.slots)n.components&&t.push(...n.components);for(let e of t){let t=r(e);if(t)return t}return null};return r(e)},copySelectedNode(){let e=this.selectedNodeId;if(!e||e===`_root`||!this.currentSchema||!this.preview)return;let t=this.findPreviewNodeById(this.preview.view,e);if(!t)return;let n=this.findSchemaNodeByComponentId(this.currentSchema.view,t.componentId);n&&(Wu(n.node,this.selectedId),Z(`已复制节点(含子节点)`,`ok`))},pasteNode(){let e=Hu();if(!e||!this.currentSchema)return;let t=Gu(e),n=[...new Set(t.filter(e=>!vu[e]))];if(n.length){Z(`粘贴失败:含未知组件类型 `+n.join(`、`),`err`);return}let r=JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t));Ku(r);let i=this.selectedNodeId&&this.selectedNodeId!==`_root`?this.findPreviewNodeById(this.preview.view,this.selectedNodeId):null,a=`root`;i&&(a=ju(i.className)?`into`:`after`),this.insertClone(r,i,a);let o=Uu();o&&o!==this.selectedId?Z(`已从卡片「${o}」跨卡片粘贴到「${this.selectedId}」`,`ok`):Z(`已粘贴节点(含子节点)`,`ok`)},duplicateNode(){let e=this.selectedNodeId;if(!e||e===`_root`||!this.currentSchema||!this.preview)return;let t=this.findPreviewNodeById(this.preview.view,e);if(!t)return;let n=this.findSchemaNodeByComponentId(this.currentSchema.view,t.componentId);if(!n)return;let r=JSON.parse(JSON.stringify(n.node,(e,t)=>e===`_schemaNode`?void 0:t));Ku(r),n.parentArr.splice(n.index+1,0,r),this.afterStructuralEdit(r.componentId)},insertClone(e,t,n){let r=this.currentSchema;if(n===`root`)r.view.components||(r.view.components=[]),r.view.components.push(e);else if(n===`into`){let n=t&&t._schemaNode;if(!n){r.view.components||(r.view.components=[]),r.view.components.push(e),this.afterStructuralEdit(e.componentId);return}let i=Mu(t.className),a=(n.slots||[]).find(e=>e.slotKey===i);a||(n.slots||=[],a={slotKey:i,slotScope:!1,slotSwitch:`1`,components:[]},n.slots.push(a)),a.components||(a.components=[]),a.components.push(e)}else{let n=this.findSchemaNodeByComponentId(r.view,t.componentId);n?n.parentArr.splice(n.index+1,0,e):(r.view.components||(r.view.components=[]),r.view.components.push(e))}this.afterStructuralEdit(e.componentId)},afterStructuralEdit(e){let t=this.currentSchema;this.reloadToken+=1,this.preview=Pu(t),typeof window<`u`&&(window.__cardModel=Bu(t)),this.syncDataToSchema(),this.selectedNodeId=e&&this.findViewIdByComponentId(this.preview.view,e)||``,this.scheduleCommit()},deleteSelectedNode(){let e=this.selectedNodeId;if(!e||e===`_root`||!this.currentSchema||!this.preview)return;let t=this.findPreviewNodeById(this.preview.view,e);if(!t)return;let n=this.findSchemaNodeByComponentId(this.currentSchema.view,t.componentId);n&&(n.parentArr.splice(n.index,1),this.reloadToken+=1,this.preview=Pu(this.currentSchema),typeof window<`u`&&(window.__cardModel=Bu(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``,Ex(),this.scheduleCommit())},hasTextSelection(){if(typeof window>`u`||!window.getSelection)return!1;let e=window.getSelection();return!!e&&!e.isCollapsed&&e.toString().trim()!==``},hasNodeSelection(){return!!this.selectedNodeId&&this.selectedNodeId!==`_root`},isEditableTarget(e){let t=e.target;if(!t)return!1;let n=t.tagName;return!!(n===`INPUT`||n===`TEXTAREA`||n===`SELECT`||t.isContentEditable)},onKeydown(e){if(this.chromeOff||!this.preview)return;let t=this.isEditableTarget(e),n=e.ctrlKey||e.metaKey,r=e.key;if(n&&(r===`c`||r===`C`)){if(t||this.hasTextSelection()||!this.hasNodeSelection())return;this.copySelectedNode(),e.preventDefault();return}if(n&&(r===`v`||r===`V`)){if(t||this.hasTextSelection()||!this.hasNodeSelection()||!Hu())return;this.pasteNode(),e.preventDefault();return}if(n&&(r===`d`||r===`D`)){if(t||this.hasTextSelection()||!this.hasNodeSelection())return;this.duplicateNode(),e.preventDefault();return}if(n&&(r===`z`||r===`Z`)){if(t)return;e.preventDefault(),e.shiftKey?this.redo():this.undo();return}if(n&&(r===`y`||r===`Y`)){if(t)return;e.preventDefault(),this.redo();return}if(r===`Delete`||r===`Backspace`){if(t||!this.selectedNodeId||this.selectedNodeId===`_root`)return;e.preventDefault(),this.deleteSelectedNode();return}if(r===`Escape`){this.selectedNodeId&&(this.selectedNodeId=``,e.preventDefault());return}},onHoverNode(e){if(!e){Ex();return}if(e.leave){Ex(e.id);return}let t=typeof document<`u`?document.querySelector(`.phone__screen`):null;if(!t){Ex(e.id);return}let n=e=>window.CSS&&CSS.escape?CSS.escape(String(e)):String(e).replace(/["\\\]]/g,`\\$&`),r=null;e.componentId!=null&&(r=t.querySelector(`[data-cid="`+n(e.componentId)+`"]`)),!r&&e.id!=null&&(r=t.querySelector(`[data-cid="`+n(e.id)+`"]`)),r?Tx(r,e.id):Ex(e.id)},onCanvasClick(e){if(!e.ctrlKey&&!e.metaKey||this.chromeOff)return;let t=e.target&&e.target.closest?e.target.closest(`[data-cid]`):null;if(!t)return;let n=t.getAttribute(`data-cid`);if(!n)return;let r=this.findPreviewNodeByCid(n);r&&(this.activeTab=`structure`,this.selectedNodeId=r.id)},findPreviewNodeByCid(e){let t=this.preview&&this.preview.view;if(!t)return null;let n=String(e),r=e=>{if(!e)return null;if(String(e.componentId==null?e.id:e.componentId)===n)return e;let t=[];if(e.components&&t.push(...e.components),e.slots)for(let n of e.slots)n.components&&t.push(...n.components);for(let e of t){let t=r(e);if(t)return t}return null};return r(t)},onAddNode(e){this.currentSchema&&(this.addNodeTarget=e||null,this.addNodePanelOpen=!0)},cancelAddNode(){this.addNodePanelOpen=!1,this.addNodeTarget=null},findSchemaLocation(e,t){let n=e=>{for(let r=0;r<e.length;r++){let i=e[r];if(i===t)return{containerArr:e,index:r};let a=i.slots||[];for(let e of a){let t=n(e.components||[]);if(t)return t}}return null};return n(e&&e.components||[])},findViewIdByComponentId(e,t){if(!e)return null;if(e.componentId===t)return e.id;let n=[];if(e.components&&n.push(...e.components),e.slots)for(let t of e.slots)t.components&&n.push(...t.components);for(let e of n){let n=this.findViewIdByComponentId(e,t);if(n)return n}return null},findSchemaNodeByComponentId(e,t){let n=(e,r)=>{for(let i=0;i<e.length;i++){let a=e[i];if(a.componentId===t)return{node:a,parentArr:e,index:i,owner:r};let o=a.slots||[];for(let e of o){let t=n(e.components||[],a);if(t)return t}}return null};return n(e&&e.components||[],`root`)},isDescendantCompId(e,t){if(!t||!t.componentId)return!1;if(t.componentId===e)return!0;let n=[];if(t.components&&n.push(...t.components),t.slots)for(let e of t.slots)e.components&&n.push(...e.components);for(let t of n)if(this.isDescendantCompId(e,t))return!0;return!1},moveNode(e,t,n){let r=this.currentSchema;if(!r||!r.view||e===t)return!1;let i=this.findSchemaNodeByComponentId(r.view,e);if(!i)return!1;let a=i.node;if(this.isDescendantCompId(t,a))return!1;let o,s;if(t===`__root__`)r.view.components||(r.view.components=[]),o=r.view.components,s=o.length;else{let e=this.findSchemaNodeByComponentId(r.view,t);if(!e)return!1;let i=e.node;if(n===`inside`){if(!ju(i.className))return!1;let e=Mu(i.className),t=(i.slots||[]).find(t=>t.slotKey===e);t||(i.slots||=[],t={slotKey:e,slotScope:!1,slotSwitch:`1`,components:[]},i.slots.push(t)),t.components||(t.components=[]),o=t.components,s=o.length}else o=e.parentArr,s=n===`before`?e.index:e.index+1}let c=o===i.parentArr&&i.index<s;return i.parentArr.splice(i.index,1),c&&--s,o.splice(s,0,a),a},onMoveNode({sourceCompId:e,targetCompId:t,position:n}){if(!this.currentSchema||!this.moveNode(e,t,n))return;this.reloadToken+=1,this.preview=Pu(this.currentSchema),typeof window<`u`&&(window.__cardModel=Bu(this.currentSchema)),this.syncDataToSchema();let r=this.findViewIdByComponentId(this.preview.view,e);this.selectedNodeId=r||``,r&&(this.moveCollapseToken+=1)},onPickComponent(e){let t=this.addNodeTarget||{},n=t.node||null,r=t.mode||`auto`,i=this.currentSchema;if(!i){this.cancelAddNode();return}let a;try{a=Nu(e)}catch(e){console.error(`[创建组件节点失败]`,e),this.rt.pushError(`addNode`,String(e&&e.message)),this.cancelAddNode();return}let o=!n||n.className===`Root`;if((r===`before`||r===`after`)&&!o){let e=n._schemaNode,t=e?this.findSchemaLocation(i.view,e):null;if(!t)i.view.components||(i.view.components=[]),i.view.components.push(a);else{let e=r===`before`?t.index:t.index+1;t.containerArr.splice(e,0,a)}}else if(o)i.view.components||(i.view.components=[]),i.view.components.push(a);else{let e=n._schemaNode;if(!e){this.cancelAddNode();return}if(ju(n.className)){let t=Mu(n.className),r=(e.slots||[]).find(e=>e.slotKey===t);r||(e.slots||=[],r={slotKey:t,slotScope:!1,slotSwitch:`1`,components:[]},e.slots.push(r)),r.components||(r.components=[]),r.components.push(a)}else{let t=this.findSchemaLocation(i.view,e);t?t.containerArr.splice(t.index+1,0,a):(i.view.components||(i.view.components=[]),i.view.components.push(a))}}this.cancelAddNode(),this.reloadToken+=1,this.preview=Pu(i),typeof window<`u`&&(window.__cardModel=Bu(i)),this.syncDataToSchema();let s=this.findViewIdByComponentId(this.preview.view,a.componentId);this.selectedNodeId=s||``,this.scheduleCommit()},async copySchema(){if(!this.currentSchema)return;this.syncDataToSchema();let e=``;try{let t={...this.currentSchema};this.rt&&this.rt.rootScope&&(t.data=this.rt.rootScope),e=JSON.stringify(t,(e,t)=>{if(e!==`_schemaNode`)return t},2)}catch{e=`(无法序列化)`}await this.doCopy(e)&&(this.copiedSchema=!0,this.copyTimer&&clearTimeout(this.copyTimer),this.copyTimer=setTimeout(()=>{this.copiedSchema=!1},1500))},async doCopy(e){try{if(navigator.clipboard&&navigator.clipboard.writeText)return await navigator.clipboard.writeText(e),!0}catch{}try{let t=document.createElement(`textarea`);t.value=e,t.style.position=`fixed`,t.style.opacity=`0`,document.body.appendChild(t),t.select();let n=document.execCommand(`copy`);return document.body.removeChild(t),n}catch{return!1}},async runSync(e){if(!this.syncingRemote&&!(e&&!this.selectedId)){this.syncingRemote=!0,this.syncedRemote=!1,this.syncTarget=e?`selected`:`full`;try{let t=await J(`/__sync`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e?{cardCode:e}:{})});if(!t.ok){let e=await t.json().catch(()=>({}));throw Error(e.message||e.error||`HTTP `+t.status)}let n=await t.json();if(n.needCredentials){this.openLoginModal();return}if(!n.success){let e=(n.failed||[]).map(e=>`${e.id}: ${e.error}`).join(`; `);throw Error(e||`同步失败`)}this.syncedRemote=!0,this.syncTimer&&clearTimeout(this.syncTimer),this.syncTimer=setTimeout(()=>{this.syncedRemote=!1,this.syncTarget=null},1800),await this.onRefresh(),Z(this.syncTarget===`selected`?`已同步当前卡片到本地`:`全量同步完成`,`ok`)}catch(e){console.error(`[同步远程数据失败]`,e),Z(`同步失败:`+(e&&e.message),`err`)}finally{this.syncingRemote=!1}}},syncRemote(){return this.runSync()},syncSelected(){return this.runSync(this.selectedId)},openLoginModal(){this.loginModal=!0,this.loginName=``,this.loginPassword=``,this.loginError=``},closeLoginModal(){this.loginSubmitting||(this.loginModal=!1,this.loginPassword=``,this.loginError=``)},async submitLogin(){if(this.loginSubmitting)return;let e=this.loginName.trim(),t=this.loginPassword;if(!e||!t){this.loginError=`账号与密码均不能为空`;return}this.loginSubmitting=!0,this.loginError=``;try{let n=await J(`/__login-credentials`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({loginName:e,loginPassword:t})}),r=await n.json().catch(()=>({}));if(!n.ok||!r.ok)throw Error(r.message||r.error||`HTTP `+n.status);this.loginModal=!1,this.loginPassword=``,Z(`凭据已保存,正在重试同步…`,`ok`),await this.runSync(this.syncTarget===`selected`?this.selectedId:void 0)}catch(e){this.loginError=`保存失败:`+(e&&e.message)}finally{this.loginSubmitting=!1}},chooseSync(e){if(e===`full`){if(this.syncingRemote)return;this.requestConfirm({title:`全量同步`,message:`即将从远端 FinMall 拉取完整卡片列表,并逐张覆盖本地同名卡片。本地独有卡片会保留,但被覆盖卡片的未保存改动将丢失,且无法撤销。是否继续?`,confirmText:`继续同步`,severity:`warn`,onConfirm:()=>this.syncRemote()});return}!this.preview||!this.selectedId||this.syncingRemote||this.requestConfirm({title:`当前同步`,message:`即将从远端 FinMall 拉取当前卡片「${this.selectedId}」的最新版本并覆盖本地。本地未保存的改动将丢失,且无法撤销。是否继续?`,confirmText:`继续同步`,onConfirm:()=>this.syncSelected()})},chooseRsave(e){if(e===`save`){if(!this.preview||!this.selectedId||this.savingRemote||!this.hasCardSource)return;this.requestConfirm({title:`版本推送`,message:`即将把本地卡片「${this.selectedId}」的当前版本推送并覆盖到远端 FinMall。线上版本会被立即更新,且该操作不可撤销。是否继续?`,confirmText:`继续推送`,severity:`warn`,onConfirm:()=>this.saveToRemote()});return}if(e===`slices`){if(!this.preview||!this.selectedId||this.uploadingSlices)return;this.requestConfirm({title:`切图上传`,message:`即将把当前卡片「${this.selectedId}」引用的本地切图(src/slices/)上传到 FinMall 图片库,并把卡片里的 /slices/ 引用替换为线上地址(@filestore/…)。本地切图文件保留不动;内容未变的切图会直接复用已有线上地址,不重复上传。`+(this.historyDirty?`
|
|
251
|
+
输入区拖高 / 反馈条出现都不会把它盖住或悬空 `),U(`div`,Kk,[U(`div`,{ref:`listEl`,class:`aichat__list`,onScroll:t[6]||=(...e)=>a.onScroll&&a.onScroll(...e)},[i.loadingHistory?(B(),V(`div`,qk,`正在加载对话记录…`)):i.messages.length?(B(),V(z,{key:2},[(B(!0),V(z,null,R(i.messages,(e,t)=>(B(),H(o,{key:e.id,msg:e,streaming:i.busy&&t===i.messages.length-1,"can-regenerate":e.role===`assistant`&&t===i.messages.length-1,onRegenerate:a.regenerate,onApproval:a.onApproval,onContinue:a.onContinue},null,8,[`msg`,`streaming`,`can-regenerate`,`onRegenerate`,`onApproval`,`onContinue`]))),128)),i.busy?(B(),V(`div`,$k,`生成中… 可随时终止`)):K(`v-if`,!0)],64)):(B(),V(`div`,Jk,[t[19]||=as(`<span class="aichat__empty-logo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-sparkles"></use></svg></span><p class="aichat__empty-title">让 AI 直接改这张卡</p><p class="aichat__empty-desc"> 描述需求即可,AI 会调用卡片工具读改校验并截图自检;也可以把设计稿标注目录、参考图拖进来,只传本机路径不上传。 </p>`,3),U(`div`,Yk,[(B(!0),V(z,null,R(i.suggestions,e=>(B(),V(`button`,{key:e.text,type:`button`,class:`aichat__suggest`,onClick:t=>a.useSuggestion(e)},[(B(),V(`svg`,Zk,[U(`use`,{href:e.icon},null,8,Qk)])),U(`span`,null,M(e.label),1)],8,Xk))),128))])]))],544),K(` 回到底部:脱离底部吸附时出现 `),I(U(`button`,{type:`button`,class:`aichat__jump`,title:`回到底部`,onClick:t[7]||=e=>a.scrollToBottom(!0)},[...t[20]||=[U(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-arrow-down`})],-1)]],512),[[Sc,!i.pinned&&i.messages.length]])]),K(` 操作反馈(附件解析失败 / 历史保存失败等) `),i.notice?(B(),V(`div`,{key:1,class:j([`aichat__notice`,`aichat__notice--${i.notice.type}`])},[U(`span`,null,M(i.notice.text),1),U(`button`,{type:`button`,class:`aichat__notice-x`,title:`关闭`,onClick:t[8]||=e=>i.notice=null},[...t[21]||=[U(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-x`})],-1)]])],2)):K(`v-if`,!0),W(s,{ref:`composerEl`,busy:i.busy,model:i.config.model,attachments:i.draftAttachments,placeholder:a.configReady?`描述你要改的卡片,Enter 发送 / Shift+Enter 换行`:`请先在设置中配置模型…`,context:i.contextUsage,onSend:a.onSend,onStop:a.stop,onPick:a.onPick,onAddPath:a.onAddPath,onRemoveAttachment:a.removeAttachment,onDropFiles:a.onDropFiles,onPasteImage:a.onPasteImage,onEscape:a.onEscape},null,8,[`busy`,`model`,`attachments`,`placeholder`,`context`,`onSend`,`onStop`,`onPick`,`onAddPath`,`onRemoveAttachment`,`onDropFiles`,`onPasteImage`,`onEscape`]),K(` 模型设置(面板内浮层,不额外抬 z-index) `),i.settingsOpen?(B(),H(c,{key:2,config:i.config,onClose:t[9]||=e=>i.settingsOpen=!1,onSaved:a.onConfigSaved},null,8,[`config`,`onSaved`])):K(`v-if`,!0),W(l,{visible:i.confirmClear,title:`清空对话记录`,message:`将删除卡片「${n.cardName||n.cardId||`当前卡片`}」的全部对话记录,此操作不可撤销。`,"confirm-text":`清空`,severity:`danger`,onConfirm:a.doClear,onCancel:t[10]||=e=>i.confirmClear=!1},null,8,[`visible`,`message`,`onConfirm`])],38)}var tA=X(zk,[[`render`,eA],[`__file`,`/Users/owlaford/Workspace/dsl-card/card-editor/components/ChatPanel.vue`]]),nA=`/assets/site-icon-CFMif8z4.png`,rA={includeMocks:!0},iA=2500,aA=new eu,oA=au({},aA);typeof window<`u`&&(window.__rt=oA);var sA=188,cA=320,lA=200,uA=160,dA={name:`App`,components:{RenderTree:ap,DebugPanel:wy,ApiCallPanel:By,CardSelect:ib,AlertCardLayer:mb,DeviceOverlay:Yb,PickerOverlay:ix,DatePickerOverlay:yx,SelectionOverlay:Cx,HoverGuides:kx,RemotePreview:_S,AppToast:TS,ConfirmDialog:_p,CardGallery:KS,NewCardDialog:uC,ComponentPalette:TC,Switch:kC,ChatPanel:tA},provide(){return{adapter:aA,cardRuntime:oA,customComponents:{}}},data(){return{adapter:aA,rt:oA,logoUrl:nA,appVersion:`0.17.1`,cardList:[],cardCount:0,galleryOpen:!1,newCardOpen:!1,selectedId:``,externalReloadNote:``,externalReloadTimer:null,selfSaveId:``,selfSaveAt:0,preview:null,chromeOff:!1,theme:`dark`,cardReady:!1,reloadToken:0,moveCollapseToken:0,refreshing:!1,loadingCard:!1,currentSchema:null,currentCard:null,cardImportMeta:null,localSchema:null,localData:null,selectedNodeId:``,activeTab:`vars`,apiPanelOpen:!0,addNodePanelOpen:!1,addNodeTarget:null,validateBadge:{state:`idle`,errors:0,warnings:0,renderOk:null,ts:0},validateTimer:null,copiedSchema:!1,copyTimer:null,savedCard:!1,savingCard:!1,saveTimer:null,savingRemote:!1,savedRemote:!1,verifiedRemote:null,saveRemoteTimer:null,syncingRemote:!1,syncedRemote:!1,remotePreviewOpen:!1,syncTarget:null,syncTimer:null,uploadingSlices:!1,loginModal:!1,loginName:``,loginPassword:``,loginSubmitting:!1,loginError:``,confirm:{show:!1,title:``,message:``,confirmText:`继续`,cancelText:`取消`,severity:`primary`,onConfirm:null},past:[],future:[],baseline:null,_commitTimer:null,viewportHeight:700,canvasBg:`gray`,refreshing:!1,bgOptions:[{key:`checker`,label:`棋盘`},{key:`white`,label:`白色`},{key:`gray`,label:`浅灰`},{key:`black`,label:`黑色`}],_previewWidthPx:372,_apiPanelWidthPx:360,presetSizes:[{key:375,value:`375`},{key:430,value:`430`},{key:188,value:`188`}],stageWidth:typeof window<`u`?window.innerWidth:1280,colsWidth:0,stageHeight:typeof window<`u`?window.innerHeight-52:748,dragging:!1,apiDragging:!1,_stageObserver:null,devServiceOpen:!1,aiChatOpen:(()=>{try{return sessionStorage.getItem(`cardbuddy:ai-chat-open`)===`1`}catch{return!1}})(),devServiceForm:Nl(),apiBaseResolved:Fl(),devSvcLive:`unknown`,devSvcDown:!1,_unsubCardsChanged:null}},computed:{currentBg(){return this.bgOptions.find(e=>e.key===this.canvasBg)||this.bgOptions[0]},cardIds(){return this.cardList.map(e=>e&&e.id).filter(Boolean)},canUndo(){return this.past.length>0},canRedo(){return this.future.length>0},devSvcBlockReason(){let e=this.devServiceForm||{};return String(e.baseUrl||``).trim()?e.enabled?null:`disabled`:`empty`},canPaste(){return!!Hu()},hasCardSource(){return!!(this.cardImportMeta&&this.cardImportMeta.source)},historyDirty(){return!this.localSchema||!this.currentSchema?!1:!this.structuralEqual(this.currentSchema,this.localSchema)||!this.dataEqual(this.rt&&this.rt.rootScope,this.localData)},devSvcConn(){return this.apiBaseResolved===null?`unconfigured`:this.devSvcLive===`open`?`open`:this.devSvcDown?`closed`:`connecting`},devSvcTitle(){if(this.apiBaseResolved===null)return this.devSvcBlockReason===`disabled`?`已填地址但未开启用本地服务`:`未配置本地开发服务(部署态必填)`;let e=this.apiBaseResolved===``?`同源开发服务`:`本地开发服务`;return this.devSvcLive===`open`?e+`已连接`:this.devSvcDown?e+`已断开(连接失败或服务已停止,重连中…)`:e+`连接中 / 重连中…`},devSvcClass(){return this.apiBaseResolved===null?`app__btn--warn`:this.devSvcLive===`open`?`app__btn--connected`:this.devSvcDown?`app__btn--disconnected`:`app__btn--reconnecting`},devSvcLabel(){return this.apiBaseResolved===null?`未配置本地服务`:this.devSvcLive===`open`?`本地服务已连接`:this.devSvcDown?`本地服务已断开`:`本地服务连接中…`},badgeText(){return{idle:`待校验`,pending:`校验中…`,ok:`校验通过`,error:`校验未通过`,nopython:`未配置校验`,fail:`校验服务异常`}[this.validateBadge.state]||`待校验`},badgeTitle(){let e=this.validateBadge;if(e.state===`ok`)return`结构合法 + 渲染不抛错(不保证画得对,视觉正确性见截图)`;if(e.state===`error`){let t=[];return e.errors&&t.push(`契约错误 ${e.errors} 个`),e.renderOk===!1&&t.push(`渲染抛出异常`),`校验未通过:`+(t.join(`;`)||`详见 /__validate 与 /__render-status`)}return e.state===`nopython`?`未找到 python3/python,无法跑契约校验(请安装 Python 或设置 DSL_PYTHON)`:e.state===`fail`?`校验服务异常(dev server 未启动或端点不可用)`:``},previewWidth(){if(this.chromeOff)return this.stageWidth;let{min:e,max:t}=this.previewBounds,n=this._previewWidthPx||sA;return Math.max(e,Math.min(t,n))},viewportWidth(){return this.stageWidth,typeof window<`u`?window.innerWidth:1280},debugSoftWidth(){return Math.max(lA,Math.floor(this.viewportWidth/3))},previewBounds(){let e=this.stageWidth||0,t=Math.floor(e/2),n=e-cA-this.debugSoftWidth;return{min:sA,max:Math.max(sA,Math.min(t,n))}},debugMinWidth(){let e=this.colsWidth||Math.max(0,(this.stageWidth||0)-this.previewWidth);return Math.max(lA,Math.min(this.debugSoftWidth,Math.floor(e)-cA))},apiPanelBounds(){let e=this.colsWidth||Math.max(0,(this.stageWidth||0)-this.previewWidth),t=Math.max(uA,Math.floor(e)-this.debugMinWidth);return{min:Math.min(cA,t),max:t}},canvasW(){return Math.round(this.previewWidth||375)},selectedCardName(){let e=this.cardList.find(e=>e.id===this.selectedId);return e&&e.name||this.selectedId||``},apiPanelWidth(){let{min:e,max:t}=this.apiPanelBounds,n=this._apiPanelWidthPx||360;return Math.max(e,Math.min(t,n))},frameStyle(){return{}}},watch:{activeTab(e){e!==`structure`&&(this.selectedNodeId=``,Ex())},aiChatOpen(e){try{sessionStorage.setItem(`cardbuddy:ai-chat-open`,e?`1`:`0`)}catch{}},theme(e){document.documentElement.dataset.theme=e},apiPanelOpen(e){try{localStorage.setItem(`cardbuddy:api-panel-open`,e?`1`:`0`)}catch{}}},beforeUnmount(){this.viewportObserver&&this.viewportObserver.disconnect(),this._stageObserver&&this._stageObserver.disconnect(),this.saveTimer&&clearTimeout(this.saveTimer),this.syncTimer&&clearTimeout(this.syncTimer),this.saveRemoteTimer&&clearTimeout(this.saveRemoteTimer),window.removeEventListener(`keydown`,this.onKeydown)},mounted(){this.viewportObserver=new ResizeObserver(e=>{for(let t of e)this.viewportHeight=Math.round(t.contentRect.height)}),this.$refs.phoneViewport&&this.viewportObserver.observe(this.$refs.phoneViewport),this.loadPreviewWidth(),this.loadApiPanelWidth(),this._stageObserver=new ResizeObserver(e=>{for(let t of e){if(t.target===this.$refs.colsEl){this.colsWidth=Math.round(t.contentRect.width);continue}this.stageWidth=Math.round(t.contentRect.width),this.stageHeight=Math.round(t.contentRect.height)}}),this.$refs.stageEl&&this._stageObserver.observe(this.$refs.stageEl),this.$refs.colsEl&&this._stageObserver.observe(this.$refs.colsEl),window.addEventListener(`keydown`,this.onKeydown)},async created(){Ib(oA);let e=new URLSearchParams(location.search).get(`chrome`);this.chromeOff=e===`off`||e===``;try{let e=localStorage.getItem(`cardbuddy:theme`);(e===`light`||e===`dark`)&&(this.theme=e)}catch{}document.documentElement.dataset.theme=this.theme;try{localStorage.getItem(`cardbuddy:api-panel-open`)===`0`&&(this.apiPanelOpen=!1)}catch{}await this.fetchCardList();let t=new URLSearchParams(location.search).get(`card`),n=null;try{let e=localStorage.getItem(`cardbuddy:selected-card`);e&&this.cardList.some(t=>t.id===e)&&(n=e)}catch{}let r=t&&this.cardList.some(e=>e.id===t)?t:n||this.cardList[0]&&this.cardList[0].id;r&&(this.selectedId=r,await this.loadCard(this.selectedId)),this.$watch(()=>this.rt.rootScope,()=>{this.syncDataToSchema()},{deep:!0}),this.setupCardsChanged()},methods:{setupCardsChanged(){if(this._unsubCardsChanged){try{this._unsubCardsChanged()}catch{}this._unsubCardsChanged=null}this.devSvcLive=`connecting`,this._unsubCardsChanged=Vl(e=>this.onCardsChanged(e),e=>this.onDevServiceState(e))},onDevServiceState(e){let t=this.devSvcLive===`connecting`;this.devSvcLive=e,e===`open`?this.devSvcDown=!1:e===`closed`&&(this.devSvcDown=!0),e===`connecting`?!t&&!this._svcWatchdog&&(this._svcWatchdog=setTimeout(()=>{this.devSvcLive===`connecting`&&(this.devSvcLive=`closed`,this.devSvcDown=!0),this._svcWatchdog=null},8e3)):this._svcWatchdog&&=(clearTimeout(this._svcWatchdog),null)},saveDevService(){Pl(this.devServiceForm),this.apiBaseResolved=Fl(),this.setupCardsChanged(),this.devServiceOpen=!1,Z(`本地开发服务配置已保存`,`ok`),this.fetchCardList()},beginColumnDrag(e,t,n){let r=e&&e.currentTarget;if(!r)return;let i=e.pointerId;try{r.setPointerCapture(i)}catch{}let a=()=>{r.removeEventListener(`pointermove`,t),r.removeEventListener(`pointerup`,a),r.removeEventListener(`pointercancel`,a),window.removeEventListener(`blur`,a);try{r.hasPointerCapture?.(i)&&r.releasePointerCapture(i)}catch{}document.body.style.cursor=``,document.body.style.userSelect=``,n()};document.body.style.cursor=`col-resize`,document.body.style.userSelect=`none`,r.addEventListener(`pointermove`,t),r.addEventListener(`pointerup`,a),r.addEventListener(`pointercancel`,a),window.addEventListener(`blur`,a)},startDrag(e){if(this.chromeOff)return;let t=this.$refs.stageEl;if(!t)return;let n=t.getBoundingClientRect().left;this.dragging=!0,this.beginColumnDrag(e,e=>{let{min:t,max:r}=this.previewBounds;this._previewWidthPx=Math.max(t,Math.min(r,Math.round(e.clientX-n)))},()=>{this.dragging=!1,this.savePreviewWidth()})},startApiDrag(e){if(this.chromeOff||!this.apiPanelOpen)return;let t=this.$refs.colsEl;if(!t)return;let n=t.getBoundingClientRect().right;this.apiDragging=!0,this.beginColumnDrag(e,e=>{let{min:t,max:r}=this.apiPanelBounds;this._apiPanelWidthPx=Math.max(t,Math.min(r,Math.round(n-e.clientX)))},()=>{this.apiDragging=!1,this.saveApiPanelWidth()})},loadPreviewWidth(){try{let e=localStorage.getItem(`cardbuddy:preview-width`);if(e!=null){let t=parseInt(e,10);!Number.isNaN(t)&&t>=188&&(this._previewWidthPx=t)}}catch{}},savePreviewWidth(){try{localStorage.setItem(`cardbuddy:preview-width`,String(Math.round(this._previewWidthPx)))}catch{}},setPresetWidth(e){if(this.chromeOff)return;let{min:t,max:n}=this.previewBounds;this._previewWidthPx=Math.max(t,Math.min(n,e)),this.savePreviewWidth()},loadApiPanelWidth(){try{let e=localStorage.getItem(`cardbuddy:api-panel-width`);if(e!=null){let t=parseInt(e,10);!Number.isNaN(t)&&t>=320&&(this._apiPanelWidthPx=t)}}catch{}},saveApiPanelWidth(){try{localStorage.setItem(`cardbuddy:api-panel-width`,String(Math.round(this.apiPanelWidth)))}catch{}},chooseBg(e){this.canvasBg=e},onSelect(e){this.loadingCard=!0,this.selectedId=e,this.loadCard(e,{clearAll:!0})},selectCard(e){this.selectedId=e;try{localStorage.setItem(`cardbuddy:selected-card`,e)}catch{}},openGallery(){this.cardList.length&&(this.galleryOpen=!0)},openNewCard(){this.newCardOpen=!0,this.fetchCardList()},closeGallery(){this.galleryOpen=!1},async openCardFolder(){if(this.selectedId)try{let e=await J(`/__open-folder`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({id:this.selectedId})}),t=(e.headers.get(`content-type`)||``).includes(`application/json`)?await e.json().catch(()=>({})):{};!e.ok||!t.ok?Z(t.error||`打开文件夹失败`,`err`):Z(`已打开卡片文件夹`,`ok`)}catch{Z(`无法连接本地开发服务,请确认已启动`,`err`)}},async refreshCard(){if(!(!this.selectedId||this.refreshing)){this.refreshing=!0;try{await this.loadCard(this.selectedId)}finally{this.refreshing=!1}}},toggleTheme(){this.theme=this.theme===`light`?`dark`:`light`;try{localStorage.setItem(`cardbuddy:theme`,this.theme)}catch{}},toggleApiPanel(){this.apiPanelOpen=!this.apiPanelOpen},async refreshValidateBadge(){let e=this.selectedId;if(!(!e||!this.preview)&&(this.validateTimer&&clearTimeout(this.validateTimer),this.validateBadge={...this.validateBadge,state:`pending`},await new Promise(e=>{this.validateTimer=setTimeout(e,120)}),this.selectedId===e))try{let[t,n]=await Promise.all([J(`/__validate/${encodeURIComponent(e)}`),J(`/__render-status/${encodeURIComponent(e)}`)]);if(!t.ok){this.validateBadge={...this.validateBadge,state:t.status===503?`nopython`:`fail`};return}let r=await t.json().catch(()=>null),i=n.ok?await n.json().catch(()=>null):null;if(!r){this.validateBadge={...this.validateBadge,state:`fail`};return}let a=!!r.ok,o=!i||!!i.renderOk,s=a&&o?`ok`:`error`,c=this.validateBadge.state!==s;this.validateBadge={state:s,errors:(r.errors||[]).length,warnings:(r.warnings||[]).length,renderOk:o,ts:Date.now()},c&&(s===`ok`||s===`error`)&&this.celebrateValidate(s)}catch{this.validateBadge={...this.validateBadge,state:`fail`}}},celebrateValidate(e=`ok`){let t=this.$refs.validateBadgeEl;if(!t||window.matchMedia&&window.matchMedia(`(prefers-reduced-motion: reduce)`).matches)return;t.classList.remove(`is-celebrating`),t.offsetWidth,t.classList.add(`is-celebrating`),setTimeout(()=>t.classList.remove(`is-celebrating`),600);let n=t.getBoundingClientRect(),r={ok:`--ok`,error:`--danger`,pending:`--warn`,fail:`--text-faint`,nopython:`--text-faint`}[e]||`--ok`,i={ok:`#52c41a`,error:`#F5222D`,pending:`#FAAD14`,fail:`#8c8c8c`,nopython:`#8c8c8c`}[e]||`#52c41a`,a=getComputedStyle(t).getPropertyValue(r).trim()||i,o=document.createElement(`div`);o.className=`app__confetti`,o.style.left=n.left+n.width/2+`px`,o.style.top=n.top+n.height/2+`px`;let s=[`polygon(50% 0%, 0% 100%, 100% 100%)`,`polygon(62% 0%, 100% 40%, 45% 100%, 0% 52%)`,`polygon(0% 0%, 100% 18%, 82% 100%, 12% 70%)`,`polygon(22% 0%, 100% 8%, 88% 100%, 0% 76%)`,`polygon(0% 32%, 100% 0%, 76% 100%, 18% 86%)`,`polygon(8% 0%, 100% 22%, 70% 100%, 0% 64%)`,`polygon(0% 12%, 100% 0%, 100% 78%, 30% 100%, 12% 55%)`],c=n.width/2,l=n.height/2;for(let e=0;e<22;e++){let e=document.createElement(`span`);e.className=`app__confetti-bit`;let t=Math.random()*4|0,n,r;t===0?(n=(Math.random()*2-1)*c,r=-l):t===1?(n=c,r=(Math.random()*2-1)*l):t===2?(n=(Math.random()*2-1)*c,r=l):(n=-c,r=(Math.random()*2-1)*l);let i=Math.hypot(n,r)||1,u=Math.atan2(r/i,n/i)+(Math.random()-.5)*.5,d=16+Math.random()*26;e.style.setProperty(`--sx`,n.toFixed(1)+`px`),e.style.setProperty(`--sy`,r.toFixed(1)+`px`),e.style.setProperty(`--tx`,(Math.cos(u)*d).toFixed(1)+`px`),e.style.setProperty(`--ty`,(Math.sin(u)*d).toFixed(1)+`px`),e.style.setProperty(`--rot`,(Math.random()*720-360).toFixed(0)+`deg`),e.style.setProperty(`--clip`,s[Math.random()*s.length|0]),e.style.setProperty(`--c`,a),e.style.setProperty(`--o`,(.68+Math.random()*.27).toFixed(2));let f=Math.random(),p,m;f<.35?(p=1.5+Math.random()*2,m=10+Math.random()*10):f<.75?(p=4+Math.random()*5,m=p*(.6+Math.random()*.8)):(p=2+Math.random()*2.5,m=p),e.style.width=p.toFixed(0)+`px`,e.style.height=m.toFixed(0)+`px`,o.appendChild(e)}document.body.appendChild(o),setTimeout(()=>o.remove(),800)},async loadCard(e,t={}){if(e){this.loadingCard=!0;try{let n=await J(`/__card/${encodeURIComponent(e)}`);if(!n.ok){this.rt.pushError(`loadCard`,`HTTP `+n.status);return}let r=await n.json();if(!r.schema){this.rt.pushError(`loadCard`,`schema 读取失败`);return}this.selectCard(e),t.clearAll?this.rt.clearErrors():this.rt.clearBindingErrors(),this.rt.clearApiCalls(),t.keepScope||this.rt.setRootScope(Xg(r.schema,rA)),this.currentCard={schema:r.schema},this.currentSchema=r.schema,this.cardImportMeta=null,this.fetchImportMeta(e),this.localSchema=this.cloneSchema(r.schema),this.localData=this.cloneData(Xg(r.schema,rA)),this.preview=Pu(r.schema),typeof window<`u`&&(window.__cardModel=Bu(r.schema)),this.cardReady=!1,this.$nextTick(()=>{this.cardReady=!0}),this.syncDataToSchema(),this.selectedNodeId=``,this.baseline={schema:this.cloneSchema(this.currentSchema)},this.reloadToken+=1,ub(),Lb(),this.refreshValidateBadge(),await this.loadHistory(e)}finally{this.loadingCard=!1}}},cloneSchema(e){return e?JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t)):null},normalizeForCompare(e){let t=this.cloneSchema(e);if(t&&t.dictionary&&Array.isArray(t.dictionary.inputParams))for(let e of t.dictionary.inputParams)e&&typeof e==`object`&&(e.defaultValue=void 0);return t},structuralEqual(e,t){return!e||!t?e===t:JSON.stringify(this.normalizeForCompare(e))===JSON.stringify(this.normalizeForCompare(t))},cloneData(e){return e==null?e:JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t))},dataEqual(e,t){return JSON.stringify(e)===JSON.stringify(t)},applySchemaSnapshot(e){if(!this.selectedId||!e)return;let t=e.schema||e,n=e.data==null?Xg(t,rA):e.data;this.currentSchema=this.cloneSchema(t),this.rt.setRootScope(this.cloneData(n)),this.preview=Pu(this.currentSchema),typeof window<`u`&&(window.__cardModel=Bu(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``,this.reloadToken+=1,this.refreshValidateBadge()},commitHistory(){if(!this.currentSchema||!this.baseline)return;let e=this.structuralEqual(this.currentSchema,this.baseline.schema),t=this.dataEqual(this.rt.rootScope,Xg(this.baseline.schema,rA));if(e&&t)return;let n={schema:this.baseline.schema};this.past.push(n),this.past.length>30&&this.past.shift(),this.future=[],this.baseline={schema:this.cloneSchema(this.currentSchema)},this.persistSnapshot(n)},scheduleCommit(){this._commitTimer&&clearTimeout(this._commitTimer),this._commitTimer=setTimeout(()=>{this._commitTimer=null,this.commitHistory()},500)},persistSnapshot(e){let t=this.selectedId;if(!t)return;let n=e.schema||e;J(`/__history/${encodeURIComponent(t)}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({schema:n},(e,t)=>e===`_schemaNode`?void 0:t,2)}).catch(e=>console.warn(`[历史快照落盘失败]`,e))},async loadHistory(e){this.past=[],this.future=[];try{let t=await J(`/__history/${encodeURIComponent(e)}`);if(!t.ok)return;let n=await t.json().catch(()=>null),r=n&&n.snapshots||[];this.past=r.slice(-30).map(e=>({schema:e.schema,data:e.data==null?Xg(e.schema,rA):e.data}))}catch(e){console.warn(`[加载编辑历史失败]`,e)}},undo(){if(!this.canUndo)return;let e=this.past.pop();this.future.push(this.baseline),this.future.length>30&&this.future.shift(),this.baseline=e,this.applySchemaSnapshot(e)},redo(){if(!this.canRedo)return;let e=this.future.pop();this.past.push(this.baseline),this.past.length>30&&this.past.shift(),this.baseline=e,this.applySchemaSnapshot(e)},async applyHistoryToLocal(){this.loadingCard||!this.historyDirty||!this.preview||!this.selectedId||await this.saveCard()&&Z(`已将该版本保存到本地`,`ok`)},onSchemaChanged(){!this.selectedId||!this.currentSchema||(this.reloadToken+=1,this.preview=Pu(this.currentSchema),typeof window<`u`&&(window.__cardModel=Bu(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``)},onSchemaCommit(){!this.selectedId||!this.currentSchema||(this.reloadToken+=1,this.preview=Pu(this.currentSchema),typeof window<`u`&&(window.__cardModel=Bu(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``,this.commitHistory())},syncDataToSchema(){let e=this.currentSchema;if(!e||!e.dictionary||!Array.isArray(e.dictionary.inputParams))return;let t=this.rt&&this.rt.rootScope;if(!t||typeof t!=`object`)return;let n=e.dictionary.inputParams,r=Zg(e);for(let e of Object.keys(t)){let i=n.find(t=>t&&(t.key===e||t.name===e));if(!i)continue;let a=JSON.parse(JSON.stringify(t[e],(e,t)=>e===`_schemaNode`?void 0:t));r&&Object.prototype.hasOwnProperty.call(r,e)?r[e]=a:i.defaultValue=a}},onSelectNode(e){this.selectedNodeId=e||``},findPreviewNodeById(e,t){if(!e||t==null)return null;let n=String(t),r=e=>{if(!e)return null;if(String(e.id)===n)return e;let t=[];if(e.components&&t.push(...e.components),e.slots)for(let n of e.slots)n.components&&t.push(...n.components);for(let e of t){let t=r(e);if(t)return t}return null};return r(e)},copySelectedNode(){let e=this.selectedNodeId;if(!e||e===`_root`||!this.currentSchema||!this.preview)return;let t=this.findPreviewNodeById(this.preview.view,e);if(!t)return;let n=this.findSchemaNodeByComponentId(this.currentSchema.view,t.componentId);n&&(Wu(n.node,this.selectedId),Z(`已复制节点(含子节点)`,`ok`))},pasteNode(){let e=Hu();if(!e||!this.currentSchema)return;let t=Gu(e),n=[...new Set(t.filter(e=>!vu[e]))];if(n.length){Z(`粘贴失败:含未知组件类型 `+n.join(`、`),`err`);return}let r=JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t));Ku(r);let i=this.selectedNodeId&&this.selectedNodeId!==`_root`?this.findPreviewNodeById(this.preview.view,this.selectedNodeId):null,a=`root`;i&&(a=ju(i.className)?`into`:`after`),this.insertClone(r,i,a);let o=Uu();o&&o!==this.selectedId?Z(`已从卡片「${o}」跨卡片粘贴到「${this.selectedId}」`,`ok`):Z(`已粘贴节点(含子节点)`,`ok`)},duplicateNode(){let e=this.selectedNodeId;if(!e||e===`_root`||!this.currentSchema||!this.preview)return;let t=this.findPreviewNodeById(this.preview.view,e);if(!t)return;let n=this.findSchemaNodeByComponentId(this.currentSchema.view,t.componentId);if(!n)return;let r=JSON.parse(JSON.stringify(n.node,(e,t)=>e===`_schemaNode`?void 0:t));Ku(r),n.parentArr.splice(n.index+1,0,r),this.afterStructuralEdit(r.componentId)},insertClone(e,t,n){let r=this.currentSchema;if(n===`root`)r.view.components||(r.view.components=[]),r.view.components.push(e);else if(n===`into`){let n=t&&t._schemaNode;if(!n){r.view.components||(r.view.components=[]),r.view.components.push(e),this.afterStructuralEdit(e.componentId);return}let i=Mu(t.className),a=(n.slots||[]).find(e=>e.slotKey===i);a||(n.slots||=[],a={slotKey:i,slotScope:!1,slotSwitch:`1`,components:[]},n.slots.push(a)),a.components||(a.components=[]),a.components.push(e)}else{let n=this.findSchemaNodeByComponentId(r.view,t.componentId);n?n.parentArr.splice(n.index+1,0,e):(r.view.components||(r.view.components=[]),r.view.components.push(e))}this.afterStructuralEdit(e.componentId)},afterStructuralEdit(e){let t=this.currentSchema;this.reloadToken+=1,this.preview=Pu(t),typeof window<`u`&&(window.__cardModel=Bu(t)),this.syncDataToSchema(),this.selectedNodeId=e&&this.findViewIdByComponentId(this.preview.view,e)||``,this.scheduleCommit()},deleteSelectedNode(){let e=this.selectedNodeId;if(!e||e===`_root`||!this.currentSchema||!this.preview)return;let t=this.findPreviewNodeById(this.preview.view,e);if(!t)return;let n=this.findSchemaNodeByComponentId(this.currentSchema.view,t.componentId);n&&(n.parentArr.splice(n.index,1),this.reloadToken+=1,this.preview=Pu(this.currentSchema),typeof window<`u`&&(window.__cardModel=Bu(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``,Ex(),this.scheduleCommit())},hasTextSelection(){if(typeof window>`u`||!window.getSelection)return!1;let e=window.getSelection();return!!e&&!e.isCollapsed&&e.toString().trim()!==``},hasNodeSelection(){return!!this.selectedNodeId&&this.selectedNodeId!==`_root`},isEditableTarget(e){let t=e.target;if(!t)return!1;let n=t.tagName;return!!(n===`INPUT`||n===`TEXTAREA`||n===`SELECT`||t.isContentEditable)},onKeydown(e){if(this.chromeOff||!this.preview)return;let t=this.isEditableTarget(e),n=e.ctrlKey||e.metaKey,r=e.key;if(n&&(r===`c`||r===`C`)){if(t||this.hasTextSelection()||!this.hasNodeSelection())return;this.copySelectedNode(),e.preventDefault();return}if(n&&(r===`v`||r===`V`)){if(t||this.hasTextSelection()||!this.hasNodeSelection()||!Hu())return;this.pasteNode(),e.preventDefault();return}if(n&&(r===`d`||r===`D`)){if(t||this.hasTextSelection()||!this.hasNodeSelection())return;this.duplicateNode(),e.preventDefault();return}if(n&&(r===`z`||r===`Z`)){if(t)return;e.preventDefault(),e.shiftKey?this.redo():this.undo();return}if(n&&(r===`y`||r===`Y`)){if(t)return;e.preventDefault(),this.redo();return}if(r===`Delete`||r===`Backspace`){if(t||!this.selectedNodeId||this.selectedNodeId===`_root`)return;e.preventDefault(),this.deleteSelectedNode();return}if(r===`Escape`){this.selectedNodeId&&(this.selectedNodeId=``,e.preventDefault());return}},onHoverNode(e){if(!e){Ex();return}if(e.leave){Ex(e.id);return}let t=typeof document<`u`?document.querySelector(`.phone__screen`):null;if(!t){Ex(e.id);return}let n=e=>window.CSS&&CSS.escape?CSS.escape(String(e)):String(e).replace(/["\\\]]/g,`\\$&`),r=null;e.componentId!=null&&(r=t.querySelector(`[data-cid="`+n(e.componentId)+`"]`)),!r&&e.id!=null&&(r=t.querySelector(`[data-cid="`+n(e.id)+`"]`)),r?Tx(r,e.id):Ex(e.id)},onCanvasClick(e){if(!e.ctrlKey&&!e.metaKey||this.chromeOff)return;let t=e.target&&e.target.closest?e.target.closest(`[data-cid]`):null;if(!t)return;let n=t.getAttribute(`data-cid`);if(!n)return;let r=this.findPreviewNodeByCid(n);r&&(this.activeTab=`structure`,this.selectedNodeId=r.id)},findPreviewNodeByCid(e){let t=this.preview&&this.preview.view;if(!t)return null;let n=String(e),r=e=>{if(!e)return null;if(String(e.componentId==null?e.id:e.componentId)===n)return e;let t=[];if(e.components&&t.push(...e.components),e.slots)for(let n of e.slots)n.components&&t.push(...n.components);for(let e of t){let t=r(e);if(t)return t}return null};return r(t)},onAddNode(e){this.currentSchema&&(this.addNodeTarget=e||null,this.addNodePanelOpen=!0)},cancelAddNode(){this.addNodePanelOpen=!1,this.addNodeTarget=null},findSchemaLocation(e,t){let n=e=>{for(let r=0;r<e.length;r++){let i=e[r];if(i===t)return{containerArr:e,index:r};let a=i.slots||[];for(let e of a){let t=n(e.components||[]);if(t)return t}}return null};return n(e&&e.components||[])},findViewIdByComponentId(e,t){if(!e)return null;if(e.componentId===t)return e.id;let n=[];if(e.components&&n.push(...e.components),e.slots)for(let t of e.slots)t.components&&n.push(...t.components);for(let e of n){let n=this.findViewIdByComponentId(e,t);if(n)return n}return null},findSchemaNodeByComponentId(e,t){let n=(e,r)=>{for(let i=0;i<e.length;i++){let a=e[i];if(a.componentId===t)return{node:a,parentArr:e,index:i,owner:r};let o=a.slots||[];for(let e of o){let t=n(e.components||[],a);if(t)return t}}return null};return n(e&&e.components||[],`root`)},isDescendantCompId(e,t){if(!t||!t.componentId)return!1;if(t.componentId===e)return!0;let n=[];if(t.components&&n.push(...t.components),t.slots)for(let e of t.slots)e.components&&n.push(...e.components);for(let t of n)if(this.isDescendantCompId(e,t))return!0;return!1},moveNode(e,t,n){let r=this.currentSchema;if(!r||!r.view||e===t)return!1;let i=this.findSchemaNodeByComponentId(r.view,e);if(!i)return!1;let a=i.node;if(this.isDescendantCompId(t,a))return!1;let o,s;if(t===`__root__`)r.view.components||(r.view.components=[]),o=r.view.components,s=o.length;else{let e=this.findSchemaNodeByComponentId(r.view,t);if(!e)return!1;let i=e.node;if(n===`inside`){if(!ju(i.className))return!1;let e=Mu(i.className),t=(i.slots||[]).find(t=>t.slotKey===e);t||(i.slots||=[],t={slotKey:e,slotScope:!1,slotSwitch:`1`,components:[]},i.slots.push(t)),t.components||(t.components=[]),o=t.components,s=o.length}else o=e.parentArr,s=n===`before`?e.index:e.index+1}let c=o===i.parentArr&&i.index<s;return i.parentArr.splice(i.index,1),c&&--s,o.splice(s,0,a),a},onMoveNode({sourceCompId:e,targetCompId:t,position:n}){if(!this.currentSchema||!this.moveNode(e,t,n))return;this.reloadToken+=1,this.preview=Pu(this.currentSchema),typeof window<`u`&&(window.__cardModel=Bu(this.currentSchema)),this.syncDataToSchema();let r=this.findViewIdByComponentId(this.preview.view,e);this.selectedNodeId=r||``,r&&(this.moveCollapseToken+=1)},onPickComponent(e){let t=this.addNodeTarget||{},n=t.node||null,r=t.mode||`auto`,i=this.currentSchema;if(!i){this.cancelAddNode();return}let a;try{a=Nu(e)}catch(e){console.error(`[创建组件节点失败]`,e),this.rt.pushError(`addNode`,String(e&&e.message)),this.cancelAddNode();return}let o=!n||n.className===`Root`;if((r===`before`||r===`after`)&&!o){let e=n._schemaNode,t=e?this.findSchemaLocation(i.view,e):null;if(!t)i.view.components||(i.view.components=[]),i.view.components.push(a);else{let e=r===`before`?t.index:t.index+1;t.containerArr.splice(e,0,a)}}else if(o)i.view.components||(i.view.components=[]),i.view.components.push(a);else{let e=n._schemaNode;if(!e){this.cancelAddNode();return}if(ju(n.className)){let t=Mu(n.className),r=(e.slots||[]).find(e=>e.slotKey===t);r||(e.slots||=[],r={slotKey:t,slotScope:!1,slotSwitch:`1`,components:[]},e.slots.push(r)),r.components||(r.components=[]),r.components.push(a)}else{let t=this.findSchemaLocation(i.view,e);t?t.containerArr.splice(t.index+1,0,a):(i.view.components||(i.view.components=[]),i.view.components.push(a))}}this.cancelAddNode(),this.reloadToken+=1,this.preview=Pu(i),typeof window<`u`&&(window.__cardModel=Bu(i)),this.syncDataToSchema();let s=this.findViewIdByComponentId(this.preview.view,a.componentId);this.selectedNodeId=s||``,this.scheduleCommit()},async copySchema(){if(!this.currentSchema)return;this.syncDataToSchema();let e=``;try{let t={...this.currentSchema};this.rt&&this.rt.rootScope&&(t.data=this.rt.rootScope),e=JSON.stringify(t,(e,t)=>{if(e!==`_schemaNode`)return t},2)}catch{e=`(无法序列化)`}await this.doCopy(e)&&(this.copiedSchema=!0,this.copyTimer&&clearTimeout(this.copyTimer),this.copyTimer=setTimeout(()=>{this.copiedSchema=!1},1500))},async doCopy(e){try{if(navigator.clipboard&&navigator.clipboard.writeText)return await navigator.clipboard.writeText(e),!0}catch{}try{let t=document.createElement(`textarea`);t.value=e,t.style.position=`fixed`,t.style.opacity=`0`,document.body.appendChild(t),t.select();let n=document.execCommand(`copy`);return document.body.removeChild(t),n}catch{return!1}},async runSync(e){if(!this.syncingRemote&&!(e&&!this.selectedId)){this.syncingRemote=!0,this.syncedRemote=!1,this.syncTarget=e?`selected`:`full`;try{let t=await J(`/__sync`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e?{cardCode:e}:{})});if(!t.ok){let e=await t.json().catch(()=>({}));throw Error(e.message||e.error||`HTTP `+t.status)}let n=await t.json();if(n.needCredentials){this.openLoginModal();return}if(!n.success){let e=(n.failed||[]).map(e=>`${e.id}: ${e.error}`).join(`; `);throw Error(e||`同步失败`)}this.syncedRemote=!0,this.syncTimer&&clearTimeout(this.syncTimer),this.syncTimer=setTimeout(()=>{this.syncedRemote=!1,this.syncTarget=null},1800),await this.onRefresh(),Z(this.syncTarget===`selected`?`已同步当前卡片到本地`:`全量同步完成`,`ok`)}catch(e){console.error(`[同步远程数据失败]`,e),Z(`同步失败:`+(e&&e.message),`err`)}finally{this.syncingRemote=!1}}},syncRemote(){return this.runSync()},syncSelected(){return this.runSync(this.selectedId)},openLoginModal(){this.loginModal=!0,this.loginName=``,this.loginPassword=``,this.loginError=``},closeLoginModal(){this.loginSubmitting||(this.loginModal=!1,this.loginPassword=``,this.loginError=``)},async submitLogin(){if(this.loginSubmitting)return;let e=this.loginName.trim(),t=this.loginPassword;if(!e||!t){this.loginError=`账号与密码均不能为空`;return}this.loginSubmitting=!0,this.loginError=``;try{let n=await J(`/__login-credentials`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({loginName:e,loginPassword:t})}),r=await n.json().catch(()=>({}));if(!n.ok||!r.ok)throw Error(r.message||r.error||`HTTP `+n.status);this.loginModal=!1,this.loginPassword=``,Z(`凭据已保存,正在重试同步…`,`ok`),await this.runSync(this.syncTarget===`selected`?this.selectedId:void 0)}catch(e){this.loginError=`保存失败:`+(e&&e.message)}finally{this.loginSubmitting=!1}},chooseSync(e){if(e===`full`){if(this.syncingRemote)return;this.requestConfirm({title:`全量同步`,message:`即将从远端 FinMall 拉取完整卡片列表,并逐张覆盖本地同名卡片。本地独有卡片会保留,但被覆盖卡片的未保存改动将丢失,且无法撤销。是否继续?`,confirmText:`继续同步`,severity:`warn`,onConfirm:()=>this.syncRemote()});return}!this.preview||!this.selectedId||this.syncingRemote||this.requestConfirm({title:`当前同步`,message:`即将从远端 FinMall 拉取当前卡片「${this.selectedId}」的最新版本并覆盖本地。本地未保存的改动将丢失,且无法撤销。是否继续?`,confirmText:`继续同步`,onConfirm:()=>this.syncSelected()})},chooseRsave(e){if(e===`save`){if(!this.preview||!this.selectedId||this.savingRemote||!this.hasCardSource)return;this.requestConfirm({title:`版本推送`,message:`即将把本地卡片「${this.selectedId}」的当前版本推送并覆盖到远端 FinMall。线上版本会被立即更新,且该操作不可撤销。是否继续?`,confirmText:`继续推送`,severity:`warn`,onConfirm:()=>this.saveToRemote()});return}if(e===`slices`){if(!this.preview||!this.selectedId||this.uploadingSlices)return;this.requestConfirm({title:`切图上传`,message:`即将把当前卡片「${this.selectedId}」引用的本地切图(src/slices/)上传到 FinMall 图片库,并把卡片里的 /slices/ 引用替换为线上地址(@filestore/…)。本地切图文件保留不动;内容未变的切图会直接复用已有线上地址,不重复上传。`+(this.historyDirty?`
|
|
252
252
|
当前卡片有未保存的改动,会先保存到本地再执行,避免被随后的重载丢弃。`:``)+`
|
|
253
253
|
是否继续?`,confirmText:`继续上传`,onConfirm:()=>this.uploadCardSlices()});return}!this.preview||!this.selectedId||!this.hasCardSource||(this.remotePreviewOpen=!0)},requestConfirm({title:e,message:t,confirmText:n,cancelText:r,severity:i,onConfirm:a}){this.confirm={show:!0,title:e,message:t,confirmText:n||`继续`,cancelText:r||`取消`,severity:[`primary`,`warn`,`danger`].includes(i)?i:`primary`,onConfirm:a}},onConfirmDialog(){let e=this.confirm.onConfirm;this.confirm.show=!1,this.confirm.onConfirm=null,typeof e==`function`&&e()},onCancelDialog(){this.confirm.show=!1,this.confirm.onConfirm=null},async saveCard(){if(!this.preview||!this.selectedId)return;this.savingCard=!0;let e=this.selectedId;this.selfSaveId=e,this.selfSaveAt=Date.now();try{try{let e=await this.fetchDiskCard(this.selectedId);if(e&&e.schema){let t=e.schema,n=this.currentSchema;n&&(t.pageId&&(n.pageId=t.pageId),t.name&&(n.name=t.name),t.desc!==void 0&&(n.desc=t.desc))}}catch{}this.syncDataToSchema();let e={schema:this.currentSchema},t=JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t,2),n=await J(`/__card/${encodeURIComponent(this.selectedId)}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:t});if(!n.ok){let e=await n.json().catch(()=>({}));throw Error(e.message||`HTTP `+n.status)}return this.currentCard={schema:e.schema},this.localSchema=this.cloneSchema(this.currentSchema),this.localData=this.cloneData(Xg(this.currentSchema,rA)),this.savedCard=!0,this.refreshValidateBadge(),this.saveTimer&&clearTimeout(this.saveTimer),this.saveTimer=setTimeout(()=>{this.savedCard=!1},1800),!0}catch(e){return console.error(`[本地保存失败]`,e),this.rt.pushError(`saveCard`,String(e&&e.message)),this.selfSaveId=``,this.selfSaveAt=0,!1}finally{this.savingCard=!1}},async fetchDiskCard(e){let t=await J(`/__card/${encodeURIComponent(e)}`);if(!t.ok)throw Error(`HTTP `+t.status);return t.json()},async fetchImportMeta(e){if(!e){this.cardImportMeta=null;return}try{let t=await J(`/__import-meta/${encodeURIComponent(e)}`);if(!t.ok){this.cardImportMeta=null;return}let n=await t.json().catch(()=>({}));this.cardImportMeta=n&&n.meta||null}catch{this.cardImportMeta=null}},async saveToRemote(){if(!(!this.preview||!this.selectedId||this.savingRemote)){this.savingRemote=!0,this.savedRemote=!1,this.verifiedRemote=null;try{try{let e=await this.fetchDiskCard(this.selectedId);e&&e.schema&&e.schema.pageId&&this.currentSchema&&(this.currentSchema.pageId=e.schema.pageId)}catch{}this.syncDataToSchema(),this.selfSaveId=this.selectedId,this.selfSaveAt=Date.now();let e=await J(`/__save`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({code:this.selectedId,schema:this.currentSchema},(e,t)=>e===`_schemaNode`?void 0:t,2)});if(!e.ok){let t=await e.json().catch(()=>({}));throw Error(t.message||t.error||`HTTP `+e.status)}let t=await e.json();if(!t.ok)throw Error(t.error||`远端保存失败`);if(this.verifiedRemote=t.verified?!!t.verified.ok:null,this.savedRemote=!0,this.verifiedRemote===!1){let e=t.verified||{};e.diff&&e.diff.length?``+e.diff.join(`;`):e.readBackError,console.warn(`[保存校验不符]`,e),Z(`已保存至远端,但读回校验未通过`,`err`)}else Z(`已保存至远端`,`ok`);this.saveRemoteTimer&&clearTimeout(this.saveRemoteTimer),this.saveRemoteTimer=setTimeout(()=>{this.savedRemote=!1,this.verifiedRemote=null},2500)}catch(e){console.error(`[保存至远端失败]`,e),Z(`保存至远端失败:`+(e&&e.message),`err`),this.selfSaveId=``,this.selfSaveAt=0}finally{this.savingRemote=!1}}},async uploadCardSlices(){if(!this.preview||!this.selectedId||this.uploadingSlices)return;let e=this.selectedId;this.uploadingSlices=!0;try{if(this.historyDirty&&!await this.saveCard())throw Error(`本地保存失败,已中止切图上传`);this.selfSaveId=e,this.selfSaveAt=Date.now();let t=await J(`/__slice-upload`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cardId:e})});if(!t.ok){let e=await t.json().catch(()=>({}));throw Error(e.message||e.error||`HTTP `+t.status)}let n=await t.json();if(n.needCredentials){this.openLoginModal();return}if(this.selfSaveAt=Date.now(),!n.success){let e=(n.failed||[]).map(e=>`${e.name}: ${e.error}`).join(`; `);throw Error(e||`切图上传失败`)}await this.onRefresh();let r=(n.rewritten||[]).reduce((e,t)=>e+t.count,0);n.referenced?Z(`切图上传完成:新上传 ${n.uploaded.length} 张、复用 ${n.skipped.length} 张,替换引用 ${r} 处`,`ok`):Z(`当前卡片没有引用本地切图,无需上传`,`ok`),n.dangling&&n.dangling.length&&Z(`有 ${n.dangling.length} 处引用的切图在 src/slices 下不存在:${n.dangling.join(`、`)}`,`err`)}catch(e){console.error(`[切图上传失败]`,e),Z(`切图上传失败:`+(e&&e.message),`err`),this.selfSaveId=``,this.selfSaveAt=0}finally{this.uploadingSlices=!1}},async fetchCardList(){try{let e=await J(`/__cards`);if(!e.ok)return;let t=await e.json(),n=t&&t.cards||[];this.cardList=n,this.cardCount=n.length}catch(e){console.error(`[加载卡片列表失败]`,e)}},async onGalleryDelete(e){try{let t=await J(`/__card/${encodeURIComponent(e)}`,{method:`DELETE`}),n=await t.json().catch(()=>null);if(!t.ok)throw Error(n&&(n.message||n.error)||`HTTP ${t.status}`);if(Z(`已删除本地卡片 ${e}`,`ok`),await this.fetchCardList(),this.selectedId===e){let e=this.cardList[0];e?await this.loadCard(e.id):(this.selectedId=``,this.currentSchema=null,this.preview=null)}}catch(e){Z(`删除卡片失败:${e.message}`,`err`)}},async onNewCardCreated(e){this.newCardOpen=!1,Z(`已创建空白卡片 ${e}`,`ok`),await this.fetchCardList(),await this.loadCard(e)},async onRefresh(){let e=this.selectedId;if(!(!e||this.refreshing)){this.refreshing=!0;try{await this.loadCard(e),await this.fetchCardList();let t=this.cardList.find(t=>t.id===e);t&&this.currentSchema&&this.currentSchema.name&&(t.name=this.currentSchema.name)}catch(e){console.error(`[刷新卡片失败]`,e),this.rt.pushError(`refresh`,String(e&&e.message))}finally{this.refreshing=!1}}},onCardsChanged(e){let t=e&&e.file||``;if(/\.history[\\/]/.test(t)||/data\.json$/i.test(t))return;let n=t.match(/src[\\/]cards[\\/]([^\\/]+)[\\/]/),r=n?n[1]:null;this._cardsChangedTimer||=setTimeout(()=>{if(this._cardsChangedTimer=null,this.fetchCardList(),r&&r===this.selectedId){if(this.selfSaveId===r&&Date.now()-this.selfSaveAt<iA){this.selfSaveId=``,this.selfSaveAt=0;return}if(this.historyDirty)this.promptExternalReload(r);else{let e=this.currentSchema&&this.currentSchema.name||r;this.showExternalReloadNote(`卡片「${e}」已在编辑器外被修改,已自动重新载入预览`),this.loadCard(this.selectedId)}}},120)},promptExternalReload(e){if(this.confirm.show)return;let t=this.currentSchema&&this.currentSchema.name||e;this.requestConfirm({title:`卡片已在编辑器外被修改`,message:`卡片「${t}」的 schema / data 在编辑器外发生了变更(例如 IDE 或其他工具覆盖了文件)。\n当前编辑器存在未保存的改动,是否放弃本地未保存改动并重新载入磁盘版本?`,confirmText:`重新载入`,cancelText:`保留我的改动`,onConfirm:()=>this.loadCard(e)})},showExternalReloadNote(e){this.externalReloadNote=e,this.externalReloadTimer&&clearTimeout(this.externalReloadTimer),this.externalReloadTimer=setTimeout(()=>{this.externalReloadNote=``,this.externalReloadTimer=null},3500)}}},fA={class:`app__bar`},pA={class:`app__logo`},mA=[`src`],hA={class:`app__logo-version`},gA={class:`csel-combo`},_A=[`disabled`],vA=[`disabled`],yA=[`title`],bA={class:`app__badge-text`},xA={class:`app__bar-actions`},SA={class:`app__op-group`,role:`group`,"aria-label":`编辑历史与本地操作`},CA=[`disabled`],wA=[`disabled`],TA=[`disabled`,`title`],EA=[`disabled`],DA=[`disabled`],OA={key:0,class:`app__icon app__icon--spin`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},kA={key:1,class:`app__icon`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},AA={key:2,class:`app__icon`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},jA={key:3,class:`app__icon`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},MA={class:`app__remote-pop dd__menu`,role:`menu`},NA=[`title`,`onClick`],PA=[`title`,`onClick`],FA=[`onClick`],IA=[`onClick`],LA=[`title`,`onClick`],RA=[`title`,`aria-pressed`],zA=[`aria-checked`,`title`],BA={class:`theme-toggle__track`},VA={class:`theme-toggle__thumb`},HA={key:0,class:`theme-toggle__icon`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},UA={key:1,class:`theme-toggle__icon`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},WA={class:`app__dev-svc`},GA=[`title`],KA={class:`app__dev-svc-head`},qA={class:`app__dev-svc-body`},JA={class:`app__dev-svc-row`},YA={class:`app__dev-svc-row app__dev-svc-row--col`},XA={class:`app__dev-svc-row app__dev-svc-row--col`},ZA={key:0,class:`app__dev-svc-hint`},QA={class:`app__dev-svc-actions`},$A={style:{display:`none`}},ej=[`title`,`aria-expanded`],tj={key:0,class:`app__nosvc-banner`},nj={key:1,class:`app__nosvc-banner app__nosvc-banner--disconnected`},rj={key:2,class:`app__nosvc-banner app__extreload-banner`},ij={class:`app__stage`,ref:`stageEl`},aj={class:`phone__scaler`},oj=[`title`],sj={class:`canvas-bar__txt`},cj={class:`canvas-bar__chip`,title:`画布宽度预设(高度随列高自适应)`},lj={class:`canvas-bar__txt`},uj={class:`canvas-bar__size`,title:`画布实际尺寸(宽 × 高,高度随窗口自适应)`},dj=[`disabled`],fj=[`data-card-ready`],pj={class:`workside`},mj={class:`workside__cols`,ref:`colsEl`},hj={class:`login-modal modal-card`,role:`dialog`,"aria-modal":`true`},gj={class:`login-modal__row`},_j=[`disabled`],vj={class:`login-modal__row`},yj=[`disabled`],bj={key:0,class:`login-modal__error`},xj={class:`login-modal__actions`},Sj=[`disabled`],Cj=[`disabled`];function wj(e,t,n,r,i,a){let o=L(`CardSelect`),s=L(`Dropdown`),c=L(`Switch`),l=L(`ChatPanel`),u=L(`RenderTree`),d=L(`SelectionOverlay`),f=L(`HoverGuides`),p=L(`AlertCardLayer`),m=L(`DeviceOverlay`),h=L(`PickerOverlay`),g=L(`DatePickerOverlay`),_=L(`DebugPanel`),v=L(`ApiCallPanel`),y=L(`RemotePreview`),b=L(`ComponentPalette`),x=L(`ConfirmDialog`),S=L(`CardGallery`),C=L(`NewCardDialog`),w=L(`AppToast`);return B(),V(`div`,{class:j([`app`,{"app--chrome-off":i.chromeOff}]),style:A({"--preview-width":a.previewWidth+`px`,"--api-panel-width":a.apiPanelWidth+`px`,"--debug-min":a.debugMinWidth+`px`})},[I(U(`header`,fA,[U(`strong`,pA,[U(`img`,{class:`app__logo-icon`,src:i.logoUrl,alt:``},null,8,mA),t[35]||=G(` CardBuddy `,-1),U(`span`,hA,`v`+M(i.appVersion),1)]),U(`div`,gA,[W(o,{class:`csel--combo`,value:i.selectedId,options:i.cardList,onChange:a.onSelect},null,8,[`value`,`options`,`onChange`]),U(`button`,{class:`csel-combo__btn`,type:`button`,disabled:!i.selectedId,onClick:t[0]||=(...e)=>a.openCardFolder&&a.openCardFolder(...e),title:`在文件管理器中打开当前卡片所在文件夹`},[...t[36]||=[U(`svg`,{class:`app__icon`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`path`,{d:`M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z`}),U(`path`,{d:`M3 9h18`})],-1)]],8,_A)]),K(` 卡片网格预览:图标按钮,打开本地全部卡片的缩略图墙,点击任一张载入工作台 `),U(`button`,{class:`app__btn app__btn--icon`,type:`button`,disabled:!i.cardList.length,onClick:t[1]||=(...e)=>a.openGallery&&a.openGallery(...e),title:`卡片网格预览`},[...t[37]||=[U(`svg`,{class:`app__icon`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-grid`})],-1)]],8,vA),K(` 新增空白卡片:紧邻网格预览的图标按钮;弹窗收 ID(必填、本地重名前置拦截)+ 描述(选填),创建成功切换到新卡 `),U(`button`,{class:`app__btn app__btn--icon`,type:`button`,onClick:t[2]||=(...e)=>a.openNewCard&&a.openNewCard(...e),title:`新增空白卡片`},[...t[38]||=[U(`svg`,{class:`app__icon`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-folder-plus`})],-1)]]),I(U(`div`,{ref:`validateBadgeEl`,class:j([`app__badge`,`app__badge--`+i.validateBadge.state]),title:a.badgeTitle},[t[39]||=U(`span`,{class:`app__badge-dot`},null,-1),U(`span`,bA,M(a.badgeText),1)],10,yA),[[Sc,!i.chromeOff]]),U(`div`,xA,[U(`div`,SA,[K(` 撤销 / 重做:图标 + 文案;无可回退/重做版本时置灰 `),U(`button`,{class:`app__btn app__btn--hist`,type:`button`,disabled:!i.preview||!a.canUndo,onClick:t[3]||=(...e)=>a.undo&&a.undo(...e),title:`后退一步(撤销)`},[...t[40]||=[U(`svg`,{class:`app__icon`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-undo`})],-1),U(`span`,null,`撤销`,-1)]],8,CA),U(`button`,{class:`app__btn app__btn--hist`,type:`button`,disabled:!i.preview||!a.canRedo,onClick:t[4]||=(...e)=>a.redo&&a.redo(...e),title:`前进一步(重做)`},[...t[41]||=[U(`svg`,{class:`app__icon`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[U(`use`,{href:`#icon-redo`})],-1),U(`span`,null,`重做`,-1)]],8,wA),K(` 保存到本地:当前显示版本(撤销/重做导航到的历史版本,或编辑后未本地保存的版本)
|
|
254
254
|
与本地磁盘版本不一致时高亮可用;一致时置灰。点击把当前版本真正写回磁盘,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16-Cg6Nrosk.png" />
|
|
9
9
|
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32-B_1744sp.png" />
|
|
10
10
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-BQcES9QK.png" />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-Cp_eg64w.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-CQScMhyg.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
@@ -32,6 +32,20 @@ function resultToContent(name, args, result) {
|
|
|
32
32
|
return { content: [{ type: 'text', text }], isError }
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// 在途请求计数 + 有界等待:stdin 关闭(对端断开,或 one-shot 管道写完即 EOF)时**不能立刻退出**。
|
|
36
|
+
// tools/call 的处理是 async 的(要先等 HTTP 失败才走本地降级,截图还要拉起浏览器),直接
|
|
37
|
+
// process.exit(0) 会把尚未写出的响应截断 —— 客户端只看到请求、永远等不到回包。
|
|
38
|
+
// 但也不能无限等:对端可能正卡在一个永不响应的请求上断开,故 DRAIN_GRACE_MS 后强制退出,
|
|
39
|
+
// 兜住「不 linger」这条底线(正常的 list/read/write/validate 收口在毫秒级,够用)。
|
|
40
|
+
const DRAIN_GRACE_MS = Number(process.env.CARD_MCP_DRAIN_GRACE_MS || 5000)
|
|
41
|
+
let inFlight = 0
|
|
42
|
+
let stdinEnded = false
|
|
43
|
+
let drainTimer = null
|
|
44
|
+
|
|
45
|
+
function exitWhenDrained() {
|
|
46
|
+
if (stdinEnded && inFlight === 0) process.exit(0)
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
rl.on('line', async (line) => {
|
|
36
50
|
const t = line.trim()
|
|
37
51
|
if (!t) return
|
|
@@ -42,6 +56,11 @@ rl.on('line', async (line) => {
|
|
|
42
56
|
return
|
|
43
57
|
}
|
|
44
58
|
const { id, method, params } = msg
|
|
59
|
+
inFlight++
|
|
60
|
+
if (drainTimer) {
|
|
61
|
+
clearTimeout(drainTimer)
|
|
62
|
+
drainTimer = null
|
|
63
|
+
}
|
|
45
64
|
try {
|
|
46
65
|
if (method === 'initialize') {
|
|
47
66
|
send({
|
|
@@ -78,7 +97,17 @@ rl.on('line', async (line) => {
|
|
|
78
97
|
if (id != null) {
|
|
79
98
|
send({ jsonrpc: '2.0', id, error: { code: -32603, message: String((e && e.message) || e) } })
|
|
80
99
|
}
|
|
100
|
+
} finally {
|
|
101
|
+
inFlight--
|
|
102
|
+
exitWhenDrained()
|
|
81
103
|
}
|
|
82
104
|
})
|
|
83
105
|
|
|
84
|
-
process.stdin.on('end', () =>
|
|
106
|
+
process.stdin.on('end', () => {
|
|
107
|
+
stdinEnded = true
|
|
108
|
+
if (inFlight === 0) {
|
|
109
|
+
process.exit(0)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
drainTimer = setTimeout(() => process.exit(0), DRAIN_GRACE_MS)
|
|
113
|
+
})
|
|
@@ -23,7 +23,7 @@ dsl-card 项目的 AI 技能层。它是**编排层**:把卡片编辑任务拆
|
|
|
23
23
|
4. **读字段结构用 `card_node_models`**:不要去解析 raw DSL + component-meta。该工具返回每节点的可编辑字段树。
|
|
24
24
|
5. **render_status.ok ≠ 画对了**(rules.md §七):视觉正确性以 `card_screenshot` 为准。**但视觉验证只对 UI 变更做**(rules.md §七.1 铁律):改 `style.*` / 节点增删移动 / 静态文案 / 图片资源 / v-show 条件才截图;改绑定表达式取值逻辑 / `inputParams` / 事件代码 / `data.json` 属非 UI 变更,用单元断言收口(`evalExpr` 比对返回值、`new Function(code).call(假 this)` 跑 customAction 断言 `state`),不截图。
|
|
25
25
|
6. **改坏可回滚**:`card_write` 每次写卡前自动把当前版本存为历史快照(与编辑器改卡同语义);编辑失误时用 `card_rollback`(默认回滚到最新快照)撤销,用 `card_history` 挑选更早的回滚目标。
|
|
26
|
-
7. **dev server 前置**:`card_render_status` / `
|
|
26
|
+
7. **dev server 前置**:`card_render_status` / `card_node_models` / `card_import_meta` 必须 dev server 已启动,未起时返回 `dev-server-required`;`card_sync` / `card_save` / `card_slice_upload` 除 dev server 外还需已登录 FinMall。(本仓库 `npm run dev` 默认 5180,由 Vite 把 `/__*` 代理到 8787;`cardbuddy init` 出来的工程 `npm run dev` 直接是 8787。)`card_list` / `card_read` / `card_write` / `card_validate` / `card_history` / `card_rollback` 会自动降级为直接读盘/写盘/跑 Python(render_status 标 skipped);`card_screenshot` 降级到包内独立离线渲染器(`source:'standalone'`)。
|
|
27
27
|
8. **建卡先判输入形态**(Workflow D):收到设计稿输入先调 `card_route_input({path})`,按返回的 `kind` 分流,**不要凭感觉决定要不要跑上游**。判为 `meaxure-export`(Sketch MeaXure 标注导出)时必须先经 `sketch-spec-to-card` 前处理产出 `UI/<稿名>/` 要素包,**禁止直接读 `preview/` 整图建卡**;`preprocess.needed=false` 表示已前处理过,直接复用存量产物。`card_route_input` 纯离线,不需要 dev server。
|
|
28
28
|
|
|
29
29
|
## 1. 可用 MCP 工具(dsl-card 服务器,命名空间 `mcp__dsl-card__card_*`)
|
|
@@ -36,7 +36,7 @@ dsl-card 项目的 AI 技能层。它是**编排层**:把卡片编辑任务拆
|
|
|
36
36
|
| `card_write` | 写回 schema/data;默认自动 `verify`(validate + render_status)一并返回 | 直接写盘 + 本地 Python 硬校验 |
|
|
37
37
|
| `card_validate` | Python 硬契约校验 `{ok, errors, warnings}` | 直接跑 Python |
|
|
38
38
|
| `card_render_status` | SSR 自检:结构统计 + `bindingErrors` / `runtimeErrors` / `jsapiCalls` | 不支持 |
|
|
39
|
-
| `card_screenshot` | 无头截图(json `{base64}` / png 图片) |
|
|
39
|
+
| `card_screenshot` | 无头截图(json `{base64}` / png 图片) | 优先 dev server;未起时降级到包内独立离线渲染器(返回 `source:'standalone'`),渲染器也失败才报 `dev-server-required` |
|
|
40
40
|
| `card_history` | 编辑历史快照元数据(ts/时间/字节数/节点数),用于选择回滚目标 | 直接读 `.history/` |
|
|
41
41
|
| `card_rollback` | 回滚到指定快照(默认最新);回滚前自动把当前版本另存为安全快照,回滚本身可撤销;写回走 card_write 同一收口 | 直接读写盘 + Python 硬校验 |
|
|
42
42
|
| `card_sync` | 从 FinMall 远端拉最新卡写回 `src/cards/` | 不支持 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cardbuddy",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Local dev service for the CardBuddy card editor — decouples card CRUD (sync/save/validate/render-status/screenshot) from the static public editor, so the editor can be deployed to any static host while each developer runs a local service against their own card source.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|