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