window.__ModuleLoader__.load({ id: "dsh-plugin-mcn", factory: (require) => { var module = { exports: {} }; var exports = module.exports; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); let react_jsx_runtime = require("react/jsx-runtime"); let react = require("react"); let react_dom = require("react-dom"); //#region plugin css const css = ":root{--mcn-brand:var(--dsw-alias-state-business-primary,#4176e6);--mcn-brand-hover:#3359c9}.mcnNav_entry{flex:0 0 100%;width:100%;min-width:0;height:49px;margin-top:8px;display:flex}.mcnNav_trigger{width:100%;min-height:40px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14);cursor:pointer;touch-action:manipulation;transition:background var(--ds-transition-duration-fast) var(--ds-ease-in-out), transform var(--ds-transition-duration-fast) var(--ds-ease-in-out);background:0 0;border:0;border-radius:12px;align-items:center;gap:8px;padding:0 8px 0 6px;display:flex}.mcnNav_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.mcnNav_triggerActive{background:var(--dsw-specific-sidebar-nav-item-active,var(--dsw-alias-interactive-bg-active));color:var(--mcn-brand)}.mcnNav_trigger:active{transform:scale(.96)}.mcnNav_trigger:focus-visible{outline:2px solid var(--mcn-brand);outline-offset:2px}.mcnNav_rail{flex:0 0 36px;width:36px;height:36px;margin:0}.mcnNav_rail .mcnNav_trigger{border-radius:50%;justify-content:center;width:36px;height:36px;min-height:36px;padding:0}.mcnNav_page{position:relative;background:var(--dsw-alias-bg-base);width:100%;min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex:1;grid-template-rows:minmax(0,1fr);display:grid;overflow:hidden}.mcnNav_header{display:flex;align-items:center;gap:12px;margin-bottom:12px}.mcnNav_title{font-size:16px;font-weight:600;margin:0}.mcnNav_body{display:flex;flex-direction:column;height:100%;min-height:0;padding:16px;box-sizing:border-box;gap:12px}.mcnNav_toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.mcnNav_input{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;padding:6px 10px;font:var(--dsw-font-s-14);background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);min-width:200px}.mcnNav_select{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;padding:6px 8px;font:var(--dsw-font-s-14);background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary)}.mcnNav_btn{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;padding:6px 14px;font:var(--dsw-font-s-14);cursor:pointer;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);transition:background var(--ds-transition-duration-fast) var(--ds-ease-in-out);white-space:nowrap}.mcnNav_btnSm{padding:2px 10px;font-size:12px;line-height:1.8}.mcnNav_btn:hover{background:var(--dsw-alias-interactive-bg-hover)}.mcnNav_btn:disabled{opacity:.45;cursor:not-allowed}.mcnNav_btn:disabled:hover{background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated))}.mcnNav_btnPrimary{background:var(--mcn-brand);color:#fff;border-color:transparent}.mcnNav_btnPrimary:hover{background:var(--mcn-brand-hover,#3359c9)}.mcnNav_spacer{flex:1}.mcnNav_btnIcon{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);cursor:pointer;transition:background var(--ds-transition-duration-fast) var(--ds-ease-in-out)}.mcnNav_btnIcon:hover{background:var(--dsw-alias-interactive-bg-hover)}.mcnNav_btnIcon:disabled{opacity:.5;cursor:not-allowed}.mcnNav_tableWrap{flex:1;min-height:0;overflow:auto;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px}.mcnNav_rankSection{margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));display:flex;flex-direction:column;gap:8px;max-height:780px;min-height:0}.mcnNav_link{color:var(--mcn-brand);text-decoration:none;cursor:pointer;font-weight:500}.mcnNav_link:hover{text-decoration:underline}.mcnNav_dateBtn{border:0;background:transparent;color:var(--dsw-alias-label-secondary);font:var(--dsw-font-s-14);cursor:pointer;padding:6px 8px;border-radius:6px;line-height:1}.mcnNav_dateBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.mcnNav_dateBtn:disabled{opacity:.35;cursor:not-allowed}.mcnNav_table{width:100%;border-collapse:collapse;font:var(--dsw-font-s-14);min-width:720px}.mcnNav_table th{position:sticky;top:0;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));text-align:left;padding:8px 10px;border-bottom:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));font-weight:600;white-space:nowrap;cursor:pointer;user-select:none}.mcnNav_table td{padding:8px 10px;border-bottom:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));vertical-align:top}.mcnNav_table tr:hover{background:var(--dsw-alias-interactive-bg-hover)}.mcnNav_cellName{font-weight:600;white-space:nowrap}.mcnNav_cellTrack{white-space:nowrap}.mcnNav_cellContent{color:var(--dsw-alias-label-secondary);max-width:378px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mcnNav_cellContent a{display:inline-block;max-width:378px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:top}.mcnNav_cellM{white-space:nowrap;text-align:center}.mcnNav_table th.mcnNav_cellM{text-align:center}.mcnNav_pager{display:flex;align-items:center;gap:12px;justify-content:flex-end}.mcnNav_pagerTotal{margin-right:auto;color:var(--dsw-alias-label-secondary);font:var(--dsw-font-s-13)}.mcnNav_pagerInfo{color:var(--dsw-alias-label-secondary);font:var(--dsw-font-s-13)}.mcnNav_back{display:inline-flex;align-items:center;gap:4px;padding:6px 12px;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14);cursor:pointer;transition:background var(--ds-transition-duration-fast) var(--ds-ease-in-out)}.mcnNav_back:hover{background:var(--dsw-alias-interactive-bg-hover)}.mcnNav_mask{position:fixed;inset:0;background:rgba(0,0,0,.4);display:flex;align-items:flex-start;justify-content:center;padding-top:28vh;overflow:auto;z-index:100}.mcnNav_modal{background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));border-radius:12px;padding:20px;min-width:320px;max-width:440px;box-shadow:0 8px 30px rgba(0,0,0,.2)}.mcnNav_modalTitle{font-size:16px;font-weight:600;margin-bottom:12px}.mcnNav_modalText{font:var(--dsw-font-s-14);color:var(--dsw-alias-label-secondary);margin-bottom:16px;line-height:1.6}.mcnNav_modalAccount{display:flex;align-items:center;gap:8px;margin-bottom:12px;padding:8px 12px;background:var(--dsw-alias-interactive-bg-hover);border-radius:8px;font:var(--dsw-font-s-14)}.mcnNav_modalAccountName{font-weight:600}.mcnNav_modalAccountTrack{color:var(--dsw-alias-label-secondary);font-size:12px}.mcnNav_textarea{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;padding:8px 10px;font:var(--dsw-font-s-14);background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);resize:vertical;min-height:80px;margin-bottom:16px}.mcnNav_textarea:focus,.mcnNav_input:focus,.mcnNav_select:focus{outline:none;border-color:var(--mcn-brand);box-shadow:0 0 0 2px rgba(65,118,230,.22)}.mcnNav_modalBtns{display:flex;justify-content:flex-end;align-items:center;gap:8px}.mcnNav_loading{display:flex;align-items:center;justify-content:center;height:100%;color:var(--dsw-alias-label-secondary);font:var(--dsw-font-s-14)}.mcnNav_error{color:#ff6b6b;padding:20px;font:var(--dsw-font-s-14)}.mcnNav_split{display:flex;flex-direction:row;flex:1 1 auto;min-width:0;min-height:0;width:100%;height:100%;overflow:hidden;background:var(--dsw-alias-bg-base)}.mcnNav_split>[data-slot='conversation']{display:contents}.mcnNav_split>[data-slot='conversation']>div{flex:1 1 0%!important;min-width:0!important;min-height:0!important;width:auto!important;height:100%!important;--dsh-chat-content-width:100%!important}.mcnNav_split>.mcnNav_resizer{flex:0 0 5px;height:100%}.mcnNav_split>.mcnNav_panel{flex:0 0 auto;height:100%}.mcnNav_panel{flex:0 0 auto;min-width:0;min-height:0;height:100%;display:flex;flex-direction:column;background:var(--dsw-alias-bg-base);box-shadow:-2px 0 10px rgba(0,0,0,.08)}.mcnNav_panelLeft{box-shadow:2px 0 10px rgba(0,0,0,.08)}.mcnNav_resizer{flex:0 0 5px;cursor:col-resize;background:transparent;z-index:2;position:relative}.mcnNav_resizer:hover,.mcnNav_resizerActive{background:var(--mcn-brand);opacity:.35}.mcnNav_sideBtn{margin-left:auto;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;padding:4px 10px;font:var(--dsw-font-s-14);cursor:pointer;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary)}.mcnNav_themeBtn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);cursor:pointer;font-size:14px;line-height:1}.mcnNav_grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;align-content:start}.mcnNav_stats{display:flex;width:100%;box-sizing:border-box;background:rgba(65,118,230,.07);border-radius:12px;padding:22px 0;margin-bottom:12px}.mcnNav_statItem{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:6px;padding:2px 12px;border-right:1px solid rgba(65,118,230,.18)}.mcnNav_statItem:last-child{border-right:none}.mcnNav_statValue{font-size:28px;font-weight:700;color:var(--mcn-brand,#4176e6);line-height:1.2}.mcnNav_statLabel{font:var(--dsw-font-s-13);color:var(--dsw-alias-label-secondary)}.mcnNav_card{display:flex;flex-direction:column;align-items:center;gap:6px;padding:16px;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:12px;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14);cursor:pointer;text-align:center;transition:background var(--ds-transition-duration-fast) var(--ds-ease-in-out),border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out)}.mcnNav_card:hover{border-color:var(--mcn-brand);background:var(--dsw-alias-interactive-bg-hover)}.mcnNav_cardIcon{font-size:24px;line-height:1}.mcnNav_entryIcon{color:#06b6d4}.mcnNav_titleDot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--mcn-brand);margin-right:7px;vertical-align:middle}.mcnNav_cardTitle{font-size:15px;font-weight:600}.mcnNav_cardDesc{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}.mcnNav_cardPlaceholder{cursor:default;opacity:.55;justify-content:center;border-style:dashed}.mcnNav_cardPlaceholder:hover{border-color:var(--dsw-alias-border-l2,var(--dsw-alias-border));background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated))}.mcnNav_crumb{display:flex;align-items:center;gap:4px;min-width:0;flex:1;font:var(--dsw-font-s-14)}.mcnNav_crumbLink{border:0;background:0 0;color:var(--dsw-alias-label-secondary);font:var(--dsw-font-s-14);cursor:pointer;padding:2px 4px;border-radius:6px}.mcnNav_crumbLink:hover{color:var(--mcn-brand);background:var(--dsw-alias-interactive-bg-hover)}.mcnNav_crumbSep{color:var(--dsw-alias-label-secondary);opacity:.6}.mcnNav_crumbCurrent{font-weight:600;color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14);white-space:nowrap}.mcnNav_close{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);cursor:pointer}.mcnNav_close:hover{background:var(--dsw-alias-interactive-bg-hover)}.mcnNav_rwModal{background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));border-radius:12px;padding:20px;width:min(1805px,94vw);height:calc(100vh - 100px);box-sizing:border-box;display:flex;flex-direction:column;box-shadow:0 8px 30px rgba(0,0,0,.2)}.mcnNav_rwSplit{flex:1;min-height:0;display:flex;gap:14px;margin:6px 0 14px}.mcnNav_rwPane{flex:1;min-width:0;display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;overflow:hidden;position:relative}.mcnNav_rwPaneTitle{padding:8px 12px;font-weight:600;font:var(--dsw-font-s-14);background:var(--dsw-alias-bg-base);border-bottom:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));flex:none}.mcnNav_rwContent{flex:1;min-height:0;overflow:auto;margin:0;padding:12px;font:var(--dsw-font-s-13);line-height:1.7;white-space:normal;word-break:break-word;color:var(--dsw-alias-label-primary)}.mcnNav_spinner{display:inline-block;width:12px;height:12px;border:2px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-top-color:var(--mcn-brand,#4176e6);border-radius:50%;animation:mcnNavSpin .8s linear infinite;vertical-align:-2px;margin-right:5px}.mcnNav_scanning{position:relative;overflow:hidden;background-image:linear-gradient(90deg,rgba(65,118,230,0) 0%,rgba(65,118,230,.22) 50%,rgba(65,118,230,0) 100%);background-size:200% 100%;background-repeat:no-repeat;animation:mcnNavScan 1.3s linear infinite}@keyframes mcnNavScan{0%{background-position:200% 0}100%{background-position:-200% 0}}@keyframes mcnNavSpin{to{transform:rotate(360deg)}}.mcnNav_toast{position:absolute;top:12px;left:50%;transform:translateX(-50%);z-index:120;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));border:1px solid var(--mcn-brand,#4176e6);border-radius:10px;padding:8px 16px;font:var(--dsw-font-s-14);box-shadow:0 8px 30px rgba(0,0,0,.25);max-width:70%;color:var(--dsw-alias-label-primary)}.mcnNav_md{font:var(--dsw-font-s-14);line-height:1.75;color:var(--dsw-alias-label-primary);word-break:break-word}.mcnNav_md h1,.mcnNav_md h2,.mcnNav_md h3,.mcnNav_md h4{margin:18px 0 8px;font-weight:600;line-height:1.4}.mcnNav_md h1{font-size:20px;margin-top:26px}.mcnNav_md h2{font-size:17px;margin-top:24px}.mcnNav_md h3{font-size:15px;margin-top:20px}.mcnNav_md h4{font-size:14px;margin-top:18px}.mcnNav_md p{margin:8px 0}.mcnNav_md ul,.mcnNav_md ol{margin:8px 0;padding-left:22px}.mcnNav_md li{margin:2px 0}.mcnNav_md a{color:var(--dsw-alias-accent-fg);text-decoration:none;border-bottom:1px dashed var(--dsw-alias-accent-fg,transparent)}.mcnNav_md a:hover{text-decoration:underline}.mcnNav_md strong{font-weight:600}.mcnNav_md em{font-style:italic}.mcnNav_md code{font-family:var(--dsw-font-mono,monospace);font-size:.9em;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:4px;padding:1px 5px}.mcnNav_md pre{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;padding:12px;overflow:auto;margin:10px 0;line-height:1.55}.mcnNav_md pre code{background:none;border:none;padding:0;display:block;white-space:pre;font-size:13px}.mcnNav_md blockquote{margin:10px 0;padding:8px 14px;border-left:3px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-base);border-radius:0 8px 8px 0}.mcnNav_md blockquote p{margin:4px 0}.mcnNav_md table{border-collapse:collapse;margin:10px 0;width:100%;font:var(--dsw-font-s-13)}.mcnNav_md th,.mcnNav_md td{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));padding:7px 10px;text-align:left;vertical-align:top;line-height:1.6}.mcnNav_md th{background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));font-weight:600;white-space:nowrap}.mcnNav_md th:first-child,.mcnNav_md td:first-child{width:18%;min-width:96px;max-width:220px;word-break:break-word}.mcnNav_md hr{border:none;border-top:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));margin:14px 0}.mcnNav_tabs{display:flex;align-items:center;gap:6px;border-bottom:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));padding-bottom:6px}.mcnNav_tab{font:var(--dsw-font-s-14);color:var(--dsw-alias-label-secondary,#8a8f98);cursor:pointer;background:0 0;border:0;border-radius:8px;padding:6px 14px;transition:color var(--ds-transition-duration-fast) var(--ds-ease-in-out),background var(--ds-transition-duration-fast) var(--ds-ease-in-out)}.mcnNav_tab:hover{color:var(--mcn-brand);background:var(--dsw-alias-interactive-bg-hover)}.mcnNav_tabActive{color:var(--mcn-brand);font-weight:600;background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover))}"; //#region V1.0 首页复刻样式(亮色令牌硬编码,与 V1.0 style.css 对齐) const v1css = [ ".v1wrap{max-width:960px;margin:0 auto;padding:2px 2px 8px;box-sizing:border-box}", ".v1stats{display:flex;width:100%;box-sizing:border-box;background:rgba(47,111,237,.07);border-radius:12px;padding:16px 0;margin:2px 0 8px}", ".v1stat{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:5px;padding:2px 12px;border-right:1px solid rgba(47,111,237,.16)}", ".v1stat:last-child{border-right:none}", ".v1statV{font-size:26px;font-weight:700;color:#2f6fed;line-height:1.2;font-variant-numeric:tabular-nums}", ".v1statL{font-size:13px;color:#6b7280}", ".v1cards{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:14px 0 16px}", ".v1card{display:flex;flex-direction:column;align-items:flex-start;gap:6px;background:#fff;border:1px solid #e3e6ea;border-radius:12px;padding:16px 18px;cursor:pointer;text-align:left;box-shadow:0 1px 3px rgba(0,0,0,.08);transition:all .15s;font-family:inherit}", ".v1card:hover{border-color:#2f6fed;transform:translateY(-2px);box-shadow:0 4px 12px rgba(47,111,237,.15)}", ".v1cardI{display:inline-flex}", ".v1cardT{font-size:17px;font-weight:600;color:#24292f}", ".v1cardD{font-size:14px;color:#6b7280}", ".v1extra{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:-8px 0 12px}", ".v1extraL{font-size:13px;color:#6b7280}", ".v1chip{border:none;background:#eaf1fe;color:#2f6fed;font-size:13px;padding:4px 12px;border-radius:12px;cursor:pointer;font-family:inherit}", ".v1chip:hover{background:#2f6fed;color:#fff}", ".v1rank{margin:2px 0 18px}", ".v1rkhead{display:flex;align-items:center;gap:6px;border-bottom:1px solid #e3e6ea;margin-bottom:10px;flex-wrap:wrap;min-height:34px}", ".v1rktabs{display:flex;gap:2px;align-items:center}", ".v1rktab{border:none;background:transparent;color:#6b7280;padding:6px 12px 5px;border-radius:8px 8px 0 0;cursor:pointer;font-size:14px;border-bottom:2px solid transparent;font-family:inherit}", ".v1rktab:hover{color:#2f6fed;background:#eaf1fe}", ".v1rktab.on{color:#2f6fed;font-weight:600;background:#eaf1fe;border-bottom:2px solid #2f6fed}", ".v1rkupdate{background:transparent;border:none;color:#6b7280;cursor:pointer;padding:4px 7px;display:inline-flex;align-items:center;border-radius:6px}", ".v1rkupdate:hover{color:#2f6fed}", ".v1rktools{display:flex;align-items:center;gap:6px;margin-left:auto}", ".v1rksel{padding:2px 6px;font-size:13px;border:1px solid #e3e6ea;border-radius:6px;background:#fff;color:#24292f;max-width:112px;font-family:inherit}", ".v1rkbtn{padding:2px 8px;font-size:13px;border:1px solid #e3e6ea;border-radius:6px;background:#fff;color:#24292f;cursor:pointer;font-family:inherit}", ".v1rkbtn:hover{border-color:#2f6fed;color:#2f6fed}", ".v1rkbtn:disabled{opacity:.45;cursor:default}", ".v1rkdate{font-size:14px;color:#6b7280;min-width:56px;text-align:center;white-space:nowrap}", ".v1rklist{border:1px solid #e3e6ea;border-radius:10px;overflow:hidden;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.08)}", ".v1rkrow{display:grid;grid-template-columns:34px 84px minmax(80px,1fr) 76px repeat(4,76px) 76px;align-items:center;gap:6px;padding:6px 10px;font-size:14px;border-bottom:1px solid #e3e6ea}", ".v1rkrow.v1rkvid{grid-template-columns:34px 76px minmax(140px,1fr) minmax(80px,1.1fr) repeat(4,74px)}", ".v1rkrow:last-child{border-bottom:none}", ".v1rkrow:not(.v1rkhead){min-height:40px}", ".v1rkrow button.v1btn{justify-self:center;width:auto}", ".v1rkhead{background:#fff;color:#6b7280;font-weight:600;font-size:13px}", ".v1no{color:#6b7280;font-weight:600}", ".v1cat{color:#6b7280;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}", ".v1name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}", ".v1score{text-align:right;font-weight:600;color:#e8590c}", ".v1num{text-align:right;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}", ".v1act{display:flex;align-items:center;justify-content:center;white-space:nowrap}", ".v1author{color:#6b7280;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}", ".v1tlink{color:inherit;cursor:pointer;text-decoration:none}", ".v1tlink:hover{color:#4a90e2;text-decoration:underline}", ".v1btn{border:1px solid #e3e6ea;background:#fff;color:#24292f;padding:3px 10px;border-radius:6px;font-size:14px;cursor:pointer;text-decoration:none;font-family:inherit}", ".v1btn:hover{border-color:#2f6fed;color:#2f6fed}", ".v1btnview{border-color:#2f6fed;color:#2f6fed;background:#fff}", ".v1btnimp{border-color:#d0d4da;color:#495057}", ".v1rkempty{border:1px dashed #e3e6ea;border-radius:10px;padding:28px 12px;text-align:center;color:#6b7280;background:#fff;font-size:14px}", ".v1recents{margin:2px 0 20px}", ".v1rhead{display:flex;align-items:center;gap:12px;margin-bottom:10px}", ".v1rhead h3{font-size:17px;color:#24292f;margin:0}", ".v1rhead .v1all{font-size:13px;margin-left:auto;margin-right:6px;color:#4a90e2;cursor:pointer;text-decoration:none;border:none;background:none;padding:0;font-family:inherit}", ".v1rlist{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:14px}", ".v1rcard{display:flex;flex-direction:column;align-items:flex-start;gap:6px;background:#fff;border:1px solid #e3e6ea;border-radius:10px;padding:14px 16px;cursor:pointer;text-align:left;min-height:168px;box-shadow:0 1px 3px rgba(0,0,0,.08);transition:all .15s;font-family:inherit}", ".v1rcard:hover{border-color:#2f6fed;box-shadow:0 2px 8px rgba(47,111,237,.12)}", ".v1rcT{font-size:15px;font-weight:600;color:#24292f;width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}", ".v1rcP{width:100%;display:flex;flex-direction:column;gap:2px}", ".v1rcL{display:block;font-size:14px;color:#57606a;line-height:1.5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:100%}", ".v1rcF{width:100%;margin-top:auto;display:flex;align-items:center;gap:8px}", ".v1rcM{font-size:14px;color:#6b7280;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}", ".v1rcS{flex:none;font-size:12px;font-weight:600;padding:2px 8px;border-radius:10px;line-height:1.5}", ".v1mdMeta{font-size:14px;color:#6b7280;margin-bottom:12px}", ".v1mdBody{max-height:66vh;overflow:auto;font-size:15px;color:#24292f;line-height:1.7}", ".v1top{display:flex;align-items:center;gap:6px;justify-content:flex-end;min-height:24px;margin:0 0 2px}", ".v1topbtn{border:1px solid #e3e6ea;background:#fff;color:#6b7280;padding:3px 9px;border-radius:13px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;font-size:13px;font-family:inherit}", ".v1topbtn:hover{border-color:#2f6fed;color:#2f6fed}", ".v1topbtn.on{border-color:#2f6fed;color:#2f6fed;background:#eaf1fe}", ".v1helpTitle{font-size:15px;font-weight:600;color:#24292f;margin:10px 0 2px}", ".v1helpDesc{font-size:13px;color:#6b7280;margin:0 0 6px}", ".v1helpQa{font-size:13px;color:#24292f;line-height:1.9;margin:0 0 8px}", ".v1helpQa b{color:#2f6fed;font-weight:600}", ".mcnNav_mask{align-items:center;padding-top:0;background:rgba(0,0,0,.35)}", ".mcnNav_modal{background:#fff;border-radius:14px;padding:22px 26px;min-width:420px;max-width:92vw;box-shadow:0 8px 30px rgba(0,0,0,.18)}", ".mcnNav_modalTitle{font-size:18px;font-weight:600;margin-bottom:14px}", ".mcnNav_btn{border:1px solid #e3e6ea;background:#fff;color:#24292f;border-radius:8px;padding:6px 14px;font-size:15px;cursor:pointer;font-family:inherit;text-decoration:none}", ".mcnNav_btn:hover{border-color:#2f6fed;color:#2f6fed;background:#fff}", ".mcnNav_btn:disabled{opacity:.5;cursor:not-allowed}", ".mcnNav_btn:disabled:hover{background:#fff;color:#24292f;border-color:#e3e6ea}", ".mcnNav_btnSm{padding:3px 10px;font-size:14px;border-radius:6px;line-height:1.5}", ".mcnNav_btnPrimary{background:#2f6fed;color:#fff;border-color:transparent}", ".mcnNav_btnPrimary:hover{background:#2a5fd0;color:#fff}", ".mcnNav_toast{position:fixed;left:50%;bottom:32px;transform:translateX(-50%) translateY(0);background:#24292f;color:#fff;padding:10px 18px;border-radius:8px;font-size:14px;max-width:70vw;z-index:200;box-shadow:0 4px 16px rgba(0,0,0,.2);word-break:break-all;border:none;top:auto}", ".vvMask{position:fixed;inset:0;background:rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;z-index:1000;padding:16px}", ".vvSmall{background:#fff;border-radius:14px;padding:22px 26px;width:520px;max-width:calc(100vw - 40px);box-shadow:0 8px 30px rgba(0,0,0,.18);max-height:88vh;overflow:auto;box-sizing:border-box}", ".vvSmall h3{font-size:18px;margin-bottom:12px;display:flex;align-items:center;gap:8px;font-weight:600}", ".vvDim{color:#6b7280;font-size:15px;margin-bottom:12px;line-height:1.6}", ".vvTarget{background:#eaf1fe;border-radius:8px;padding:10px 12px;font-size:16px;font-weight:600;color:#2f6fed;margin:8px 0 6px;word-break:break-all}", ".vvUrl{color:#6b7280;font-size:13px;word-break:break-all;padding:0 2px 6px;margin-bottom:6px}", ".vvWarn{color:#b7791f;font-size:14px;margin:4px 0 0;line-height:1.6}", ".vvActions{text-align:right;margin-top:14px;display:flex;justify-content:flex-end;gap:8px}", ".vb{border:1px solid #e3e6ea;background:#fff;color:#24292f;padding:6px 14px;border-radius:8px;cursor:pointer;font-size:15px;text-decoration:none;font-family:inherit;display:inline-flex;align-items:center;gap:5px;line-height:1.4;box-sizing:border-box}", ".vb:hover{border-color:#2f6fed;color:#2f6fed}", ".vb:disabled{opacity:.5;cursor:not-allowed}", ".vb:disabled:hover{border-color:#e3e6ea;color:#24292f}", ".vbSm{padding:3px 10px;font-size:14px;border-radius:6px}", ".vbView{background:#fff;border-color:#2f6fed;color:#2f6fed}", ".vbView:hover{background:#eaf1fe;color:#2f6fed}", ".vbPrimary{background:#2f6fed;color:#fff;border-color:transparent}", ".vbPrimary:hover{background:#2a5fd0;color:#fff}", ".vbDel{background:#fdf0ef;border-color:#f0c9c6;color:#d9534f}", ".vbDel:hover{background:#fbe0de;border-color:#d9534f;color:#d9534f}", ".vbRed{background:#d9534f;color:#fff;border-color:#d9534f}", ".vbRed:hover{background:#c93f3b;color:#fff}", ".vvModal{width:min(1600px,calc(100% - 20px));height:calc(100vh - 40px);max-width:calc(100vw - 20px);box-sizing:border-box;background:#fff;border-radius:14px;box-shadow:0 8px 30px rgba(0,0,0,.18);display:flex;flex-direction:column;overflow:hidden;padding:18px 20px}", ".vvTitleRow{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px;font-size:17px;font-weight:600;color:#24292f;flex:none}", ".vvTitleRow .vvT{display:flex;align-items:center;gap:8px;min-width:0;white-space:pre-wrap;word-break:break-word}", ".vvTitleRow .vvIcon{display:inline-flex;color:#2f6fed;flex:none}", ".vvTitleRow .vvMeta{font-weight:400;color:#6b7280;font-size:15px}", ".vvStack{flex:1;min-height:0;display:flex;flex-direction:row;gap:12px}", ".vvSec{display:flex;flex-direction:column;flex:1;min-width:0;min-height:0;border:1px solid #e3e6ea;border-radius:8px;overflow:hidden;background:#fff}", ".vvSecHead{flex:none;display:flex;align-items:center;gap:10px;padding:8px 12px;font-weight:600;background:#fff;border-bottom:1px solid #e3e6ea;color:#24292f;font-size:14px;min-height:38px}", ".vvSecBody{flex:1;min-height:0;overflow:auto;padding:12px 14px;font-size:14px;color:#24292f;line-height:1.7;background:#fff}", ".vvSecFoot{flex:none;min-height:46px;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:7px 12px;border-top:1px solid #e3e6ea}", ".vvCount{margin-right:auto;color:#6b7280;font-size:13px;white-space:nowrap}", ".vvNewBadge{display:inline-block;margin-left:6px;padding:0 7px;border-radius:9px;background:#2f6fed;color:#fff;font-size:12px;font-weight:600;line-height:17px;vertical-align:1px}", ".vvHidden{display:none}", ".vvSrcOn{background:#e8f1fd;border-color:#4a90e2;color:#2f6fed}", ".vvSideHead{padding:4px 6px;gap:0}", ".vvTabs{display:inline-flex;gap:2px;align-items:center}", ".vvTab{border:none;background:transparent;color:#6b7280;font-size:14px;font-weight:500;padding:5px 12px;border-radius:6px;cursor:pointer;line-height:1.4;font-family:inherit}", ".vvTab:hover{color:#2f6fed}", ".vvTab.on{color:#2f6fed;font-weight:600}", ".vvSep{color:#e3e6ea;font-size:14px;line-height:1;user-select:none}", ".vvSideToggle{margin-left:auto;border:none;background:transparent;color:#6b7280;padding:4px;border-radius:6px;cursor:pointer;display:inline-flex;align-items:center;line-height:1;font-family:inherit}", ".vvSideToggle:hover{color:#2f6fed;background:#eaf1fe}", ".vvSideToggle.off{color:#2f6fed;background:#eaf1fe}", ".vvMd{word-break:break-word}", ".vvMd p{margin:6px 0}", ".vvMd h1,.vvMd h2,.vvMd h3,.vvMd h4{margin:10px 0 6px;color:#24292f}", ".vvMd h1{font-size:19px;border-bottom:1px solid #e3e6ea;padding-bottom:4px}", ".vvMd h2{font-size:17px;border-bottom:1px solid #e3e6ea;padding-bottom:3px}", ".vvMd h3{font-size:15px}", ".vvMd ul,.vvMd ol{margin:6px 0 6px 20px}", ".vvMd pre{background:#f6f8fa;border:1px solid #e3e6ea;border-radius:8px;padding:12px;overflow:auto;font-family:Consolas,monospace;font-size:13px;line-height:1.6;white-space:pre-wrap;word-break:break-word}", ".vvMd code{background:#f0f2f5;padding:1px 5px;border-radius:4px;font-size:13px}", ".vvMd pre code{background:none;padding:0}", ".vvMd blockquote{border-left:3px solid #2f6fed;margin:6px 0;padding:2px 10px;color:#57606a;background:#f6f9ff;border-radius:0 6px 6px 0}", ".vvMd hr{border:none;border-top:1px solid #e3e6ea;margin:10px 0}", ".vvMd strong{font-weight:600}", ".vvScoreBox{padding:10px;border:1px solid #e3e6ea;border-radius:8px;margin-bottom:10px;background:#fff}", ".vvScoreBoxTitle{font-weight:600;margin-bottom:8px;font-size:14px;color:#24292f}", ".vvScoreRow{display:flex;align-items:center;gap:8px;margin-bottom:4px}", ".vvScoreName{width:88px;font-size:13px;color:#6b7280;white-space:nowrap;flex-shrink:0}", ".vvScoreBar{flex:1;height:6px;border-radius:3px;background:#eef1f5;overflow:hidden}", ".vvScoreFill{height:100%}", ".vvScoreVal{width:36px;text-align:right;font-size:13px;font-weight:600;flex-shrink:0}", ".vvIssues{margin-bottom:10px}", ".vvIssueTitle{font-weight:600;margin-bottom:6px;font-size:14px;color:#24292f}", ".vvIssueRow{display:flex;gap:6px;font-size:13px;margin-bottom:4px;color:#24292f;line-height:1.6}", ".vvSummary{font-size:13px;color:#24292f;margin-bottom:8px;line-height:1.7}", ".vvLink{color:#4a90e2;cursor:pointer;font-size:13px;border:none;background:none;padding:0;font-family:inherit;text-decoration:none}", ".vvLink:hover{text-decoration:underline}", ".vvEmpty{color:#6b7280;font-size:14px;padding:14px 2px 10px;line-height:1.6}", ".vvLoading{color:#6b7280;font-size:13px;padding:12px 2px}", ".vvHint{font-size:12px;color:#6b7280;padding:4px 2px 0;line-height:1.6}", ".vvSbBlock{border:1px solid #e3e6ea;border-radius:8px;margin-bottom:8px;overflow:hidden;background:#fff}", ".vvSbHead{display:flex;align-items:center;gap:10px;padding:6px 12px;background:#f6f8fa;border-bottom:1px solid #e3e6ea}", ".vvSbTitle{font-weight:600;color:#2f6fed;font-size:14px}", ".vvSbMeta{margin-left:auto;font-size:13px;color:#6b7280}", ".mcnNav_rankSection .rkAcc thead th:nth-child(n+4):nth-child(-n+9),.mcnNav_rankSection .rkAcc tbody td:nth-child(n+4):nth-child(-n+9){text-align:right}", ".mcnNav_rankSection .rkAcc thead th:nth-child(10),.mcnNav_rankSection .rkAcc tbody td:nth-child(10){text-align:center}", ".mcnNav_page{overflow:auto}", ".mcnNav_page::-webkit-scrollbar{width:10px;height:10px}", ".mcnNav_page::-webkit-scrollbar-thumb{background:rgba(60,70,90,.28);border-radius:6px;border:2px solid transparent;background-clip:content-box}", ".mcnNav_page::-webkit-scrollbar-track{background:transparent}", ".mcnNav_page{overflow:hidden;grid-template-columns:minmax(0,1fr)}", ".mcnNav_body{overflow-y:auto;overflow-x:hidden;min-width:0;scrollbar-width:thin}", ".mcnNav_body::-webkit-scrollbar{width:10px}", ".mcnNav_body::-webkit-scrollbar-thumb{background:rgba(60,70,90,.28);border-radius:6px;border:2px solid transparent;background-clip:content-box}", ".mcnNav_header{position:sticky;top:0;z-index:6;background:var(--dsw-alias-bg-base,#f5f6f8);flex-wrap:wrap}", ".mcnNav_tableWrap{min-width:0}", ".mcnNav_body{overflow:visible}", ".mcnNav_rankSection .rkNum thead th:nth-child(n+5):nth-child(-n+8),.mcnNav_rankSection .rkNum tbody td:nth-child(n+5):nth-child(-n+8){text-align:right}", ".vvSbBody{padding:8px 12px;font-size:13px;color:#24292f;line-height:1.7;white-space:pre-wrap;word-break:break-word}", ].join(""); if (!document.querySelector("style[data-plugin-css=\"mcn-v1\"]")) { const el = document.createElement("style"); el.dataset.plugin = "dsh-plugin-mcn"; el.dataset.pluginCss = "mcn-v1"; el.textContent = v1css; document.head.appendChild(el); } //#endregion const key = "dsh-plugin-mcn/McnNav.module.css"; if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(key) + "]")) { const el = document.createElement("style"); el.dataset.plugin = "dsh-plugin-mcn"; el.dataset.pluginCss = key; el.textContent = css; document.head.appendChild(el); } if (!document.querySelector('style[data-plugin-css="mcn-layout"]')) { const el2 = document.createElement("style"); el2.dataset.pluginCss = "mcn-layout"; el2.textContent = ".mcnNav_page{overflow:hidden;grid-template-columns:minmax(0,1fr)}.mcnNav_body{overflow-y:auto!important;overflow-x:hidden!important;min-width:0;scrollbar-width:thin}.mcnNav_header{position:sticky!important;top:0!important;z-index:6;background:var(--dsw-alias-bg-base,#f5f6f8)}.mcnNav_tableWrap{min-width:0}.tblAiVid thead th:nth-child(n+3):nth-child(-n+6),.tblAiVid tbody td:nth-child(n+3):nth-child(-n+6){text-align:right}.tblAiVid thead th:nth-child(9),.tblAiVid thead th:nth-child(10),.tblAiVid tbody td:nth-child(9),.tblAiVid tbody td:nth-child(10){text-align:center}.tblAiCus thead th:nth-child(3),.tblAiCus tbody td:nth-child(3){text-align:right}.tblAiVid thead th:nth-child(n+3):nth-child(-n+6){color:#e11d48!important}"; document.head.appendChild(el2); } var McnNav_module_css_default = { "entry": "mcnNav_entry", "triggerActive": "mcnNav_triggerActive", "tabs": "mcnNav_tabs", "tab": "mcnNav_tab", "tabActive": "mcnNav_tabActive", "page": "mcnNav_page", "trigger": "mcnNav_trigger", "frameWrap": "mcnNav_frameWrap", "back": "mcnNav_back", "body": "mcnNav_body", "header": "mcnNav_header", "title": "mcnNav_title", "toolbar": "mcnNav_toolbar", "input": "mcnNav_input", "select": "mcnNav_select", "btn": "mcnNav_btn", "btnSm": "mcnNav_btnSm", "btnPrimary": "mcnNav_btnPrimary", "spacer": "mcnNav_spacer", "btnIcon": "mcnNav_btnIcon", "tableWrap": "mcnNav_tableWrap", "table": "mcnNav_table", "cellName": "mcnNav_cellName", "cellTrack": "mcnNav_cellTrack", "cellContent": "mcnNav_cellContent", "cellM": "mcnNav_cellM", "pager": "mcnNav_pager", "pagerInfo": "mcnNav_pagerInfo", "pagerTotal": "mcnNav_pagerTotal", "rankSection": "mcnNav_rankSection", "link": "mcnNav_link", "dateBtn": "mcnNav_dateBtn", "mask": "mcnNav_mask", "modal": "mcnNav_modal", "modalTitle": "mcnNav_modalTitle", "modalText": "mcnNav_modalText", "modalAccount": "mcnNav_modalAccount", "modalAccountName": "mcnNav_modalAccountName", "modalAccountTrack": "mcnNav_modalAccountTrack", "textarea": "mcnNav_textarea", "modalBtns": "mcnNav_modalBtns", "loading": "mcnNav_loading", "error": "mcnNav_error", "rail": "mcnNav_rail", "split": "mcnNav_split", "panel": "mcnNav_panel", "panelLeft": "mcnNav_panelLeft", "resizer": "mcnNav_resizer", "resizerActive": "mcnNav_resizerActive", "sideBtn": "mcnNav_sideBtn", "themeBtn": "mcnNav_themeBtn", "grid": "mcnNav_grid", "stats": "mcnNav_stats", "statItem": "mcnNav_statItem", "statValue": "mcnNav_statValue", "statLabel": "mcnNav_statLabel", "card": "mcnNav_card", "cardIcon": "mcnNav_cardIcon", "entryIcon": "mcnNav_entryIcon", "titleDot": "mcnNav_titleDot", "cardTitle": "mcnNav_cardTitle", "cardDesc": "mcnNav_cardDesc", "cardPlaceholder": "mcnNav_cardPlaceholder", "crumb": "mcnNav_crumb", "crumbLink": "mcnNav_crumbLink", "crumbSep": "mcnNav_crumbSep", "crumbCurrent": "mcnNav_crumbCurrent", "close": "mcnNav_close", "rwModal": "mcnNav_rwModal", "rwSplit": "mcnNav_rwSplit", "rwPane": "mcnNav_rwPane", "rwPaneTitle": "mcnNav_rwPaneTitle", "rwContent": "mcnNav_rwContent", "md": "mcnNav_md", "spinner": "mcnNav_spinner", "scanning": "mcnNav_scanning", "toast": "mcnNav_toast" }; //#endregion //#region McnNav // 用 hash 路由(/#/mcn):DSH 静态服务无 SPA fallback,pathname 路由直接访问/刷新会 404; // hash 只在客户端变化,服务端始终返回 / 的 index.html const mcnPath = "/#/mcn"; function isMcnRoute() { return window.location.hash === "#/mcn"; } // 统一线性 SVG 图标(Lucide 风格:24 viewBox、stroke currentColor、圆角端点) function SvgBase({ d, size = 18, color }) { return (0, react_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: size, height: size, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", style: color ? { color } : undefined, children: d }); } function IconSun({ size }) { return (0, react_jsx_runtime.jsx)(SvgBase, { size, color: "#f59e0b", d: [(0, react_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "4" }), (0, react_jsx_runtime.jsx)("path", { d: "M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" })] }); } function IconMoon({ size }) { return (0, react_jsx_runtime.jsx)(SvgBase, { size, color: "#6366f1", d: [(0, react_jsx_runtime.jsx)("path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" })] }); } function IconPen({ size }) { return (0, react_jsx_runtime.jsx)(SvgBase, { size, color: "#ff7a45", d: [(0, react_jsx_runtime.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }), (0, react_jsx_runtime.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }), (0, react_jsx_runtime.jsx)("path", { d: "M10 9H8" }), (0, react_jsx_runtime.jsx)("path", { d: "M16 13H8" }), (0, react_jsx_runtime.jsx)("path", { d: "M16 17H8" })] }); } function IconSparkles({ size }) { return (0, react_jsx_runtime.jsx)(SvgBase, { size, color: "#8b5cf6", d: [(0, react_jsx_runtime.jsx)("path", { d: "M9.9 2.4 11.2 6l3.6 1.3-3.6 1.3-1.3 3.6-1.3-3.6L4.9 7.3 8.5 6 9.9 2.4Z" }), (0, react_jsx_runtime.jsx)("path", { d: "M17.5 12.5 18.4 15l2.5.9-2.5.9-.9 2.5-.9-2.5-2.5-.9 2.5-.9.9-2.5Z" }), (0, react_jsx_runtime.jsx)("path", { d: "M5 14.5 5.7 16.5 7.7 17.2 5.7 17.9 5 19.9 4.3 17.9 2.3 17.2 4.3 16.5 5 14.5Z" })] }); } function McnIcon() { return (0, react_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "18", height: "18", "aria-hidden": "true", style: { color: "var(--mcn-brand)" }, children: (0, react_jsx_runtime.jsx)("path", { d: "M14.4 3.5c.2 1.6 1.1 3 2.4 3.9l-1 2.3c-1.7-.9-3-2.3-3.8-4.1L14.4 3.5Zm3.5 5.2c.3 1.4 1.5 2.4 3 2.4l.1 2.5c-2.6-.1-4.9-1.4-6.2-3.4l3.1-1.5ZM4 5.5h6.3c.2 1.3 1 2.4 2.1 3.1l-1 2.4c-1.9-1.1-3.3-2.8-4-4.9L4 5.5Zm7.4 5.6h6.5c.2 1.2 1 2.3 2.1 3l-1 2.4c-1.8-1-3.2-2.7-3.9-4.6l-3.7-.8ZM4 12.4h5.9c.1 1.1.8 2 1.8 2.6l-.9 2.3c-1.5-.9-2.6-2.3-3.1-4l-3.7-.9Zm6.8 3.2h5.9c.1 1 .8 1.9 1.7 2.4l-.9 2.3c-1.3-.8-2.3-2-2.7-3.4l-4-.9-.6-.4Z", fill: "currentColor" }) }); } function McnNavTrigger({ wide, useSessions, openSession }) { const sessions = useSessions((state) => state); const [open, setOpen] = (0, react.useState)(isMcnRoute); const [pageHost, setPageHost] = (0, react.useState)(null); const splitRef = (0, react.useRef)(null); const sideRef = (0, react.useRef)("right"); const widthRef = (0, react.useRef)(480); const [panelSide, setPanelSide] = (0, react.useState)(() => { try { return localStorage.getItem("mcnNav.panelSide") === "left" ? "left" : "right"; } catch (e) { return "right"; } }); const [panelWidth, setPanelWidth] = (0, react.useState)(() => { // 百分比宽度:默认 50%(对话区/面板各半),范围 30%~70%(各自最少保留 30%) try { const w = parseFloat(localStorage.getItem("mcnNav.panelWidth") || "50"); return Number.isFinite(w) && w >= 30 && w <= 70 ? w : 50; } catch (e) { return 50; } }); sideRef.current = panelSide; widthRef.current = panelWidth; const previousSession = (0, react.useRef)(sessions.current); (0, react.useEffect)(() => { if (!wide) return; const host = document.querySelector("[data-slot=\"sidebar.footer.action\"]")?.parentElement; if (host === void 0 || host === null) return; const previous = host.style.flexWrap; host.style.flexWrap = "wrap"; return () => { host.style.flexWrap = previous; }; }, [wide]); (0, react.useEffect)(() => { const syncRoute = () => { const next = isMcnRoute(); if (next) previousSession.current = sessions.current; else if (open && sessions.current === void 0 && previousSession.current !== void 0) openSession(previousSession.current); setOpen(next); }; window.addEventListener("popstate", syncRoute); return () => window.removeEventListener("popstate", syncRoute); }, [ open, openSession, sessions.current ]); const applySide = (next) => { const s = splitRef.current; if (s !== null && s.wrapper.isConnected) { const conv = s.conversation; if (next === "left") { // 顺序: [panel, resizer, conversation] s.wrapper.insertBefore(s.panel, conv); s.wrapper.insertBefore(s.resizer, conv); } else { // 顺序: [conversation, resizer, panel] s.wrapper.insertBefore(conv, s.wrapper.firstChild); s.wrapper.insertBefore(s.resizer, conv.nextSibling); s.wrapper.appendChild(s.panel); } s.panel.classList.toggle(McnNav_module_css_default.panelLeft, next === "left"); } setPanelSide(next); sideRef.current = next; try { localStorage.setItem("mcnNav.panelSide", next); } catch (e) {} }; const startResize = (e) => { e.preventDefault(); const startX = e.clientX; const startW = widthRef.current; const side = sideRef.current; const s0 = splitRef.current; if (s0 !== null) s0.resizer.classList.add(McnNav_module_css_default.resizerActive); let cur = startW; const onMove = (ev) => { // 分隔条跟随鼠标;宽度按百分比:对话区与面板各自最少 30% const s = splitRef.current; if (s === null) return; const wrapW = s.wrapper.getBoundingClientRect().width || 1; const deltaPx = side === "left" ? (ev.clientX - startX) : (startX - ev.clientX); const deltaPct = (deltaPx / wrapW) * 100; cur = Math.min(70, Math.max(30, startW + deltaPct)); s.panel.style.width = cur + "%"; setPanelWidth(cur); }; const onUp = () => { document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); const s = splitRef.current; if (s !== null) s.resizer.classList.remove(McnNav_module_css_default.resizerActive); try { localStorage.setItem("mcnNav.panelWidth", String(cur)); } catch (e) {} }; document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onUp); }; (0, react.useEffect)(() => { if (!open) { setPageHost(null); return; } const conversation = document.querySelector("[data-slot=\"conversation\"]"); const host = conversation?.parentElement; if (conversation === void 0 || conversation === null || host === void 0 || host === null) return; let s = splitRef.current; if (s === null) { const previousCss = conversation.style.cssText; // 嵌入式分栏:conversation 移入 flex-row wrapper,与面板同流左右排列 // flex 布局全部由 CSS 规则驱动(.mcnNav_split 及子选择器 !important), // 避免 React 重渲染对话区时重置 inline style 导致 flex 丢失 const wrapper = document.createElement("div"); wrapper.setAttribute("data-mcn-split", "1"); wrapper.className = McnNav_module_css_default.split; host.insertBefore(wrapper, conversation); wrapper.appendChild(conversation); // 对话区铺满与 flex 伸缩均由 CSS 规则驱动(见 .mcnNav_split 子选择器) const resizer = document.createElement("div"); resizer.setAttribute("data-mcn-resizer", "1"); resizer.className = McnNav_module_css_default.resizer; resizer.addEventListener("mousedown", startResize); const panel = document.createElement("div"); panel.setAttribute("data-mcn-panel", "1"); panel.className = McnNav_module_css_default.panel; panel.style.width = panelWidth + "%"; wrapper.append(resizer, panel); s = { wrapper, panel, resizer, conversation, host, previousCss }; splitRef.current = s; } applySide(panelSide); setPageHost(s.panel); return () => { const cur = splitRef.current; if (cur !== null && cur.wrapper.isConnected) { cur.host.insertBefore(cur.conversation, cur.wrapper); cur.conversation.style.cssText = cur.previousCss; cur.wrapper.remove(); } splitRef.current = null; }; }, [open]); const showMcn = () => { if (open) { closeMcn(); return; } previousSession.current = sessions.current; window.history.pushState({ ...window.history.state, mcn: true }, "", mcnPath); setOpen(true); }; // 暴露"打开工作台"给独立入口(如 AI会话 侧边栏项):只打开不切换视图 (0, react.useEffect)(() => { window.__mcnNavOpen = () => { if (!open) { previousSession.current = sessions.current; window.history.pushState({ ...window.history.state, mcn: true }, "", mcnPath); setOpen(true); } }; return () => { if (window.__mcnNavOpen) delete window.__mcnNavOpen; }; /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [open]); const closeMcn = () => { // 清空 hash 回到根路径(replaceState 会移除 hash 段) window.history.replaceState(window.history.state, "", "/"); setOpen(false); if (sessions.current === void 0 && previousSession.current !== void 0) openSession(previousSession.current); }; return (0, react_jsx_runtime.jsxs)("div", { className: `${McnNav_module_css_default.entry} ${wide ? "" : McnNav_module_css_default.rail}`, children: [(0, react_jsx_runtime.jsxs)("button", { type: "button", className: `${McnNav_module_css_default.trigger} ${open ? McnNav_module_css_default.triggerActive : ""}`, "aria-label": "MCN工作台", "aria-current": open ? "page" : void 0, onClick: showMcn, children: [(0, react_jsx_runtime.jsx)(McnIcon, {}), wide && (0, react_jsx_runtime.jsx)("span", { children: "MCN工作台" })] }), open && pageHost !== null && (0, react_dom.createPortal)((0, react_jsx_runtime.jsx)(McnPage, { onClose: closeMcn, side: panelSide, onToggleSide: () => applySide(panelSide === "left" ? "right" : "left") }), pageHost)] }); } /** 独立侧边栏入口:MCN任务会话(打开 MCN 工作台并切到 MCN任务会话 视图) */ function AiSessionsTrigger({ wide }) { const openAiSessions = () => { try { window.__mcnNavOpen?.(); } catch (e) { /* 打开失败忽略 */ } setTimeout(() => { try { if (window.__mcnNav) window.__mcnNav.openView("ai-sessions"); } catch (e) {} }, 60); }; return (0, react_jsx_runtime.jsxs)("div", { className: `${McnNav_module_css_default.entry} ${wide ? "" : McnNav_module_css_default.rail}`, children: [(0, react_jsx_runtime.jsxs)("button", { type: "button", className: McnNav_module_css_default.trigger, "aria-label": "MCN任务会话", title: "查看插件创建的 MCN 任务会话", onClick: openAiSessions, children: [(0, react_jsx_runtime.jsx)("span", { children: "💬" }), wide && (0, react_jsx_runtime.jsx)("span", { children: "MCN任务会话" })] })] }); } //#endregion //#region 极简 markdown 渲染(无第三方依赖,渲染为 React 元素避免 XSS;移植自 dsh-plugin-douyin-account-detail) const INLINE_RE = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\[[^\]]+\]\([^)]+\))/g; function textWithBreaks(seg, keyBase) { if (!seg.includes("\n")) return [seg]; const parts = seg.split("\n"); const out = []; parts.forEach((p, i) => { if (p) out.push(p); if (i < parts.length - 1) out.push(react.createElement("br", { key: keyBase + "-br" + i })); }); return out; } function parseInline(text, keyBase) { if (!text) return [text]; const nodes = []; let last = 0, k = 0; for (const m of text.matchAll(INLINE_RE)) { if (m.index > last) nodes.push(...textWithBreaks(text.slice(last, m.index), keyBase + "-t" + k)); const tok = m[0]; if (tok.startsWith("**") && tok.endsWith("**") && tok.length > 4) { nodes.push(react.createElement("strong", { key: keyBase + "-b" + k }, tok.slice(2, -2))); } else if (tok.startsWith("*") && tok.endsWith("*") && tok.length > 2) { nodes.push(react.createElement("em", { key: keyBase + "-i" + k }, tok.slice(1, -1))); } else if (tok.startsWith("`") && tok.endsWith("`") && tok.length > 2) { nodes.push(react.createElement("code", { key: keyBase + "-c" + k }, tok.slice(1, -1))); } else if (tok.startsWith("[")) { const mm = tok.match(/^\[([^\]]+)\]\(([^)]+)\)$/); const href = mm && /^(https?:|mailto:)/i.test(mm[2]) ? mm[2] : null; nodes.push(href ? react.createElement("a", { key: keyBase + "-a" + k, href: href, target: "_blank", rel: "noopener noreferrer" }, mm[1]) : (mm ? mm[1] : tok)); } else { nodes.push(tok); } last = m.index + tok.length; k++; } if (last < text.length) nodes.push(...textWithBreaks(text.slice(last), keyBase + "-t" + k)); return nodes; } function mdTable(rows, keyBase) { const split = (r) => r.replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim()); const head = split(rows[0]); const body = rows.slice(1).map(split); const firstColStyle = { width: "72px", maxWidth: "72px", minWidth: 0, wordBreak: "break-word" }; return react.createElement("table", { key: keyBase, style: { width: "100%" } }, react.createElement("thead", null, react.createElement("tr", null, head.map((h, i) => react.createElement("th", { key: "h" + i, style: i === 0 ? firstColStyle : undefined }, parseInline(h, keyBase + "-h" + i))))), react.createElement("tbody", null, body.map((row, ri) => react.createElement("tr", { key: "r" + ri }, row.map((c, ci) => react.createElement("td", { key: "c" + ci, style: ci === 0 ? firstColStyle : undefined }, parseInline(c, keyBase + "-c" + ri + "-" + ci))))))); } function mdToReact(md) { if (typeof md !== "string" || !md.trim()) return null; const lines = md.replace(/\r\n/g, "\n").split("\n"); const out = []; let i = 0, k = 0; while (i < lines.length) { const t = lines[i].trim(); const fence = t.match(/^```(\S*)\s*$/); if (fence) { const buf = []; i++; while (i < lines.length && !/^```\s*$/.test(lines[i].trim())) { buf.push(lines[i]); i++; } i++; out.push(react.createElement("pre", { key: "md" + k++ }, react.createElement("code", null, buf.join("\n")))); continue; } const h = t.match(/^(#{1,4})\s+(.*)$/); if (h) { out.push(react.createElement("h" + h[1].length, { key: "md" + k++ }, parseInline(h[2], "h" + k))); i++; continue; } if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(t)) { out.push(react.createElement("hr", { key: "md" + k++ })); i++; continue; } if (t.startsWith(">")) { const buf = []; while (i < lines.length && lines[i].trim().startsWith(">")) { buf.push(lines[i].trim().replace(/^>\s?/, "")); i++; } out.push(react.createElement("blockquote", { key: "md" + k++ }, mdToReact(buf.join("\n")))); continue; } if (t.includes("|") && i + 1 < lines.length && /^\s*\|?[\s:|-]+\|?\s*$/.test(lines[i + 1]) && lines[i + 1].includes("-")) { const sep = lines[i + 1].replace(/\s/g, ""); if (/^\|?(:?-+:?\|)+:?-+:?\|?$/.test(sep)) { const buf = [t]; i += 2; while (i < lines.length && lines[i].trim().includes("|")) { buf.push(lines[i].trim()); i++; } out.push(mdTable(buf, "md" + k++)); continue; } } const ul = t.match(/^[-*+]\s+(.*)$/); if (ul) { const items = []; while (i < lines.length) { const mt = lines[i].trim().match(/^[-*+]\s+(.*)$/); if (mt) { items.push(mt[1]); i++; continue; } break; } out.push(react.createElement("ul", { key: "md" + k++ }, items.map((it, ii) => react.createElement("li", { key: ii }, parseInline(it, "li" + k + "-" + ii))))); continue; } const ol = t.match(/^\d+[.)]\s+(.*)$/); if (ol) { const items = []; while (i < lines.length) { const mt = lines[i].trim().match(/^\d+[.)]\s+(.*)$/); if (mt) { items.push(mt[1]); i++; continue; } break; } out.push(react.createElement("ol", { key: "md" + k++ }, items.map((it, ii) => react.createElement("li", { key: ii }, parseInline(it, "li" + k + "-" + ii))))); continue; } if (t === "") { i++; continue; } const para = [t]; i++; while (i < lines.length) { const nt = lines[i].trim(); if (nt === "" || /^(#{1,4}\s|```|>|[-*+]\s|\d+[.)]\s|.*\|)/.test(nt) || /^(-{3,}|\*{3,}|_{3,})\s*$/.test(nt)) break; para.push(nt); i++; } out.push(react.createElement("p", { key: "md" + k++ }, parseInline(para.join("\n"), "p" + k))); } return out; } //#endregion //#region AI写作提升:全部视频列表(已解析优先 → 点赞降序) function AiVideosPanel({ videos, total, page, loading, search, parsed, onSearch, onParsedChange, onPage, onBack, onCreate, onAnalyze, onRewritesChanged, days, account, onDaysChange, onClearAccount }) { const totalPages = Math.max(1, Math.ceil(total / 20)); // 分镜提示词分块渲染:按 `---` 分隔成「分镜」块;每块拆分为 header(分镜号/标题/时间段/总秒数)+ content(4层结构内容) // 首行格式:段N(开始-结束秒)|标题(如「段1(0-6秒)|场1:二爸家卧室·深夜」) const renderStoryboardBlocks = (text) => { const blocks = String(text || "").split(/\n\s*---+\s*\n/).map((b) => b.trim()).filter(Boolean); if (blocks.length <= 1) return mdToReact(text || ""); return blocks.map((b, bi) => { const lines = b.split("\n"); const first = lines[0].trim(); const m = /^段\s*(\d+)\s*[((]\s*([\d.]+)\s*-\s*([\d.]+)\s*秒\s*[))]\s*[||]\s*(.+)$/.exec(first); let header = null; let content = b; if (m) { const num = m[1]; const start = parseFloat(m[2]); const end = parseFloat(m[3]); const title = m[4].trim(); const total = (Number.isFinite(start) && Number.isFinite(end)) ? (end - start) : NaN; header = { num, title, range: `${m[2]} - ${m[3]}秒`, total: Number.isFinite(total) ? `${total}秒` : null, }; content = lines.slice(1).join("\n").trim(); } return (0, react_jsx_runtime.jsxs)("div", { style: { border: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", borderRadius: 8, marginBottom: 8, overflow: "hidden", background: "var(--dsw-alias-bg-base)" }, children: [ (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "6px 12px", background: "var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated))", borderBottom: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", flexWrap: "wrap" }, children: [ (0, react_jsx_runtime.jsx)("span", { style: { fontWeight: 600, color: "var(--mcn-brand,#4176e6)", whiteSpace: "nowrap" }, children: header && header.title ? header.title : `场${header ? header.num : bi + 1}` }), (0, react_jsx_runtime.jsx)("span", { style: { marginLeft: "auto", display: "flex", gap: 10, fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)", whiteSpace: "nowrap" }, children: [(0, react_jsx_runtime.jsx)("span", { children: header ? header.range : "" }), header && header.total && (0, react_jsx_runtime.jsx)("span", { children: `总${header.total}` })] }) ] }), (0, react_jsx_runtime.jsx)("div", { style: { padding: "8px 12px" }, children: mdToReact(content) }) ] }, "sb" + bi); }); }; // 功能 TAB:rewrite=参考选题创作(现有列表+按账号预筛);custom=原创选题创作(本页实现);abc=ABC融合创作(待开发) const [tab, setTab] = (0, react.useState)("rewrite"); const TAB_TITLES = { rewrite: "参考选题创作", custom: "原创选题创作", abc: "ABC融合创作" }; // ===== 原创选题创作中心(custom tab):列表/徽标/查看/复盘/删除(本组件自管理) ===== const custAcc = account && (account.name || account.account_name) ? String(account.name || account.account_name) : ""; // 服务端 custom-scripts/rewrite-stats 按账号名过滤 const [custStats, setCustStats] = (0, react.useState)({ videoScripts: 0, customScripts: 0 }); const [custRows, setCustRows] = (0, react.useState)([]); const [custTotal, setCustTotal] = (0, react.useState)(0); const [custPage, setCustPage] = (0, react.useState)(1); const [custLoading, setCustLoading] = (0, react.useState)(true); // 初始 true:首次进入 custom tab 先显示加载态 // 原创脚本单列查看弹窗 / 删除确认 / 提交复盘 const [csDetail, setCsDetail] = (0, react.useState)(null); const [csDetailData, setCsDetailData] = (0, react.useState)(null); const [csDel, setCsDel] = (0, react.useState)(null); const [csBusy, setCsBusy] = (0, react.useState)(false); const [cToast, setCToast] = (0, react.useState)(null); const custLandRef = (0, react.useRef)(""); (0, react.useEffect)(() => { if (!cToast) return; const t = setTimeout(() => setCToast(null), 3000); return () => clearTimeout(t); }, [cToast]); const custFmtTime = (t) => String(t || "").slice(0, 16).replace("T", " "); const loadCustStats = () => { fetch("/mcn/api/rewrite-stats?account=" + encodeURIComponent(custAcc)) .then((r) => r.json()) .then((d) => { if (d.ok) setCustStats({ videoScripts: d.videoScripts || 0, customScripts: d.customScripts || 0 }); }) .catch(() => {}); }; const loadCustom = (p, silent) => { const pageNo = p || 1; if (!silent) setCustLoading(true); fetch("/mcn/api/custom-scripts?search=" + encodeURIComponent(search || "") + "&days=" + encodeURIComponent(String(days || "")) + "&account=" + encodeURIComponent(custAcc) + "&page=" + pageNo + "&pageSize=20") .then((r) => r.json()) .then((d) => { if (d.ok) { setCustRows(d.items || []); setCustTotal(d.total || 0); setCustPage(d.page || 1); } }) .catch(() => {}) .finally(() => { if (!silent) setCustLoading(false); }); }; // 徽标计数:scope 变化(含清空)都拉一次 (0, react.useEffect)(() => { loadCustStats(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [custAcc]); // custom 列表数据:切到 custom 或 筛选(搜索/天数/账号)变化时刷新 (0, react.useEffect)(() => { if (tab !== "custom") return; loadCustom(1); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [tab, search, days, custAcc]); // 账号级入口:该账号已有原创脚本 → 初始落在 custom tab(每个账号上下文仅一次) (0, react.useEffect)(() => { if (custAcc && custStats.customScripts > 0 && custLandRef.current !== custAcc) { custLandRef.current = custAcc; setTab("custom"); } }, [custStats, custAcc]); const openCsDetail = (row) => { setCsDetail(row); setCsDetailData(null); fetch("/mcn/api/custom-script?id=" + row.id) .then((r) => r.json()) .then((d) => { setCsDetailData(d.ok && d.script ? d.script : null); }) .catch(() => setCsDetailData(null)); }; const csStartReview = () => { if (!csDetail || csBusy) return; setCsBusy(true); fetch("/mcn/api/review/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ logId: csDetail.id, type: "ai" }), }) .then((r) => r.json()) .then((d) => { if (!d.ok) throw new Error(d.error || "提交失败"); setCToast(d.message || "已提交 AI 脚本复盘"); loadCustom(custPage, true); }) .catch((e) => setCToast("提交失败: " + e.message)) .finally(() => setCsBusy(false)); }; const csConfirmDelete = () => { if (!csDel) return; const id = csDel.id; setCsDel(null); fetch("/mcn/api/rewrite/delete", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id }), }) .then((r) => r.json()) .then((d) => { if (!d.ok) { setCToast(d.error || "删除失败"); return; } setCToast("已删除原创脚本"); if (csDetail && csDetail.id === id) { setCsDetail(null); setCsDetailData(null); } setCustRows((prev) => prev.filter((x) => x.id !== id)); loadCustom(custPage, true); loadCustStats(); }) .catch(() => setCToast("删除失败,请重试")); }; const fmt = (v) => (v == null || v === "") ? "-" : String(v); const fmtDuration = (v) => { const s = Number(v); if (!Number.isFinite(s) || s <= 0) return "-"; const m = Math.floor(s / 60); const sec = Math.round(s % 60); return m > 0 ? `${m}分${sec}秒` : `${sec}秒`; }; // 改写对比弹窗:detail=当前视频;detailData={source, rewrites, storyboards};verIdx=改写版本索引(0=最新);sbVerIdx=分镜提示词版本索引(0=最新) const [detail, setDetail] = (0, react.useState)(null); const [detailData, setDetailData] = (0, react.useState)(null); const [verIdx, setVerIdx] = (0, react.useState)(0); const [sbVerIdx, setSbVerIdx] = (0, react.useState)(0); // 当前 AI脚本版本对应的分镜提示词列表(按 rewrite_id 过滤,切换 AI脚本版本时分镜内容联动) const curRewrite = detailData && detailData.rewrites && detailData.rewrites[verIdx] ? detailData.rewrites[verIdx] : null; const visibleStoryboards = ((detailData && detailData.storyboards) || []).filter((s) => !curRewrite || s.rewrite_id === curRewrite.id); // 渲染用分镜索引(防越界:切版本/删除后 visibleStoryboards 长度变化) const sbIdx = Math.min(sbVerIdx, Math.max(0, visibleStoryboards.length - 1)); // 切换 AI脚本版本时回到该版本分镜的第一条 (0, react.useEffect)(() => { setSbVerIdx(0); }, [verIdx]); // 解析中状态(videoId):点击「未解析」后置位,防重复点击;列表刷新后已解析则显示已解析 const [analyzing, setAnalyzing] = (0, react.useState)(null); const startAnalyze = (row) => { setAnalyzing(row.id); onAnalyze(row); }; const openDetail = (row) => { setDetail(row); setDetailData(null); setVerIdx(0); setSbVerIdx(0); fetch("/mcn/api/video/script?videoId=" + row.id) .then((r) => r.json()) .then((d) => { if (d.ok) setDetailData((prev) => ({ ...prev, source: d.source, rewrites: d.rewrites || [] })); }) .catch(() => setDetailData((prev) => ({ ...prev, source: null, rewrites: [] }))); // 分镜提示词列表(并行加载,供右侧「AI分镜提示词」面板展示与版本切换) fetch("/mcn/api/storyboard/list?videoId=" + row.id) .then((r) => r.json()) .then((d) => { if (d.ok) setDetailData((prev) => ({ ...prev, storyboards: d.items || [] })); }) .catch(() => setDetailData((prev) => ({ ...prev, storyboards: [] }))); }; // 分镜提示词生成确认:confirmStoryboardRow={rewriteId, videoTitle}(null 关闭);确认后提交生成并刷新分镜列表 const [confirmStoryboardRow, setConfirmStoryboardRow] = (0, react.useState)(null); // 分镜提示词生成中:sbBusy=true 时「AI提示词」按钮禁用并显示「生成中…」;轮询检测到新 done 版本后恢复 const [sbBusy, setSbBusy] = (0, react.useState)(false); const sbPollRef = (0, react.useRef)(null); const sbBaseCountRef = (0, react.useRef)(0); const stopSbPoll = () => { if (sbPollRef.current) { clearInterval(sbPollRef.current); sbPollRef.current = null; } }; const refreshStoryboards = (videoId) => { return fetch("/mcn/api/storyboard/list?videoId=" + videoId) .then((r) => r.json()) .then((dd) => { if (dd.ok) setDetailData((prev) => ({ ...prev, storyboards: dd.items || [] })); return dd; }) .catch(() => null); }; const confirmStoryboard = () => { if (!confirmStoryboardRow) return; fetch("/mcn/api/storyboard/create", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ rewriteId: confirmStoryboardRow.rewriteId }), }) .then((r) => r.json()) .then((d) => { setConfirmStoryboardRow(null); if (!d.ok) throw new Error(d.error || "生成失败"); // 进入生成中:按钮禁用;轮询直到出现新的 done 版本(任务完成)后恢复 if (detail) { const videoId = detail.id; sbBaseCountRef.current = (detailData && detailData.storyboards || []).length; setSbBusy(true); refreshStoryboards(videoId); stopSbPoll(); sbPollRef.current = setInterval(() => { refreshStoryboards(videoId).then((dd) => { if (dd && dd.ok) { const done = (dd.items || []).filter((x) => x.status === "done" && x.script_text); if (done.length > sbBaseCountRef.current) { stopSbPoll(); setSbBusy(false); setSbVerIdx(0); } } }); }, 5000); } }) .catch((e) => { setConfirmStoryboardRow(null); }); }; // 弹窗关闭/组件卸载时停止轮询并重置生成中状态(避免重开弹窗时按钮残留「生成中…」禁用) (0, react.useEffect)(() => { if (detail === null) { stopSbPoll(); setSbBusy(false); } return () => stopSbPoll(); }, [detail]); // 分镜提示词删除确认:storyboardDeleteConfirm={id}(null 关闭);确认后删除当前查看的版本并刷新 const [storyboardDeleteConfirm, setStoryboardDeleteConfirm] = (0, react.useState)(null); const confirmStoryboardDelete = () => { if (!storyboardDeleteConfirm) return; fetch("/mcn/api/storyboard/delete", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: storyboardDeleteConfirm.id }), }) .then((r) => r.json()) .then((d) => { setStoryboardDeleteConfirm(null); if (!d.ok) throw new Error(d.error || "删除失败"); const removedId = storyboardDeleteConfirm.id; setDetailData((prev) => { const storyboards = (prev?.storyboards || []).filter((s) => s.id !== removedId); setSbVerIdx((v) => Math.max(0, Math.min(v, storyboards.length - 1))); return { ...prev, storyboards }; }); }) .catch(() => { setStoryboardDeleteConfirm(null); }); }; // 改写确认:confirmRow=待确认改写的视频(null 关闭);确认后执行改写并关闭对比弹窗 const [confirmRow, setConfirmRow] = (0, react.useState)(null); // 重新解析确认:reanalyzeRow=待确认重新解析的视频(null 关闭) const [reanalyzeRow, setReanalyzeRow] = (0, react.useState)(null); // 未解析确认:unParseRow=待确认触发解析的视频(null 关闭) const [unParseRow, setUnParseRow] = (0, react.useState)(null); // 删除改写脚本确认:deleteConfirm={id}(null 关闭) const [deleteConfirm, setDeleteConfirm] = (0, react.useState)(null); const confirmDelete = () => { if (!deleteConfirm) return; fetch("/mcn/api/rewrite/delete", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: deleteConfirm.id }), }) .then((r) => r.json()) .then((d) => { if (!d.ok) throw new Error(d.error || "删除失败"); const removedId = deleteConfirm.id; setDeleteConfirm(null); setDetailData((prev) => { const rewrites = (prev?.rewrites || []).filter((r) => r.id !== removedId); setVerIdx((v) => Math.max(0, Math.min(v, rewrites.length - 1))); return { ...prev, rewrites }; }); if (onRewritesChanged) onRewritesChanged(); }) .catch(() => { setDeleteConfirm(null); }); }; const confirmRewrite = () => { if (!confirmRow) return; onCreate(confirmRow); setConfirmRow(null); if (detail) setDetail(null); }; return (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.body, children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.toolbar, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, title: "返回工作台", onClick: onBack, children: "←" }), (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.title, style: { fontSize: 16, fontWeight: 600 }, children: "AI写脚本" })] }), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.tabs, style: { flexWrap: "wrap" }, children: [[["rewrite", "参考选题创作"], ["custom", "原创选题创作"], ["abc", "ABC融合创作"]].map(([id, label]) => { const badgeCnt = id === "rewrite" ? custStats.videoScripts : (id === "custom" ? custStats.customScripts : 0); return (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.tab + (tab === id ? " " + McnNav_module_css_default.tabActive : ""), onClick: () => setTab(id), children: [(0, react_jsx_runtime.jsx)("span", { children: label }), id !== "abc" && (0, react_jsx_runtime.jsx)("span", { style: { marginLeft: 4, padding: "0 6px", borderRadius: 9, fontSize: 11, lineHeight: "16px", minWidth: 16, textAlign: "center", background: "var(--mcn-brand,#4176e6)", color: "#fff" }, children: badgeCnt })] }, id); }), (0, react_jsx_runtime.jsxs)("div", { style: { marginLeft: "auto", flex: "1 1 0%", minWidth: 0, display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", justifyContent: "flex-end" }, children: [account && (0, react_jsx_runtime.jsx)("span", { style: { display: "inline-flex", alignItems: "center", gap: 6, padding: "1px 4px 1px 10px", borderRadius: 14, border: "1px solid var(--mcn-brand,#4176e6)", fontSize: 12, color: "var(--mcn-brand,#4176e6)", maxWidth: 260 }, children: [(0, react_jsx_runtime.jsx)("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", maxWidth: 150 }, children: (account.name || ("#" + account.id)) + " 的视频/原创" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, style: { border: 0, background: "transparent", color: "var(--mcn-brand,#4176e6)" }, title: "清除账号筛选", onClick: onClearAccount, children: "清除筛选" })] }), (0, react_jsx_runtime.jsx)("input", { className: McnNav_module_css_default.input, placeholder: "搜索视频标题 / 账号", value: search, onChange: (e) => onSearch(e.target.value) }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, onClick: () => onSearch(search), children: "搜索" }), (0, react_jsx_runtime.jsx)("select", { className: McnNav_module_css_default.select, value: String(days || ""), title: "发布时间范围", onChange: (e) => onDaysChange(e.target.value), children: [(0, react_jsx_runtime.jsx)("option", { value: "", children: "全部时间" }), (0, react_jsx_runtime.jsx)("option", { value: "30", children: "近一月" }), (0, react_jsx_runtime.jsx)("option", { value: "90", children: "近三月" }), (0, react_jsx_runtime.jsx)("option", { value: "180", children: "近半年" }), (0, react_jsx_runtime.jsx)("option", { value: "365", children: "近一年" })] }), (0, react_jsx_runtime.jsx)("select", { className: McnNav_module_css_default.select, value: parsed || "", title: "解析状态筛选", onChange: (e) => onParsedChange(e.target.value), children: [(0, react_jsx_runtime.jsx)("option", { value: "", children: "全部状态" }), (0, react_jsx_runtime.jsx)("option", { value: "1", children: "已解析" }), (0, react_jsx_runtime.jsx)("option", { value: "0", children: "未解析" })] })] }), (0, react_jsx_runtime.jsx)("div", { style: { flexBasis: "100%", fontSize: 12, color: "#6b7280", margin: "0 2px 8px", lineHeight: 1.6 }, children: tab === "rewrite" ? (custAcc ? "账号「" + custAcc + "」· 参考选题创作:以该账号视频为选题来源,行尾「AI创作」= 基于该视频创作新脚本(完成后自动保存并挂到该视频下)" : "参考选题创作:以库中视频为选题来源(关注账号优先 · 按发布时间倒序),行尾「AI创作」= 基于该视频创作新脚本(完成后自动保存并挂到该视频下)") : (tab === "custom" ? (custAcc ? "账号「" + custAcc + "」· 原创选题创作:无原视频的脚本(来自账号列表「AI创作」弹窗等),保存后自动入库显示在这里" : "原创选题创作:无原视频的脚本(来自账号列表 / 账号详情「AI创作」弹窗等),保存后自动入库显示在这里") : "ABC 融合创作:将同账号多条视频的爆款元素融合成一条全新脚本。功能开发中,敬请期待。") })] }), tab === "rewrite" && (loading ? react.createElement("div", { className: McnNav_module_css_default.loading }, "加载中…") : videos.length === 0 ? react.createElement("div", { className: McnNav_module_css_default.empty, style: { padding: 26, textAlign: "center", color: "#6b7280" } }, "暂无视频数据") : (() => { const tmpl = "110px minmax(150px,1fr) 70px 70px 70px 70px 116px 96px 96px 108px"; const CS = (al, clip) => ({ padding: "9px 10px", borderBottom: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", textAlign: al || "left", minWidth: 0, whiteSpace: "nowrap", overflow: clip ? "hidden" : undefined, textOverflow: clip ? "ellipsis" : undefined, boxSizing: "border-box" }); const H = (t, al, i) => react.createElement("div", { key: "h" + i, style: Object.assign(CS(al), { color: "var(--dsw-alias-label-secondary,#6b7280)", fontWeight: 600, fontSize: 13 }) }, t); const heads = ["账号", "视频标题", "点赞", "评论", "分享", "时长", "发布时间", "状态", "AI脚本", "操作"].map((t, i) => H(t, i >= 2 && i <= 5 ? "right" : i >= 7 ? "center" : "left", i)); const rowEl = (row) => react.createElement("div", { key: row.id, style: { display: "grid", gridTemplateColumns: tmpl, alignItems: "center" } }, react.createElement("div", { style: CS("left", true) }, react.createElement("span", { className: McnNav_module_css_default.link, title: "查看账号详情", style: { cursor: "pointer" }, onClick: () => { try { window.__mcnNav && window.__mcnNav.open("douyin-account-detail", { accountId: row.account_id, accountName: row.account_name }); } catch (e) {} } }, fmt(row.account_name))), react.createElement("div", { style: CS("left", true) }, react.createElement("span", { className: McnNav_module_css_default.link, title: "查看视频详情", style: { cursor: "pointer", color: "var(--mcn-brand,#4176e6)" }, onClick: () => { try { window.__mcnNav && window.__mcnNav.open("douyin-video-detail", { videoId: row.id, awemeId: row.aweme_id, accountId: row.account_id }); } catch (e) {} } }, fmt(row.video_title))), react.createElement("div", { style: CS("right") }, fmt(row.like_display || row.like_count)), react.createElement("div", { style: CS("right") }, fmt(row.comment_count)), react.createElement("div", { style: CS("right") }, fmt(row.share_count)), react.createElement("div", { style: CS("right") }, fmtDuration(row.duration)), react.createElement("div", { style: CS("left") }, fmt(row.publish_time)), react.createElement("div", { style: CS("center") }, (row.has_source || row.has_analysis) ? ((row.analyzing || analyzing === row.id) ? react.createElement("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm + " " + McnNav_module_css_default.scanning, disabled: true, title: "视频解析中,请勿重复提交" }, "解析中") : react.createElement("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, style: { color: "var(--dsw-alias-state-success-primary,#22c55e)", fontWeight: 600 }, title: "点击可重新解析该视频", onClick: () => setReanalyzeRow(row) }, "已解析")) : (row.analyzing || analyzing === row.id) ? react.createElement("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm + " " + McnNav_module_css_default.scanning, disabled: true, title: "视频解析中,请勿重复提交" }, "解析中") : react.createElement("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, title: "触发视频解析", onClick: () => setUnParseRow(row) }, "未解析")), react.createElement("div", { style: CS("center") }, react.createElement("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, disabled: !(row.rewrite_count > 0), title: (row.rewrite_count > 0) ? "查看该视频下的 AI 脚本(源脚本对比/分镜)" : "暂无 AI 脚本", onClick: () => openDetail(row) }, (row.rewrite_count > 0) ? (String(row.rewrite_count) + " 查看") : "—")), react.createElement("div", { style: CS("center") }, (row.has_source || row.has_analysis) ? (row.rewrite_running ? react.createElement("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm + " " + McnNav_module_css_default.scanning, disabled: true, title: "AI 正在改写中,请勿重复提交" }, "执行中") : react.createElement("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, title: "基于账号设定与视频选题改写全新 S9 故事脚本", onClick: () => setConfirmRow(row) }, "AI创作")) : react.createElement("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, disabled: true, title: "需先完成视频解析后才能创作" }, "AI创作"))); return react.createElement("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 } }, react.createElement("div", { style: { display: "grid", gridTemplateColumns: tmpl, alignItems: "center", background: "var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated))" } }, heads), videos.map((row) => rowEl(row))); })()), tab === "rewrite" && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.pager, children: [(0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.pagerTotal, children: `共 ${total} 条` }), (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.pagerInfo, children: `第 ${page} / ${totalPages} 页` }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: page <= 1 || loading, onClick: () => onPage(page - 1), children: "上一页" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: page >= totalPages || loading, onClick: () => onPage(page + 1), children: "下一页" })] }), tab === "custom" && (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [(custLoading ? react.createElement("div", { className: McnNav_module_css_default.loading }, "加载中…") : custRows.length === 0 ? react.createElement("div", { className: McnNav_module_css_default.empty, style: { padding: 26, textAlign: "center", color: "#6b7280" } }, "暂无原创选题脚本(可从账号列表/账号详情行内 AI创作 创作,完成后自动入库)") : (() => { const tmpl = "120px minmax(150px,1fr) 70px 140px 110px"; const CS = (al, clip) => ({ padding: "9px 10px", borderBottom: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", textAlign: al || "left", minWidth: 0, whiteSpace: "nowrap", overflow: clip ? "hidden" : undefined, textOverflow: clip ? "ellipsis" : undefined, boxSizing: "border-box" }); const H = (t, al, i) => react.createElement("div", { key: "h" + i, style: Object.assign(CS(al), { color: "var(--dsw-alias-label-secondary,#6b7280)", fontWeight: 600, fontSize: 13 }) }, t); const heads = ["账号", "脚本标题", "字数", "创建时间", "操作"].map((t, i) => H(t, i === 2 ? "right" : i === 4 ? "center" : "left", i)); const rowEl = (row) => react.createElement("div", { key: row.id, style: { display: "grid", gridTemplateColumns: tmpl, alignItems: "center" } }, react.createElement("div", { style: CS("left", true) }, row.account_name || "-"), react.createElement("div", { style: CS("left", true) }, row.title ? react.createElement("span", { className: McnNav_module_css_default.link, title: "查看原创选题脚本", style: { cursor: "pointer", color: "var(--mcn-brand,#4176e6)" }, onClick: () => openCsDetail(row) }, row.title) : "-"), react.createElement("div", { style: CS("right") }, row.script_len ? row.script_len : "-"), react.createElement("div", { style: CS("left") }, custFmtTime(row.created_time)), react.createElement("div", { style: CS("center") }, react.createElement("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm + " " + McnNav_module_css_default.btnPrimary, onClick: () => openCsDetail(row), title: "查看完整脚本(含复盘/删除入口)" }, "查看"))); return react.createElement("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 } }, react.createElement("div", { style: { display: "grid", gridTemplateColumns: tmpl, alignItems: "center", background: "var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated))" } }, heads), custRows.map((row) => rowEl(row))); })()) ,, !custLoading && custRows.length > 0 && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.pager, children: [(0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.pagerTotal, children: `共 ${custTotal} 条` }), (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.pagerInfo, children: `第 ${custPage} / ${Math.max(1, Math.ceil(custTotal / 20))} 页` }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: custPage <= 1 || custLoading, onClick: () => loadCustom(custPage - 1), children: "上一页" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: custPage >= Math.max(1, Math.ceil(custTotal / 20)) || custLoading, onClick: () => loadCustom(custPage + 1), children: "下一页" })] }), csDetail && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { paddingTop: "50px" }, onClick: () => setCsDetail(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.rwModal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "8px" }, children: [(0, react_jsx_runtime.jsx)("span", { children: "原创选题脚本" + (csDetail && csDetail.title ? " — " + csDetail.title : "") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.close, title: "关闭", onClick: () => setCsDetail(null), children: (0, react_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: 14, height: 14, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, react_jsx_runtime.jsx)("path", { d: "M18 6L6 18M6 6l12 12" }) }) })] }), (0, react_jsx_runtime.jsx)("div", { style: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap", padding: "0 2px 8px" }, children: [(0, react_jsx_runtime.jsx)("span", { style: { padding: "1px 8px", borderRadius: 10, fontSize: 12, background: "var(--mcn-brand,#4176e6)", color: "#fff", whiteSpace: "nowrap" }, children: "原创选题创作" }), csDetail && csDetail.id != null && (0, react_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)" }, children: "AI脚本 #" + csDetail.id }), csDetail && csDetail.account_name && (0, react_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)" }, children: csDetail.account_name })] }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwContent + " " + McnNav_module_css_default.md, style: { maxHeight: "60vh", overflow: "auto" }, children: csDetailData ? mdToReact(csDetailData.script_text || "(暂无脚本内容)") : "加载中…" }), (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", alignItems: "center", gap: 8, paddingTop: 10, borderTop: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))" }, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, style: { color: "#d9534f", borderColor: "#d9534f" }, onClick: () => setCsDel(csDetail), children: "删除" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, disabled: csBusy || !csDetailData, title: "发起 AI 脚本复盘(无源模式,独立 10 维评分)", onClick: csStartReview, children: csBusy ? "提交中…" : "AI 脚本复盘" })] })] })] }), csDel && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setCsDel(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, children: "确认删除原创选题脚本" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalText, children: csDel ? `将删除原创选题 AI脚本 #${csDel.id}(无源视频)及其关联复盘数据,删除后不可恢复。` : "" }), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modalBtns, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, onClick: () => setCsDel(null), children: "取消" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, style: { color: "#fff", background: "#d9534f", borderColor: "#d9534f" }, onClick: csConfirmDelete, children: "确认删除" })] })] })] }), cToast && (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.toast, children: cToast })] }), tab === "abc" && (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.empty, children: [(0, react_jsx_runtime.jsx)("div", { style: { fontWeight: 600, fontSize: 17, marginBottom: 8 }, children: "ABC 融合创作" }), (0, react_jsx_runtime.jsx)("div", { style: { color: "#6b7280", fontSize: 14 }, children: "将同账号多条视频的爆款元素融合成一条全新脚本,功能开发中。" })] }), detail && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { paddingTop: "50px" }, onClick: () => setDetail(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.rwModal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "8px" }, children: [(0, react_jsx_runtime.jsx)("span", { children: `源脚本 vs AI写脚本 — ${detail.video_title || ""}` }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.close, title: "关闭", onClick: () => setDetail(null), children: (0, react_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: 14, height: 14, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, react_jsx_runtime.jsx)("path", { d: "M18 6L6 18M6 6l12 12" }) }) })] }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwSplit, children: [(0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", flex: 1, minWidth: 0, gap: 12 }, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPane, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPaneTitle, children: "源脚本" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwContent + " " + McnNav_module_css_default.md, children: mdToReact(detailData ? (detailData.source || "(暂无源脚本数据)") : "加载中…") })] }), (0, react_jsx_runtime.jsx)("div", { style: { height: 32, display: "flex", alignItems: "center", justifyContent: "flex-end" } })] }), (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", flex: 1, minWidth: 0, gap: 12 }, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPane, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPaneTitle, children: "AI脚本" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwContent + " " + McnNav_module_css_default.md, children: mdToReact(!detailData ? "加载中…" : ((detailData.rewrites || []).length === 0 ? "(暂无改写脚本,点击下方「AI脚本」生成)" : (detailData.rewrites[verIdx] || {}).script_text)) }), detailData && (detailData.rewrites || []).length > 0 && (0, react_jsx_runtime.jsx)("button", { type: "button", title: "删除当前展示的改写脚本", onClick: () => setDeleteConfirm({ id: detailData.rewrites[verIdx].id }), style: { position: "absolute", right: "14px", bottom: "14px", display: "inline-flex", alignItems: "center", justifyContent: "center", width: 30, height: 30, background: "var(--dsw-alias-bg-base)", border: "1px solid rgba(229,72,77,.35)", borderRadius: 8, color: "#d9534f", cursor: "pointer", zIndex: 2 }, children: (0, react_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: 15, height: 15, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, react_jsx_runtime.jsx)("path", { d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }) }) })] }), (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", alignItems: "center", gap: 8 }, children: [detailData && (detailData.rewrites || []).length > 1 && (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: verIdx >= detailData.rewrites.length - 1, onClick: () => setVerIdx(verIdx + 1), children: "上一版" }), detailData && (detailData.rewrites || []).length > 1 && (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.pagerInfo, children: `第 ${detailData.rewrites.length - verIdx} / ${detailData.rewrites.length} 版` }), detailData && (detailData.rewrites || []).length > 1 && (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: verIdx <= 0, onClick: () => setVerIdx(verIdx - 1), children: "下一版" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, onClick: () => setConfirmRow(detail), children: "AI脚本" })] })] }), (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", flex: 1, minWidth: 0, gap: 12 }, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPane, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPaneTitle, children: "AI提示词" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwContent + " " + McnNav_module_css_default.md, children: !detailData ? "加载中…" : (visibleStoryboards.length === 0 ? "(暂无分镜提示词,点击下方「AI提示词」基于当前改写脚本生成)" : renderStoryboardBlocks((visibleStoryboards[sbIdx] || {}).script_text)) }), detailData && visibleStoryboards.length > 0 && (0, react_jsx_runtime.jsx)("button", { type: "button", title: "删除当前展示的分镜提示词", onClick: () => setStoryboardDeleteConfirm({ id: (visibleStoryboards[sbIdx] || {}).id }), style: { position: "absolute", right: "14px", bottom: "14px", display: "inline-flex", alignItems: "center", justifyContent: "center", width: 30, height: 30, background: "var(--dsw-alias-bg-base)", border: "1px solid rgba(229,72,77,.35)", borderRadius: 8, color: "#d9534f", cursor: "pointer", zIndex: 2 }, children: (0, react_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: 15, height: 15, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, react_jsx_runtime.jsx)("path", { d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }) }) })] }), (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", alignItems: "center", gap: 8 }, children: [detailData && visibleStoryboards.length > 1 && (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: sbIdx >= visibleStoryboards.length - 1, onClick: () => setSbVerIdx(sbIdx + 1), children: "上一版" }), detailData && visibleStoryboards.length > 1 && (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.pagerInfo, children: `第 ${visibleStoryboards.length - sbIdx} / ${visibleStoryboards.length} 版` }), detailData && visibleStoryboards.length > 1 && (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: sbIdx <= 0, onClick: () => setSbVerIdx(sbIdx - 1), children: "下一版" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary + (sbBusy ? " " + McnNav_module_css_default.scanning : ""), disabled: sbBusy || !detailData || !detailData.rewrites || detailData.rewrites.length === 0, title: sbBusy ? "分镜提示词生成中,请稍候" : "基于当前改写脚本生成分镜提示词", onClick: () => setConfirmStoryboardRow({ rewriteId: detailData.rewrites[verIdx].id, videoTitle: detail.video_title || "" }), children: sbBusy ? "生成中…" : "AI提示词" })] })] })] })] })]}) , confirmStoryboardRow && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setConfirmStoryboardRow(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, children: "确认生成 AI 分镜提示词" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalText, children: `确定基于「${confirmStoryboardRow.videoTitle || ""}」的当前改写脚本生成分镜提示词?将调用 storyboard-prompt 技能生成可直接用于 AI 生视频平台的分镜提示词序列。` }), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modalBtns, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, onClick: () => setConfirmStoryboardRow(null), children: "取消" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, onClick: confirmStoryboard, children: "确认生成" })] })] })] }) , confirmRow && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setConfirmRow(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, children: "确认发起 AI 改写" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalText, children: `确定对「${confirmRow.video_title || ""}」发起 AI 改写?将基于账号设定与视频选题生成全新 S9 故事脚本。` }), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modalBtns, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, onClick: () => setConfirmRow(null), children: "取消" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, onClick: confirmRewrite, children: "确认改写" })] })] })] }), reanalyzeRow && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setReanalyzeRow(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, children: "确认重新解析" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalText, children: `确定对「${reanalyzeRow.video_title || ""}」重新解析?将重新获取并分析该视频的源脚本与拆解内容。` }), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modalBtns, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, onClick: () => setReanalyzeRow(null), children: "取消" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, onClick: () => { startAnalyze(reanalyzeRow); setReanalyzeRow(null); }, children: "确认解析" })] })] })] }), unParseRow && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setUnParseRow(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, children: "确认解析" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalText, children: `确定对「${unParseRow.video_title || ""}」触发解析?将获取并分析该视频的源脚本与拆解内容,完成后自动入库。` }), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modalBtns, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, onClick: () => setUnParseRow(null), children: "取消" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, onClick: () => { startAnalyze(unParseRow); setUnParseRow(null); }, children: "确认解析" })] })] })] }), deleteConfirm && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setDeleteConfirm(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, children: "确认删除改写脚本" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalText, children: "确定删除当前展示的这版改写脚本?删除后不可恢复。" }), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modalBtns, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, onClick: () => setDeleteConfirm(null), children: "取消" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, style: { color: "#fff", background: "#d9534f", borderColor: "#d9534f" }, onClick: confirmDelete, children: "确认删除" })] })] })] }), storyboardDeleteConfirm && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setStoryboardDeleteConfirm(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, children: "确认删除分镜提示词" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalText, children: "确定删除当前展示的这版分镜提示词?删除后不可恢复。" }), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modalBtns, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, onClick: () => setStoryboardDeleteConfirm(null), children: "取消" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, style: { color: "#fff", background: "#d9534f", borderColor: "#d9534f" }, onClick: confirmStoryboardDelete, children: "确认删除" })] })] })] })] }); } //#endregion //#region 脚本诊断:AI写脚本列表(按生成时间倒序)+ 写作弹窗 + 复盘入口 function ScriptReviewPanel({ onBack }) { const GEN_METHODS = { rewrite: "参考选题创作", new: "原创选题创作", abc: "ABC融合创作" }; // 生成方式标签键:行内 genMethod/gen_method 优先;video_id 为空 = 原创选题(new) const methodKeyOf = (row) => row.genMethod || row.gen_method || (row.video_id > 0 ? "rewrite" : "new"); const DIMS = [["共鸣和共情", "创意看点"], ["设定承接", "设定承接"], ["框架节奏", "框架节奏"], ["钩子设置", "爆款开场"], ["叙事逻辑", "叙事逻辑"], ["极致事件", "极致事件"], ["其他", "其他"], ["总分", "总分"]]; const PAGE_SIZE = 20; // 生成时间格式化:ISO → YYYY-MM-DD HH:mm(本地时区) const fmtTime = (t) => { if (!t) return "-"; const d = new Date(t); if (Number.isNaN(d.getTime())) return String(t); const p = (n) => String(n).padStart(2, "0"); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`; }; // 列表:rewrite_log(AI 写脚本),仅已生成脚本,按生成时间倒序 const [rows, setRows] = (0, react.useState)([]); const [loading, setLoading] = (0, react.useState)(false); const [page, setPage] = (0, react.useState)(1); const loadList = (silent) => { if (!silent) setLoading(true); fetch("/mcn/api/rewrite/list") .then((r) => r.json()) .then((d) => { if (!d.ok) return; const items = (d.items || []) .filter((x) => x.status === "done" && x.script_len > 0) .sort((a, b) => String(b.created_time || "").localeCompare(String(a.created_time || "")) || b.id - a.id); // 并行补充账号名称(video_id → 视频详情;同一视频多版复用一次请求) const vidSet = [...new Set(items.map((x) => x.video_id).filter((v) => v > 0))]; Promise.all(vidSet.map((vid) => fetch("/mcn/api/video/detail?videoId=" + vid) .then((r) => r.json()) .then((vd) => ({ vid, name: vd.ok && vd.video ? (vd.video.account_name || "") : "" })) .catch(() => ({ vid, name: "" })))) .then((pairs) => { const nameMap = {}; pairs.forEach((p) => { nameMap[p.vid] = p.name; }); setRows(items.map((x) => ({ ...x, account_name: nameMap[x.video_id] || "" }))); }) .catch(() => setRows(items)); setPage(1); }) .catch(() => {}) .finally(() => { if (!silent) setLoading(false); }); }; (0, react.useEffect)(() => { loadList(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, []); const totalPages = Math.max(1, Math.ceil(rows.length / PAGE_SIZE)); const pageRows = rows.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE); // 写作弹窗:detail=当前行;detailData={videoTitle,source,rewrites,storyboards} const [detail, setDetail] = (0, react.useState)(null); const [detailData, setDetailData] = (0, react.useState)(null); const [verIdx, setVerIdx] = (0, react.useState)(0); const [sbVerIdx, setSbVerIdx] = (0, react.useState)(0); // 原创选题脚本(video_id 空,gen_method=new)单列查看 / AI 复盘 / 删除 const [csDetail, setCsDetail] = (0, react.useState)(null); const [csDetailData, setCsDetailData] = (0, react.useState)(null); const [csDel, setCsDel] = (0, react.useState)(null); const [csBusy, setCsBusy] = (0, react.useState)(false); const [csToast, setCsToast] = (0, react.useState)(null); (0, react.useEffect)(() => { if (!csToast) return; const t = setTimeout(() => setCsToast(null), 3000); return () => clearTimeout(t); }, [csToast]); const csPollRef = (0, react.useRef)(null); (0, react.useEffect)(() => () => { if (csPollRef.current) clearInterval(csPollRef.current); }, []); const openCsDetail = (row) => { setCsDetail(row); setCsDetailData(null); fetch("/mcn/api/custom-script?id=" + row.id) .then((r) => r.json()) .then((d) => { setCsDetailData(d.ok && d.script ? d.script : null); }) .catch(() => setCsDetailData(null)); }; const csStartReview = () => { if (!csDetail || csBusy) return; setCsBusy(true); fetch("/mcn/api/review/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ logId: csDetail.id, type: "ai" }), }) .then((r) => r.json()) .then((d) => { if (!d.ok) throw new Error(d.error || "提交失败"); setCsToast(d.message || "已提交 AI 脚本复盘"); loadList(true); // 与源脚本复盘一致:轮询列表直到完成(5s × ≤2 分钟) if (csPollRef.current) clearInterval(csPollRef.current); let n = 0; csPollRef.current = setInterval(() => { n++; loadList(true); if (n >= 24) { clearInterval(csPollRef.current); csPollRef.current = null; } }, 5000); }) .catch((e) => setCsToast("提交失败: " + e.message)) .finally(() => setCsBusy(false)); }; const csConfirmDelete = () => { if (!csDel) return; const id = csDel.id; setCsDel(null); fetch("/mcn/api/rewrite/delete", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id }), }) .then((r) => r.json()) .then((d) => { if (!d.ok) throw new Error(d.error || "删除失败"); setCsToast("已删除原创脚本"); if (csDetail && csDetail.id === id) { setCsDetail(null); setCsDetailData(null); } setRows((prev) => prev.filter((x) => x.id !== id)); setPage(1); }) .catch((e) => { setCsDel(null); setCsToast("删除失败: " + e.message); }); }; const openScript = (row) => { // 原创选题(无源视频):单列查看(custom-script);不触发源/对比/分镜接口 if (!(row.video_id > 0)) { openCsDetail(row); return; } setDetail(row); setDetailData(null); setVerIdx(0); setSbVerIdx(0); fetch("/mcn/api/video/detail?videoId=" + row.video_id) .then((r) => r.json()) .then((d) => { if (d.ok && d.video) setDetailData((prev) => ({ ...(prev || {}), videoTitle: d.video.video_title || "" })); }) .catch(() => {}); fetch("/mcn/api/video/script?videoId=" + row.video_id) .then((r) => r.json()) .then((d) => { if (!d.ok) { setDetailData((prev) => ({ ...(prev || {}), source: null, rewrites: [] })); return; } const rewrites = d.rewrites || []; const idx = rewrites.findIndex((x) => x.id === row.id); setDetailData((prev) => ({ ...(prev || {}), source: d.source, rewrites })); if (idx >= 0) setVerIdx(idx); }) .catch(() => setDetailData((prev) => ({ ...(prev || {}), source: null, rewrites: [] }))); fetch("/mcn/api/storyboard/list?videoId=" + row.video_id) .then((r) => r.json()) .then((d) => { if (d.ok) setDetailData((prev) => ({ ...(prev || {}), storyboards: d.items || [] })); }) .catch(() => {}); }; const curRewrite = detailData && detailData.rewrites && detailData.rewrites[verIdx] ? detailData.rewrites[verIdx] : null; const visibleStoryboards = ((detailData && detailData.storyboards) || []).filter((s) => !curRewrite || s.rewrite_id === curRewrite.id); const sbIdx = Math.min(sbVerIdx, Math.max(0, visibleStoryboards.length - 1)); (0, react.useEffect)(() => { setSbVerIdx(0); }, [verIdx]); // 分镜提示词分块渲染:按 --- 分隔 + 段首行 header(与 AI写脚本 页一致) const renderStoryboardBlocks = (text) => { const blocks = String(text || "").split(/\n\s*---+\s*\n/).map((b) => b.trim()).filter(Boolean); if (blocks.length <= 1) return mdToReact(text || ""); return blocks.map((b, bi) => { const lines = b.split("\n"); const first = lines[0].trim(); const m = /^段\s*(\d+)\s*[((]\s*([\d.]+)\s*-\s*([\d.]+)\s*秒\s*[))]\s*[||]\s*(.+)$/.exec(first); let header = null; let content = b; if (m) { const num = m[1]; const start = parseFloat(m[2]); const end = parseFloat(m[3]); const title = m[4].trim(); const total = (Number.isFinite(start) && Number.isFinite(end)) ? (end - start) : NaN; header = { num, title, range: `${m[2]} - ${m[3]}秒`, total: Number.isFinite(total) ? `${total}秒` : null }; content = lines.slice(1).join("\n").trim(); } return (0, react_jsx_runtime.jsxs)("div", { style: { border: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", borderRadius: 8, marginBottom: 8, overflow: "hidden", background: "var(--dsw-alias-bg-base)" }, children: [ (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "6px 12px", background: "var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated))", borderBottom: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", flexWrap: "wrap" }, children: [ (0, react_jsx_runtime.jsx)("span", { style: { fontWeight: 600, color: "var(--mcn-brand,#4176e6)", whiteSpace: "nowrap" }, children: header && header.title ? header.title : `场${header ? header.num : bi + 1}` }), (0, react_jsx_runtime.jsx)("span", { style: { marginLeft: "auto", display: "flex", gap: 10, fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)", whiteSpace: "nowrap" }, children: [(0, react_jsx_runtime.jsx)("span", { children: header ? header.range : "" }), header && header.total && (0, react_jsx_runtime.jsx)("span", { children: `总${header.total}` })] }) ] }), (0, react_jsx_runtime.jsx)("div", { style: { padding: "8px 12px" }, children: mdToReact(content) }) ] }, "sb" + bi); }); }; // 复盘:三模式(ai=AI脚本复盘 / source=原视频脚本复盘 / compare=对比分析),三份独立存储 const [reviewRow, setReviewRow] = (0, react.useState)(null); // 当前操作行(打开模式选择弹窗) const [reviewBusy, setReviewBusy] = (0, react.useState)(false); // 提交中 const [reviewDetail, setReviewDetail] = (0, react.useState)(null); // 查看详情的 review id const [reviewDetailData, setReviewDetailData] = (0, react.useState)(null); const [delReviewRow, setDelReviewRow] = (0, react.useState)(null); // 确认删除复盘数据的行 const [fullReport, setFullReport] = (0, react.useState)(false); const [fullReportData, setFullReportData] = (0, react.useState)(null); const [toast, setToast] = (0, react.useState)(null); const pollRef = (0, react.useRef)(null); // 列表行的评分数据:优先 AI 复盘,回退对比分析的 ai 段(无则空) const dimsOf = (row) => { const pick = (rv) => { try { return { dims: JSON.parse(rv.dims_json || "{}"), total: Number(rv.total) }; } catch (e) { return { dims: {}, total: NaN }; } }; if (row.review_ai && row.review_ai.status === "done") return pick(row.review_ai); if (row.review_compare && row.review_compare.status === "done") { try { const j = JSON.parse(row.review_compare.dims_json || "{}"); const t = JSON.parse(row.review_compare.total || "{}"); return { dims: j.ai || {}, total: Number(t.ai) }; } catch (e) { return { dims: {}, total: NaN }; } } return { dims: {}, total: NaN }; }; const fmtScore = (v) => (typeof v === "number" && Number.isFinite(v) ? v : "-"); // 列表行的复盘记录(详情弹窗用):优先 AI 复盘,回退对比分析 const reviewOf = (row) => (row.review_ai && row.review_ai.status === "done" ? row.review_ai : (row.review_compare && row.review_compare.status === "done" ? row.review_compare : null)); // 提交复盘 + 轮询列表直到完成(最多 2 分钟) const startReview = (type) => { if (!reviewRow || reviewBusy) return; const logId = reviewRow.id; setReviewBusy(true); fetch("/mcn/api/review/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ logId, type }), }) .then((r) => r.json()) .then((d) => { setReviewRow(null); if (d.ok) { setToast(d.message || "已提交复盘"); loadList(true); if (pollRef.current) clearInterval(pollRef.current); let n = 0; pollRef.current = setInterval(() => { n++; loadList(true); if (n >= 24) { clearInterval(pollRef.current); pollRef.current = null; } }, 5000); } else { setToast(d.error || "提交失败"); if (d.review && d.review.id) { setReviewDetail(d.review.id); setReviewDetailData(null); } } }) .catch(() => { setReviewRow(null); setToast("提交失败,请重试"); }) .finally(() => setReviewBusy(false)); }; // 打开复盘详情(含"重新复盘"入口:关闭详情后回到模式弹窗) const openReviewDetail = (id) => { setReviewDetail(id); setReviewDetailData(null); fetch("/mcn/api/review/detail?id=" + id) .then((r) => r.json()) .then((d) => { if (d.ok) setReviewDetailData(d.review); }) .catch(() => {}); }; // 删除复盘数据:AI 复盘 + 对比分析(源视频复盘为视频级共享保留,脚本保留) const delReview = () => { if (!delReviewRow) return; const rewriteId = delReviewRow.id; setDelReviewRow(null); fetch("/mcn/api/review/delete", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ rewriteId }), }) .then((r) => r.json()) .then((d) => { setToast(d.ok ? "复盘数据已删除" : (d.error || "删除失败")); if (d.ok) loadList(true); }) .catch(() => setToast("删除失败,请重试")); }; const reviewTypeName = (t) => t === "source" ? "原视频脚本复盘" : t === "compare" ? "对比分析" : "AI 脚本复盘"; const fmtRevTime = (t) => { if (!t) return ""; const d = new Date(t); if (Number.isNaN(d.getTime())) return ""; return fmtTime(t); }; // 打开完整报告:拉取该脚本三份完整复盘(左=源 / 右=AI / 下=对比) const openFullReport = (rv) => { setFullReport(true); setFullReportData(null); const rid = rv && rv.rewrite_id; if (!rid) { setFullReportData({ empty: true }); return; } fetch("/mcn/api/review/full?rewriteId=" + rid) .then((r) => r.json()) .then((d) => { if (d.ok) setFullReportData(d.reviews); else setFullReportData({ empty: true }); }) .catch(() => setFullReportData({ empty: true })); }; (0, react.useEffect)(() => { if (!toast) return; const t = setTimeout(() => setToast(null), 3000); return () => clearTimeout(t); }, [toast]); return (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.body, children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.toolbar, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, title: "返回工作台", onClick: onBack, children: "←" }), (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.title, children: "脚本诊断" }), (0, react_jsx_runtime.jsx)("span", { style: { marginLeft: "auto", fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)", whiteSpace: "nowrap" }, children: `按生成时间倒序 · 共 ${rows.length} 条` })] }), loading ? (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.loading, children: "加载中…" }) : rows.length === 0 ? (0, react_jsx_runtime.jsx)("div", { style: { padding: "40px 20px", textAlign: "center", color: "var(--dsw-alias-label-secondary)", font: "var(--dsw-font-s-14)" }, children: "暂无 AI 写脚本(完成改写的脚本会出现在这里)" }) : (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.tableWrap, children: [(0, react_jsx_runtime.jsx)("table", { className: McnNav_module_css_default.table, children: [(0, react_jsx_runtime.jsx)("thead", { children: (0, react_jsx_runtime.jsx)("tr", { children: ["ID", "账号名称", "生成方式", "脚本标题", "生成时间"].concat(DIMS.map((x) => x[1]), ["操作"]).map((h, i) => (0, react_jsx_runtime.jsx)("th", { style: h === "操作" ? { textAlign: "center" } : (i === 0 ? { minWidth: "35px" } : i === 1 ? { maxWidth: "80px" } : i === 2 ? { maxWidth: "80px" } : i === 3 ? { minWidth: "117px" } : i === 4 ? { maxWidth: "80px" } : undefined), children: h }, i)) }) }), (0, react_jsx_runtime.jsx)("tbody", { children: pageRows.map((row) => (0, react_jsx_runtime.jsx)("tr", { children: [(0, react_jsx_runtime.jsx)("td", { className: McnNav_module_css_default.cellTrack, children: "#" + row.id }), (0, react_jsx_runtime.jsx)("td", { className: McnNav_module_css_default.cellContent, style: { maxWidth: "80px" }, children: row.account_name || "-" }), (0, react_jsx_runtime.jsx)("td", { className: McnNav_module_css_default.cellContent, style: { maxWidth: "80px" }, children: GEN_METHODS[methodKeyOf(row)] }), (0, react_jsx_runtime.jsx)("td", { className: McnNav_module_css_default.cellContent, style: { maxWidth: "117px" }, children: row.title ? (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.link, title: row.video_id > 0 ? "打开写作弹窗查看 源脚本 vs AI写脚本" : "打开原创选题脚本(单列展示)", style: { cursor: "pointer", color: "var(--mcn-brand,#4176e6)" }, onClick: () => openScript(row), children: row.title }) : "-" }), (0, react_jsx_runtime.jsx)("td", { className: McnNav_module_css_default.cellContent, style: { maxWidth: "80px" }, children: fmtTime(row.created_time) }), DIMS.map((pair) => { const sd = dimsOf(row); const key = pair[0]; const v = key === "总分" ? sd.total : sd.dims[key]; const rv = key === "总分" ? reviewOf(row) : null; const clickable = key === "总分" && rv && typeof v === "number"; return (0, react_jsx_runtime.jsx)("td", { style: { color: key === "总分" && typeof v === "number" ? "var(--mcn-brand,#4176e6)" : "var(--dsw-alias-label-secondary,#8a8f98)", textAlign: "center", fontWeight: key === "总分" ? 600 : undefined }, children: clickable ? (0, react_jsx_runtime.jsx)("span", { style: { cursor: "pointer", textDecoration: "underline", textDecorationStyle: "dotted" }, title: "点击查看复盘详情", onClick: () => openReviewDetail(rv.id), children: fmtScore(v) }) : fmtScore(v) }, pair[1]); }), (0, react_jsx_runtime.jsx)("td", { className: McnNav_module_css_default.cellTrack, style: { textAlign: "center" }, children: (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 4, justifyContent: "center" }, children: [row.review_running ? (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, disabled: true, title: "复盘任务执行中", children: "复盘运行中…" }) : (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, title: "发起复盘:AI脚本 / 原视频脚本 / 对比分析", onClick: () => setReviewRow(row), children: "复盘" }), ((row.review_ai && row.review_ai.status === "done") || (row.review_compare && row.review_compare.status === "done")) ? (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, title: "删除该脚本的复盘数据(AI 复盘 + 对比分析;源视频复盘与脚本保留)", onClick: () => setDelReviewRow(row), children: "删除" }) : null] }) })] }, row.id)) })] }), rows.length > PAGE_SIZE && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.pager, children: [(0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.pagerTotal, children: `共 ${rows.length} 条` }), (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.pagerInfo, children: `第 ${page} / ${totalPages} 页` }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: page <= 1 || loading, onClick: () => setPage(page - 1), children: "上一页" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: page >= totalPages || loading, onClick: () => setPage(page + 1), children: "下一页" })] })] }), detail && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { paddingTop: "50px" }, onClick: () => setDetail(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.rwModal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "8px" }, children: [(0, react_jsx_runtime.jsx)("span", { children: `源脚本 vs AI写脚本 — ${(detailData && detailData.videoTitle) || ""}` }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.close, title: "关闭", onClick: () => setDetail(null), children: (0, react_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: 14, height: 14, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, react_jsx_runtime.jsx)("path", { d: "M18 6L6 18M6 6l12 12" }) }) })] }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwSplit, children: [(0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", flex: 1, minWidth: 0, gap: 12 }, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPane, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPaneTitle, children: "源脚本" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwContent + " " + McnNav_module_css_default.md, children: mdToReact(detailData ? (detailData.source || "(暂无源脚本数据)") : "加载中…") })] })] }), (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", flex: 1, minWidth: 0, gap: 12 }, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPane, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPaneTitle, children: "AI脚本" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwContent + " " + McnNav_module_css_default.md, children: mdToReact(!detailData ? "加载中…" : ((detailData.rewrites || []).length === 0 ? "(暂无改写脚本)" : ((detailData.rewrites[verIdx] || {}).script_text || ""))) })] }), (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", alignItems: "center", gap: 8 }, children: [detailData && (detailData.rewrites || []).length > 1 && (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: verIdx >= detailData.rewrites.length - 1, onClick: () => setVerIdx(verIdx + 1), children: "上一版" }), detailData && (detailData.rewrites || []).length > 1 && (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.pagerInfo, children: `第 ${detailData.rewrites.length - verIdx} / ${detailData.rewrites.length} 版` }), detailData && (detailData.rewrites || []).length > 1 && (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: verIdx <= 0, onClick: () => setVerIdx(verIdx - 1), children: "下一版" })] })] }), (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", flex: 1, minWidth: 0, gap: 12 }, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPane, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPaneTitle, children: "AI提示词" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwContent + " " + McnNav_module_css_default.md, children: !detailData ? "加载中…" : (visibleStoryboards.length === 0 ? "(暂无分镜提示词)" : renderStoryboardBlocks((visibleStoryboards[sbIdx] || {}).script_text)) })] }), (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", alignItems: "center", gap: 8 }, children: [detailData && visibleStoryboards.length > 1 && (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: sbIdx >= visibleStoryboards.length - 1, onClick: () => setSbVerIdx(sbIdx + 1), children: "上一版" }), detailData && visibleStoryboards.length > 1 && (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.pagerInfo, children: `第 ${visibleStoryboards.length - sbIdx} / ${visibleStoryboards.length} 版` }), detailData && visibleStoryboards.length > 1 && (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: sbIdx <= 0, onClick: () => setSbVerIdx(sbIdx - 1), children: "下一版" })] })] })] })] })] }), csDetail && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { paddingTop: "50px" }, onClick: () => setCsDetail(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.rwModal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "8px" }, children: [(0, react_jsx_runtime.jsx)("span", { children: "原创选题脚本" + (csDetail && csDetail.title ? " — " + csDetail.title : "") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.close, title: "关闭", onClick: () => setCsDetail(null), children: (0, react_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: 14, height: 14, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, react_jsx_runtime.jsx)("path", { d: "M18 6L6 18M6 6l12 12" }) }) })] }), (0, react_jsx_runtime.jsx)("div", { style: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap", padding: "0 2px 8px" }, children: [(0, react_jsx_runtime.jsx)("span", { style: { padding: "1px 8px", borderRadius: 10, fontSize: 12, background: "var(--mcn-brand,#4176e6)", color: "#fff", whiteSpace: "nowrap" }, children: "原创选题创作" }), csDetail && csDetail.id != null && (0, react_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)" }, children: "AI脚本 #" + csDetail.id }), csDetail && csDetail.account_name && (0, react_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)" }, children: csDetail.account_name })] }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwContent + " " + McnNav_module_css_default.md, style: { maxHeight: "60vh", overflow: "auto" }, children: csDetailData ? mdToReact(csDetailData.script_text || "(暂无脚本内容)") : "加载中…" }), (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", alignItems: "center", gap: 8, paddingTop: 10, borderTop: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))" }, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, style: { color: "#d9534f", borderColor: "#d9534f" }, onClick: () => setCsDel(csDetail), children: "删除" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, disabled: csBusy || !csDetailData, title: "发起 AI 脚本复盘(无源模式,独立 10 维评分)", onClick: csStartReview, children: csBusy ? "提交中…" : "AI 脚本复盘" })] })] })] }), csDel && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setCsDel(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, children: "确认删除原创选题脚本" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalText, children: csDel ? `将删除原创选题 AI脚本 #${csDel.id}(无源视频)及其关联复盘数据,删除后不可恢复。` : "" }), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modalBtns, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, onClick: () => setCsDel(null), children: "取消" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, style: { color: "#fff", background: "#d9534f", borderColor: "#d9534f" }, onClick: csConfirmDelete, children: "确认删除" })] })] })] }), csToast && (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.toast, children: csToast }), reviewRow && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setReviewRow(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, children: `发起复盘 — AI脚本 #${reviewRow.id}` }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalText, children: (reviewRow.video_id > 0 ? "三类复盘独立存储;同一对象已复盘的直接查看,不会重复发起。" : "原创选题脚本无原视频:仅支持 AI 脚本复盘(独立 10 维评分)。") }), (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8, margin: "12px 0" }, children: (() => { const ai = reviewRow.review_ai; const src = reviewRow.review_source; const cmp = reviewRow.review_compare; // 原创选题(video_id 空,无源视频):仅支持 AI 复盘;source/compare 模式隐藏(后端会 400) const noSource = !(reviewRow.video_id > 0); const modes = [ { key: "ai", name: "AI 脚本复盘", desc: noSource ? "对原创选题脚本独立分析(独立 10 维评分制)" : "对 AI 写脚本独立分析(10 维评分制)", done: !!(ai && ai.status === "done"), running: !!(ai && ai.status === "running"), rev: ai || null }, ...(noSource ? [] : [ { key: "source", name: "原视频脚本复盘", desc: "对源脚本独立分析(按视频共享一份,避免重复复盘)", done: !!(src && src.status === "done"), running: !!(src && src.status === "running"), rev: src || null }, { key: "compare", name: "对比分析", desc: "AI vs 源 — 复用两份独立复盘做三段式对比(不重新分析)", done: !!(cmp && cmp.status === "done"), running: !!(cmp && cmp.status === "running"), rev: cmp || null, prereq: !(ai && ai.status === "done" && src && src.status === "done") }, ]), ]; return modes.map((m) => (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "10px 12px", border: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", borderRadius: 8, background: "var(--dsw-alias-bg-base)" }, children: [(0, react_jsx_runtime.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [(0, react_jsx_runtime.jsx)("div", { style: { fontWeight: 600, fontSize: 13 }, children: m.name }), (0, react_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)" }, children: m.done ? `已复盘(${fmtRevTime(m.rev && m.rev.updated_time)})· 点击查看` : (m.running ? "复盘进行中…" : m.desc) })] }), m.running ? (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, disabled: true, children: "进行中" }) : m.done ? (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, onClick: () => { const rid = m.rev && m.rev.id; if (rid) { setReviewRow(null); openReviewDetail(rid); } }, children: "查看" }) : m.prereq ? (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, disabled: true, title: "需先完成 AI 脚本复盘 + 原视频脚本复盘", children: "待前置" }) : (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm + " " + McnNav_module_css_default.btnPrimary, disabled: reviewBusy, onClick: () => startReview(m.key), children: "发起" })] }, m.key)); })() }), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modalBtns, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, onClick: () => setReviewRow(null), children: "关闭" })] })] })] }), reviewDetail && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { paddingTop: "50px" }, onClick: () => setReviewDetail(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.rwModal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "8px" }, children: [(0, react_jsx_runtime.jsx)("span", { children: `${reviewDetailData ? reviewTypeName(reviewDetailData.type) : "复盘详情"}${reviewDetailData && reviewDetailData.verdict ? " · " + reviewDetailData.verdict : ""}` }), (0, react_jsx_runtime.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [reviewDetailData && reviewDetailData.content ? (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, title: "查看完整复盘报告(左=源视频 / 右=AI脚本 / 下=对比)", onClick: () => openFullReport(reviewDetailData), children: "完整报告" }) : null, (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.close, title: "关闭", onClick: () => setReviewDetail(null), children: (0, react_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: 14, height: 14, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, react_jsx_runtime.jsx)("path", { d: "M18 6L6 18M6 6l12 12" }) }) })] })] }), (0, react_jsx_runtime.jsx)("div", { style: { padding: "12px 16px", maxHeight: "72vh", overflow: "auto" }, children: !reviewDetailData ? "加载中…" : (() => { let dimsObj = {}; try { dimsObj = JSON.parse(reviewDetailData.dims_json || "{}"); } catch (e) {} const DIMS_ALL = [["共鸣和共情", "创意看点"], ["设定承接", "设定承接"], ["框架节奏", "框架节奏"], ["钩子设置", "爆款开场"], ["叙事逻辑", "叙事逻辑"], ["极致事件", "极致事件"], ["格式规范性", "格式规范性"], ["台词口语化", "台词口语化"], ["内容合规", "内容合规"], ["教程完整链", "教程完整链"], ["其他", "其他"]]; const scoreColor = (v) => (v >= 75 ? "var(--mcn-ok,#3a9e6d)" : v >= 60 ? "#d99a2b" : "#d9534f"); const scoreBar = (dims, keyPrefix) => DIMS_ALL.map((k) => { const v = dims[k[0]]; const has = typeof v === "number" && Number.isFinite(v); return (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }, children: [(0, react_jsx_runtime.jsx)("span", { style: { width: 96, fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)", whiteSpace: "nowrap" }, children: k[1] }), (0, react_jsx_runtime.jsx)("div", { style: { flex: 1, height: 6, borderRadius: 3, background: "var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated))", overflow: "hidden" }, children: has && (0, react_jsx_runtime.jsx)("div", { style: { width: v + "%", height: "100%", background: scoreColor(v) } }) }), (0, react_jsx_runtime.jsx)("span", { style: { width: 36, textAlign: "right", fontSize: 12, fontWeight: 600, color: has ? scoreColor(v) : "var(--dsw-alias-label-secondary,#8a8f98)" }, children: has ? v : "—" })] }, keyPrefix + k[0]); }); let issues = []; try { issues = JSON.parse(reviewDetailData.issues_json || "[]"); } catch (e) {} const sevColor = { 高: "#d9534f", 中: "#d99a2b", 低: "#3a9e6d" }; if (reviewDetailData.type === "compare") { const aiD = dimsObj.ai || {}; const srcD = dimsObj.source || {}; let t = {}; try { t = JSON.parse(reviewDetailData.total || "{}"); } catch (e) {} return (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 12, marginBottom: 12 }, children: [(0, react_jsx_runtime.jsx)("div", { style: { flex: 1, padding: 10, border: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", borderRadius: 8 }, children: (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("div", { style: { fontWeight: 600, marginBottom: 6 }, children: `AI 总分 ${fmtScore(t.ai)}` }), scoreBar(aiD, "c-ai-")] }) }), (0, react_jsx_runtime.jsx)("div", { style: { flex: 1, padding: 10, border: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", borderRadius: 8 }, children: (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("div", { style: { fontWeight: 600, marginBottom: 6 }, children: `源脚本 总分 ${fmtScore(t.source)}` }), scoreBar(srcD, "c-src-")] }) })] }), reviewDetailData.summary && (0, react_jsx_runtime.jsx)("div", { style: { marginBottom: 10, fontSize: 13 }, children: "综合判断:" + reviewDetailData.summary }), issues.length > 0 && (0, react_jsx_runtime.jsxs)("div", { style: { marginBottom: 10 }, children: [(0, react_jsx_runtime.jsx)("div", { style: { fontWeight: 600, marginBottom: 4 }, children: "问题清单(AI 差的点)" }), issues.map((it, i) => (0, react_jsx_runtime.jsxs)("div", { style: { fontSize: 12, marginBottom: 3, display: "flex", gap: 6 }, children: [(0, react_jsx_runtime.jsx)("span", { style: { color: sevColor[it.severity] || "#8a8f98", fontWeight: 600 }, children: it.severity || "低" }), (0, react_jsx_runtime.jsx)("span", { children: it.issue }), it.suggestion && (0, react_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-label-secondary,#8a8f98)" }, children: "→ " + it.suggestion })] }, "iss" + i)) ] }), reviewDetailData.content && (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.md, style: { borderTop: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", paddingTop: 10 }, children: mdToReact(reviewDetailData.content) })] }); } return (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 12, marginBottom: 12 }, children: [(0, react_jsx_runtime.jsx)("div", { style: { flex: 1, padding: 10, border: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", borderRadius: 8 }, children: (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("div", { style: { fontWeight: 600, marginBottom: 6 }, children: `总分 ${fmtScore(Number(reviewDetailData.total))}` }), scoreBar(dimsObj, "d-")] }) })] }), reviewDetailData.summary && (0, react_jsx_runtime.jsx)("div", { style: { marginBottom: 10, fontSize: 13 }, children: "综合评语:" + reviewDetailData.summary }), issues.length > 0 && (0, react_jsx_runtime.jsxs)("div", { style: { marginBottom: 10 }, children: [(0, react_jsx_runtime.jsx)("div", { style: { fontWeight: 600, marginBottom: 4 }, children: "问题清单" }), issues.map((it, i) => (0, react_jsx_runtime.jsxs)("div", { style: { fontSize: 12, marginBottom: 3, display: "flex", gap: 6 }, children: [(0, react_jsx_runtime.jsx)("span", { style: { color: sevColor[it.severity] || "#8a8f98", fontWeight: 600 }, children: it.severity || "低" }), (0, react_jsx_runtime.jsx)("span", { children: it.issue }), it.locate && (0, react_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-label-secondary,#8a8f98)" }, children: "[" + it.locate + "]" }), it.suggestion && (0, react_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-label-secondary,#8a8f98)" }, children: "→ " + it.suggestion }), it.step && (0, react_jsx_runtime.jsx)("span", { style: { color: "var(--mcn-brand,#4176e6)" }, children: it.step })] }, "iss" + i)) ] }), reviewDetailData.content && (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.md, style: { borderTop: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", paddingTop: 10 }, children: mdToReact(reviewDetailData.content) })] }); })() })] })] }), fullReport && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { paddingTop: "30px" }, onClick: () => setFullReport(false), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.rwModal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "8px" }, children: [(0, react_jsx_runtime.jsx)("span", { children: `完整报告 — AI脚本 #${(reviewDetailData && reviewDetailData.rewrite_id) || ""}(左=源视频 / 右=AI脚本 / 下=对比)` }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.close, title: "关闭", onClick: () => setFullReport(false), children: (0, react_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: 14, height: 14, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, react_jsx_runtime.jsx)("path", { d: "M18 6L6 18M6 6l12 12" }) }) })] }), (0, react_jsx_runtime.jsx)("div", { style: { padding: "12px 16px", maxHeight: "85vh", overflow: "auto" }, children: !fullReportData ? "加载中…" : (fullReportData.empty ? "(未能加载完整报告数据)" : (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [(0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 12 }, children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPane, style: { flex: 1, minWidth: 0 }, children: (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPaneTitle, children: "源视频复盘" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwContent + " " + McnNav_module_css_default.md, children: fullReportData.source ? mdToReact(fullReportData.source.content || "(该复盘无报告内容)") : "(该视频源脚本尚未复盘)" })] }) }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPane, style: { flex: 1, minWidth: 0 }, children: (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPaneTitle, children: "AI脚本复盘" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwContent + " " + McnNav_module_css_default.md, children: fullReportData.ai ? mdToReact(fullReportData.ai.content || "(该复盘无报告内容)") : "(该脚本尚未复盘)" })] }) })] }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPane, children: (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwPaneTitle, children: "对比分析" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.rwContent + " " + McnNav_module_css_default.md, children: fullReportData.compare ? mdToReact(fullReportData.compare.content || "(该对比分析无报告内容)") : "(尚未做对比分析,需先完成 AI 脚本复盘 + 原视频脚本复盘)" })] }) })] })) })] })] }), delReviewRow && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setDelReviewRow(null), children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modal, onClick: (e) => e.stopPropagation(), children: [(0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalTitle, children: "确认删除复盘数据" }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalText, children: `将删除 AI脚本 #${delReviewRow.id} 的 AI 复盘与对比分析数据(源视频复盘为视频级共享保留,脚本本身保留)。删除后如需可重新发起复盘。` }), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.modalBtns, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, onClick: () => setDelReviewRow(null), children: "取消" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, onClick: delReview, children: "确认删除" })] })] })] }), toast && (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.toast, children: toast })] }); } //#endregion //#region AI会话:MCN 工作台内查看插件创建的 dsh_* 任务会话(列表分组 + 对话详情,读会话文件 zstd 解压) function AiSessionsView({ onBack }) { const [items, setItems] = (0, react.useState)([]); const [detail, setDetail] = (0, react.useState)(null); // {id, messages, error} const detailScrollRef = (0, react.useRef)(null); // 详情打开/消息变化 → 自动滚动到最新(底部) (0, react.useEffect)(() => { if (detail && detailScrollRef.current) { detailScrollRef.current.scrollTop = detailScrollRef.current.scrollHeight; } }, [detail]); // 详情打开时轮询刷新(每 5 秒,执行中的会话新内容自动加载到查看窗口) (0, react.useEffect)(() => { const id = detail && detail.id; if (!id) return; const t = setInterval(() => { fetch("/mcn/api/session/detail?id=" + encodeURIComponent(id)) .then((r) => r.json()) .then((d) => { if (d.ok) setDetail({ id, messages: d.messages || [], error: d.error || null }); }) .catch(() => {}); }, 5000); return () => clearInterval(t); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [detail && detail.id]); const load = () => { fetch("/mcn/api/sessions") .then((r) => r.json()) .then((d) => { if (d.ok) setItems(d.items || []); }) .catch(() => {}); }; (0, react.useEffect)(() => { load(); const t = setInterval(load, 8000); return () => clearInterval(t); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, []); const openDetail = (id) => { setDetail({ id, messages: null, error: null }); fetch("/mcn/api/session/detail?id=" + encodeURIComponent(id)) .then((r) => r.json()) .then((d) => { if (d.ok) setDetail({ id, messages: d.messages || [], error: d.error || null }); else setDetail({ id, messages: [], error: d.error || "加载失败" }); }) .catch(() => setDetail({ id, messages: [], error: "加载失败" })); }; const fmtAgo = (iso) => { if (!iso) return ""; const diff = Date.now() - new Date(iso).getTime(); if (diff < 60 * 1000) return "刚刚"; if (diff < 3600 * 1000) return Math.round(diff / 60000) + " 分钟前"; if (diff < 86400 * 1000) return Math.round(diff / 3600000) + " 小时前"; return Math.round(diff / 86400000) + " 天前"; }; const groups = {}; for (const it of items) { (groups[it.group] = groups[it.group] || []).push(it); } const groupOrder = ["创作任务", "解析任务", "分镜", "计划任务", "对标创作", "其他"]; return (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.body, children: [(0, react_jsx_runtime.jsx)("style", { children: "@keyframes mcnSpin{to{transform:rotate(360deg)}}" }), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.toolbar, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, title: "返回工作台", onClick: onBack, children: "←" }), (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.title, children: "MCN任务会话" }), (0, react_jsx_runtime.jsx)("span", { style: { marginLeft: "auto", fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)", whiteSpace: "nowrap" }, children: "插件创建的 MCN 任务会话 · 共 " + items.length + " 个" })] }), detail ? (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", flex: 1, minHeight: 0 }, children: [(0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "8px 4px" }, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, onClick: () => setDetail(null), children: "← 返回列表" }), (0, react_jsx_runtime.jsx)("span", { style: { fontWeight: 600 }, children: detail.id }), detail.messages && (0, react_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)" }, children: `共 ${detail.messages.length} 条消息` })] }), detail.error && (0, react_jsx_runtime.jsx)("div", { style: { padding: 12, color: "#d9534f", fontSize: 13 }, children: detail.error }), (0, react_jsx_runtime.jsx)("div", { ref: detailScrollRef, style: { flex: 1, overflow: "auto", display: "flex", flexDirection: "column", gap: 10, padding: "4px 4px 20px" }, children: !detail.messages ? "加载中…" : detail.messages.length === 0 ? (0, react_jsx_runtime.jsx)("div", { style: { color: "var(--dsw-alias-label-secondary,#8a8f98)", padding: 20, textAlign: "center" }, children: "(暂无对话消息)" }) : detail.messages.map((m, i) => (0, react_jsx_runtime.jsxs)("div", { style: m.role === "assistant" ? { background: "var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated))", border: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", borderRadius: 10, padding: "10px 14px" } : m.role === "user" ? { background: "var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-1))", borderRadius: 10, padding: "10px 14px" } : { borderRadius: 8, padding: "4px 10px", fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)", background: "var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated))" }, children: [(0, react_jsx_runtime.jsx)("div", { style: { fontSize: 11, fontWeight: 600, marginBottom: 4, color: m.role === "user" ? "var(--mcn-brand,#4176e6)" : m.role === "assistant" ? "var(--dsw-alias-label-primary)" : "var(--dsw-alias-label-secondary,#8a8f98)" }, children: m.role === "user" ? "用户" : m.role === "assistant" ? "AI" : ("工具: " + (m.name || "")) }), m.role === "tool" ? (0, react_jsx_runtime.jsx)("div", { style: { fontFamily: "monospace", fontSize: 11, whiteSpace: "pre-wrap", wordBreak: "break-all" }, children: m.args }) : (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.md, children: mdToReact(m.text || "") })] }, "m" + i)) })] }) : items.length === 0 ? (0, react_jsx_runtime.jsx)("div", { style: { padding: "40px 20px", textAlign: "center", color: "var(--dsw-alias-label-secondary)", font: "var(--dsw-font-s-14)" }, children: "暂无插件创建的 AI 会话(改写/复盘/解析任务执行后出现)" }) : (0, react_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 10, overflow: "auto", paddingBottom: 20 }, children: groupOrder.filter((g) => groups[g]).map((g) => (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-secondary,#8a8f98)", margin: "10px 0 6px" }, children: g }), groups[g].map((it) => (0, react_jsx_runtime.jsx)("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "9px 12px", border: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", borderRadius: 8, background: "var(--dsw-alias-bg-base)", cursor: "pointer" }, onClick: () => openDetail(it.id), children: [(0, react_jsx_runtime.jsx)("span", { style: { width: 8, height: 8, borderRadius: "50%", background: it.status === "running" ? "#3a9e6d" : "var(--dsw-alias-border)", flexShrink: 0 } }), (0, react_jsx_runtime.jsx)("span", { style: { fontWeight: 500, flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: it.id }), it.status === "running" && (0, react_jsx_runtime.jsxs)("span", { style: { display: "inline-flex", alignItems: "center", gap: 6, whiteSpace: "nowrap" }, children: [(0, react_jsx_runtime.jsx)("span", { style: { width: 12, height: 12, borderRadius: "50%", border: "2px solid rgba(58,158,109,.25)", borderTopColor: "#3a9e6d", animation: "mcnSpin 0.8s linear infinite", display: "inline-block", flexShrink: 0 } }), (0, react_jsx_runtime.jsx)("span", { style: { fontSize: 11, color: "#3a9e6d" }, children: "执行中" })] }), (0, react_jsx_runtime.jsx)("span", { style: { fontSize: 11, color: "var(--dsw-alias-label-secondary,#8a8f98)", whiteSpace: "nowrap" }, children: fmtAgo(it.updated) })] }, it.id))] }, g)) })] }); } //#endregion //#region 抖音账号日榜板块({工作区}/抖音日榜 数据:日期/赛道筛选 + 数值排序) /** djb2 哈希(与宿主端 index.js hashKey 一致),用于匹配导入任务 key */ function mcnHashKey(s) { let h = 5381; for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) >>> 0; return h.toString(16).padStart(8, "0"); } /** 周榜行对应的导入任务 key(与 /mcn/api/external/stage 提交参数一致:name + url,抖音号缺省为空串) */ function importTaskKey(row) { return "acc:" + mcnHashKey(row.accountName + "|" + "|" + (row.profileUrl || "")); } function RankingSection() { // 四榜:week=账号周榜 / day=账号日榜 / hot=视频热榜 / likes=点赞榜;默认排序键随榜 const BOARDS = [{ id: "week", name: "账号周榜" }, { id: "day", name: "账号日榜" }, { id: "hot", name: "视频热榜" }, { id: "likes", name: "点赞榜" }]; const defSort = (b) => (b === "hot" ? "likeCount" : b === "likes" ? "add_digg_count" : "comprehensiveScore"); const [board, setBoard] = (0, react.useState)("week"); const boardRef = (0, react.useRef)("week"); boardRef.current = board; // 各榜独立记忆 date/category/sortBy/order/page(简单 map,无需缓存完整数据) const memRef = (0, react.useRef)({}); const memOf = (b) => { if (!memRef.current[b]) memRef.current[b] = { date: "", category: "", sortBy: defSort(b), order: "desc", page: 1 }; return memRef.current[b]; }; const [dates, setDates] = (0, react.useState)([]); const [categories, setCategories] = (0, react.useState)([]); const [date, setDate] = (0, react.useState)(""); const [category, setCategory] = (0, react.useState)(""); const [items, setItems] = (0, react.useState)([]); const [total, setTotal] = (0, react.useState)(0); const [loading, setLoading] = (0, react.useState)(false); const [sortBy, setSortBy] = (0, react.useState)("comprehensiveScore"); const [order, setOrder] = (0, react.useState)("desc"); const [page, setPage] = (0, react.useState)(1); const [importing, setImporting] = (0, react.useState)(null); const [runningKeys, setRunningKeys] = (0, react.useState)(new Set()); const runningKeysRef = (0, react.useRef)(new Set()); const [msg, setMsg] = (0, react.useState)(null); // 榜单更新流程(红狐 API 按次计费;30 分钟冷却防重) const [upd, setUpd] = (0, react.useState)(null); // GET /ranking/update 预检结果 const [updForce, setUpdForce] = (0, react.useState)(null); // 409 → 「仍要更新(force)」二次确认(含 error 文本) const [updRunning, setUpdRunning] = (0, react.useState)(false); const updPollRef = (0, react.useRef)(null); const stopUpdPoll = () => { if (updPollRef.current) { clearInterval(updPollRef.current); updPollRef.current = null; } }; const syncRunning = () => { fetch("/mcn/api/task/status") .then((r) => r.json()) .then((res) => { const set = new Set(); for (const t of res.imports || []) { if (t.status === "running" && t.id.startsWith("acc:")) set.add(t.id); } let finished = false; for (const k of runningKeysRef.current) if (!set.has(k)) finished = true; runningKeysRef.current = set; setRunningKeys(set); if (finished) loadRankRef.current(); }) .catch(() => {}); }; const importRank = (row) => { const key = importTaskKey(row); if (importing || runningKeysRef.current.has(key)) return; setImporting(row.rank); setMsg(null); runningKeysRef.current = new Set(runningKeysRef.current).add(key); setRunningKeys(new Set(runningKeysRef.current)); fetch("/mcn/api/external/stage", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ account: { name: row.accountName, url: row.profileUrl, category: row.category || "" } }), }) .then((r) => r.json()) .then((d) => { if (!d.ok) throw new Error(d.error || "提交失败"); setMsg("已提交,AI 正在获取账号信息与视频列表,请到对话中确认导入"); }) .catch((e) => setMsg("提交失败: " + e.message)) .finally(() => { setImporting(null); syncRunning(); }); }; const openAccountDetail = (row) => { try { if (window.__mcnNav) window.__mcnNav.open("douyin-account-detail", { accountId: row.dbId }); } catch (e) {} }; const loadRank = (d = date, c = category, s = sortBy, o = order, p = page, b = board) => { setLoading(true); fetch("/mcn/api/ranking?board=" + encodeURIComponent(b) + "&date=" + encodeURIComponent(d || "") + "&category=" + encodeURIComponent(c || "") + "&sortBy=" + encodeURIComponent(s) + "&order=" + encodeURIComponent(o) + "&page=" + p + "&pageSize=20") .then((r) => r.json()) .then((res) => { if (!res.ok) return; if (b !== boardRef.current) return; // 切榜竞态:丢弃过期响应 setDates(res.dates || []); setCategories(res.categories || []); setDate(res.date || ""); setCategory(res.category || ""); setItems(res.items || []); setTotal(res.total || 0); setPage(res.page || 1); const m = memOf(b); m.date = res.date || ""; m.category = res.category || ""; m.sortBy = s; m.order = o; m.page = res.page || 1; }) .catch(() => {}) .finally(() => { if (b === boardRef.current) setLoading(false); }); }; // 供 5s 导入状态轮询 / 更新完成后「刷新当前榜」使用:始终取最新一次渲染的 loadRank(避免闭包使用旧榜单/旧筛选) const loadRankRef = (0, react.useRef)(loadRank); loadRankRef.current = loadRank; const switchBoard = (b) => { if (b === board) return; const mOld = memOf(board); mOld.date = date; mOld.category = category; mOld.sortBy = sortBy; mOld.order = order; mOld.page = page; const m = memOf(b); setBoard(b); setDate(m.date); setCategory(m.category); setSortBy(m.sortBy); setOrder(m.order); setPage(m.page); loadRank(m.date, m.category, m.sortBy, m.order, m.page, b); }; (0, react.useEffect)(() => { loadRankRef.current(); syncRunning(); const t = setInterval(syncRunning, 5000); return () => { clearInterval(t); stopUpdPoll(); }; /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, []); const toggleSort = (key) => { const nextOrder = sortBy === key ? (order === "asc" ? "desc" : "asc") : (key === "rank" ? "asc" : "desc"); setSortBy(key); setOrder(nextOrder); loadRank(date, category, key, nextOrder, 1); }; // 日期切换(左右箭头):dates 已按日期倒序,index 0 = 最新 const dateIdx = dates.indexOf(date); const shiftDate = (delta) => { const idx = dateIdx + delta; if (idx < 0 || idx >= dates.length) return; const nd = dates[idx]; setDate(nd); loadRank(nd, category, sortBy, order, 1); }; const totalPages = Math.max(1, Math.ceil(total / 20)); const num = (v) => (v == null || v === "") ? "-" : String(v); const isAccBoard = board === "week" || board === "day"; // 各榜表头 + 可排序键(null=不可排序列) const headers = isAccBoard ? [{ t: "排名", k: "rank", w: 60 }, { t: "赛道", k: null }, { t: "账号", k: null, w: 140 }, { t: "综合得分", k: "comprehensiveScore", w: 90 }, { t: "粉丝", k: "followers" }, { t: "新增粉丝", k: "newFans" }, { t: "新增点赞", k: "newLikes" }, { t: "新增评论", k: "newComments" }, { t: "新增分享", k: "newShares" }, { t: "操作", k: null, center: true }] : board === "hot" ? [{ t: "排名", k: "rank", w: 60 }, { t: "赛道", k: null }, { t: "作品标题", k: null }, { t: "作者", k: null }, { t: "点赞", k: "likeCount" }, { t: "评论", k: "comment_count" }, { t: "分享", k: "share_count" }, { t: "收藏", k: "collect_count" }, { t: "发布时间", k: "publish_time" }] : [{ t: "排名", k: "rank", w: 60 }, { t: "赛道", k: null }, { t: "作品", k: null }, { t: "作者", k: null }, { t: "新增点赞", k: "add_digg_count" }, { t: "新增收藏", k: "add_collect_count" }, { t: "新增评论", k: "add_comment_count" }, { t: "新增分享", k: "add_share_count" }, { t: "发布时间", k: "publish_time" }]; // 榜单更新:预检 → 确认 → POST → 轮询直至完成(5s × ≤15 分钟)→ 刷新当前榜 const precheckUpdate = () => { if (updRunning) return; fetch("/mcn/api/ranking/update") .then((r) => r.json()) .then((d) => { if (d && d.ok) setUpd(d); else setUpd({ ok: true, error: (d && d.error) || "状态查询失败" }); }) .catch(() => setUpd({ ok: true, error: "状态查询失败" })); }; const submitUpdate = (force) => { setUpd(null); setUpdForce(null); fetch("/mcn/api/ranking/update", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(force ? { force: true } : {}), }) .then((r) => r.json().then((d) => ({ status: r.status, d }))) .then(({ status, d }) => { if (d && d.ok) { setMsg(d.message || "已开始更新榜单(红狐 API 按次计费)"); startUpdPoll(); return; } if (status === 409) { const err = (d && d.error) || "榜单更新冷却中或已在执行"; setMsg(err); setUpdForce({ error: err, remainingMs: d && d.remainingMs }); return; } setMsg((d && d.error) || "发起更新失败"); }) .catch(() => { setMsg("发起更新失败"); setUpd(null); }); }; const startUpdPoll = () => { stopUpdPoll(); setUpdRunning(true); let n = 0; updPollRef.current = setInterval(() => { n++; fetch("/mcn/api/ranking/update") .then((r) => r.json()) .then((d) => { if (!d || !d.ok) return; if (!d.running) { stopUpdPoll(); setUpdRunning(false); setMsg("榜单更新完成"); loadRankRef.current(); } else if (n >= 180) { stopUpdPoll(); setUpdRunning(false); setMsg("等待更新超过 15 分钟,已停止自动刷新,请稍后手动查看"); loadRankRef.current(); } }) .catch(() => {}); }, 5000); }; const fmtUpdAt = (t) => t ? String(t).slice(0, 16).replace("T", " ") : ""; const updText = upd ? (upd.running ? "榜单更新进行中…(不可重复触发)" : (upd.canUpdate === false || upd.remainingMs > 0 ? "距上次更新仍在冷却中,剩余约 " + Math.ceil((upd.remainingMs || 0) / 60000) + " 分钟;如需提前抓取可强制更新" : "上次更新时刻:" + (upd.lastAt ? fmtUpdAt(upd.lastAt) : "首次抓取") + (upd.lastError ? "(上次失败:" + upd.lastError + ")" : ""))) : ""; // ===== V1.1 热点数据:账号列表/AI写脚本 同款弹性行 grid(替换原