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(替换原 ) ===== // 弹性列=账号名/作品标题(吃余量);数值右对齐、文字左对齐、排名与操作居中;表头不换行,点击排序保留 const rkTpl = isAccBoard ? "40px 84px minmax(150px,1fr) 88px 88px 88px 88px 88px 88px 104px" : "40px 84px minmax(160px,1fr) 108px 88px 88px 88px 88px 150px"; const rkAl = isAccBoard ? ["center", "left", "left", "right", "right", "right", "right", "right", "right", "center"] : ["center", "left", "left", "left", "right", "right", "right", "right", "left"]; const rkCS = (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 rkHeadEls = headers.map((h, i) => react.createElement("div", { key: "h" + i, title: h.k ? "点击排序" : undefined, style: Object.assign(rkCS(rkAl[i]), { color: "var(--dsw-alias-label-secondary,#6b7280)", fontWeight: 600, fontSize: 13, cursor: h.k ? "pointer" : "default" }), onClick: h.k ? () => toggleSort(h.k) : undefined }, h.k ? h.t + (sortBy === h.k ? (order === "asc" ? " ↑" : " ↓") : "") : h.t)); const rkRowEls = items.map((row, idx) => { const cellEls = []; const pushCell = (al, clip, node, ck, extra) => cellEls.push(react.createElement("div", { key: "c" + (ck == null ? cellEls.length : ck), style: Object.assign(rkCS(al, clip), extra || {}) }, node)); if (isAccBoard) { const rowKey = importTaskKey(row); const rowImporting = importing === row.rank || runningKeys.has(rowKey); pushCell("center", false, row.rank ?? idx + 1, 0); pushCell("left", true, row.category || "-", 1); pushCell("left", false, row.profileUrl ? react.createElement("a", { className: McnNav_module_css_default.link, href: row.profileUrl, target: "_blank", rel: "noopener noreferrer", title: "打开账号主页" }, row.accountName || "-") : (row.accountName || "-"), 2, { whiteSpace: "normal", wordBreak: "break-all" }); pushCell("right", false, num(row.comprehensiveScore), 3); pushCell("right", false, num(row.followers), 4); pushCell("right", false, num(row.newFans), 5); pushCell("right", false, num(row.newLikes), 6); pushCell("right", false, num(row.newComments), 7); pushCell("right", false, num(row.newShares), 8); pushCell("center", false, row.inDb ? react.createElement("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, onClick: () => openAccountDetail(row), title: "查看账号详情" }, "查看") : react.createElement("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, disabled: rowImporting, onClick: () => importRank(row), title: "导入到账号数据库" }, rowImporting ? "导入中…" : "导入"), 9); } else { pushCell("center", false, row.rank ?? idx + 1, 0); pushCell("left", true, row.category || "-", 1); pushCell("left", true, react.createElement("span", { title: row.workUrl ? "打开作品链接" : String(row.workTitle || "") }, row.workTitle || "-"), 2); pushCell("left", true, row.author || "-", 3); const metricKeys = board === "hot" ? ["likeCount", "comment_count", "share_count", "collect_count"] : ["add_digg_count", "add_collect_count", "add_comment_count", "add_share_count"]; metricKeys.forEach((k, ki) => pushCell("right", false, num(row[k]), 4 + ki)); pushCell("left", false, num(row.publish_time), 8); } return react.createElement("div", { key: isAccBoard ? ("rkacc" + (row.dbId || row.accountName || idx)) : ("rkvid" + (row.rank ?? idx + 1) + "-" + (row.workTitle || row.author || idx)), style: { display: "grid", gridTemplateColumns: rkTpl, alignItems: "center" } }, cellEls); }); const rkGridList = react.createElement("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 } }, react.createElement("div", { style: { display: "grid", gridTemplateColumns: rkTpl, alignItems: "center", background: "var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated))", position: "sticky", top: 0, zIndex: 2 } }, rkHeadEls), rkRowEls); return (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.rankSection, children: [ (0, react_jsx_runtime.jsx)("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 12 }, children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, title: "返回工作台", onClick: () => { try { window.__mcnNav && window.__mcnNav.back(); } catch (e) {} }, children: "←" }), (0, react_jsx_runtime.jsx)("span", { style: { fontSize: 16, fontWeight: 600 }, children: "热点数据" })]}), (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.toolbar, children: [(0, react_jsx_runtime.jsx)("div", { style: { display: "inline-flex", gap: 4, flexWrap: "wrap" }, children: BOARDS.map((b) => (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.tab + (board === b.id ? " " + McnNav_module_css_default.tabActive : ""), onClick: () => switchBoard(b.id), children: b.name }, b.id)) }), (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }), (0, react_jsx_runtime.jsx)("select", { className: McnNav_module_css_default.select, value: category, onChange: (e) => { setCategory(e.target.value); loadRank(date, e.target.value, sortBy, order, 1); }, title: "赛道筛选(白名单赛道)", children: [(0, react_jsx_runtime.jsx)("option", { value: "", children: "全部赛道" })].concat(categories.map((c) => (0, react_jsx_runtime.jsx)("option", { value: c, key: c, children: c }))) }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.dateBtn, title: "前一天", disabled: dateIdx <= 0, onClick: () => shiftDate(-1), children: "◀" }), (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.pagerInfo, title: "选择日期", children: date || "-" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.dateBtn, title: "后一天", disabled: dateIdx < 0 || dateIdx >= dates.length - 1, onClick: () => shiftDate(1), children: "▶" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, disabled: updRunning, title: "更新榜单数据(账号日榜 1 次 + 账号周榜 5 赛道 + 视频热榜/点赞榜;红狐 API 按次计费,30 分钟冷却防重)", onClick: precheckUpdate, children: updRunning ? "更新中…" : "⟳ 更新榜单" })] }), (msg || updRunning) && (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.pagerInfo, style: { margin: 0, color: String(msg || "").includes("失败") || String(msg || "").includes("冷却") ? "#d9534f" : undefined }, children: updRunning ? "榜单更新中…系统会自动检测完成状态(最长等待 15 分钟)" : msg }), loading ? (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.loading, children: "加载中…" }) : items.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.empty, children: "暂无数据——点击右上角「更新榜单」抓取最新一期(账号日榜 1 次 + 账号周榜 5 赛道 + 视频热榜/点赞榜;红狐 API 按次计费)" }) : (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.tableWrap, children: rkGridList }), items.length > 0 && !loading && (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: () => loadRank(date, category, sortBy, order, page - 1), children: "上一页" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, disabled: page >= totalPages || loading, onClick: () => loadRank(date, category, sortBy, order, page + 1), children: "下一页" })] }), upd && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setUpd(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: updText }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalText, children: "预计调用范围:账号日榜全品类 1 次 + 账号周榜 5 赛道 + 视频热榜/点赞榜。红狐 API 按次计费,触发后 30 分钟内不可重复(冷却)。" }), (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: () => setUpd(null), children: "取消" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, disabled: upd.running, onClick: () => submitUpdate(false), children: upd.running ? "已在执行" : "确认更新" })] })] })] }), updForce && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setUpdForce(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: (updForce.error || "榜单更新冷却中或已在执行") + (updForce.remainingMs ? ",剩余约 " + Math.ceil(updForce.remainingMs / 60000) + " 分钟" : "") + "。强制更新将忽略冷却/防重直接调用红狐 API(按次计费),请确认成本。仍要更新请点「仍要更新」。若提示已在执行,请等待完成即可。" }), (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: () => setUpdForce(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: () => submitUpdate(true), children: "仍要更新" })] })] })] })] }); } //#endregion //#region 首页「最近 AI 脚本」:/mcn/api/rewrite/list 前 8 条 done+标题非空;点击打开脚本(video_id>0 走 video/script,空走 custom-script 单列);原创支持 AI 复盘/删除 function RecentScriptsSection({ onOpenAll }) { const GEN_LABELS = { rewrite: "参考选题创作", new: "原创选题创作", abc: "ABC融合创作" }; const [items, setItems] = (0, react.useState)([]); const [viewRow, setViewRow] = (0, react.useState)(null); const [viewData, setViewData] = (0, react.useState)(null); const [delRow, setDelRow] = (0, react.useState)(null); const [busy, setBusy] = (0, react.useState)(false); const [toast, setToast] = (0, react.useState)(null); const load = () => { fetch("/mcn/api/rewrite/list") .then((r) => r.json()) .then((d) => { if (!d.ok) return; const list = (d.items || []) .filter((x) => x.status === "done" && x.title && x.script_len > 0) .sort((a, b) => String(b.created_time || "").localeCompare(String(a.created_time || "")) || b.id - a.id) .slice(0, 8); setItems(list); }) .catch(() => {}); }; (0, react.useEffect)(() => { load(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, []); (0, react.useEffect)(() => { if (!toast) return; const t = setTimeout(() => setToast(null), 3000); return () => clearTimeout(t); }, [toast]); const isOrig = (row) => !(row.video_id > 0); const methodOf = (row) => row.genMethod || row.gen_method || (isOrig(row) ? "new" : "rewrite"); // 评分:优先 AI 复盘 total,回退 对比分析 的 ai 段 const scoreOf = (row) => { if (row.review_ai && row.review_ai.status === "done") return Number(row.review_ai.total); if (row.review_compare && row.review_compare.status === "done") { try { return Number(JSON.parse(row.review_compare.total || "{}").ai); } catch (e) { return NaN; } } return NaN; }; const fmtT = (t) => String(t || "").slice(0, 16).replace("T", " "); const scoreColor = (v) => (v >= 75 ? "var(--mcn-ok,#3a9e6d)" : v >= 60 ? "#d99a2b" : "#d9534f"); const openScript = (row) => { setViewRow(row); setViewData(null); if (isOrig(row)) { // 原创选题(无源视频):custom-script 单列 fetch("/mcn/api/custom-script?id=" + row.id) .then((r) => r.json()) .then((d) => { setViewData({ orig: true, script: d.ok && d.script ? d.script.script_text : null }); }) .catch(() => setViewData({ orig: true, script: null })); return; } fetch("/mcn/api/video/script?videoId=" + row.video_id) .then((r) => r.json()) .then((d) => { if (!d.ok) { setViewData({ orig: false, script: null }); return; } const rew = (d.rewrites || []).find((x) => x.id === row.id); setViewData({ orig: false, script: rew ? rew.script_text : null }); }) .catch(() => setViewData({ orig: false, script: null })); }; const startReview = () => { if (!viewRow || busy) return; setBusy(true); fetch("/mcn/api/review/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ logId: viewRow.id, type: "ai" }), }) .then((r) => r.json()) .then((d) => { setToast(d.ok ? (d.message || "已提交 AI 脚本复盘") : (d.error || "提交失败")); }) .catch(() => setToast("提交失败,请重试")) .finally(() => setBusy(false)); }; const confirmDelete = () => { if (!delRow) return; const id = delRow.id; setDelRow(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) { setToast(d.error || "删除失败"); return; } setToast("已删除"); if (viewRow && viewRow.id === id) { setViewRow(null); setViewData(null); } setItems((prev) => prev.filter((x) => x.id !== id)); }) .catch(() => setToast("删除失败,请重试")); }; return (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.toolbar, children: [(0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.title, children: [(0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.titleDot }), "最近 AI 脚本"] }), (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnSm, onClick: onOpenAll, children: "查看全部" })] }), items.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.empty, children: "暂无 AI 脚本" }) : (0, react_jsx_runtime.jsx)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))", gap: 8 }, children: items.map((row) => { const sc = scoreOf(row); const has = Number.isFinite(sc); const orig = isOrig(row); return (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 6, padding: "10px 12px", border: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", borderRadius: 8, background: "var(--dsw-alias-bg-base)", minWidth: 0 }, children: [(0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.link, title: orig ? "打开原创选题脚本(单列)" : "打开 AI 写脚本", style: { cursor: "pointer", color: "var(--mcn-brand,#4176e6)", fontWeight: 600, lineHeight: "18px", overflow: "hidden", textOverflow: "ellipsis", display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical" }, onClick: () => openScript(row), children: row.title }), (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6, fontSize: 12 }, children: [(0, react_jsx_runtime.jsx)("span", { style: { padding: "0 6px", borderRadius: 9, background: "var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated))", color: "var(--dsw-alias-label-secondary,#8a8f98)", whiteSpace: "nowrap" }, children: GEN_LABELS[methodOf(row)] || "AI写脚本" }), (0, react_jsx_runtime.jsx)("span", { style: { marginLeft: "auto", fontWeight: 600, color: has ? scoreColor(sc) : "var(--dsw-alias-label-secondary,#8a8f98)" }, title: has ? "AI 复盘评分" : "暂无复盘评分", children: has ? sc : "-" })] }), (0, react_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)", whiteSpace: "nowrap" }, children: fmtT(row.created_time) + (row.script_len ? " · " + row.script_len + " 字" : "") })] }, row.id); }) }), viewRow && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { paddingTop: "50px" }, onClick: () => setViewRow(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: (isOrig(viewRow) ? "原创选题脚本" : "AI写脚本") + " — " + (viewRow.title || "") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.close, title: "关闭", onClick: () => setViewRow(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.rwContent + " " + McnNav_module_css_default.md, style: { maxHeight: "60vh", overflow: "auto" }, children: !viewData ? "加载中…" : (viewData.script ? mdToReact(viewData.script) : "(暂无脚本内容)") }), (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)("span", { style: { marginRight: "auto", fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)" }, children: "生成方式:" + (GEN_LABELS[methodOf(viewRow)] || "-") }), isOrig(viewRow) && (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn, style: { color: "#d9534f", borderColor: "#d9534f" }, onClick: () => setDelRow(viewRow), children: "删除" }), isOrig(viewRow) && (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, disabled: busy, title: "发起 AI 脚本复盘(无源模式,独立 10 维评分)", onClick: startReview, children: busy ? "提交中…" : "AI 复盘" })] })] })] }), delRow && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, style: { alignItems: "center", paddingTop: 0 }, onClick: () => setDelRow(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: delRow ? `将删除 AI脚本 #${delRow.id}${isOrig(delRow) ? "(原创选题,无源视频)" : ""},删除后不可恢复。` : "" }), (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: () => setDelRow(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: confirmDelete, children: "确认删除" })] })] })] }), toast && (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.toast, children: toast })] }); } //#endregion //#region 账号列表页面 //#region V1.0 首页复刻组件(HomeView / V1Icon / RankPreviewV1 / RecentScriptsV1) const V1_ICON_INNER = { trending: '', users: '', pen: '', flask: '', globe: '', clock: '', help: '', more: '', }; function V1Icon({ icon, size, color }) { return react.createElement("span", { style: { display: "inline-flex", color: color || "#2f6fed", flexShrink: 0 } }, react.createElement("svg", { viewBox: "0 0 24 24", width: size || 20, height: size || 20, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", dangerouslySetInnerHTML: { __html: V1_ICON_INNER[icon] || "" }, })); } function V1StatBar({ stats }) { const items = [ ["关注账号", stats && stats.accounts], ["视频解析", stats && stats.analyses], ["AI写脚本", stats && stats.rewrites], ["AI脚本诊断", stats && stats.reviews], ["AI脚本评分", stats && stats.writingScore], ]; return react.createElement("div", { className: "v1stats" }, items.map((it) => react.createElement("div", { className: "v1stat", key: it[0] }, react.createElement("span", { className: "v1statV" }, it[1] == null ? "-" : String(it[1])), react.createElement("span", { className: "v1statL" }, it[0])))); } function V1Card({ title, desc, color, icon, onClick }) { return react.createElement("button", { type: "button", className: "v1card", onClick: onClick, title: desc }, react.createElement("span", { className: "v1cardI", style: { color: color } }, react.createElement(V1Icon, { icon: icon, size: 22 })), react.createElement("span", { className: "v1cardT" }, title), react.createElement("span", { className: "v1cardD" }, desc)); } const V1_HELP_GROUPS = [ ["创作写稿", "短视频脚本全流程创作:写脚本、口播/带货脚本、分镜脚本展开。", ["帮我想几个美妆赛道的爆款选题", "帮我写一条带货视频脚本,产品是XX面膜", "把刚才的脚本展开为分镜脚本"]], ["对标拆解", "账号定位与人设设计、对标视频拆解、账号内容套路分析。", ["帮我拆解这个抖音视频", "帮我拆解一下XX账号的内容套路", "分析我的账号定位有什么问题"]], ["账号分析", "达人账号信息获取、视频列表、人设卡生成。", ["帮我查一下俊希的账号设定", "帮我提炼这个账号的账号设定"]], ["诊断评分", "脚本质量诊断:四维评分/复盘报告/合规审查。", ["帮我诊断这个脚本", "给这个脚本打个分", "检查带货脚本有没有违规风险"]], ["榜单数据", "账号周榜/日榜、视频热榜、点赞榜浏览与更新。", ["看看最新的抖音榜单", "更新榜单数据"]] ]; function HomeView({ stats, entries, openEntry, openAiVideos }) { return react.createElement("div", { className: "v1wrap" }, react.createElement(V1StatBar, { stats: stats }), react.createElement("div", { className: "v1cards" }, react.createElement(V1Card, { key: "rk", title: "热点数据", desc: "账号日榜/周榜 · 视频热榜 · 点赞榜", color: "#e8590c", icon: "trending", onClick: () => openEntry("ranking") }), react.createElement(V1Card, { key: "acc", title: "账号列表", desc: "关注的账号 + 视频列表", color: "#2f6fed", icon: "users", onClick: () => openEntry("entry:douyin-accounts") }), react.createElement(V1Card, { key: "ai", title: "AI写脚本", desc: "AI脚本+分镜提示词", color: "#ec4899", icon: "pen", onClick: () => openAiVideos() }), react.createElement(V1Card, { key: "rev", title: "脚本诊断", desc: "检查清单+复盘报告", color: "#0ea5e9", icon: "flask", onClick: () => openEntry("script-review") })), react.createElement(RankPreviewV1, {}), react.createElement(RecentScriptsV1, { onOpenAll: openAiVideos })); } //#endregion //#region RankPreviewV1(首页紧凑榜单预览,复刻 V1.0:前10行/无分页排序/幽灵刷新) const V1_BOARD_NAMES = { week: "账号周榜", day: "账号日榜", hot: "视频热榜", likes: "点赞榜" }; const V1_PREVIEW_COLS = { week: { grid: "", head: "排名 赛道 账号 综合得分 新增粉丝 新增点赞 新增评论 新增分享 功能", fields: ["no", "cat", "name", "score", "num", "num", "num", "num", "act"] }, day: { grid: "", head: "排名 赛道 账号 综合得分 新增粉丝 新增点赞 新增评论 新增分享 功能", fields: ["no", "cat", "name", "score", "num", "num", "num", "num", "act"] }, hot: { grid: " v1rkvid", head: "排名 赛道 作品标题 作者 点赞 评论 分享 收藏", fields: ["no", "cat", "name", "author", "num", "num", "num", "num"] }, likes: { grid: " v1rkvid", head: "排名 赛道 作品标题 作者 新增点赞 新增收藏 新增评论 新增分享", fields: ["no", "cat", "name", "author", "num", "num", "num", "num"] }, }; function RankPreviewV1() { const [board, setBoard] = react.useState("week"); const [dates, setDates] = react.useState([]); const [categories, setCategories] = react.useState([]); const [date, setDate] = react.useState(""); const [category, setCategory] = react.useState(""); const [rows, setRows] = react.useState(null); const [loading, setLoading] = react.useState(false); const [msg, setMsg] = react.useState(null); const [updRunning, setUpdRunning] = react.useState(false); const metaRef = react.useRef({}); const timersRef = react.useRef([]); react.useEffect(() => () => { timersRef.current.forEach((x) => clearInterval(x)); timersRef.current = []; }, []); const loadMeta = (b, cb) => { fetch("/mcn/api/ranking?board=" + b + "&page=1&pageSize=1") .then((r) => r.json()) .then((d) => { if (!d.ok) throw new Error(d.error || "加载失败"); const m = { dates: d.dates || [], categories: d.categories || [], date: d.date || (d.dates && d.dates[0]) || "", category: d.category || "" }; metaRef.current[b] = m; if (cb) cb(m); }) .catch((e) => { metaRef.current[b] = { dates: [], categories: [], date: "", category: "" }; if (cb) cb(metaRef.current[b]); setMsg(e.message); }); }; const loadRows = (b, dt, ct, silent) => { if (!silent) setLoading(true); fetch("/mcn/api/ranking?board=" + b + "&date=" + encodeURIComponent(dt || "") + "&category=" + encodeURIComponent(ct || "") + "&page=1&pageSize=10") .then((r) => r.json()) .then((d) => { if (!d.ok) throw new Error(d.error || "加载失败"); setRows(d.items || []); }) .catch((e) => { setRows([]); if (!silent) setMsg(e.message); }) .finally(() => { if (!silent) setLoading(false); }); }; const activate = (b) => { const m = metaRef.current[b]; if (m) { setBoard(b); setDates(m.dates); setCategories(m.categories); setDate(m.date); setCategory(m.category); setRows(null); loadRows(b, m.date, m.category); } else { setBoard(b); setRows(null); setLoading(true); loadMeta(b, (mm) => { setDates(mm.dates); setCategories(mm.categories); setDate(mm.date); setCategory(mm.category); loadRows(b, mm.date, mm.category); }); } }; react.useEffect(() => { activate("week"); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, []); const shiftDate = (dir) => { const di = dates.indexOf(date); const ni = di + dir; if (ni < 0 || ni >= dates.length) return; setDate(dates[ni]); loadRows(board, dates[ni], category); }; const onCategory = (v) => { setCategory(v); loadRows(board, date, v); }; const importAccount = (r) => { const name = r.accountName || ""; v1ConfirmBox({ title: "关注账号", desc: "关注该账号并加入账号列表(触发抖音网页抓取账号信息与视频列表,AI 处理完成后自动入库)?", target: name, url: r.profileUrl || "", okText: "确认关注", onOk: () => { setMsg("已提交关注「" + name + "」,AI 正在抓取入库…"); fetch("/mcn/api/external/stage", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ account: { name: r.accountName || "", url: r.profileUrl || "", category: r.category || "" } }), }).then((r2) => r2.json()) .then((dd) => { setMsg(dd.ok ? (dd.message || "已提交") : (dd.error || "提交失败")); }) .catch((ee) => setMsg("提交失败: " + ee.message)); }, }); }; const updateRank = () => { const doUpdate = (force) => { return fetch("/mcn/api/ranking/update", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ force: !!force }) }) .then((r2) => r2.json()) .then((dd) => { if (!dd.ok) { setMsg(dd.error || "提交失败"); return null; } setMsg("已提交榜单更新(四榜最新一期),AI 正在抓取落盘,完成后自动刷新…"); setUpdRunning(true); const started = Date.now(); const timer = setInterval(() => { fetch("/mcn/api/ranking/update").then((r3) => r3.json()).then((st) => { if (!st.running || Date.now() - started > 8 * 60 * 1000) { clearInterval(timer); setUpdRunning(false); if (!st.running) { setMsg("榜单更新完成,已刷新数据"); activate(board); } else { setMsg("榜单更新仍在执行(可在 MCN任务会话 查看),稍后可手动刷新"); } } }).catch(() => {}); }, 5000); timersRef.current.push(timer); return null; }); }; fetch("/mcn/api/ranking/update") .then((r) => r.json()) .then((d) => { if (d.running) { setMsg("榜单更新任务正在执行中,请等待完成后再试"); return null; } const mins = Math.ceil((d.remainingMs || 0) / 60000); const force = mins > 0; v1ConfirmBox({ title: force ? "仍要更新?(将强制触发)" : "更新榜单数据", desc: force ? "榜单数据 " + mins + " 分钟前刚更新过,重复抓取会浪费红狐 API 配额。确需强制刷新?" : "账号日榜(全品类 1 次)+ 账号周榜(保留 5 赛道)+ 视频热榜/点赞榜(红狐 API 按次计费)。确认更新?", warn: force ? "强制更新将忽略冷却/防重直接调用红狐 API(按次计费),请确认成本。" : "", danger: force, okText: force ? "仍要更新" : "确认更新", onOk: () => { doUpdate(force); }, }); return null; }) .catch((e) => setMsg("操作失败: " + e.message)); }; const headParts = V1_PREVIEW_COLS[board].head.split(" "); const clsFor = (f) => f === "no" ? "v1no" : f === "cat" ? "v1cat" : f === "name" ? "v1name" : f === "author" ? "v1author" : f === "score" ? "v1score" : f === "num" ? "v1num" : f === "act" ? "v1act" : "v1num"; const headEls = headParts.map((h, i) => react.createElement("span", { className: clsFor(i === 3 && (board === "week" || board === "day") ? "score" : (board === "week" || board === "day" ? (i === 0 ? "no" : i === 1 ? "cat" : i === 2 ? "name" : i === 8 ? "act" : "num") : (i === 0 ? "no" : i === 1 ? "cat" : i === 2 ? "name" : i === 3 ? "author" : "num"))), key: "h" + i }, h)); const rowCells = (r, ri) => { const cells = []; const accountLike = board === "week" || board === "day"; for (let i = 0; i < headParts.length; i++) { let content = null; if (i === 0) content = react.createElement("span", { className: "v1no", key: "c0" }, r.rank == null ? "-" : String(r.rank)); else if (i === 1) content = react.createElement("span", { className: "v1cat", key: "c1" }, r.category || "-"); else if (i === 2 && accountLike) { content = r.inDb && r.dbId ? react.createElement("span", { className: "v1tlink", style: { color: "#4a90e2", cursor: "pointer" }, title: "查看账号详情", key: "c2", onClick: () => { try { window.__mcnNav && window.__mcnNav.open("douyin-account-detail", { accountId: r.dbId }); } catch (e) {} } }, String(r.accountName || "")) : (r.profileUrl ? react.createElement("a", { className: "v1tlink", href: r.profileUrl, target: "_blank", rel: "noopener noreferrer", key: "c2" }, String(r.accountName || "")) : react.createElement("span", { className: "v1name", key: "c2" }, String(r.accountName || "-"))); } else if (i === 2) { const title = String(r.title || r.aweme_desc || "-"); const url = r.workUrl || r.share_url; content = url ? react.createElement("a", { className: "v1tlink", href: url, target: "_blank", rel: "noopener noreferrer", title: "打开作品页", key: "c2" }, title) : react.createElement("span", { className: "v1name", title: title, key: "c2" }, title); } else if (i === 3 && accountLike) content = react.createElement("span", { className: "v1score", key: "c3" }, r.comprehensiveScore == null ? "-" : String(r.comprehensiveScore)); else if (i === 3) content = react.createElement("span", { className: "v1author", title: String(r.accountName || r.user_nickname || ""), key: "c3" }, String(r.accountName || r.user_nickname || "-")); else if (i === 8) { content = r.inDb && r.dbId ? react.createElement("button", { type: "button", className: "v1btn v1btnview", key: "c8", onClick: () => { try { window.__mcnNav && window.__mcnNav.open("douyin-account-detail", { accountId: r.dbId }); } catch (e) {} } }, "查看") : react.createElement("button", { type: "button", className: "v1btn v1btnimp", key: "c8", title: "关注该账号并加入账号列表", onClick: () => importAccount(r) }, "关注"); } else { const keyMap = accountLike ? { 4: r.newFans, 5: r.newLikes, 6: r.newComments, 7: r.newShares } : (board === "hot" ? { 4: r.likeCount, 5: r.commentCount, 6: r.shareCount, 7: r.collectCount } : { 4: r.add_digg_count, 5: r.add_collect_count, 6: r.add_comment_count, 7: r.add_share_count }); content = react.createElement("span", { className: "v1num", key: "c" + i }, keyMap[i] == null || keyMap[i] === "" ? "-" : String(keyMap[i])); } cells.push(content); } return cells; }; const di = dates.indexOf(date); const colDef = V1_PREVIEW_COLS[board]; const isAcct = board === "week" || board === "day"; const emptyRows = isAcct ? null : null; let body = null; if (loading) body = react.createElement("div", { style: { padding: "14px", color: "#6b7280", fontSize: 14 } }, "加载中…"); else if (dates.length === 0) body = react.createElement("div", { className: "v1rkempty" }, "「" + (V1_BOARD_NAMES[board] || "") + "」暂无本地数据文件,先运行 mcn-data-insight 抓取技能落盘"); else if (rows === null || rows.length === 0) body = react.createElement("div", { style: { padding: "14px", color: "#6b7280", fontSize: 14 } }, "暂无数据"); else body = react.createElement("div", { className: "v1rklist" }, react.createElement("div", { className: "v1rkrow" + colDef.grid + " v1rkhead" }, headEls), rows.map((r, ri) => react.createElement("div", { className: "v1rkrow" + colDef.grid, key: "r" + ri }, rowCells(r, ri)))); return react.createElement("div", { className: "v1rank" }, react.createElement("div", { className: "v1rkhead" }, react.createElement("div", { className: "v1rktabs" }, ["week", "day", "hot", "likes"].map((b) => react.createElement("button", { type: "button", key: b, className: "v1rktab" + (board === b ? " on" : ""), onClick: () => activate(b), }, V1_BOARD_NAMES[b]))), react.createElement("button", { type: "button", className: "v1rkupdate", title: "更新榜单数据:后台任务重新抓取四榜(账号日榜 全品类 1 次 / 账号周榜 5 赛道 / 视频热榜 / 点赞榜)最新一期并落盘;30 分钟内已更新会拦截防重复", onClick: updateRank, }, updRunning ? "更新中…" : react.createElement(V1Icon, { icon: "globe", size: 16 })), react.createElement("div", { className: "v1rktools" }, categories.length > 0 && react.createElement("select", { className: "v1rksel", value: category, title: "赛道筛选", onChange: (e) => onCategory(e.target.value) }, react.createElement("option", { value: "", key: "" }, "全部赛道"), categories.map((c) => react.createElement("option", { value: c, key: c }, c))), react.createElement("button", { type: "button", className: "v1rkbtn", disabled: di <= 0, onClick: () => shiftDate(-1), title: "上一期" }, "◀"), react.createElement("span", { className: "v1rkdate" }, date ? String(date).slice(5).replace("-", "-") : ""), react.createElement("button", { type: "button", className: "v1rkbtn", disabled: di < 0 || di >= dates.length - 1, onClick: () => shiftDate(1), title: "下一期" }, "▶"))), msg && react.createElement("div", { style: { fontSize: 12, color: "#6b7280", margin: "4px 2px 8px" } }, msg), body); } //#endregion //#region RecentScriptsV1(首页「AI脚本」卡片,复刻 V1.0:标题/5行预览/账号|日期/评分徽章/查看全部) function v1CleanTitle(text) { const first = String(text || "").replace(/\r\n/g, "\n").split("\n").map((l) => l.trim()).find((l) => l && !/^#{1,4}\s/.test(l)); if (!first) return null; let t = first.replace(/^#+\s*/, "").trim(); const m = t.match(/《([^》]+)》/); if (m && m[1]) t = m[1].trim(); return t ? t.slice(0, 40) : null; } function v1PreviewLines(text, n) { return String(text || "").replace(/\r\n/g, "\n").split("\n").map((l) => l.trim()).filter((l) => l && !/^#{1,4}\s/.test(l)).slice(0, n || 5); } function v1FmtDate(t) { return String(t || "").slice(0, 10); } //#region V1 统一脚本内容查看器(复刻 V1.0 openScriptModal:源脚本/AI脚本/右侧诊断|分镜 + 版本条 + 删除)与 V1 确认弹窗 function vvI(pd, size, color) { return react.createElement("svg", { viewBox: "0 0 24 24", width: size || 15, height: size || 15, fill: "none", stroke: color || "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", style: { flex: "none" } }, react.createElement("path", { d: pd })); } function vvFmtT(t) { const s = String(t || "").replace("T", " ").slice(0, 16); return s; } function vvTitleQ(t) { return String(t || "").replace(/^《/, "").replace(/》$/, ""); } function vvMdSafe(t) { try { if (typeof mdToReact === "function") { const el = mdToReact(t); if (el != null) return el; } } catch (e) { /* md 解析失败降级纯文本 */ } return react.createElement("pre", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word", margin: 0, fontFamily: "inherit", fontSize: 14, lineHeight: 1.7, color: "#24292f" } }, String(t == null ? "" : t)); } function vvScoreColor(v) { return v >= 75 ? "#3a9e6d" : v >= 60 ? "#d99a2b" : "#d9534f"; } const VV_DIAG_ORDER = ["设定承接", "框架节奏", "钩子设置", "叙事逻辑", "极致事件", "共鸣和共情", "格式规范性", "台词口语化", "内容合规", "教程完整链", "其他"]; function vvParseDims(rv) { let dimsObj = {}; try { dimsObj = JSON.parse(rv.dims_json || "{}"); } catch (e) { dimsObj = {}; } if (rv.type === "compare" && dimsObj && !Array.isArray(dimsObj) && typeof dimsObj === "object" && dimsObj.ai) dimsObj = dimsObj.ai; const names = VV_DIAG_ORDER.filter((k) => dimsObj[k] != null && dimsObj[k] !== ""); return names.map((k) => { const v = Number(dimsObj[k]); return { name: k, val: Number.isFinite(v) ? v : null }; }); } function vvParseIssues(rv) { try { return JSON.parse(rv.issues_json || "[]"); } catch (e) { return []; } } function vvIconEl(pd, size, color) { const ns = "http://www.w3.org/2000/svg"; const svg = document.createElementNS(ns, "svg"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("width", String(size || 15)); svg.setAttribute("height", String(size || 15)); svg.setAttribute("fill", "none"); svg.setAttribute("stroke", color || "currentColor"); svg.setAttribute("stroke-width", "2"); svg.setAttribute("stroke-linecap", "round"); svg.setAttribute("stroke-linejoin", "round"); svg.style.flex = "none"; const p = document.createElementNS(ns, "path"); p.setAttribute("d", pd); svg.appendChild(p); return svg; } // V1 风格确认弹窗(DOM 即用;返回关闭函数)// V1 风格确认弹窗(DOM 即用;返回关闭函数) function v1ConfirmBox(opts) { const mask = document.createElement("div"); mask.className = "vvMask"; const box = document.createElement("div"); box.className = "vvSmall"; const h3 = document.createElement("h3"); h3.appendChild(vvIconEl(opts.iconPd || "M13 2 3 14h9l-1 8 10-12h-9l1-8", 18, opts.danger ? "#d9534f" : "#2f6fed")); h3.appendChild(document.createTextNode(opts.title || "确认")); box.appendChild(h3); if (opts.desc) { const p = document.createElement("p"); p.className = "vvDim"; p.textContent = opts.desc; box.appendChild(p); } if (opts.target) { const d = document.createElement("div"); d.className = "vvTarget"; d.textContent = opts.target; box.appendChild(d); } if (opts.url) { const u = document.createElement("div"); u.className = "vvUrl"; u.textContent = opts.url; box.appendChild(u); } if (opts.warn) { const w = document.createElement("p"); w.className = "vvWarn"; w.textContent = opts.warn; box.appendChild(w); } const act = document.createElement("div"); act.className = "vvActions"; const cancel = document.createElement("button"); cancel.type = "button"; cancel.className = "vb"; cancel.textContent = "取消"; const ok = document.createElement("button"); ok.type = "button"; ok.className = "vb " + (opts.danger ? "vbRed" : "vbPrimary"); ok.textContent = opts.okText || "确认执行"; act.appendChild(cancel); act.appendChild(ok); box.appendChild(act); mask.appendChild(box); document.body.appendChild(mask); const close = () => { try { mask.remove(); } catch (e) {} }; mask.addEventListener("click", (e) => { if (e.target === mask) close(); }); cancel.addEventListener("click", close); ok.addEventListener("click", () => { close(); try { if (opts.onOk) opts.onOk(); } catch (e) {} }); return close; } const vvJ = (u) => fetch(u).then((r) => r.json()); const vvPost = (u, b) => fetch(u, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(b || {}) }).then((r) => r.json()); function ScriptViewerV1({ rewriteId, videoId, fallbackTitle, accountName, onClose, onChanged }) { const [state, setState] = react.useState({ mode: "loading", err: "" }); const [vi, setVi] = react.useState(-1); const [showSrc, setShowSrc] = react.useState(false); const [showSide, setShowSide] = react.useState(false); const [stab, setStab] = react.useState("diag"); const [sbIdx, setSbIdx] = react.useState(-1); const [side, setSide] = react.useState(null); const [sideTick, setSideTick] = react.useState(0); const [busy, setBusy] = react.useState(false); const [hint, setHint] = react.useState(""); const [ask, setAsk] = react.useState(null); const stateRef = react.useRef(state); stateRef.current = state; const viRef = react.useRef(vi); viRef.current = vi; const stabRef = react.useRef(stab); stabRef.current = stab; const showSideRef = react.useRef(showSide); showSideRef.current = showSide; // 初始装配:ref=视频容器全部版本;orig=单条(若实际挂视频下 → 切换 ref 模式) react.useEffect(() => { let alive = true; const vid = Number(videoId) > 0 ? Number(videoId) : 0; const rid = Number(rewriteId) > 0 ? Number(rewriteId) : 0; const loadRef = async (v, pickId, acc, ttl) => { const [vs, vd, st] = await Promise.all([ vvJ("/mcn/api/video/script?videoId=" + v).catch(() => ({ ok: false, rewrites: [], source: null })), vvJ("/mcn/api/video/detail?videoId=" + v).catch(() => ({ ok: false, video: null })), vvJ("/mcn/api/storyboard/list?videoId=" + v).catch(() => ({ ok: false, items: [] })), ]); if (!alive) return; let rewrites = (vs && vs.rewrites) || []; if (!rewrites.length) { setState({ mode: "empty", err: "该脚本不存在或已删除" }); return; } rewrites = rewrites.slice().reverse(); // 旧→新,末位=最新 let idx = pickId ? rewrites.findIndex((x) => String(x.id) === String(pickId)) : -1; if (idx < 0) idx = rewrites.length - 1; setState({ mode: "ready", vid: v, source: (vs && vs.source) || null, rewrites, sbAll: (st && st.items) || [], title: ttl || ((vd && vd.video && vd.video.video_title) || ""), acc: acc || ((vd && vd.video && vd.video.account_name) || "") }); setVi(idx); }; const loadOrig = async () => { const cs = await vvJ("/mcn/api/custom-script?id=" + rid).catch(() => null); if (cs && cs.ok && cs.script) { const s = cs.script; if (Number(s.video_id) > 0) { await loadRef(Number(s.video_id), rid, s.account_name || "", s.title || ""); return; } if (!alive) return; const one = { id: s.id, script_text: s.script_text || "", created_time: s.created_time || "" }; setState({ mode: "ready", vid: 0, source: null, rewrites: [one], sbAll: [], title: s.title || ttl || "", acc: s.account_name || "" }); setVi(0); return; } const all = await vvJ("/mcn/api/rewrite/list?limit=100").catch(() => ({ items: [] })); const row = ((all.items) || []).find((x) => String(x.id) === String(rid)); if (row && Number(row.video_id) > 0) { await loadRef(Number(row.video_id), rid, "", row.title || ""); return; } if (!alive) return; if (row) { const one = { id: row.id, script_text: "", created_time: row.created_time || "" }; setState({ mode: "ready", vid: 0, source: null, rewrites: [one], sbAll: [], title: row.title || ttl || "", acc: "" }); setVi(0); return; } setState({ mode: "empty", err: "该脚本不存在或已删除" }); }; const ttl = String(fallbackTitle || ""); (vid > 0 ? loadRef(vid, rid, accountName || "", ttl) : (rid > 0 ? loadOrig() : Promise.resolve(setState({ mode: "empty", err: "缺少脚本标识" })))); return () => { alive = false; }; }, []); const st = state; const cur = st.mode === "ready" ? st.rewrites[Math.min(vi, st.rewrites.length - 1)] : null; // 右侧面板数据加载(diag / sb) const loadSide = (c) => { if (!c) return; setSide(null); const token = (sideTickRef.current = sideTickRef.current + 1); const doLoad = async () => { if (stabRef.current === "diag") { const rf = await vvJ("/mcn/api/review/full?rewriteId=" + c.id).catch(() => null); const reviews = (rf && rf.reviews) || {}; const rv = (reviews.ai || reviews.compare) || null; if (!rv) { if (stateRef.current.mode === "ready" && token === sideTickRef.current) setSide({ kind: "empty", diag: false }); return; } const dims = vvParseDims(rv); const issues = vvParseIssues(rv); let total = Number(rv.total); let srcTotal = NaN; if (rv.type === "compare") { try { const t = JSON.parse(rv.total || "{}"); total = Number(t.ai); srcTotal = Number(t.source); } catch (e) {} } if (token === sideTickRef.current) setSide({ kind: "diag", rv, dims, issues, total, srcTotal, summary: rv.summary || "" }); } else { const stt = stateRef.current; const vis = ((stt.sbAll) || []).filter((s) => s.rewrite_id == null || String(s.rewrite_id) === String(c.id)); if (!vis.length) { if (token === sideTickRef.current) setSide({ kind: "sbEmpty", vid: stt.vid }); return; } if (token === sideTickRef.current) setSide({ kind: "sb", vis }); } }; doLoad(); }; const curId = cur && cur.id; react.useEffect(() => { if (cur && showSide) loadSide(cur); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [curId, stab, showSide]); const sideTickRef = react.useRef(0); const fireReview = () => { if (!cur) return; const hasSource = st.mode === "ready" && st.vid > 0 && !!st.source; setAsk({ title: hasSource ? "复盘评分" : "AI脚本复盘", desc: hasSource ? "将对该 AI 脚本做复盘评分(AI脚本 / 原视频脚本 / 对比分析三种模式)。" : "将对该 AI 脚本做质量复盘评分(独立分析模式 · 10 维评分制,该脚本无原视频脚本)。", target: "《" + vvTitleQ(st.title || cur.title || "AI 脚本") + "》", okText: "确认执行", onOk: () => { setBusy(true); setHint(""); vvPost("/mcn/api/review/start", { logId: cur.id, type: "ai" }).then((d) => { if (!d.ok) throw new Error(d.error || "提交失败"); setHint("已提交复盘任务(" + (d.message || "") + "),完成后点「更新诊断」查看"); }).catch((e) => setHint("提交失败: " + e.message)).finally(() => setBusy(false)); } }); }; const fireGenSb = (regenerate) => { if (!cur) return; setAsk({ title: regenerate ? "重新生成分镜" : "生成分镜", desc: "将基于该 AI 脚本展开成 AI 分镜提示词(按分镜技能生成镜头级描述)。", target: "《" + vvTitleQ(st.title || cur.title || "") + "》", okText: "确认生成", onOk: () => { setBusy(true); setHint(""); vvPost("/mcn/api/storyboard/create", { rewriteId: cur.id }).then((d) => { if (!d.ok) throw new Error(d.error || "生成失败"); setHint("已提交分镜生成任务,生成完成后本列表自动出现新版本"); let n = 0; const timer = setInterval(() => { n++; vvJ("/mcn/api/storyboard/list?videoId=" + st.vid).then((r2) => { if (r2.ok) { const items = r2.items || []; if (items.length > (stateRef.current.sbAll || []).length || n >= 8) { clearInterval(timer); if (n < 8) setHint("分镜已生成 ✓"); setState((prev) => (prev.mode === "ready" ? { ...prev, sbAll: items } : prev)); setSide(null); loadSide(cur); } } }).catch(() => { if (n >= 8) clearInterval(timer); }); }, 5000); }).catch((e) => setHint("生成失败: " + e.message)).finally(() => setBusy(false)); } }); }; const openDeleteAsk = () => { if (!cur) return; setAsk({ title: "删除当前脚本", danger: true, desc: "将删除 AI 脚本" + (st.rewrites.length > 1 ? "第 " + (vi + 1) + "/" + st.rewrites.length + " 版" : "") + "(创建于 " + vvFmtT(cur.created_time) + "):", target: "《" + vvTitleQ(st.title || cur.title || "AI 脚本") + "》", warn: "该脚本的诊断复盘与分镜提示词将一并删除,不可恢复" + (st.rewrites.length <= 1 ? ";其视频若无其他 AI 脚本,视频级复盘也会清理" : "") + "。", okText: "确认删除", onOk: () => { setBusy(true); vvPost("/mcn/api/rewrite/delete", { id: cur.id }).then((d) => { if (!d.ok) throw new Error(d.error || "删除失败"); const rest = st.rewrites.filter((x) => String(x.id) !== String(cur.id)); if (!rest.length) { try { if (onChanged) onChanged(cur.id); } catch (e) {} try { onClose(); } catch (e) {} return; } setState((prev) => ({ ...prev, rewrites: rest })); const nv = Math.min(vi, rest.length - 1); setVi(nv); setSbIdx(-1); setSide(null); try { if (onChanged) onChanged(cur.id); } catch (e) {} setHint("已删除该脚本及关联数据"); }).catch((e) => setHint("删除失败: " + e.message)).finally(() => setBusy(false)); } }); }; if (st.mode === "loading") return react.createElement("div", { className: "vvMask", onClick: onClose }, react.createElement("div", { className: "vvSmall", style: { width: 340 }, onClick: (e) => e.stopPropagation() }, react.createElement("div", { className: "vvLoading" }, "加载中…"))); if (st.mode === "empty") return react.createElement("div", { className: "vvMask", onClick: onClose }, react.createElement("div", { className: "vvSmall", style: { width: 380 }, onClick: (e) => e.stopPropagation() }, react.createElement("div", { className: "vvDim" }, st.err || "该脚本不存在或已删除"), react.createElement("div", { className: "vvActions" }, react.createElement("button", { type: "button", className: "vb vbPrimary", onClick: onClose }, "关闭")))); if (!cur) return null; const hasSource = st.vid > 0 && !!st.source; const sbVisible = ((st.sbAll || []).filter((s) => s.rewrite_id == null || String(s.rewrite_id) === String(cur.id))); const sbCur = sbVisible.length ? sbVisible[Math.min(Math.max(sbIdx, 0), sbVisible.length - 1)] : null; const xBtn = react.createElement("button", { type: "button", className: "vb", title: "关闭", onClick: onClose, style: { padding: "4px 8px", border: "none", background: "none", color: "#6b7280" } }, vvI("M18 6L6 18M6 6l12 12", 16)); const verCtrl = (label, len, idx, timeStr, onPrev, onNext, extra) => react.createElement(react.Fragment, null, react.createElement("span", { className: "vvCount" }, label + " " + (idx + 1) + "/" + len, len > 1 && idx >= len - 1 ? react.createElement("span", { className: "vvNewBadge" }, "最新") : null, timeStr ? react.createElement(react.Fragment, null, " · ", react.createElement("span", { style: { color: "#6b7280" } }, vvFmtT(timeStr))) : null), extra || null, len > 1 && react.createElement("button", { type: "button", className: "vb vbSm", disabled: idx <= 0, onClick: onPrev, title: "查看较旧版本" }, "← 较旧"), len > 1 && react.createElement("button", { type: "button", className: "vb vbSm", disabled: idx >= len - 1, onClick: onNext, title: "查看较新版本" }, "较新 →")); const aiHead = react.createElement("div", { className: "vvSecHead" }, react.createElement("span", { style: { color: "#24292f" } }, "AI脚本"), hasSource && react.createElement("button", { type: "button", className: "vb vbSm vbView vvSrcToggle" + (showSrc ? " vvSrcOn" : ""), title: showSrc ? "收起源脚本" : "展开源脚本", onClick: () => setShowSrc(!showSrc) }, vvI("M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z M14 2v4a2 2 0 0 0 2 2h4 M10 9H8 M16 13H8 M16 17H8", 14)), react.createElement("button", { type: "button", className: "vvSideToggle" + (showSide ? "" : " off"), title: showSide ? "收起脚本诊断/分镜提示词,只看 AI 脚本" : "展开脚本诊断/分镜提示词", onClick: () => setShowSide(!showSide) }, vvI(showSide ? "M13 17l5-5-5-5M6 17l5-5-5-5" : "M11 17l-5-5 5-5M18 17l-5-5 5-5", 16))); const srcSec = hasSource && react.createElement("div", { className: "vvSec" + (showSrc ? "" : " vvHidden") }, react.createElement("div", { className: "vvSecHead" }, "源脚本"), react.createElement("div", { className: "vvSecBody vvMd" }, vvMdSafe(st.source || "(暂无源脚本数据)")), react.createElement("div", { className: "vvSecFoot" })); const sideHead = react.createElement("div", { className: "vvSecHead vvSideHead" }, react.createElement("div", { className: "vvTabs" }, react.createElement("button", { type: "button", className: "vvTab" + (stab === "diag" ? " on" : ""), title: "该版本 AI 脚本的复盘评分与问题清单", onClick: () => { setStab("diag"); setSide(null); } }, "脚本诊断"), react.createElement("span", { className: "vvSep" }, "|"), react.createElement("button", { type: "button", className: "vvTab" + (stab === "sb" ? " on" : ""), title: "基于 AI 脚本生成的镜头级分镜提示词", onClick: () => { setStab("sb"); setSide(null); } }, "分镜提示词"))); let sideBody = null, sideFoot = null; if (!showSide) { /* 隐藏 */ } else if (!side) { sideBody = react.createElement("div", { className: "vvLoading" }, "加载中…"); } else if (side.kind === "diag") { const totalFin = Number.isFinite(side.total); sideBody = react.createElement("div", null, react.createElement("div", { className: "vvScoreBox" }, react.createElement("div", { className: "vvScoreBoxTitle" }, "AI 总分 " + (totalFin ? side.total : Number(side.rv.total)), side.rv.type === "compare" && Number.isFinite(side.srcTotal) ? "(对比源脚本 " + side.srcTotal + ")" : null, side.rv.verdict ? react.createElement("span", { style: { color: totalFin ? vvScoreColor(side.total) : "#6b7280" } }, " · " + side.rv.verdict) : null), side.dims.map((d) => react.createElement("div", { className: "vvScoreRow", key: d.name }, react.createElement("span", { className: "vvScoreName" }, d.name), react.createElement("div", { className: "vvScoreBar" }, react.createElement("div", { className: "vvScoreFill", style: { width: (d.val == null ? 0 : d.val) + "%", background: d.val == null ? "#eee" : vvScoreColor(d.val) } })), react.createElement("span", { className: "vvScoreVal", style: { color: d.val == null ? "#6b7280" : vvScoreColor(d.val) } }, d.val == null ? "—" : d.val)))), side.issues.length ? react.createElement("div", { className: "vvIssues" }, react.createElement("div", { className: "vvIssueTitle" }, "问题清单"), side.issues.map((it, ix) => react.createElement("div", { className: "vvIssueRow", key: "i" + ix }, react.createElement("span", { style: { color: it.severity === "高" ? "#d9534f" : it.severity === "中" ? "#d99a2b" : "#3a9e6d", fontWeight: 600, flex: "none" } }, it.severity || "低"), react.createElement("span", null, it.issue || ""), it.suggestion ? react.createElement("span", { style: { color: "#6b7280" } }, "→ " + it.suggestion) : null))) : null, side.summary ? react.createElement("div", { className: "vvSummary" }, "综合评语:" + side.summary) : null, side.rv.content ? react.createElement("div", { className: "vvMd", style: { marginTop: 6 } }, vvMdSafe(side.rv.content)) : null); sideFoot = react.createElement("div", { className: "vvSecFoot" }, react.createElement("button", { type: "button", className: "vb vbSm" + (side.rv ? "" : " vbView"), onClick: fireReview, disabled: busy }, vvI("M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8M21 3v5h-5M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16M8 16H3v5", 14), side.rv ? "更新诊断" : "开始诊断")); } else if (side.kind === "empty") { sideBody = react.createElement("div", { className: "vvEmpty" }, "该版本 AI 脚本尚未诊断"); sideFoot = react.createElement("div", { className: "vvSecFoot" }, react.createElement("button", { type: "button", className: "vb vbSm vbView", onClick: fireReview, disabled: busy }, vvI("M13 2 3 14h9l-1 8 10-12h-9l1-8", 13), "开始诊断")); } else if (side.kind === "sb") { const idx = Math.min(Math.max(sbIdx < 0 ? Math.max(0, side.vis.length - 1) : sbIdx, 0), side.vis.length - 1); const sb = side.vis[idx]; sideBody = sb ? react.createElement("div", { className: "vvMd" }, vvMdSafe(sb.script_text || "")) : null; sideFoot = react.createElement("div", { className: "vvSecFoot" }, react.createElement("span", { className: "vvCount" }, "分镜 " + (idx + 1) + "/" + side.vis.length, side.vis.length > 1 && idx >= side.vis.length - 1 ? react.createElement("span", { className: "vvNewBadge" }, "最新") : null, sb && sb.created_time ? react.createElement(react.Fragment, null, " · ", vvFmtT(sb.created_time)) : null), react.createElement("button", { type: "button", className: "vb vbSm", disabled: idx <= 0, onClick: () => setSbIdx(idx - 1), title: "查看较旧版本" }, "← 较旧"), react.createElement("button", { type: "button", className: "vb vbSm", disabled: idx >= side.vis.length - 1, onClick: () => setSbIdx(idx + 1), title: "查看较新版本" }, "较新 →"), st.vid > 0 && react.createElement("button", { type: "button", className: "vb vbSm", onClick: () => fireGenSb(true), disabled: busy }, vvI("M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8M21 3v5h-5M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16M8 16H3v5", 13), "重新生成")); } else if (side.kind === "sbEmpty") { sideBody = react.createElement("div", { className: "vvEmpty" }, st.vid > 0 ? "(暂无分镜提示词)" : "该脚本为原创选题(无关联视频),暂不支持分镜提示词"); if (st.vid > 0) sideFoot = react.createElement("div", { className: "vvSecFoot" }, react.createElement("button", { type: "button", className: "vb vbSm vbView", onClick: () => fireGenSb(false), disabled: busy }, vvI("M13 2 3 14h9l-1 8 10-12h-9l1-8", 14), "生成分镜")); } const aiBody = react.createElement("div", { className: "vvSecBody vvMd" }, vvMdSafe(cur.script_text || "(暂无内容)")); const aiFoot = react.createElement("div", { className: "vvSecFoot" }, react.createElement("button", { type: "button", className: "vb vbSm vbDel", title: "删除当前脚本(同时删除其诊断复盘与分镜提示词数据,不可恢复)", onClick: openDeleteAsk, disabled: busy }, vvI("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-2V6h14", 13)), hint ? react.createElement("span", { className: "vvHint", style: { marginLeft: 6 } }, hint) : null, st.rewrites.length > 1 && verCtrl("版本", st.rewrites.length, vi, cur.created_time, () => { setVi(Math.max(0, vi - 1)); setSbIdx(-1); setSide(null); }, () => { setVi(Math.min(st.rewrites.length - 1, vi + 1)); setSbIdx(-1); setSide(null); }, null)); return react.createElement("div", { className: "vvMask", onClick: (e) => { if (e.target === e.currentTarget) onClose(); } }, react.createElement("div", { className: "vvModal" }, react.createElement("div", { className: "vvTitleRow" }, react.createElement("span", { className: "vvT" }, react.createElement("span", { className: "vvIcon" }, vvI("M12 20h9M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z", 15)), vvTitleQ(st.title || cur.title || fallbackTitle || "AI 脚本"), st.acc ? react.createElement("span", { className: "vvMeta" }, " · " + st.acc) : null), xBtn), hint ? react.createElement("div", { style: { flex: "none", color: "#b7791f", fontSize: 12, margin: "-6px 0 8px", lineHeight: 1.6 } }, hint) : null, react.createElement("div", { className: "vvStack" }, srcSec, react.createElement("div", { className: "vvSec", style: { flex: showSide ? "1.2" : "1" } }, aiHead, aiBody, aiFoot), react.createElement("div", { className: "vvSec" + (showSide ? "" : " vvHidden") }, sideHead, react.createElement("div", { className: "vvSecBody" }, sideBody), sideFoot))), ask && react.createElement("div", { className: "vvMask", style: { zIndex: 1100, background: "rgba(0,0,0,.25)" }, onClick: () => setAsk(null) }, react.createElement("div", { className: "vvSmall", onClick: (e) => e.stopPropagation() }, react.createElement("h3", null, vvI("M13 2 3 14h9l-1 8 10-12h-9l1-8", 18, ask.danger ? "#d9534f" : "#2f6fed"), ask.title), ask.desc ? react.createElement("p", { className: "vvDim" }, ask.desc) : null, ask.target ? react.createElement("div", { className: "vvTarget" }, ask.target) : null, ask.warn ? react.createElement("p", { className: "vvWarn" }, "⚠️ " + ask.warn) : null, react.createElement("div", { className: "vvActions" }, react.createElement("button", { type: "button", className: "vb", onClick: () => setAsk(null) }, "取消"), react.createElement("button", { type: "button", className: "vb " + (ask.danger ? "vbRed" : "vbPrimary"), onClick: () => { const fn = ask.onOk; setAsk(null); try { fn(); } catch (e) {} } }, ask.okText || "确认执行"))))); } //#endregion function RecentScriptsV1({ onOpenAll }) { const [rows, setRows] = react.useState(null); const [open, setOpen] = react.useState(null); react.useEffect(() => { let alive = true; const fetchText = (r) => { const base = { id: r.id, video_id: r.video_id, title: r.title || "", account_name: r.account_name || "", created_time: r.created_time || "", score: r.score }; if (r.video_id > 0) { const pScript = fetch("/mcn/api/video/script?videoId=" + r.video_id).then((x) => x.json()).then((d) => { const rw = ((d.rewrites || []).find((w) => w.id === r.id)) || {}; return { script_text: rw.script_text || "" }; }).catch(() => ({ script_text: "" })); const pDetail = fetch("/mcn/api/video/detail?videoId=" + r.video_id).then((x) => x.json()).then((d) => ({ acc: (d.video && d.video.account_name) || "" })).catch(() => ({ acc: "" })); return Promise.all([pScript, pDetail]).then(([s, dv]) => ({ ...base, script_text: s.script_text, account_name: base.account_name || dv.acc || "" })); } return fetch("/mcn/api/custom-script?id=" + r.id).then((x) => x.json()).then((d) => ({ ...base, script_text: (d.script && d.script.script_text) || d.script_text || "", account_name: base.account_name || (d.script && d.script.account_name) || "" })).catch(() => ({ ...base, script_text: "" })); }; const assemble = (list) => { const picked = (list || []).filter((r) => r && (r.status === "done" || !r.status) && r.script_len > 0).sort((a, b) => String(b.created_time || "").localeCompare(String(a.created_time || "")) || (b.id - a.id)).slice(0, 8); if (!picked.length) { if (alive) setRows([]); return; } const jobs = picked.map((r) => { let ra = r.review_ai; if (typeof ra === "string") { try { ra = JSON.parse(ra); } catch (e) { ra = null; } } const scoreRaw = ra && ra.total; const sc = scoreRaw != null && scoreRaw !== "" && Number.isFinite(Number(scoreRaw)) ? Number(scoreRaw) : null; return fetchText({ ...r, review_ai: ra, score: sc }); }); Promise.all(jobs).then((items) => { if (alive) setRows(items.filter((it) => it.script_text)); }); }; const assembleFallback = () => { fetch("/mcn/api/rewrite/list?limit=40").then((r) => r.json()).then((d) => { if (alive) assemble(d.items || []); }).catch(() => { if (alive) setRows([]); }); }; fetch("/mcn/api/recent-scripts?limit=8") .then((r) => r.json()) .then((d) => { if (alive) { if (d.ok && d.items && d.items.length) { setRows(d.items); } else { assembleFallback(); } } }) .catch(() => { if (alive) assembleFallback(); }); return () => { alive = false; }; }, []); const scoreColor = (v) => (v >= 75 ? "#3a9e6d" : v >= 60 ? "#d99a2b" : "#d9534f"); const list = rows === null ? react.createElement("span", { style: { color: "#6b7280" } }, "加载中…") : rows.length === 0 ? react.createElement("span", { style: { color: "#6b7280" } }, "暂无 AI 写剧本") : react.createElement("div", { className: "v1rlist" }, rows.map((s) => { const title = v1CleanTitle(s.title || s.video_title) || "未命名剧本"; const meta = [s.account_name, s.created_time ? v1FmtDate(s.created_time) : ""].filter(Boolean).join(" | "); const preview = v1PreviewLines(s.script_text, 5); const sc = Number(s.score); const hasScore = s.score !== null && s.score !== undefined && s.score !== "" && Number.isFinite(sc); return react.createElement("button", { type: "button", className: "v1rcard", key: s.id, title: "点击查看完整剧本", onClick: () => setOpen(s) }, react.createElement("span", { className: "v1rcT" }, title), react.createElement("span", { className: "v1rcP" }, preview.map((l, i) => react.createElement("span", { className: "v1rcL", key: "l" + i }, l))), react.createElement("span", { className: "v1rcF" }, react.createElement("span", { className: "v1rcM" }, meta), hasScore && react.createElement("span", { className: "v1rcS", style: { background: scoreColor(sc) + "1a", color: scoreColor(sc) } }, "评分 " + (sc % 1 === 0 ? sc : sc.toFixed(1))))); })); return react.createElement("div", { className: "v1recents" }, react.createElement("div", { className: "v1rhead" }, react.createElement("h3", null, "AI脚本"), react.createElement("button", { type: "button", className: "v1all", onClick: () => { if (onOpenAll) onOpenAll(); } }, "查看全部")), list, open && react.createElement(ScriptViewerV1, { key: open.id, rewriteId: open.id, videoId: (open.video_id > 0 ? open.video_id : 0), fallbackTitle: (open.title || open.video_title || ""), accountName: (open.account_name || ""), onClose: () => setOpen(null) })); } //#endregion function McnPage({ onClose, side, onToggleSide }) { const [toast, setToast] = (0, react.useState)(null); const [helpOpen, setHelpOpen] = (0, react.useState)(false); const [moreOpen, setMoreOpen] = (0, react.useState)(false); const [menuAt, setMenuAt] = (0, react.useState)(null); const [creativeOpen, setCreativeOpen] = (0, react.useState)(false); const [creativePrompt, setCreativePrompt] = (0, react.useState)(""); const [creativeLoading, setCreativeLoading] = (0, react.useState)(false); // 当前创作目标达人(null = 通用创作) const [creativeAccount, setCreativeAccount] = (0, react.useState)(null); // 手动色调:dark / light / null(跟随系统),localStorage 持久化(下次打开保持) const [theme, setTheme] = (0, react.useState)(() => { try { const v = localStorage.getItem("mcnNav.theme"); return v === "dark" || v === "light" ? v : null; } catch (e) { return null; } }); const isDarkNow = theme === "dark" || (theme !== "light" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches); (0, react.useEffect)(() => { document.documentElement.style.colorScheme = isDarkNow ? "dark" : "light"; document.body.toggleAttribute("data-ds-dark-theme", isDarkNow); }, [isDarkNow]); const toggleTheme = () => { const next = isDarkNow ? "light" : "dark"; setTheme(next); try { localStorage.setItem("mcnNav.theme", next); } catch (e) {} }; // 工作台视图:home = 入口网格;entry: = 外部插件注册的功能页;ai-videos = AI写作提升视频列表 const [view, setView] = (0, react.useState)("home"); // 程序化导航来源栈(__mcnNav.back 逐层返回用;ref 避免 useEffect 闭包捕获旧值) const viewRef = (0, react.useRef)(view); viewRef.current = view; // 导航栈:每项 { view, params } —— open/卡片点击入栈,back 弹栈恢复, // 支持多层跳转(如 首页→热门账号→账号详情→视频详情 后逐层返回,参数一并恢复) const navStackRef = (0, react.useRef)([]); // 首页卡片/入口进入:入栈当前页(供 back 逐层返回),再切到目标视图(view 为完整视图名,如 "entry:douyin-accounts" 或 "ai-videos") const openEntry = (view) => { navStackRef.current.push({ view: viewRef.current, params: entryParamsRef.current }); setEntryParams(null); setView(view); }; // 数据预览统计(后端 60s 缓存,前端仅挂载时拉取一次) const [stats, setStats] = (0, react.useState)(null); (0, react.useEffect)(() => { let alive = true; fetch("/mcn/api/stats") .then((r) => r.json()) .then((d) => { if (alive && d.ok) setStats(d); }) .catch(() => {}); return () => { alive = false; }; }, []); // AI写作提升:全部视频列表(已解析优先 → 点赞降序) const [aiVideos, setAiVideos] = (0, react.useState)([]); const [aiTotal, setAiTotal] = (0, react.useState)(0); const [aiPage, setAiPage] = (0, react.useState)(1); const [aiSearch, setAiSearch] = (0, react.useState)(""); const [aiParsed, setAiParsed] = (0, react.useState)(""); const [aiLoading, setAiLoading] = (0, react.useState)(false); // 账号预筛与时间窗口:来源=账号列表/详情 __mcnNav.openView('ai-videos',{accountId,accountName}) 或本页工具栏;作用于 rewrite 与 custom 两个 tab(null/"")=全部 const [aiScope, setAiScope] = (0, react.useState)(null); const [aiDays, setAiDays] = (0, react.useState)(""); const appliedScopeRef = (0, react.useRef)(""); const loadAiVideos = (p = 1, s = aiSearch, pr = aiParsed, silent = false, acc, days) => { if (!silent) setAiLoading(true); // acc/days 显式传入(含 null/"")用于「清除筛选」等场景;省略时取当前 state const scopeId = acc === undefined ? (aiScope ? String(aiScope.id) : "") : (acc ? String(acc.id) : ""); const daysQ = days === undefined ? (aiDays || "") : (days || ""); fetch("/mcn/api/videos?page=" + p + "&pageSize=20&search=" + encodeURIComponent(s) + "&parsed=" + encodeURIComponent(pr) + (scopeId ? "&account=" + encodeURIComponent(scopeId) : "") + (daysQ ? "&days=" + encodeURIComponent(daysQ) : "")) .then((r) => r.json()) .then((d) => { if (d.ok) { setAiVideos(d.items || []); setAiTotal(d.total || 0); setAiPage(d.page || 1); } }) .catch(() => {}) .finally(() => { if (!silent) setAiLoading(false); }); }; // 清除账号预筛与时间窗口(AiVideosPanel 顶部「清除筛选」) const clearAiScope = () => { appliedScopeRef.current = ""; setAiScope(null); setAiDays(""); loadAiVideos(1, aiSearch, aiParsed, false, null, ""); }; // 打开「AI写脚本」视图(首页卡片 / 最近AI脚本「查看全部」) const openAiVideosView = () => { appliedScopeRef.current = ""; setAiScope(null); setAiDays(""); loadAiVideos(1); openEntry("ai-videos"); }; // 改写状态轮询:列表视图下每 10s 静默刷新(网页/聊天窗口触发的改写任务都能实时反映「执行中」与完成状态) (0, react.useEffect)(() => { if (view !== "ai-videos") return; const t = setInterval(() => loadAiVideos(aiPage, aiSearch, aiParsed, true), 10000); return () => clearInterval(t); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [view, aiPage, aiSearch, aiParsed, aiScope, aiDays]); // 视频解析:提交 MCP 解析任务(后端并发上限 8) const analyzeVideo = (row) => { fetch("/mcn/api/video/analyze", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ videoId: row.id }), }) .then((r) => r.json()) .then((d) => { if (d.ok) { setToast(d.message || "已提交视频解析"); loadAiVideos(aiPage); } else { setToast(d.error || "解析提交失败"); } }) .catch(() => setToast("解析提交失败")); }; // 视频改写:调用 short-video-script 技能基于账号设定+选题改写 S9 故事脚本(后端校验并发 8/信息齐备) const rewriteVideo = (row) => { fetch("/mcn/api/video/rewrite", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ videoId: row.id }), }) .then((r) => r.json()) .then((d) => { if (d.ok) { setToast(d.message || "已提交改写"); // 立即本地标记该行为执行中(避免刷新竞态),随后轮询接管 setAiVideos((prev) => prev.map((v) => (v.id === row.id ? { ...v, rewrite_running: true } : v))); loadAiVideos(aiPage); } else { setToast(d.error || "改写提交失败"); } }) .catch(() => setToast("改写提交失败")); }; // 外部插件注册的功能入口(window.__mcnEntries),即插即用 const entries = window.__mcnEntries || []; // 程序化导航参数(如账号详情页的 accountId),由 __mcnNav.open(entryId, params) 携带 const [entryParams, setEntryParams] = (0, react.useState)(null); // entryParams 同步 ref(nav.open 在 useEffect 闭包内,读 state 会拿到旧值,需经 ref) const entryParamsRef = (0, react.useRef)(null); entryParamsRef.current = entryParams; // 账号预筛应用:外部 openView('ai-videos',{accountId,accountName}) 进入时按账号拉取 rewrite 列表并重置时间窗口(每个账号上下文仅一次;清除筛选后重置标记可再次应用) (0, react.useEffect)(() => { if (view !== "ai-videos") return; const p = entryParams; const accId = p && p.accountId != null ? String(p.accountId) : ""; if (accId && appliedScopeRef.current !== accId) { appliedScopeRef.current = accId; const scope = { id: accId, name: p.accountName || "" }; setAiScope(scope); setAiDays(""); loadAiVideos(1, aiSearch, aiParsed, false, scope, ""); } /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [view, entryParams]); const activeEntry = view.startsWith("entry:") ? entries.find((e) => e.id === view.slice(6)) : undefined; const scheduleEntry = (entries || []).find((e) => e.id === "mcn-schedule"); const socialEntry = (entries || []).find((e) => e.id === "social-workbench"); // 程序化导航 API:供其他插件(如账号列表行点击)跳转到指定功能页并携带参数 (0, react.useEffect)(() => { const nav = { open: (entryId, params) => { if (!window.__mcnEntries?.some((e) => e.id === entryId)) return false; // 入栈当前页 + 其参数(供 back 原样返回,支持多层跳转) navStackRef.current.push({ view: viewRef.current, params: entryParamsRef.current }); setEntryParams(params ?? null); setView("entry:" + entryId); return true; }, // 逐层返回来源页(从详情/子页面返回时回到进入前的页面与参数,如账号详情→视频详情→返回) back: () => { const from = navStackRef.current.pop(); setEntryParams(from ? from.params : null); setView(from ? from.view : "home"); }, goHome: () => { navStackRef.current = []; setEntryParams(null); setView("home"); }, // 切到任意内部视图(如 AI会话/AI写脚本),可携带 params(如 {accountId,accountName} 账号预筛) openView: (v, params) => { navStackRef.current.push({ view: viewRef.current, params: entryParamsRef.current }); setEntryParams(params ?? null); setView(v); }, }; window.__mcnNav = nav; return () => { if (window.__mcnNav === nav) delete window.__mcnNav; }; }, []); // 打开创作弹窗:可选传入达人,自动带入其信息生成创作需求 const openCreative = (account) => { setCreativeAccount(account); // 输入框只留"需求补充"(可空);达人信息通过 state 携带,提交时拼接 setCreativePrompt(""); setCreativeOpen(true); }; const submitCreative = () => { const extra = creativePrompt.trim(); // 组装最终 prompt:有达人则先带达人信息,再拼用户补充需求 let prompt = ""; if (creativeAccount) { const info = [ `账号名:${creativeAccount.account_name}`, creativeAccount.track ? `赛道:${creativeAccount.track}` : "", creativeAccount.content ? `账号定位:${creativeAccount.content}` : "", ].filter(Boolean).join(";"); prompt = `请为达人「${creativeAccount.account_name}」创作一条短视频脚本。\n达人信息:${info}\n请先进行账号设定分析,再生成完整脚本。`; } if (extra) { prompt = prompt ? `${prompt}\n补充需求:${extra}` : extra; } if (!prompt) { setToast("请输入创作需求"); return; } setCreativeLoading(true); fetch("/mcn/api/creative", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt }), }) .then((r) => r.json()) .then((d) => { if (!d.ok) throw new Error(d.error || "触发失败"); setToast(d.message || "已开始创作,请切换到对应会话查看"); setCreativeOpen(false); setCreativePrompt(""); setCreativeAccount(null); }) .catch((e) => setToast("创作触发失败: " + e.message)) .finally(() => setCreativeLoading(false)); }; (0, react.useEffect)(() => { if (!toast) return; const timer = setTimeout(() => setToast(null), 3000); return () => clearTimeout(timer); }, [toast]); return (0, react_jsx_runtime.jsxs)("main", { className: McnNav_module_css_default.page, "aria-label": "MCN 工作台", children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.body, children: [(0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.header, children: [(0, react_jsx_runtime.jsxs)("nav", { className: McnNav_module_css_default.crumb, "aria-label": "面包屑", children: view === "home" ? [(0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.crumbCurrent, children: "MCN 工作台" })] : [(0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.crumbLink, onClick: () => { setEntryParams(null); setView("home"); }, children: "MCN 工作台" }), (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.crumbSep, children: "/" }), (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.crumbCurrent, children: activeEntry ? activeEntry.title : (view === "ranking" ? "热点数据" : view === "ai-videos" ? "AI写脚本" : view === "script-review" ? "脚本诊断" : "功能页") })] }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.themeBtn, "aria-label": isDarkNow ? "切换到浅色" : "切换到深色", title: isDarkNow ? "切换到浅色" : "切换到深色", onClick: toggleTheme, children: isDarkNow ? (0, react_jsx_runtime.jsx)(IconSun, {}) : (0, react_jsx_runtime.jsx)(IconMoon, {}) }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.sideBtn, "aria-label": side === "right" ? "面板在右侧,点击移到左侧" : "面板在左侧,点击移到右侧", title: side === "right" ? "面板在右侧,点击移到左侧" : "面板在左侧,点击移到右侧", onClick: onToggleSide, children: "⇄" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.themeBtn, title: "功能帮助", "aria-label": "功能帮助", onClick: () => setHelpOpen(true), children: (0, react_jsx_runtime.jsx)(V1Icon, { icon: "help", size: 15 }) }), scheduleEntry && (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.themeBtn, title: "计划任务:定时提醒/周期任务", "aria-label": "计划任务", onClick: () => openEntry("entry:mcn-schedule"), children: (0, react_jsx_runtime.jsx)(V1Icon, { icon: "clock", size: 15 }) }), socialEntry && (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.themeBtn, title: "社媒工作台:Easel 内容工作台", "aria-label": "社媒工作台", onClick: () => openEntry("entry:social-workbench"), children: (0, react_jsx_runtime.jsx)(V1Icon, { icon: "globe", size: 15 }) }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.themeBtn, title: "更多功能", "aria-label": "更多功能", onClick: (e) => { const r = e.currentTarget.getBoundingClientRect(); setMenuAt({ x: Math.max(8, Math.round(r.right) - 172), y: Math.round(r.bottom) + 6 }); setMoreOpen((v) => !v); }, children: (0, react_jsx_runtime.jsx)(V1Icon, { icon: "more", size: 15 }) }) ] }), view === "home" && (0, react_jsx_runtime.jsx)(HomeView, { stats: stats, entries: entries, openEntry: openEntry, openAiVideos: openAiVideosView }), view === "ranking" && (0, react_jsx_runtime.jsx)(RankingSection, {}), view === "ai-videos" && (0, react_jsx_runtime.jsx)(AiVideosPanel, { videos: aiVideos, total: aiTotal, page: aiPage, loading: aiLoading, search: aiSearch, parsed: aiParsed, onSearch: (s) => { setAiSearch(s); loadAiVideos(1, s, aiParsed); }, onParsedChange: (pr) => { setAiParsed(pr); loadAiVideos(1, aiSearch, pr); }, days: aiDays, account: aiScope, onDaysChange: (d) => { setAiDays(d); loadAiVideos(1, aiSearch, aiParsed, false, aiScope, d); }, onClearAccount: clearAiScope, onPage: (p) => loadAiVideos(p), onBack: () => { window.__mcnNav?.back(); }, onCreate: (row) => rewriteVideo(row), onAnalyze: (row) => analyzeVideo(row), onRewritesChanged: () => loadAiVideos(aiPage), }), view === "script-review" && (0, react_jsx_runtime.jsx)(ScriptReviewPanel, { onBack: () => { window.__mcnNav?.back(); }, }), view === "ai-sessions" && (0, react_jsx_runtime.jsx)(AiSessionsView, { onBack: () => { window.__mcnNav?.back(); }, }), view.startsWith("entry:") && activeEntry && (0, react_jsx_runtime.jsx)(activeEntry.component, { params: entryParams })] }), helpOpen && react.createElement("div", { style: { position: "fixed", inset: 0, background: "rgba(0,0,0,.35)", zIndex: 2000, display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }, onClick: () => setHelpOpen(false) }, react.createElement("div", { style: { background: "#fff", borderRadius: 12, maxWidth: 640, width: "100%", maxHeight: "82vh", overflow: "auto", padding: "18px 22px", boxShadow: "0 8px 30px rgba(0,0,0,.18)" }, onClick: (e) => e.stopPropagation() }, react.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 10 } }, react.createElement("span", { style: { fontSize: 18, fontWeight: 700, color: "#24292f", flex: 1 } }, "功能帮助"), react.createElement("button", { type: "button", style: { border: "none", background: "none", fontSize: 18, cursor: "pointer", color: "#6b7280" }, title: "关闭", onClick: () => setHelpOpen(false) }, "✕")), react.createElement("div", { style: { color: "#6b7280", fontSize: 13, marginBottom: 12 } }, "直接对 AI 说需求即可,无需记指令(以下为常见问法)。"), V1_HELP_GROUPS.map((g) => react.createElement("div", { key: g[0] }, react.createElement("div", { className: "v1helpTitle" }, g[0]), react.createElement("div", { className: "v1helpDesc" }, g[1]), react.createElement("div", { className: "v1helpQa" }, g[2].map((q) => react.createElement("div", { key: q }, "• ", react.createElement("b", null, q)))))))), moreOpen && menuAt && react.createElement("div", { style: { position: "fixed", inset: 0, zIndex: 2999 }, onClick: () => setMoreOpen(false) }), moreOpen && menuAt && react.createElement("div", { style: { position: "fixed", left: menuAt.x, top: menuAt.y, zIndex: 3000, minWidth: 158, background: "#fff", border: "1px solid #e3e6ea", borderRadius: 10, boxShadow: "0 6px 24px rgba(0,0,0,.14)", padding: "6px", display: "flex", flexDirection: "column" } }, react.createElement("button", { type: "button", style: { border: "none", background: "none", textAlign: "left", padding: "8px 12px", borderRadius: 8, fontSize: 14, color: "#24292f", cursor: "pointer", fontFamily: "inherit" }, onClick: () => { setMoreOpen(false); openEntry("entry:mcn-schedule"); } }, "自动任务"), react.createElement("button", { type: "button", style: { border: "none", background: "none", textAlign: "left", padding: "8px 12px", borderRadius: 8, fontSize: 14, color: "#d9534f", cursor: "pointer", fontFamily: "inherit" }, onClick: () => { setMoreOpen(false); onClose(); } }, "关闭面板")), creativeOpen && (0, react_jsx_runtime.jsxs)("div", { className: McnNav_module_css_default.mask, onClick: () => setCreativeOpen(false), 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: creativeAccount ? `为「${creativeAccount.account_name}」创作短视频脚本` : "短视频脚本创作" }), creativeAccount && (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalAccount, children: [(0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.modalAccountName, children: creativeAccount.account_name }), creativeAccount.track && (0, react_jsx_runtime.jsx)("span", { className: McnNav_module_css_default.modalAccountTrack, children: creativeAccount.track })] }), (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.modalText, children: creativeAccount ? "已带入该达人信息,确认创作时自动携带。可在下方补充创作需求(可选):" : "将调用短视频脚本创作技能(会话:dsh_short-video-script)为你创作脚本。请输入创作需求:" }), (0, react_jsx_runtime.jsx)("textarea", { className: McnNav_module_css_default.textarea, rows: 4, placeholder: creativeAccount ? "可选:补充创作需求(如时长、风格、广告植入等)" : "例如:为美妆账号写一个 30 秒种草脚本,产品是 XX 面膜", value: creativePrompt, onChange: (e) => setCreativePrompt(e.target.value) }), (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: () => setCreativeOpen(false), disabled: creativeLoading, children: "取消" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: McnNav_module_css_default.btn + " " + McnNav_module_css_default.btnPrimary, onClick: submitCreative, disabled: creativeLoading, children: creativeLoading ? "触发中…" : "开始创作" })] })] })] }), toast && (0, react_jsx_runtime.jsx)("div", { className: McnNav_module_css_default.toast, children: toast })] }); } //#endregion //#region plugin entry const inject = ["slots", "sessions"]; function apply(ctx) { ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({ name: "sidebar.footer.action", id: "mcn-nav", order: 60, inject: () => ({ openSession: (sessionId) => { ctx.sessions.open(sessionId); } }) }, McnNavTrigger)); // 独立侧边栏入口:AI会话(MCN工作台 下方单独一行) ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({ name: "sidebar.footer.action", id: "mcn-sessions", order: 61, inject: () => ({}) }, AiSessionsTrigger)); } //#endregion exports.apply = apply; exports.inject = inject; return module.exports; } });