cardbuddy 0.21.0 → 0.21.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.
@@ -337,7 +337,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
337
337
  `:``}[已挂载本地文件(仅本机路径,未上传;需要内容请用工具读取)]\n${e.join(`
338
338
  `)}`}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`?bH(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),BL(e,t,{keepalive:!0}).catch(()=>{})},fmtClock:EI,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(jH)===`1`}catch{}},setCompact(e){let t=!!e;if(this.compactSteps!==t){this.compactSteps=t;try{t?localStorage.setItem(jH,`1`):localStorage.removeItem(jH)}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)}}},zH={class:`aichat__head`},BH={class:`aichat__titles`},VH={class:`aichat__sub`},HH={class:`aichat__acts`},UH=[`title`,`aria-pressed`],WH={viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},GH=[`href`],KH=[`disabled`],qH={key:0,class:`aichat__banner`},JH={class:`aichat__scroll`},YH={key:0,class:`aichat__archnote`,title:`长对话记录超出落盘体积上限时,较早的工具参数与结果会被裁剪归档;正文、思考与过程事件都完整保留`},XH={key:1,class:`aichat__loading`},ZH={key:2,class:`aichat__empty`},QH={class:`aichat__empty-title`},$H={class:`aichat__empty-desc`},eU={class:`aichat__suggests`},tU=[`title`,`data-suggest-id`,`onClick`],nU={viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},rU=[`href`],iU={class:`aichat__suggest-text`},aU={class:`aichat__suggest-label`},oU={class:`aichat__suggest-hint`},sU={key:0,class:`aichat__suggest-badge`,title:`该操作会改动卡片`},cU={class:`aichat__live-text`},lU={key:1,class:`aichat__quick`},uU=[`title`,`data-suggest-id`,`onClick`],dU={viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},fU=[`href`],pU={class:`ni-panel modal-card ccsw-dlg`,role:`dialog`,"aria-modal":`true`,"aria-label":`从 cc-switch 导入模型`},mU={class:`ni-head`},hU={class:`ni-body`},gU={class:`ni-scroll`},_U={key:0,class:`ccsw-dlg__msg`},vU={class:`ccsw-dlg__msg ccsw-dlg__msg--err`},yU={class:`ccsw-dlg__list`},bU=[`onClick`],xU={key:0,viewBox:`0 0 16 16`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.8`,"stroke-linecap":`round`,"stroke-linejoin":`round`},SU={class:`ccsw-dlg__info`},CU={class:`ccsw-dlg__name`},wU={key:0,class:`ccsw-dlg__badge ccsw-dlg__badge--cur`,title:`cc-switch 中当前生效`},TU={key:1,class:`ccsw-dlg__badge ccsw-dlg__badge--dup`,title:`模型库中已存在同名模型`},EU={class:`ccsw-dlg__meta`},DU={class:`ccsw-dlg__model`},OU={class:`ccsw-dlg__host`},kU={class:`ni-foot`},AU={class:`ni-tip`},jU={class:`ni-foot-btns`},MU=[`disabled`];function NU(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`,zH,[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`,BH,[t[23]||=W(`span`,{class:`aichat__title`},`AI 开发助手`,-1),W(`span`,VH,[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`,HH,[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`,WH,[W(`use`,{href:i.compactSteps?`#icon-eye-off`:`#icon-eye`},null,8,GH)]))],10,UH),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,KH),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`,qH,[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(` 消息区:滚动容器与悬浮「回到底部」共处一个定位上下文,按钮只贴消息区底缘,
