cardbuddy 0.25.0 → 0.25.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.
@@ -358,16 +358,16 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
358
358
  `:``}[已挂载本地文件(仅本机路径,未上传;需要内容请用工具读取)]\n${e.join(`
359
359
  `)}`}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`?HU(e.toolName,e.result):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},startTurn(){let e=Date.now();this.turnId=``,this.lastSeq=0,this.turnStep=0,this.turnStage=null,this.turnToolLabel=``,this.turnStartedAt=e,this.lastByteAt=e,this.nowTs=e,this.stopTurnTimers(),this._liveTicker=setInterval(()=>{this.nowTs=Date.now(),this.$emit(`turn-progress`,{steps:this.turnStep,startedAt:this.turnStartedAt,ms:this.nowTs-this.turnStartedAt,stage:this.turnStage?`${this.turnStage.id} ${this.turnStage.label}`:this.turnToolLabel,stalled:this.stalled})},1e3),this._turnPersistTimer=setInterval(()=>this.persist(),8e3),this.persist(),this.$emit(`busy`,!0)},stopTurnTimers(){this._liveTicker&&clearInterval(this._liveTicker),this._liveTicker=0,this._turnPersistTimer&&clearInterval(this._turnPersistTimer),this._turnPersistTimer=0},onPagehide(){let e=this.cardId;if(!e||!this.messages.length)return;let t=this.fitArchive(this.serialize());this._dirtyFor=null,this._saveTimer&&=(clearTimeout(this._saveTimer),0),iz(e,t,{keepalive:!0}).catch(()=>{})},fmtClock:WL,pushEvent(e,t){let n=String(t&&t.message||``);n&&e&&(Array.isArray(e.events)||(e.events=[]),e.events.push({ts:Date.now(),level:t.level||`warn`,code:t.code||``,message:n}))},applyUsage(e,t){if(!e||!t)return;let n=e=>Number.isFinite(Number(e))?Number(e):0;e.usage={steps:n(t.steps),promptTokens:n(t.promptTokens),completionTokens:n(t.completionTokens),cachedTokens:n(t.cachedTokens),cacheMissTokens:n(t.cacheMissTokens),hitRate:n(t.hitRate),reported:t.reported!==!1}},loadCompact(){try{this.compactSteps=localStorage.getItem($U)===`1`}catch{}},setCompact(e){let t=!!e;if(this.compactSteps!==t){this.compactSteps=t;try{t?localStorage.setItem($U,`1`):localStorage.removeItem($U)}catch{}this.$nextTick(()=>this.scrollToBottom())}},toggleCompact(){this.setCompact(!this.compactSteps)},onLocateNode(e){e&&this.$emit(`locate-node`,String(e))},onRollbackTurn(e){if(this.busy)return;let t=this.cardId;t&&e&&this.$emit(`rollback-turn`,{cardId:t,ts:Number(e.ts)||0,msgId:e.id})},notify(e,t){this.notice={type:e,text:t},this._noticeTimer&&clearTimeout(this._noticeTimer),this._noticeTimer=setTimeout(()=>{this.notice=null},6e3)}}},sW={class:`aichat__head`},cW={class:`aichat__titles`},lW={class:`aichat__sub`},uW={class:`aichat__acts`},dW=[`title`,`aria-pressed`],fW={viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},pW=[`href`],mW=[`disabled`],hW={key:0,class:`aichat__banner`},gW={class:`aichat__scroll`},_W={key:0,class:`aichat__archnote`,title:`长对话记录超出落盘体积上限时,较早的工具参数与结果会被裁剪归档;正文、思考与过程事件都完整保留`},vW={key:1,class:`aichat__loading`},yW={key:2,class:`aichat__empty`},bW={class:`aichat__empty-title`},xW={class:`aichat__empty-desc`},SW={class:`aichat__suggests`},CW=[`title`,`data-suggest-id`,`onClick`],wW={viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},TW=[`href`],EW={class:`aichat__suggest-text`},DW={class:`aichat__suggest-label`},OW={class:`aichat__suggest-hint`},kW={key:0,class:`aichat__suggest-badge`,title:`该操作会改动卡片`},AW={class:`aichat__live-text`},jW={key:1,class:`aichat__quick`},MW=[`title`,`data-suggest-id`,`onClick`],NW={viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},PW=[`href`],FW={class:`ni-panel modal-card ccsw-dlg`,role:`dialog`,"aria-modal":`true`,"aria-label":`从 cc-switch 导入模型`},IW={class:`ni-head`},LW={class:`ni-body`},RW={class:`ni-scroll`},zW={key:0,class:`ccsw-dlg__msg`},BW={class:`ccsw-dlg__msg ccsw-dlg__msg--err`},VW={class:`ccsw-dlg__list`},HW=[`onClick`],UW={key:0,viewBox:`0 0 16 16`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.8`,"stroke-linecap":`round`,"stroke-linejoin":`round`},WW={class:`ccsw-dlg__info`},GW={class:`ccsw-dlg__name`},KW={key:0,class:`ccsw-dlg__badge ccsw-dlg__badge--cur`,title:`cc-switch 中当前生效`},qW={key:1,class:`ccsw-dlg__badge ccsw-dlg__badge--dup`,title:`模型库中已存在同名模型`},JW={class:`ccsw-dlg__meta`},YW={class:`ccsw-dlg__model`},XW={class:`ccsw-dlg__host`},ZW={class:`ni-foot`},QW={class:`ni-tip`},$W={class:`ni-foot-btns`},eG=[`disabled`];function tG(e,t,n,r,i,a){let o=R(`ChatMessage`),s=R(`ChatComposer`),c=R(`ModelList`),l=R(`AiSettings`),u=R(`ConfirmDialog`),d=R(`AiCommonSettings`);return V(),H(`aside`,{class:j([`aichat`,{"is-open":n.visible}]),style:A(a.panelStyle),role:`complementary`,"aria-label":`AI 开发助手`,onKeydown:t[21]||=jl((...e)=>a.onEscape&&a.onEscape(...e),[`esc`]),onClick:t[22]||=(...e)=>a.onImageClick&&a.onImageClick(...e)},[q(` 左缘拖拽把手:调宽(双击恢复默认 33%) `),W(`div`,{class:j([`aichat__grip`,{"is-dragging":i.resizing}]),title:`拖拽调整宽度(双击恢复默认)`,onPointerdown:t[0]||=J((...e)=>a.startResize&&a.startResize(...e),[`prevent`]),onDblclick:t[1]||=J((...e)=>a.resetWidth&&a.resetWidth(...e),[`prevent`])},null,34),q(` 头部:标题 + 当前卡片 / 模型 + 清空 / 设置 / 关闭 `),W(`header`,sW,[t[27]||=W(`span`,{class:`aichat__logo`},[W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-sparkles`})])],-1),W(`span`,cW,[t[23]||=W(`span`,{class:`aichat__title`},`AI 开发助手`,-1),W(`span`,lW,[n.cardId?(V(),H(B,{key:0},[K(M(n.cardName||n.cardId),1)],64)):(V(),H(B,{key:1},[K(`未选择卡片`)],64)),i.config.model?(V(),H(B,{key:2},[K(` · `+M(i.config.model),1)],64)):q(`v-if`,!0)])]),W(`span`,uW,[q(` 紧凑视图(面板级):长任务里读取类调用能刷几十行,把写入与门禁挤到折叠线以下 `),W(`button`,{type:`button`,class:j([`aichat__iconbtn`,{"is-on":i.compactSteps}]),title:i.compactSteps?`紧凑视图已开启:已折叠读取类调用,点击显示全部步骤`:`只看关键步骤:折叠读取类调用(写入 / 跑脚本 / 报错始终显示)`,"aria-pressed":i.compactSteps?`true`:`false`,onClick:t[2]||=(...e)=>a.toggleCompact&&a.toggleCompact(...e)},[(V(),H(`svg`,fW,[W(`use`,{href:i.compactSteps?`#icon-eye-off`:`#icon-eye`},null,8,pW)]))],10,dW),W(`button`,{type:`button`,class:`aichat__iconbtn`,title:`清空本卡片对话记录`,disabled:!i.messages.length||i.busy,onClick:t[3]||=(...e)=>a.askClear&&a.askClear(...e)},[...t[24]||=[W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-trash`})],-1)]],8,mW),W(`button`,{type:`button`,class:`aichat__iconbtn`,title:`模型库`,onClick:t[4]||=(...e)=>a.openModelList&&a.openModelList(...e)},[...t[25]||=[W(`svg`,{class:`is-solid-icon`,viewBox:`0 0 1024 1024`,"aria-hidden":`true`},[W(`use`,{href:`#icon-model`})],-1)]]),W(`button`,{type:`button`,class:`aichat__iconbtn`,title:`关闭(Esc)`,onClick:t[5]||=t=>e.$emit(`close`)},[...t[26]||=[W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-x`})],-1)]])])]),q(` 未配置模型:常驻提示条,一键打开设置 `),a.configReady?q(`v-if`,!0):(V(),H(`div`,hW,[t[28]||=W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-alert-triangle`})],-1),t[29]||=W(`span`,null,`尚未配置模型,无法对话。`,-1),W(`button`,{type:`button`,class:`aichat__banner-btn`,onClick:t[6]||=(...e)=>a.openModelList&&a.openModelList(...e)},`去设置`)])),q(` 消息区:滚动容器与悬浮「回到底部」共处一个定位上下文,按钮只贴消息区底缘,
360
360
  输入区拖高 / 反馈条出现都不会把它盖住或悬空 `),W(`div`,gW,[W(`div`,{ref:`listEl`,class:`aichat__list`,onScroll:t[10]||=(...e)=>a.onScroll&&a.onScroll(...e)},[q(` 体积治理痕迹:较早的工具结果被裁剪过就说清楚,别让人以为记录丢了 `),i.archiveTrimmed?(V(),H(`div`,_W,[...t[30]||=[W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-activity`})],-1),W(`span`,null,`较早的工具参数 / 结果已裁剪归档(对话正文与过程事件完整保留)`,-1)]])):q(`v-if`,!0),i.loadingHistory?(V(),H(`div`,vW,`正在加载对话记录…`)):i.messages.length?(V(),H(B,{key:3},[q(` compact = 面板级「只看关键步骤」;can-rollback 只对当前卡片、非流式中开放(切了卡再回滚会写错对象) `),(V(!0),H(B,null,z(i.messages,(e,r)=>(V(),U(o,{key:e.id,msg:e,streaming:i.busy&&r===i.messages.length-1,"can-regenerate":e.role===`assistant`&&r===i.messages.length-1,compact:i.compactSteps,"can-rollback":e.role===`assistant`&&!i.busy&&!!n.cardId,"now-ts":i.busy&&r===i.messages.length-1?i.nowTs:0,"pick-owner":a.pickOwnerRow,onRegenerate:a.regenerate,onApproval:a.onApproval,onContinue:a.onContinue,onRollback:a.onRollbackTurn,onOpenSettings:a.openModelList,onShowAllSteps:t[8]||=e=>a.setCompact(!1),onLocateNode:a.onLocateNode,onPickBoard:a.onPickBoard},null,8,[`msg`,`streaming`,`can-regenerate`,`compact`,`can-rollback`,`now-ts`,`pick-owner`,`onRegenerate`,`onApproval`,`onContinue`,`onRollback`,`onOpenSettings`,`onLocateNode`,`onPickBoard`]))),128)),q(` 面板级进度:步数 / 已用时长 / 建卡阶段 / 最近收到数据多久前。
361
- 长任务里「还活着吗」全靠这一行——旧版只有一句「生成中…」,网关静默与真在跑无从区分 `),i.busy?(V(),H(`div`,{key:0,class:j([`aichat__live`,{"is-stalled":a.stalled}]),role:`status`,"aria-live":`polite`},[t[33]||=W(`span`,{class:`aichat__live-dot`},null,-1),W(`span`,AW,M(a.liveText),1),a.stalled?(V(),H(`button`,{key:0,type:`button`,class:`aichat__live-act`,title:`连接可能已中断,终止本轮(已落盘的产物会保留)`,onClick:t[9]||=(...e)=>a.stop&&a.stop(...e)},` 终止 `)):q(`v-if`,!0)],2)):q(`v-if`,!0)],64)):(V(),H(`div`,yW,[t[32]||=W(`span`,{class:`aichat__empty-logo`},[W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-sparkles`})])],-1),W(`p`,bW,M(a.emptyTitle),1),W(`p`,xW,M(a.emptyDesc),1),W(`div`,SW,[(V(!0),H(B,null,z(a.suggestions,e=>(V(),H(`button`,{key:e.id,type:`button`,class:j([`aichat__suggest`,`is-${e.weight}`]),title:e.text,"data-suggest-id":e.id,onClick:t=>a.useSuggestion(e)},[(V(),H(`svg`,wW,[W(`use`,{href:e.icon},null,8,TW)])),W(`span`,EW,[W(`span`,DW,M(e.label),1),W(`span`,OW,M(e.hint),1)]),e.weight===`write`?(V(),H(`span`,kW,`改卡`)):q(`v-if`,!0)],10,CW))),128)),a.canShuffle?(V(),H(`button`,{key:0,type:`button`,class:`aichat__suggest-shuffle`,onClick:t[7]||=(...e)=>a.shuffleSuggestions&&a.shuffleSuggestions(...e)},[...t[31]||=[W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-refresh`})],-1),W(`span`,null,`换一批`,-1)]])):q(`v-if`,!0)])]))],544),q(` 回到底部:脱离底部吸附时出现 `),L(W(`button`,{type:`button`,class:`aichat__jump`,title:`回到底部`,onClick:t[11]||=e=>a.scrollToBottom(!0)},[...t[34]||=[W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-arrow-down`})],-1)]],512),[[Nc,!i.pinned&&i.messages.length]])]),q(` 常驻「常用」指令:有对话后空态建议区不再出现,这里保留一排轻量入口,随上下文同源 `),a.quickChips.length&&i.messages.length&&!i.busy?(V(),H(`div`,jW,[(V(!0),H(B,null,z(a.quickChips,e=>(V(),H(`button`,{key:e.id,type:`button`,class:j([`aichat__chip`,`is-${e.weight}`]),title:e.text,"data-suggest-id":e.id,onClick:t=>a.useSuggestion(e)},[(V(),H(`svg`,NW,[W(`use`,{href:e.icon},null,8,PW)])),W(`span`,null,M(e.label),1)],10,MW))),128))])):q(`v-if`,!0),q(` 操作反馈(附件解析失败 / 历史保存失败等) `),i.notice?(V(),H(`div`,{key:2,class:j([`aichat__notice`,`aichat__notice--${i.notice.type}`])},[W(`span`,null,M(i.notice.text),1),W(`button`,{type:`button`,class:`aichat__notice-x`,title:`关闭`,onClick:t[12]||=e=>i.notice=null},[...t[35]||=[W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-x`})],-1)]])],2)):q(`v-if`,!0),G(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`]),q(` 一级面板:模型库。从齿轮 / 横幅 / 消息内「去设置」进入 `),i.modelListOpen?(V(),U(c,{key:3,models:i.models,"active-id":i.activeId,"turn-running":i.turnRunning,onClose:a.closeModelList,onAdd:a.onModelAdd,onEdit:a.onModelEdit,onDelete:a.onModelDelete,onActivate:a.onModelActivate,onImportCcswitch:a.onImportCcSwitch,onOpenCommon:a.openCommon},null,8,[`models`,`active-id`,`turn-running`,`onClose`,`onAdd`,`onEdit`,`onDelete`,`onActivate`,`onImportCcswitch`,`onOpenCommon`])):q(`v-if`,!0),q(` 新增 / 编辑模型:复用 AiSettings(modelId 区分,initialModel 预填) `),i.modelEditOpen?(V(),U(l,{key:4,config:i.config,"model-id":i.editModelId,"initial-model":i.editInitialModel,onClose:a.onModelEditClose,onSaved:a.onModelSaved},null,8,[`config`,`model-id`,`initial-model`,`onClose`,`onSaved`])):q(`v-if`,!0),q(` 删除模型确认:二次确认后才真正落盘删除 `),G(u,{visible:!!i.confirmDeleteModel,"close-on-mask":!1,title:`删除模型`,message:a.deleteModelMessage,"confirm-text":`删除`,severity:`danger`,onConfirm:a.doDeleteModel,onCancel:t[13]||=e=>i.confirmDeleteModel=null},null,8,[`visible`,`message`,`onConfirm`]),q(` cc-switch 导入弹窗:列出 cc-switch 的 Codex 类供应商,勾选后批量导入模型库(按 model 去重) `),G(hc,{name:`modal`},{default:I(()=>[i.ccSwitchOpen?(V(),H(`div`,{key:0,class:`ni-overlay`,onClick:t[17]||=J(e=>i.ccSwitchOpen=!1,[`self`])},[W(`div`,FW,[W(`div`,IW,[t[36]||=W(`span`,{class:`ni-title`},`从 cc-switch 导入`,-1),W(`button`,{class:`ni-close`,type:`button`,title:`关闭`,onClick:t[14]||=e=>i.ccSwitchOpen=!1})]),W(`div`,LW,[W(`div`,RW,[q(` 加载中 `),i.ccSwitchLoading?(V(),H(`p`,zW,`正在读取 cc-switch 供应商…`)):i.ccSwitchReason?(V(),H(B,{key:1},[q(` 无可导入项 `),W(`p`,BW,M(i.ccSwitchReason),1)],2112)):(V(),H(B,{key:2},[q(` 供应商列表 `),W(`ul`,VW,[(V(!0),H(B,null,z(i.ccSwitchProviders,e=>(V(),H(`li`,{key:e.id,class:j([`ccsw-dlg__item`,{"is-active":i.ccSwitchSelected[e.id],"is-dup":e.duplicate}]),onClick:t=>a.toggleCcSwitchItem(e)},[W(`span`,{class:j([`ccsw-dlg__check`,{"is-on":i.ccSwitchSelected[e.id]}])},[i.ccSwitchSelected[e.id]?(V(),H(`svg`,UW,[...t[37]||=[W(`use`,{href:`#icon-check`},null,-1)]])):q(`v-if`,!0)],2),W(`span`,WW,[W(`span`,GW,[K(M(e.name||e.model)+` `,1),e.isCurrent?(V(),H(`span`,KW,`当前`)):q(`v-if`,!0),e.duplicate?(V(),H(`span`,qW,`已存在`)):q(`v-if`,!0)]),W(`span`,JW,[W(`span`,YW,M(e.model),1),t[38]||=W(`span`,{class:`ccsw-dlg__dot`},`·`,-1),W(`span`,XW,M(e.baseUrl),1)])])],10,HW))),128))])],2112))])]),W(`div`,ZW,[W(`span`,QW,M(a.ccSwitchDupCount>0?`已有 ${a.ccSwitchDupCount} 个重复模型被自动跳过`:`勾选要导入的供应商,批量加入模型库`),1),W(`div`,$W,[W(`button`,{class:`ni-btn ni-btn--ghost`,type:`button`,onClick:t[15]||=e=>i.ccSwitchOpen=!1},`取消`),W(`button`,{class:`ni-btn`,type:`button`,disabled:i.ccSwitchLoading||!i.ccSwitchProviders.length||a.ccSwitchCheckedCount===0,onClick:t[16]||=(...e)=>a.confirmImportCcSwitch&&a.confirmImportCcSwitch(...e)},M(a.ccSwitchCheckedCount>0?`导入选中 ${a.ccSwitchCheckedCount} 项`:`导入`),9,eG)])])])])):q(`v-if`,!0)]),_:1}),q(` 通用设置:所有模型共享的执行策略 / 环境参数 `),i.commonOpen?(V(),U(d,{key:5,onClose:t[18]||=e=>i.commonOpen=!1,onSaved:a.onCommonSaved},null,8,[`onSaved`])):q(`v-if`,!0),G(u,{visible:i.confirmClear,"close-on-mask":!1,title:`清空对话记录`,message:`将删除卡片「${n.cardName||n.cardId||`当前卡片`}」的全部对话记录,此操作不可撤销。`,"confirm-text":`清空`,severity:`danger`,onConfirm:a.doClear,onCancel:t[19]||=e=>i.confirmClear=!1},null,8,[`visible`,`message`,`onConfirm`]),q(` 终止确认:生成中按 Esc 会走到这里。几十分钟的建卡被一次误按抹掉代价太大,故加一道确认 `),G(u,{visible:i.confirmStop,"close-on-mask":!1,title:`终止本次生成`,message:`已执行的写入与落盘产物都会保留,上下文也留在对话里,之后回复「继续」可从中断处接着做。确定现在终止?`,"confirm-text":`终止生成`,severity:`danger`,onConfirm:a.doStop,onCancel:t[20]||=e=>i.confirmStop=!1},null,8,[`visible`,`onConfirm`])],38)}var nG=Z(oW,[[`render`,tG],[`__file`,`/Users/ynet/dsl-card/card-editor/components/ChatPanel.vue`]]),rG=`/assets/site-icon-CFMif8z4.png`,iG={includeMocks:!0};function aG(e){return e?JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t)):null}function oG(e){let t=aG(e);if(t&&t.dictionary&&Array.isArray(t.dictionary.inputParams)){let e=hv(t);for(let n of t.dictionary.inputParams)n&&typeof n==`object`&&!gv(e,n.key)&&(n.defaultValue=void 0)}return t}function sG(e,t){return!e||!t?e===t:JSON.stringify(oG(e))===JSON.stringify(oG(t))}function cG(e,t){return JSON.stringify(e)===JSON.stringify(t)}function lG(e,t){return!t||!e?!1:!sG(e,t)}function uG(e,t,n){return!t||!e?!1:!cG(e,n??mv(t,iG))}function dG(e,t,n,r){return!t||!e?!1:lG(e,t)||uG(n,t,r)}var fG={includeMocks:!0},pG=2500,mG=8e3,hG=`cardbuddy:remote-cards-cache:v1`,gG=3e5;function _G(){try{let e=JSON.parse(localStorage.getItem(hG)||`null`);if(e&&e.v===1&&Array.isArray(e.cards))return e}catch{}return null}function vG(e){try{localStorage.setItem(hG,JSON.stringify({v:1,at:Date.now(),cards:e}))}catch{}}var yG=new pu,bG=vu({},yG);typeof window<`u`&&(window.__rt=bG);var xG=188,SG=320,CG=200,wG=160,TG={name:`App`,components:{RenderTree:vp,DebugPanel:Gx,ApiCallPanel:pS,CardSelect:MS,AlertCardLayer:US,DeviceOverlay:ND,PickerOverlay:UD,DatePickerOverlay:eO,KeyboardOverlay:oO,LoadingExOverlay:fO,SelectionOverlay:gO,HoverGuides:SO,RemotePreview:uk,AppToast:_k,ThemeSettings:Rk,ImageLightbox:iA,ConfirmDialog:Ap,CardGallery:LA,RemoteAllCards:vj,RemoteCardFormDialog:$j,SyncProgressModal:uM,ExportDirDialog:EM,ExportProgressModal:LM,NewCardDialog:rN,ComponentPalette:vN,Switch:SN,ChatPanel:nG},provide(){return{adapter:yG,cardRuntime:bG,customComponents:{}}},data(){return{adapter:yG,rt:bG,logoUrl:rG,appVersion:`0.83.1`,cardList:[],cardCount:0,galleryOpen:!1,remoteAllOpen:!1,remoteCards:[],remoteCardsLoading:!1,remoteCardsRefreshing:!1,remoteCardsError:``,remoteCardFormOpen:!1,remoteCardFormMode:`create`,remoteCardFormCard:null,remoteCardDeletingId:``,syncJobId:``,syncProgressOpen:!1,syncProgress:{done:0,total:0,current:``},syncPollTimer:null,exportDirDialog:{visible:!1,ids:[]},compilingCards:!1,exportJobId:``,exportProgressOpen:!1,exportProgress:{done:0,total:0,current:``},exportPollTimer:null,syncPendingCodes:void 0,cardDialog:{visible:!1,mode:`create`,cardId:``,cardDesc:``},selectedId:``,externalReloadNote:``,externalReloadTimer:null,selfSaveId:``,selfSaveAt:0,preview:null,chromeOff:!1,theme:`dark`,accent:bk,cardReady:!1,reloadToken:0,moveCollapseToken:0,refreshing:!1,loadingCard:!1,currentSchema:null,currentCard:null,loadedCardId:``,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,savedCard:!1,savingCard:!1,saveTimer:null,coverBusy:!1,savingRemote:!1,restoringHistory:!1,savedRemote:!1,verifiedRemote:null,saveRemoteTimer:null,syncingRemote:!1,syncedRemote:!1,remotePreviewOpen:!1,syncTarget:null,syncTimer:null,uploadingSlices:!1,publishing:!1,metaRefreshToken:0,historyRefreshToken:0,loginModal:!1,loginName:``,loginPassword:``,loginSubmitting:!1,loginError:``,credentialsReady:!1,loginPendingAction:``,remoteToggleFn:null,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}})(),chatBusy:!1,chatProgress:null,fab:{x:null,y:null,dragging:!1},fabDrag:null,fabSuppressClick:!1,devServiceForm:Kl(),apiBaseResolved:Jl(),devSvcLive:`unknown`,devSvcDown:!1,_unsubCardsChanged:null}},computed:{fabTitle(){if(this.aiChatOpen)return`AI 开发助手(已打开)`;if(!this.chatBusy)return`AI 开发助手`;let e=this.chatProgress,t=e&&e.startedAt?`${GL(e.startedAt)} 开始 · `:``;return`AI 开发助手正在生成(${e?`${t}第 ${e.steps} 步 · 已用 ${WL(e.ms)}${e.stage?` · ${e.stage}`:``}${e.stalled?` · 疑似断流`:``}`:``}),点击展开查看`},fabStyle(){return this.fab.x==null||this.fab.y==null?{}:{right:this.fab.x+`px`,bottom:this.fab.y+`px`,left:`auto`,top:`auto`,transition:this.fab.dragging?`none`:``}},currentBg(){return this.bgOptions.find(e=>e.key===this.canvasBg)||this.bgOptions[0]},cardIds(){return this.cardList.map(e=>e&&e.id).filter(Boolean)},remoteImportedIds(){return this.cardList.filter(e=>e&&e.imported).map(e=>e.id)},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!!td()},hasCardSource(){return!!(this.cardImportMeta&&this.cardImportMeta.source)},remoteBusy(){return!!(this.savingRemote||this.syncingRemote||this.uploadingSlices||this.publishing)},remoteBusyReason(){return this.savingRemote?`正在推送至远端…`:this.syncingRemote?`正在拉取远端卡片…`:this.uploadingSlices?`正在上传切图…`:this.publishing?`正在发布版本…`:``},historyDirty(){return dG(this.currentSchema,this.localSchema,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||xG;return Math.max(e,Math.min(t,n))},viewportWidth(){return this.stageWidth,typeof window<`u`?window.innerWidth:1280},debugSoftWidth(){return Math.max(CG,Math.floor(this.viewportWidth/3))},previewBounds(){let e=this.stageWidth||0,t=Math.floor(e/2),n=e-SG-this.debugSoftWidth;return{min:xG,max:Math.max(xG,Math.min(t,n))}},debugMinWidth(){let e=this.colsWidth||Math.max(0,(this.stageWidth||0)-this.previewWidth);return Math.max(CG,Math.min(this.debugSoftWidth,Math.floor(e)-SG))},apiPanelBounds(){let e=this.colsWidth||Math.max(0,(this.stageWidth||0)-this.previewWidth),t=Math.max(wG,Math.floor(e)-this.debugMinWidth);return{min:Math.min(SG,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||``},cardEmpty(){let e=this.currentSchema;if(!e||!e.view)return!0;let t=e.view.components;return!Array.isArray(t)||t.length===0},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=``,yO())},credentialsReady(e){!e&&this.activeTab===`history`&&(this.activeTab=`vars`)},aiChatOpen(e){try{sessionStorage.setItem(`cardbuddy:ai-chat-open`,e?`1`:`0`)}catch{}},theme(e){document.documentElement.dataset.theme=e;try{localStorage.setItem(`cardbuddy:theme`,e)}catch{}},accent(e){Ck(e),wk(e)},apiPanelOpen(e){try{localStorage.setItem(`cardbuddy:api-panel-open`,e?`1`:`0`)}catch{}},remoteBusy(e){e&&(this.devServiceOpen=!1)},chatBusy(e,t){if(t&&!e&&this.selectedId){if(this.historyDirty){let e=this.currentSchema&&this.currentSchema.name||this.selectedId;this.showExternalReloadNote(`AI 已更新磁盘上的卡片「${e}」,当前有未保存编辑,未自动重载(点顶栏刷新可载入最新版)`)}else this.loadCard(this.selectedId)}}},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),this.syncPollTimer&&clearTimeout(this.syncPollTimer),this.exportPollTimer&&clearTimeout(this.exportPollTimer),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.loadCanvasBg(),this.loadApiPanelWidth(),this._stageObserver=new ResizeObserver(e=>{for(let t of e)t.target===this.$refs.colsEl?this.colsWidth=Math.round(t.contentRect.width):(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),this.fabInitPosition(),window.addEventListener(`resize`,this.onFabResize),this.checkCredentials()},beforeDestroy(){window.removeEventListener(`resize`,this.onFabResize)},async created(){wD(bG);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,this.accent=Sk(),Ck(this.accent);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:{fabBounds(){let e=this.$refs.aiFab,t=e?e.offsetWidth:56,n=e?e.offsetHeight:56,r=document.documentElement.clientWidth,i=document.documentElement.clientHeight;return{minX:0,minY:0,maxX:Math.max(0,r-t),maxY:Math.max(0,i-n)}},clampFab(e,t){let n=this.fabBounds();return[Math.min(Math.max(e,n.minX),n.maxX),Math.min(Math.max(t,n.minY),n.maxY)]},fabInitPosition(){let e=this.$refs.aiFab;if(!e)return;let t=this.loadFabPos(),n=document.documentElement.clientWidth,r=document.documentElement.clientHeight,i,a;if(t)[i,a]=this.clampFab(t.x,t.y);else{let t=e.getBoundingClientRect();[i,a]=this.clampFab(n-t.right,r-t.bottom)}this.fab.x=i,this.fab.y=a},loadFabPos(){try{let e=localStorage.getItem(`cardbuddy:ai-fab-pos`);if(!e)return null;let t=JSON.parse(e);if(typeof t.x==`number`&&typeof t.y==`number`&&isFinite(t.x)&&isFinite(t.y))return t}catch{}return null},saveFabPos(){try{localStorage.setItem(`cardbuddy:ai-fab-pos`,JSON.stringify({x:this.fab.x,y:this.fab.y}))}catch{}},onFabPointerDown(e){if(!this.aiChatOpen){this.fabSuppressClick=!1,this.fab.x??this.fabInitPosition(),this.fabDrag={pointerId:e.pointerId,startX:e.clientX,startY:e.clientY,originX:this.fab.x,originY:this.fab.y,moved:!1};try{e.currentTarget.setPointerCapture(e.pointerId)}catch{}}},onFabPointerMove(e){let t=this.fabDrag;if(!t||t.pointerId!==e.pointerId)return;let n=e.clientX-t.startX,r=e.clientY-t.startY;if(!t.moved){if(Math.hypot(n,r)<4)return;t.moved=!0,this.fab.dragging=!0}let[i,a]=this.clampFab(t.originX-n,t.originY-r);this.fab.x=i,this.fab.y=a},onFabPointerUp(e){let t=this.fabDrag;if(!t||t.pointerId!==e.pointerId)this.fab.dragging=!1;else{try{e.currentTarget.releasePointerCapture(e.pointerId)}catch{}t.moved&&(this.fabSuppressClick=!0,this.saveFabPos()),this.fab.dragging=!1,this.fabDrag=null}},onFabClick(){this.fabSuppressClick?this.fabSuppressClick=!1:this.aiChatOpen=!this.aiChatOpen},onFabResize(){if(this.fab.x==null)return;let[e,t]=this.clampFab(this.fab.x,this.fab.y);this.fab.x=e,this.fab.y=t},setupCardsChanged(){if(this._unsubCardsChanged){try{this._unsubCardsChanged()}catch{}this._unsubCardsChanged=null}this.devSvcLive=`connecting`,this._unsubCardsChanged=eu(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(){ql(this.devServiceForm),this.apiBaseResolved=Jl(),this.setupCardsChanged(),this.devServiceOpen=!1,Q(`本地开发服务配置已保存`,`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()})},loadCanvasBg(){try{let e=localStorage.getItem(`cardbuddy:canvas-bg`);e!=null&&this.bgOptions.some(t=>t.key===e)&&(this.canvasBg=e)}catch{}},saveCanvasBg(){try{localStorage.setItem(`cardbuddy:canvas-bg`,this.canvasBg)}catch{}},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,this.saveCanvasBg()},onSelect(e){e!==this.selectedId&&(this.remoteBusy?Q(`远端操作进行中(${this.remoteBusyReason}),完成前不能切换卡片`,`err`,3200):this.chatBusy?Q(`AI 正在生成「${this.selectedId}」:先点 ■ 停止,或等本轮结束后再切卡`,`err`,3600):(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.cardDialog.mode=`create`,this.cardDialog.cardId=``,this.cardDialog.cardDesc=``,this.cardDialog.visible=!0,this.fetchCardList()},openRenameCard(){this.selectedId&&(this.cardDialog.mode=`rename`,this.cardDialog.cardId=this.selectedId,this.cardDialog.cardDesc=this.currentSchema&&this.currentSchema.desc||``,this.cardDialog.visible=!0,this.fetchCardList())},async onCardRenamed({oldId:e,id:t}){if(this.cardDialog.visible=!1,Q(t===e?`已更新卡片描述`:`已重命名卡片 ${e} → ${t}`,`ok`),t!==e)return this.applyCardRename(e,t);await this.fetchCardList(),await this.loadCard(t)},async applyCardRename(e,t){if(!e||!t||e===t)return;let n=`${e}->${t}`;if(this._renameAppliedKey===n)return;this._renameAppliedKey=n,setTimeout(()=>{this._renameAppliedKey===n&&(this._renameAppliedKey=``)},1e3);let r=this.selectedId===e;this.$refs.chatPanel?.migrateCard(e,t),r&&this.selectCard(t),this.selfSaveId=t,this.selfSaveAt=Date.now(),await this.fetchCardList(),r&&(this.historyDirty&&!this.confirm.show?this.requestConfirm({title:`卡片已重命名`,message:`卡片「${e}」已重命名为「${t}」,而编辑器内还有未保存的改动。\n重新载入会放弃这些未保存改动并显示磁盘版本;保留改动则继续用当前编辑器内容(后续保存会写到新目录 ${t})。`,confirmText:`重新载入`,cancelText:`保留我的改动`,onConfirm:()=>this.loadCard(t)}):await this.loadCard(t))},closeGallery(){this.galleryOpen=!1},onGalleryExport(e){!this.exportProgressOpen&&e&&e.length&&(this.exportDirDialog={visible:!0,ids:e})},async onGalleryCompile(e){if(!this.compilingCards&&e&&e.length){this.compilingCards=!0;try{let t=await Y(`/__compile-cards`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cardIds:e})}),n=await t.json().catch(()=>({}));if(!t.ok||!n.ok)throw Error(n.message||n.error||`HTTP `+t.status);let r=(n.cards||[]).filter(e=>e.ok),i=(n.cards||[]).filter(e=>!e.ok),a=r.reduce((e,t)=>e+(t.warnings?t.warnings.length:0),0);console.info(`[compile] 产物目录`,n.outDir);for(let e of r){console.info(`[compile] ${e.id} 模板 ${e.templates} 个 / 节点 ${e.nodes} / 客户端 ${e.clientBytes}B / H5 ${e.h5Bytes}B`);for(let t of e.warnings||[])console.warn(`[compile] ${e.id} ${t}`)}for(let e of i)console.error(`[compile] ${e.id} 失败:${e.error}`);if(i.length)Q(`已编译 ${r.length} 张,${i.length} 张失败(原因见控制台)`,`err`,4200);else{let e=a?`(${a} 条告警见控制台)`:``;Q(`已编译 ${r.length} 张卡片,客户端模板与 H5 脚本已写入 flame-build 目录${e}`,`ok`,3600)}this.$refs.cardGallery?.setChecked(i.map(e=>e.id))}catch(e){Q(`编译失败:`+(e&&e.message?e.message:`无法连接本地开发服务`),`err`,4200)}finally{this.compilingCards=!1}}},async onExportDirConfirm(e){this.exportDirDialog.visible=!1;let t=this.exportDirDialog.ids;try{let n=await Y(`/__export-cards`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cardIds:t,targetDir:e})}),r=await n.json().catch(()=>({}));if(!n.ok||!r.ok)throw Error(r.message||r.error||`HTTP `+n.status);this.exportJobId=r.jobId,this.exportProgress={done:0,total:Number(r.total)||0,current:``},this.exportProgressOpen=!0,this.pollExportProgress()}catch(e){this.exportDirDialog.ids=[],Q(`启动导出失败:`+(e&&e.message),`err`)}},pollExportProgress(){this.exportPollTimer&&clearTimeout(this.exportPollTimer);let e=async()=>{if(this.exportJobId&&this.exportProgressOpen){try{let e=await Y(`/__export/progress?jobId=`+encodeURIComponent(this.exportJobId)),t=await e.json().catch(()=>({}));if(!e.ok||!t.ok)throw Error(t.message||`进度查询失败`);if(this.exportProgress={done:Number(t.done)||0,total:Number(t.total)||0,current:t.currentTitle||t.currentId||``},t.finished){this.onExportJobFinished(t.result||{});return}}catch(e){console.error(`[导出进度查询失败]`,e)}this.exportPollTimer=setTimeout(e,600)}};e()},stopExportPolling(){this.exportPollTimer&&=(clearTimeout(this.exportPollTimer),null)},onExportJobFinished(e){this.stopExportPolling(),this.exportProgressOpen=!1,this.exportJobId=``,this.exportDirDialog.ids=[];let t=e.failed||[],n=e.warnings||[];if(t.length&&console.warn(`[导出失败明细]`,t),n.length&&console.warn(`[导出告警明细]`,n),!e.cancelled&&e.success&&this.$refs.cardGallery?.setChecked(t.map(e=>e&&e.id).filter(Boolean)),e.cancelled){let t=e.exported||0;Q(t?`导出已取消(已完成 ${t} 张,未生成 zip)`:`导出已取消`,`ok`)}else if(e.success){let r=e.exported||0,i=t.length,a=e.folderName?`${e.folderName}.zip`:`zip`,o=e.revealed?`,已打开所在目录`:``,s=i?`(${t[0].id||`?`}:${String(t[0].error||``).slice(0,60)})`:``,c=n.length?`,${n.length} 条告警见控制台`:``;Q(i?`导出完成:成功 ${r} 张,失败 ${i} 张${s} → ${a}${o}${c}`:`导出成功:${a}(${r} 张卡片)${o}${c}`,i?`warn`:`ok`)}else Q(`导出失败:`+(e.error||`未知错误`),`err`)},cancelExportJob(){this.exportJobId&&Y(`/__export/cancel`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({jobId:this.exportJobId})}).catch(()=>{})},async openCardFolder(){if(this.selectedId)try{let e=await Y(`/__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?Q(t.error||`打开文件夹失败`,`err`):Q(`已打开卡片文件夹`,`ok`)}catch{Q(`无法连接本地开发服务,请确认已启动`,`err`)}},async refreshCard(){if(this.selectedId&&!this.refreshing){this.refreshing=!0;try{await this.loadCard(this.selectedId)}finally{this.refreshing=!1}}},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([Y(`/__validate/${encodeURIComponent(e)}`),Y(`/__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 Y(`/__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),e!==this.loadedCardId&&(this.activeTab=`vars`),this.loadedCardId=e,t.clearAll?this.rt.clearErrors():this.rt.clearBindingErrors(),this.rt.clearApiCalls(),t.keepScope||this.rt.setRootScope(mv(r.schema,fG)),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(mv(r.schema,fG)),this.preview=qu(r.schema),typeof window<`u`&&(window.__cardModel=$u(r.schema)),this.cardReady=!1,this.$nextTick(()=>{this.cardReady=!0}),this.syncDataToSchema(),this.selectedNodeId=``,this.baseline={schema:this.cloneSchema(this.currentSchema)},this.reloadToken+=1,RS(),TD(),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},cloneData(e){return e==null?e:JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t))},applySchemaSnapshot(e){if(!this.selectedId||!e)return;let t=e.schema||e,n=e.data==null?mv(t,fG):e.data;this.currentSchema=this.cloneSchema(t),this.rt.setRootScope(this.cloneData(n)),this.preview=qu(this.currentSchema),typeof window<`u`&&(window.__cardModel=$u(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``,this.reloadToken+=1,this.refreshValidateBadge()},commitHistory(){if(!this.currentSchema||!this.baseline||!dG(this.currentSchema,this.baseline.schema,this.rt.rootScope))return;let e={schema:this.baseline.schema};this.past.push(e),this.past.length>30&&this.past.shift(),this.future=[],this.baseline={schema:this.cloneSchema(this.currentSchema)},this.persistSnapshot(e)},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;Y(`/__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 Y(`/__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?mv(e.schema,fG):e.data}))}catch(e){console.warn(`[加载编辑历史失败]`,e)}},undo(){if(!this.canUndo||this.remoteBusy)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||this.remoteBusy)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.remoteBusy||this.historyDirty&&this.preview&&this.selectedId&&await this.saveCard()&&(this.credentialsReady&&this.hasCardSource?(Q(`已保存到本地,正在同步至远端…`,`ok`),Promise.resolve(this.saveToRemote({fromAutoSave:!0})).then(e=>{e&&(e.ok?e.verified===!1?Q(`已保存到本地,远端已提交但读回校验未通过`,`warn`,4200):Q(`已保存到本地并同步至远端`,`ok`):Q(`已保存到本地,但同步至远端失败`+(e.message?`:`+e.message:``),`warn`,4200))}).catch(()=>{})):Q(`已将该版本保存到本地`,`ok`))},async generateCardCover(){if(!this.preview||!this.selectedId||this.coverBusy||this.remoteBusy)return;if(this.historyDirty&&!await this.saveCard()){Q(`本地保存失败,已取消生成预览图`,`err`);return}let e=null;try{let t=await Y(`/__card-image/`+encodeURIComponent(this.selectedId)+`?probe=1`),n=await t.json().catch(()=>({}));t.ok&&n.exists&&(e=n.file)}catch{}e?this.requestConfirm({title:`重新生成截图`,message:`该卡片已有截图(${e}),继续将覆盖它。确定重新生成吗?`,confirmText:`重新生成`,severity:`warn`,onConfirm:()=>this.doGenerateCover()}):this.doGenerateCover()},async doGenerateCover(){this.coverBusy=!0;try{let e=await Y(`/__card-image/`+encodeURIComponent(this.selectedId),{method:`POST`}),t=await e.json().catch(()=>({}));if(!e.ok)throw Error(t.message||`HTTP `+e.status);Q(`截图已生成(${t.width}×${t.height})→ ${t.file}`,`ok`,3e3)}catch(e){Q(`生成截图失败:`+String(e&&e.message||e),`err`,3600)}finally{this.coverBusy=!1}},onSchemaChanged(){this.selectedId&&this.currentSchema&&(this.reloadToken+=1,this.preview=qu(this.currentSchema),typeof window<`u`&&(window.__cardModel=$u(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``)},onSchemaCommit(){this.selectedId&&this.currentSchema&&(this.reloadToken+=1,this.preview=qu(this.currentSchema),typeof window<`u`&&(window.__cardModel=$u(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=hv(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&&(rd(n.node,this.selectedId),Q(`已复制节点(含子节点)`,`ok`))},pasteNode(){let e=td();if(!e||!this.currentSchema)return;let t=id(e),n=[...new Set(t.filter(e=>!ju[e]))];if(n.length){Q(`粘贴失败:含未知组件类型 `+n.join(`、`),`err`);return}let r=JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t));ad(r);let i=this.selectedNodeId&&this.selectedNodeId!==`_root`?this.findPreviewNodeById(this.preview.view,this.selectedNodeId):null,a=`root`;i&&(a=Wu(i.className)?`into`:`after`),this.insertClone(r,i,a);let o=nd();o&&o!==this.selectedId?Q(`已从卡片「${o}」跨卡片粘贴到「${this.selectedId}」`,`ok`):Q(`已粘贴节点(含子节点)`,`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));ad(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=Gu(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=qu(t),typeof window<`u`&&(window.__cardModel=$u(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=qu(this.currentSchema),typeof window<`u`&&(window.__cardModel=$u(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``,yO(),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()}else if(n&&(r===`v`||r===`V`)){if(t||this.hasTextSelection()||!this.hasNodeSelection()||!td())return;this.pasteNode(),e.preventDefault()}else if(n&&(r===`d`||r===`D`)){if(t||this.hasTextSelection()||!this.hasNodeSelection())return;this.duplicateNode(),e.preventDefault()}else if(n&&(r===`z`||r===`Z`)){if(t)return;e.preventDefault(),e.shiftKey?this.redo():this.undo()}else if(n&&(r===`y`||r===`Y`)){if(t)return;e.preventDefault(),this.redo()}else if(r===`Delete`||r===`Backspace`){if(t||!this.selectedNodeId||this.selectedNodeId===`_root`)return;e.preventDefault(),this.deleteSelectedNode()}else r===`Escape`&&this.selectedNodeId&&(this.selectedNodeId=``,e.preventDefault())},onHoverNode(e){if(!e){yO();return}if(e.leave){yO(e.id);return}let t=typeof document<`u`?document.querySelector(`.phone__screen`):null;if(!t){yO(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?vO(r,e.id):yO(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)},onAiLocateNode(e){let t=e?this.findPreviewNodeByCid(e):null;t?(this.activeTab=`structure`,this.selectedNodeId=t.id):Q(`没找到组件 ${e||``}:卡片可能已被改动,重新校验一次即可`,`err`)},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(!Wu(i.className))return!1;let e=Gu(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=qu(this.currentSchema),typeof window<`u`&&(window.__cardModel=$u(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=Ku(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(Wu(n.className)){let t=Gu(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=qu(i),typeof window<`u`&&(window.__cardModel=$u(i)),this.syncDataToSchema();let s=this.findViewIdByComponentId(this.preview.view,a.componentId);this.selectedNodeId=s||``,this.scheduleCommit()},async runSync(e){if(this.syncingRemote||e&&!this.selectedId)return;this.syncingRemote=!0,this.syncedRemote=!1,this.syncTarget=e?`selected`:`full`;let t=null,n=()=>{t&&=(clearTimeout(t),null)};try{let n=await Y(`/__sync`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e?{cardCode:e}:{})});if(!n.ok){let e=await n.json().catch(()=>({}));throw Error(e.message||e.error||`HTTP `+n.status)}let r=await n.json().catch(()=>({}));if(!r.ok||!r.jobId)throw Error(r.message||r.error||`同步任务启动失败`);let i=await new Promise((e,n)=>{let i=0,a=async()=>{try{let t=await Y(`/__sync/progress?jobId=`+encodeURIComponent(r.jobId)),n=await t.json().catch(()=>({}));if(!t.ok||!n.ok)throw Error(n.message||`进度查询失败`);if(n.finished){e(n.result||{});return}}catch(e){console.error(`[同步进度查询失败]`,e)}++i>=200?n(Error(`同步超时(进度查询无响应)`)):t=setTimeout(a,600)};a()});if(i.needCredentials){this.openLoginModal(`sync`);return}if(i.cancelled){Q(`同步已取消`,`ok`);return}if(!i.success){let e=(i.failed||[]).map(e=>`${e.id}: ${e.error}`).join(`; `);throw Error(e||i.error||`同步失败`)}this.syncedRemote=!0,this.credentialsReady=!0,this.syncTimer&&clearTimeout(this.syncTimer),this.syncTimer=setTimeout(()=>{this.syncedRemote=!1,this.syncTarget=null},1800),await this.onRefresh(),Q(this.syncTarget===`selected`?`已拉取最新卡到本地`:`拉取全部卡完成`,`ok`),this.persistRemoteVersion(this.selectedId,null)}catch(e){console.error(`[同步远程数据失败]`,e),Q(`同步失败:`+(e&&e.message),`err`)}finally{n(),this.syncingRemote=!1}},syncSelected(){return this.runSync(this.selectedId)},async openRemoteAllCards(){if(this.syncProgressOpen||this.syncingRemote)return;this.remoteAllOpen=!0;let e=this.readRemoteCardsCache();if(e){if(this.remoteCards=e.cards,this.remoteCardsError=``,this.remoteCardsLoading=!1,Date.now()-e.at<gG)return;this.revalidateRemoteCards()}else await this.fetchRemoteCards()},async fetchRemoteCards(){this.remoteCardsLoading=!0,this.remoteCardsError=``;try{let e=await Y(`/__remote-cards`),t=await e.json().catch(()=>({}));if(t.needCredentials){this.remoteAllOpen=!1,this.openLoginModal(`remote-all`);return}if(!e.ok||!t.ok)throw Error(t.message||t.error||`HTTP `+e.status);let n=Array.isArray(t.cards)?t.cards:[];this.remoteCards=n,this.writeRemoteCardsCache(n)}catch(e){this.remoteCardsError=e&&e.message||`远端卡片列表加载失败`}finally{this.remoteCardsLoading=!1}},async revalidateRemoteCards(){if(!this.remoteCardsRefreshing){this.remoteCardsRefreshing=!0;try{let e=await Y(`/__remote-cards`),t=await e.json().catch(()=>({}));if(!e.ok||!t.ok||t.needCredentials)return;let n=Array.isArray(t.cards)?t.cards:[];this.remoteCards=n,this.remoteCardsError=``,this.writeRemoteCardsCache(n)}catch{}finally{this.remoteCardsRefreshing=!1}}},readRemoteCardsCache(){if(this._remoteCardsMem)return this._remoteCardsMem;let e=_G();return e&&(this._remoteCardsMem=e),e},writeRemoteCardsCache(e){let t={v:1,at:Date.now(),cards:e};this._remoteCardsMem=t,vG(e)},closeRemoteAllCards(){this.remoteAllOpen=!1,this.remoteCardFormOpen=!1},openRemoteCardCreate(){this.remoteCardFormMode=`create`,this.remoteCardFormCard=null,this.remoteCardFormOpen=!0},openRemoteCardEdit(e){e&&(this.remoteCardFormMode=`edit`,this.remoteCardFormCard=e,this.remoteCardFormOpen=!0)},requestDeleteRemoteCard(e){if(!e||this.remoteCardDeletingId)return;let t=e.title||e.id;this.requestConfirm({title:`删除远端卡片`,message:`即将从远端删除卡片「${t}」(${e.id})。删除后该卡片及其发布记录将从远端移除,且无法恢复——此操作不可撤销。本地 src/cards 下的同名目录不受影响。`,confirmText:`删除`,severity:`danger`,onConfirm:()=>this.deleteRemoteCardItem(e)})},async deleteRemoteCardItem(e){if(!e||!e.cardId)Q(`删除失败:该卡片缺少远端主键 cardId,请刷新列表后重试`,`err`,3600);else{this.remoteCardDeletingId=e.id;try{let t=await Y(`/__remote-card/delete`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cardId:e.cardId})}),n=await t.json().catch(()=>({}));if(n.needCredentials){this.openLoginModal(`remote-all`);return}if(!t.ok||!n.ok)throw Error(n.message||n.error||`HTTP `+t.status);Q(`已删除远端卡片 ${e.id}`,`ok`),this.revalidateRemoteCards()}catch(e){Q(`删除失败:`+(e&&e.message||e),`err`,3600)}finally{this.remoteCardDeletingId=``}}},onRemoteCardSubmitted({mode:e,code:t}){this.remoteCardFormOpen=!1,Q(e===`edit`?`已更新远端卡片 ${t} 的基本信息`:`已在远端新建卡片 ${t}`,`ok`),this.revalidateRemoteCards()},requestDownloadSelected(e){!this.syncProgressOpen&&e&&e.length&&this.requestConfirm({title:`下载选中卡片`,message:`即将从远端拉取 ${e.length} 张卡片并覆盖本地同名卡(已有内容将被替换,不可撤销)。是否继续?`,confirmText:`开始下载`,severity:`warn`,onConfirm:()=>this.startSyncJob(e)})},requestDownloadAll(){this.syncProgressOpen||this.requestConfirm({title:`下载全部卡片`,message:`即将从远端拉取完整卡片列表,并逐张覆盖本地同名卡片。本地独有卡片会保留,但被覆盖卡片的未保存改动将丢失,且无法撤销。是否继续?`,confirmText:`开始下载`,severity:`warn`,onConfirm:()=>this.startSyncJob(null)})},async startSyncJob(e){this.syncPendingCodes=e;try{let t=await Y(`/__sync`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e?{cardCodes:e}:{})}),n=await t.json().catch(()=>({}));if(n.needCredentials){this.openLoginModal(`remote-all`);return}if(!t.ok||!n.ok)throw Error(n.message||n.error||`HTTP `+t.status);this.syncJobId=n.jobId,this.syncProgress={done:0,total:Number(n.total)||0,current:``},this.syncProgressOpen=!0,this.pollSyncProgress()}catch(e){this.syncPendingCodes=void 0,Q(`启动下载失败:`+(e&&e.message),`err`)}},pollSyncProgress(){this.syncPollTimer&&clearTimeout(this.syncPollTimer);let e=async()=>{if(this.syncJobId&&this.syncProgressOpen){try{let e=await Y(`/__sync/progress?jobId=`+encodeURIComponent(this.syncJobId)),t=await e.json().catch(()=>({}));if(!e.ok||!t.ok)throw Error(t.message||`进度查询失败`);if(this.syncProgress={done:Number(t.done)||0,total:Number(t.total)||0,current:t.currentTitle||t.currentId||``},t.finished){this.onSyncJobFinished(t.result||{});return}}catch(e){console.error(`[同步进度查询失败]`,e)}this.syncPollTimer=setTimeout(e,600)}};e()},stopSyncPolling(){this.syncPollTimer&&=(clearTimeout(this.syncPollTimer),null)},async onSyncJobFinished(e){if(this.stopSyncPolling(),this.syncProgressOpen=!1,this.syncJobId=``,e.needCredentials){this.openLoginModal(`remote-all`);return}let t=this.syncPendingCodes;if(this.syncPendingCodes=void 0,e.cancelled){let t=(e.synced||[]).length;Q(t?`下载已取消(已完成 ${t} 张)`:`下载已取消`,`ok`)}else if(e.success){let t=(e.synced||[]).length,n=(e.failed||[]).length;Q(n?`下载完成:成功 ${t} 张,失败 ${n} 张`:`下载完成:共 ${t} 张`,n?`warn`:`ok`)}else Q(`下载失败:`+(e.error||`未知错误`),`err`);if((e.success||e.cancelled)&&(this.credentialsReady=!0),await this.fetchCardList(),this.selectedId)try{await this.onRefresh()}catch{}e.success&&t&&t.length&&(this.remoteAllOpen=!1)},cancelSyncJob(){this.syncJobId&&Y(`/__sync/cancel`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({jobId:this.syncJobId})}).catch(()=>{})},async checkCredentials(){try{let e=await Y(`/__login-credentials`),t=await e.json().catch(()=>({}));this.credentialsReady=!e.ok||!!t.hasCredentials}catch{this.credentialsReady=!0}},onRemoteTrigger(e){this.remoteToggleFn=e,!this.remoteBusy&&(this.credentialsReady?e():this.openLoginModal(`dropdown`))},openLoginModal(e){this.loginModal=!0,this.loginName=``,this.loginPassword=``,this.loginError=``,this.loginPendingAction=e||``},closeLoginModal(){this.loginSubmitting||(this.loginModal=!1,this.loginPassword=``,this.loginError=``,this.loginPendingAction=``,this.remoteToggleFn=null)},async submitLogin(){if(this.loginSubmitting)return;let e=this.loginName.trim(),t=this.loginPassword;if(!e||!t)this.loginError=`账号与密码均不能为空`;else{this.loginSubmitting=!0,this.loginError=``;try{let n=await Y(`/__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.credentialsReady=!0,this.loginModal=!1,this.loginPassword=``,Q(`登录验证成功,凭据已保存`,`ok`);let i=this.loginPendingAction;if(this.loginPendingAction=``,i===`sync`)await this.runSync(this.syncTarget===`selected`?this.selectedId:void 0);else if(i===`publish`)await this.doPublish();else if(i===`slices`)await this.uploadCardSlices();else if(i===`dropdown`){let e=this.remoteToggleFn;this.remoteToggleFn=null,e&&this.$nextTick(()=>e())}else if(i===`remote-all`){this.remoteAllOpen=!0;let e=this.syncPendingCodes;e===void 0?this.openRemoteAllCards():this.startSyncJob(e)}}catch(e){this.loginError=e&&e.message||`登录验证失败`,this.loginPassword=``}finally{this.loginSubmitting=!1}}},chooseSync(e){this.preview&&this.selectedId&&!this.syncingRemote&&this.requestConfirm({title:`拉取最新卡`,message:`即将从远端拉取当前卡片「${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}」的当前版本推送并覆盖到远端。线上版本会被立即更新,且该操作不可撤销。推送成功后会为当前卡片截图并上传为远端列表预览图。是否继续?`,confirmText:`继续推送`,severity:`warn`,onConfirm:()=>this.saveToRemote()})}else if(e===`publish`){if(!this.preview||!this.selectedId||this.publishing||!this.hasCardSource)return;let e=this.historyDirty;this.requestConfirm({title:`卡片发布`,message:e?`当前卡片存在未保存到远端的改动,发布将使用远端「已保存」的版本(不含这些本地改动)。仍将以「最大版本号 +1」作为新版本号上线。确认继续?`:`即将取远端「${this.selectedId}」当前最大版本号并 +1,发布上线为新版本。发布的是远端已保存的卡片内容(建议先「推送当前卡」)。该操作不可撤销。确认继续?`,confirmText:`继续发布`,severity:e?`danger`:`warn`,onConfirm:()=>this.doPublish()})}else if(e===`slices`){if(!this.preview||!this.selectedId||this.uploadingSlices)return;this.requestConfirm({title:`当前卡切图上传`,message:`即将把当前卡片「${this.selectedId}」引用的本地切图(src/slices/)上传到远端图片库,并把卡片里的 /slices/ 引用替换为线上地址(@filestore/…)。本地切图文件保留不动;内容未变的切图会直接复用已有线上地址,不重复上传。`+(this.historyDirty?`
361
+ 长任务里「还活着吗」全靠这一行——旧版只有一句「生成中…」,网关静默与真在跑无从区分 `),i.busy?(V(),H(`div`,{key:0,class:j([`aichat__live`,{"is-stalled":a.stalled}]),role:`status`,"aria-live":`polite`},[t[33]||=W(`span`,{class:`aichat__live-dot`},null,-1),W(`span`,AW,M(a.liveText),1),a.stalled?(V(),H(`button`,{key:0,type:`button`,class:`aichat__live-act`,title:`连接可能已中断,终止本轮(已落盘的产物会保留)`,onClick:t[9]||=(...e)=>a.stop&&a.stop(...e)},` 终止 `)):q(`v-if`,!0)],2)):q(`v-if`,!0)],64)):(V(),H(`div`,yW,[t[32]||=W(`span`,{class:`aichat__empty-logo`},[W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-sparkles`})])],-1),W(`p`,bW,M(a.emptyTitle),1),W(`p`,xW,M(a.emptyDesc),1),W(`div`,SW,[(V(!0),H(B,null,z(a.suggestions,e=>(V(),H(`button`,{key:e.id,type:`button`,class:j([`aichat__suggest`,`is-${e.weight}`]),title:e.text,"data-suggest-id":e.id,onClick:t=>a.useSuggestion(e)},[(V(),H(`svg`,wW,[W(`use`,{href:e.icon},null,8,TW)])),W(`span`,EW,[W(`span`,DW,M(e.label),1),W(`span`,OW,M(e.hint),1)]),e.weight===`write`?(V(),H(`span`,kW,`改卡`)):q(`v-if`,!0)],10,CW))),128)),a.canShuffle?(V(),H(`button`,{key:0,type:`button`,class:`aichat__suggest-shuffle`,onClick:t[7]||=(...e)=>a.shuffleSuggestions&&a.shuffleSuggestions(...e)},[...t[31]||=[W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-refresh`})],-1),W(`span`,null,`换一批`,-1)]])):q(`v-if`,!0)])]))],544),q(` 回到底部:脱离底部吸附时出现 `),L(W(`button`,{type:`button`,class:`aichat__jump`,title:`回到底部`,onClick:t[11]||=e=>a.scrollToBottom(!0)},[...t[34]||=[W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-arrow-down`})],-1)]],512),[[Nc,!i.pinned&&i.messages.length]])]),q(` 常驻「常用」指令:有对话后空态建议区不再出现,这里保留一排轻量入口,随上下文同源 `),a.quickChips.length&&i.messages.length&&!i.busy?(V(),H(`div`,jW,[(V(!0),H(B,null,z(a.quickChips,e=>(V(),H(`button`,{key:e.id,type:`button`,class:j([`aichat__chip`,`is-${e.weight}`]),title:e.text,"data-suggest-id":e.id,onClick:t=>a.useSuggestion(e)},[(V(),H(`svg`,NW,[W(`use`,{href:e.icon},null,8,PW)])),W(`span`,null,M(e.label),1)],10,MW))),128))])):q(`v-if`,!0),q(` 操作反馈(附件解析失败 / 历史保存失败等) `),i.notice?(V(),H(`div`,{key:2,class:j([`aichat__notice`,`aichat__notice--${i.notice.type}`])},[W(`span`,null,M(i.notice.text),1),W(`button`,{type:`button`,class:`aichat__notice-x`,title:`关闭`,onClick:t[12]||=e=>i.notice=null},[...t[35]||=[W(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[W(`use`,{href:`#icon-x`})],-1)]])],2)):q(`v-if`,!0),G(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`]),q(` 一级面板:模型库。从齿轮 / 横幅 / 消息内「去设置」进入 `),i.modelListOpen?(V(),U(c,{key:3,models:i.models,"active-id":i.activeId,"turn-running":i.turnRunning,onClose:a.closeModelList,onAdd:a.onModelAdd,onEdit:a.onModelEdit,onDelete:a.onModelDelete,onActivate:a.onModelActivate,onImportCcswitch:a.onImportCcSwitch,onOpenCommon:a.openCommon},null,8,[`models`,`active-id`,`turn-running`,`onClose`,`onAdd`,`onEdit`,`onDelete`,`onActivate`,`onImportCcswitch`,`onOpenCommon`])):q(`v-if`,!0),q(` 新增 / 编辑模型:复用 AiSettings(modelId 区分,initialModel 预填) `),i.modelEditOpen?(V(),U(l,{key:4,config:i.config,"model-id":i.editModelId,"initial-model":i.editInitialModel,onClose:a.onModelEditClose,onSaved:a.onModelSaved},null,8,[`config`,`model-id`,`initial-model`,`onClose`,`onSaved`])):q(`v-if`,!0),q(` 删除模型确认:二次确认后才真正落盘删除 `),G(u,{visible:!!i.confirmDeleteModel,"close-on-mask":!1,title:`删除模型`,message:a.deleteModelMessage,"confirm-text":`删除`,severity:`danger`,onConfirm:a.doDeleteModel,onCancel:t[13]||=e=>i.confirmDeleteModel=null},null,8,[`visible`,`message`,`onConfirm`]),q(` cc-switch 导入弹窗:列出 cc-switch 的 Codex 类供应商,勾选后批量导入模型库(按 model 去重) `),G(hc,{name:`modal`},{default:I(()=>[i.ccSwitchOpen?(V(),H(`div`,{key:0,class:`ni-overlay`,onClick:t[17]||=J(e=>i.ccSwitchOpen=!1,[`self`])},[W(`div`,FW,[W(`div`,IW,[t[36]||=W(`span`,{class:`ni-title`},`从 cc-switch 导入`,-1),W(`button`,{class:`ni-close`,type:`button`,title:`关闭`,onClick:t[14]||=e=>i.ccSwitchOpen=!1})]),W(`div`,LW,[W(`div`,RW,[q(` 加载中 `),i.ccSwitchLoading?(V(),H(`p`,zW,`正在读取 cc-switch 供应商…`)):i.ccSwitchReason?(V(),H(B,{key:1},[q(` 无可导入项 `),W(`p`,BW,M(i.ccSwitchReason),1)],2112)):(V(),H(B,{key:2},[q(` 供应商列表 `),W(`ul`,VW,[(V(!0),H(B,null,z(i.ccSwitchProviders,e=>(V(),H(`li`,{key:e.id,class:j([`ccsw-dlg__item`,{"is-active":i.ccSwitchSelected[e.id],"is-dup":e.duplicate}]),onClick:t=>a.toggleCcSwitchItem(e)},[W(`span`,{class:j([`ccsw-dlg__check`,{"is-on":i.ccSwitchSelected[e.id]}])},[i.ccSwitchSelected[e.id]?(V(),H(`svg`,UW,[...t[37]||=[W(`use`,{href:`#icon-check`},null,-1)]])):q(`v-if`,!0)],2),W(`span`,WW,[W(`span`,GW,[K(M(e.name||e.model)+` `,1),e.isCurrent?(V(),H(`span`,KW,`当前`)):q(`v-if`,!0),e.duplicate?(V(),H(`span`,qW,`已存在`)):q(`v-if`,!0)]),W(`span`,JW,[W(`span`,YW,M(e.model),1),t[38]||=W(`span`,{class:`ccsw-dlg__dot`},`·`,-1),W(`span`,XW,M(e.baseUrl),1)])])],10,HW))),128))])],2112))])]),W(`div`,ZW,[W(`span`,QW,M(a.ccSwitchDupCount>0?`已有 ${a.ccSwitchDupCount} 个重复模型被自动跳过`:`勾选要导入的供应商,批量加入模型库`),1),W(`div`,$W,[W(`button`,{class:`ni-btn ni-btn--ghost`,type:`button`,onClick:t[15]||=e=>i.ccSwitchOpen=!1},`取消`),W(`button`,{class:`ni-btn`,type:`button`,disabled:i.ccSwitchLoading||!i.ccSwitchProviders.length||a.ccSwitchCheckedCount===0,onClick:t[16]||=(...e)=>a.confirmImportCcSwitch&&a.confirmImportCcSwitch(...e)},M(a.ccSwitchCheckedCount>0?`导入选中 ${a.ccSwitchCheckedCount} 项`:`导入`),9,eG)])])])])):q(`v-if`,!0)]),_:1}),q(` 通用设置:所有模型共享的执行策略 / 环境参数 `),i.commonOpen?(V(),U(d,{key:5,onClose:t[18]||=e=>i.commonOpen=!1,onSaved:a.onCommonSaved},null,8,[`onSaved`])):q(`v-if`,!0),G(u,{visible:i.confirmClear,"close-on-mask":!1,title:`清空对话记录`,message:`将删除卡片「${n.cardName||n.cardId||`当前卡片`}」的全部对话记录,此操作不可撤销。`,"confirm-text":`清空`,severity:`danger`,onConfirm:a.doClear,onCancel:t[19]||=e=>i.confirmClear=!1},null,8,[`visible`,`message`,`onConfirm`]),q(` 终止确认:生成中按 Esc 会走到这里。几十分钟的建卡被一次误按抹掉代价太大,故加一道确认 `),G(u,{visible:i.confirmStop,"close-on-mask":!1,title:`终止本次生成`,message:`已执行的写入与落盘产物都会保留,上下文也留在对话里,之后回复「继续」可从中断处接着做。确定现在终止?`,"confirm-text":`终止生成`,severity:`danger`,onConfirm:a.doStop,onCancel:t[20]||=e=>i.confirmStop=!1},null,8,[`visible`,`onConfirm`])],38)}var nG=Z(oW,[[`render`,tG],[`__file`,`/Users/ynet/dsl-card/card-editor/components/ChatPanel.vue`]]),rG=`/assets/site-icon-CFMif8z4.png`,iG={includeMocks:!0};function aG(e){return e?JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t)):null}function oG(e){let t=aG(e);if(t&&t.dictionary&&Array.isArray(t.dictionary.inputParams)){let e=hv(t);for(let n of t.dictionary.inputParams)n&&typeof n==`object`&&!gv(e,n.key)&&(n.defaultValue=void 0)}return t}function sG(e,t){return!e||!t?e===t:JSON.stringify(oG(e))===JSON.stringify(oG(t))}function cG(e,t){return JSON.stringify(e)===JSON.stringify(t)}function lG(e,t){return!t||!e?!1:!sG(e,t)}function uG(e,t,n){return!t||!e?!1:!cG(e,n??mv(t,iG))}function dG(e,t,n,r){return!t||!e?!1:lG(e,t)||uG(n,t,r)}var fG={includeMocks:!0},pG=2500,mG=8e3,hG=`cardbuddy:remote-cards-cache:v1`,gG=3e5;function _G(){try{let e=JSON.parse(localStorage.getItem(hG)||`null`);if(e&&e.v===1&&Array.isArray(e.cards))return e}catch{}return null}function vG(e){try{localStorage.setItem(hG,JSON.stringify({v:1,at:Date.now(),cards:e}))}catch{}}var yG=new pu,bG=vu({},yG);typeof window<`u`&&(window.__rt=bG);var xG=188,SG=320,CG=200,wG=160,TG={name:`App`,components:{RenderTree:vp,DebugPanel:Gx,ApiCallPanel:pS,CardSelect:MS,AlertCardLayer:US,DeviceOverlay:ND,PickerOverlay:UD,DatePickerOverlay:eO,KeyboardOverlay:oO,LoadingExOverlay:fO,SelectionOverlay:gO,HoverGuides:SO,RemotePreview:uk,AppToast:_k,ThemeSettings:Rk,ImageLightbox:iA,ConfirmDialog:Ap,CardGallery:LA,RemoteAllCards:vj,RemoteCardFormDialog:$j,SyncProgressModal:uM,ExportDirDialog:EM,ExportProgressModal:LM,NewCardDialog:rN,ComponentPalette:vN,Switch:SN,ChatPanel:nG},provide(){return{adapter:yG,cardRuntime:bG,customComponents:{}}},data(){return{adapter:yG,rt:bG,logoUrl:rG,appVersion:`0.84.3`,cardList:[],cardCount:0,galleryOpen:!1,remoteAllOpen:!1,remoteCards:[],remoteCardsLoading:!1,remoteCardsRefreshing:!1,remoteCardsError:``,remoteCardFormOpen:!1,remoteCardFormMode:`create`,remoteCardFormCard:null,remoteCardDeletingId:``,syncJobId:``,syncProgressOpen:!1,syncProgress:{done:0,total:0,current:``},syncPollTimer:null,exportDirDialog:{visible:!1,ids:[]},compilingCards:!1,exportJobId:``,exportProgressOpen:!1,exportProgress:{done:0,total:0,current:``},exportPollTimer:null,syncPendingCodes:void 0,cardDialog:{visible:!1,mode:`create`,cardId:``,cardDesc:``},selectedId:``,externalReloadNote:``,externalReloadTimer:null,selfSaveId:``,selfSaveAt:0,preview:null,chromeOff:!1,theme:`dark`,accent:bk,cardReady:!1,reloadToken:0,moveCollapseToken:0,refreshing:!1,loadingCard:!1,currentSchema:null,currentCard:null,loadedCardId:``,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,savedCard:!1,savingCard:!1,saveTimer:null,coverBusy:!1,savingRemote:!1,restoringHistory:!1,savedRemote:!1,verifiedRemote:null,saveRemoteTimer:null,syncingRemote:!1,syncedRemote:!1,remotePreviewOpen:!1,syncTarget:null,syncTimer:null,uploadingSlices:!1,publishing:!1,metaRefreshToken:0,historyRefreshToken:0,loginModal:!1,loginName:``,loginPassword:``,loginSubmitting:!1,loginError:``,credentialsReady:!1,loginPendingAction:``,remoteToggleFn:null,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}})(),chatBusy:!1,chatProgress:null,fab:{x:null,y:null,dragging:!1},fabDrag:null,fabSuppressClick:!1,devServiceForm:Kl(),apiBaseResolved:Jl(),devSvcLive:`unknown`,devSvcDown:!1,_unsubCardsChanged:null}},computed:{fabTitle(){if(this.aiChatOpen)return`AI 开发助手(已打开)`;if(!this.chatBusy)return`AI 开发助手`;let e=this.chatProgress,t=e&&e.startedAt?`${GL(e.startedAt)} 开始 · `:``;return`AI 开发助手正在生成(${e?`${t}第 ${e.steps} 步 · 已用 ${WL(e.ms)}${e.stage?` · ${e.stage}`:``}${e.stalled?` · 疑似断流`:``}`:``}),点击展开查看`},fabStyle(){return this.fab.x==null||this.fab.y==null?{}:{right:this.fab.x+`px`,bottom:this.fab.y+`px`,left:`auto`,top:`auto`,transition:this.fab.dragging?`none`:``}},currentBg(){return this.bgOptions.find(e=>e.key===this.canvasBg)||this.bgOptions[0]},cardIds(){return this.cardList.map(e=>e&&e.id).filter(Boolean)},remoteImportedIds(){return this.cardList.filter(e=>e&&e.imported).map(e=>e.id)},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!!td()},hasCardSource(){return!!(this.cardImportMeta&&this.cardImportMeta.source)},remoteBusy(){return!!(this.savingRemote||this.syncingRemote||this.uploadingSlices||this.publishing)},remoteBusyReason(){return this.savingRemote?`正在推送至远端…`:this.syncingRemote?`正在拉取远端卡片…`:this.uploadingSlices?`正在上传切图…`:this.publishing?`正在发布版本…`:``},historyDirty(){return dG(this.currentSchema,this.localSchema,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||xG;return Math.max(e,Math.min(t,n))},viewportWidth(){return this.stageWidth,typeof window<`u`?window.innerWidth:1280},debugSoftWidth(){return Math.max(CG,Math.floor(this.viewportWidth/3))},previewBounds(){let e=this.stageWidth||0,t=Math.floor(e/2),n=e-SG-this.debugSoftWidth;return{min:xG,max:Math.max(xG,Math.min(t,n))}},debugMinWidth(){let e=this.colsWidth||Math.max(0,(this.stageWidth||0)-this.previewWidth);return Math.max(CG,Math.min(this.debugSoftWidth,Math.floor(e)-SG))},apiPanelBounds(){let e=this.colsWidth||Math.max(0,(this.stageWidth||0)-this.previewWidth),t=Math.max(wG,Math.floor(e)-this.debugMinWidth);return{min:Math.min(SG,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||``},cardEmpty(){let e=this.currentSchema;if(!e||!e.view)return!0;let t=e.view.components;return!Array.isArray(t)||t.length===0},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=``,yO())},credentialsReady(e){!e&&this.activeTab===`history`&&(this.activeTab=`vars`)},aiChatOpen(e){try{sessionStorage.setItem(`cardbuddy:ai-chat-open`,e?`1`:`0`)}catch{}},theme(e){document.documentElement.dataset.theme=e;try{localStorage.setItem(`cardbuddy:theme`,e)}catch{}},accent(e){Ck(e),wk(e)},apiPanelOpen(e){try{localStorage.setItem(`cardbuddy:api-panel-open`,e?`1`:`0`)}catch{}},remoteBusy(e){e&&(this.devServiceOpen=!1)},chatBusy(e,t){if(t&&!e&&this.selectedId){if(this.historyDirty){let e=this.currentSchema&&this.currentSchema.name||this.selectedId;this.showExternalReloadNote(`AI 已更新磁盘上的卡片「${e}」,当前有未保存编辑,未自动重载(点顶栏刷新可载入最新版)`)}else this.loadCard(this.selectedId)}}},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),this.syncPollTimer&&clearTimeout(this.syncPollTimer),this.exportPollTimer&&clearTimeout(this.exportPollTimer),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.loadCanvasBg(),this.loadApiPanelWidth(),this._stageObserver=new ResizeObserver(e=>{for(let t of e)t.target===this.$refs.colsEl?this.colsWidth=Math.round(t.contentRect.width):(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),this.fabInitPosition(),window.addEventListener(`resize`,this.onFabResize),this.checkCredentials()},beforeDestroy(){window.removeEventListener(`resize`,this.onFabResize)},async created(){wD(bG);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,this.accent=Sk(),Ck(this.accent);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:{fabBounds(){let e=this.$refs.aiFab,t=e?e.offsetWidth:56,n=e?e.offsetHeight:56,r=document.documentElement.clientWidth,i=document.documentElement.clientHeight;return{minX:0,minY:0,maxX:Math.max(0,r-t),maxY:Math.max(0,i-n)}},clampFab(e,t){let n=this.fabBounds();return[Math.min(Math.max(e,n.minX),n.maxX),Math.min(Math.max(t,n.minY),n.maxY)]},fabInitPosition(){let e=this.$refs.aiFab;if(!e)return;let t=this.loadFabPos(),n=document.documentElement.clientWidth,r=document.documentElement.clientHeight,i,a;if(t)[i,a]=this.clampFab(t.x,t.y);else{let t=e.getBoundingClientRect();[i,a]=this.clampFab(n-t.right,r-t.bottom)}this.fab.x=i,this.fab.y=a},loadFabPos(){try{let e=localStorage.getItem(`cardbuddy:ai-fab-pos`);if(!e)return null;let t=JSON.parse(e);if(typeof t.x==`number`&&typeof t.y==`number`&&isFinite(t.x)&&isFinite(t.y))return t}catch{}return null},saveFabPos(){try{localStorage.setItem(`cardbuddy:ai-fab-pos`,JSON.stringify({x:this.fab.x,y:this.fab.y}))}catch{}},onFabPointerDown(e){if(!this.aiChatOpen){this.fabSuppressClick=!1,this.fab.x??this.fabInitPosition(),this.fabDrag={pointerId:e.pointerId,startX:e.clientX,startY:e.clientY,originX:this.fab.x,originY:this.fab.y,moved:!1};try{e.currentTarget.setPointerCapture(e.pointerId)}catch{}}},onFabPointerMove(e){let t=this.fabDrag;if(!t||t.pointerId!==e.pointerId)return;let n=e.clientX-t.startX,r=e.clientY-t.startY;if(!t.moved){if(Math.hypot(n,r)<4)return;t.moved=!0,this.fab.dragging=!0}let[i,a]=this.clampFab(t.originX-n,t.originY-r);this.fab.x=i,this.fab.y=a},onFabPointerUp(e){let t=this.fabDrag;if(!t||t.pointerId!==e.pointerId)this.fab.dragging=!1;else{try{e.currentTarget.releasePointerCapture(e.pointerId)}catch{}t.moved&&(this.fabSuppressClick=!0,this.saveFabPos()),this.fab.dragging=!1,this.fabDrag=null}},onFabClick(){this.fabSuppressClick?this.fabSuppressClick=!1:this.aiChatOpen=!this.aiChatOpen},onFabResize(){if(this.fab.x==null)return;let[e,t]=this.clampFab(this.fab.x,this.fab.y);this.fab.x=e,this.fab.y=t},setupCardsChanged(){if(this._unsubCardsChanged){try{this._unsubCardsChanged()}catch{}this._unsubCardsChanged=null}this.devSvcLive=`connecting`,this._unsubCardsChanged=eu(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(){ql(this.devServiceForm),this.apiBaseResolved=Jl(),this.setupCardsChanged(),this.devServiceOpen=!1,Q(`本地开发服务配置已保存`,`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()})},loadCanvasBg(){try{let e=localStorage.getItem(`cardbuddy:canvas-bg`);e!=null&&this.bgOptions.some(t=>t.key===e)&&(this.canvasBg=e)}catch{}},saveCanvasBg(){try{localStorage.setItem(`cardbuddy:canvas-bg`,this.canvasBg)}catch{}},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,this.saveCanvasBg()},onSelect(e){e!==this.selectedId&&(this.remoteBusy?Q(`远端操作进行中(${this.remoteBusyReason}),完成前不能切换卡片`,`err`,3200):this.chatBusy?Q(`AI 正在生成「${this.selectedId}」:先点 ■ 停止,或等本轮结束后再切卡`,`err`,3600):(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.cardDialog.mode=`create`,this.cardDialog.cardId=``,this.cardDialog.cardDesc=``,this.cardDialog.visible=!0,this.fetchCardList()},openRenameCard(){this.selectedId&&(this.cardDialog.mode=`rename`,this.cardDialog.cardId=this.selectedId,this.cardDialog.cardDesc=this.currentSchema&&this.currentSchema.desc||``,this.cardDialog.visible=!0,this.fetchCardList())},async onCardRenamed({oldId:e,id:t}){if(this.cardDialog.visible=!1,Q(t===e?`已更新卡片描述`:`已重命名卡片 ${e} → ${t}`,`ok`),t!==e)return this.applyCardRename(e,t);await this.fetchCardList(),await this.loadCard(t)},async applyCardRename(e,t){if(!e||!t||e===t)return;let n=`${e}->${t}`;if(this._renameAppliedKey===n)return;this._renameAppliedKey=n,setTimeout(()=>{this._renameAppliedKey===n&&(this._renameAppliedKey=``)},1e3);let r=this.selectedId===e;this.$refs.chatPanel?.migrateCard(e,t),r&&this.selectCard(t),this.selfSaveId=t,this.selfSaveAt=Date.now(),await this.fetchCardList(),r&&(this.historyDirty&&!this.confirm.show?this.requestConfirm({title:`卡片已重命名`,message:`卡片「${e}」已重命名为「${t}」,而编辑器内还有未保存的改动。\n重新载入会放弃这些未保存改动并显示磁盘版本;保留改动则继续用当前编辑器内容(后续保存会写到新目录 ${t})。`,confirmText:`重新载入`,cancelText:`保留我的改动`,onConfirm:()=>this.loadCard(t)}):await this.loadCard(t))},closeGallery(){this.galleryOpen=!1},onGalleryExport(e){!this.exportProgressOpen&&e&&e.length&&(this.exportDirDialog={visible:!0,ids:e})},async onGalleryCompile(e){if(!this.compilingCards&&e&&e.length){this.compilingCards=!0;try{let t=await Y(`/__compile-cards`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cardIds:e})}),n=await t.json().catch(()=>({}));if(!t.ok||!n.ok)throw Error(n.message||n.error||`HTTP `+t.status);let r=(n.cards||[]).filter(e=>e.ok),i=(n.cards||[]).filter(e=>!e.ok),a=r.reduce((e,t)=>e+(t.warnings?t.warnings.length:0),0);console.info(`[compile] 产物目录`,n.outDir);for(let e of r){console.info(`[compile] ${e.id} 模板 ${e.templates} 个 / 节点 ${e.nodes} / 客户端 ${e.clientBytes}B / H5 ${e.h5Bytes}B`);for(let t of e.warnings||[])console.warn(`[compile] ${e.id} ${t}`)}for(let e of i)console.error(`[compile] ${e.id} 失败:${e.error}`);if(i.length)Q(`已编译 ${r.length} 张,${i.length} 张失败(原因见控制台)`,`err`,4200);else{let e=a?`(${a} 条告警见控制台)`:``;Q(`已编译 ${r.length} 张卡片,客户端模板与 H5 脚本已写入 flame-build 目录${e}`,`ok`,3600)}this.$refs.cardGallery?.setChecked(i.map(e=>e.id))}catch(e){Q(`编译失败:`+(e&&e.message?e.message:`无法连接本地开发服务`),`err`,4200)}finally{this.compilingCards=!1}}},async onExportDirConfirm(e){this.exportDirDialog.visible=!1;let t=this.exportDirDialog.ids;try{let n=await Y(`/__export-cards`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cardIds:t,targetDir:e})}),r=await n.json().catch(()=>({}));if(!n.ok||!r.ok)throw Error(r.message||r.error||`HTTP `+n.status);this.exportJobId=r.jobId,this.exportProgress={done:0,total:Number(r.total)||0,current:``},this.exportProgressOpen=!0,this.pollExportProgress()}catch(e){this.exportDirDialog.ids=[],Q(`启动导出失败:`+(e&&e.message),`err`)}},pollExportProgress(){this.exportPollTimer&&clearTimeout(this.exportPollTimer);let e=async()=>{if(this.exportJobId&&this.exportProgressOpen){try{let e=await Y(`/__export/progress?jobId=`+encodeURIComponent(this.exportJobId)),t=await e.json().catch(()=>({}));if(!e.ok||!t.ok)throw Error(t.message||`进度查询失败`);if(this.exportProgress={done:Number(t.done)||0,total:Number(t.total)||0,current:t.currentTitle||t.currentId||``},t.finished){this.onExportJobFinished(t.result||{});return}}catch(e){console.error(`[导出进度查询失败]`,e)}this.exportPollTimer=setTimeout(e,600)}};e()},stopExportPolling(){this.exportPollTimer&&=(clearTimeout(this.exportPollTimer),null)},onExportJobFinished(e){this.stopExportPolling(),this.exportProgressOpen=!1,this.exportJobId=``,this.exportDirDialog.ids=[];let t=e.failed||[],n=e.warnings||[];if(t.length&&console.warn(`[导出失败明细]`,t),n.length&&console.warn(`[导出告警明细]`,n),!e.cancelled&&e.success&&this.$refs.cardGallery?.setChecked(t.map(e=>e&&e.id).filter(Boolean)),e.cancelled){let t=e.exported||0;Q(t?`导出已取消(已完成 ${t} 张,未生成 zip)`:`导出已取消`,`ok`)}else if(e.success){let r=e.exported||0,i=t.length,a=e.folderName?`${e.folderName}.zip`:`zip`,o=e.revealed?`,已打开所在目录`:``,s=i?`(${t[0].id||`?`}:${String(t[0].error||``).slice(0,60)})`:``,c=n.length?`,${n.length} 条告警见控制台`:``;Q(i?`导出完成:成功 ${r} 张,失败 ${i} 张${s} → ${a}${o}${c}`:`导出成功:${a}(${r} 张卡片)${o}${c}`,i?`warn`:`ok`)}else Q(`导出失败:`+(e.error||`未知错误`),`err`)},cancelExportJob(){this.exportJobId&&Y(`/__export/cancel`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({jobId:this.exportJobId})}).catch(()=>{})},async openCardFolder(){if(this.selectedId)try{let e=await Y(`/__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?Q(t.error||`打开文件夹失败`,`err`):Q(`已打开卡片文件夹`,`ok`)}catch{Q(`无法连接本地开发服务,请确认已启动`,`err`)}},async refreshCard(){if(this.selectedId&&!this.refreshing){this.refreshing=!0;try{await this.loadCard(this.selectedId)}finally{this.refreshing=!1}}},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([Y(`/__validate/${encodeURIComponent(e)}`),Y(`/__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 Y(`/__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),e!==this.loadedCardId&&(this.activeTab=`vars`),this.loadedCardId=e,t.clearAll?this.rt.clearErrors():this.rt.clearBindingErrors(),this.rt.clearApiCalls(),t.keepScope||this.rt.setRootScope(mv(r.schema,fG)),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(mv(r.schema,fG)),this.preview=qu(r.schema),typeof window<`u`&&(window.__cardModel=$u(r.schema)),this.cardReady=!1,this.$nextTick(()=>{this.cardReady=!0}),this.syncDataToSchema(),this.selectedNodeId=``,this.baseline={schema:this.cloneSchema(this.currentSchema)},this.reloadToken+=1,RS(),TD(),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},cloneData(e){return e==null?e:JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t))},applySchemaSnapshot(e){if(!this.selectedId||!e)return;let t=e.schema||e,n=e.data==null?mv(t,fG):e.data;this.currentSchema=this.cloneSchema(t),this.rt.setRootScope(this.cloneData(n)),this.preview=qu(this.currentSchema),typeof window<`u`&&(window.__cardModel=$u(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``,this.reloadToken+=1,this.refreshValidateBadge()},commitHistory(){if(!this.currentSchema||!this.baseline||!dG(this.currentSchema,this.baseline.schema,this.rt.rootScope))return;let e={schema:this.baseline.schema};this.past.push(e),this.past.length>30&&this.past.shift(),this.future=[],this.baseline={schema:this.cloneSchema(this.currentSchema)},this.persistSnapshot(e)},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;Y(`/__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 Y(`/__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?mv(e.schema,fG):e.data}))}catch(e){console.warn(`[加载编辑历史失败]`,e)}},undo(){if(!this.canUndo||this.remoteBusy)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||this.remoteBusy)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.remoteBusy||this.historyDirty&&this.preview&&this.selectedId&&await this.saveCard()&&Q(`已将该版本保存到本地`,`ok`)},async generateCardCover(){if(!this.preview||!this.selectedId||this.coverBusy||this.remoteBusy)return;if(this.historyDirty&&!await this.saveCard()){Q(`本地保存失败,已取消生成预览图`,`err`);return}let e=null;try{let t=await Y(`/__card-image/`+encodeURIComponent(this.selectedId)+`?probe=1`),n=await t.json().catch(()=>({}));t.ok&&n.exists&&(e=n.file)}catch{}e?this.requestConfirm({title:`重新生成截图`,message:`该卡片已有截图(${e}),继续将覆盖它。确定重新生成吗?`,confirmText:`重新生成`,severity:`warn`,onConfirm:()=>this.doGenerateCover()}):this.doGenerateCover()},async doGenerateCover(){this.coverBusy=!0;try{let e=await Y(`/__card-image/`+encodeURIComponent(this.selectedId),{method:`POST`}),t=await e.json().catch(()=>({}));if(!e.ok)throw Error(t.message||`HTTP `+e.status);Q(`截图已生成(${t.width}×${t.height})→ ${t.file}`,`ok`,3e3)}catch(e){Q(`生成截图失败:`+String(e&&e.message||e),`err`,3600)}finally{this.coverBusy=!1}},onSchemaChanged(){this.selectedId&&this.currentSchema&&(this.reloadToken+=1,this.preview=qu(this.currentSchema),typeof window<`u`&&(window.__cardModel=$u(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``)},onSchemaCommit(){this.selectedId&&this.currentSchema&&(this.reloadToken+=1,this.preview=qu(this.currentSchema),typeof window<`u`&&(window.__cardModel=$u(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=hv(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&&(rd(n.node,this.selectedId),Q(`已复制节点(含子节点)`,`ok`))},pasteNode(){let e=td();if(!e||!this.currentSchema)return;let t=id(e),n=[...new Set(t.filter(e=>!ju[e]))];if(n.length){Q(`粘贴失败:含未知组件类型 `+n.join(`、`),`err`);return}let r=JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t));ad(r);let i=this.selectedNodeId&&this.selectedNodeId!==`_root`?this.findPreviewNodeById(this.preview.view,this.selectedNodeId):null,a=`root`;i&&(a=Wu(i.className)?`into`:`after`),this.insertClone(r,i,a);let o=nd();o&&o!==this.selectedId?Q(`已从卡片「${o}」跨卡片粘贴到「${this.selectedId}」`,`ok`):Q(`已粘贴节点(含子节点)`,`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));ad(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=Gu(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=qu(t),typeof window<`u`&&(window.__cardModel=$u(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=qu(this.currentSchema),typeof window<`u`&&(window.__cardModel=$u(this.currentSchema)),this.syncDataToSchema(),this.selectedNodeId=``,yO(),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()}else if(n&&(r===`v`||r===`V`)){if(t||this.hasTextSelection()||!this.hasNodeSelection()||!td())return;this.pasteNode(),e.preventDefault()}else if(n&&(r===`d`||r===`D`)){if(t||this.hasTextSelection()||!this.hasNodeSelection())return;this.duplicateNode(),e.preventDefault()}else if(n&&(r===`z`||r===`Z`)){if(t)return;e.preventDefault(),e.shiftKey?this.redo():this.undo()}else if(n&&(r===`y`||r===`Y`)){if(t)return;e.preventDefault(),this.redo()}else if(r===`Delete`||r===`Backspace`){if(t||!this.selectedNodeId||this.selectedNodeId===`_root`)return;e.preventDefault(),this.deleteSelectedNode()}else r===`Escape`&&this.selectedNodeId&&(this.selectedNodeId=``,e.preventDefault())},onHoverNode(e){if(!e){yO();return}if(e.leave){yO(e.id);return}let t=typeof document<`u`?document.querySelector(`.phone__screen`):null;if(!t){yO(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?vO(r,e.id):yO(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)},onAiLocateNode(e){let t=e?this.findPreviewNodeByCid(e):null;t?(this.activeTab=`structure`,this.selectedNodeId=t.id):Q(`没找到组件 ${e||``}:卡片可能已被改动,重新校验一次即可`,`err`)},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(!Wu(i.className))return!1;let e=Gu(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=qu(this.currentSchema),typeof window<`u`&&(window.__cardModel=$u(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=Ku(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(Wu(n.className)){let t=Gu(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=qu(i),typeof window<`u`&&(window.__cardModel=$u(i)),this.syncDataToSchema();let s=this.findViewIdByComponentId(this.preview.view,a.componentId);this.selectedNodeId=s||``,this.scheduleCommit()},async runSync(e){if(this.syncingRemote||e&&!this.selectedId)return;this.syncingRemote=!0,this.syncedRemote=!1,this.syncTarget=e?`selected`:`full`;let t=null,n=()=>{t&&=(clearTimeout(t),null)};try{let n=await Y(`/__sync`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e?{cardCode:e}:{})});if(!n.ok){let e=await n.json().catch(()=>({}));throw Error(e.message||e.error||`HTTP `+n.status)}let r=await n.json().catch(()=>({}));if(!r.ok||!r.jobId)throw Error(r.message||r.error||`同步任务启动失败`);let i=await new Promise((e,n)=>{let i=0,a=async()=>{try{let t=await Y(`/__sync/progress?jobId=`+encodeURIComponent(r.jobId)),n=await t.json().catch(()=>({}));if(!t.ok||!n.ok)throw Error(n.message||`进度查询失败`);if(n.finished){e(n.result||{});return}}catch(e){console.error(`[同步进度查询失败]`,e)}++i>=200?n(Error(`同步超时(进度查询无响应)`)):t=setTimeout(a,600)};a()});if(i.needCredentials){this.openLoginModal(`sync`);return}if(i.cancelled){Q(`同步已取消`,`ok`);return}if(!i.success){let e=(i.failed||[]).map(e=>`${e.id}: ${e.error}`).join(`; `);throw Error(e||i.error||`同步失败`)}this.syncedRemote=!0,this.credentialsReady=!0,this.syncTimer&&clearTimeout(this.syncTimer),this.syncTimer=setTimeout(()=>{this.syncedRemote=!1,this.syncTarget=null},1800),await this.onRefresh(),Q(this.syncTarget===`selected`?`已拉取最新卡到本地`:`拉取全部卡完成`,`ok`),this.persistRemoteVersion(this.selectedId,null)}catch(e){console.error(`[同步远程数据失败]`,e),Q(`同步失败:`+(e&&e.message),`err`)}finally{n(),this.syncingRemote=!1}},syncSelected(){return this.runSync(this.selectedId)},async openRemoteAllCards(){if(this.syncProgressOpen||this.syncingRemote)return;this.remoteAllOpen=!0;let e=this.readRemoteCardsCache();if(e){if(this.remoteCards=e.cards,this.remoteCardsError=``,this.remoteCardsLoading=!1,Date.now()-e.at<gG)return;this.revalidateRemoteCards()}else await this.fetchRemoteCards()},async fetchRemoteCards(){this.remoteCardsLoading=!0,this.remoteCardsError=``;try{let e=await Y(`/__remote-cards`),t=await e.json().catch(()=>({}));if(t.needCredentials){this.remoteAllOpen=!1,this.openLoginModal(`remote-all`);return}if(!e.ok||!t.ok)throw Error(t.message||t.error||`HTTP `+e.status);let n=Array.isArray(t.cards)?t.cards:[];this.remoteCards=n,this.writeRemoteCardsCache(n)}catch(e){this.remoteCardsError=e&&e.message||`远端卡片列表加载失败`}finally{this.remoteCardsLoading=!1}},async revalidateRemoteCards(){if(!this.remoteCardsRefreshing){this.remoteCardsRefreshing=!0;try{let e=await Y(`/__remote-cards`),t=await e.json().catch(()=>({}));if(!e.ok||!t.ok||t.needCredentials)return;let n=Array.isArray(t.cards)?t.cards:[];this.remoteCards=n,this.remoteCardsError=``,this.writeRemoteCardsCache(n)}catch{}finally{this.remoteCardsRefreshing=!1}}},readRemoteCardsCache(){if(this._remoteCardsMem)return this._remoteCardsMem;let e=_G();return e&&(this._remoteCardsMem=e),e},writeRemoteCardsCache(e){let t={v:1,at:Date.now(),cards:e};this._remoteCardsMem=t,vG(e)},closeRemoteAllCards(){this.remoteAllOpen=!1,this.remoteCardFormOpen=!1},openRemoteCardCreate(){this.remoteCardFormMode=`create`,this.remoteCardFormCard=null,this.remoteCardFormOpen=!0},openRemoteCardEdit(e){e&&(this.remoteCardFormMode=`edit`,this.remoteCardFormCard=e,this.remoteCardFormOpen=!0)},requestDeleteRemoteCard(e){if(!e||this.remoteCardDeletingId)return;let t=e.title||e.id;this.requestConfirm({title:`删除远端卡片`,message:`即将从远端删除卡片「${t}」(${e.id})。删除后该卡片及其发布记录将从远端移除,且无法恢复——此操作不可撤销。本地 src/cards 下的同名目录不受影响。`,confirmText:`删除`,severity:`danger`,onConfirm:()=>this.deleteRemoteCardItem(e)})},async deleteRemoteCardItem(e){if(!e||!e.cardId)Q(`删除失败:该卡片缺少远端主键 cardId,请刷新列表后重试`,`err`,3600);else{this.remoteCardDeletingId=e.id;try{let t=await Y(`/__remote-card/delete`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cardId:e.cardId})}),n=await t.json().catch(()=>({}));if(n.needCredentials){this.openLoginModal(`remote-all`);return}if(!t.ok||!n.ok)throw Error(n.message||n.error||`HTTP `+t.status);Q(`已删除远端卡片 ${e.id}`,`ok`),this.revalidateRemoteCards()}catch(e){Q(`删除失败:`+(e&&e.message||e),`err`,3600)}finally{this.remoteCardDeletingId=``}}},onRemoteCardSubmitted({mode:e,code:t}){this.remoteCardFormOpen=!1,Q(e===`edit`?`已更新远端卡片 ${t} 的基本信息`:`已在远端新建卡片 ${t}`,`ok`),this.revalidateRemoteCards()},requestDownloadSelected(e){!this.syncProgressOpen&&e&&e.length&&this.requestConfirm({title:`下载选中卡片`,message:`即将从远端拉取 ${e.length} 张卡片并覆盖本地同名卡(已有内容将被替换,不可撤销)。是否继续?`,confirmText:`开始下载`,severity:`warn`,onConfirm:()=>this.startSyncJob(e)})},requestDownloadAll(){this.syncProgressOpen||this.requestConfirm({title:`下载全部卡片`,message:`即将从远端拉取完整卡片列表,并逐张覆盖本地同名卡片。本地独有卡片会保留,但被覆盖卡片的未保存改动将丢失,且无法撤销。是否继续?`,confirmText:`开始下载`,severity:`warn`,onConfirm:()=>this.startSyncJob(null)})},async startSyncJob(e){this.syncPendingCodes=e;try{let t=await Y(`/__sync`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e?{cardCodes:e}:{})}),n=await t.json().catch(()=>({}));if(n.needCredentials){this.openLoginModal(`remote-all`);return}if(!t.ok||!n.ok)throw Error(n.message||n.error||`HTTP `+t.status);this.syncJobId=n.jobId,this.syncProgress={done:0,total:Number(n.total)||0,current:``},this.syncProgressOpen=!0,this.pollSyncProgress()}catch(e){this.syncPendingCodes=void 0,Q(`启动下载失败:`+(e&&e.message),`err`)}},pollSyncProgress(){this.syncPollTimer&&clearTimeout(this.syncPollTimer);let e=async()=>{if(this.syncJobId&&this.syncProgressOpen){try{let e=await Y(`/__sync/progress?jobId=`+encodeURIComponent(this.syncJobId)),t=await e.json().catch(()=>({}));if(!e.ok||!t.ok)throw Error(t.message||`进度查询失败`);if(this.syncProgress={done:Number(t.done)||0,total:Number(t.total)||0,current:t.currentTitle||t.currentId||``},t.finished){this.onSyncJobFinished(t.result||{});return}}catch(e){console.error(`[同步进度查询失败]`,e)}this.syncPollTimer=setTimeout(e,600)}};e()},stopSyncPolling(){this.syncPollTimer&&=(clearTimeout(this.syncPollTimer),null)},async onSyncJobFinished(e){if(this.stopSyncPolling(),this.syncProgressOpen=!1,this.syncJobId=``,e.needCredentials){this.openLoginModal(`remote-all`);return}let t=this.syncPendingCodes;if(this.syncPendingCodes=void 0,e.cancelled){let t=(e.synced||[]).length;Q(t?`下载已取消(已完成 ${t} 张)`:`下载已取消`,`ok`)}else if(e.success){let t=(e.synced||[]).length,n=(e.failed||[]).length;Q(n?`下载完成:成功 ${t} 张,失败 ${n} 张`:`下载完成:共 ${t} 张`,n?`warn`:`ok`)}else Q(`下载失败:`+(e.error||`未知错误`),`err`);if((e.success||e.cancelled)&&(this.credentialsReady=!0),await this.fetchCardList(),this.selectedId)try{await this.onRefresh()}catch{}e.success&&t&&t.length&&(this.remoteAllOpen=!1)},cancelSyncJob(){this.syncJobId&&Y(`/__sync/cancel`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({jobId:this.syncJobId})}).catch(()=>{})},async checkCredentials(){try{let e=await Y(`/__login-credentials`),t=await e.json().catch(()=>({}));this.credentialsReady=!e.ok||!!t.hasCredentials}catch{this.credentialsReady=!0}},onRemoteTrigger(e){this.remoteToggleFn=e,!this.remoteBusy&&(this.credentialsReady?e():this.openLoginModal(`dropdown`))},openLoginModal(e){this.loginModal=!0,this.loginName=``,this.loginPassword=``,this.loginError=``,this.loginPendingAction=e||``},closeLoginModal(){this.loginSubmitting||(this.loginModal=!1,this.loginPassword=``,this.loginError=``,this.loginPendingAction=``,this.remoteToggleFn=null)},async submitLogin(){if(this.loginSubmitting)return;let e=this.loginName.trim(),t=this.loginPassword;if(!e||!t)this.loginError=`账号与密码均不能为空`;else{this.loginSubmitting=!0,this.loginError=``;try{let n=await Y(`/__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.credentialsReady=!0,this.loginModal=!1,this.loginPassword=``,Q(`登录验证成功,凭据已保存`,`ok`);let i=this.loginPendingAction;if(this.loginPendingAction=``,i===`sync`)await this.runSync(this.syncTarget===`selected`?this.selectedId:void 0);else if(i===`publish`)await this.doPublish();else if(i===`slices`)await this.uploadCardSlices();else if(i===`dropdown`){let e=this.remoteToggleFn;this.remoteToggleFn=null,e&&this.$nextTick(()=>e())}else if(i===`remote-all`){this.remoteAllOpen=!0;let e=this.syncPendingCodes;e===void 0?this.openRemoteAllCards():this.startSyncJob(e)}}catch(e){this.loginError=e&&e.message||`登录验证失败`,this.loginPassword=``}finally{this.loginSubmitting=!1}}},chooseSync(e){this.preview&&this.selectedId&&!this.syncingRemote&&this.requestConfirm({title:`拉取最新卡`,message:`即将从远端拉取当前卡片「${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}」的当前版本推送并覆盖到远端。线上版本会被立即更新,且该操作不可撤销。推送成功后会为当前卡片截图并上传为远端列表预览图。是否继续?`,confirmText:`继续推送`,severity:`warn`,onConfirm:()=>this.saveToRemote()})}else if(e===`publish`){if(!this.preview||!this.selectedId||this.publishing||!this.hasCardSource)return;let e=this.historyDirty;this.requestConfirm({title:`卡片发布`,message:e?`当前卡片存在未保存到远端的改动,发布将使用远端「已保存」的版本(不含这些本地改动)。仍将以「最大版本号 +1」作为新版本号上线。确认继续?`:`即将取远端「${this.selectedId}」当前最大版本号并 +1,发布上线为新版本。发布的是远端已保存的卡片内容(建议先「推送当前卡」)。该操作不可撤销。确认继续?`,confirmText:`继续发布`,severity:e?`danger`:`warn`,onConfirm:()=>this.doPublish()})}else if(e===`slices`){if(!this.preview||!this.selectedId||this.uploadingSlices)return;this.requestConfirm({title:`当前卡切图上传`,message:`即将把当前卡片「${this.selectedId}」引用的本地切图(src/slices/)上传到远端图片库,并把卡片里的 /slices/ 引用替换为线上地址(@filestore/…)。本地切图文件保留不动;内容未变的切图会直接复用已有线上地址,不重复上传。`+(this.historyDirty?`
362
362
  当前卡片有未保存的改动,会先保存到本地再执行,避免被随后的重载丢弃。`:``)+`
363
- 是否继续?`,confirmText:`继续上传`,onConfirm:()=>this.uploadCardSlices()})}else 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 doPublish(){if(this.preview&&this.selectedId&&!this.publishing&&this.hasCardSource){this.publishing=!0;try{let e=await Y(`/__publish`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({schema:this.currentSchema})});if(!e.ok){let t=await e.json().catch(()=>({}));if(t.needCredentials){this.openLoginModal(`publish`);return}throw Error(t.message||t.error||`HTTP `+e.status)}let t=await e.json();if(!t.ok)throw Error(t.message||`发布失败`);let n=t.commit,r=n&&n.committed?` · 已提交 ${n.hash||`本地变更`}`:``;Q(`已发布版本 ${t.nextVersion}${r}`,`ok`),n&&n.committed===!1&&n.reason===`failed`&&Q(`发布成功,但卡片本地变更提交失败:${n.message||`未知原因`}`,`warn`,4e3),await this.persistRemoteVersion(this.selectedId,t.nextVersion),this.metaRefreshToken++,this.historyRefreshToken++}catch(e){console.error(`[卡片发布失败]`,e),Q(`发布失败:`+(e&&e.message),`err`)}finally{this.publishing=!1}}},async persistRemoteVersion(e,t){if(e)try{let n=t==null?`{}`:JSON.stringify({version:t}),r=await Y(`/__card-version/${encodeURIComponent(e)}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:n});if(!r.ok){let e=await r.json().catch(()=>({}));if(e.needCredentials)return;throw Error(e.message||`HTTP `+r.status)}}catch(e){console.warn(`[persistRemoteVersion 失败]`,e&&e.message)}},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 Y(`/__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(mv(this.currentSchema,fG)),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 Y(`/__card/${encodeURIComponent(e)}`);if(!t.ok)throw Error(`HTTP `+t.status);return t.json()},async fetchImportMeta(e){if(!e)this.setCardImportMeta(null);else try{let t=await Y(`/__import-meta/${encodeURIComponent(e)}`);if(!t.ok){this.setCardImportMeta(null);return}let n=await t.json().catch(()=>({}));this.setCardImportMeta(n&&n.meta||null)}catch{this.setCardImportMeta(null)}},setCardImportMeta(e){this.cardImportMeta=e,!(e&&e.source)&&this.activeTab===`history`&&(this.activeTab=`vars`)},async saveToRemote({fromAutoSave:e=!1}={}){if(!this.preview||!this.selectedId||this.savingRemote)return;this.savingRemote=!0,this.savedRemote=!1,this.verifiedRemote=null;let t=!1;try{if(this.historyDirty){let e=await this.saveCard();t=!!e,e||console.warn(`[推送] 本地落盘失败:预览图可能滞后于本次推送的内容`)}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 n=await Y(`/__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(!n.ok){let e=await n.json().catch(()=>({}));throw Error(e.message||e.error||`HTTP `+n.status)}let r=await n.json();if(!r.ok)throw Error(r.error||`远端保存失败`);this.verifiedRemote=r.verified?!!r.verified.ok:null,this.savedRemote=!0;let i=r.preview||null;if(this.verifiedRemote===!1){let t=r.verified||{},n=t.diff&&t.diff.length?`差异:`+t.diff.join(`;`):t.readBackError||`读回校验未通过`;return console.warn(`[保存校验不符]`,t),e||Q(`已保存至远端,但读回校验未通过`,`err`),{ok:!0,verified:!1,message:n,preview:i}}return e||(i&&i.ok===!1?Q(`已保存至远端,但预览图未更新:`+(i.message||i.error||`未知原因`),`warn`,3600):Q(i&&i.ok?`已保存至远端,预览图已更新`:`已保存至远端`,`ok`)),this.metaRefreshToken++,this.historyRefreshToken++,this.saveRemoteTimer&&clearTimeout(this.saveRemoteTimer),this.saveRemoteTimer=setTimeout(()=>{this.savedRemote=!1,this.verifiedRemote=null},2500),{ok:!0,verified:this.verifiedRemote,preview:i}}catch(n){return console.error(`[保存至远端失败]`,n),e||Q(`保存至远端失败:`+(n&&n.message),`err`),t||(this.selfSaveId=``,this.selfSaveAt=0),{ok:!1,verified:null,message:n&&n.message||String(n)}}finally{this.savingRemote=!1}},onRestoreHistory(e){if(!e||!this.selectedId||this.restoringHistory)return;let t=e.summary&&e.summary.componentCount!=null?`,${e.summary.componentCount} 个组件`:``;this.requestConfirm({title:`加载历史版本到编辑器`,message:`将把远端版本 #${e.id}(${e.createTime}${t})写入本地并重新载入编辑器。\n仅作加载,不会自动推送或保存到远端;当前未保存的本地编辑会被覆盖。`,confirmText:`加载此版本`,severity:`warn`,onConfirm:()=>this.restoreHistoryVersion(e)})},async restoreHistoryVersion(e){if(this.selectedId&&!this.restoringHistory){this.restoringHistory=!0;try{let t=await Y(`/__card-history/${encodeURIComponent(this.selectedId)}/restore`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({versionId:String(e.id)})});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.ok)throw Error(n.error||`恢复历史版本失败`);if(n.needCredentials){this.openLoginModal(`history`);return}this._restoreSuppressUntil=Date.now()+mG,await this.loadCard(this.selectedId,{clearAll:!0}),Q(`已加载历史版本 #${e.id}(仅本地加载,未推送远端)`,`ok`)}catch(e){console.error(`[加载历史版本失败]`,e),Q(`加载历史版本失败:`+(e&&e.message||e),`err`)}finally{this.restoringHistory=!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 Y(`/__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(`slices`);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?Q(`当前卡切图上传完成:新上传 ${n.uploaded.length} 张、复用 ${n.skipped.length} 张,替换引用 ${r} 处`,`ok`):Q(`当前卡片没有引用本地切图,无需上传`,`ok`),n.dangling&&n.dangling.length&&Q(`有 ${n.dangling.length} 处引用的切图在 src/slices 下不存在:${n.dangling.join(`、`)}`,`err`)}catch(e){console.error(`[切图上传失败]`,e),Q(`当前卡切图上传失败:`+(e&&e.message),`err`),this.selfSaveId=``,this.selfSaveAt=0}finally{this.uploadingSlices=!1}},async syncCardMeta(e){let t=await this.fetchCardMeta(e);if(!t)return;let n=this.cardList.findIndex(t=>t.id===e);n>=0?this.cardList.splice(n,1,t):this.cardList.push(t),this.cardCount=this.cardList.length},async fetchCardMeta(e){try{let t=await Y(`/__card/`+encodeURIComponent(e)+`/meta`);return t.ok?await t.json():null}catch(e){return console.error(`[读取卡片列表项失败]`,e),null}},async fetchCardList(){try{let e=await Y(`/__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 Y(`/__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(Q(`已删除本地卡片 ${e}`,`ok`),this.$refs.chatPanel?.discardCard(e),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){Q(`删除卡片失败:${e.message}`,`err`)}},async onNewCardCreated(e){this.cardDialog.visible=!1,Q(`已创建空白卡片 ${e}`,`ok`),await this.fetchCardList(),this.chatBusy?Q(`AI 正在生成「${this.selectedId}」:新卡 ${e} 已建好,本轮结束后再切过去`,`err`,3600):await this.loadCard(e)},async onRefresh(){let e=this.selectedId;if(e&&!this.refreshing){this.refreshing=!0;try{await this.loadCard(e),await this.syncCardMeta(e);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}}},onChatTurnFinished(e){if(this.chatProgress=null,this.aiChatOpen)return;let t=e&&e.steps||0,n=WL(e&&e.ms||0),r=GL(e&&e.finishedAt||Date.now());e&&e.status===`error`?Q(`AI 助手本轮报错(${r} · ${t} 步 / ${n}),展开面板查看原因`,`err`):e&&e.status===`aborted`?Q(`AI 助手本轮已终止(${r} · ${t} 步):展开面板可「从中断处继续」`,`ok`):Q(`AI 助手已完成本轮(${r} · ${t} 步 / ${n})`,`ok`)},async onChatRollbackTurn(e){let t=e&&e.cardId||this.selectedId;if(!t||this.loadingCard)return;if(t!==this.selectedId){Q(`请先切换到那张卡片再回滚`,`err`);return}let n=[];try{let e=await Y(`/__history/${encodeURIComponent(t)}`);if(!e.ok)throw Error(`HTTP ${e.status}`);let r=await e.json().catch(()=>null);n=r&&r.snapshots||[]}catch(e){Q(`读取历史快照失败:${e.message||e}`,`err`);return}let r=Number(e&&e.ts)||0,i=r?n.filter(e=>Number(e.ts)>=r-2e3):[],a=i.length?i[0]:n[n.length-1];if(!a||!a.schema){Q(`没找到本轮之前的历史快照(可能已被 30 张上限裁掉,或本轮没写入过卡片)`,`err`);return}let o=new Date(Number(a.ts)||Date.now()).toLocaleString(`zh-CN`,{hour12:!1});this.requestConfirm({title:`回滚到 AI 动手之前`,message:`将把卡片「${this.selectedCardName||t}」恢复到 ${o} 的版本,本轮及其后的 ${i.length||1} 张快照都会被撤销。回滚只改当前编辑态,可用 Ctrl+Z 撤回;确认无误后仍需点顶栏「保存到本地」才写盘。`,confirmText:`回滚`,severity:`warn`,onConfirm:()=>this.applyTurnRollback(a)})},applyTurnRollback(e){this.currentSchema&&e&&(this.past.push({schema:this.cloneSchema(this.currentSchema)}),this.past.length>30&&this.past.shift(),this.future=[],this.baseline={schema:this.cloneSchema(e.schema)},this.applySchemaSnapshot({schema:e.schema,data:e.data}),Q(`已回滚到本轮之前的版本(未落盘,可撤销;确认后点「保存到本地」)`,`ok`))},onCardsChanged(e){let t=e&&e.file||``,n=e&&e.type||`change`;if(n===`rename`&&e.oldId&&e.id&&e.oldId!==e.id)return this.applyCardRename(e.oldId,e.id);if(/\.history[\\/]/.test(t)||/data\.json$/i.test(t)||/\.import-meta\.json(\.tmp)?$/i.test(t))return;let r=t.match(/src[\\/]cards[\\/]([^\\/]+)[\\/]/),i=r?r[1]:null;this._ccIds||=new Set,this._ccTypes||={},i&&(this._ccIds.add(i),this._ccTypes[i]=n),!this._ccTimer&&(this._ccTimer=setTimeout(async()=>{this._ccTimer=null;let e=this._ccIds,t=this._ccTypes;if(this._ccIds=new Set,this._ccTypes={},!e.size)return;let n=Array.from(e),r=n[n.length-1];if(e.size===1&&r&&r===this.selectedId){if(this.selfSaveId===r&&Date.now()-this.selfSaveAt<pG){this.selfSaveId=``,this.selfSaveAt=0;return}if(this._restoreSuppressUntil&&Date.now()<this._restoreSuppressUntil){this._restoreSuppressUntil=0;return}if(this.historyDirty)this.promptExternalReload(r);else{let e=this.currentSchema&&this.currentSchema.name||r;this.showExternalReloadNote(this.chatBusy?`AI 助手已写入卡片「${e}」,预览已自动刷新`:`卡片「${e}」已在编辑器外被修改,已自动重新载入预览`),this.loadCard(this.selectedId)}return}if(e.size>1){await this.fetchCardList();let t=e.has(this.selectedId),n=this.selfSaveId===this.selectedId&&Date.now()-this.selfSaveAt<pG;if(t&&!n){if(this.historyDirty)this.promptExternalReload(this.selectedId);else{this.selfSaveId===this.selectedId&&(this.selfSaveId=``,this.selfSaveAt=0);let e=this.currentSchema&&this.currentSchema.name||this.selectedId;this.showExternalReloadNote(this.chatBusy?`AI 助手已写入卡片「${e}」,预览已自动刷新`:`卡片「${e}」已在编辑器外被修改,已自动重新载入预览`),this.loadCard(this.selectedId)}}return}if((t[r]||`change`)===`unlink`){let e=this.cardList.findIndex(e=>e.id===r);e>=0&&(this.cardList.splice(e,1),this.cardCount=this.cardList.length);return}let i=await this.fetchCardMeta(r);if(!i){await this.fetchCardList();return}let a=this.cardList.findIndex(e=>e.id===r);a>=0?this.cardList.splice(a,1,i):this.cardList.push(i),this.cardCount=this.cardList.length},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)}}},EG=[`aria-busy`],DG={class:`app__logo`},OG=[`src`],kG={class:`app__logo-version`},AG={class:`csel-combo`},jG={class:`app__btn-group`},MG=[`disabled`],NG=[`disabled`],PG=[`disabled`],FG=[`title`],IG={class:`app__badge-text`},LG={class:`app__bar-actions`},RG={class:`app__op-group`,role:`group`,"aria-label":`编辑历史与本地操作`},zG=[`disabled`],BG=[`disabled`],VG=[`disabled`,`title`],HG=[`disabled`],UG=[`disabled`,`onClick`],WG={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`},GG={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`},KG={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`},qG={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`},JG={class:`app__remote-pop dd__menu`,role:`menu`},YG=[`title`,`onClick`],XG=[`title`,`onClick`],ZG=[`onClick`],QG=[`title`,`onClick`],$G=[`title`,`onClick`],eK=[`onClick`],tK=[`disabled`,`title`,`aria-pressed`],nK={class:`app__dev-svc`},rK=[`disabled`,`title`],iK={class:`app__dev-svc-head`},aK={class:`app__dev-svc-body`},oK={class:`app__dev-svc-row`},sK={class:`app__dev-svc-row app__dev-svc-row--col`},cK={class:`app__dev-svc-row app__dev-svc-row--col`},lK={key:0,class:`app__dev-svc-hint`},uK={class:`app__dev-svc-actions`},dK=[`title`],fK={style:{display:`none`}},pK=[`title`,`aria-expanded`],mK={key:0,class:`app__nosvc-banner`},hK={key:1,class:`app__nosvc-banner app__nosvc-banner--disconnected`},gK={key:2,class:`app__nosvc-banner app__extreload-banner`},_K={class:`app__stage`,ref:`stageEl`},vK={class:`phone__scaler`},yK=[`title`],bK={class:`canvas-bar__txt`},xK={class:`canvas-bar__chip`,title:`画布宽度预设(高度随列高自适应)`},SK={class:`canvas-bar__txt`},CK={class:`canvas-bar__size`,title:`画布实际尺寸(宽 × 高,高度随窗口自适应)`},wK=[`disabled`],TK=[`data-card-ready`],EK={class:`workside`},DK={class:`workside__cols`,ref:`colsEl`},OK={class:`login-modal modal-card`,role:`dialog`,"aria-modal":`true`},kK={class:`login-modal__row`},AK=[`disabled`],jK={class:`login-modal__row`},MK=[`disabled`],NK={key:0,class:`login-modal__error`},PK={class:`login-modal__actions`},FK=[`disabled`],IK=[`disabled`];function LK(e,t,n,r,i,a){let o=R(`CardSelect`),s=R(`Dropdown`),c=R(`ThemeSettings`),l=R(`Switch`),u=R(`ChatPanel`),d=R(`RenderTree`),f=R(`SelectionOverlay`),p=R(`HoverGuides`),m=R(`AlertCardLayer`),h=R(`DeviceOverlay`),g=R(`PickerOverlay`),_=R(`DatePickerOverlay`),v=R(`KeyboardOverlay`),y=R(`LoadingExOverlay`),b=R(`DebugPanel`),x=R(`ApiCallPanel`),S=R(`RemotePreview`),C=R(`ComponentPalette`),w=R(`CardGallery`),ee=R(`RemoteAllCards`),T=R(`RemoteCardFormDialog`),E=R(`SyncProgressModal`),D=R(`ExportDirDialog`),O=R(`ExportProgressModal`),te=R(`NewCardDialog`),ne=R(`ConfirmDialog`),re=R(`AppToast`),ie=R(`ImageLightbox`);return V(),H(`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`})},[q(` 远端接口调用中(推送 / 拉取 / 切图上传 / 发布):顶栏整体冻结。
363
+ 是否继续?`,confirmText:`继续上传`,onConfirm:()=>this.uploadCardSlices()})}else 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 doPublish(){if(this.preview&&this.selectedId&&!this.publishing&&this.hasCardSource){this.publishing=!0;try{let e=await Y(`/__publish`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({schema:this.currentSchema})});if(!e.ok){let t=await e.json().catch(()=>({}));if(t.needCredentials){this.openLoginModal(`publish`);return}throw Error(t.message||t.error||`HTTP `+e.status)}let t=await e.json();if(!t.ok)throw Error(t.message||`发布失败`);let n=t.commit,r=n&&n.committed?` · 已提交 ${n.hash||`本地变更`}`:``;Q(`已发布版本 ${t.nextVersion}${r}`,`ok`),n&&n.committed===!1&&n.reason===`failed`&&Q(`发布成功,但卡片本地变更提交失败:${n.message||`未知原因`}`,`warn`,4e3),await this.persistRemoteVersion(this.selectedId,t.nextVersion),this.metaRefreshToken++,this.historyRefreshToken++}catch(e){console.error(`[卡片发布失败]`,e),Q(`发布失败:`+(e&&e.message),`err`)}finally{this.publishing=!1}}},async persistRemoteVersion(e,t){if(e)try{let n=t==null?`{}`:JSON.stringify({version:t}),r=await Y(`/__card-version/${encodeURIComponent(e)}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:n});if(!r.ok){let e=await r.json().catch(()=>({}));if(e.needCredentials)return;throw Error(e.message||`HTTP `+r.status)}}catch(e){console.warn(`[persistRemoteVersion 失败]`,e&&e.message)}},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 Y(`/__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(mv(this.currentSchema,fG)),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 Y(`/__card/${encodeURIComponent(e)}`);if(!t.ok)throw Error(`HTTP `+t.status);return t.json()},async fetchImportMeta(e){if(!e)this.setCardImportMeta(null);else try{let t=await Y(`/__import-meta/${encodeURIComponent(e)}`);if(!t.ok){this.setCardImportMeta(null);return}let n=await t.json().catch(()=>({}));this.setCardImportMeta(n&&n.meta||null)}catch{this.setCardImportMeta(null)}},setCardImportMeta(e){this.cardImportMeta=e,!(e&&e.source)&&this.activeTab===`history`&&(this.activeTab=`vars`)},async saveToRemote(){if(!this.preview||!this.selectedId||this.savingRemote)return;this.savingRemote=!0,this.savedRemote=!1,this.verifiedRemote=null;let e=!1;try{if(this.historyDirty){let t=await this.saveCard();e=!!t,t||console.warn(`[推送] 本地落盘失败:预览图可能滞后于本次推送的内容`)}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 t=await Y(`/__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(!t.ok){let e=await t.json().catch(()=>({}));throw Error(e.message||e.error||`HTTP `+t.status)}let n=await t.json();if(!n.ok)throw Error(n.error||`远端保存失败`);this.verifiedRemote=n.verified?!!n.verified.ok:null,this.savedRemote=!0;let r=n.preview||null;if(this.verifiedRemote===!1){let e=n.verified||{},t=e.diff&&e.diff.length?`差异:`+e.diff.join(`;`):e.readBackError||`读回校验未通过`;return console.warn(`[保存校验不符]`,e),Q(`已保存至远端,但读回校验未通过`,`err`),{ok:!0,verified:!1,message:t,preview:r}}return r&&r.ok===!1?Q(`已保存至远端,但预览图未更新:`+(r.message||r.error||`未知原因`),`warn`,3600):Q(r&&r.ok?`已保存至远端,预览图已更新`:`已保存至远端`,`ok`),this.metaRefreshToken++,this.historyRefreshToken++,this.saveRemoteTimer&&clearTimeout(this.saveRemoteTimer),this.saveRemoteTimer=setTimeout(()=>{this.savedRemote=!1,this.verifiedRemote=null},2500),{ok:!0,verified:this.verifiedRemote,preview:r}}catch(t){return console.error(`[保存至远端失败]`,t),Q(`保存至远端失败:`+(t&&t.message),`err`),e||(this.selfSaveId=``,this.selfSaveAt=0),{ok:!1,verified:null,message:t&&t.message||String(t)}}finally{this.savingRemote=!1}},onRestoreHistory(e){if(!e||!this.selectedId||this.restoringHistory)return;let t=e.summary&&e.summary.componentCount!=null?`,${e.summary.componentCount} 个组件`:``;this.requestConfirm({title:`加载历史版本到编辑器`,message:`将把远端版本 #${e.id}(${e.createTime}${t})写入本地并重新载入编辑器。\n仅作加载,不会自动推送或保存到远端;当前未保存的本地编辑会被覆盖。`,confirmText:`加载此版本`,severity:`warn`,onConfirm:()=>this.restoreHistoryVersion(e)})},async restoreHistoryVersion(e){if(this.selectedId&&!this.restoringHistory){this.restoringHistory=!0;try{let t=await Y(`/__card-history/${encodeURIComponent(this.selectedId)}/restore`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({versionId:String(e.id)})});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.ok)throw Error(n.error||`恢复历史版本失败`);if(n.needCredentials){this.openLoginModal(`history`);return}this._restoreSuppressUntil=Date.now()+mG,await this.loadCard(this.selectedId,{clearAll:!0}),Q(`已加载历史版本 #${e.id}(仅本地加载,未推送远端)`,`ok`)}catch(e){console.error(`[加载历史版本失败]`,e),Q(`加载历史版本失败:`+(e&&e.message||e),`err`)}finally{this.restoringHistory=!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 Y(`/__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(`slices`);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?Q(`当前卡切图上传完成:新上传 ${n.uploaded.length} 张、复用 ${n.skipped.length} 张,替换引用 ${r} 处`,`ok`):Q(`当前卡片没有引用本地切图,无需上传`,`ok`),n.dangling&&n.dangling.length&&Q(`有 ${n.dangling.length} 处引用的切图在 src/slices 下不存在:${n.dangling.join(`、`)}`,`err`)}catch(e){console.error(`[切图上传失败]`,e),Q(`当前卡切图上传失败:`+(e&&e.message),`err`),this.selfSaveId=``,this.selfSaveAt=0}finally{this.uploadingSlices=!1}},async syncCardMeta(e){let t=await this.fetchCardMeta(e);if(!t)return;let n=this.cardList.findIndex(t=>t.id===e);n>=0?this.cardList.splice(n,1,t):this.cardList.push(t),this.cardCount=this.cardList.length},async fetchCardMeta(e){try{let t=await Y(`/__card/`+encodeURIComponent(e)+`/meta`);return t.ok?await t.json():null}catch(e){return console.error(`[读取卡片列表项失败]`,e),null}},async fetchCardList(){try{let e=await Y(`/__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 Y(`/__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(Q(`已删除本地卡片 ${e}`,`ok`),this.$refs.chatPanel?.discardCard(e),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){Q(`删除卡片失败:${e.message}`,`err`)}},async onNewCardCreated(e){this.cardDialog.visible=!1,Q(`已创建空白卡片 ${e}`,`ok`),await this.fetchCardList(),this.chatBusy?Q(`AI 正在生成「${this.selectedId}」:新卡 ${e} 已建好,本轮结束后再切过去`,`err`,3600):await this.loadCard(e)},async onRefresh(){let e=this.selectedId;if(e&&!this.refreshing){this.refreshing=!0;try{await this.loadCard(e),await this.syncCardMeta(e);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}}},onChatTurnFinished(e){if(this.chatProgress=null,this.aiChatOpen)return;let t=e&&e.steps||0,n=WL(e&&e.ms||0),r=GL(e&&e.finishedAt||Date.now());e&&e.status===`error`?Q(`AI 助手本轮报错(${r} · ${t} 步 / ${n}),展开面板查看原因`,`err`):e&&e.status===`aborted`?Q(`AI 助手本轮已终止(${r} · ${t} 步):展开面板可「从中断处继续」`,`ok`):Q(`AI 助手已完成本轮(${r} · ${t} 步 / ${n})`,`ok`)},async onChatRollbackTurn(e){let t=e&&e.cardId||this.selectedId;if(!t||this.loadingCard)return;if(t!==this.selectedId){Q(`请先切换到那张卡片再回滚`,`err`);return}let n=[];try{let e=await Y(`/__history/${encodeURIComponent(t)}`);if(!e.ok)throw Error(`HTTP ${e.status}`);let r=await e.json().catch(()=>null);n=r&&r.snapshots||[]}catch(e){Q(`读取历史快照失败:${e.message||e}`,`err`);return}let r=Number(e&&e.ts)||0,i=r?n.filter(e=>Number(e.ts)>=r-2e3):[],a=i.length?i[0]:n[n.length-1];if(!a||!a.schema){Q(`没找到本轮之前的历史快照(可能已被 30 张上限裁掉,或本轮没写入过卡片)`,`err`);return}let o=new Date(Number(a.ts)||Date.now()).toLocaleString(`zh-CN`,{hour12:!1});this.requestConfirm({title:`回滚到 AI 动手之前`,message:`将把卡片「${this.selectedCardName||t}」恢复到 ${o} 的版本,本轮及其后的 ${i.length||1} 张快照都会被撤销。回滚只改当前编辑态,可用 Ctrl+Z 撤回;确认无误后仍需点顶栏「保存到本地」才写盘。`,confirmText:`回滚`,severity:`warn`,onConfirm:()=>this.applyTurnRollback(a)})},applyTurnRollback(e){this.currentSchema&&e&&(this.past.push({schema:this.cloneSchema(this.currentSchema)}),this.past.length>30&&this.past.shift(),this.future=[],this.baseline={schema:this.cloneSchema(e.schema)},this.applySchemaSnapshot({schema:e.schema,data:e.data}),Q(`已回滚到本轮之前的版本(未落盘,可撤销;确认后点「保存到本地」)`,`ok`))},onCardsChanged(e){let t=e&&e.file||``,n=e&&e.type||`change`;if(n===`rename`&&e.oldId&&e.id&&e.oldId!==e.id)return this.applyCardRename(e.oldId,e.id);if(/\.history[\\/]/.test(t)||/data\.json$/i.test(t)||/\.import-meta\.json(\.tmp)?$/i.test(t))return;let r=t.match(/src[\\/]cards[\\/]([^\\/]+)[\\/]/),i=r?r[1]:null;this._ccIds||=new Set,this._ccTypes||={},i&&(this._ccIds.add(i),this._ccTypes[i]=n),!this._ccTimer&&(this._ccTimer=setTimeout(async()=>{this._ccTimer=null;let e=this._ccIds,t=this._ccTypes;if(this._ccIds=new Set,this._ccTypes={},!e.size)return;let n=Array.from(e),r=n[n.length-1];if(e.size===1&&r&&r===this.selectedId){if(this.selfSaveId===r&&Date.now()-this.selfSaveAt<pG){this.selfSaveId=``,this.selfSaveAt=0;return}if(this._restoreSuppressUntil&&Date.now()<this._restoreSuppressUntil){this._restoreSuppressUntil=0;return}if(this.historyDirty)this.promptExternalReload(r);else{let e=this.currentSchema&&this.currentSchema.name||r;this.showExternalReloadNote(this.chatBusy?`AI 助手已写入卡片「${e}」,预览已自动刷新`:`卡片「${e}」已在编辑器外被修改,已自动重新载入预览`),this.loadCard(this.selectedId)}return}if(e.size>1){await this.fetchCardList();let t=e.has(this.selectedId),n=this.selfSaveId===this.selectedId&&Date.now()-this.selfSaveAt<pG;if(t&&!n){if(this.historyDirty)this.promptExternalReload(this.selectedId);else{this.selfSaveId===this.selectedId&&(this.selfSaveId=``,this.selfSaveAt=0);let e=this.currentSchema&&this.currentSchema.name||this.selectedId;this.showExternalReloadNote(this.chatBusy?`AI 助手已写入卡片「${e}」,预览已自动刷新`:`卡片「${e}」已在编辑器外被修改,已自动重新载入预览`),this.loadCard(this.selectedId)}}return}if((t[r]||`change`)===`unlink`){let e=this.cardList.findIndex(e=>e.id===r);e>=0&&(this.cardList.splice(e,1),this.cardCount=this.cardList.length);return}let i=await this.fetchCardMeta(r);if(!i){await this.fetchCardList();return}let a=this.cardList.findIndex(e=>e.id===r);a>=0?this.cardList.splice(a,1,i):this.cardList.push(i),this.cardCount=this.cardList.length},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)}}},EG=[`aria-busy`],DG={class:`app__logo`},OG=[`src`],kG={class:`app__logo-version`},AG={class:`csel-combo`},jG={class:`app__btn-group`},MG=[`disabled`],NG=[`disabled`],PG=[`disabled`],FG=[`title`],IG={class:`app__badge-text`},LG={class:`app__bar-actions`},RG={class:`app__op-group`,role:`group`,"aria-label":`编辑历史与本地操作`},zG=[`disabled`],BG=[`disabled`],VG=[`disabled`,`title`],HG=[`disabled`],UG=[`disabled`,`onClick`],WG={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`},GG={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`},KG={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`},qG={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`},JG={class:`app__remote-pop dd__menu`,role:`menu`},YG=[`title`,`onClick`],XG=[`title`,`onClick`],ZG=[`onClick`],QG=[`title`,`onClick`],$G=[`title`,`onClick`],eK=[`onClick`],tK=[`disabled`,`title`,`aria-pressed`],nK={class:`app__dev-svc`},rK=[`disabled`,`title`],iK={class:`app__dev-svc-head`},aK={class:`app__dev-svc-body`},oK={class:`app__dev-svc-row`},sK={class:`app__dev-svc-row app__dev-svc-row--col`},cK={class:`app__dev-svc-row app__dev-svc-row--col`},lK={key:0,class:`app__dev-svc-hint`},uK={class:`app__dev-svc-actions`},dK=[`title`],fK={style:{display:`none`}},pK=[`title`,`aria-expanded`],mK={key:0,class:`app__nosvc-banner`},hK={key:1,class:`app__nosvc-banner app__nosvc-banner--disconnected`},gK={key:2,class:`app__nosvc-banner app__extreload-banner`},_K={class:`app__stage`,ref:`stageEl`},vK={class:`phone__scaler`},yK=[`title`],bK={class:`canvas-bar__txt`},xK={class:`canvas-bar__chip`,title:`画布宽度预设(高度随列高自适应)`},SK={class:`canvas-bar__txt`},CK={class:`canvas-bar__size`,title:`画布实际尺寸(宽 × 高,高度随窗口自适应)`},wK=[`disabled`],TK=[`data-card-ready`],EK={class:`workside`},DK={class:`workside__cols`,ref:`colsEl`},OK={class:`login-modal modal-card`,role:`dialog`,"aria-modal":`true`},kK={class:`login-modal__row`},AK=[`disabled`],jK={class:`login-modal__row`},MK=[`disabled`],NK={key:0,class:`login-modal__error`},PK={class:`login-modal__actions`},FK=[`disabled`],IK=[`disabled`];function LK(e,t,n,r,i,a){let o=R(`CardSelect`),s=R(`Dropdown`),c=R(`ThemeSettings`),l=R(`Switch`),u=R(`ChatPanel`),d=R(`RenderTree`),f=R(`SelectionOverlay`),p=R(`HoverGuides`),m=R(`AlertCardLayer`),h=R(`DeviceOverlay`),g=R(`PickerOverlay`),_=R(`DatePickerOverlay`),v=R(`KeyboardOverlay`),y=R(`LoadingExOverlay`),b=R(`DebugPanel`),x=R(`ApiCallPanel`),S=R(`RemotePreview`),C=R(`ComponentPalette`),w=R(`CardGallery`),ee=R(`RemoteAllCards`),T=R(`RemoteCardFormDialog`),E=R(`SyncProgressModal`),D=R(`ExportDirDialog`),O=R(`ExportProgressModal`),te=R(`NewCardDialog`),ne=R(`ConfirmDialog`),re=R(`AppToast`),ie=R(`ImageLightbox`);return V(),H(`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`})},[q(` 远端接口调用中(推送 / 拉取 / 切图上传 / 发布):顶栏整体冻结。
364
364
  这些操作都会读写远端,并与本地卡片状态(当前卡、撤销栈、磁盘版本、预览图)交叉;
365
365
  执行期间允许切卡 / 撤销 / 本地保存,极易造成版本互相覆盖或状态错乱。
366
366
  两道保险:① 控件自身 disabled(挡住键盘与焦点路径);② 顶层冻结层吃掉所有指针事件。 `),L(W(`header`,{class:j([`app__bar`,{"app__bar--locked":a.remoteBusy}]),"aria-busy":a.remoteBusy?`true`:`false`},[W(`strong`,DG,[W(`img`,{class:`app__logo-icon`,src:i.logoUrl,alt:``},null,8,OG),t[44]||=K(` CardBuddy `,-1),W(`span`,kG,`v`+M(i.appVersion),1)]),W(`div`,AG,[G(o,{class:`csel--combo`,value:i.selectedId,options:i.cardList,disabled:a.remoteBusy,onChange:a.onSelect},null,8,[`value`,`options`,`disabled`,`onChange`])]),q(` 卡片网格预览:图标按钮,打开本地全部卡片的缩略图墙,点击任一张载入工作台 `),W(`div`,jG,[W(`button`,{class:`app__btn app__btn--icon`,type:`button`,disabled:!i.cardList.length||a.remoteBusy,onClick:t[0]||=(...e)=>a.openGallery&&a.openGallery(...e),title:`卡片网格预览`},[...t[45]||=[W(`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`},[W(`use`,{href:`#icon-grid`})],-1)]],8,MG),q(` 新增空白卡片:紧邻网格预览的图标按钮;弹窗收 ID(必填、本地重名前置拦截)+ 描述(选填),创建成功切换到新卡 `),W(`button`,{class:`app__btn app__btn--icon`,type:`button`,disabled:a.remoteBusy,onClick:t[1]||=(...e)=>a.openNewCard&&a.openNewCard(...e),title:`新增空白卡片`},[...t[46]||=[W(`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`},[W(`use`,{href:`#icon-folder-plus`})],-1)]],8,NG),q(` 打开当前卡片所在文件夹:从卡片下拉里拆出,合并进本组作为第三位 `),W(`button`,{class:`app__btn app__btn--icon`,type:`button`,disabled:!i.selectedId||a.remoteBusy,onClick:t[2]||=(...e)=>a.openCardFolder&&a.openCardFolder(...e),title:`在文件管理器中打开当前卡片所在文件夹`},[...t[47]||=[W(`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`},[W(`use`,{href:`#icon-folder`})],-1)]],8,PG)]),L(W(`div`,{ref:`validateBadgeEl`,class:j([`app__badge`,`app__badge--`+i.validateBadge.state]),title:a.badgeTitle},[t[48]||=W(`span`,{class:`app__badge-dot`},null,-1),W(`span`,IG,M(a.badgeText),1)],10,FG),[[Nc,!i.chromeOff]]),W(`div`,LG,[W(`div`,RG,[q(` 撤销 / 重做:图标 + 文案;无可回退/重做版本时置灰 `),W(`button`,{class:`app__btn app__btn--hist`,type:`button`,disabled:!i.preview||!a.canUndo||a.remoteBusy,onClick:t[3]||=(...e)=>a.undo&&a.undo(...e),title:`后退一步(撤销)`},[...t[49]||=[W(`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`},[W(`use`,{href:`#icon-undo`})],-1),W(`span`,null,`撤销`,-1)]],8,zG),W(`button`,{class:`app__btn app__btn--hist`,type:`button`,disabled:!i.preview||!a.canRedo||a.remoteBusy,onClick:t[4]||=(...e)=>a.redo&&a.redo(...e),title:`前进一步(重做)`},[...t[50]||=[W(`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`},[W(`use`,{href:`#icon-redo`})],-1),W(`span`,null,`重做`,-1)]],8,BG),q(` 保存到本地:当前显示版本(撤销/重做导航到的历史版本,或编辑后未本地保存的版本)
367
367
  与本地磁盘版本不一致时高亮可用;一致时置灰。点击把当前版本真正写回磁盘,
368
368
  使下一次重新打开加载的是这个「真实版本」,而非任意历史记录。
369
- 已登录远端且当前卡有导入来源时,本地写盘成功后自动续调保存接口(IDC10010)
370
- 同步为远端最新版本;未登录/无来源则仅保存到本地。 `),W(`button`,{class:j([`app__btn app__btn--history-save`,{"is-dirty":a.historyDirty}]),type:`button`,disabled:!i.preview||!a.historyDirty||i.savingCard||i.loadingCard||a.remoteBusy,onClick:t[5]||=(...e)=>a.applyHistoryToLocal&&a.applyHistoryToLocal(...e),title:a.historyDirty?i.credentialsReady&&a.hasCardSource?`当前版本与本地不一致,点击保存到本地并同步至远端`:`当前版本与本地不一致,点击将该版本保存到本地`:`当前版本已与本地一致`},[...t[51]||=[W(`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`},[W(`use`,{href:`#icon-save`})],-1),W(`span`,null,`保存`,-1)]],10,VG),q(` 生成预览图:无头渲染当前卡 → 透明底、与卡片实际尺寸 1:1 的 PNG → 覆盖写
369
+ 本按钮**只写本地**,不触发任何远端请求;需要同步远端时由顶栏「远端 → 保存至远端」
370
+ 手动推送(自动续调已按需求移除,避免一次点击产生两处副作用)。 `),W(`button`,{class:j([`app__btn app__btn--history-save`,{"is-dirty":a.historyDirty}]),type:`button`,disabled:!i.preview||!a.historyDirty||i.savingCard||i.loadingCard||a.remoteBusy,onClick:t[5]||=(...e)=>a.applyHistoryToLocal&&a.applyHistoryToLocal(...e),title:a.historyDirty?`当前版本与本地不一致,点击将该版本保存到本地(不同步远端)`:`当前版本已与本地一致`},[...t[51]||=[W(`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`},[W(`use`,{href:`#icon-save`})],-1),W(`span`,null,`保存`,-1)]],10,VG),q(` 生成预览图:无头渲染当前卡 → 透明底、与卡片实际尺寸 1:1 的 PNG → 覆盖写
371
371
  src/cards/<id>/<id>.png(手动搭建 / 生成卡片后补齐封面用)。
372
372
  未保存改动先落盘:服务端渲染的是磁盘态,不先保存会截到旧版。 `),W(`button`,{class:`app__btn app__btn--hist`,type:`button`,disabled:!i.preview||i.coverBusy||i.savingCard||i.loadingCard||a.remoteBusy,onClick:t[6]||=(...e)=>a.generateCardCover&&a.generateCardCover(...e),title:`生成预览图:透明底、与卡片实际尺寸一致,写入 src/cards/<id>/<id>.png(已存在则覆盖)`},[...t[52]||=[W(`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`},[W(`use`,{href:`#icon-image`})],-1),W(`span`,null,`截图`,-1)]],8,HG)]),q(` 远端:合并「远端保存 / 远端预览 / 切图上传 / 全量同步 / 选中卡片同步」为一个分组,组名「远端」,云图标 `),G(s,{class:`app__remote-menu`,size:`md`,placement:`bottom-end`,disabled:a.remoteBusy,title:a.remoteBusy?a.remoteBusyReason+`(顶栏已临时冻结)`:i.credentialsReady?`远端操作:推送 / 卡片发布 / 预览(真机)/ 拉取 / 查看全部 / 切图上传`:`未检测到远端登录凭据:点击先填写账号密码并验证,通过后才能使用远端功能`,"data-testid":`remote-group`},{trigger:I(({open:e,toggle:n})=>[W(`button`,{type:`button`,class:j([`app__btn app__btn--remote`,{"app__btn--warn":i.savedRemote&&i.verifiedRemote===!1,"app__remote-trigger--active":e,"is-processing":a.remoteBusy}]),disabled:a.remoteBusy,onClick:J(e=>a.onRemoteTrigger(n),[`stop`])},[a.remoteBusy?(V(),H(`svg`,WG,[...t[53]||=[W(`use`,{href:`#icon-loading`},null,-1)]])):i.savedRemote&&i.verifiedRemote!==!1?(V(),H(`svg`,GG,[...t[54]||=[W(`use`,{href:`#icon-cloud-check`},null,-1)]])):i.savedRemote&&i.verifiedRemote===!1?(V(),H(`svg`,KG,[...t[55]||=[W(`use`,{href:`#icon-cloud-alert`},null,-1)]])):(V(),H(`svg`,qG,[...t[56]||=[W(`use`,{href:`#icon-cloud`},null,-1)]])),t[58]||=W(`span`,null,`远端`,-1),(V(),H(`svg`,{class:j([`app__icon app__icon--chev`,{"app__icon--chev--open":e}]),viewBox:`0 0 16 16`,width:`12`,height:`12`,"aria-hidden":`true`},[...t[57]||=[W(`use`,{href:`#icon-chevron`},null,-1)]],2))],10,UG)]),default:I(({close:e})=>[W(`ul`,JG,[W(`li`,{class:j([`app__remote-item`,{"is-disabled":!i.preview||!i.selectedId||!a.hasCardSource}]),title:a.hasCardSource?`预览当前卡:生成真机预览二维码(远端 App 扫码)`:`该卡未经「同步远程」导入,无远端来源信息,无法生成真机预览二维码`,"data-testid":`remote-preview`,role:`menuitem`,onClick:t=>{a.chooseRsave(`preview`),e()}},[...t[59]||=[W(`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`},[W(`use`,{href:`#icon-device-mobile`})],-1),W(`span`,null,`真机预览`,-1)]],10,YG),q(` 切图上传:仅点击时执行,把「当前卡片引用到的」src/slices 切图上传到远端图片库
373
373
  (CC0000.do),并把该卡 schema/data 里的 /slices/ 引用改写为返回的 @filestore/ 线上地址。
@@ -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-DL4ZCJCA.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-B6NhNlBx.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-MWKPP9nd.css">
13
13
  </head>
14
14
  <body>
@@ -238,17 +238,19 @@ function convertDirectives(component, template, warnings) {
238
238
  const extend = bucket.extend && typeof bucket.extend === 'object' ? bucket.extend : {}
239
239
  bucket.extend = extend
240
240
 
241
+ // 可见时写空串而非 'block':display 由样式类决定(.ce_row/.ce_column 为 flex),
242
+ // 内联 block 会盖掉 flex,导致 justify/align 失效(同 h5.js applyDirectives 注释)。
241
243
  const expression = conditionMap.expression
242
244
  if (hasText(expression)) {
243
- if (showType === '01') extend.display = `(${expression}) ? 'block' : 'none'`
244
- else if (showType === '02') extend.display = `(${expression}) ? 'none' : 'block'`
245
+ if (showType === '01') extend.display = `(${expression}) ? '' : 'none'`
246
+ else if (showType === '02') extend.display = `(${expression}) ? 'none' : ''`
245
247
  return
246
248
  }
247
249
 
248
250
  if (Array.isArray(conditionMap.group) && conditionMap.group.length > 0) {
249
251
  const expr = convertCondition(conditionMap.group)
250
- if (showType === '01') extend.display = `${expr} ? 'block' : 'none'`
251
- else if (showType === '02') extend.display = `${expr} ? 'none' : 'block'`
252
+ if (showType === '01') extend.display = `${expr} ? '' : 'none'`
253
+ else if (showType === '02') extend.display = `${expr} ? 'none' : ''`
252
254
  return
253
255
  }
254
256
 
@@ -116,6 +116,51 @@ export function collectProps(properties = []) {
116
116
 
117
117
  const IDENT_RE = /^[A-Za-z0-9_$]+$/
118
118
 
119
+ /* ------------------------ fastjson / HashMap 键序 ------------------------ */
120
+
121
+ /** Java String.hashCode */
122
+ function javaStringHash(text) {
123
+ let h = 0
124
+ for (let i = 0; i < text.length; i++) h = (Math.imul(31, h) + text.charCodeAt(i)) | 0
125
+ return h
126
+ }
127
+
128
+ /** HashMap 的扰动函数:h ^ (h >>> 16) */
129
+ function hashSpread(h) {
130
+ return (h ^ (h >>> 16)) >>> 0
131
+ }
132
+
133
+ /** 按元素个数推算 HashMap 表容量(默认 16,负载因子 0.75,翻倍扩容) */
134
+ function hashCapacity(size) {
135
+ let cap = 16
136
+ while (size > cap * 0.75) cap *= 2
137
+ return cap
138
+ }
139
+
140
+ /**
141
+ * 复现 Java HashMap 的迭代顺序
142
+ *
143
+ * 平台产物里的 JSON 由 fastjson 的 `JSONObject`(底层 HashMap)序列化,键序是**哈希桶顺序**
144
+ * 而不是插入顺序。产物只用于「替换线上文件」时,键序语义上无所谓;但只要希望与平台产物
145
+ * 逐字节一致(便于 diff 对拍),就必须复现这个顺序。
146
+ *
147
+ * 规则:cap = 16 起按 0.75 负载因子扩容;桶下标 = (h ^ h>>>16) & (cap-1);
148
+ * 按下标升序输出,同桶内保持插入顺序。
149
+ */
150
+ export function javaHashMapOrder(value) {
151
+ if (Array.isArray(value)) return value.map(javaHashMapOrder)
152
+ if (!value || typeof value !== 'object') return value
153
+ const entries = Object.entries(value)
154
+ const cap = hashCapacity(entries.length)
155
+ return entries
156
+ .map(([k, v], i) => ({ k, v: javaHashMapOrder(v), i, bucket: hashSpread(javaStringHash(k)) & (cap - 1) }))
157
+ .sort((a, b) => a.bucket - b.bucket || a.i - b.i)
158
+ .reduce((acc, x) => {
159
+ acc[x.k] = x.v
160
+ return acc
161
+ }, {})
162
+ }
163
+
119
164
  /**
120
165
  * JS 对象字面量的键名写法(平台口径)
121
166
  *
@@ -143,7 +188,7 @@ export function convertPropsH5(properties = []) {
143
188
  parts.push(`${key}:${convertPropsH5(prop.children)}`)
144
189
  } else if (prop.list && prop.list.length > 0) {
145
190
  const groups = prop.list.map((g) => convertPropsH5(g))
146
- parts.push(`${key}:[${groups.join(',')}]`)
191
+ parts.push(`${key}: [${groups.join(',')}]`)
147
192
  } else {
148
193
  parts.push(`${key}:${JSON.stringify(prop.itemValue)}`)
149
194
  }
@@ -161,7 +206,7 @@ export function convertPropsH5(properties = []) {
161
206
  /**
162
207
  * `$foo` → `s.$foo`
163
208
  *
164
- * 平台卡片状态挂在上下文实例的 `state` 上(生成代码里 `var s = c.state`),
209
+ * 平台卡片状态挂在上下文实例的 `s` 上(生成代码里 `var s = c.s`,ExpressionState),
165
210
  * 所以表达式里的 `$x` 必须改写为 `s.$x`。
166
211
  * 字符串字面量('…' / "…" / `…`)内部的 `$` 不得改写。
167
212
  *
@@ -14,7 +14,8 @@
14
14
  * 元素组件通过 `this.getElement('Text')` 取宿主 elements 注册表。
15
15
  *
16
16
  * 关键契约(照抄平台侧,改动会直接导致真机渲染空白):
17
- * - 数据挂在 `s = c.state`,表达式里的 `$foo` 已改写为 `s.$foo`;
17
+ * - 数据挂在 `s = c.s`(ExpressionState,提供 $key / $$),表达式里的 `$foo` 已改写为 `s.$foo`;
18
+ * 不能用 `c.state` —— 那是 MethodState,只有平铺键名,`s.$foo` 会一律求值为 undefined;
18
19
  * - `data` 里必须同时给 defaultState / state / n(n 是刷新因子,模板里靠它触发重算);
19
20
  * - watch.data 重新合并 state,宿主下发新数据时才能刷新;
20
21
  * - 每个事件的实现体被搬到 methods 里,on 只留转发(避免闭包捕获旧 state)。
@@ -27,6 +28,7 @@ import {
27
28
  convertPropsClient,
28
29
  convertPropsH5,
29
30
  hasText,
31
+ javaHashMapOrder,
30
32
  jsKey,
31
33
  pickVarProps,
32
34
  } from './common.js'
@@ -89,8 +91,12 @@ export function createH5Compiler(card, { warnings = [] } = {}) {
89
91
  for (const child of card.components) slot.components.push(convertComponent(child))
90
92
 
91
93
  function convertComponent(component) {
94
+ const desc = elementOf(component.className)
92
95
  const layer = {
93
- id: component.componentId,
96
+ // 列表 / 网格在平台侧会先被客户端编译改写成 `<cardCode>_sub_<componentId>`(共享同一份
97
+ // CardModel,H5 编译在其后,于是把这个改写后的 id 带进了 ref)。此处等价复现:
98
+ // 只影响容器自身的 ref,循环项内部的组件 id 保持原样。
99
+ id: desc.isNewTemplate ? `${card.code}_sub_${component.componentId}` : component.componentId,
94
100
  name: component.className,
95
101
  style: {},
96
102
  props: convertPropsH5(component.properties),
@@ -148,8 +154,12 @@ export function createH5Compiler(card, { warnings = [] } = {}) {
148
154
  return
149
155
  }
150
156
 
151
- if (showType === '01') layer.style.display = `(${expression}) ? 'block' : 'none'`
152
- else if (showType === '02') layer.style.display = `(${expression}) ? 'none' : 'block'`
157
+ // 可见时不能写死 `block`:布局元素的 display 由样式类决定(`.ce_row`/`.ce_column`
158
+ // 是 flex),内联 `display:block` 会盖掉 flex → justifyContent/alignItems 全失效。
159
+ // accountCreditedlist 的「更多转账伙伴」行就是被它压成 block,文字靠左 + 图标换行被裁掉。
160
+ // 空串等价于 v-show 的「显隐只切 none」——清掉内联值,恢复原生 display。
161
+ if (showType === '01') layer.style.display = `(${expression}) ? '' : 'none'`
162
+ else if (showType === '02') layer.style.display = `(${expression}) ? 'none' : ''`
153
163
  }
154
164
 
155
165
  /**
@@ -190,9 +200,16 @@ export function createH5Compiler(card, { warnings = [] } = {}) {
190
200
  const fn = constProps.function
191
201
  const code = fn && typeof fn === 'object' ? fn.code : null
192
202
  const compiled = fn && typeof fn === 'object' ? fn.compiled : null
193
- // 平台侧优先用 compiled(ES5 转译版),缺失才回退手写 code
194
- if (hasText(compiled)) return compiled
203
+ // 取 code 优先,compiled 只作兜底 —— 与编辑器预览(RenderTree.actionCode)和
204
+ // 运行时(preview 的 actionCode,`code || compiled`)保持同一优先级。
205
+ //
206
+ // 反过来(compiled 优先)会让「编辑器里改好、导出产物还是旧的」:两者是双份数据,
207
+ // compiled 是平台侧某次转译留下的快照,编辑器改 code 时它不会跟着变(SetCode 只覆写 code;
208
+ // ai-chat 的自动同步也只在「原本就同步」时生效)。实例:account 的 onMeasured 标题脚本,
209
+ // code 已是左对齐 `600 17px` / 浅绿 #D8F5E8 / x=0,compiled 仍停在居中的 `bold 16px` /
210
+ // #B9D0FA / x=canvas.width/2,产物跑旧版,标题位置与配色全错。
195
211
  if (hasText(code)) return code
212
+ if (hasText(compiled)) return compiled
196
213
  warnings.push(`事件 ${eventAction.eventKey} 的 customAction 缺少 code,已跳过`)
197
214
  return ''
198
215
  }
@@ -241,9 +258,9 @@ export function createH5Compiler(card, { warnings = [] } = {}) {
241
258
  const code = scopeSlots
242
259
  .map(
243
260
  (s) =>
244
- `${jsKey(s.slotKey)}:function(_sd_){return (function(c){var s = c.state; return [${s.components
261
+ `${jsKey(s.slotKey)}:function(_sd_){return (function(c){var s = c.s; return [${s.components
245
262
  .map(serializeComponent)
246
- .join(',')}]})(new CC(_vm,_sd_,c))}`
263
+ .join(',')}]})(new CC(_vm,_sd_,c)) }`
247
264
  )
248
265
  .join(',')
249
266
  options.push(`scopedSlots:{${code}}`)
@@ -271,7 +288,9 @@ export function createH5Compiler(card, { warnings = [] } = {}) {
271
288
  "var CC = this.getObject('context');" +
272
289
  'var e = this.getElement;' +
273
290
  'var c = new CC(this,this);' +
274
- 'var s = c.state;' +
291
+ // 必须是 c.s(ExpressionState,提供 $key / $$ 读取),不能用 c.state(MethodState,只有平铺键名)。
292
+ // 平台 Context 里两者并存:s 走表达式读、state 走方法写。写成 c.state 会让所有 s.$xxx 求值为 undefined。
293
+ 'var s = c.s;' +
275
294
  'var n = this.n;' +
276
295
  `return ${components.map(serializeComponent).join('')}` +
277
296
  '}'
@@ -286,7 +305,8 @@ export function createH5Compiler(card, { warnings = [] } = {}) {
286
305
  parts.push('props:{data:{type:Object,default:function(){ return {} }}}')
287
306
 
288
307
  const dataCode = [
289
- `defaultState:${JSON.stringify(defaultState)}`,
308
+ // 键序按 Java HashMap 桶序输出:与平台产物逐字节一致(语义等价,仅便于 diff 对拍)
309
+ `defaultState:${JSON.stringify(javaHashMapOrder(defaultState))}`,
290
310
  'state:{}',
291
311
  'n:0',
292
312
  ].join(',')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cardbuddy",
3
- "version": "0.25.0",
3
+ "version": "0.25.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": {