From 824e1f779c5f092bde77f6bdbc98ee792fc0384c Mon Sep 17 00:00:00 2001 From: Eeveid <448859157@qq.com> Date: Sat, 28 Mar 2026 19:34:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=AD=E6=96=87=E4=B9=B1?= =?UTF-8?q?=E7=A0=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dist/assets/{index-sgHRZUw3.js => index-DoCMU-eK.js} | 2 +- frontend/dist/index.html | 2 +- frontend/src/App.vue | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) rename frontend/dist/assets/{index-sgHRZUw3.js => index-DoCMU-eK.js} (98%) diff --git a/frontend/dist/assets/index-sgHRZUw3.js b/frontend/dist/assets/index-DoCMU-eK.js similarity index 98% rename from frontend/dist/assets/index-sgHRZUw3.js rename to frontend/dist/assets/index-DoCMU-eK.js index e80a406..01cff60 100644 --- a/frontend/dist/assets/index-sgHRZUw3.js +++ b/frontend/dist/assets/index-DoCMU-eK.js @@ -14,4 +14,4 @@ * @vue/runtime-dom v3.5.30 * (c) 2018-present Yuxi (Evan) You and Vue contributors * @license MIT -**/let Ns;const Vi=typeof window<"u"&&window.trustedTypes;if(Vi)try{Ns=Vi.createPolicy("vue",{createHTML:e=>e})}catch{}const _o=Ns?e=>Ns.createHTML(e):e=>e,Qa="http://www.w3.org/2000/svg",ec="http://www.w3.org/1998/Math/MathML",ct=typeof document<"u"?document:null,qi=ct&&ct.createElement("template"),tc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const i=t==="svg"?ct.createElementNS(Qa,e):t==="mathml"?ct.createElementNS(ec,e):n?ct.createElement(e,{is:n}):ct.createElement(e);return e==="select"&&s&&s.multiple!=null&&i.setAttribute("multiple",s.multiple),i},createText:e=>ct.createTextNode(e),createComment:e=>ct.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>ct.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,i,o){const a=n?n.previousSibling:t.lastChild;if(i&&(i===o||i.nextSibling))for(;t.insertBefore(i.cloneNode(!0),n),!(i===o||!(i=i.nextSibling)););else{qi.innerHTML=_o(s==="svg"?`${e}`:s==="mathml"?`${e}`:e);const c=qi.content;if(s==="svg"||s==="mathml"){const f=c.firstChild;for(;f.firstChild;)c.appendChild(f.firstChild);c.removeChild(f)}t.insertBefore(c,n)}return[a?a.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},nc=Symbol("_vtc");function sc(e,t,n){const s=e[nc];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Gi=Symbol("_vod"),ic=Symbol("_vsh"),rc=Symbol(""),oc=/(?:^|;)\s*display\s*:/;function lc(e,t,n){const s=e.style,i=ae(n);let o=!1;if(n&&!i){if(t)if(ae(t))for(const a of t.split(";")){const c=a.slice(0,a.indexOf(":")).trim();n[c]==null&&zn(s,c,"")}else for(const a in t)n[a]==null&&zn(s,a,"");for(const a in n)a==="display"&&(o=!0),zn(s,a,n[a])}else if(i){if(t!==n){const a=s[rc];a&&(n+=";"+a),s.cssText=n,o=oc.test(n)}}else t&&e.removeAttribute("style");Gi in e&&(e[Gi]=o?s.display:"",e[ic]&&(s.display="none"))}const Ji=/\s*!important$/;function zn(e,t,n){if(H(n))n.forEach(s=>zn(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=ac(e,t);Ji.test(n)?e.setProperty(Ct(s),n.replace(Ji,""),"important"):e[s]=n}}const Yi=["Webkit","Moz","ms"],ws={};function ac(e,t){const n=ws[t];if(n)return n;let s=Ae(t);if(s!=="filter"&&s in e)return ws[t]=s;s=Qn(s);for(let i=0;ixs||(dc.then(()=>xs=0),xs=Date.now());function hc(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;nt(mc(s,n.value),t,5,[s])};return n.value=e,n.attached=pc(),n}function mc(e,t){if(H(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>i=>!i._stopped&&s&&s(i))}else return t}const nr=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,gc=(e,t,n,s,i,o)=>{const a=i==="svg";t==="class"?sc(e,s,a):t==="style"?lc(e,n,s):Xn(t)?Us(t)||uc(e,t,n,s,o):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):vc(e,t,s,a))?(Qi(e,t,s),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Zi(e,t,s,a,o,t!=="value")):e._isVueCE&&(yc(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!ae(s)))?Qi(e,Ae(t),s,o,t):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),Zi(e,t,s,a))};function vc(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&nr(t)&&K(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const i=e.tagName;if(i==="IMG"||i==="VIDEO"||i==="CANVAS"||i==="SOURCE")return!1}return nr(t)&&ae(n)?!1:t in e}function yc(e,t){const n=e._def.props;if(!n)return!1;const s=Ae(t);return Array.isArray(n)?n.some(i=>Ae(i)===s):Object.keys(n).some(i=>Ae(i)===s)}const sr=e=>{const t=e.props["onUpdate:modelValue"]||!1;return H(t)?n=>jn(t,n):t};function _c(e){e.target.composing=!0}function ir(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const Is=Symbol("_assign");function rr(e,t,n){return t&&(e=e.trim()),n&&(e=js(e)),e}const bc={created(e,{modifiers:{lazy:t,trim:n,number:s}},i){e[Is]=sr(i);const o=s||i.props&&i.props.type==="number";Wt(e,t?"change":"input",a=>{a.target.composing||e[Is](rr(e.value,n,o))}),(n||o)&&Wt(e,"change",()=>{e.value=rr(e.value,n,o)}),t||(Wt(e,"compositionstart",_c),Wt(e,"compositionend",ir),Wt(e,"change",ir))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:s,trim:i,number:o}},a){if(e[Is]=sr(a),e.composing)return;const c=(o||e.type==="number")&&!/^0\d/.test(e.value)?js(e.value):e.value,f=t??"";c!==f&&(document.activeElement===e&&e.type!=="range"&&(s&&t===n||i&&e.value.trim()===f)||(e.value=f))}},wc=["ctrl","shift","alt","meta"],xc={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>wc.some(n=>e[`${n}Key`]&&!t.includes(n))},Pn=(e,t)=>{if(!e)return e;const n=e._withMods||(e._withMods={}),s=t.join(".");return n[s]||(n[s]=(i,...o)=>{for(let a=0;a{const n=e._withKeys||(e._withKeys={}),s=t.join(".");return n[s]||(n[s]=i=>{if(!("key"in i))return;const o=Ct(i.key);if(t.some(a=>a===o||Ic[a]===o))return e(i)})},Sc=Ce({patchProp:gc},tc);let or;function Cc(){return or||(or=Pa(Sc))}const Tc=(...e)=>{const t=Cc().createApp(...e),{mount:n}=t;return t.mount=s=>{const i=kc(s);if(!i)return;const o=t._component;!K(o)&&!o.render&&!o.template&&(o.template=i.innerHTML),i.nodeType===1&&(i.textContent="");const a=n(i,!1,$c(i));return i instanceof Element&&(i.removeAttribute("v-cloak"),i.setAttribute("data-v-app","")),a},t};function $c(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function kc(e){return ae(e)?document.querySelector(e):e}const Mc={class:"footer"},Rc={__name:"AppFooter",emits:["request-admin"],setup(e,{emit:t}){const n=t,s=oe(0);let i=null;Qs(()=>{i&&window.clearTimeout(i)});function o(){s.value+=1,s.value===1&&(i=window.setTimeout(()=>{s.value=0,i=null},2e3)),s.value>=5&&(i&&(window.clearTimeout(i),i=null),s.value=0,n("request-admin"))}return(a,c)=>(j(),V("div",Mc,[y("div",null,[c[0]||(c[0]=nn(" © 2026 AirShare Pro. All rights reserved. ",-1)),c[1]||(c[1]=y("span",{class:"divider-line"},"|",-1)),y("span",{id:"admin-trigger",title:"点击 5 次进入后台",onClick:o},"V 1.0.0")]),c[2]||(c[2]=y("div",{style:{"font-size":"12px","margin-top":"4px"}},[y("a",{href:"https://beian.miit.gov.cn/",target:"_blank",rel:"noreferrer"}," 粤ICP备2026888888号-1 ")],-1))]))}},Ac=["fill","stroke"],pe={__name:"LocalIcon",props:{name:{type:String,required:!0},size:{type:[Number,String],default:24}},setup(e){const t=e,n={light_mode:{type:"stroke",shapes:[{tag:"circle",attrs:{cx:"12",cy:"12",r:"4"}},{tag:"path",attrs:{d:"M12 2v2.2"}},{tag:"path",attrs:{d:"M12 19.8V22"}},{tag:"path",attrs:{d:"M4.93 4.93 6.5 6.5"}},{tag:"path",attrs:{d:"m17.5 17.5 1.57 1.57"}},{tag:"path",attrs:{d:"M2 12h2.2"}},{tag:"path",attrs:{d:"M19.8 12H22"}},{tag:"path",attrs:{d:"m4.93 19.07 1.57-1.57"}},{tag:"path",attrs:{d:"M17.5 6.5 19.07 4.93"}}]},dark_mode:{type:"fill",shapes:[{tag:"path",attrs:{d:"M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z"}}]},add_circle:{type:"stroke",shapes:[{tag:"circle",attrs:{cx:"12",cy:"12",r:"9"}},{tag:"path",attrs:{d:"M12 8v8"}},{tag:"path",attrs:{d:"M8 12h8"}}]},sensors:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M12 12h.01"}},{tag:"path",attrs:{d:"M9.2 14.8a4 4 0 0 1 0-5.6"}},{tag:"path",attrs:{d:"M14.8 9.2a4 4 0 0 1 0 5.6"}},{tag:"path",attrs:{d:"M6.4 17.6a8 8 0 0 1 0-11.2"}},{tag:"path",attrs:{d:"M17.6 6.4a8 8 0 0 1 0 11.2"}}]},smartphone:{type:"stroke",shapes:[{tag:"rect",attrs:{x:"7",y:"2.5",width:"10",height:"19",rx:"2.5"}},{tag:"path",attrs:{d:"M10 5h4"}},{tag:"circle",attrs:{cx:"12",cy:"18",r:"0.8"}}]},laptop_mac:{type:"stroke",shapes:[{tag:"rect",attrs:{x:"5",y:"4",width:"14",height:"10",rx:"1.5"}},{tag:"path",attrs:{d:"M3 18h18"}},{tag:"path",attrs:{d:"M8 18a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2"}}]},close:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M6 6l12 12"}},{tag:"path",attrs:{d:"M18 6 6 18"}}]},cloud_upload:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M7 18a4 4 0 1 1 .7-7.94A5.5 5.5 0 0 1 18 11a3.5 3.5 0 1 1-.5 7"}},{tag:"path",attrs:{d:"M12 10v8"}},{tag:"path",attrs:{d:"m8.8 13.2 3.2-3.2 3.2 3.2"}}]},arrow_upward:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M12 19V6"}},{tag:"path",attrs:{d:"m6.5 11.5 5.5-5.5 5.5 5.5"}}]},send_and_archive:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M3 6h18l-2 4H5Z"}},{tag:"path",attrs:{d:"M5 10v8a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-8"}},{tag:"path",attrs:{d:"M12 11v5"}},{tag:"path",attrs:{d:"m9.5 13.5 2.5 2.5 2.5-2.5"}}]},chat_bubble:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M6 18.5 3.5 21v-5A7.5 7.5 0 0 1 11 4.5h2A7.5 7.5 0 0 1 20.5 12v.5A7.5 7.5 0 0 1 13 20H8.5"}}]},content_copy:{type:"stroke",shapes:[{tag:"rect",attrs:{x:"9",y:"9",width:"10",height:"10",rx:"2"}},{tag:"path",attrs:{d:"M7 15H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v1"}}]},check:{type:"stroke",shapes:[{tag:"path",attrs:{d:"m5 12 4.2 4.2L19 7.5"}}]},draft:{type:"stroke",shapes:[{tag:"rect",attrs:{x:"4",y:"5",width:"16",height:"14",rx:"2"}},{tag:"path",attrs:{d:"m5 7 7 5 7-5"}}]},save:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M5 20h14a1 1 0 0 0 1-1V7.5L16.5 4H5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1Z"}},{tag:"path",attrs:{d:"M8 4v5h7"}},{tag:"rect",attrs:{x:"8",y:"14",width:"8",height:"4",rx:"1"}}]},download:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M12 5v10"}},{tag:"path",attrs:{d:"m7.5 10.5 4.5 4.5 4.5-4.5"}},{tag:"path",attrs:{d:"M5 19h14"}}]}},s=It(()=>n[t.name]||n.close),i=It(()=>typeof t.size=="number"?`${t.size}px`:/^\d+(\.\d+)?$/.test(t.size)?`${t.size}px`:t.size);return(o,a)=>(j(),V("span",{class:"app-icon",style:Le({width:i.value,height:i.value}),"aria-hidden":"true"},[(j(),V("svg",{viewBox:"0 0 24 24",fill:s.value.type==="fill"?"currentColor":"none",stroke:s.value.type==="stroke"?"currentColor":"none","stroke-width":"1.8","stroke-linecap":"round","stroke-linejoin":"round"},[(j(!0),V(fe,null,en(s.value.shapes,(c,f)=>(j(),vt(ua(c.tag),go({key:`${e.name}-${f}`},{ref_for:!0},c.attrs),null,16))),128))],8,Ac))],4))}},Ec={class:"header"},Oc={__name:"AppHeader",props:{theme:{type:String,required:!0}},emits:["toggle-theme"],setup(e){return(t,n)=>(j(),V("div",Ec,[n[1]||(n[1]=y("h1",null,"AirShare Pro",-1)),n[2]||(n[2]=y("p",null,"跨端局域网 & P2P 传输中心",-1)),y("button",{class:"theme-toggle",title:"切换日夜模式",onClick:n[0]||(n[0]=s=>t.$emit("toggle-theme"))},[q(pe,{id:"theme-icon",name:e.theme==="dark"?"dark_mode":"light_mode",size:"22"},null,8,["name"])])]))}},Pc={class:"card"},Dc={key:0,class:"section-title"},mn={__name:"GlassCard",props:{title:{type:String,default:""}},setup(e){return(t,n)=>(j(),V("div",Pc,[e.title?(j(),V("div",Dc,te(e.title),1)):Be("",!0),da(t.$slots,"default")]))}},Nc={class:"admin-panel active"},Fc={class:"card admin-header-card"},Uc={class:"transfer-head transfer-head-compact"},Lc={class:"main-grid admin-summary-grid"},Bc={class:"admin-stats-panel"},jc={class:"admin-stats-row"},Hc={class:"admin-fluid-content"},Kc={class:"admin-fluid-icon"},zc={class:"admin-fluid-copy"},Wc={key:0,class:"stat-suffix"},Vc={class:"admin-config-stack"},qc={class:"text-input-group admin-config-row admin-config-row-field admin-config-row-last"},Gc={class:"admin-field-control-row"},Jc=["value"],Yc={class:"text-input-group admin-config-row admin-config-row-field admin-config-row-last"},Xc={class:"admin-field-control-row"},Zc=["value"],Qc={class:"admin-config-insights"},eu={class:"admin-config-highlight"},tu={class:"admin-config-highlight"},nu={class:"admin-table-wrapper"},su={class:"admin-table"},iu={class:"admin-record-type-cell"},ru=["title"],ou={__name:"AdminPanel",props:{stats:{type:Array,required:!0},records:{type:Array,required:!0},fileLimit:{type:Number,required:!0},minioCapacity:{type:Number,required:!0}},emits:["exit","save-config","update:file-limit","update:minio-capacity"],setup(e){function t(a){const c=Number(a)||0;return c>=1024?`${(c/1024).toFixed(c%1024===0?0:1)} GB`:`${c} MB`}function n(a){const c=Number(a)||0;return c>=1024?`${(c/1024).toFixed(c%1024===0?0:1)} TB`:`${c} GB`}function s(a){return a==="blue"?{color:"var(--accent-blue)"}:a==="cyan"?{color:"var(--accent-cyan)"}:a==="success"?{color:"var(--success-green)"}:a==="danger"?{color:"var(--danger-red)"}:{color:"var(--text-main)"}}function i(a){return a==="success"?{color:"var(--success-green)",fontWeight:500}:a==="primary"?{color:"var(--accent-blue)",fontWeight:500}:{color:"var(--danger-red)",fontWeight:500}}function o(a){const c=Number(a)||0;return{"--fluid-level":`${Math.max(0,Math.min(c,100))}%`}}return(a,c)=>(j(),V("div",Nc,[y("div",Fc,[y("div",Uc,[c[5]||(c[5]=y("div",{class:"connected-to"},[y("h2",{class:"admin-title"},"管理控制台"),y("p",{class:"admin-subtitle"},"AirShare Pro System Dashboard")],-1)),y("button",{class:"btn-small-primary",type:"button",onClick:c[0]||(c[0]=f=>a.$emit("exit"))},"退出管理")])]),y("div",Lc,[q(mn,{class:"admin-stats-card",title:"系统运行状态"},{default:Yt(()=>[y("div",Bc,[y("div",jc,[(j(!0),V(fe,null,en(e.stats,f=>(j(),V("div",{key:f.label,class:St(["admin-stat-item",{"admin-stat-item-fluid":f.kind==="minio"}])},[f.kind==="minio"?(j(),V("div",{key:0,class:"admin-fluid-card",style:Le(o(f.percent))},[c[6]||(c[6]=y("div",{class:"admin-fluid-fill"},[y("span",{class:"admin-fluid-wave admin-fluid-wave-a"}),y("span",{class:"admin-fluid-wave admin-fluid-wave-b"})],-1)),y("div",Hc,[y("div",Kc,[q(pe,{name:"save",size:"18"})]),y("div",zc,[y("h3",{style:Le(s(f.tone))},te(f.value),5),y("p",null,te(f.label),1),y("small",null,te(f.detail),1)])])],4)):(j(),V(fe,{key:1},[c[7]||(c[7]=y("span",{class:"admin-stat-kicker"},"实时指标",-1)),y("h3",{style:Le(s(f.tone))},[nn(te(f.value),1),f.suffix?(j(),V("span",Wc,te(f.suffix),1)):Be("",!0)],4),y("p",null,te(f.label),1)],64))],2))),128))])])]),_:1}),q(mn,{class:"admin-config-card",title:"核心参数配置"},{default:Yt(()=>[y("div",Vc,[y("div",qc,[c[8]||(c[8]=y("div",{class:"admin-field-meta"},[y("label",{class:"admin-field-label",for:"admin-file-limit"},"单文件大小限制"),y("p",{class:"admin-field-hint"},"单位为 MB,超过该阈值的文件会按当前后端策略处理。")],-1)),y("div",Gc,[y("input",{id:"admin-file-limit",value:e.fileLimit,min:"1",placeholder:"10240",type:"number",onInput:c[1]||(c[1]=f=>a.$emit("update:file-limit",Number(f.target.value)||0))},null,40,Jc),y("button",{title:"保存配置",type:"button",onClick:c[2]||(c[2]=f=>a.$emit("save-config"))},[q(pe,{name:"save",size:"18"})])])]),y("div",Yc,[c[9]||(c[9]=y("div",{class:"admin-field-meta"},[y("label",{class:"admin-field-label",for:"admin-minio-capacity"},"MinIO 总容量"),y("p",{class:"admin-field-hint"},"单位为 GB,用于容量卡和液位比例计算。")],-1)),y("div",Xc,[y("input",{id:"admin-minio-capacity",value:e.minioCapacity,min:"1",placeholder:"120",type:"number",onInput:c[3]||(c[3]=f=>a.$emit("update:minio-capacity",Number(f.target.value)||0))},null,40,Zc),y("button",{title:"保存配置",type:"button",onClick:c[4]||(c[4]=f=>a.$emit("save-config"))},[q(pe,{name:"save",size:"18"})])])]),y("div",Qc,[y("div",eu,[c[10]||(c[10]=y("span",{class:"admin-config-badge"},"ACTIVE POLICY",-1)),y("h3",null,te(t(e.fileLimit)),1),c[11]||(c[11]=y("p",null,"当前单文件阈值。超过该体积后,文件会按后端已设定的传输与存档策略处理。",-1))]),y("div",tu,[c[12]||(c[12]=y("span",{class:"admin-config-badge"},"MINIO CAPACITY",-1)),y("h3",null,te(n(e.minioCapacity)),1),c[13]||(c[13]=y("p",null,"当前 MinIO 总容量基线,用于后台容量展示与液位占比计算。",-1))])])])]),_:1})]),q(mn,{class:"admin-table-card",title:"最近传输记录(Top 5)"},{default:Yt(()=>[y("div",nu,[y("table",su,[c[14]||(c[14]=y("thead",null,[y("tr",null,[y("th",null,"时间"),y("th",null,"发送端特征"),y("th",null,"传输类型"),y("th",null,"数据量"),y("th",null,"状态")])],-1)),y("tbody",null,[(j(!0),V(fe,null,en(e.records,f=>(j(),V("tr",{key:`${f.time}-${f.peer}`},[y("td",null,te(f.time),1),y("td",null,te(f.peer),1),y("td",iu,[y("span",{class:"admin-record-type",title:f.type},te(f.type),9,ru)]),y("td",null,te(f.size),1),y("td",{style:Le(i(f.tone))},te(f.status),5)]))),128))])])])]),_:1})]))}},lu={class:"local-device-name"},au={key:0,class:"radar-container"},cu={class:"radar"},uu={key:1,class:"device-list"},fu=["onClick"],du={class:"device-icon"},pu={class:"device-info"},hu={key:2,class:"radar-container"},mu={class:"radar"},gu={__name:"DeviceDiscoveryCard",props:{isScanning:{type:Boolean,required:!0},localDeviceName:{type:String,default:""},devices:{type:Array,required:!0}},emits:["select-device"],setup(e,{emit:t}){const n=t;function s(i){n("select-device",i)}return(i,o)=>(j(),vt(mn,{title:"局域网自动发现"},{default:Yt(()=>[y("p",lu,[o[0]||(o[0]=nn(" 本机:",-1)),y("strong",null,te(e.localDeviceName||"识别中"),1)]),e.isScanning?(j(),V("div",au,[y("div",cu,[q(pe,{class:"radar-icon",name:"sensors",size:"36"})]),o[1]||(o[1]=y("p",{class:"scan-status"},"正在扫描附近设备...",-1))])):e.devices.length?(j(),V("div",uu,[(j(!0),V(fe,null,en(e.devices,a=>(j(),V("button",{key:a.id,class:"device-item",type:"button",onClick:c=>s(a)},[y("div",du,[q(pe,{name:a.icon,size:"24"},null,8,["name"])]),y("div",pu,[y("h4",null,te(a.name),1),y("p",null,te(a.description),1)]),o[2]||(o[2]=y("div",{class:"device-status-beacon","aria-hidden":"true"},[y("span",{class:"device-status-dot"}),y("span",{class:"device-status-ring"}),y("span",{class:"device-status-ring device-status-ring-delay"})],-1))],8,fu))),128))])):(j(),V("div",hu,[y("div",mu,[q(pe,{class:"radar-icon",name:"devices",size:"36"})]),o[3]||(o[3]=y("p",{class:"scan-status"},"暂未发现局域网设备,请保持页面开启后重试",-1))]))]),_:1}))}},vu={key:0,class:"room-action-area"},yu={class:"room-input-group"},_u=["value"],bu={key:0,class:"pending-downloads"},wu={class:"pending-downloads-head"},xu=["href"],Iu={class:"pending-download-copy"},Su=["title"],Cu={class:"pending-download-icon","aria-hidden":"true"},Tu={key:1,class:"waiting-area"},$u={class:"huge-code"},ku={__name:"RemoteRoomCard",props:{roomCodeInput:{type:String,required:!0},isWaiting:{type:Boolean,required:!0},generatedCode:{type:String,required:!0},pendingDownloads:{type:Array,required:!0}},emits:["update-room-code","create-room","join-room","cancel-room"],setup(e,{emit:t}){const n=t;function s(o){n("update-room-code",o.target.value)}function i(){n("join-room")}return(o,a)=>(j(),vt(mn,{title:"远程直连"},{default:Yt(()=>[e.isWaiting?(j(),V("div",Tu,[a[6]||(a[6]=y("p",{class:"waiting-subtitle"},"您的房间号码",-1)),y("div",$u,te(e.generatedCode),1),a[7]||(a[7]=y("div",{class:"spinner"},null,-1)),a[8]||(a[8]=y("p",{class:"waiting-tip"},"等待对方加入...",-1)),y("button",{class:"btn-cancel",type:"button",onClick:a[2]||(a[2]=c=>o.$emit("cancel-room"))},"取消建房")])):(j(),V("div",vu,[y("button",{class:"btn-create",type:"button",onClick:a[0]||(a[0]=c=>o.$emit("create-room"))},[q(pe,{name:"add_circle",size:"22"}),a[3]||(a[3]=nn(" 创建专属传输房间 ",-1))]),a[5]||(a[5]=y("div",{class:"divider"},"或",-1)),y("div",yu,[y("input",{class:"room-code",inputmode:"numeric",maxlength:"4",pattern:"\\d*",placeholder:"输入4位房间号",type:"text",value:e.roomCodeInput,onInput:s,onKeyup:bo(i,["enter"])},null,40,_u),y("button",{class:"btn-primary",type:"button",onClick:a[1]||(a[1]=c=>o.$emit("join-room"))},"加入房间")]),e.pendingDownloads.length?(j(),V("div",bu,[y("div",wu,[a[4]||(a[4]=y("span",null,"待领取文件",-1)),y("span",null,te(e.pendingDownloads.length),1)]),(j(!0),V(fe,null,en(e.pendingDownloads,c=>(j(),V("a",{key:c.transfer_id,class:"pending-download-item",href:c.download_path,target:"_blank",rel:"noopener noreferrer"},[y("div",Iu,[y("strong",{title:c.name},te(c.name),9,Su),y("p",null,te(c.size_label)+" · "+te(c.created_label),1)]),y("span",Cu,[q(pe,{name:"download",size:"18"})])],8,xu))),128))])):Be("",!0)]))]),_:1}))}},Mu={class:"file-info"},Ru=["title"],Au={class:"file-info-right"},Eu=["download","href"],Ou={key:0,class:"progress-bar-container"},Pu={__name:"TransferQueueItem",props:{item:{type:Object,required:!0}},emits:["remove","start-upload","copy"],setup(e){const t=e,n=It(()=>t.item.tone==="success"?{color:"var(--success-green)"}:t.item.tone==="primary"?{color:"var(--accent-blue)"}:t.item.tone==="danger"?{color:"var(--danger-red)"}:{color:"var(--text-secondary)"}),s=It(()=>t.item.kind==="text"?{color:"var(--success-green)",background:"rgba(48, 209, 88, 0.1)"}:{});return(i,o)=>(j(),V("div",{class:St(["batch-item",{"pending-file":e.item.kind==="file"&&e.item.pending}])},[y("div",Mu,[y("div",{class:"file-info-left",style:Le(e.item.kind==="text"?{maxWidth:"70%"}:null)},[y("div",{class:"file-icon-wrapper",style:Le(s.value)},[q(pe,{name:e.item.kind==="text"?"chat_bubble":"draft",size:"18"},null,8,["name"])],4),y("span",{class:"file-name",title:e.item.kind==="text"?e.item.text:e.item.name},te(e.item.kind==="text"?e.item.text:e.item.name),9,Ru)],4),y("div",Au,[y("span",{class:"file-status",style:Le(n.value)},te(e.item.kind==="text"&&e.item.copied?"已复制":e.item.status),5),e.item.kind==="text"?(j(),V("button",{key:0,class:"action-btn",title:"复制文本",type:"button",onClick:o[0]||(o[0]=a=>i.$emit("copy",e.item.id))},[q(pe,{name:e.item.copied?"check":"content_copy",size:"16"},null,8,["name"])])):Be("",!0),e.item.kind==="file"&&e.item.pending?(j(),V("button",{key:1,class:"action-btn primary",title:"发送文件",type:"button",onClick:o[1]||(o[1]=a=>i.$emit("start-upload",e.item.id))},[q(pe,{name:"arrow_upward",size:"16"})])):Be("",!0),e.item.kind==="file"&&e.item.downloadUrl?(j(),V("a",{key:2,class:"action-btn primary",download:e.item.name,href:e.item.downloadUrl,title:"保存文件"},[q(pe,{name:"download",size:"16"})],8,Eu)):Be("",!0),y("button",{class:"action-btn danger",title:"移除记录",type:"button",onClick:o[2]||(o[2]=a=>i.$emit("remove",e.item.id))},[q(pe,{name:"close",size:"16"})])])]),e.item.kind==="file"?(j(),V("div",Ou,[y("div",{class:St(["progress-bar-fill",{success:e.item.tone==="success"}]),style:Le({width:`${e.item.progress}%`})},null,6)])):Be("",!0)],2))}},Du={class:"transfer-panel active"},Nu={class:"card"},Fu={class:"transfer-head"},Uu={class:"connected-to"},Lu={key:0,class:"connection-hint"},Bu={class:"text-input-group"},ju={__name:"TransferPanel",props:{peerName:{type:String,required:!0},connectionType:{type:String,required:!0},networkHint:{type:String,default:""},items:{type:Array,required:!0},hasPendingItems:{type:Boolean,required:!0}},emits:["close","send-text","files-selected","send-all-pending","remove-item","start-upload","copy-item"],setup(e,{emit:t}){const n=e,s=t,i=oe(""),o=oe(!1),a=oe(null),c=oe(null);Ut(()=>n.items.length,async()=>{await Nr(),a.value&&(a.value.scrollTop=a.value.scrollHeight)});function f(){var $;($=c.value)==null||$.click()}function h(){s("send-text",i.value),i.value=""}function p($){const S=Array.from($.target.files||[]);S.length&&s("files-selected",S),$.target.value=""}function _($){var U;o.value=!1;const S=Array.from(((U=$.dataTransfer)==null?void 0:U.files)||[]);S.length&&s("files-selected",S)}return($,S)=>(j(),V("div",Du,[y("div",Nu,[y("div",Fu,[y("div",Uu,[y("h2",null,te(e.peerName),1),y("p",null,te(e.connectionType),1),e.networkHint?(j(),V("small",Lu,te(e.networkHint),1)):Be("",!0)]),y("button",{class:"close-btn",type:"button",onClick:S[0]||(S[0]=U=>$.$emit("close"))},[q(pe,{name:"close",size:"20"})])]),y("div",{class:St(["drop-zone",{"drop-zone-active":o.value}]),onClick:f,onDragenter:S[1]||(S[1]=Pn(U=>o.value=!0,["prevent"])),onDragover:S[2]||(S[2]=Pn(U=>o.value=!0,["prevent"])),onDragleave:S[3]||(S[3]=Pn(U=>o.value=!1,["prevent"])),onDrop:Pn(_,["prevent"])},[q(pe,{class:"drop-zone-icon",name:"cloud_upload",size:"42"}),S[9]||(S[9]=y("p",{class:"drop-zone-text"},"点击或拖拽多个文件到这里",-1)),y("input",{ref_key:"fileInput",ref:c,class:"hidden",multiple:"",type:"file",onChange:p},null,544)],34),y("div",Bu,[Wl(y("input",{"onUpdate:modelValue":S[4]||(S[4]=U=>i.value=U),placeholder:"输入要发送的文本或链接...",type:"text",onKeyup:bo(h,["enter"])},null,544),[[bc,i.value]]),y("button",{title:"发送文本",type:"button",onClick:h},[q(pe,{name:"arrow_upward",size:"20"})])]),y("div",{class:St(["batch-actions",{active:e.hasPendingItems}])},[y("button",{class:"btn-small-primary",type:"button",onClick:S[5]||(S[5]=U=>$.$emit("send-all-pending"))},[q(pe,{name:"send_and_archive",size:"16"}),S[10]||(S[10]=nn(" 一键发送全部 ",-1))])],2),e.items.length?(j(),V("div",{key:0,ref_key:"batchContainer",ref:a,class:"batch-progress-container"},[(j(!0),V(fe,null,en(e.items,U=>(j(),vt(Pu,{key:U.id,item:U,onCopy:S[6]||(S[6]=N=>$.$emit("copy-item",N)),onRemove:S[7]||(S[7]=N=>$.$emit("remove-item",N)),onStartUpload:S[8]||(S[8]=N=>$.$emit("start-upload",N))},null,8,["item"]))),128))],512)):Be("",!0)])]))}};let Vt={deviceId:"",token:""};const Hu="filefast_device_id",Ku="filefast_device_token";function wo(){return!Vt.deviceId||!Vt.token?{}:{"X-Device-ID":Vt.deviceId,"X-Device-Token":Vt.token}}function zu(e={},t=!1){return{...t?{"Content-Type":"application/json"}:{},...wo(),...e}}function Wu(e,t){if(!t||Object.keys(t).length===0)return e;const n=new URLSearchParams;Object.entries(t).forEach(([i,o])=>{o!=null&&o!==""&&n.set(i,String(o))});const s=n.toString();return s?`${e}?${s}`:e}async function Dn(e,t={}){const n=t.body!==void 0,s=await fetch(Wu(e,t.query),{method:t.method||"GET",headers:zu(t.headers,n),body:n?JSON.stringify(t.body):void 0}),i=await s.json().catch(()=>({}));if(!s.ok){const o=new Error(i.error||`Request failed: ${s.status}`);throw o.status=s.status,o}return i.data}const ve={get(e,t={}){return Dn(e,{...t,method:"GET"})},post(e,t,n={}){return Dn(e,{...n,method:"POST",body:t})},put(e,t,n={}){return Dn(e,{...n,method:"PUT",body:t})},patch(e,t,n={}){return Dn(e,{...n,method:"PATCH",body:t})}};function lr(e,t){Vt={deviceId:e||"",token:t||""},Gu(Vt)}function Vu(){return wo()}function qu(e){return{Authorization:`Bearer ${e}`}}function Gu(e){typeof document>"u"||(ar(Hu,e.deviceId),ar(Ku,e.token))}function ar(e,t){if(!t){document.cookie=`${e}=; Path=/; Max-Age=0; SameSite=Lax`;return}document.cookie=`${e}=${encodeURIComponent(t)}; Path=/; SameSite=Lax`}function Nn(e){return{headers:qu(e)}}const Kt={login(e,t){return ve.post("/api/admin/login",{username:e,password:t})},stats(e){return ve.get("/api/admin/stats",Nn(e))},config(e){return ve.get("/api/admin/config",Nn(e))},updateConfig(e,t){return ve.put("/api/admin/config",t,Nn(e))},recentTransfers(e){return ve.get("/api/admin/transfers/recent",Nn(e))}},Fn={register(e){return ve.post("/api/devices/register",e)},heartbeat(e){return ve.post("/api/devices/heartbeat",{device_id:e})},listCandidates(e){return ve.get("/api/devices/candidates",{query:{deviceId:e}})},listPendingDownloads(e){return ve.get(`/api/devices/${encodeURIComponent(e)}/pending-downloads`)}},Un={create(e){return ve.post("/api/rooms",{creator_device_id:e})},get(e){return ve.get(`/api/rooms/${encodeURIComponent(e)}`)},join(e,t){return ve.post("/api/rooms/join",{code:e,joiner_device_id:t})},cancel(e,t){return ve.post(`/api/rooms/${encodeURIComponent(e)}/cancel`,{requester_id:t})}},Ju={config(){return ve.get("/api/runtime/config")}},ge={create(e){return ve.post("/api/transfers",e)},presignFallback(e){return ve.post(`/api/transfers/${encodeURIComponent(e)}/fallback/presign`,{})},uploadFallback(e,t,n){return Yu(`/api/transfers/${encodeURIComponent(e)}/fallback/upload`,t,n)},updateStatus(e,t){return ve.patch(`/api/transfers/${encodeURIComponent(e)}/status`,t)}};function Yu(e,t,n){return new Promise((s,i)=>{const o=new XMLHttpRequest;o.open("PUT",e),o.responseType="json",o.setRequestHeader("Content-Type",t.type||"application/octet-stream"),Object.entries(Vu()).forEach(([a,c])=>{o.setRequestHeader(a,c)}),o.upload.onprogress=a=>{!a.lengthComputable||typeof n!="function"||n(Math.round(a.loaded/a.total*100))},o.onload=()=>{const a=o.response||Xu(o.responseText);if(o.status>=200&&o.status<300){s(a.data);return}i(new Error((a==null?void 0:a.error)||`Upload failed: ${o.status}`))},o.onerror=()=>i(new Error("Upload failed")),o.send(t)})}function Xu(e){try{return JSON.parse(e)}catch{return null}}const Zu={class:"container"},Qu={key:0,class:"main-grid"},Ss="filefast-admin-token",Ln="filefast-admin-view",Bn="filefast-device-id",cr="filefast-device-name",Cs="filefast-device-token",ef=15e3,tf=5e3,nf=2e3,sf=3e3,rf=4*1024*1024,of=2e4,lf=16*1024,ur=512*1024,af={__name:"App",setup(e){const t=oe(localStorage.getItem("airshare-theme")||"light"),n=oe(localStorage.getItem(Ln)==="admin"?"admin":"main"),s=oe(!0),i=oe([]),o=oe(""),a=oe(!1),c=oe("----"),f=oe([]),h=oe({name:"--",type:"等待连接",deviceId:"",networkGroupKey:""}),p=oe([]),_=oe("/ws"),$=oe(10240),S=oe(120),U=oe([]),N=oe([]),Y=oe(null),z=oe(localStorage.getItem(Ss)||""),A=oe({id:"",name:"",type:""}),Q=localStorage.getItem(Bn)||"",F=localStorage.getItem(Cs)||"";Q&&F&&lr(Q,F);const le=new Map,he=new Map,Te=new Map,$e=new Map;let _t=null,He=null,Je=null,Ke=null,bt=null,de=null,st=null,L=null,B=null,G="",_e="p2p",ze=!1,be=!1,ue=!1,wt=null,Tt=null;const Sn=It(()=>p.value.filter(r=>r.kind==="file"&&r.pending)),$t=It(()=>Sn.value.length>0),Lt=It(()=>!h.value.deviceId||ai(h.value.networkGroupKey)||Uo()?"":"当前是跨网络访问,未配置 TURN 时实时通道可能失败。文本和小文件可回退中转,大文件建议使用 MinIO。");Ut(t,r=>{document.body.setAttribute("data-theme",r),localStorage.setItem("airshare-theme",r)},{immediate:!0}),Ut(n,r=>{if(r==="admin"&&z.value){localStorage.setItem(Ln,"admin");return}localStorage.removeItem(Ln)}),Ut([n,z],([r,l])=>{Ke&&(window.clearInterval(Ke),Ke=null),!(r!=="admin"||!l)&&(Ke=window.setInterval(()=>{ls().catch(u=>{console.error(u)})},5e3))}),Vr(async()=>{_.value=Fo(),await sn(),n.value==="admin"&&z.value&&ls().catch(r=>{console.error(r)}),He=window.setInterval(()=>{w()},ef),_t=window.setInterval(()=>{v()},tf),bt=window.setInterval(()=>{I()},1e4)}),Qs(()=>{_t&&window.clearInterval(_t),He&&window.clearInterval(He),Ke&&window.clearInterval(Ke),bt&&window.clearInterval(bt),b(),ot(),yi(),D()});async function sn(){try{await kt(),await g(),await v()}catch(r){window.alert(`后端连接失败:${r.message}`)}}function Cn(){t.value=t.value==="dark"?"light":"dark"}async function kt(){try{Tn(await Ju.config())}catch(r){console.error(r)}}function Tn(r){r&&(Y.value=r,$.value=Math.round((r.max_minio_fallback_size_bytes||0)/1024/1024),S.value=Math.max(0,Math.round((r.minio_capacity_bytes||0)/1024/1024/1024)))}function d(r){o.value=r.replace(/\D/g,"").slice(0,4)}async function g(){const r=Ao(),l=Eo(r),u=Do(),m=await Fn.register({device_id:r,name:l,type:u,network_group_key:li()});localStorage.setItem(Bn,m.id),m.auth_token&&(localStorage.setItem(Cs,m.auth_token),lr(m.id,m.auth_token)),A.value={id:m.id,name:m.name,type:m.type},await I(),vi()}async function v(){if(A.value.id)try{const r=await ge.create({kind:"text",name:"text-message",content:value,sender_device_id:A.value.id,receiver_device_id:h.value.deviceId});try{await rn(r,value)}catch(l){console.warn("realtime text send failed, fallback to relay",l),await ii(r,value)}p.value.push({id:Ue("text"),transferId:r.id,kind:"text",text:value,status:"已发送",tone:"success",copied:!1})}catch(r){window.alert(`发送文本失败:${r.message}`)}}async function I(){if(!A.value.id){f.value=[];return}try{const r=await Fn.listPendingDownloads(A.value.id);f.value=r.map(l=>({...l,name:it(l.name),download_path:l.download_path||`/api/transfers/${encodeURIComponent(l.transfer_id)}/fallback/download`,size_label:At(Number(l.size_bytes||0)),created_label:Rn(l.created_at)}))}catch(r){if((r==null?void 0:r.status)===404){f.value=[];return}console.error(r)}}async function w(){if(A.value.id)try{await Fn.heartbeat(A.value.id)}catch(r){console.error(r)}}async function x(){if(!A.value.id){window.alert("当前设备尚未注册到后端");return}try{const r=await Un.create(A.value.id);c.value=r.code,a.value=!0,C(r.code)}catch(r){window.alert(`创建房间失败:${r.message}`)}}async function R(){const r=c.value;b();try{a.value&&r!=="----"&&await Un.cancel(r,A.value.id)}catch(l){console.error(l)}finally{a.value=!1,c.value="----"}}async function k(){if(!(o.value.length<4))try{const r=await Un.join(o.value,A.value.id),l=Rt(r.creator_device_id);o.value="",P({deviceId:r.creator_device_id,name:(l==null?void 0:l.name)||`房间 ${r.code} 创建者`,type:"房间配对成功"})}catch(r){window.alert(`加入房间失败:${r.message}`)}}function C(r){b(),Je=window.setInterval(async()=>{try{const l=await Un.get(r);if(l.status==="joined"&&l.joiner_device_id){const u=Rt(l.joiner_device_id);P({deviceId:l.joiner_device_id,name:(u==null?void 0:u.name)||`房间 ${r} 对端`,type:"房间配对成功"});return}(l.status==="expired"||l.status==="canceled")&&(b(),a.value=!1,c.value="----")}catch(l){console.error(l)}},nf)}function b(){Je&&(window.clearInterval(Je),Je=null)}function P(r){const l=r.deviceId||r.id||"",u=r.connectionType||r.type||"点对点传输";b(),h.value.deviceId!==l&&(ot(),D()),h.value={name:r.name,type:r.connectionType||r.type||"点对点传输",deviceId:r.deviceId||r.id||"",networkGroupKey:r.network_group_key||""},a.value=!1,c.value="----",n.value="transfer",h.value.baseType=u,h.value.type=u,h.value.deviceId=l,We("正在建立实时通道"),rt(l,{initiate:!0})}function M(r,l=!1){const u=r.deviceId||r.id||"",m=r.connectionType||r.type||"点对点传输";h.value.deviceId===u&&n.value==="transfer"||(ot(),l||D()),h.value={name:r.name,type:r.connectionType||r.type||"点对点传输",deviceId:u,networkGroupKey:r.network_group_key||""},a.value=!1,c.value="----",n.value="transfer",h.value.baseType=m,h.value.type=m,h.value.deviceId=u,We("正在建立实时通道"),rt(u)}function O(){ot(),D(),h.value={name:"--",type:"等待连接",deviceId:"",networkGroupKey:""},n.value="main",h.value.baseType="等待连接",h.value.type="等待连接"}function D(){p.value.forEach(r=>Pe(r)),p.value=[],he.clear()}async function W(r){const l=r.trim();if(l){if(!h.value.deviceId){window.alert("当前没有可用的接收端");return}try{const u=await ge.create({kind:"text",name:"text-message",content:l,sender_device_id:A.value.id,receiver_device_id:h.value.deviceId});ce("transfer.created",h.value.deviceId,{transfer_id:u.id,kind:"text",name:"text-message",content:l,sender_device_id:A.value.id,sender_name:A.value.name,sender_type:A.value.type,receiver_device_id:h.value.deviceId,final_status:"completed",current_channel:"p2p",transport_options:as()}),await ge.updateStatus(u.id,{current_channel:"p2p",final_status:"completed"}),ce("transfer.updated",h.value.deviceId,{transfer_id:u.id,final_status:"completed",current_channel:"p2p"}),p.value.push({id:Ue("text"),transferId:u.id,kind:"text",text:l,status:"已发送",tone:"success",copied:!1})}catch(u){window.alert(`发送文本失败:${u.message}`)}}}function ee(r){const l=r.filter(Boolean).map((u,m)=>({id:Ue(`file-${m}`),kind:"file",file:u,name:it(u.name),size:At(u.size),sizeBytes:u.size,status:"待发送",tone:"muted",progress:0,pending:!0,transferId:""}));l.length&&p.value.push(...l)}async function J(r){const l=p.value.find(u=>u.id===r);if(!(!l||l.kind!=="file"||!l.pending)){if(!h.value.deviceId){window.alert("当前没有可用的接收端");return}l.pending=!1,l.status="创建传输中...",l.tone="primary";try{const u=await ge.create({kind:"file",name:l.name,size_bytes:l.sizeBytes,sender_device_id:A.value.id,receiver_device_id:h.value.deviceId});if(l.transferId=u.id,l.sizeBytes>rf){await me(l,u);return}ce("transfer.created",h.value.deviceId,{transfer_id:u.id,kind:"file",name:l.name,size_bytes:l.sizeBytes,sender_device_id:A.value.id,sender_name:A.value.name,sender_type:A.value.type,receiver_device_id:h.value.deviceId,final_status:"connecting",current_channel:"p2p",transport_options:as()});try{await ri(l,u)}catch(m){console.warn("realtime file send failed, fallback to relay",m),await oi(l,u)}}catch(u){l.pending=!0,l.status=`发送失败:${u.message}`,l.tone="danger"}}}async function me(r,l){r.progress=0,r.status="上传准备中...";try{if(!l.fallback_allowed)throw new Error("当前文件过大,且未启用 MinIO 回退");await ge.presignFallback(r.transferId),ce("transfer.updated",h.value.deviceId,{transfer_id:r.transferId,final_status:"fallback_uploading",current_channel:"minio"}),r.status="上传中...";const u=await ge.uploadFallback(r.transferId,r.file,m=>{r.progress=Math.max(1,Math.min(m,99))});await ge.updateStatus(r.transferId,{current_channel:"minio",final_status:"completed"}),ce("transfer.updated",h.value.deviceId,{transfer_id:r.transferId,final_status:"completed",current_channel:"minio"}),ce("transfer.file",h.value.deviceId,{transfer_id:r.transferId,name:r.name,download_url:u.download_path||u.download_url}),r.progress=100,r.status="上传完成",r.tone="success"}catch(u){r.pending=!0,r.status=`上传失败:${u.message}`,r.tone="danger"}}async function we(){for(const r of Sn.value)await J(r.id)}async function Ne(r){const l=p.value.find(u=>u.id===r);if(l&&Pe(l),p.value=p.value.filter(u=>u.id!==r),!(!(l!=null&&l.transferId)||l.tone==="success"))try{await ge.updateStatus(l.transferId,{final_status:"cancelled"}),ce("transfer.updated",h.value.deviceId,{transfer_id:l.transferId,final_status:"cancelled"})}catch(u){console.error(u)}}async function Fe(r){const l=p.value.find(u=>u.id===r);if(!(!l||l.kind!=="text"))try{await Mt(l.text),l.copied=!0,window.setTimeout(()=>{const u=p.value.find(m=>m.id===r);u&&u.kind==="text"&&(u.copied=!1)},2e3)}catch{window.alert("复制失败")}}async function Mt(r){var u;if((u=navigator.clipboard)!=null&&u.writeText&&window.isSecureContext){await navigator.clipboard.writeText(r);return}const l=document.createElement("textarea");l.value=r,l.setAttribute("readonly","readonly"),l.style.position="fixed",l.style.top="0",l.style.left="-9999px",l.style.opacity="0",document.body.appendChild(l),l.focus(),l.select(),l.setSelectionRange(0,l.value.length);try{if(!document.execCommand("copy"))throw new Error("copy command failed")}finally{document.body.removeChild(l)}}function $n(r){const l=le.get(r);l&&(window.clearInterval(l),le.delete(r))}function ke(r){return new Promise((l,u)=>{const m=new FileReader;m.onload=()=>l(String(m.result||"")),m.onerror=()=>u(new Error("Failed to read file")),m.readAsDataURL(r)})}function Pe(r){if($n(r.id),r.ownedDownloadUrl&&r.downloadUrl)try{URL.revokeObjectURL(r.downloadUrl)}catch(l){console.error(l)}r.transferId&&he.delete(r.transferId)}function Bt(r,l,u=!1){if(r.ownedDownloadUrl&&r.downloadUrl&&r.downloadUrl!==l)try{URL.revokeObjectURL(r.downloadUrl)}catch(m){console.error(m)}r.downloadUrl=l,r.ownedDownloadUrl=u}async function rn(r,l){const u=await di(h.value.deviceId);kn(u,{type:"text",transfer_id:r.id,text:l,sender_device_id:A.value.id,sender_name:A.value.name,sender_type:A.value.type});const m=gi();await ge.updateStatus(r.id,{current_channel:m,final_status:"completed"})}async function ii(r,l){ce("transfer.created",h.value.deviceId,{transfer_id:r.id,kind:"text",name:"text-message",content:l,sender_device_id:A.value.id,sender_name:A.value.name,sender_type:A.value.type,receiver_device_id:h.value.deviceId,final_status:"completed",current_channel:"p2p"}),await ge.updateStatus(r.id,{current_channel:"p2p",final_status:"completed"})}async function ri(r,l){var E;const u=await di(h.value.deviceId);r.status="正在通过 WebRTC 发送...",r.progress=1,kn(u,{type:"file-meta",transfer_id:l.id,name:r.name,mime_type:((E=r.file)==null?void 0:E.type)||"application/octet-stream",size_bytes:r.sizeBytes,sender_device_id:A.value.id,sender_name:A.value.name,sender_type:A.value.type});let m=0;for(;mur;)await To(20)}function To(r){return new Promise(l=>{window.setTimeout(l,r)})}function $o(r,l,u){return new Promise((m,T)=>{const E=window.setTimeout(()=>{T(new Error(u))},l);r.then(se=>{window.clearTimeout(E),m(se)}).catch(se=>{window.clearTimeout(E),T(se)})})}async function ko(){const r=window.prompt("管理员用户名","admin");if(r===null)return;const l=window.prompt("管理员密码");if(l!==null)try{const u=await Kt.login(r.trim()||"admin",l);z.value=u.token,localStorage.setItem(Ss,u.token),await ls(),n.value="admin"}catch(u){window.alert(`管理员登录失败:${u.message}`)}}function Mo(){n.value="main"}async function ls(){if(z.value)try{const[r,l,u]=await Promise.all([Kt.stats(z.value),Kt.config(z.value),Kt.recentTransfers(z.value)]);Tn(l),U.value=xi(r.stats||{},r.minio||{}),N.value=u.map(m=>Ii(m))}catch(r){throw(r==null?void 0:r.status)===401&&(localStorage.removeItem(Ss),localStorage.removeItem(Ln),z.value="",n.value="main"),r}}async function Ro(){if(!z.value||!Y.value){window.alert("当前没有可用的管理员会话");return}try{const r={...Y.value,max_minio_fallback_size_bytes:Math.max(0,$.value)*1024*1024,minio_capacity_bytes:Math.max(0,S.value)*1024*1024*1024},l=await Kt.updateConfig(z.value,r);Tn(l);{const u=await Kt.stats(z.value);U.value=xi(u.stats||{},u.minio||{})}window.alert("配置已保存")}catch(r){window.alert(`保存配置失败:${r.message}`)}}function Ao(){let r=localStorage.getItem(Bn);return r||(r=typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():`web-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,localStorage.setItem(Bn,r)),r}function Eo(r){let l=localStorage.getItem(cr);return(!l||Oo(l,r))&&(l=`${Po()} ${r.slice(0,4)}`,localStorage.setItem(cr,l)),l}function Oo(r,l){const u=String(r||"").trim(),m=l.slice(0,4);return!u||!m||!u.endsWith(` ${m}`)?!1:/^(android|iphone|ipad|linux|macintel|macos|windows|win32|web)\s/i.test(u)}function Po(){const r=`${navigator.userAgent} ${navigator.platform}`.toLowerCase();return r.includes("iphone")?"iPhone":r.includes("ipad")?"iPad":r.includes("android")?"Android":r.includes("windows")||r.includes("win32")?"Windows":r.includes("mac os")||r.includes("macintosh")||r.includes("macintel")?"macOS":r.includes("linux")?"Linux":"Web"}function Do(){const r=`${navigator.userAgent} ${navigator.platform}`.toLowerCase();return r.includes("iphone")||r.includes("android")||r.includes("mobile")?"phone":r.includes("ipad")||r.includes("tablet")?"tablet":"desktop"}function it(r,l="file"){const u=String(r||"").trim();if(!u)return l;if(!/%[0-9A-Fa-f]{2}/.test(u))return u;try{return decodeURIComponent(u)}catch{return u}}function No(r){return r==="phone"?"smartphone":r==="tablet"?"tablet_mac":"laptop_mac"}function jt(r){return r==="phone"?"手机":r==="tablet"?"平板":"桌面端"}function Fo(){return`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/ws`}function Uo(){var r;return Array.isArray((r=Y.value)==null?void 0:r.turn_urls)&&Y.value.turn_urls.some(l=>String(l||"").trim())}function li(){const r=String(window.location.hostname||"").trim().toLowerCase();return r?r==="localhost"||r==="127.0.0.1"||r==="::1"||r.endsWith(".local")||Lo(r)?r:"":"local"}function ai(r){const l=li();return!!l&&r===l}function Lo(r){const l=r.split(".");if(l.length!==4||l.some(T=>!/^\d+$/.test(T)))return!1;const[u,m]=l.map(T=>Number(T));return u===10||u===127||u===192&&m===168?!0:u===172&&m>=16&&m<=31}function ci(r){const l=Array.isArray(r==null?void 0:r.turn_urls)?r.turn_urls.map(u=>String(u||"").trim()).filter(Boolean):[];return l.length?[{urls:l,username:(r==null?void 0:r.turn_username)||"",credential:(r==null?void 0:r.turn_password)||""}]:[]}function as(){var r,l;return{ice_servers:ci(Y.value),p2p_connect_timeout_sec:((r=Y.value)==null?void 0:r.p2p_connect_timeout_sec)||15,turn_connect_timeout_sec:((l=Y.value)==null?void 0:l.turn_connect_timeout_sec)||20}}function ui(){return typeof RTCPeerConnection<"u"}function cs(){wt=null,Tt=null}function Bo(){return wt||(wt=new Promise(r=>{Tt=r})),wt}function fi(r){Tt&&Tt(r),wt=Promise.resolve(r),Tt=null}function We(r=""){if(!h.value.deviceId)return;const l=h.value.baseType||h.value.type||"点对点传输";h.value={...h.value,type:r?`${l} · ${r}`:l}}function jo(r){return!L||G!==r||L.signalingState==="closed"||["failed","disconnected","closed"].includes(L.connectionState)||["failed","disconnected","closed"].includes(L.iceConnectionState)?!0:!B||B.readyState==="closed"}async function rt(r,l={}){return!r||!ui()?null:(jo(r)&&(ot(),Ho(r)),l.initiate&&L.signalingState==="stable"&&await Ko(r),L)}function Ho(r){G=r,_e="p2p",ze=!1,be=!1,ue=!1,$e.delete(r),cs(),L=new RTCPeerConnection({iceServers:ci(Y.value)}),B=L.createDataChannel("filefast-control",{negotiated:!0,id:0,ordered:!0}),zo(B),L.onicecandidate=({candidate:l})=>{if(l)try{ce("webrtc.candidate",r,{candidate:l})}catch(u){console.error(u)}},L.onconnectionstatechange=()=>{if(L){if(us(),L.connectionState==="connected"){We(_e==="turn"?"TURN 中继已连接":"WebRTC 直连已连接");return}if(L.connectionState==="connecting"){We("实时通道连接中");return}if(L.connectionState==="failed"){We("实时通道连接失败");return}(L.connectionState==="disconnected"||L.connectionState==="closed")&&We("实时通道已断开")}},L.oniceconnectionstatechange=()=>{us()}}async function Ko(r){if(L)try{ze=!0,await L.setLocalDescription(),ce("webrtc.description",r,{description:L.localDescription})}finally{ze=!1}}function zo(r){B=r,r.bufferedAmountLowThreshold=ur/2,r.onopen=()=>{fi(r),We(_e==="turn"?"TURN 中继已连接":"WebRTC 直连已连接"),us()},r.onclose=()=>{B===r&&(B=null,cs(),We("实时通道已关闭"))},r.onerror=l=>{console.error(l)},r.onmessage=l=>{Wo(l.data)},r.readyState==="open"&&fi(r)}function ot(){he.clear(),G&&$e.delete(G),B&&(B.onopen=null,B.onclose=null,B.onerror=null,B.onmessage=null,B.close(),B=null),L&&(L.onicecandidate=null,L.onconnectionstatechange=null,L.oniceconnectionstatechange=null,L.close(),L=null),G="",_e="p2p",ze=!1,be=!1,ue=!1,cs()}async function di(r){if(!ui())throw new Error("当前浏览器不支持 WebRTC");if(await rt(r,{initiate:!0}),(B==null?void 0:B.readyState)==="open")return B;const l=await $o(Bo(),of,"WebRTC 连接超时");if(!l||l.readyState!=="open")throw new Error("实时通道未建立");return l}function kn(r,l){if(!r||r.readyState!=="open")throw new Error("实时通道未就绪");r.send(JSON.stringify(l))}function Wo(r){try{const l=JSON.parse(String(r||"{}"));if(l.type==="text"){Vo(l);return}if(l.type==="file-meta"){qo(l);return}if(l.type==="file-chunk"){Go(l);return}l.type==="file-complete"&&Jo(l)}catch(l){console.error(l)}}function Vo(r){var T;const l=r.sender_device_id||G,u={id:l,name:r.sender_name||((T=Rt(l))==null?void 0:T.name)||`设备 ${lt(l)}`,type:jt(r.sender_type||"desktop"),connectionType:_e==="turn"?"TURN 中继已连接":"WebRTC 直连已连接"};M(u,!0);const m=p.value.find(E=>E.transferId===r.transfer_id);if(m&&m.kind==="text"){m.text=r.text||"",m.status="已接收",m.tone="success";return}p.value.push({id:Ue("incoming-text"),transferId:r.transfer_id,kind:"text",text:r.text||"",status:"已接收",tone:"success",copied:!1})}function qo(r){var T;const l=r.sender_device_id||G,u={id:l,name:r.sender_name||((T=Rt(l))==null?void 0:T.name)||`设备 ${lt(l)}`,type:jt(r.sender_type||"desktop"),connectionType:_e==="turn"?"TURN 中继已连接":"WebRTC 直连已连接"};M(u,!0),he.set(r.transfer_id,{name:it(r.name,"file"),mimeType:r.mime_type||"application/octet-stream",sizeBytes:Number(r.size_bytes||0),receivedBytes:0,chunks:[]});let m=p.value.find(E=>E.transferId===r.transfer_id);m?(m.status="正在接收...",m.tone="primary",m.progress=0):(m={id:Ue("incoming-file"),transferId:r.transfer_id,kind:"file",name:it(r.name,"file"),size:At(Number(r.size_bytes||0)),sizeBytes:Number(r.size_bytes||0),status:"正在接收...",tone:"primary",progress:0,pending:!1,downloadUrl:"",ownedDownloadUrl:!1},p.value.push(m))}function Go(r){const l=he.get(r.transfer_id);if(!l)return;const u=So(String(r.chunk_base64||""));l.receivedBytes+=Number(r.chunk_size||u.byteLength||0),l.chunks.push(u);const m=p.value.find(T=>T.transferId===r.transfer_id);if(m){const T=l.sizeBytes>0?l.receivedBytes/l.sizeBytes*100:0;m.progress=Math.max(1,Math.min(99,Math.round(T))),m.status="正在接收...",m.tone="primary"}}function Jo(r){const l=he.get(r.transfer_id);if(!l)return;const u=p.value.find(E=>E.transferId===r.transfer_id);if(!u){he.delete(r.transfer_id);return}const m=new Blob(l.chunks,{type:l.mimeType||"application/octet-stream"}),T=URL.createObjectURL(m);Bt(u,T,!0),u.progress=100,u.status="可保存",u.tone="success",he.delete(r.transfer_id)}function pi(r){return A.value.id.localeCompare(r)>0}function Mn(r,l="等待实时数据"){const u=Rt(r);return{id:r,deviceId:r,name:(u==null?void 0:u.name)||`设备 ${lt(r)}`,type:jt((u==null?void 0:u.type)||"desktop"),connectionType:l,network_group_key:(u==null?void 0:u.network_group_key)||""}}async function hi(r){const u=(r.payload||{}).description,m=r.device_id||"";if(!u||!m)return;M(Mn(m),!0);const T=await rt(m);if(!T)return;const E=pi(m),se=!ze&&(T.signalingState==="stable"||ue),Et=u.type==="offer"&&!se;be=!E&&Et,!be&&(ue=u.type==="answer",await T.setRemoteDescription(u),ue=!1,u.type==="offer"&&(await T.setLocalDescription(),ce("webrtc.description",m,{description:T.localDescription})))}async function mi(r){const l=r.payload||{},u=r.device_id||"";if(!l.candidate||!u)return;(n.value!=="transfer"||h.value.deviceId!==u)&&M(Mn(u),!0);const m=await rt(u);if(m)try{await m.addIceCandidate(l.candidate)}catch(T){be||console.error(T)}}async function us(){if(!(!L||L.connectionState!=="connected"))try{const r=await L.getStats();let l=null;if(r.forEach(E=>{E.type==="transport"&&E.selectedCandidatePairId&&(l=r.get(E.selectedCandidatePairId)||l)}),l||r.forEach(E=>{E.type==="candidate-pair"&&E.state==="succeeded"&&(E.nominated||E.selected)&&(l=E)}),!l)return;const u=r.get(l.localCandidateId),m=r.get(l.remoteCandidateId),T=(u==null?void 0:u.candidateType)==="relay"||(m==null?void 0:m.candidateType)==="relay";_e=T?"turn":"p2p",(B==null?void 0:B.readyState)==="open"&&We(T?"TURN 中继已连接":"WebRTC 直连已连接")}catch(r){console.error(r)}}function gi(){return _e==="turn"?"turn":"p2p"}function vi(){if(!A.value.id)return;const r=localStorage.getItem(Cs)||"";r&&(yi(),de=new WebSocket(`${_.value}?deviceId=${encodeURIComponent(A.value.id)}&deviceToken=${encodeURIComponent(r)}`),de.addEventListener("message",l=>{Xo(l.data)}),de.addEventListener("close",()=>{de=null,Yo()}),de.addEventListener("error",()=>{de==null||de.close()}))}function yi(){if(st&&(window.clearTimeout(st),st=null),!de)return;const r=de;de=null,r.onclose=null,r.close()}function Yo(){st||!A.value.id||(st=window.setTimeout(()=>{st=null,vi()},sf))}function ce(r,l,u){!de||de.readyState!==WebSocket.OPEN||!l||de.send(JSON.stringify({type:r,target_device_id:l,payload:u}))}function Xo(r){try{const l=JSON.parse(r);if(l.type==="presence.update"){v();return}if(l.type==="webrtc.description"){hi(l);return}if(l.type==="webrtc.candidate"){mi(l);return}if(l.type==="transfer.created"){_i(l);return}if(l.type==="transfer.updated"){bi(l);return}if(l.type==="transfer.file"){wi(l);return}l.type==="peer.session.closed"&&Zo(l)}catch(l){console.error(l)}}function Zo(r){const l=r.device_id||"";!l||h.value.deviceId!==l||(ot(),D(),h.value={name:"--",type:"绛夊緟杩炴帴",baseType:"绛夊緟杩炴帴",deviceId:"",networkGroupKey:""},n.value="main")}function _i(r){var E;const l=r.payload||{},u=r.device_id||l.sender_device_id||"",m={id:u,name:l.sender_name||((E=Rt(u))==null?void 0:E.name)||`Device ${lt(u)}`,type:jt(l.sender_type||"desktop")};if(m.connectionType="等待实时数据",M(m,!0),!p.value.find(se=>se.transferId===l.transfer_id)){if(l.kind==="text"){p.value.push({id:Ue("incoming-text"),transferId:l.transfer_id,kind:"text",text:l.content||"",status:"已接收",tone:"success",copied:!1});return}p.value.push({id:Ue("incoming-file"),transferId:l.transfer_id,kind:"file",name:it(l.name,"file"),size:At(Number(l.size_bytes||0)),sizeBytes:Number(l.size_bytes||0),status:"接收中...",tone:"primary",progress:35,pending:!1,downloadUrl:"",ownedDownloadUrl:!1})}}function bi(r){const l=r.payload||{},u=p.value.find(m=>m.transferId===l.transfer_id);if(u&&u.kind==="file"){if(l.final_status==="completed"){u.progress=100,u.status="已接收",u.tone="success",u.downloadUrl&&(u.status="可保存");return}l.final_status==="cancelled"&&(u.status="已取消",u.tone="danger")}}function wi(r){const l=r.payload||{};let u=p.value.find(m=>m.transferId===l.transfer_id);!u&&l.transfer_id&&(u={id:Ue("incoming-file"),transferId:l.transfer_id,kind:"file",name:it(l.name,"file"),size:"",sizeBytes:0,status:"可保存",tone:"success",progress:100,pending:!1,downloadUrl:"",ownedDownloadUrl:!1},p.value.push(u)),!(!u||u.kind!=="file")&&(Bt(u,l.download_url||l.data_url||"",!1),u.status="可保存",u.progress=100,u.tone="success")}function Rt(r){return i.value.find(l=>l.id===r)}function xi(r,l={}){return[{label:"在线设备",value:`${r.devices_online||0}`,tone:"blue"},{label:"待加入房间",value:`${r.rooms_waiting||0}`,tone:"cyan"},{label:"有效传输",value:`${r.transfers_total||0}`,tone:"default"},{label:"累计传输",value:`${r.transfers_cumulative||0}`,tone:"default"},{kind:"minio",label:"MinIO 剩余容量",value:fs(l.remaining_bytes||0),tone:Number(l.usage_percent||0)>=85?"danger":Number(l.usage_percent||0)>=60?"cyan":"blue",percent:Math.max(0,100-Number(l.usage_percent||0)),detail:`已用 ${fs(l.used_bytes||0)} / 总计 ${fs(l.capacity_bytes||0)}`,kicker:`存档 ${l.object_count||0} 份`}]}function fs(r){const l=Number(r||0);if(!l||l<=0)return"0 GB";const u=["B","KB","MB","GB","TB"],m=Math.min(Math.floor(Math.log(l)/Math.log(1024)),u.length-1),T=l/1024**m,E=m>=3?2:T>=10?1:2;return`${T.toFixed(E)} ${u[m]}`}function Ii(r){const l=r.final_status==="completed",u=r.final_status==="failed"||r.final_status==="cancelled";return{time:Rn(r.created_at),peer:`${lt(r.sender_device_id)} -> ${lt(r.receiver_device_id)}`,type:r.kind==="text"?"文本消息":`文件 ${r.name}`,size:At(Number(r.size_bytes||0)),status:l?`已完成 (${r.current_channel||"p2p"})`:u?`已结束 (${r.final_status})`:`进行中 (${r.final_status||"pending"})`,tone:l?"success":u?"danger":"primary"}}function lt(r){return r?r.slice(0,8):"--"}function Rn(r){if(!r)return"刚刚";const l=new Date(r),u=Date.now()-l.getTime();if(!Number.isFinite(u))return"刚刚";const m=Math.max(0,Math.floor(u/1e3));if(m<60)return`${m} 秒前`;const T=Math.floor(m/60);if(T<60)return`${T} 分钟前`;const E=Math.floor(T/60);return E<24?`${E} 小时前`:`${Math.floor(E/24)} 天前`}function Ue(r){return`${r}-${Date.now()}-${Math.random().toString(36).slice(2,8)}`}function At(r){if(!r||r<=0)return"0 B";const l=["B","KB","MB","GB","TB"],u=Math.min(Math.floor(Math.log(r)/Math.log(1024)),l.length-1),m=r/1024**u,T=m>=10||u===0?0:1;return`${m.toFixed(T)} ${l[u]}`}v=async function(){return A.value.id?Fn.listCandidates(A.value.id).then(l=>{i.value=l.map(u=>({...u,description:`${jt(u.type)} · 最近活跃 ${Rn(u.last_seen_at)}`,icon:No(u.type),connectionType:ai(u.network_group_key)?"局域网直连优先":"跨网络实时传输"})),s.value=i.value.length===0}).catch(l=>{s.value=!1,console.error(l)}):Promise.resolve()},P=function(l){const u=l.deviceId||l.id||"",m=l.connectionType||l.type||"点对点传输";b(),h.value.deviceId!==u&&(ot(),D()),h.value={name:l.name,type:m,baseType:m,deviceId:u,networkGroupKey:l.network_group_key||""},a.value=!1,c.value="----",n.value="transfer",We("正在建立实时通道"),rt(u,{initiate:!0})},M=function(l,u=!1){const m=l.deviceId||l.id||"",T=l.connectionType||l.type||"点对点传输";h.value.deviceId===m&&n.value==="transfer"||(ot(),u||D()),h.value={name:l.name,type:T,baseType:T,deviceId:m,networkGroupKey:l.network_group_key||""},a.value=!1,c.value="----",n.value="transfer",m&&(We("正在建立实时通道"),rt(m))},O=function(){h.value.deviceId&&ce("peer.session.closed",h.value.deviceId,{}),ot(),D(),h.value={name:"--",type:"等待连接",baseType:"等待连接",deviceId:"",networkGroupKey:""},n.value="main"},W=async function(l){const u=l.trim();if(u){if(!h.value.deviceId){window.alert("当前没有可用的接收端");return}try{const m=await ge.create({kind:"text",name:"text-message",content:u,sender_device_id:A.value.id,receiver_device_id:h.value.deviceId});try{await rn(m,u)}catch(T){console.warn("realtime text send failed, fallback to relay",T),await ii(m,u)}p.value.push({id:Ue("text"),transferId:m.id,kind:"text",text:u,status:"已发送",tone:"success",copied:!1})}catch(m){window.alert(`发送文本失败:${m.message}`)}}};function Si(r,l){ce("transfer.file",h.value.deviceId,{transfer_id:r.transferId,name:r.name,download_url:l.download_path||l.download_url})}function Ci(r,l,{onProgress:u}={}){if(!(r!=null&&r.file))return Promise.reject(new Error("未找到待上传文件"));if(!(l!=null&&l.fallback_allowed))return Promise.reject(new Error("MinIO 存档未启用"));const m=l.id;if(Te.has(m))return Te.get(m);const T=(async()=>(await ge.presignFallback(m),ge.uploadFallback(m,r.file,E=>{typeof u=="function"&&u(E)})))().finally(()=>{Te.delete(m)});return Te.set(m,T),T}async function Ti(r,l,u){await ge.updateStatus(l.id,{current_channel:"minio",final_status:"completed"}),ce("transfer.updated",h.value.deviceId,{transfer_id:l.id,final_status:"completed",current_channel:"minio"}),Si(r,u),r.progress=100,r.status="已上传到 MinIO,对方可直接领取",r.tone="success"}async function Qo(r,l){const u=$e.get(r);if(!(!(u!=null&&u.length)||!(l!=null&&l.remoteDescription))){$e.delete(r);for(const m of u)try{await l.addIceCandidate(m)}catch(T){console.error(T)}}}return me=async function(l,u){l.progress=Math.max(5,l.progress||0),l.status="正在切换到 MinIO...",l.tone="primary";try{ce("transfer.updated",h.value.deviceId,{transfer_id:l.transferId,final_status:"fallback_uploading",current_channel:"minio"});const m=await Ci(l,u,{onProgress:T=>{l.progress=Math.max(5,Math.min(T,99))}});await Ti(l,u,m)}catch(m){l.pending=!0,l.status=`上传失败:${m.message}`,l.tone="danger"}},hi=async function(l){const m=(l.payload||{}).description,T=l.device_id||"";if(!m||!T)return;M(Mn(T),!0);const E=await rt(T);if(!E)return;const se=pi(T),Et=!ze&&(E.signalingState==="stable"||ue),el=m.type==="offer"&&!Et;if(be=!se&&el,!be&&!(m.type==="answer"&&(E.signalingState!=="have-local-offer"||!E.localDescription))){try{ue=m.type==="answer",await E.setRemoteDescription(m),await Qo(T,E)}catch(ds){console.error(ds)}finally{ue=!1}if(m.type==="offer")try{await E.setLocalDescription(),ce("webrtc.description",T,{description:E.localDescription})}catch(ds){console.error(ds)}}},mi=async function(l){const u=l.payload||{},m=l.device_id||"",T=u.candidate;if(!T||!m)return;(n.value!=="transfer"||h.value.deviceId!==m)&&M(Mn(m),!0);const E=await rt(m);if(E){if(!E.remoteDescription){const se=$e.get(m)||[];se.push(T),$e.set(m,se);return}try{await E.addIceCandidate(T)}catch(se){be||console.error(se)}}},J=async function(l){const u=p.value.find(m=>m.id===l);if(!(!u||u.kind!=="file"||!u.pending)){if(!h.value.deviceId){window.alert("当前没有可用的接收端");return}u.pending=!1,u.status="创建传输中...",u.tone="primary";try{const m=await ge.create({kind:"file",name:u.name,size_bytes:u.sizeBytes,sender_device_id:A.value.id,receiver_device_id:h.value.deviceId});u.transferId=m.id;const T=m.fallback_allowed?Ci(u,m).catch(E=>{throw console.warn("minio backup sync failed",E),E}):Promise.resolve(null);ce("transfer.created",h.value.deviceId,{transfer_id:m.id,kind:"file",name:u.name,size_bytes:u.sizeBytes,sender_device_id:A.value.id,sender_name:A.value.name,sender_type:A.value.type,receiver_device_id:h.value.deviceId,final_status:"connecting",current_channel:"p2p",transport_options:as()});try{if(await ri(u,m),m.fallback_allowed){u.status="实时传输完成,正在同步云端备份...",u.tone="primary";try{const E=await T;E&&(Si(u,E),u.status="已发送,2 小时内可离线领取")}catch(E){u.status=`实时传输成功,但 MinIO 备份失败:${E.message}`,u.tone="danger";return}u.tone="success"}}catch(E){console.warn("realtime file send failed, fallback to minio",E);try{const se=await T;if(se){await Ti(u,m,se);return}}catch(se){console.warn("minio backup sync failed after realtime failure",se)}await oi(u,m)}}catch(m){u.pending=!0,u.status=`发送失败:${m.message}`,u.tone="danger"}}},_i=function(l){var se;const u=l.payload||{},m=l.device_id||u.sender_device_id||"",T={id:m,name:u.sender_name||((se=Rt(m))==null?void 0:se.name)||`设备 ${lt(m)}`,type:jt(u.sender_type||"desktop"),connectionType:"等待实时数据"};if(M(T,!0),!p.value.find(Et=>Et.transferId===u.transfer_id)){if(u.kind==="text"){u.content&&p.value.push({id:Ue("incoming-text"),transferId:u.transfer_id,kind:"text",text:u.content||"",status:"已接收",tone:"success",copied:!1});return}p.value.push({id:Ue("incoming-file"),transferId:u.transfer_id,kind:"file",name:it(u.name,"file"),size:At(Number(u.size_bytes||0)),sizeBytes:Number(u.size_bytes||0),status:"等待接收...",tone:"primary",progress:5,pending:!1,downloadUrl:"",ownedDownloadUrl:!1})}},bi=function(l){const u=l.payload||{},m=p.value.find(T=>T.transferId===u.transfer_id);if(m&&m.kind==="file"){if(u.final_status==="completed"){m.progress=100,m.status=m.downloadUrl?"可保存":"传输完成",m.tone="success";return}if(u.final_status==="cancelled"){m.status="已取消",m.tone="danger";return}u.final_status==="fallback_uploading"&&(m.status="发送端正在上传回退文件...",m.tone="primary")}},wi=function(l){const u=l.payload||{};let m=p.value.find(T=>T.transferId===u.transfer_id);!m&&u.transfer_id&&(m={id:Ue("incoming-file"),transferId:u.transfer_id,kind:"file",name:it(u.name,"file"),size:"",sizeBytes:0,status:"可保存",tone:"success",progress:100,pending:!1,downloadUrl:"",ownedDownloadUrl:!1},p.value.push(m)),!(!m||m.kind!=="file")&&(Bt(m,u.download_url||u.data_url||"",!1),m.status="可保存",m.progress=100,m.tone="success")},Ii=function(l){const u=l.final_status==="completed",m=l.final_status==="failed"||l.final_status==="cancelled",T=it(l.name,"file");return{time:Rn(l.created_at),peer:`${lt(l.sender_device_id)} -> ${lt(l.receiver_device_id)}`,type:l.kind==="text"?"鏂囨湰娑堟伅":`鏂囦欢 ${T}`,size:At(Number(l.size_bytes||0)),status:u?`宸插畬鎴?(${l.current_channel||"p2p"})`:m?`宸茬粨鏉?(${l.final_status})`:`杩涜涓?(${l.final_status||"pending"})`,tone:u?"success":m?"danger":"primary"}},(r,l)=>(j(),V("div",null,[y("div",Zu,[q(Oc,{theme:t.value,onToggleTheme:Cn},null,8,["theme"]),n.value==="main"?(j(),V("div",Qu,[q(gu,{devices:i.value,"is-scanning":s.value,"local-device-name":A.value.name,onSelectDevice:P},null,8,["devices","is-scanning","local-device-name"]),q(ku,{"generated-code":c.value,"is-waiting":a.value,"pending-downloads":f.value,"room-code-input":o.value,onCancelRoom:R,onCreateRoom:x,onJoinRoom:k,onUpdateRoomCode:d},null,8,["generated-code","is-waiting","pending-downloads","room-code-input"])])):Be("",!0),n.value==="transfer"?(j(),vt(ju,{key:1,"connection-type":h.value.type,"has-pending-items":$t.value,items:p.value,"network-hint":Lt.value,"peer-name":h.value.name,onClose:O,onCopyItem:Fe,onFilesSelected:ee,onRemoveItem:Ne,onSendAllPending:we,onSendText:W,onStartUpload:J},null,8,["connection-type","has-pending-items","items","network-hint","peer-name"])):Be("",!0),n.value==="admin"?(j(),vt(ou,{key:2,"file-limit":$.value,"minio-capacity":S.value,records:N.value,stats:U.value,onExit:Mo,onSaveConfig:Ro,"onUpdate:fileLimit":l[0]||(l[0]=u=>$.value=u),"onUpdate:minioCapacity":l[1]||(l[1]=u=>S.value=u)},null,8,["file-limit","minio-capacity","records","stats"])):Be("",!0)]),q(Rc,{onRequestAdmin:ko})]))}};Tc(af).mount("#app"); +**/let Ns;const Vi=typeof window<"u"&&window.trustedTypes;if(Vi)try{Ns=Vi.createPolicy("vue",{createHTML:e=>e})}catch{}const _o=Ns?e=>Ns.createHTML(e):e=>e,Qa="http://www.w3.org/2000/svg",ec="http://www.w3.org/1998/Math/MathML",ct=typeof document<"u"?document:null,qi=ct&&ct.createElement("template"),tc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const i=t==="svg"?ct.createElementNS(Qa,e):t==="mathml"?ct.createElementNS(ec,e):n?ct.createElement(e,{is:n}):ct.createElement(e);return e==="select"&&s&&s.multiple!=null&&i.setAttribute("multiple",s.multiple),i},createText:e=>ct.createTextNode(e),createComment:e=>ct.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>ct.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,i,o){const a=n?n.previousSibling:t.lastChild;if(i&&(i===o||i.nextSibling))for(;t.insertBefore(i.cloneNode(!0),n),!(i===o||!(i=i.nextSibling)););else{qi.innerHTML=_o(s==="svg"?`${e}`:s==="mathml"?`${e}`:e);const c=qi.content;if(s==="svg"||s==="mathml"){const f=c.firstChild;for(;f.firstChild;)c.appendChild(f.firstChild);c.removeChild(f)}t.insertBefore(c,n)}return[a?a.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},nc=Symbol("_vtc");function sc(e,t,n){const s=e[nc];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Gi=Symbol("_vod"),ic=Symbol("_vsh"),rc=Symbol(""),oc=/(?:^|;)\s*display\s*:/;function lc(e,t,n){const s=e.style,i=ae(n);let o=!1;if(n&&!i){if(t)if(ae(t))for(const a of t.split(";")){const c=a.slice(0,a.indexOf(":")).trim();n[c]==null&&zn(s,c,"")}else for(const a in t)n[a]==null&&zn(s,a,"");for(const a in n)a==="display"&&(o=!0),zn(s,a,n[a])}else if(i){if(t!==n){const a=s[rc];a&&(n+=";"+a),s.cssText=n,o=oc.test(n)}}else t&&e.removeAttribute("style");Gi in e&&(e[Gi]=o?s.display:"",e[ic]&&(s.display="none"))}const Ji=/\s*!important$/;function zn(e,t,n){if(H(n))n.forEach(s=>zn(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=ac(e,t);Ji.test(n)?e.setProperty(Ct(s),n.replace(Ji,""),"important"):e[s]=n}}const Yi=["Webkit","Moz","ms"],ws={};function ac(e,t){const n=ws[t];if(n)return n;let s=Ae(t);if(s!=="filter"&&s in e)return ws[t]=s;s=Qn(s);for(let i=0;ixs||(dc.then(()=>xs=0),xs=Date.now());function hc(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;nt(mc(s,n.value),t,5,[s])};return n.value=e,n.attached=pc(),n}function mc(e,t){if(H(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>i=>!i._stopped&&s&&s(i))}else return t}const nr=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,gc=(e,t,n,s,i,o)=>{const a=i==="svg";t==="class"?sc(e,s,a):t==="style"?lc(e,n,s):Xn(t)?Us(t)||uc(e,t,n,s,o):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):vc(e,t,s,a))?(Qi(e,t,s),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Zi(e,t,s,a,o,t!=="value")):e._isVueCE&&(yc(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!ae(s)))?Qi(e,Ae(t),s,o,t):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),Zi(e,t,s,a))};function vc(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&nr(t)&&K(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const i=e.tagName;if(i==="IMG"||i==="VIDEO"||i==="CANVAS"||i==="SOURCE")return!1}return nr(t)&&ae(n)?!1:t in e}function yc(e,t){const n=e._def.props;if(!n)return!1;const s=Ae(t);return Array.isArray(n)?n.some(i=>Ae(i)===s):Object.keys(n).some(i=>Ae(i)===s)}const sr=e=>{const t=e.props["onUpdate:modelValue"]||!1;return H(t)?n=>jn(t,n):t};function _c(e){e.target.composing=!0}function ir(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const Is=Symbol("_assign");function rr(e,t,n){return t&&(e=e.trim()),n&&(e=js(e)),e}const bc={created(e,{modifiers:{lazy:t,trim:n,number:s}},i){e[Is]=sr(i);const o=s||i.props&&i.props.type==="number";Wt(e,t?"change":"input",a=>{a.target.composing||e[Is](rr(e.value,n,o))}),(n||o)&&Wt(e,"change",()=>{e.value=rr(e.value,n,o)}),t||(Wt(e,"compositionstart",_c),Wt(e,"compositionend",ir),Wt(e,"change",ir))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:s,trim:i,number:o}},a){if(e[Is]=sr(a),e.composing)return;const c=(o||e.type==="number")&&!/^0\d/.test(e.value)?js(e.value):e.value,f=t??"";c!==f&&(document.activeElement===e&&e.type!=="range"&&(s&&t===n||i&&e.value.trim()===f)||(e.value=f))}},wc=["ctrl","shift","alt","meta"],xc={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>wc.some(n=>e[`${n}Key`]&&!t.includes(n))},Pn=(e,t)=>{if(!e)return e;const n=e._withMods||(e._withMods={}),s=t.join(".");return n[s]||(n[s]=(i,...o)=>{for(let a=0;a{const n=e._withKeys||(e._withKeys={}),s=t.join(".");return n[s]||(n[s]=i=>{if(!("key"in i))return;const o=Ct(i.key);if(t.some(a=>a===o||Ic[a]===o))return e(i)})},Sc=Ce({patchProp:gc},tc);let or;function Cc(){return or||(or=Pa(Sc))}const Tc=(...e)=>{const t=Cc().createApp(...e),{mount:n}=t;return t.mount=s=>{const i=kc(s);if(!i)return;const o=t._component;!K(o)&&!o.render&&!o.template&&(o.template=i.innerHTML),i.nodeType===1&&(i.textContent="");const a=n(i,!1,$c(i));return i instanceof Element&&(i.removeAttribute("v-cloak"),i.setAttribute("data-v-app","")),a},t};function $c(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function kc(e){return ae(e)?document.querySelector(e):e}const Mc={class:"footer"},Rc={__name:"AppFooter",emits:["request-admin"],setup(e,{emit:t}){const n=t,s=oe(0);let i=null;Qs(()=>{i&&window.clearTimeout(i)});function o(){s.value+=1,s.value===1&&(i=window.setTimeout(()=>{s.value=0,i=null},2e3)),s.value>=5&&(i&&(window.clearTimeout(i),i=null),s.value=0,n("request-admin"))}return(a,c)=>(j(),V("div",Mc,[y("div",null,[c[0]||(c[0]=nn(" © 2026 AirShare Pro. All rights reserved. ",-1)),c[1]||(c[1]=y("span",{class:"divider-line"},"|",-1)),y("span",{id:"admin-trigger",title:"点击 5 次进入后台",onClick:o},"V 1.0.0")]),c[2]||(c[2]=y("div",{style:{"font-size":"12px","margin-top":"4px"}},[y("a",{href:"https://beian.miit.gov.cn/",target:"_blank",rel:"noreferrer"}," 粤ICP备2026888888号-1 ")],-1))]))}},Ac=["fill","stroke"],pe={__name:"LocalIcon",props:{name:{type:String,required:!0},size:{type:[Number,String],default:24}},setup(e){const t=e,n={light_mode:{type:"stroke",shapes:[{tag:"circle",attrs:{cx:"12",cy:"12",r:"4"}},{tag:"path",attrs:{d:"M12 2v2.2"}},{tag:"path",attrs:{d:"M12 19.8V22"}},{tag:"path",attrs:{d:"M4.93 4.93 6.5 6.5"}},{tag:"path",attrs:{d:"m17.5 17.5 1.57 1.57"}},{tag:"path",attrs:{d:"M2 12h2.2"}},{tag:"path",attrs:{d:"M19.8 12H22"}},{tag:"path",attrs:{d:"m4.93 19.07 1.57-1.57"}},{tag:"path",attrs:{d:"M17.5 6.5 19.07 4.93"}}]},dark_mode:{type:"fill",shapes:[{tag:"path",attrs:{d:"M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z"}}]},add_circle:{type:"stroke",shapes:[{tag:"circle",attrs:{cx:"12",cy:"12",r:"9"}},{tag:"path",attrs:{d:"M12 8v8"}},{tag:"path",attrs:{d:"M8 12h8"}}]},sensors:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M12 12h.01"}},{tag:"path",attrs:{d:"M9.2 14.8a4 4 0 0 1 0-5.6"}},{tag:"path",attrs:{d:"M14.8 9.2a4 4 0 0 1 0 5.6"}},{tag:"path",attrs:{d:"M6.4 17.6a8 8 0 0 1 0-11.2"}},{tag:"path",attrs:{d:"M17.6 6.4a8 8 0 0 1 0 11.2"}}]},smartphone:{type:"stroke",shapes:[{tag:"rect",attrs:{x:"7",y:"2.5",width:"10",height:"19",rx:"2.5"}},{tag:"path",attrs:{d:"M10 5h4"}},{tag:"circle",attrs:{cx:"12",cy:"18",r:"0.8"}}]},laptop_mac:{type:"stroke",shapes:[{tag:"rect",attrs:{x:"5",y:"4",width:"14",height:"10",rx:"1.5"}},{tag:"path",attrs:{d:"M3 18h18"}},{tag:"path",attrs:{d:"M8 18a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2"}}]},close:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M6 6l12 12"}},{tag:"path",attrs:{d:"M18 6 6 18"}}]},cloud_upload:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M7 18a4 4 0 1 1 .7-7.94A5.5 5.5 0 0 1 18 11a3.5 3.5 0 1 1-.5 7"}},{tag:"path",attrs:{d:"M12 10v8"}},{tag:"path",attrs:{d:"m8.8 13.2 3.2-3.2 3.2 3.2"}}]},arrow_upward:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M12 19V6"}},{tag:"path",attrs:{d:"m6.5 11.5 5.5-5.5 5.5 5.5"}}]},send_and_archive:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M3 6h18l-2 4H5Z"}},{tag:"path",attrs:{d:"M5 10v8a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-8"}},{tag:"path",attrs:{d:"M12 11v5"}},{tag:"path",attrs:{d:"m9.5 13.5 2.5 2.5 2.5-2.5"}}]},chat_bubble:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M6 18.5 3.5 21v-5A7.5 7.5 0 0 1 11 4.5h2A7.5 7.5 0 0 1 20.5 12v.5A7.5 7.5 0 0 1 13 20H8.5"}}]},content_copy:{type:"stroke",shapes:[{tag:"rect",attrs:{x:"9",y:"9",width:"10",height:"10",rx:"2"}},{tag:"path",attrs:{d:"M7 15H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v1"}}]},check:{type:"stroke",shapes:[{tag:"path",attrs:{d:"m5 12 4.2 4.2L19 7.5"}}]},draft:{type:"stroke",shapes:[{tag:"rect",attrs:{x:"4",y:"5",width:"16",height:"14",rx:"2"}},{tag:"path",attrs:{d:"m5 7 7 5 7-5"}}]},save:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M5 20h14a1 1 0 0 0 1-1V7.5L16.5 4H5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1Z"}},{tag:"path",attrs:{d:"M8 4v5h7"}},{tag:"rect",attrs:{x:"8",y:"14",width:"8",height:"4",rx:"1"}}]},download:{type:"stroke",shapes:[{tag:"path",attrs:{d:"M12 5v10"}},{tag:"path",attrs:{d:"m7.5 10.5 4.5 4.5 4.5-4.5"}},{tag:"path",attrs:{d:"M5 19h14"}}]}},s=It(()=>n[t.name]||n.close),i=It(()=>typeof t.size=="number"?`${t.size}px`:/^\d+(\.\d+)?$/.test(t.size)?`${t.size}px`:t.size);return(o,a)=>(j(),V("span",{class:"app-icon",style:Le({width:i.value,height:i.value}),"aria-hidden":"true"},[(j(),V("svg",{viewBox:"0 0 24 24",fill:s.value.type==="fill"?"currentColor":"none",stroke:s.value.type==="stroke"?"currentColor":"none","stroke-width":"1.8","stroke-linecap":"round","stroke-linejoin":"round"},[(j(!0),V(fe,null,en(s.value.shapes,(c,f)=>(j(),vt(ua(c.tag),go({key:`${e.name}-${f}`},{ref_for:!0},c.attrs),null,16))),128))],8,Ac))],4))}},Ec={class:"header"},Oc={__name:"AppHeader",props:{theme:{type:String,required:!0}},emits:["toggle-theme"],setup(e){return(t,n)=>(j(),V("div",Ec,[n[1]||(n[1]=y("h1",null,"AirShare Pro",-1)),n[2]||(n[2]=y("p",null,"跨端局域网 & P2P 传输中心",-1)),y("button",{class:"theme-toggle",title:"切换日夜模式",onClick:n[0]||(n[0]=s=>t.$emit("toggle-theme"))},[q(pe,{id:"theme-icon",name:e.theme==="dark"?"dark_mode":"light_mode",size:"22"},null,8,["name"])])]))}},Pc={class:"card"},Dc={key:0,class:"section-title"},mn={__name:"GlassCard",props:{title:{type:String,default:""}},setup(e){return(t,n)=>(j(),V("div",Pc,[e.title?(j(),V("div",Dc,te(e.title),1)):Be("",!0),da(t.$slots,"default")]))}},Nc={class:"admin-panel active"},Fc={class:"card admin-header-card"},Uc={class:"transfer-head transfer-head-compact"},Lc={class:"main-grid admin-summary-grid"},Bc={class:"admin-stats-panel"},jc={class:"admin-stats-row"},Hc={class:"admin-fluid-content"},Kc={class:"admin-fluid-icon"},zc={class:"admin-fluid-copy"},Wc={key:0,class:"stat-suffix"},Vc={class:"admin-config-stack"},qc={class:"text-input-group admin-config-row admin-config-row-field admin-config-row-last"},Gc={class:"admin-field-control-row"},Jc=["value"],Yc={class:"text-input-group admin-config-row admin-config-row-field admin-config-row-last"},Xc={class:"admin-field-control-row"},Zc=["value"],Qc={class:"admin-config-insights"},eu={class:"admin-config-highlight"},tu={class:"admin-config-highlight"},nu={class:"admin-table-wrapper"},su={class:"admin-table"},iu={class:"admin-record-type-cell"},ru=["title"],ou={__name:"AdminPanel",props:{stats:{type:Array,required:!0},records:{type:Array,required:!0},fileLimit:{type:Number,required:!0},minioCapacity:{type:Number,required:!0}},emits:["exit","save-config","update:file-limit","update:minio-capacity"],setup(e){function t(a){const c=Number(a)||0;return c>=1024?`${(c/1024).toFixed(c%1024===0?0:1)} GB`:`${c} MB`}function n(a){const c=Number(a)||0;return c>=1024?`${(c/1024).toFixed(c%1024===0?0:1)} TB`:`${c} GB`}function s(a){return a==="blue"?{color:"var(--accent-blue)"}:a==="cyan"?{color:"var(--accent-cyan)"}:a==="success"?{color:"var(--success-green)"}:a==="danger"?{color:"var(--danger-red)"}:{color:"var(--text-main)"}}function i(a){return a==="success"?{color:"var(--success-green)",fontWeight:500}:a==="primary"?{color:"var(--accent-blue)",fontWeight:500}:{color:"var(--danger-red)",fontWeight:500}}function o(a){const c=Number(a)||0;return{"--fluid-level":`${Math.max(0,Math.min(c,100))}%`}}return(a,c)=>(j(),V("div",Nc,[y("div",Fc,[y("div",Uc,[c[5]||(c[5]=y("div",{class:"connected-to"},[y("h2",{class:"admin-title"},"管理控制台"),y("p",{class:"admin-subtitle"},"AirShare Pro System Dashboard")],-1)),y("button",{class:"btn-small-primary",type:"button",onClick:c[0]||(c[0]=f=>a.$emit("exit"))},"退出管理")])]),y("div",Lc,[q(mn,{class:"admin-stats-card",title:"系统运行状态"},{default:Yt(()=>[y("div",Bc,[y("div",jc,[(j(!0),V(fe,null,en(e.stats,f=>(j(),V("div",{key:f.label,class:St(["admin-stat-item",{"admin-stat-item-fluid":f.kind==="minio"}])},[f.kind==="minio"?(j(),V("div",{key:0,class:"admin-fluid-card",style:Le(o(f.percent))},[c[6]||(c[6]=y("div",{class:"admin-fluid-fill"},[y("span",{class:"admin-fluid-wave admin-fluid-wave-a"}),y("span",{class:"admin-fluid-wave admin-fluid-wave-b"})],-1)),y("div",Hc,[y("div",Kc,[q(pe,{name:"save",size:"18"})]),y("div",zc,[y("h3",{style:Le(s(f.tone))},te(f.value),5),y("p",null,te(f.label),1),y("small",null,te(f.detail),1)])])],4)):(j(),V(fe,{key:1},[c[7]||(c[7]=y("span",{class:"admin-stat-kicker"},"实时指标",-1)),y("h3",{style:Le(s(f.tone))},[nn(te(f.value),1),f.suffix?(j(),V("span",Wc,te(f.suffix),1)):Be("",!0)],4),y("p",null,te(f.label),1)],64))],2))),128))])])]),_:1}),q(mn,{class:"admin-config-card",title:"核心参数配置"},{default:Yt(()=>[y("div",Vc,[y("div",qc,[c[8]||(c[8]=y("div",{class:"admin-field-meta"},[y("label",{class:"admin-field-label",for:"admin-file-limit"},"单文件大小限制"),y("p",{class:"admin-field-hint"},"单位为 MB,超过该阈值的文件会按当前后端策略处理。")],-1)),y("div",Gc,[y("input",{id:"admin-file-limit",value:e.fileLimit,min:"1",placeholder:"10240",type:"number",onInput:c[1]||(c[1]=f=>a.$emit("update:file-limit",Number(f.target.value)||0))},null,40,Jc),y("button",{title:"保存配置",type:"button",onClick:c[2]||(c[2]=f=>a.$emit("save-config"))},[q(pe,{name:"save",size:"18"})])])]),y("div",Yc,[c[9]||(c[9]=y("div",{class:"admin-field-meta"},[y("label",{class:"admin-field-label",for:"admin-minio-capacity"},"MinIO 总容量"),y("p",{class:"admin-field-hint"},"单位为 GB,用于容量卡和液位比例计算。")],-1)),y("div",Xc,[y("input",{id:"admin-minio-capacity",value:e.minioCapacity,min:"1",placeholder:"120",type:"number",onInput:c[3]||(c[3]=f=>a.$emit("update:minio-capacity",Number(f.target.value)||0))},null,40,Zc),y("button",{title:"保存配置",type:"button",onClick:c[4]||(c[4]=f=>a.$emit("save-config"))},[q(pe,{name:"save",size:"18"})])])]),y("div",Qc,[y("div",eu,[c[10]||(c[10]=y("span",{class:"admin-config-badge"},"ACTIVE POLICY",-1)),y("h3",null,te(t(e.fileLimit)),1),c[11]||(c[11]=y("p",null,"当前单文件阈值。超过该体积后,文件会按后端已设定的传输与存档策略处理。",-1))]),y("div",tu,[c[12]||(c[12]=y("span",{class:"admin-config-badge"},"MINIO CAPACITY",-1)),y("h3",null,te(n(e.minioCapacity)),1),c[13]||(c[13]=y("p",null,"当前 MinIO 总容量基线,用于后台容量展示与液位占比计算。",-1))])])])]),_:1})]),q(mn,{class:"admin-table-card",title:"最近传输记录(Top 5)"},{default:Yt(()=>[y("div",nu,[y("table",su,[c[14]||(c[14]=y("thead",null,[y("tr",null,[y("th",null,"时间"),y("th",null,"发送端特征"),y("th",null,"传输类型"),y("th",null,"数据量"),y("th",null,"状态")])],-1)),y("tbody",null,[(j(!0),V(fe,null,en(e.records,f=>(j(),V("tr",{key:`${f.time}-${f.peer}`},[y("td",null,te(f.time),1),y("td",null,te(f.peer),1),y("td",iu,[y("span",{class:"admin-record-type",title:f.type},te(f.type),9,ru)]),y("td",null,te(f.size),1),y("td",{style:Le(i(f.tone))},te(f.status),5)]))),128))])])])]),_:1})]))}},lu={class:"local-device-name"},au={key:0,class:"radar-container"},cu={class:"radar"},uu={key:1,class:"device-list"},fu=["onClick"],du={class:"device-icon"},pu={class:"device-info"},hu={key:2,class:"radar-container"},mu={class:"radar"},gu={__name:"DeviceDiscoveryCard",props:{isScanning:{type:Boolean,required:!0},localDeviceName:{type:String,default:""},devices:{type:Array,required:!0}},emits:["select-device"],setup(e,{emit:t}){const n=t;function s(i){n("select-device",i)}return(i,o)=>(j(),vt(mn,{title:"局域网自动发现"},{default:Yt(()=>[y("p",lu,[o[0]||(o[0]=nn(" 本机:",-1)),y("strong",null,te(e.localDeviceName||"识别中"),1)]),e.isScanning?(j(),V("div",au,[y("div",cu,[q(pe,{class:"radar-icon",name:"sensors",size:"36"})]),o[1]||(o[1]=y("p",{class:"scan-status"},"正在扫描附近设备...",-1))])):e.devices.length?(j(),V("div",uu,[(j(!0),V(fe,null,en(e.devices,a=>(j(),V("button",{key:a.id,class:"device-item",type:"button",onClick:c=>s(a)},[y("div",du,[q(pe,{name:a.icon,size:"24"},null,8,["name"])]),y("div",pu,[y("h4",null,te(a.name),1),y("p",null,te(a.description),1)]),o[2]||(o[2]=y("div",{class:"device-status-beacon","aria-hidden":"true"},[y("span",{class:"device-status-dot"}),y("span",{class:"device-status-ring"}),y("span",{class:"device-status-ring device-status-ring-delay"})],-1))],8,fu))),128))])):(j(),V("div",hu,[y("div",mu,[q(pe,{class:"radar-icon",name:"devices",size:"36"})]),o[3]||(o[3]=y("p",{class:"scan-status"},"暂未发现局域网设备,请保持页面开启后重试",-1))]))]),_:1}))}},vu={key:0,class:"room-action-area"},yu={class:"room-input-group"},_u=["value"],bu={key:0,class:"pending-downloads"},wu={class:"pending-downloads-head"},xu=["href"],Iu={class:"pending-download-copy"},Su=["title"],Cu={class:"pending-download-icon","aria-hidden":"true"},Tu={key:1,class:"waiting-area"},$u={class:"huge-code"},ku={__name:"RemoteRoomCard",props:{roomCodeInput:{type:String,required:!0},isWaiting:{type:Boolean,required:!0},generatedCode:{type:String,required:!0},pendingDownloads:{type:Array,required:!0}},emits:["update-room-code","create-room","join-room","cancel-room"],setup(e,{emit:t}){const n=t;function s(o){n("update-room-code",o.target.value)}function i(){n("join-room")}return(o,a)=>(j(),vt(mn,{title:"远程直连"},{default:Yt(()=>[e.isWaiting?(j(),V("div",Tu,[a[6]||(a[6]=y("p",{class:"waiting-subtitle"},"您的房间号码",-1)),y("div",$u,te(e.generatedCode),1),a[7]||(a[7]=y("div",{class:"spinner"},null,-1)),a[8]||(a[8]=y("p",{class:"waiting-tip"},"等待对方加入...",-1)),y("button",{class:"btn-cancel",type:"button",onClick:a[2]||(a[2]=c=>o.$emit("cancel-room"))},"取消建房")])):(j(),V("div",vu,[y("button",{class:"btn-create",type:"button",onClick:a[0]||(a[0]=c=>o.$emit("create-room"))},[q(pe,{name:"add_circle",size:"22"}),a[3]||(a[3]=nn(" 创建专属传输房间 ",-1))]),a[5]||(a[5]=y("div",{class:"divider"},"或",-1)),y("div",yu,[y("input",{class:"room-code",inputmode:"numeric",maxlength:"4",pattern:"\\d*",placeholder:"输入4位房间号",type:"text",value:e.roomCodeInput,onInput:s,onKeyup:bo(i,["enter"])},null,40,_u),y("button",{class:"btn-primary",type:"button",onClick:a[1]||(a[1]=c=>o.$emit("join-room"))},"加入房间")]),e.pendingDownloads.length?(j(),V("div",bu,[y("div",wu,[a[4]||(a[4]=y("span",null,"待领取文件",-1)),y("span",null,te(e.pendingDownloads.length),1)]),(j(!0),V(fe,null,en(e.pendingDownloads,c=>(j(),V("a",{key:c.transfer_id,class:"pending-download-item",href:c.download_path,target:"_blank",rel:"noopener noreferrer"},[y("div",Iu,[y("strong",{title:c.name},te(c.name),9,Su),y("p",null,te(c.size_label)+" · "+te(c.created_label),1)]),y("span",Cu,[q(pe,{name:"download",size:"18"})])],8,xu))),128))])):Be("",!0)]))]),_:1}))}},Mu={class:"file-info"},Ru=["title"],Au={class:"file-info-right"},Eu=["download","href"],Ou={key:0,class:"progress-bar-container"},Pu={__name:"TransferQueueItem",props:{item:{type:Object,required:!0}},emits:["remove","start-upload","copy"],setup(e){const t=e,n=It(()=>t.item.tone==="success"?{color:"var(--success-green)"}:t.item.tone==="primary"?{color:"var(--accent-blue)"}:t.item.tone==="danger"?{color:"var(--danger-red)"}:{color:"var(--text-secondary)"}),s=It(()=>t.item.kind==="text"?{color:"var(--success-green)",background:"rgba(48, 209, 88, 0.1)"}:{});return(i,o)=>(j(),V("div",{class:St(["batch-item",{"pending-file":e.item.kind==="file"&&e.item.pending}])},[y("div",Mu,[y("div",{class:"file-info-left",style:Le(e.item.kind==="text"?{maxWidth:"70%"}:null)},[y("div",{class:"file-icon-wrapper",style:Le(s.value)},[q(pe,{name:e.item.kind==="text"?"chat_bubble":"draft",size:"18"},null,8,["name"])],4),y("span",{class:"file-name",title:e.item.kind==="text"?e.item.text:e.item.name},te(e.item.kind==="text"?e.item.text:e.item.name),9,Ru)],4),y("div",Au,[y("span",{class:"file-status",style:Le(n.value)},te(e.item.kind==="text"&&e.item.copied?"已复制":e.item.status),5),e.item.kind==="text"?(j(),V("button",{key:0,class:"action-btn",title:"复制文本",type:"button",onClick:o[0]||(o[0]=a=>i.$emit("copy",e.item.id))},[q(pe,{name:e.item.copied?"check":"content_copy",size:"16"},null,8,["name"])])):Be("",!0),e.item.kind==="file"&&e.item.pending?(j(),V("button",{key:1,class:"action-btn primary",title:"发送文件",type:"button",onClick:o[1]||(o[1]=a=>i.$emit("start-upload",e.item.id))},[q(pe,{name:"arrow_upward",size:"16"})])):Be("",!0),e.item.kind==="file"&&e.item.downloadUrl?(j(),V("a",{key:2,class:"action-btn primary",download:e.item.name,href:e.item.downloadUrl,title:"保存文件"},[q(pe,{name:"download",size:"16"})],8,Eu)):Be("",!0),y("button",{class:"action-btn danger",title:"移除记录",type:"button",onClick:o[2]||(o[2]=a=>i.$emit("remove",e.item.id))},[q(pe,{name:"close",size:"16"})])])]),e.item.kind==="file"?(j(),V("div",Ou,[y("div",{class:St(["progress-bar-fill",{success:e.item.tone==="success"}]),style:Le({width:`${e.item.progress}%`})},null,6)])):Be("",!0)],2))}},Du={class:"transfer-panel active"},Nu={class:"card"},Fu={class:"transfer-head"},Uu={class:"connected-to"},Lu={key:0,class:"connection-hint"},Bu={class:"text-input-group"},ju={__name:"TransferPanel",props:{peerName:{type:String,required:!0},connectionType:{type:String,required:!0},networkHint:{type:String,default:""},items:{type:Array,required:!0},hasPendingItems:{type:Boolean,required:!0}},emits:["close","send-text","files-selected","send-all-pending","remove-item","start-upload","copy-item"],setup(e,{emit:t}){const n=e,s=t,i=oe(""),o=oe(!1),a=oe(null),c=oe(null);Ut(()=>n.items.length,async()=>{await Nr(),a.value&&(a.value.scrollTop=a.value.scrollHeight)});function f(){var $;($=c.value)==null||$.click()}function h(){s("send-text",i.value),i.value=""}function p($){const S=Array.from($.target.files||[]);S.length&&s("files-selected",S),$.target.value=""}function _($){var U;o.value=!1;const S=Array.from(((U=$.dataTransfer)==null?void 0:U.files)||[]);S.length&&s("files-selected",S)}return($,S)=>(j(),V("div",Du,[y("div",Nu,[y("div",Fu,[y("div",Uu,[y("h2",null,te(e.peerName),1),y("p",null,te(e.connectionType),1),e.networkHint?(j(),V("small",Lu,te(e.networkHint),1)):Be("",!0)]),y("button",{class:"close-btn",type:"button",onClick:S[0]||(S[0]=U=>$.$emit("close"))},[q(pe,{name:"close",size:"20"})])]),y("div",{class:St(["drop-zone",{"drop-zone-active":o.value}]),onClick:f,onDragenter:S[1]||(S[1]=Pn(U=>o.value=!0,["prevent"])),onDragover:S[2]||(S[2]=Pn(U=>o.value=!0,["prevent"])),onDragleave:S[3]||(S[3]=Pn(U=>o.value=!1,["prevent"])),onDrop:Pn(_,["prevent"])},[q(pe,{class:"drop-zone-icon",name:"cloud_upload",size:"42"}),S[9]||(S[9]=y("p",{class:"drop-zone-text"},"点击或拖拽多个文件到这里",-1)),y("input",{ref_key:"fileInput",ref:c,class:"hidden",multiple:"",type:"file",onChange:p},null,544)],34),y("div",Bu,[Wl(y("input",{"onUpdate:modelValue":S[4]||(S[4]=U=>i.value=U),placeholder:"输入要发送的文本或链接...",type:"text",onKeyup:bo(h,["enter"])},null,544),[[bc,i.value]]),y("button",{title:"发送文本",type:"button",onClick:h},[q(pe,{name:"arrow_upward",size:"20"})])]),y("div",{class:St(["batch-actions",{active:e.hasPendingItems}])},[y("button",{class:"btn-small-primary",type:"button",onClick:S[5]||(S[5]=U=>$.$emit("send-all-pending"))},[q(pe,{name:"send_and_archive",size:"16"}),S[10]||(S[10]=nn(" 一键发送全部 ",-1))])],2),e.items.length?(j(),V("div",{key:0,ref_key:"batchContainer",ref:a,class:"batch-progress-container"},[(j(!0),V(fe,null,en(e.items,U=>(j(),vt(Pu,{key:U.id,item:U,onCopy:S[6]||(S[6]=N=>$.$emit("copy-item",N)),onRemove:S[7]||(S[7]=N=>$.$emit("remove-item",N)),onStartUpload:S[8]||(S[8]=N=>$.$emit("start-upload",N))},null,8,["item"]))),128))],512)):Be("",!0)])]))}};let Vt={deviceId:"",token:""};const Hu="filefast_device_id",Ku="filefast_device_token";function wo(){return!Vt.deviceId||!Vt.token?{}:{"X-Device-ID":Vt.deviceId,"X-Device-Token":Vt.token}}function zu(e={},t=!1){return{...t?{"Content-Type":"application/json"}:{},...wo(),...e}}function Wu(e,t){if(!t||Object.keys(t).length===0)return e;const n=new URLSearchParams;Object.entries(t).forEach(([i,o])=>{o!=null&&o!==""&&n.set(i,String(o))});const s=n.toString();return s?`${e}?${s}`:e}async function Dn(e,t={}){const n=t.body!==void 0,s=await fetch(Wu(e,t.query),{method:t.method||"GET",headers:zu(t.headers,n),body:n?JSON.stringify(t.body):void 0}),i=await s.json().catch(()=>({}));if(!s.ok){const o=new Error(i.error||`Request failed: ${s.status}`);throw o.status=s.status,o}return i.data}const ve={get(e,t={}){return Dn(e,{...t,method:"GET"})},post(e,t,n={}){return Dn(e,{...n,method:"POST",body:t})},put(e,t,n={}){return Dn(e,{...n,method:"PUT",body:t})},patch(e,t,n={}){return Dn(e,{...n,method:"PATCH",body:t})}};function lr(e,t){Vt={deviceId:e||"",token:t||""},Gu(Vt)}function Vu(){return wo()}function qu(e){return{Authorization:`Bearer ${e}`}}function Gu(e){typeof document>"u"||(ar(Hu,e.deviceId),ar(Ku,e.token))}function ar(e,t){if(!t){document.cookie=`${e}=; Path=/; Max-Age=0; SameSite=Lax`;return}document.cookie=`${e}=${encodeURIComponent(t)}; Path=/; SameSite=Lax`}function Nn(e){return{headers:qu(e)}}const Kt={login(e,t){return ve.post("/api/admin/login",{username:e,password:t})},stats(e){return ve.get("/api/admin/stats",Nn(e))},config(e){return ve.get("/api/admin/config",Nn(e))},updateConfig(e,t){return ve.put("/api/admin/config",t,Nn(e))},recentTransfers(e){return ve.get("/api/admin/transfers/recent",Nn(e))}},Fn={register(e){return ve.post("/api/devices/register",e)},heartbeat(e){return ve.post("/api/devices/heartbeat",{device_id:e})},listCandidates(e){return ve.get("/api/devices/candidates",{query:{deviceId:e}})},listPendingDownloads(e){return ve.get(`/api/devices/${encodeURIComponent(e)}/pending-downloads`)}},Un={create(e){return ve.post("/api/rooms",{creator_device_id:e})},get(e){return ve.get(`/api/rooms/${encodeURIComponent(e)}`)},join(e,t){return ve.post("/api/rooms/join",{code:e,joiner_device_id:t})},cancel(e,t){return ve.post(`/api/rooms/${encodeURIComponent(e)}/cancel`,{requester_id:t})}},Ju={config(){return ve.get("/api/runtime/config")}},ge={create(e){return ve.post("/api/transfers",e)},presignFallback(e){return ve.post(`/api/transfers/${encodeURIComponent(e)}/fallback/presign`,{})},uploadFallback(e,t,n){return Yu(`/api/transfers/${encodeURIComponent(e)}/fallback/upload`,t,n)},updateStatus(e,t){return ve.patch(`/api/transfers/${encodeURIComponent(e)}/status`,t)}};function Yu(e,t,n){return new Promise((s,i)=>{const o=new XMLHttpRequest;o.open("PUT",e),o.responseType="json",o.setRequestHeader("Content-Type",t.type||"application/octet-stream"),Object.entries(Vu()).forEach(([a,c])=>{o.setRequestHeader(a,c)}),o.upload.onprogress=a=>{!a.lengthComputable||typeof n!="function"||n(Math.round(a.loaded/a.total*100))},o.onload=()=>{const a=o.response||Xu(o.responseText);if(o.status>=200&&o.status<300){s(a.data);return}i(new Error((a==null?void 0:a.error)||`Upload failed: ${o.status}`))},o.onerror=()=>i(new Error("Upload failed")),o.send(t)})}function Xu(e){try{return JSON.parse(e)}catch{return null}}const Zu={class:"container"},Qu={key:0,class:"main-grid"},Ss="filefast-admin-token",Ln="filefast-admin-view",Bn="filefast-device-id",cr="filefast-device-name",Cs="filefast-device-token",ef=15e3,tf=5e3,nf=2e3,sf=3e3,rf=4*1024*1024,of=2e4,lf=16*1024,ur=512*1024,af={__name:"App",setup(e){const t=oe(localStorage.getItem("airshare-theme")||"light"),n=oe(localStorage.getItem(Ln)==="admin"?"admin":"main"),s=oe(!0),i=oe([]),o=oe(""),a=oe(!1),c=oe("----"),f=oe([]),h=oe({name:"--",type:"等待连接",deviceId:"",networkGroupKey:""}),p=oe([]),_=oe("/ws"),$=oe(10240),S=oe(120),U=oe([]),N=oe([]),Y=oe(null),z=oe(localStorage.getItem(Ss)||""),A=oe({id:"",name:"",type:""}),Q=localStorage.getItem(Bn)||"",F=localStorage.getItem(Cs)||"";Q&&F&&lr(Q,F);const le=new Map,he=new Map,Te=new Map,$e=new Map;let _t=null,He=null,Je=null,Ke=null,bt=null,de=null,st=null,L=null,B=null,G="",_e="p2p",ze=!1,be=!1,ue=!1,wt=null,Tt=null;const Sn=It(()=>p.value.filter(r=>r.kind==="file"&&r.pending)),$t=It(()=>Sn.value.length>0),Lt=It(()=>!h.value.deviceId||ai(h.value.networkGroupKey)||Uo()?"":"当前是跨网络访问,未配置 TURN 时实时通道可能失败。文本和小文件可回退中转,大文件建议使用 MinIO。");Ut(t,r=>{document.body.setAttribute("data-theme",r),localStorage.setItem("airshare-theme",r)},{immediate:!0}),Ut(n,r=>{if(r==="admin"&&z.value){localStorage.setItem(Ln,"admin");return}localStorage.removeItem(Ln)}),Ut([n,z],([r,l])=>{Ke&&(window.clearInterval(Ke),Ke=null),!(r!=="admin"||!l)&&(Ke=window.setInterval(()=>{ls().catch(u=>{console.error(u)})},5e3))}),Vr(async()=>{_.value=Fo(),await sn(),n.value==="admin"&&z.value&&ls().catch(r=>{console.error(r)}),He=window.setInterval(()=>{w()},ef),_t=window.setInterval(()=>{v()},tf),bt=window.setInterval(()=>{I()},1e4)}),Qs(()=>{_t&&window.clearInterval(_t),He&&window.clearInterval(He),Ke&&window.clearInterval(Ke),bt&&window.clearInterval(bt),b(),ot(),yi(),D()});async function sn(){try{await kt(),await g(),await v()}catch(r){window.alert(`后端连接失败:${r.message}`)}}function Cn(){t.value=t.value==="dark"?"light":"dark"}async function kt(){try{Tn(await Ju.config())}catch(r){console.error(r)}}function Tn(r){r&&(Y.value=r,$.value=Math.round((r.max_minio_fallback_size_bytes||0)/1024/1024),S.value=Math.max(0,Math.round((r.minio_capacity_bytes||0)/1024/1024/1024)))}function d(r){o.value=r.replace(/\D/g,"").slice(0,4)}async function g(){const r=Ao(),l=Eo(r),u=Do(),m=await Fn.register({device_id:r,name:l,type:u,network_group_key:li()});localStorage.setItem(Bn,m.id),m.auth_token&&(localStorage.setItem(Cs,m.auth_token),lr(m.id,m.auth_token)),A.value={id:m.id,name:m.name,type:m.type},await I(),vi()}async function v(){if(A.value.id)try{const r=await ge.create({kind:"text",name:"text-message",content:value,sender_device_id:A.value.id,receiver_device_id:h.value.deviceId});try{await rn(r,value)}catch(l){console.warn("realtime text send failed, fallback to relay",l),await ii(r,value)}p.value.push({id:Ue("text"),transferId:r.id,kind:"text",text:value,status:"已发送",tone:"success",copied:!1})}catch(r){window.alert(`发送文本失败:${r.message}`)}}async function I(){if(!A.value.id){f.value=[];return}try{const r=await Fn.listPendingDownloads(A.value.id);f.value=r.map(l=>({...l,name:it(l.name),download_path:l.download_path||`/api/transfers/${encodeURIComponent(l.transfer_id)}/fallback/download`,size_label:At(Number(l.size_bytes||0)),created_label:Rn(l.created_at)}))}catch(r){if((r==null?void 0:r.status)===404){f.value=[];return}console.error(r)}}async function w(){if(A.value.id)try{await Fn.heartbeat(A.value.id)}catch(r){console.error(r)}}async function x(){if(!A.value.id){window.alert("当前设备尚未注册到后端");return}try{const r=await Un.create(A.value.id);c.value=r.code,a.value=!0,C(r.code)}catch(r){window.alert(`创建房间失败:${r.message}`)}}async function R(){const r=c.value;b();try{a.value&&r!=="----"&&await Un.cancel(r,A.value.id)}catch(l){console.error(l)}finally{a.value=!1,c.value="----"}}async function k(){if(!(o.value.length<4))try{const r=await Un.join(o.value,A.value.id),l=Rt(r.creator_device_id);o.value="",P({deviceId:r.creator_device_id,name:(l==null?void 0:l.name)||`房间 ${r.code} 创建者`,type:"房间配对成功"})}catch(r){window.alert(`加入房间失败:${r.message}`)}}function C(r){b(),Je=window.setInterval(async()=>{try{const l=await Un.get(r);if(l.status==="joined"&&l.joiner_device_id){const u=Rt(l.joiner_device_id);P({deviceId:l.joiner_device_id,name:(u==null?void 0:u.name)||`房间 ${r} 对端`,type:"房间配对成功"});return}(l.status==="expired"||l.status==="canceled")&&(b(),a.value=!1,c.value="----")}catch(l){console.error(l)}},nf)}function b(){Je&&(window.clearInterval(Je),Je=null)}function P(r){const l=r.deviceId||r.id||"",u=r.connectionType||r.type||"点对点传输";b(),h.value.deviceId!==l&&(ot(),D()),h.value={name:r.name,type:r.connectionType||r.type||"点对点传输",deviceId:r.deviceId||r.id||"",networkGroupKey:r.network_group_key||""},a.value=!1,c.value="----",n.value="transfer",h.value.baseType=u,h.value.type=u,h.value.deviceId=l,We("正在建立实时通道"),rt(l,{initiate:!0})}function M(r,l=!1){const u=r.deviceId||r.id||"",m=r.connectionType||r.type||"点对点传输";h.value.deviceId===u&&n.value==="transfer"||(ot(),l||D()),h.value={name:r.name,type:r.connectionType||r.type||"点对点传输",deviceId:u,networkGroupKey:r.network_group_key||""},a.value=!1,c.value="----",n.value="transfer",h.value.baseType=m,h.value.type=m,h.value.deviceId=u,We("正在建立实时通道"),rt(u)}function O(){ot(),D(),h.value={name:"--",type:"等待连接",deviceId:"",networkGroupKey:""},n.value="main",h.value.baseType="等待连接",h.value.type="等待连接"}function D(){p.value.forEach(r=>Pe(r)),p.value=[],he.clear()}async function W(r){const l=r.trim();if(l){if(!h.value.deviceId){window.alert("当前没有可用的接收端");return}try{const u=await ge.create({kind:"text",name:"text-message",content:l,sender_device_id:A.value.id,receiver_device_id:h.value.deviceId});ce("transfer.created",h.value.deviceId,{transfer_id:u.id,kind:"text",name:"text-message",content:l,sender_device_id:A.value.id,sender_name:A.value.name,sender_type:A.value.type,receiver_device_id:h.value.deviceId,final_status:"completed",current_channel:"p2p",transport_options:as()}),await ge.updateStatus(u.id,{current_channel:"p2p",final_status:"completed"}),ce("transfer.updated",h.value.deviceId,{transfer_id:u.id,final_status:"completed",current_channel:"p2p"}),p.value.push({id:Ue("text"),transferId:u.id,kind:"text",text:l,status:"已发送",tone:"success",copied:!1})}catch(u){window.alert(`发送文本失败:${u.message}`)}}}function ee(r){const l=r.filter(Boolean).map((u,m)=>({id:Ue(`file-${m}`),kind:"file",file:u,name:it(u.name),size:At(u.size),sizeBytes:u.size,status:"待发送",tone:"muted",progress:0,pending:!0,transferId:""}));l.length&&p.value.push(...l)}async function J(r){const l=p.value.find(u=>u.id===r);if(!(!l||l.kind!=="file"||!l.pending)){if(!h.value.deviceId){window.alert("当前没有可用的接收端");return}l.pending=!1,l.status="创建传输中...",l.tone="primary";try{const u=await ge.create({kind:"file",name:l.name,size_bytes:l.sizeBytes,sender_device_id:A.value.id,receiver_device_id:h.value.deviceId});if(l.transferId=u.id,l.sizeBytes>rf){await me(l,u);return}ce("transfer.created",h.value.deviceId,{transfer_id:u.id,kind:"file",name:l.name,size_bytes:l.sizeBytes,sender_device_id:A.value.id,sender_name:A.value.name,sender_type:A.value.type,receiver_device_id:h.value.deviceId,final_status:"connecting",current_channel:"p2p",transport_options:as()});try{await ri(l,u)}catch(m){console.warn("realtime file send failed, fallback to relay",m),await oi(l,u)}}catch(u){l.pending=!0,l.status=`发送失败:${u.message}`,l.tone="danger"}}}async function me(r,l){r.progress=0,r.status="上传准备中...";try{if(!l.fallback_allowed)throw new Error("当前文件过大,且未启用 MinIO 回退");await ge.presignFallback(r.transferId),ce("transfer.updated",h.value.deviceId,{transfer_id:r.transferId,final_status:"fallback_uploading",current_channel:"minio"}),r.status="上传中...";const u=await ge.uploadFallback(r.transferId,r.file,m=>{r.progress=Math.max(1,Math.min(m,99))});await ge.updateStatus(r.transferId,{current_channel:"minio",final_status:"completed"}),ce("transfer.updated",h.value.deviceId,{transfer_id:r.transferId,final_status:"completed",current_channel:"minio"}),ce("transfer.file",h.value.deviceId,{transfer_id:r.transferId,name:r.name,download_url:u.download_path||u.download_url}),r.progress=100,r.status="上传完成",r.tone="success"}catch(u){r.pending=!0,r.status=`上传失败:${u.message}`,r.tone="danger"}}async function we(){for(const r of Sn.value)await J(r.id)}async function Ne(r){const l=p.value.find(u=>u.id===r);if(l&&Pe(l),p.value=p.value.filter(u=>u.id!==r),!(!(l!=null&&l.transferId)||l.tone==="success"))try{await ge.updateStatus(l.transferId,{final_status:"cancelled"}),ce("transfer.updated",h.value.deviceId,{transfer_id:l.transferId,final_status:"cancelled"})}catch(u){console.error(u)}}async function Fe(r){const l=p.value.find(u=>u.id===r);if(!(!l||l.kind!=="text"))try{await Mt(l.text),l.copied=!0,window.setTimeout(()=>{const u=p.value.find(m=>m.id===r);u&&u.kind==="text"&&(u.copied=!1)},2e3)}catch{window.alert("复制失败")}}async function Mt(r){var u;if((u=navigator.clipboard)!=null&&u.writeText&&window.isSecureContext){await navigator.clipboard.writeText(r);return}const l=document.createElement("textarea");l.value=r,l.setAttribute("readonly","readonly"),l.style.position="fixed",l.style.top="0",l.style.left="-9999px",l.style.opacity="0",document.body.appendChild(l),l.focus(),l.select(),l.setSelectionRange(0,l.value.length);try{if(!document.execCommand("copy"))throw new Error("copy command failed")}finally{document.body.removeChild(l)}}function $n(r){const l=le.get(r);l&&(window.clearInterval(l),le.delete(r))}function ke(r){return new Promise((l,u)=>{const m=new FileReader;m.onload=()=>l(String(m.result||"")),m.onerror=()=>u(new Error("Failed to read file")),m.readAsDataURL(r)})}function Pe(r){if($n(r.id),r.ownedDownloadUrl&&r.downloadUrl)try{URL.revokeObjectURL(r.downloadUrl)}catch(l){console.error(l)}r.transferId&&he.delete(r.transferId)}function Bt(r,l,u=!1){if(r.ownedDownloadUrl&&r.downloadUrl&&r.downloadUrl!==l)try{URL.revokeObjectURL(r.downloadUrl)}catch(m){console.error(m)}r.downloadUrl=l,r.ownedDownloadUrl=u}async function rn(r,l){const u=await di(h.value.deviceId);kn(u,{type:"text",transfer_id:r.id,text:l,sender_device_id:A.value.id,sender_name:A.value.name,sender_type:A.value.type});const m=gi();await ge.updateStatus(r.id,{current_channel:m,final_status:"completed"})}async function ii(r,l){ce("transfer.created",h.value.deviceId,{transfer_id:r.id,kind:"text",name:"text-message",content:l,sender_device_id:A.value.id,sender_name:A.value.name,sender_type:A.value.type,receiver_device_id:h.value.deviceId,final_status:"completed",current_channel:"p2p"}),await ge.updateStatus(r.id,{current_channel:"p2p",final_status:"completed"})}async function ri(r,l){var E;const u=await di(h.value.deviceId);r.status="正在通过 WebRTC 发送...",r.progress=1,kn(u,{type:"file-meta",transfer_id:l.id,name:r.name,mime_type:((E=r.file)==null?void 0:E.type)||"application/octet-stream",size_bytes:r.sizeBytes,sender_device_id:A.value.id,sender_name:A.value.name,sender_type:A.value.type});let m=0;for(;mur;)await To(20)}function To(r){return new Promise(l=>{window.setTimeout(l,r)})}function $o(r,l,u){return new Promise((m,T)=>{const E=window.setTimeout(()=>{T(new Error(u))},l);r.then(se=>{window.clearTimeout(E),m(se)}).catch(se=>{window.clearTimeout(E),T(se)})})}async function ko(){const r=window.prompt("管理员用户名","admin");if(r===null)return;const l=window.prompt("管理员密码");if(l!==null)try{const u=await Kt.login(r.trim()||"admin",l);z.value=u.token,localStorage.setItem(Ss,u.token),await ls(),n.value="admin"}catch(u){window.alert(`管理员登录失败:${u.message}`)}}function Mo(){n.value="main"}async function ls(){if(z.value)try{const[r,l,u]=await Promise.all([Kt.stats(z.value),Kt.config(z.value),Kt.recentTransfers(z.value)]);Tn(l),U.value=xi(r.stats||{},r.minio||{}),N.value=u.map(m=>Ii(m))}catch(r){throw(r==null?void 0:r.status)===401&&(localStorage.removeItem(Ss),localStorage.removeItem(Ln),z.value="",n.value="main"),r}}async function Ro(){if(!z.value||!Y.value){window.alert("当前没有可用的管理员会话");return}try{const r={...Y.value,max_minio_fallback_size_bytes:Math.max(0,$.value)*1024*1024,minio_capacity_bytes:Math.max(0,S.value)*1024*1024*1024},l=await Kt.updateConfig(z.value,r);Tn(l);{const u=await Kt.stats(z.value);U.value=xi(u.stats||{},u.minio||{})}window.alert("配置已保存")}catch(r){window.alert(`保存配置失败:${r.message}`)}}function Ao(){let r=localStorage.getItem(Bn);return r||(r=typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():`web-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,localStorage.setItem(Bn,r)),r}function Eo(r){let l=localStorage.getItem(cr);return(!l||Oo(l,r))&&(l=`${Po()} ${r.slice(0,4)}`,localStorage.setItem(cr,l)),l}function Oo(r,l){const u=String(r||"").trim(),m=l.slice(0,4);return!u||!m||!u.endsWith(` ${m}`)?!1:/^(android|iphone|ipad|linux|macintel|macos|windows|win32|web)\s/i.test(u)}function Po(){const r=`${navigator.userAgent} ${navigator.platform}`.toLowerCase();return r.includes("iphone")?"iPhone":r.includes("ipad")?"iPad":r.includes("android")?"Android":r.includes("windows")||r.includes("win32")?"Windows":r.includes("mac os")||r.includes("macintosh")||r.includes("macintel")?"macOS":r.includes("linux")?"Linux":"Web"}function Do(){const r=`${navigator.userAgent} ${navigator.platform}`.toLowerCase();return r.includes("iphone")||r.includes("android")||r.includes("mobile")?"phone":r.includes("ipad")||r.includes("tablet")?"tablet":"desktop"}function it(r,l="file"){const u=String(r||"").trim();if(!u)return l;if(!/%[0-9A-Fa-f]{2}/.test(u))return u;try{return decodeURIComponent(u)}catch{return u}}function No(r){return r==="phone"?"smartphone":r==="tablet"?"tablet_mac":"laptop_mac"}function jt(r){return r==="phone"?"手机":r==="tablet"?"平板":"桌面端"}function Fo(){return`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/ws`}function Uo(){var r;return Array.isArray((r=Y.value)==null?void 0:r.turn_urls)&&Y.value.turn_urls.some(l=>String(l||"").trim())}function li(){const r=String(window.location.hostname||"").trim().toLowerCase();return r?r==="localhost"||r==="127.0.0.1"||r==="::1"||r.endsWith(".local")||Lo(r)?r:"":"local"}function ai(r){const l=li();return!!l&&r===l}function Lo(r){const l=r.split(".");if(l.length!==4||l.some(T=>!/^\d+$/.test(T)))return!1;const[u,m]=l.map(T=>Number(T));return u===10||u===127||u===192&&m===168?!0:u===172&&m>=16&&m<=31}function ci(r){const l=Array.isArray(r==null?void 0:r.turn_urls)?r.turn_urls.map(u=>String(u||"").trim()).filter(Boolean):[];return l.length?[{urls:l,username:(r==null?void 0:r.turn_username)||"",credential:(r==null?void 0:r.turn_password)||""}]:[]}function as(){var r,l;return{ice_servers:ci(Y.value),p2p_connect_timeout_sec:((r=Y.value)==null?void 0:r.p2p_connect_timeout_sec)||15,turn_connect_timeout_sec:((l=Y.value)==null?void 0:l.turn_connect_timeout_sec)||20}}function ui(){return typeof RTCPeerConnection<"u"}function cs(){wt=null,Tt=null}function Bo(){return wt||(wt=new Promise(r=>{Tt=r})),wt}function fi(r){Tt&&Tt(r),wt=Promise.resolve(r),Tt=null}function We(r=""){if(!h.value.deviceId)return;const l=h.value.baseType||h.value.type||"点对点传输";h.value={...h.value,type:r?`${l} · ${r}`:l}}function jo(r){return!L||G!==r||L.signalingState==="closed"||["failed","disconnected","closed"].includes(L.connectionState)||["failed","disconnected","closed"].includes(L.iceConnectionState)?!0:!B||B.readyState==="closed"}async function rt(r,l={}){return!r||!ui()?null:(jo(r)&&(ot(),Ho(r)),l.initiate&&L.signalingState==="stable"&&await Ko(r),L)}function Ho(r){G=r,_e="p2p",ze=!1,be=!1,ue=!1,$e.delete(r),cs(),L=new RTCPeerConnection({iceServers:ci(Y.value)}),B=L.createDataChannel("filefast-control",{negotiated:!0,id:0,ordered:!0}),zo(B),L.onicecandidate=({candidate:l})=>{if(l)try{ce("webrtc.candidate",r,{candidate:l})}catch(u){console.error(u)}},L.onconnectionstatechange=()=>{if(L){if(us(),L.connectionState==="connected"){We(_e==="turn"?"TURN 中继已连接":"WebRTC 直连已连接");return}if(L.connectionState==="connecting"){We("实时通道连接中");return}if(L.connectionState==="failed"){We("实时通道连接失败");return}(L.connectionState==="disconnected"||L.connectionState==="closed")&&We("实时通道已断开")}},L.oniceconnectionstatechange=()=>{us()}}async function Ko(r){if(L)try{ze=!0,await L.setLocalDescription(),ce("webrtc.description",r,{description:L.localDescription})}finally{ze=!1}}function zo(r){B=r,r.bufferedAmountLowThreshold=ur/2,r.onopen=()=>{fi(r),We(_e==="turn"?"TURN 中继已连接":"WebRTC 直连已连接"),us()},r.onclose=()=>{B===r&&(B=null,cs(),We("实时通道已关闭"))},r.onerror=l=>{console.error(l)},r.onmessage=l=>{Wo(l.data)},r.readyState==="open"&&fi(r)}function ot(){he.clear(),G&&$e.delete(G),B&&(B.onopen=null,B.onclose=null,B.onerror=null,B.onmessage=null,B.close(),B=null),L&&(L.onicecandidate=null,L.onconnectionstatechange=null,L.oniceconnectionstatechange=null,L.close(),L=null),G="",_e="p2p",ze=!1,be=!1,ue=!1,cs()}async function di(r){if(!ui())throw new Error("当前浏览器不支持 WebRTC");if(await rt(r,{initiate:!0}),(B==null?void 0:B.readyState)==="open")return B;const l=await $o(Bo(),of,"WebRTC 连接超时");if(!l||l.readyState!=="open")throw new Error("实时通道未建立");return l}function kn(r,l){if(!r||r.readyState!=="open")throw new Error("实时通道未就绪");r.send(JSON.stringify(l))}function Wo(r){try{const l=JSON.parse(String(r||"{}"));if(l.type==="text"){Vo(l);return}if(l.type==="file-meta"){qo(l);return}if(l.type==="file-chunk"){Go(l);return}l.type==="file-complete"&&Jo(l)}catch(l){console.error(l)}}function Vo(r){var T;const l=r.sender_device_id||G,u={id:l,name:r.sender_name||((T=Rt(l))==null?void 0:T.name)||`设备 ${lt(l)}`,type:jt(r.sender_type||"desktop"),connectionType:_e==="turn"?"TURN 中继已连接":"WebRTC 直连已连接"};M(u,!0);const m=p.value.find(E=>E.transferId===r.transfer_id);if(m&&m.kind==="text"){m.text=r.text||"",m.status="已接收",m.tone="success";return}p.value.push({id:Ue("incoming-text"),transferId:r.transfer_id,kind:"text",text:r.text||"",status:"已接收",tone:"success",copied:!1})}function qo(r){var T;const l=r.sender_device_id||G,u={id:l,name:r.sender_name||((T=Rt(l))==null?void 0:T.name)||`设备 ${lt(l)}`,type:jt(r.sender_type||"desktop"),connectionType:_e==="turn"?"TURN 中继已连接":"WebRTC 直连已连接"};M(u,!0),he.set(r.transfer_id,{name:it(r.name,"file"),mimeType:r.mime_type||"application/octet-stream",sizeBytes:Number(r.size_bytes||0),receivedBytes:0,chunks:[]});let m=p.value.find(E=>E.transferId===r.transfer_id);m?(m.status="正在接收...",m.tone="primary",m.progress=0):(m={id:Ue("incoming-file"),transferId:r.transfer_id,kind:"file",name:it(r.name,"file"),size:At(Number(r.size_bytes||0)),sizeBytes:Number(r.size_bytes||0),status:"正在接收...",tone:"primary",progress:0,pending:!1,downloadUrl:"",ownedDownloadUrl:!1},p.value.push(m))}function Go(r){const l=he.get(r.transfer_id);if(!l)return;const u=So(String(r.chunk_base64||""));l.receivedBytes+=Number(r.chunk_size||u.byteLength||0),l.chunks.push(u);const m=p.value.find(T=>T.transferId===r.transfer_id);if(m){const T=l.sizeBytes>0?l.receivedBytes/l.sizeBytes*100:0;m.progress=Math.max(1,Math.min(99,Math.round(T))),m.status="正在接收...",m.tone="primary"}}function Jo(r){const l=he.get(r.transfer_id);if(!l)return;const u=p.value.find(E=>E.transferId===r.transfer_id);if(!u){he.delete(r.transfer_id);return}const m=new Blob(l.chunks,{type:l.mimeType||"application/octet-stream"}),T=URL.createObjectURL(m);Bt(u,T,!0),u.progress=100,u.status="可保存",u.tone="success",he.delete(r.transfer_id)}function pi(r){return A.value.id.localeCompare(r)>0}function Mn(r,l="等待实时数据"){const u=Rt(r);return{id:r,deviceId:r,name:(u==null?void 0:u.name)||`设备 ${lt(r)}`,type:jt((u==null?void 0:u.type)||"desktop"),connectionType:l,network_group_key:(u==null?void 0:u.network_group_key)||""}}async function hi(r){const u=(r.payload||{}).description,m=r.device_id||"";if(!u||!m)return;M(Mn(m),!0);const T=await rt(m);if(!T)return;const E=pi(m),se=!ze&&(T.signalingState==="stable"||ue),Et=u.type==="offer"&&!se;be=!E&&Et,!be&&(ue=u.type==="answer",await T.setRemoteDescription(u),ue=!1,u.type==="offer"&&(await T.setLocalDescription(),ce("webrtc.description",m,{description:T.localDescription})))}async function mi(r){const l=r.payload||{},u=r.device_id||"";if(!l.candidate||!u)return;(n.value!=="transfer"||h.value.deviceId!==u)&&M(Mn(u),!0);const m=await rt(u);if(m)try{await m.addIceCandidate(l.candidate)}catch(T){be||console.error(T)}}async function us(){if(!(!L||L.connectionState!=="connected"))try{const r=await L.getStats();let l=null;if(r.forEach(E=>{E.type==="transport"&&E.selectedCandidatePairId&&(l=r.get(E.selectedCandidatePairId)||l)}),l||r.forEach(E=>{E.type==="candidate-pair"&&E.state==="succeeded"&&(E.nominated||E.selected)&&(l=E)}),!l)return;const u=r.get(l.localCandidateId),m=r.get(l.remoteCandidateId),T=(u==null?void 0:u.candidateType)==="relay"||(m==null?void 0:m.candidateType)==="relay";_e=T?"turn":"p2p",(B==null?void 0:B.readyState)==="open"&&We(T?"TURN 中继已连接":"WebRTC 直连已连接")}catch(r){console.error(r)}}function gi(){return _e==="turn"?"turn":"p2p"}function vi(){if(!A.value.id)return;const r=localStorage.getItem(Cs)||"";r&&(yi(),de=new WebSocket(`${_.value}?deviceId=${encodeURIComponent(A.value.id)}&deviceToken=${encodeURIComponent(r)}`),de.addEventListener("message",l=>{Xo(l.data)}),de.addEventListener("close",()=>{de=null,Yo()}),de.addEventListener("error",()=>{de==null||de.close()}))}function yi(){if(st&&(window.clearTimeout(st),st=null),!de)return;const r=de;de=null,r.onclose=null,r.close()}function Yo(){st||!A.value.id||(st=window.setTimeout(()=>{st=null,vi()},sf))}function ce(r,l,u){!de||de.readyState!==WebSocket.OPEN||!l||de.send(JSON.stringify({type:r,target_device_id:l,payload:u}))}function Xo(r){try{const l=JSON.parse(r);if(l.type==="presence.update"){v();return}if(l.type==="webrtc.description"){hi(l);return}if(l.type==="webrtc.candidate"){mi(l);return}if(l.type==="transfer.created"){_i(l);return}if(l.type==="transfer.updated"){bi(l);return}if(l.type==="transfer.file"){wi(l);return}l.type==="peer.session.closed"&&Zo(l)}catch(l){console.error(l)}}function Zo(r){const l=r.device_id||"";!l||h.value.deviceId!==l||(ot(),D(),h.value={name:"--",type:"绛夊緟杩炴帴",baseType:"绛夊緟杩炴帴",deviceId:"",networkGroupKey:""},n.value="main")}function _i(r){var E;const l=r.payload||{},u=r.device_id||l.sender_device_id||"",m={id:u,name:l.sender_name||((E=Rt(u))==null?void 0:E.name)||`Device ${lt(u)}`,type:jt(l.sender_type||"desktop")};if(m.connectionType="等待实时数据",M(m,!0),!p.value.find(se=>se.transferId===l.transfer_id)){if(l.kind==="text"){p.value.push({id:Ue("incoming-text"),transferId:l.transfer_id,kind:"text",text:l.content||"",status:"已接收",tone:"success",copied:!1});return}p.value.push({id:Ue("incoming-file"),transferId:l.transfer_id,kind:"file",name:it(l.name,"file"),size:At(Number(l.size_bytes||0)),sizeBytes:Number(l.size_bytes||0),status:"接收中...",tone:"primary",progress:35,pending:!1,downloadUrl:"",ownedDownloadUrl:!1})}}function bi(r){const l=r.payload||{},u=p.value.find(m=>m.transferId===l.transfer_id);if(u&&u.kind==="file"){if(l.final_status==="completed"){u.progress=100,u.status="已接收",u.tone="success",u.downloadUrl&&(u.status="可保存");return}l.final_status==="cancelled"&&(u.status="已取消",u.tone="danger")}}function wi(r){const l=r.payload||{};let u=p.value.find(m=>m.transferId===l.transfer_id);!u&&l.transfer_id&&(u={id:Ue("incoming-file"),transferId:l.transfer_id,kind:"file",name:it(l.name,"file"),size:"",sizeBytes:0,status:"可保存",tone:"success",progress:100,pending:!1,downloadUrl:"",ownedDownloadUrl:!1},p.value.push(u)),!(!u||u.kind!=="file")&&(Bt(u,l.download_url||l.data_url||"",!1),u.status="可保存",u.progress=100,u.tone="success")}function Rt(r){return i.value.find(l=>l.id===r)}function xi(r,l={}){return[{label:"在线设备",value:`${r.devices_online||0}`,tone:"blue"},{label:"待加入房间",value:`${r.rooms_waiting||0}`,tone:"cyan"},{label:"有效传输",value:`${r.transfers_total||0}`,tone:"default"},{label:"累计传输",value:`${r.transfers_cumulative||0}`,tone:"default"},{kind:"minio",label:"MinIO 剩余容量",value:fs(l.remaining_bytes||0),tone:Number(l.usage_percent||0)>=85?"danger":Number(l.usage_percent||0)>=60?"cyan":"blue",percent:Math.max(0,100-Number(l.usage_percent||0)),detail:`已用 ${fs(l.used_bytes||0)} / 总计 ${fs(l.capacity_bytes||0)}`,kicker:`存档 ${l.object_count||0} 份`}]}function fs(r){const l=Number(r||0);if(!l||l<=0)return"0 GB";const u=["B","KB","MB","GB","TB"],m=Math.min(Math.floor(Math.log(l)/Math.log(1024)),u.length-1),T=l/1024**m,E=m>=3?2:T>=10?1:2;return`${T.toFixed(E)} ${u[m]}`}function Ii(r){const l=r.final_status==="completed",u=r.final_status==="failed"||r.final_status==="cancelled";return{time:Rn(r.created_at),peer:`${lt(r.sender_device_id)} -> ${lt(r.receiver_device_id)}`,type:r.kind==="text"?"文本消息":`文件 ${r.name}`,size:At(Number(r.size_bytes||0)),status:l?`已完成 (${r.current_channel||"p2p"})`:u?`已结束 (${r.final_status})`:`进行中 (${r.final_status||"pending"})`,tone:l?"success":u?"danger":"primary"}}function lt(r){return r?r.slice(0,8):"--"}function Rn(r){if(!r)return"刚刚";const l=new Date(r),u=Date.now()-l.getTime();if(!Number.isFinite(u))return"刚刚";const m=Math.max(0,Math.floor(u/1e3));if(m<60)return`${m} 秒前`;const T=Math.floor(m/60);if(T<60)return`${T} 分钟前`;const E=Math.floor(T/60);return E<24?`${E} 小时前`:`${Math.floor(E/24)} 天前`}function Ue(r){return`${r}-${Date.now()}-${Math.random().toString(36).slice(2,8)}`}function At(r){if(!r||r<=0)return"0 B";const l=["B","KB","MB","GB","TB"],u=Math.min(Math.floor(Math.log(r)/Math.log(1024)),l.length-1),m=r/1024**u,T=m>=10||u===0?0:1;return`${m.toFixed(T)} ${l[u]}`}v=async function(){return A.value.id?Fn.listCandidates(A.value.id).then(l=>{i.value=l.map(u=>({...u,description:`${jt(u.type)} · 最近活跃 ${Rn(u.last_seen_at)}`,icon:No(u.type),connectionType:ai(u.network_group_key)?"局域网直连优先":"跨网络实时传输"})),s.value=i.value.length===0}).catch(l=>{s.value=!1,console.error(l)}):Promise.resolve()},P=function(l){const u=l.deviceId||l.id||"",m=l.connectionType||l.type||"点对点传输";b(),h.value.deviceId!==u&&(ot(),D()),h.value={name:l.name,type:m,baseType:m,deviceId:u,networkGroupKey:l.network_group_key||""},a.value=!1,c.value="----",n.value="transfer",We("正在建立实时通道"),rt(u,{initiate:!0})},M=function(l,u=!1){const m=l.deviceId||l.id||"",T=l.connectionType||l.type||"点对点传输";h.value.deviceId===m&&n.value==="transfer"||(ot(),u||D()),h.value={name:l.name,type:T,baseType:T,deviceId:m,networkGroupKey:l.network_group_key||""},a.value=!1,c.value="----",n.value="transfer",m&&(We("正在建立实时通道"),rt(m))},O=function(){h.value.deviceId&&ce("peer.session.closed",h.value.deviceId,{}),ot(),D(),h.value={name:"--",type:"等待连接",baseType:"等待连接",deviceId:"",networkGroupKey:""},n.value="main"},W=async function(l){const u=l.trim();if(u){if(!h.value.deviceId){window.alert("当前没有可用的接收端");return}try{const m=await ge.create({kind:"text",name:"text-message",content:u,sender_device_id:A.value.id,receiver_device_id:h.value.deviceId});try{await rn(m,u)}catch(T){console.warn("realtime text send failed, fallback to relay",T),await ii(m,u)}p.value.push({id:Ue("text"),transferId:m.id,kind:"text",text:u,status:"已发送",tone:"success",copied:!1})}catch(m){window.alert(`发送文本失败:${m.message}`)}}};function Si(r,l){ce("transfer.file",h.value.deviceId,{transfer_id:r.transferId,name:r.name,download_url:l.download_path||l.download_url})}function Ci(r,l,{onProgress:u}={}){if(!(r!=null&&r.file))return Promise.reject(new Error("未找到待上传文件"));if(!(l!=null&&l.fallback_allowed))return Promise.reject(new Error("MinIO 存档未启用"));const m=l.id;if(Te.has(m))return Te.get(m);const T=(async()=>(await ge.presignFallback(m),ge.uploadFallback(m,r.file,E=>{typeof u=="function"&&u(E)})))().finally(()=>{Te.delete(m)});return Te.set(m,T),T}async function Ti(r,l,u){await ge.updateStatus(l.id,{current_channel:"minio",final_status:"completed"}),ce("transfer.updated",h.value.deviceId,{transfer_id:l.id,final_status:"completed",current_channel:"minio"}),Si(r,u),r.progress=100,r.status="已上传到 MinIO,对方可直接领取",r.tone="success"}async function Qo(r,l){const u=$e.get(r);if(!(!(u!=null&&u.length)||!(l!=null&&l.remoteDescription))){$e.delete(r);for(const m of u)try{await l.addIceCandidate(m)}catch(T){console.error(T)}}}return me=async function(l,u){l.progress=Math.max(5,l.progress||0),l.status="正在切换到 MinIO...",l.tone="primary";try{ce("transfer.updated",h.value.deviceId,{transfer_id:l.transferId,final_status:"fallback_uploading",current_channel:"minio"});const m=await Ci(l,u,{onProgress:T=>{l.progress=Math.max(5,Math.min(T,99))}});await Ti(l,u,m)}catch(m){l.pending=!0,l.status=`上传失败:${m.message}`,l.tone="danger"}},hi=async function(l){const m=(l.payload||{}).description,T=l.device_id||"";if(!m||!T)return;M(Mn(T),!0);const E=await rt(T);if(!E)return;const se=pi(T),Et=!ze&&(E.signalingState==="stable"||ue),el=m.type==="offer"&&!Et;if(be=!se&&el,!be&&!(m.type==="answer"&&(E.signalingState!=="have-local-offer"||!E.localDescription))){try{ue=m.type==="answer",await E.setRemoteDescription(m),await Qo(T,E)}catch(ds){console.error(ds)}finally{ue=!1}if(m.type==="offer")try{await E.setLocalDescription(),ce("webrtc.description",T,{description:E.localDescription})}catch(ds){console.error(ds)}}},mi=async function(l){const u=l.payload||{},m=l.device_id||"",T=u.candidate;if(!T||!m)return;(n.value!=="transfer"||h.value.deviceId!==m)&&M(Mn(m),!0);const E=await rt(m);if(E){if(!E.remoteDescription){const se=$e.get(m)||[];se.push(T),$e.set(m,se);return}try{await E.addIceCandidate(T)}catch(se){be||console.error(se)}}},J=async function(l){const u=p.value.find(m=>m.id===l);if(!(!u||u.kind!=="file"||!u.pending)){if(!h.value.deviceId){window.alert("当前没有可用的接收端");return}u.pending=!1,u.status="创建传输中...",u.tone="primary";try{const m=await ge.create({kind:"file",name:u.name,size_bytes:u.sizeBytes,sender_device_id:A.value.id,receiver_device_id:h.value.deviceId});u.transferId=m.id;const T=m.fallback_allowed?Ci(u,m).catch(E=>{throw console.warn("minio backup sync failed",E),E}):Promise.resolve(null);ce("transfer.created",h.value.deviceId,{transfer_id:m.id,kind:"file",name:u.name,size_bytes:u.sizeBytes,sender_device_id:A.value.id,sender_name:A.value.name,sender_type:A.value.type,receiver_device_id:h.value.deviceId,final_status:"connecting",current_channel:"p2p",transport_options:as()});try{if(await ri(u,m),m.fallback_allowed){u.status="实时传输完成,正在同步云端备份...",u.tone="primary";try{const E=await T;E&&(Si(u,E),u.status="已发送,2 小时内可离线领取")}catch(E){u.status=`实时传输成功,但 MinIO 备份失败:${E.message}`,u.tone="danger";return}u.tone="success"}}catch(E){console.warn("realtime file send failed, fallback to minio",E);try{const se=await T;if(se){await Ti(u,m,se);return}}catch(se){console.warn("minio backup sync failed after realtime failure",se)}await oi(u,m)}}catch(m){u.pending=!0,u.status=`发送失败:${m.message}`,u.tone="danger"}}},_i=function(l){var se;const u=l.payload||{},m=l.device_id||u.sender_device_id||"",T={id:m,name:u.sender_name||((se=Rt(m))==null?void 0:se.name)||`设备 ${lt(m)}`,type:jt(u.sender_type||"desktop"),connectionType:"等待实时数据"};if(M(T,!0),!p.value.find(Et=>Et.transferId===u.transfer_id)){if(u.kind==="text"){u.content&&p.value.push({id:Ue("incoming-text"),transferId:u.transfer_id,kind:"text",text:u.content||"",status:"已接收",tone:"success",copied:!1});return}p.value.push({id:Ue("incoming-file"),transferId:u.transfer_id,kind:"file",name:it(u.name,"file"),size:At(Number(u.size_bytes||0)),sizeBytes:Number(u.size_bytes||0),status:"等待接收...",tone:"primary",progress:5,pending:!1,downloadUrl:"",ownedDownloadUrl:!1})}},bi=function(l){const u=l.payload||{},m=p.value.find(T=>T.transferId===u.transfer_id);if(m&&m.kind==="file"){if(u.final_status==="completed"){m.progress=100,m.status=m.downloadUrl?"可保存":"传输完成",m.tone="success";return}if(u.final_status==="cancelled"){m.status="已取消",m.tone="danger";return}u.final_status==="fallback_uploading"&&(m.status="发送端正在上传回退文件...",m.tone="primary")}},wi=function(l){const u=l.payload||{};let m=p.value.find(T=>T.transferId===u.transfer_id);!m&&u.transfer_id&&(m={id:Ue("incoming-file"),transferId:u.transfer_id,kind:"file",name:it(u.name,"file"),size:"",sizeBytes:0,status:"可保存",tone:"success",progress:100,pending:!1,downloadUrl:"",ownedDownloadUrl:!1},p.value.push(m)),!(!m||m.kind!=="file")&&(Bt(m,u.download_url||u.data_url||"",!1),m.status="可保存",m.progress=100,m.tone="success")},Ii=function(l){const u=l.final_status==="completed",m=l.final_status==="failed"||l.final_status==="cancelled",T=it(l.name,"file");return{time:Rn(l.created_at),peer:`${lt(l.sender_device_id)} -> ${lt(l.receiver_device_id)}`,type:l.kind==="text"?"文本消息":`文件 ${T}`,size:At(Number(l.size_bytes||0)),status:u?`已完成(${l.current_channel||"p2p"})`:m?`已结束(${l.final_status})`:`进行中(${l.final_status||"pending"})`,tone:u?"success":m?"danger":"primary"}},(r,l)=>(j(),V("div",null,[y("div",Zu,[q(Oc,{theme:t.value,onToggleTheme:Cn},null,8,["theme"]),n.value==="main"?(j(),V("div",Qu,[q(gu,{devices:i.value,"is-scanning":s.value,"local-device-name":A.value.name,onSelectDevice:P},null,8,["devices","is-scanning","local-device-name"]),q(ku,{"generated-code":c.value,"is-waiting":a.value,"pending-downloads":f.value,"room-code-input":o.value,onCancelRoom:R,onCreateRoom:x,onJoinRoom:k,onUpdateRoomCode:d},null,8,["generated-code","is-waiting","pending-downloads","room-code-input"])])):Be("",!0),n.value==="transfer"?(j(),vt(ju,{key:1,"connection-type":h.value.type,"has-pending-items":$t.value,items:p.value,"network-hint":Lt.value,"peer-name":h.value.name,onClose:O,onCopyItem:Fe,onFilesSelected:ee,onRemoveItem:Ne,onSendAllPending:we,onSendText:W,onStartUpload:J},null,8,["connection-type","has-pending-items","items","network-hint","peer-name"])):Be("",!0),n.value==="admin"?(j(),vt(ou,{key:2,"file-limit":$.value,"minio-capacity":S.value,records:N.value,stats:U.value,onExit:Mo,onSaveConfig:Ro,"onUpdate:fileLimit":l[0]||(l[0]=u=>$.value=u),"onUpdate:minioCapacity":l[1]||(l[1]=u=>S.value=u)},null,8,["file-limit","minio-capacity","records","stats"])):Be("",!0)]),q(Rc,{onRequestAdmin:ko})]))}};Tc(af).mount("#app"); diff --git a/frontend/dist/index.html b/frontend/dist/index.html index 047300a..8316d94 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -4,7 +4,7 @@ AirShare Pro - + diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 2a3c76a..1b8d4be 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -2871,13 +2871,13 @@ mapTransferRecord = function mapTransferRecordOverride(record) { return { time: formatRelativeTime(record.created_at), peer: `${shortId(record.sender_device_id)} -> ${shortId(record.receiver_device_id)}`, - type: record.kind === 'text' ? '鏂囨湰娑堟伅' : `鏂囦欢 ${displayName}`, + type: record.kind === 'text' ? '文本消息' : `文件 ${displayName}`, size: formatFileSize(Number(record.size_bytes || 0)), status: isCompleted - ? `宸插畬鎴?(${record.current_channel || 'p2p'})` + ? `已完成(${record.current_channel || 'p2p'})` : isFailed - ? `宸茬粨鏉?(${record.final_status})` - : `杩涜涓?(${record.final_status || 'pending'})`, + ? `已结束(${record.final_status})` + : `进行中(${record.final_status || 'pending'})`, tone: isCompleted ? 'success' : isFailed ? 'danger' : 'primary', } }