339
339
  输入区拖高 / 反馈条出现都不会把它盖住或悬空 `),W(`div`,JH,[W(`div`,{ref:`listEl`,class:`aichat__list`,onScroll:t[10]||=(...e)=>a.onScroll&&a.onScroll(...e)},[q(` 体积治理痕迹:较早的工具结果被裁剪过就说清楚,别让人以为记录丢了 `),i.archiveTrimmed?(V(),H(`div`,YH,[...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`,XH,`正在加载对话记录…`)):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(` 面板级进度:步数 / 已用时长 / 建卡阶段 / 最近收到数据多久前。
340
- 长任务里「还活着吗」全靠这一行——旧版只有一句「生成中…」,网关静默与真在跑无从区分 `),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`,cU,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`,ZH,[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`,QH,M(a.emptyTitle),1),W(`p`,$H,M(a.emptyDesc),1),W(`div`,eU,[(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`,nU,[W(`use`,{href:e.icon},null,8,rU)])),W(`span`,iU,[W(`span`,aU,M(e.label),1),W(`span`,oU,M(e.hint),1)]),e.weight===`write`?(V(),H(`span`,sU,`改卡`)):q(`v-if`,!0)],10,tU))),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`,lU,[(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`,dU,[W(`use`,{href:e.icon},null,8,fU)])),W(`span`,null,M(e.label),1)],10,uU))),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:Gr(()=>[i.ccSwitchOpen?(V(),H(`div`,{key:0,class:`ni-overlay`,onClick:t[17]||=J(e=>i.ccSwitchOpen=!1,[`self`])},[W(`div`,pU,[W(`div`,mU,[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`,hU,[W(`div`,gU,[q(` 加载中 `),i.ccSwitchLoading?(V(),H(`p`,_U,`正在读取 cc-switch 供应商…`)):i.ccSwitchReason?(V(),H(B,{key:1},[q(` 无可导入项 `),W(`p`,vU,M(i.ccSwitchReason),1)],2112)):(V(),H(B,{key:2},[q(` 供应商列表 `),W(`ul`,yU,[(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`,xU,[...t[37]||=[W(`use`,{href:`#icon-check`},null,-1)]])):q(`v-if`,!0)],2),W(`span`,SU,[W(`span`,CU,[K(M(e.name||e.model)+` `,1),e.isCurrent?(V(),H(`span`,wU,`当前`)):q(`v-if`,!0),e.duplicate?(V(),H(`span`,TU,`已存在`)):q(`v-if`,!0)]),W(`span`,EU,[W(`span`,DU,M(e.model),1),t[38]||=W(`span`,{class:`ccsw-dlg__dot`},`·`,-1),W(`span`,OU,M(e.baseUrl),1)])])],10,bU))),128))])],2112))])]),W(`div`,kU,[W(`span`,AU,M(a.ccSwitchDupCount>0?`已有 ${a.ccSwitchDupCount} 个重复模型被自动跳过`:`勾选要导入的供应商,批量加入模型库`),1),W(`div`,jU,[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,MU)])])])])):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 PU=Z(RH,[[`render`,NU],[`__file`,`/Users/ynet/dsl-card/card-editor/components/ChatPanel.vue`]]),FU=`/assets/site-icon-CFMif8z4.png`,IU={includeMocks:!0};function LU(e){return e?JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t)):null}function RU(e){let t=LU(e);if(t&&t.dictionary&&Array.isArray(t.dictionary.inputParams)){let e=uv(t);for(let n of t.dictionary.inputParams)n&&typeof n==`object`&&!dv(e,n.key)&&(n.defaultValue=void 0)}return t}function zU(e,t){return!e||!t?e===t:JSON.stringify(RU(e))===JSON.stringify(RU(t))}function BU(e,t){return JSON.stringify(e)===JSON.stringify(t)}function VU(e,t){return!t||!e?!1:!zU(e,t)}function HU(e,t,n){return!t||!e?!1:!BU(e,n??lv(t,IU))}function UU(e,t,n,r){return!t||!e?!1:VU(e,t)||HU(n,t,r)}var WU={includeMocks:!0},GU=2500,KU=8e3,qU=`cardbuddy:remote-cards-cache:v1`,JU=3e5;function YU(){try{let e=JSON.parse(localStorage.getItem(qU)||`null`);if(e&&e.v===1&&Array.isArray(e.cards))return e}catch{}return null}function XU(e){try{localStorage.setItem(qU,JSON.stringify({v:1,at:Date.now(),cards:e}))}catch{}}var ZU=new pu,QU=vu({},ZU);typeof window<`u`&&(window.__rt=QU);var $U=188,eW=320,tW=200,nW=160,rW={name:`App`,components:{RenderTree:vp,DebugPanel:Bx,ApiCallPanel:cS,CardSelect:DS,AlertCardLayer:LS,DeviceOverlay:DD,PickerOverlay:LD,DatePickerOverlay:JD,KeyboardOverlay:eO,LoadingExOverlay:oO,SelectionOverlay:uO,HoverGuides:gO,RemotePreview:ik,AppToast:dk,ImageLightbox:jk,ConfirmDialog:Ap,CardGallery:aA,RemoteAllCards:FA,SyncProgressModal:KA,ExportDirDialog:cj,ExportProgressModal:yj,NewCardDialog:zj,ComponentPalette:eM,Switch:iM,ChatPanel:PU},provide(){return{adapter:ZU,cardRuntime:QU,customComponents:{}}},data(){return{adapter:ZU,rt:QU,logoUrl:FU,appVersion:`0.72.0`,cardList:[],cardCount:0,galleryOpen:!1,remoteAllOpen:!1,remoteCards:[],remoteCardsLoading:!1,remoteCardsRefreshing:!1,remoteCardsError:``,syncJobId:``,syncProgressOpen:!1,syncProgress:{done:0,total:0,current:``},syncPollTimer:null,exportDirDialog:{visible:!1,ids:[]},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`,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?`${DI(e.startedAt)} 开始 · `:``;return`AI 开发助手正在生成(${e?`${t}第 ${e.steps} 步 · 已用 ${EI(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 UU(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||$U;return Math.max(e,Math.min(t,n))},viewportWidth(){return this.stageWidth,typeof window<`u`?window.innerWidth:1280},debugSoftWidth(){return Math.max(tW,Math.floor(this.viewportWidth/3))},previewBounds(){let e=this.stageWidth||0,t=Math.floor(e/2),n=e-eW-this.debugSoftWidth;return{min:$U,max:Math.max($U,Math.min(t,n))}},debugMinWidth(){let e=this.colsWidth||Math.max(0,(this.stageWidth||0)-this.previewWidth);return Math.max(tW,Math.min(this.debugSoftWidth,Math.floor(e)-eW))},apiPanelBounds(){let e=this.colsWidth||Math.max(0,(this.stageWidth||0)-this.previewWidth),t=Math.max(nW,Math.floor(e)-this.debugMinWidth);return{min:Math.min(eW,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=``,pO())},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},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(){vD(QU);let e=new URLSearchParams(location.search).get(`chrome`);this.chromeOff=e===`off`||e===``;try{let e=localStorage.getItem(`cardbuddy:theme`);(e===`light`||e===`dark`)&&(this.theme=e)}catch{}document.documentElement.dataset.theme=this.theme;try{localStorage.getItem(`cardbuddy:api-panel-open`)===`0`&&(this.apiPanelOpen=!1)}catch{}await this.fetchCardList();let t=new URLSearchParams(location.search).get(`card`),n=null;try{let e=localStorage.getItem(`cardbuddy:selected-card`);e&&this.cardList.some(t=>t.id===e)&&(n=e)}catch{}let r=t&&this.cardList.some(e=>e.id===t)?t:n||this.cardList[0]&&this.cardList[0].id;r&&(this.selectedId=r,await this.loadCard(this.selectedId)),this.$watch(()=>this.rt.rootScope,()=>{this.syncDataToSchema()},{deep:!0}),this.setupCardsChanged()},methods:{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 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){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}}},toggleTheme(){this.theme=this.theme===`light`?`dark`:`light`;try{localStorage.setItem(`cardbuddy:theme`,this.theme)}catch{}},toggleApiPanel(){this.apiPanelOpen=!this.apiPanelOpen},async refreshValidateBadge(){let e=this.selectedId;if(e&&this.preview&&(this.validateTimer&&clearTimeout(this.validateTimer),this.validateBadge={...this.validateBadge,state:`pending`},await new Promise(e=>{this.validateTimer=setTimeout(e,120)}),this.selectedId===e))try{let[t,n]=await Promise.all([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(lv(r.schema,WU)),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(lv(r.schema,WU)),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,NS(),yD(),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?lv(t,WU):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||!UU(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?lv(e.schema,WU):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=uv(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=``,pO(),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){pO();return}if(e.leave){pO(e.id);return}let t=typeof document<`u`?document.querySelector(`.phone__screen`):null;if(!t){pO(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?fO(r,e.id):pO(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<JU)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=YU();return e&&(this._remoteCardsMem=e),e},writeRemoteCardsCache(e){let t={v:1,at:Date.now(),cards:e};this._remoteCardsMem=t,XU(e)},closeRemoteAllCards(){this.remoteAllOpen=!1},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:`即将从远端 FinMall 拉取完整卡片列表,并逐张覆盖本地同名卡片。本地独有卡片会保留,但被覆盖卡片的未保存改动将丢失,且无法撤销。是否继续?`,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:`即将从远端 FinMall 拉取当前卡片「${this.selectedId}」的最新版本并覆盖本地。本地未保存的改动将丢失,且无法撤销。是否继续?`,confirmText:`继续同步`,onConfirm:()=>this.syncSelected()})},chooseRsave(e){if(e===`save`){if(!this.preview||!this.selectedId||this.savingRemote||!this.hasCardSource)return;this.requestConfirm({title:`推送当前卡`,message:`即将把本地卡片「${this.selectedId}」的当前版本推送并覆盖到远端 FinMall。线上版本会被立即更新,且该操作不可撤销。推送成功后会为当前卡片截图并上传为远端列表预览图。是否继续?`,confirmText:`继续推送`,severity:`warn`,onConfirm:()=>this.saveToRemote()})}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/)上传到 FinMall 图片库,并把卡片里的 /slices/ 引用替换为线上地址(@filestore/…)。本地切图文件保留不动;内容未变的切图会直接复用已有线上地址,不重复上传。`+(this.historyDirty?`
340
+ 长任务里「还活着吗」全靠这一行——旧版只有一句「生成中…」,网关静默与真在跑无从区分 `),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`,cU,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`,ZH,[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`,QH,M(a.emptyTitle),1),W(`p`,$H,M(a.emptyDesc),1),W(`div`,eU,[(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`,nU,[W(`use`,{href:e.icon},null,8,rU)])),W(`span`,iU,[W(`span`,aU,M(e.label),1),W(`span`,oU,M(e.hint),1)]),e.weight===`write`?(V(),H(`span`,sU,`改卡`)):q(`v-if`,!0)],10,tU))),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`,lU,[(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`,dU,[W(`use`,{href:e.icon},null,8,fU)])),W(`span`,null,M(e.label),1)],10,uU))),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:Gr(()=>[i.ccSwitchOpen?(V(),H(`div`,{key:0,class:`ni-overlay`,onClick:t[17]||=J(e=>i.ccSwitchOpen=!1,[`self`])},[W(`div`,pU,[W(`div`,mU,[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`,hU,[W(`div`,gU,[q(` 加载中 `),i.ccSwitchLoading?(V(),H(`p`,_U,`正在读取 cc-switch 供应商…`)):i.ccSwitchReason?(V(),H(B,{key:1},[q(` 无可导入项 `),W(`p`,vU,M(i.ccSwitchReason),1)],2112)):(V(),H(B,{key:2},[q(` 供应商列表 `),W(`ul`,yU,[(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`,xU,[...t[37]||=[W(`use`,{href:`#icon-check`},null,-1)]])):q(`v-if`,!0)],2),W(`span`,SU,[W(`span`,CU,[K(M(e.name||e.model)+` `,1),e.isCurrent?(V(),H(`span`,wU,`当前`)):q(`v-if`,!0),e.duplicate?(V(),H(`span`,TU,`已存在`)):q(`v-if`,!0)]),W(`span`,EU,[W(`span`,DU,M(e.model),1),t[38]||=W(`span`,{class:`ccsw-dlg__dot`},`·`,-1),W(`span`,OU,M(e.baseUrl),1)])])],10,bU))),128))])],2112))])]),W(`div`,kU,[W(`span`,AU,M(a.ccSwitchDupCount>0?`已有 ${a.ccSwitchDupCount} 个重复模型被自动跳过`:`勾选要导入的供应商,批量加入模型库`),1),W(`div`,jU,[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,MU)])])])])):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 PU=Z(RH,[[`render`,NU],[`__file`,`/Users/ynet/dsl-card/card-editor/components/ChatPanel.vue`]]),FU=`/assets/site-icon-CFMif8z4.png`,IU={includeMocks:!0};function LU(e){return e?JSON.parse(JSON.stringify(e,(e,t)=>e===`_schemaNode`?void 0:t)):null}function RU(e){let t=LU(e);if(t&&t.dictionary&&Array.isArray(t.dictionary.inputParams)){let e=uv(t);for(let n of t.dictionary.inputParams)n&&typeof n==`object`&&!dv(e,n.key)&&(n.defaultValue=void 0)}return t}function zU(e,t){return!e||!t?e===t:JSON.stringify(RU(e))===JSON.stringify(RU(t))}function BU(e,t){return JSON.stringify(e)===JSON.stringify(t)}function VU(e,t){return!t||!e?!1:!zU(e,t)}function HU(e,t,n){return!t||!e?!1:!BU(e,n??lv(t,IU))}function UU(e,t,n,r){return!t||!e?!1:VU(e,t)||HU(n,t,r)}var WU={includeMocks:!0},GU=2500,KU=8e3,qU=`cardbuddy:remote-cards-cache:v1`,JU=3e5;function YU(){try{let e=JSON.parse(localStorage.getItem(qU)||`null`);if(e&&e.v===1&&Array.isArray(e.cards))return e}catch{}return null}function XU(e){try{localStorage.setItem(qU,JSON.stringify({v:1,at:Date.now(),cards:e}))}catch{}}var ZU=new pu,QU=vu({},ZU);typeof window<`u`&&(window.__rt=QU);var $U=188,eW=320,tW=200,nW=160,rW={name:`App`,components:{RenderTree:vp,DebugPanel:Bx,ApiCallPanel:cS,CardSelect:DS,AlertCardLayer:LS,DeviceOverlay:DD,PickerOverlay:LD,DatePickerOverlay:JD,KeyboardOverlay:eO,LoadingExOverlay:oO,SelectionOverlay:uO,HoverGuides:gO,RemotePreview:ik,AppToast:dk,ImageLightbox:jk,ConfirmDialog:Ap,CardGallery:aA,RemoteAllCards:FA,SyncProgressModal:KA,ExportDirDialog:cj,ExportProgressModal:yj,NewCardDialog:zj,ComponentPalette:eM,Switch:iM,ChatPanel:PU},provide(){return{adapter:ZU,cardRuntime:QU,customComponents:{}}},data(){return{adapter:ZU,rt:QU,logoUrl:FU,appVersion:`0.73.2`,cardList:[],cardCount:0,galleryOpen:!1,remoteAllOpen:!1,remoteCards:[],remoteCardsLoading:!1,remoteCardsRefreshing:!1,remoteCardsError:``,syncJobId:``,syncProgressOpen:!1,syncProgress:{done:0,total:0,current:``},syncPollTimer:null,exportDirDialog:{visible:!1,ids:[]},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`,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?`${DI(e.startedAt)} 开始 · `:``;return`AI 开发助手正在生成(${e?`${t}第 ${e.steps} 步 · 已用 ${EI(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 UU(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||$U;return Math.max(e,Math.min(t,n))},viewportWidth(){return this.stageWidth,typeof window<`u`?window.innerWidth:1280},debugSoftWidth(){return Math.max(tW,Math.floor(this.viewportWidth/3))},previewBounds(){let e=this.stageWidth||0,t=Math.floor(e/2),n=e-eW-this.debugSoftWidth;return{min:$U,max:Math.max($U,Math.min(t,n))}},debugMinWidth(){let e=this.colsWidth||Math.max(0,(this.stageWidth||0)-this.previewWidth);return Math.max(tW,Math.min(this.debugSoftWidth,Math.floor(e)-eW))},apiPanelBounds(){let e=this.colsWidth||Math.max(0,(this.stageWidth||0)-this.previewWidth),t=Math.max(nW,Math.floor(e)-this.debugMinWidth);return{min:Math.min(eW,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=``,pO())},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},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(){vD(QU);let e=new URLSearchParams(location.search).get(`chrome`);this.chromeOff=e===`off`||e===``;try{let e=localStorage.getItem(`cardbuddy:theme`);(e===`light`||e===`dark`)&&(this.theme=e)}catch{}document.documentElement.dataset.theme=this.theme;try{localStorage.getItem(`cardbuddy:api-panel-open`)===`0`&&(this.apiPanelOpen=!1)}catch{}await this.fetchCardList();let t=new URLSearchParams(location.search).get(`card`),n=null;try{let e=localStorage.getItem(`cardbuddy:selected-card`);e&&this.cardList.some(t=>t.id===e)&&(n=e)}catch{}let r=t&&this.cardList.some(e=>e.id===t)?t:n||this.cardList[0]&&this.cardList[0].id;r&&(this.selectedId=r,await this.loadCard(this.selectedId)),this.$watch(()=>this.rt.rootScope,()=>{this.syncDataToSchema()},{deep:!0}),this.setupCardsChanged()},methods:{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 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){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}}},toggleTheme(){this.theme=this.theme===`light`?`dark`:`light`;try{localStorage.setItem(`cardbuddy:theme`,this.theme)}catch{}},toggleApiPanel(){this.apiPanelOpen=!this.apiPanelOpen},async refreshValidateBadge(){let e=this.selectedId;if(e&&this.preview&&(this.validateTimer&&clearTimeout(this.validateTimer),this.validateBadge={...this.validateBadge,state:`pending`},await new Promise(e=>{this.validateTimer=setTimeout(e,120)}),this.selectedId===e))try{let[t,n]=await Promise.all([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(lv(r.schema,WU)),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(lv(r.schema,WU)),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,NS(),yD(),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?lv(t,WU):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||!UU(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?lv(e.schema,WU):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=uv(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=``,pO(),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){pO();return}if(e.leave){pO(e.id);return}let t=typeof document<`u`?document.querySelector(`.phone__screen`):null;if(!t){pO(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?fO(r,e.id):pO(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<JU)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=YU();return e&&(this._remoteCardsMem=e),e},writeRemoteCardsCache(e){let t={v:1,at:Date.now(),cards:e};this._remoteCardsMem=t,XU(e)},closeRemoteAllCards(){this.remoteAllOpen=!1},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:`即将从远端 FinMall 拉取完整卡片列表,并逐张覆盖本地同名卡片。本地独有卡片会保留,但被覆盖卡片的未保存改动将丢失,且无法撤销。是否继续?`,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:`即将从远端 FinMall 拉取当前卡片「${this.selectedId}」的最新版本并覆盖本地。本地未保存的改动将丢失,且无法撤销。是否继续?`,confirmText:`继续同步`,onConfirm:()=>this.syncSelected()})},chooseRsave(e){if(e===`save`){if(!this.preview||!this.selectedId||this.savingRemote||!this.hasCardSource)return;this.requestConfirm({title:`推送当前卡`,message:`即将把本地卡片「${this.selectedId}」的当前版本推送并覆盖到远端 FinMall。线上版本会被立即更新,且该操作不可撤销。推送成功后会为当前卡片截图并上传为远端列表预览图。是否继续?`,confirmText:`继续推送`,severity:`warn`,onConfirm:()=>this.saveToRemote()})}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/)上传到 FinMall 图片库,并把卡片里的 /slices/ 引用替换为线上地址(@filestore/…)。本地切图文件保留不动;内容未变的切图会直接复用已有线上地址,不重复上传。`+(this.historyDirty?`
341
341
  当前卡片有未保存的改动,会先保存到本地再执行,避免被随后的重载丢弃。`:``)+`
342
342
  是否继续?`,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||`发布失败`);Q(`已发布版本 ${t.nextVersion}`,`ok`),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(lv(this.currentSchema,WU)),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()+KU,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=EI(e&&e.ms||0),r=DI(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<GU){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<GU;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)}}},iW=[`aria-busy`],aW={class:`app__logo`},oW=[`src`],sW={class:`app__logo-version`},cW={class:`csel-combo`},lW={class:`app__btn-group`},uW=[`disabled`],dW=[`disabled`],fW=[`disabled`],pW=[`title`],mW={class:`app__badge-text`},hW={class:`app__bar-actions`},gW={class:`app__op-group`,role:`group`,"aria-label":`编辑历史与本地操作`},_W=[`disabled`],vW=[`disabled`],yW=[`disabled`,`title`],bW=[`disabled`],xW=[`disabled`,`onClick`],SW={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`},CW={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`},wW={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`},TW={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`},EW={class:`app__remote-pop dd__menu`,role:`menu`},DW=[`title`,`onClick`],OW=[`title`,`onClick`],kW=[`onClick`],AW=[`title`,`onClick`],jW=[`title`,`onClick`],MW=[`onClick`],NW=[`disabled`,`title`,`aria-pressed`],PW=[`aria-checked`,`disabled`,`title`],FW={class:`theme-toggle__track`},IW={class:`theme-toggle__thumb`},LW={key:0,class:`theme-toggle__icon`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},RW={key:1,class:`theme-toggle__icon`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},zW={class:`app__dev-svc`},BW=[`disabled`,`title`],VW={class:`app__dev-svc-head`},HW={class:`app__dev-svc-body`},UW={class:`app__dev-svc-row`},WW={class:`app__dev-svc-row app__dev-svc-row--col`},GW={class:`app__dev-svc-row app__dev-svc-row--col`},KW={key:0,class:`app__dev-svc-hint`},qW={class:`app__dev-svc-actions`},JW=[`title`],YW={style:{display:`none`}},XW=[`title`,`aria-expanded`],ZW={key:0,class:`app__nosvc-banner`},QW={key:1,class:`app__nosvc-banner app__nosvc-banner--disconnected`},$W={key:2,class:`app__nosvc-banner app__extreload-banner`},eG={class:`app__stage`,ref:`stageEl`},tG={class:`phone__scaler`},nG=[`title`],rG={class:`canvas-bar__txt`},iG={class:`canvas-bar__chip`,title:`画布宽度预设(高度随列高自适应)`},aG={class:`canvas-bar__txt`},oG={class:`canvas-bar__size`,title:`画布实际尺寸(宽 × 高,高度随窗口自适应)`},sG=[`disabled`],cG=[`data-card-ready`],lG={class:`workside`},uG={class:`workside__cols`,ref:`colsEl`},dG={class:`login-modal modal-card`,role:`dialog`,"aria-modal":`true`},fG={class:`login-modal__row`},pG=[`disabled`],mG={class:`login-modal__row`},hG=[`disabled`],gG={key:0,class:`login-modal__error`},_G={class:`login-modal__actions`},vG=[`disabled`],yG=[`disabled`];function bG(e,t,n,r,i,a){let o=R(`CardSelect`),s=R(`Dropdown`),c=R(`Switch`),l=R(`ChatPanel`),u=R(`RenderTree`),d=R(`SelectionOverlay`),f=R(`HoverGuides`),p=R(`AlertCardLayer`),m=R(`DeviceOverlay`),h=R(`PickerOverlay`),g=R(`DatePickerOverlay`),_=R(`KeyboardOverlay`),v=R(`LoadingExOverlay`),y=R(`DebugPanel`),b=R(`ApiCallPanel`),x=R(`RemotePreview`),S=R(`ComponentPalette`),C=R(`CardGallery`),w=R(`RemoteAllCards`),ee=R(`SyncProgressModal`),T=R(`ExportDirDialog`),E=R(`ExportProgressModal`),D=R(`NewCardDialog`),O=R(`ConfirmDialog`),te=R(`AppToast`),ne=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(` 远端接口调用中(推送 / 拉取 / 切图上传 / 发布):顶栏整体冻结。
343
343
  这些操作都会读写 FinMall,并与本地卡片状态(当前卡、撤销栈、磁盘版本、预览图)交叉;
@@ -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-CpDojF__.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-CqRhiLWo.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-C8VLzU18.css">
13
13
  </head>
14
14
  <body>
@@ -26,6 +26,12 @@ import { resolveCardDir } from './cardOpenFolder.js'
26
26
  * - 有 `.import-meta.json` 的 remote 快照时沿用远端真值(picUrl/cardClassId/applyScene);
27
27
  * 本地新建卡按同一形态合成 picUrl,cardClassId 留空、applyScene 回落 '1'。
28
28
  * - 写 zip 前跑 verifyCardsJsonInvariants 自检,违例即整单失败。
29
+ *
30
+ * 条目名铁律(2026-10-23 事故后定):**zip 条目名必须全 ASCII**。官方 57 卡大样本
31
+ * 439 个条目里非 ASCII 为 0;中文名一律 percent-encode 成 `%XX`(整段 encodeURIComponent),
32
+ * 而 JSON 里的 picUrl / imageName / localUrl 保持中文原文。后端读 zip 条目名不认 UTF-8
33
+ * 标志位,非 ASCII 名会被解成乱码 → 找不到 JSON 引用的文件 → 导入失败。
34
+ * 写 zip 前跑 verifyZipEntryNames 自检(全 ASCII / 无重名 / 封面条目按编码名命中)。
29
35
  * 任务制端点(与 /__sync 同构:单飞制 + 看门狗 + 轮询 + 取消):
30
36
  * - POST /__export-cards body { cardIds[], targetDir } → { ok, jobId, total }
31
37
  * - GET /__export/progress?jobId → { ok, done, total, currentId, currentTitle, finished, result }
@@ -333,6 +339,72 @@ export function resolveCoverForExport({ remotePicUrl, coverPath }) {
333
339
  return { fileName: localName, picUrl: `${COVER_FILESTORE_PREFIX}/${localName}`, inherited: false }
334
340
  }
335
341
 
342
+ /**
343
+ * zip 条目名转 ASCII:**JSON 保留原名,包内文件名 percent-encode**。
344
+ *
345
+ * 契约依据(官方 57 卡大样本 `2609091710000055.zip`:439 个条目里非 ASCII 为 0):
346
+ * 中文名一律整段 `encodeURIComponent`——`20260519101312_右箭头@3x.png` 落地为
347
+ * `20260519101312_%E5%8F%B3%E7%AE%AD%E5%A4%B4%403x.png`(注意 `@`→`%40`、空格→`%20`),
348
+ * 而 `cards.json`/`<id>.json` 里的 `picUrl`、`imageName`、`localUrl` 全部保持中文原文。
349
+ *
350
+ * 为什么必须这样(2026-10-23 事故):后端(Java)读 zip 条目名不认 UTF-8 语言编码标志位,
351
+ * 非 ASCII 名会被按默认字符集解成乱码 → 解压落盘的文件名与 JSON 里引用的名字对不上 →
352
+ * 导入报错。上一版只落中文原名(旧 §8.1「只落原名一份」)正是这次失败的直接原因。
353
+ *
354
+ * **所有段无条件编码**(不是「命中中文才编码」):官方样本 `20260522173828_VS-44@3x.png`
355
+ * 整段无中文,仍落地为 `20260522173828_VS-44%403x.png`——说明规则是「整段 encodeURIComponent」,
356
+ * 而不是「含非 ASCII 才编码」。纯 ASCII 名里出现 `@`/空格/`#` 同样要编(本地 greetingRecommend
357
+ * 的 `mascotDing@2x.png` 就属这一类)。
358
+ *
359
+ * 幂等:已编码段(本地切图名可能本来就是 %XX 形式)先 decode 再 encode,避免二次编码;
360
+ * 含裸 `%` 无法解码时按原文编码(`%`→`%25`)。
361
+ * @param {string} rel 相对路径(POSIX 分隔符)
362
+ * @returns {string}
363
+ */
364
+ export function zipEntryName(rel) {
365
+ return String(rel)
366
+ .split('/')
367
+ .map((seg) => {
368
+ let raw = seg
369
+ try {
370
+ raw = decodeURIComponent(seg)
371
+ } catch {
372
+ /* 含裸 % / 非法转义:直接按原文编码 */
373
+ }
374
+ return encodeURIComponent(raw)
375
+ })
376
+ .join('/')
377
+ }
378
+
379
+ /**
380
+ * zip 层自检(与后端无关的结构断言):
381
+ * 1. 条目名全 ASCII(官方 439/439 全 ASCII;非 ASCII 名会被后端按错误字符集解成乱码);
382
+ * 2. 无重名(同卡两个不同文件编码后撞名会让其中一份被覆盖);
383
+ * 3. 每张卡的封面以「picUrl 末段编码后」的条目名存在(后端编码后按此名取文件)。
384
+ * @param {{ cardsPO: Array<object>, zipNames: string[] }} o
385
+ * @returns {string[]}
386
+ */
387
+ export function verifyZipEntryNames({ cardsPO, zipNames }) {
388
+ const problems = []
389
+ const names = zipNames || []
390
+ for (const n of names) {
391
+ if (/[^\x00-\x7F]/.test(n)) problems.push(`条目名非 ASCII:${n}`)
392
+ }
393
+ const seen = new Set()
394
+ for (const n of names) {
395
+ if (seen.has(n)) problems.push(`条目名重复:${n}`)
396
+ seen.add(n)
397
+ }
398
+ for (const po of cardsPO || []) {
399
+ if (!po || !po.picUrl) continue
400
+ const id = String(po.id)
401
+ const cover = path.basename(String(po.picUrl).split('?')[0])
402
+ const expect = zipEntryName(`${id}/${cover}`)
403
+ if (!seen.has(expect)) problems.push(`${id}: 封面条目缺失 ${expect}`)
404
+ }
405
+ return problems
406
+ }
407
+
336
408
  /**
337
409
  * 产物自检:cards.json 的每条 cardPO 都能被导入侧无障碍消费。
338
410
  * 只做**与后端无关**的结构断言(不依赖远端服务,纯本地即可证伪):
@@ -607,13 +679,14 @@ async function runExportJob(job, { root, cardIds, targetDir, upstream, viteServe
607
679
  )
608
680
  }
609
681
 
610
- // 收集 zip 条目(按名排序保证确定性),cards.json 殿后(与样本一致)
682
+ // 收集 zip 条目(按名排序保证确定性),cards.json 殿后(与样本一致)。
683
+ // 条目名一律经 zipEntryName 转 ASCII:JSON 里保留原名,包内文件名 percent-encode。
611
684
  const collectEntries = (dir, prefix) => {
612
685
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
613
686
  const full = path.join(dir, entry.name)
614
687
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name
615
688
  if (entry.isDirectory()) collectEntries(full, rel)
616
- else zipEntries.push({ name: rel, data: fs.readFileSync(full) })
689
+ else zipEntries.push({ name: zipEntryName(rel), data: fs.readFileSync(full) })
617
690
  }
618
691
  }
619
692
  collectEntries(outDir, '')
@@ -621,6 +694,18 @@ async function runExportJob(job, { root, cardIds, targetDir, upstream, viteServe
621
694
  fs.writeFileSync(path.join(outDir, 'cards.json'), JSON.stringify(cardsPO), 'utf8')
622
695
  zipEntries.push({ name: 'cards.json', data: Buffer.from(JSON.stringify(cardsPO), 'utf8') })
623
696
 
697
+ // zip 层自检:条目名全 ASCII(对齐官方 439/439)、无重名、每张卡的封面条目按编码名命中
698
+ const zipProblems = verifyZipEntryNames({
699
+ cardsPO,
700
+ zipNames: zipEntries.map((e) => e.name),
701
+ })
702
+ if (zipProblems.length) {
703
+ throw new Error(
704
+ `导出产物 zip 自检未通过(${zipProblems.length} 项):${zipProblems.slice(0, 5).join(';')}` +
705
+ (zipProblems.length > 5 ? ' 等' : '')
706
+ )
707
+ }
708
+
624
709
  const zipPath = path.join(targetDir, `${folderName}.zip`)
625
710
  fs.writeFileSync(zipPath, buildZip(zipEntries))
626
711
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cardbuddy",
3
- "version": "0.21.0",
3
+ "version": "0.21.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": {