var _a,_b,_c,_d,_e,_f,_g,_h,_i,_j,_k,_l,_m,_n,_o,_p,_q,_r,_s,_t,_u,_v,_w,_x,_y,_z,_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q,_R,_S,_T,_U,_V,_W,_X,_Y,_Z,__,_$,_aa,_ba,_ca,_da,_ea,_fa,_ga,_ha,_ia,_ja,_ka,_la,_ma,_na,_oa,_pa,_qa,_ra,_sa,_ta,_ua,_va,_wa,_xa,_ya,_za,_Aa,_Ba,_Ca,_Da,_Ea,_Fa,_Ga,_Ha,_Ia,_Ja,_Ka,_La,_Ma,_Na,_Oa,_Pa,_Qa,_Ra,_Sa,_Ta,_Ua,_Va,_Wa,_Xa,_Ya,_Za,__a,_$a,_ab,_bb,_cb,_db,_eb,_fb,_gb,_hb,_ib,_jb,_kb,_lb,_mb,_nb,_ob,_pb,_qb,_rb,_sb,_tb,_ub,_vb,_wb,_xb,_yb,_zb,_Ab,_Bb,_Cb,_Db,_Eb,_Fb,_Gb,_Hb,_Ib,_Jb,_Kb,_Lb,_Mb,_Nb,_Ob,_Pb,_Qb,_Rb,_Sb,_Tb,_Ub,_Vb,_Wb,_Xb,_Yb,_Zb,__b,_$b,_ac,_bc,_cc,_dc,_ec,_fc,_gc,_hc,_ic,_jc,_kc,_lc,_mc,_nc,_oc;(function(){"use strict";const _pickers={};function _voiceData(id){return(window._voices||[]).find(v=>v.id===id)||null}function _voiceLang(v,id){const raw=String((v==null?void 0:v.lang)||(v==null?void 0:v.language)||"").trim(),fromMeta=raw&&raw.length<=5?raw:"",fromId=String(id||"").split("_")[0]||"";return(fromMeta||fromId).toUpperCase()}function _voiceGender(v,id){const raw=String((v==null?void 0:v.gender)||(v==null?void 0:v.sex)||"").trim(),first=raw?raw.charAt(0).toUpperCase():"";if(["F","M","N"].includes(first))return first;const fromId=(String(id||"").split("_")[1]||"").charAt(0).toUpperCase();return["F","M","N"].includes(fromId)?fromId:""}function _voiceMetaLabel(id){const v=_voiceData(id);return[_voiceGender(v,id),_voiceLang(v,id)].filter(Boolean).join(" ")}function _voiceOptionLabel(id){const meta=_voiceMetaLabel(id);return meta?`${id} ${meta}`:id}const VOICE_AVATAR_ICONS={male:"mdi-face-man",female:"mdi-face-woman",neutral:"mdi-account",robot:"mdi-robot-outline",animal:"mdi-paw"},VOICE_AVATAR_COLORS={male:"#3b82f6",female:"#ec4899",neutral:"#6b7280",robot:"#0ea5e9",animal:"#f59e0b"};window.VOICE_AVATAR_ICONS=VOICE_AVATAR_ICONS,window.voiceAvatarIcon=function(avatarKey,size){const icon=VOICE_AVATAR_ICONS[avatarKey];if(!icon)return null;const s=size+"px",r=Math.round(size/2)+"px",bg=VOICE_AVATAR_COLORS[avatarKey]||"#6b7280";return``};function _avatarHtml(id,size){const v=_voiceData(id),s=size+"px",r=Math.round(size/2)+"px";if(v!=null&&v.has_picture)return``;const icon=window.voiceAvatarIcon?window.voiceAvatarIcon(v==null?void 0:v.avatar,size):null;if(icon)return icon;const lang=(v==null?void 0:v.lang)||"",color=_langColor(lang,id),init=(id||"?")[0].toUpperCase();return`${init}`}function _langColor(lang,id){const str=(lang||id||"").toLowerCase();if(str.startsWith("de"))return"#3b82f6";if(str.startsWith("en"))return"#10b981";if(str.startsWith("fr"))return"#8b5cf6";if(str.startsWith("es"))return"#f59e0b";if(str.startsWith("it"))return"#ef4444";if(str.startsWith("zh"))return"#ec4899";if(str.startsWith("ja"))return"#f97316";const palette=["#3b82f6","#10b981","#8b5cf6","#f59e0b","#ef4444","#ec4899","#06b6d4","#84cc16"];let h=0;for(let i=0;i>>0;return palette[h%palette.length]}function _flagSpan(v){return v&&v.flag?`${v.flag}`:""}function _buildItem(id,label){const v=_voiceData(id),meta=_voiceMetaLabel(id),name=id||label||"";return`
${_avatarHtml(id,26)} ${_esc(name)} ${meta?`${_esc(meta)}`:_flagSpan(v)}
`}function _esc(s){return String(s).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}function upgrade(selectId){const sel=document.getElementById(selectId);if(!sel||sel.dataset.vpUpgraded)return;sel.dataset.vpUpgraded="1",sel.style.display="none";const root=document.createElement("div");root.className="vp-root",root.dataset.vpFor=selectId,sel.parentNode.insertBefore(root,sel);const trigger=document.createElement("div");trigger.className="vp-trigger",trigger.tabIndex=0,trigger.innerHTML='\u2014 select after fetch \u2014',root.appendChild(trigger);const drop=document.createElement("div");drop.className="vp-drop",drop.hidden=!0,drop.innerHTML='
',root.appendChild(drop);const searchInp=drop.querySelector(".vp-search"),list=drop.querySelector(".vp-list");function _renderList(filter){const opts=Array.from(sel.options).filter(o=>o.value),q=(filter||"").toLowerCase(),filtered=q?opts.filter(o=>o.value.toLowerCase().includes(q)||o.textContent.toLowerCase().includes(q)):opts;if(!filtered.length){list.innerHTML='
No voices found
';return}list.innerHTML=filtered.map(o=>_buildItem(o.value,o.textContent)).join(""),list.querySelectorAll(".vp-item").forEach(el=>{el.classList.toggle("selected",el.dataset.value===sel.value)})}function _open(){drop.hidden=!1,drop.style.display="",trigger.classList.add("open"),searchInp.value="",_renderList(""),searchInp.focus(),requestAnimationFrame(()=>{const sel2=list.querySelector(".vp-item.selected");sel2&&sel2.scrollIntoView({block:"nearest"})})}function _close(){drop.hidden=!0,drop.style.display="none",trigger.classList.remove("open"),searchInp.blur()}function _select(value){sel.value=value,sel.dispatchEvent(new Event("change",{bubbles:!0})),_syncTrigger(),_close(),trigger.focus()}function _syncTrigger(){const v=sel.value,content=trigger.querySelector(".vp-trigger-content");if(!v)content.innerHTML='\u2014 select after fetch \u2014';else{const label=v,meta=_voiceMetaLabel(v);content.innerHTML=`${_avatarHtml(v,24)}${_esc(label)}${meta?`${_esc(meta)}`:""}`}}trigger.addEventListener("click",()=>drop.hidden?_open():_close()),trigger.addEventListener("keydown",e=>{(e.key==="Enter"||e.key===" ")&&(e.preventDefault(),_open())}),searchInp.addEventListener("input",()=>_renderList(searchInp.value)),searchInp.addEventListener("keydown",e=>{var _a2;e.key==="Escape"&&(e.stopPropagation(),_close(),trigger.focus()),e.key==="ArrowDown"&&(e.preventDefault(),(_a2=list.querySelector(".vp-item"))==null||_a2.focus())}),list.addEventListener("pointerdown",e=>{const item=e.target.closest(".vp-item");item&&(e.preventDefault(),_select(item.dataset.value))}),list.addEventListener("click",e=>{const item=e.target.closest(".vp-item");item&&_select(item.dataset.value)}),list.addEventListener("keydown",e=>{var _a2;const item=e.target.closest(".vp-item");item&&((e.key==="Enter"||e.key===" ")&&(e.preventDefault(),_select(item.dataset.value)),e.key==="ArrowDown"&&(e.preventDefault(),(item.nextElementSibling||item).focus()),e.key==="ArrowUp"&&(e.preventDefault(),(_a2=item.previousElementSibling||item)!=null&&_a2.focus()||searchInp.focus()),e.key==="Escape"&&(_close(),trigger.focus()))}),document.addEventListener("click",e=>{root.contains(e.target)||_close()},!0),sel.addEventListener("change",()=>{_syncTrigger(),_close()}),new MutationObserver(()=>{_syncTrigger()}).observe(sel,{childList:!0,attributes:!0,subtree:!0}),_pickers[selectId]={root,trigger,drop,searchInp,list,sel,renderList:_renderList,syncTrigger:_syncTrigger}}function populate(selectId,voices){const p=_pickers[selectId];if(!p)return;const{sel,renderList,syncTrigger}=p,prev=sel.value;sel.innerHTML='',voices.forEach(id=>{const opt=document.createElement("option");opt.value=id,opt.textContent=_voiceOptionLabel(id),sel.appendChild(opt)}),prev&&voices.includes(prev)&&(sel.value=prev),renderList(""),syncTrigger()}function getValue(selectId){var _a2;return((_a2=document.getElementById(selectId))==null?void 0:_a2.value)||""}function setValue(selectId,value){var _a2;const sel=document.getElementById(selectId);sel&&(sel.value=value,(_a2=_pickers[selectId])==null||_a2.syncTrigger())}function attachTextPicker(input){if(!input||input.dataset.vpTextAttached)return;input.dataset.vpTextAttached="1";const btn=document.createElement("button");btn.type="button",btn.className="vp-text-browse",btn.title="Browse voices",btn.innerHTML='',input.insertAdjacentElement("afterend",btn);const drop=document.createElement("div");drop.className="vp-drop vp-drop-floating",drop.hidden=!0,drop.innerHTML='
',document.body.appendChild(drop);const searchInp=drop.querySelector(".vp-search"),list=drop.querySelector(".vp-list");function _voices2(){return(window._voices||[]).filter(v=>v.enabled!==!1)}function _renderList(filter){const q=(filter||"").toLowerCase(),filtered=q?_voices2().filter(v=>v.id.toLowerCase().includes(q)):_voices2();if(!filtered.length){list.innerHTML='
No voices found
';return}list.innerHTML=filtered.map(v=>_buildItem(v.id,v.id)).join(""),list.querySelectorAll(".vp-item").forEach(el=>el.classList.toggle("selected",el.dataset.value===input.value))}function _position(){const r=input.getBoundingClientRect();drop.style.left=r.left+"px",drop.style.top=r.bottom+4+"px",drop.style.width=Math.max(240,r.width+32)+"px"}function _open(){_position(),drop.hidden=!1,searchInp.value="",_renderList(""),searchInp.focus()}function _close(){drop.hidden=!0}function _select(value){input.value=value,input.dispatchEvent(new Event("input",{bubbles:!0})),input.dispatchEvent(new Event("change",{bubbles:!0})),_close()}btn.addEventListener("click",e=>{e.stopPropagation(),drop.hidden?_open():_close()}),searchInp.addEventListener("input",()=>_renderList(searchInp.value)),searchInp.addEventListener("keydown",e=>{e.key==="Escape"&&(e.stopPropagation(),_close())}),list.addEventListener("click",e=>{const item=e.target.closest(".vp-item");item&&_select(item.dataset.value)}),document.addEventListener("click",e=>{!drop.contains(e.target)&&e.target!==btn&&_close()},!0),window.addEventListener("resize",()=>{drop.hidden||_position()})}window.VoicePicker={upgrade,populate,getValue,setValue,attachTextPicker}})(),function(){"use strict";const pickers={};function esc(value){return String(value==null?"":value).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}function norm(value){if(typeof value=="string")return{id:value,label:value,meta:null,raw:value};const id=(value==null?void 0:value.id)||(value==null?void 0:value.voice)||(value==null?void 0:value.name)||(value==null?void 0:value.voice_id)||"",hasExplicitMeta=Object.prototype.hasOwnProperty.call(value||{},"meta"),looksLikeVoice=!!(value!=null&&value.display_name||value!=null&&value.duration||value!=null&&value.benchmark||value!=null&&value.has_picture||value!=null&&value.transcript);return{id,label:(value==null?void 0:value.label)||(value==null?void 0:value.display_name)||(value==null?void 0:value.name)||(value==null?void 0:value.voice)||id,meta:hasExplicitMeta?value.meta:looksLikeVoice?value:null,raw:value}}function libraryVoice(id){return(window._voices||[]).find(v=>v&&v.id===id)||null}function metaFor(item){return(item==null?void 0:item.meta)||libraryVoice(item==null?void 0:item.id)||(item==null?void 0:item.raw)||null}function langColor(lang,id){const str=String(lang||id||"").toLowerCase();if(str.startsWith("de"))return"#3b82f6";if(str.startsWith("en"))return"#10b981";if(str.startsWith("fr"))return"#8b5cf6";if(str.startsWith("es"))return"#f59e0b";if(str.startsWith("it"))return"#ef4444";if(str.startsWith("zh"))return"#ec4899";if(str.startsWith("ja"))return"#f97316";const palette=["#3b82f6","#10b981","#8b5cf6","#f59e0b","#ef4444","#ec4899","#06b6d4","#84cc16"];let h=0;for(let i=0;i>>0;return palette[h%palette.length]}function avatarHtml(item,size){var _a2;const id=(item==null?void 0:item.id)||"",meta=metaFor(item),s=`${size}px`,r=`${Math.round(size/2)}px`;if(meta!=null&&meta.has_picture)return``;const icon=window.voiceAvatarIcon?window.voiceAvatarIcon(meta==null?void 0:meta.avatar,size):null;if(icon)return icon.replace(/vp-avatar/g,"bvp-avatar");const color=langColor((meta==null?void 0:meta.lang)||(meta==null?void 0:meta.language),id),init=((_a2=((item==null?void 0:item.label)||id||"?").trim()[0])==null?void 0:_a2.toUpperCase())||"?";return`${esc(init)}`}function numeric(value){const n=Number(value);return Number.isFinite(n)?n:null}function statsFor(item){var _a2,_b2,_c2,_d2,_e2,_f2;const meta=metaFor(item)||{},b=meta.benchmark||(item==null?void 0:item.benchmark)||{},parts=[],duration=numeric((_a2=meta.duration)!=null?_a2:item==null?void 0:item.duration),speed=numeric((_c2=(_b2=b.speed)!=null?_b2:b.avg_speed)!=null?_c2:meta.speed),rtf=numeric((_e2=(_d2=b.rtf)!=null?_d2:b.avg_rtf)!=null?_e2:meta.rtf),elapsed=numeric((_f2=b.elapsed_sec)!=null?_f2:b.avg_elapsed_sec);return duration!=null&&duration>0&&parts.push(`${duration.toFixed(1)}s`),speed!=null&&speed>0?parts.push(`${speed.toFixed(2)}x`):rtf!=null&&rtf>0&&parts.push(`RTF ${rtf.toFixed(2)}`),!parts.length&&elapsed!=null&&elapsed>0&&parts.push(`${elapsed.toFixed(1)}s TTS`),!parts.length&&b.ok===!1&&parts.push("benchmark failed"),parts.join(" \xB7 ")}function searchText(item){const meta=metaFor(item)||{};return[item==null?void 0:item.id,item==null?void 0:item.label,meta.display_name,meta.name,meta.lang,meta.language,Array.isArray(meta.tags)?meta.tags.join(" "):meta.tags,statsFor(item)].filter(Boolean).join(" ").toLowerCase()}function optionLabel(item){const stats=statsFor(item);return`${item.label||item.id}${stats?` (${stats})`:""}`}function renderTrigger(picker){const value=picker.select.value,item=picker.items.find(v=>v.id===value),content=picker.trigger.querySelector(".bvp-trigger-content");if(!item){content.innerHTML=`${esc(picker.placeholder)}`;return}const stats=statsFor(item);content.innerHTML=`${avatarHtml(item,24)}${esc(item.label||item.id)}${stats?`${esc(stats)}`:""}`}function renderList(picker,filter){const q=String(filter||"").trim().toLowerCase(),items=q?picker.items.filter(item=>searchText(item).includes(q)):picker.items;if(!items.length){picker.list.innerHTML=`
${esc(q?"No voices found":picker.empty)}
`;return}picker.list.innerHTML=items.map(item=>{const meta=metaFor(item)||{},stats=statsFor(item),sub=[meta.lang||meta.language,meta.file_type].filter(Boolean).join(" \xB7 ");return``}).join("")}function close(picker){picker&&(picker.drop.hidden=!0,picker.drop.style.display="none",picker.trigger.classList.remove("open"))}function closeAll(except){Object.values(pickers).forEach(p=>{p!==except&&close(p)})}function open(picker){closeAll(picker),picker.drop.hidden=!1,picker.drop.style.display="",picker.trigger.classList.add("open"),picker.search.value="",renderList(picker,""),picker.search.focus(),requestAnimationFrame(()=>{var _a2;return(_a2=picker.list.querySelector(".bvp-item.selected"))==null?void 0:_a2.scrollIntoView({block:"nearest"})})}function setValue(picker,value,dispatch){picker.select.value=value||"",renderTrigger(picker),renderList(picker,picker.search.value),close(picker),dispatch!==!1&&picker.select.dispatchEvent(new Event("change",{bubbles:!0})),picker.trigger.focus()}function upgrade(selectId,options){var _a2;const select=document.getElementById(selectId);if(!select)return null;if(pickers[selectId])return pickers[selectId];const picker={select,items:[],placeholder:(options==null?void 0:options.placeholder)||((_a2=Array.from(select.options).find(o=>!o.value))==null?void 0:_a2.textContent)||"\u2014 select after fetch \u2014",empty:(options==null?void 0:options.empty)||"No voices"};select.dataset.bvpUpgraded="1",select.style.display="none";const root=document.createElement("div");return root.className="bvp-root",root.dataset.bvpFor=selectId,select.parentNode.insertBefore(root,select),root.innerHTML=` `,Object.assign(picker,{root,trigger:root.querySelector(".bvp-trigger"),drop:root.querySelector(".bvp-drop"),search:root.querySelector(".bvp-search"),list:root.querySelector(".bvp-list")}),picker.trigger.addEventListener("click",e=>{e.preventDefault(),picker.drop.hidden?open(picker):close(picker)}),picker.trigger.addEventListener("keydown",e=>{var _a3;(e.key==="Enter"||e.key===" ")&&(e.preventDefault(),open(picker)),e.key==="ArrowDown"&&(e.preventDefault(),open(picker),(_a3=picker.list.querySelector(".bvp-item"))==null||_a3.focus())}),picker.search.addEventListener("input",()=>renderList(picker,picker.search.value)),picker.search.addEventListener("keydown",e=>{var _a3;e.key==="Escape"&&(e.preventDefault(),close(picker),picker.trigger.focus()),e.key==="ArrowDown"&&(e.preventDefault(),(_a3=picker.list.querySelector(".bvp-item"))==null||_a3.focus())}),picker.list.addEventListener("click",e=>{const item=e.target.closest(".bvp-item");item&&setValue(picker,item.dataset.value,!0)}),picker.list.addEventListener("keydown",e=>{const item=e.target.closest(".bvp-item");item&&((e.key==="Enter"||e.key===" ")&&(e.preventDefault(),setValue(picker,item.dataset.value,!0)),e.key==="ArrowDown"&&(e.preventDefault(),(item.nextElementSibling||item).focus()),e.key==="ArrowUp"&&(e.preventDefault(),(item.previousElementSibling||picker.search).focus()),e.key==="Escape"&&(e.preventDefault(),close(picker),picker.trigger.focus()))}),select.addEventListener("change",()=>{renderTrigger(picker),close(picker)}),document.addEventListener("click",e=>{root.contains(e.target)||close(picker)},!0),pickers[selectId]=picker,syncFromSelect(selectId),picker}function populate(selectId,values,options){var _a2;const picker=upgrade(selectId,options||{});if(!picker)return;picker.placeholder=(options==null?void 0:options.placeholder)||picker.placeholder,picker.empty=(options==null?void 0:options.empty)||picker.empty;const previous=picker.select.value;picker.items=(values||[]).map(norm).filter(item=>item.id),picker.select.innerHTML=``+picker.items.map(item=>``).join(""),previous&&picker.items.some(item=>item.id===previous)&&(picker.select.value=previous),renderTrigger(picker),renderList(picker,((_a2=picker.search)==null?void 0:_a2.value)||"")}function syncFromSelect(selectId){const picker=pickers[selectId];picker&&(picker.items=Array.from(picker.select.options).filter(o=>o.value).map(o=>({id:o.value,label:o.textContent||o.value,meta:libraryVoice(o.value)||{}})),renderTrigger(picker),renderList(picker,""))}function getValue(selectId){var _a2;return((_a2=document.getElementById(selectId))==null?void 0:_a2.value)||""}function set(selectId,value){const picker=pickers[selectId]||upgrade(selectId);picker&&setValue(picker,value,!0)}window.BenchmarkVoicePicker={upgrade,populate,syncFromSelect,getValue,set}}();let _selectedVoiceWrap=null;function selectVoice(wrap){var _a2;const inspector=document.getElementById("voices-inspector");if(!inspector)return;function restoreToRow(targetWrap){(targetWrap._extracted||[]).forEach(({el,target})=>{el&&target&&target.appendChild(el)}),targetWrap._extracted=[],inspector.querySelectorAll(".vr-main-row,.vr-optimizer").forEach(el=>targetWrap.appendChild(el))}if(_selectedVoiceWrap&&_selectedVoiceWrap!==wrap&&(_selectedVoiceWrap.classList.remove("vr-selected"),restoreToRow(_selectedVoiceWrap)),_selectedVoiceWrap===wrap){restoreToRow(wrap),wrap.classList.remove("vr-selected"),_selectedVoiceWrap=null,inspector.innerHTML='

Pick a voice on the left
to edit it here

',typeof window.onMobileInspectorClose=="function"&&window.onMobileInspectorClose();return}_selectedVoiceWrap=wrap,wrap.classList.add("vr-selected"),typeof window.onMobileInspectorOpen=="function"&&window.onMobileInspectorOpen();const voiceId=wrap.dataset.id||"",color=wrap.dataset.color||"#9575CD",isClone=wrap.dataset.hasRef==="true",dbfs=wrap.dataset.dbfs||"-",hasPicture=wrap.dataset.hasPicture==="true",v=(_voices||[]).find(vv=>vv.id===voiceId)||{},langCode=v.lang||wrap.dataset.lang||"",flagCc=v.flag||wrap.dataset.flagCc||langCode,rating=v.rating||0,curGender=v.gender||"N",nameParts=voiceId.split("_"),dispName=v.name&&String(v.name).trim()?String(v.name).trim():nameParts.length>1?nameParts[nameParts.length-1]:voiceId,initial=(dispName[0]||voiceId[0]||"?").toUpperCase(),picSrcInsp=hasPicture?`/api/voice/picture/${encodeURIComponent(voiceId)}`:null,inspFlagCc=flagCc||langCode,inspFlagIcon=inspFlagCc?``:null,silHtml=``,inspIconHtml=window.voiceAvatarIcon?window.voiceAvatarIcon(v.avatar,48):null,avatarHtml=picSrcInsp?``:inspIconHtml||silHtml,avatarBgStyle=picSrcInsp||inspIconHtml?"":`style="background:${color}"`,flagIconHtml=inspFlagIcon||"",makeStars=n=>[1,2,3,4,5].map(i=>``).join(""),inspBenchText=typeof fmtBenchmark=="function"?fmtBenchmark(v):"-",inspBenchTitle=typeof benchmarkTitle=="function"?benchmarkTitle(v):"",inspBenchCls=typeof benchmarkClass=="function"?benchmarkClass(v):"",LANGS=["EN","DE","IT","ES","FR","PT","NL","PL","ZH","JA","KO","AR","RU","TR","HI","SV","DA","FI","NB","HU","CS","RO","UK"],_gM={F:"\u2640",M:"\u2642",N:"\u26A5","":"?"},_gL={F:"Female",M:"Male",N:"Diverse","":"\u2014"},_gC={F:"g-f",M:"g-m",N:"g-n","":"g-n"},genderLabelHtml=`${_gM[curGender]||"?"} ${_gL[curGender]||"\u2014"}`;inspector.innerHTML=`
${avatarHtml}

${escHtml(dispName)}

${escHtml(voiceId)}
${v.seed!==void 0&&v.seed!==null?`
Pin Seed # ${v.seed}
`:''}
${flagIconHtml} ${escHtml(langCode)} ${escHtml(genderLabelHtml)} ${isClone?"Clone":"Design"} ${inspBenchText!=="-"?` ${escHtml(inspBenchText)}`:""} ${makeStars(rating)} ${rating}/5
`,window.innerWidth<=767&&typeof window.onMobileInspectorOpen=="function"&&window.onMobileInspectorOpen();const saveSlot=inspector.querySelector(".insp-actions-save"),activeSlot=inspector.querySelector(".insp-actions-active"),deleteSlot=inspector.querySelector(".insp-actions-delete"),body=inspector.querySelector(".inspector-body"),saveBtn=document.createElement("button");saveBtn.className="btn-primary insp-save-btn",saveBtn.textContent="Save changes",saveBtn.addEventListener("click",()=>{var _a3;return(_a3=body.querySelector(".opt-save-text"))==null?void 0:_a3.click()}),saveSlot.appendChild(saveBtn);const detailRow=wrap.querySelector(".vr-detail-row"),activeEl=detailRow==null?void 0:detailRow.querySelector(".vr-detail-active"),deleteEl=detailRow==null?void 0:detailRow.querySelector(".vr-detail-delete");if(activeEl&&activeSlot.appendChild(activeEl),deleteEl){deleteSlot.appendChild(deleteEl);const _dBtn=deleteEl.querySelector(".delete-btn"),_dCnl=deleteEl.querySelector(".delete-confirm-cancel");_dBtn==null||_dBtn.addEventListener("click",()=>deleteEl.classList.add("delete-pending")),_dCnl==null||_dCnl.addEventListener("click",()=>deleteEl.classList.remove("delete-pending"))}const inspAvatar=inspector.querySelector(".inspector-avatar");inspAvatar.addEventListener("click",()=>{var _a3;(_a3=body.querySelector(".photo-input"))==null||_a3.click()}),inspAvatar.addEventListener("dragenter",e=>{e.preventDefault(),inspAvatar.classList.add("drag-over")}),inspAvatar.addEventListener("dragover",e=>{e.preventDefault(),inspAvatar.classList.add("drag-over")}),inspAvatar.addEventListener("dragleave",()=>inspAvatar.classList.remove("drag-over")),inspAvatar.addEventListener("drop",async e=>{if(e.preventDefault(),inspAvatar.classList.remove("drag-over"),e.dataTransfer.files&&e.dataTransfer.files.length>0){const photoInput=body.querySelector(".photo-input");photoInput&&(photoInput.files=e.dataTransfer.files,photoInput.dispatchEvent(new Event("change")));return}let url=e.dataTransfer.getData("text/uri-list");if(!url){const html=e.dataTransfer.getData("text/html");if(html){const match=html.match(/src=["'](.*?)["']/);match&&(url=match[1])}}if(url||(url=e.dataTransfer.getData("text/plain")),url&&/^https?:\/\//i.test(url)){typeof status=="function"&&status("Downloading picture from URL...");try{const r=await fetch("/api/voice/picture-url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:voiceId,image_url:url})});if(!r.ok)throw new Error((await r.json()).detail);const photoCell=body.querySelector(".vr-photo");photoCell&&photoCell.dispatchEvent(new Event("update-photo")),typeof toast=="function"&&toast("Photo saved from URL","success"),typeof status=="function"&&status("Photo saved successfully")}catch(err){typeof toast=="function"&&toast("Photo URL download failed: "+err.message,"error"),typeof status=="function"&&status("Photo URL download failed")}}}),inspector.querySelector(".insp-copy-id-btn").addEventListener("click",()=>{copyText(voiceId).then(()=>toast("Copied: "+voiceId))});const dispNameEl=inspector.querySelector(".insp-disp-name"),nameEditEl=inspector.querySelector(".insp-name-edit"),fullIdEl=inspector.querySelector(".insp-full-id");let _editMode=null,_editCancelled=!1;const showEditor=(mode,val)=>{_editMode=mode,_editCancelled=!1,dispNameEl.style.display="none",fullIdEl.style.display="none",nameEditEl.style.display="block",nameEditEl.placeholder=mode==="name"?"Display name":"Voice ID",nameEditEl.value=val,nameEditEl.focus(),nameEditEl.select()},startNameEdit=()=>showEditor("name",dispName),startInspRename=()=>showEditor("id",voiceId),commitEdit=async()=>{const mode=_editMode;if(_editMode=null,dispNameEl.style.display="",fullIdEl.style.display="",nameEditEl.style.display="none",_editCancelled||!mode)return;const val=nameEditEl.value.trim();if(mode==="id"){if(!val||val===voiceId)return;const rowInput=wrap.querySelector(".vr-name-input"),rowOk=wrap.querySelector(".rename-ok");rowInput&&rowOk&&(rowInput.value=val,rowOk.click());return}if(val!==dispName)try{const r=await fetch("/api/voice/meta",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:voiceId,name:val})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}v.name=val,dispNameEl.textContent=val||voiceId.split("_").pop(),typeof toast=="function"&&toast("Display name updated","success"),typeof renderVoiceList=="function"&&renderVoiceList()}catch(e){typeof toast=="function"&&toast("Rename failed: "+(e.message||e),"error")}};dispNameEl.addEventListener("dblclick",startNameEdit),fullIdEl.addEventListener("dblclick",startInspRename),fullIdEl.addEventListener("click",startInspRename),(_a2=inspector.querySelector(".insp-edit-id-btn"))==null||_a2.addEventListener("click",startInspRename),nameEditEl.addEventListener("blur",commitEdit),nameEditEl.addEventListener("keydown",e=>{e.key==="Enter"&&nameEditEl.blur(),e.key==="Escape"&&(_editCancelled=!0,nameEditEl.blur())});const flagSpan=inspector.querySelector(".insp-flag"),flagIconEl=inspector.querySelector(".insp-flag-icon"),applyFlag=async cc=>{v.flag=cc,wrap.dataset.flagCc=cc;const fi=``;flagIconEl&&(flagIconEl.innerHTML=fi),await saveMeta(voiceId,{flag:cc})};flagSpan.addEventListener("click",()=>{const langList=FLAG_OPTIONS[langCode.toUpperCase()]||[],items=uniqueFlagOptions([langList,ALL_FLAGS]).map(([cc,name])=>[cc.toLowerCase(),name]);createSearchablePicker(flagSpan,items,applyFlag,{placeholder:"Country or accent\u2026",renderItem:(cc,name)=>`${escHtml(name)}`})});const langCodeEl=inspector.querySelector(".insp-lang-code"),langWrap=inspector.querySelector(".insp-lang-wrap"),applyLang=async newLang=>{v.lang=newLang,wrap.dataset.lang=newLang,langCodeEl&&(langCodeEl.textContent=newLang),await saveMeta(voiceId,{lang:newLang})};langWrap==null||langWrap.addEventListener("click",()=>{const items=LANGS.map(l=>[l,LANGUAGE_LABELS[l]?`${LANGUAGE_LABELS[l]} (${l})`:l]);createSearchablePicker(langWrap,items,applyLang,{placeholder:"Language\u2026",renderItem:(l,label)=>`${escHtml(l)}${escHtml(LANGUAGE_LABELS[l]||l)}`})});const genderLabelEl=inspector.querySelector(".insp-gender-label"),genderSel=inspector.querySelector(".insp-gender-sel"),applyGender=async ng=>{v.gender=ng,genderLabelEl&&(genderLabelEl.textContent=`${_gM[ng]||"?"} ${_gL[ng]||"\u2014"}`),genderSel&&(genderSel.value=ng);const gBadge=wrap.querySelector(".gender-badge");gBadge&&(gBadge.innerHTML=`${_gM[ng]||"?"}${_gL[ng]||"\u2014"}`,gBadge.className="gender-badge "+(_gC[ng]||"g-n")),await saveMeta(voiceId,{gender:ng})};genderLabelEl==null||genderLabelEl.addEventListener("click",()=>{const cycle=["F","M","N"];applyGender(cycle[(cycle.indexOf(v.gender||"N")+1)%3])}),genderSel==null||genderSel.addEventListener("change",()=>applyGender(genderSel.value));const tagInput=inspector.querySelector(".insp-tag-input"),getAllKnownTags=()=>{const fromDb=(_voices||[]).flatMap(vv=>(vv.tag||"").split(",").map(t=>t.trim()).filter(Boolean)),fromStorage=getStoredTags();return[...new Set([...fromStorage,...fromDb])].sort((a,b)=>a.localeCompare(b))},tagLastToken=val=>val.split(",").pop().trimStart(),tagReplaceLastToken=(val,rep)=>{const parts=val.split(",");return parts[parts.length-1]=parts.length>1?" "+rep:rep,parts.join(",")};let _tagDrop=null;const hideTagDrop=()=>{_tagDrop==null||_tagDrop.remove(),_tagDrop=null},showTagDrop=()=>{hideTagDrop();const token=tagLastToken(tagInput.value),matches=getAllKnownTags().filter(t=>t.toLowerCase().startsWith(token.toLowerCase())&&t.toLowerCase()!==token.toLowerCase());if(!matches.length)return;_tagDrop=document.createElement("div"),_tagDrop.className="tag-suggest",matches.slice(0,10).forEach(tag=>{const btn=document.createElement("button");btn.type="button",btn.className="tag-suggest-item",btn.textContent=tag,btn.addEventListener("mousedown",e=>{e.preventDefault(),tagInput.value=tagReplaceLastToken(tagInput.value,tag),tagInput.dispatchEvent(new Event("input")),hideTagDrop(),tagInput.focus()}),_tagDrop.appendChild(btn)}),document.body.appendChild(_tagDrop);const r=tagInput.getBoundingClientRect();_tagDrop.style.top=r.bottom+3+"px",_tagDrop.style.left=r.left+"px",_tagDrop.style.minWidth=Math.max(r.width,140)+"px"};tagInput.addEventListener("input",()=>showTagDrop()),tagInput.addEventListener("focus",()=>showTagDrop()),tagInput.addEventListener("blur",()=>setTimeout(hideTagDrop,150)),tagInput.addEventListener("keydown",e=>{e.key==="Escape"&&hideTagDrop(),e.key===","&&_tagDrop&&setTimeout(showTagDrop,10)});const saveTagValue=debounce(async()=>{tagInput.value.split(",").map(t=>t.trim()).filter(Boolean).forEach(addStoredTag),v.tag=tagInput.value,await saveMeta(voiceId,{tag:tagInput.value})},700);tagInput.addEventListener("input",saveTagValue),tagInput.addEventListener("change",saveTagValue);const updateRating=async newRating=>{v.rating=newRating,wrap.dataset.rating=newRating,inspector.querySelectorAll(".insp-star").forEach(s=>s.classList.toggle("on",parseInt(s.dataset.val)<=newRating)),inspector.querySelectorAll(".insp-rating-label, .insp-meta-rating-label").forEach(el=>el.textContent=`${newRating}/5`),wrap.querySelectorAll(".vr-rating .star").forEach((s,i)=>s.classList.toggle("on",i{const stars=[...inspector.querySelectorAll(`${sel} .insp-star`)];stars.forEach(s=>{s.addEventListener("click",()=>{const val=parseInt(s.dataset.val);updateRating(val===(v.rating||0)?0:val)}),s.addEventListener("mouseenter",()=>{const val=parseInt(s.dataset.val);stars.forEach(ss=>ss.classList.toggle("on",parseInt(ss.dataset.val)<=val))}),s.addEventListener("mouseleave",()=>stars.forEach(ss=>ss.classList.toggle("on",parseInt(ss.dataset.val)<=(v.rating||0))))})});const noteEl=detailRow==null?void 0:detailRow.querySelector(".vr-note"),noteRowEl=inspector.querySelector(".insp-note-slot");noteEl&¬eRowEl&¬eRowEl.appendChild(noteEl);const mainRow=wrap.querySelector(".vr-main-row"),optimizer=wrap.querySelector(".vr-optimizer");mainRow&&body.appendChild(mainRow),optimizer&&body.appendChild(optimizer),wrap._loadOptimizer&&wrap._loadOptimizer().catch(e=>console.warn("Auto-load waveform failed:",e));const personaVal=v.persona||"",personaPanel=document.createElement("div");personaPanel.className="insp-persona-panel",personaPanel.innerHTML=`
Character persona Used by LLM to rewrite text in this voice's style. Leave empty to skip.
`,body.appendChild(personaPanel);const personaTextarea=personaPanel.querySelector(".insp-persona-input"),personaStatus=personaPanel.querySelector(".insp-persona-status");personaPanel.querySelector(".insp-persona-save-btn").addEventListener("click",async()=>{const pText=personaTextarea.value.trim();try{await fetch("/api/voice/meta",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:voiceId,persona:pText})}),v.persona=pText,personaStatus.textContent="Saved.",setTimeout(()=>{personaStatus.textContent=""},2e3),toast("Persona saved","success")}catch(e){toast("Save failed: "+e.message,"error")}});const maintTitle=body.querySelector(".opt-maintenance .opt-group-title");maintTitle&&(maintTitle.innerHTML=`Loudness Current ${escHtml(dbfs)} dBFS`),body.querySelectorAll(".opt-group").forEach(group=>{const title=group.querySelector(":scope > .opt-group-title");title&&title.addEventListener("click",()=>{group.classList.toggle("open")})}),typeof attachSeedFinder=="function"&&attachSeedFinder(voiceId,body),wrap._extracted=[activeEl?{el:activeEl,target:detailRow}:null,deleteEl?{el:deleteEl,target:detailRow}:null,noteEl?{el:noteEl,target:detailRow}:null].filter(Boolean)}const SEED_FINDER_TEXT_DE="Die 3.567 neuen High-End Ger\xE4te f\xFCr das Server-Update ben\xF6tigen eine au\xDFergew\xF6hnlich starke K\xFChlung und regelm\xE4\xDFige Ma\xDFnahmen, um die Performance bei gro\xDFer Last zu gew\xE4hrleisten. - The system administrator successfully configured the customized Docker stacks and benchmarked the inference engines at exactly 8:45 AM. - Notiere dir an Midsummer 21.06. um 14 Uhr - Es ist reine Zeitverschwendung, etwas Mittelm\xE4\xDFiges zu tun! Sch\xF6ne Gr\xFC\xDFe! \u2014 Madonna - Tr\xE4ume beginnt mit einem positiven Mindset.",SEED_FINDER_TEXT_EN=SEED_FINDER_TEXT_DE,SEED_FINDER_TEXT_MIXED=SEED_FINDER_TEXT_DE;function _seedFinderDefaultText(voiceId){if(window._appSettings&&window._appSettings.seed_finder_text)return window._appSettings.seed_finder_text;const lc=(voiceId||"").toLowerCase();return lc.startsWith("de_")?SEED_FINDER_TEXT_MIXED:lc.startsWith("en_")||lc.startsWith("gb_")?SEED_FINDER_TEXT_EN:SEED_FINDER_TEXT_MIXED}const SF_DB="seed-finder",SF_STORE="samples",SF_CACHE_VERSION="wav-seed-v2";function _sfHash(s){let h=0;for(let i=0;i>>0;return h.toString(36)}function _sfDbOpen(){return new Promise((res,rej)=>{const r=indexedDB.open(SF_DB,1);r.onupgradeneeded=e=>{const db=e.target.result;db.objectStoreNames.contains(SF_STORE)||db.createObjectStore(SF_STORE,{keyPath:"key"})},r.onsuccess=e=>res(e.target.result),r.onerror=e=>rej(e.target.error)})}async function _sfDbGet(key){try{const db=await _sfDbOpen();return await new Promise((res,rej)=>{const r=db.transaction(SF_STORE,"readonly").objectStore(SF_STORE).get(key);r.onsuccess=e=>res(e.target.result||null),r.onerror=e=>rej(e.target.error)})}catch{return null}}async function _sfDbPut(rec){try{const db=await _sfDbOpen();await new Promise((res,rej)=>{const r=db.transaction(SF_STORE,"readwrite").objectStore(SF_STORE).put(rec);r.onsuccess=()=>res(),r.onerror=e=>rej(e.target.error)})}catch{}}async function _sfDbAllForVoice(voiceId){try{const db=await _sfDbOpen();return await new Promise((res,rej)=>{const out=[],cur=db.transaction(SF_STORE,"readonly").objectStore(SF_STORE).openCursor();cur.onsuccess=e=>{const c=e.target.result;c?(c.value.voiceId===voiceId&&out.push(c.value),c.continue()):res(out)},cur.onerror=e=>rej(e.target.error)})}catch{return[]}}async function _sfDbClearVoice(voiceId){try{const db=await _sfDbOpen();await new Promise((res,rej)=>{const store=db.transaction(SF_STORE,"readwrite").objectStore(SF_STORE),cur=store.openCursor();cur.onsuccess=e=>{const c=e.target.result;c?(c.value.voiceId===voiceId&&store.delete(c.primaryKey),c.continue()):res()},cur.onerror=e=>rej(e.target.error)})}catch{}}function attachSeedFinder(voiceId,body){const panel=document.createElement("div");panel.className="opt-group seed-finder-group",panel.innerHTML=`
\u{1F3B2} Seed Finder Find the most natural-sounding voice
Generates audio for each seed so you can listen and pick the best one. Click "Use this seed" to save it to the TTS server.
`,body.appendChild(panel);let _batchLoaded=!1;panel.querySelector(".opt-group-title").addEventListener("click",()=>{panel.classList.toggle("open"),panel.classList.contains("open")&&!_batchLoaded&&(_batchLoaded=!0,_loadBatchSamples())});const textEl=panel.querySelector(".seed-finder-text"),fromEl=panel.querySelector(".seed-finder-from"),toEl=panel.querySelector(".seed-finder-to"),backendEl=panel.querySelector(".seed-finder-backend"),runBtn=panel.querySelector(".seed-finder-run-btn"),cancelBtn=panel.querySelector(".seed-finder-cancel-btn"),clearBtn=panel.querySelector(".seed-finder-clear-btn"),statusEl=panel.querySelector(".seed-finder-status"),progressEl=panel.querySelector(".seed-finder-progress"),progLabel=panel.querySelector(".seed-finder-progress-label"),progCount=panel.querySelector(".seed-finder-progress-count"),progBar=panel.querySelector(".seed-finder-bar"),resultsEl=panel.querySelector(".seed-finder-results");textEl.value=_seedFinderDefaultText(voiceId);const pinInput=panel.querySelector(".seed-finder-pin-input"),pinBtn=panel.querySelector(".seed-finder-pin-btn"),pinClear=panel.querySelector(".seed-finder-pin-clear"),pinStatus=panel.querySelector(".seed-finder-pin-status"),voiceObj=(window._voices||[]).find(v=>v.id===voiceId)||{};voiceObj.seed!==void 0&&voiceObj.seed!==null&&(pinInput.value=voiceObj.seed,pinStatus.textContent=`\u2713 Pinned seed ${voiceObj.seed}`);let _cancelled=!1,_currentAudio=null;async function _loadBatchSamples(){try{const resp=await fetch(`/api/seed-samples/${encodeURIComponent(voiceId)}`);if(!resp.ok)return;const{seeds}=await resp.json();if(!seeds||seeds.length===0)return;resultsEl.innerHTML="";const header=document.createElement("div");header.className="sfr-batch-header",header.textContent=`${seeds.length} pre-generated sample${seeds.length!==1?"s":""} from batch run \u2014 click Play to listen`,resultsEl.appendChild(header);for(const seed of seeds){const row=document.createElement("div");row.className="seed-finder-result-row sfr-batch",row.dataset.seed=seed,row.innerHTML=` Seed ${seed} pre-generated `;let _cachedBlob=null;row.querySelector(".sfr-play-btn").addEventListener("click",async()=>{if(!_cachedBlob){const r=await fetch(`/api/seed-sample/${encodeURIComponent(voiceId)}/${seed}`);if(!r.ok){typeof toast=="function"&&toast("Failed to load sample","error");return}_cachedBlob=await r.blob()}_playBlob(_cachedBlob)}),row.querySelector(".sfr-use-btn").addEventListener("click",async()=>{try{await _saveSeed(seed),resultsEl.querySelectorAll(".sfr-saved").forEach(el=>el.textContent=""),resultsEl.querySelectorAll(".seed-finder-result-row").forEach(r=>r.classList.remove("sfr-active")),row.classList.add("sfr-active"),row.querySelector(".sfr-saved").textContent="\u2713 Saved",pinInput&&(pinInput.value=seed),typeof toast=="function"&&toast(`Seed ${seed} saved for ${voiceId}`,"success")}catch(e){typeof toast=="function"&&toast("Save failed: "+e.message,"error")}}),resultsEl.appendChild(row)}}catch{}}async function _saveSeed(seed){const resp=await fetch("/api/tts-voice-seed",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice:voiceId,seed})});if(!resp.ok){const e=await resp.json().catch(()=>({}));throw new Error(e.detail||resp.statusText)}const v=(window._voices||[]).find(vv=>vv.id===voiceId);v&&(v.seed=seed);const pinnedSeedEl=document.querySelector(".insp-pinned-seed");pinnedSeedEl&&(seed!=null?(pinnedSeedEl.textContent=`Pin Seed # ${seed}`,pinnedSeedEl.style.display=""):(pinnedSeedEl.style.display="none",pinnedSeedEl.textContent=""))}async function _fetchWithRetry(fn,tries=2,delay=1200){let lastErr;for(let t=0;t<=tries;t++){if(_cancelled)throw new Error("cancelled");try{return await fn()}catch(e){lastErr=e,tsetTimeout(r,delay))}}throw lastErr}function _playBlob(blob){_currentAudio&&(_currentAudio.pause(),_currentAudio=null);const url=URL.createObjectURL(blob),audio=new Audio(url);_currentAudio=audio,audio.play().catch(()=>{}),audio.addEventListener("ended",()=>{URL.revokeObjectURL(url),_currentAudio=null})}const seedRows=new Map,_keyFor=(seed,text,backend)=>`${SF_CACHE_VERSION}|${voiceId}|${backend}|${_sfHash(text)}|${seed}`;async function _useSeed(seed,row){_currentAudio&&(_currentAudio.pause(),_currentAudio=null);try{await _saveSeed(seed),resultsEl.querySelectorAll(".sfr-saved").forEach(el=>el.textContent=""),resultsEl.querySelectorAll(".seed-finder-result-row").forEach(r=>r.classList.remove("sfr-active")),row.classList.add("sfr-active"),row.querySelector(".sfr-saved").textContent="\u2713 Saved",pinInput&&(pinInput.value=seed),typeof toast=="function"&&toast(`Seed ${seed} saved for ${voiceId}`,"success")}catch(e){typeof toast=="function"&&toast("Save failed: "+e.message,"error")}}function _seedMetricText(data){const parts=[];return data.dur&&parts.push(`audio ${data.dur.toFixed(1)}s${data.clipped?" clipped":""}`),data.genSec&&parts.push(`gen ${data.genSec.toFixed(1)}s`),data.rtf&&parts.push(`${data.rtf.toFixed(2)}x`),data.cached&&parts.push("saved"),parts.join(" \xB7 ")}function _renderSeed(seed,data){const row=document.createElement("div");if(row.className="seed-finder-result-row"+(data.err?" seed-finder-result-error":""),row.dataset.seed=seed,data.err)row.innerHTML=`Seed ${seed} Failed: ${escHtml(String(data.err))} `,row.querySelector(".sfr-retry-btn").addEventListener("click",()=>_retrySeed(seed));else{const durStr=_seedMetricText(data);row.innerHTML=`Seed ${seed} ${escHtml(durStr)} `,row.querySelector(".sfr-play-btn").addEventListener("click",()=>_playBlob(data.blob)),row.querySelector(".sfr-use-btn").addEventListener("click",()=>_useSeed(seed,row))}const existing=seedRows.get(seed);existing?existing.replaceWith(row):resultsEl.appendChild(row),seedRows.set(seed,row)}async function _fetchSeedSample(seed,text,backend){const body2={text,voice:voiceId,response_format:"wav",instruct:"",backend,seed,seed_finder:!0},t0=performance.now(),r=await fetch("/api/tts-preview",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body2)}),genSec=Math.max(0,(performance.now()-t0)/1e3);if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const blob=await r.blob(),headerDur=parseFloat(r.headers.get("X-TTS-Audio-Duration")||""),dur=Number.isFinite(headerDur)&&headerDur>0?headerDur:Math.max(0,(blob.size-44)/(24e3*2)),clipped=String(r.headers.get("X-TTS-Audio-Clipped")||"").toLowerCase()==="true",rtf=dur>0?genSec/dur:0;return{blob,dur,genSec,rtf,clipped}}async function _generateSeed(seed,text,backend,force){const key=_keyFor(seed,text,backend);if(!force){const c=await _sfDbGet(key);if(c&&c.blob)return{blob:c.blob,dur:c.dur||0,genSec:c.genSec||0,rtf:c.rtf||0,clipped:!!c.clipped,cached:!0}}const data=await _fetchWithRetry(()=>_fetchSeedSample(seed,text,backend));return _sfDbPut({key,cacheVersion:SF_CACHE_VERSION,voiceId,backend,textHash:_sfHash(text),seed,...data,ts:Date.now()}),{...data,cached:!1}}async function _retrySeed(seed){const text=textEl.value.trim(),backend=backendEl.value,row=seedRows.get(seed),err=row&&row.querySelector(".sfr-error");err&&(err.textContent="Retrying\u2026");try{_renderSeed(seed,await _generateSeed(seed,text,backend,!0))}catch(e){_renderSeed(seed,{err:e.message||String(e)})}}async function _loadCached(){const text=textEl.value.trim(),backend=backendEl.value,th=_sfHash(text),all=(await _sfDbAllForVoice(voiceId)).filter(r=>r.cacheVersion===SF_CACHE_VERSION&&r.backend===backend&&r.textHash===th&&r.blob);if(!all.length){clearBtn.style.display="none";return}all.sort((a,b)=>a.seed-b.seed),resultsEl.innerHTML="",seedRows.clear(),all.forEach(r=>_renderSeed(r.seed,{blob:r.blob,dur:r.dur||0,genSec:r.genSec||0,rtf:r.rtf||0,clipped:!!r.clipped,cached:!0})),clearBtn.style.display="",statusEl.textContent=`${all.length} saved sample${all.length!==1?"s":""} loaded \u2014 Run to fill the rest.`}async function _runFinder(){const text=textEl.value.trim();if(!text){statusEl.textContent="Enter a test sentence.";return}const seedFrom=Math.max(0,parseInt(fromEl.value,10)||1),seedTo=Math.max(seedFrom,parseInt(toEl.value,10)||15),backend=backendEl.value,seeds=[];for(let s=seedFrom;s<=seedTo;s++)seeds.push(s);const total=seeds.length;_cancelled=!1,resultsEl.innerHTML="",seedRows.clear(),progressEl.style.display="",runBtn.style.display="none",cancelBtn.style.display="",clearBtn.style.display="none",statusEl.textContent="",progBar.style.width="0%",progCount.textContent=`0 / ${total}`,progLabel.textContent="Generating\u2026";let i=0,cached=0;for(const seed of seeds){if(_cancelled)break;progLabel.textContent=`Seed ${seed}\u2026`,progCount.textContent=`${i} / ${total}`,progBar.style.width=`${Math.round(i/total*100)}%`;try{const d=await _generateSeed(seed,text,backend,!1);d.cached&&cached++,_renderSeed(seed,d)}catch(e){if(_cancelled)break;_renderSeed(seed,{err:e.message||String(e)})}i++}progBar.style.width="100%",progCount.textContent=`${i} / ${total}`,progLabel.textContent=_cancelled?"Cancelled.":`Done \u2014 listen and pick your seed.${cached?` (${cached} from cache)`:""}`,runBtn.style.display="",cancelBtn.style.display="none",seedRows.size&&(clearBtn.style.display="")}runBtn.addEventListener("click",_runFinder),cancelBtn.addEventListener("click",()=>{_cancelled=!0}),clearBtn.addEventListener("click",async()=>{confirm("Delete the cached seed samples for this voice?")&&(await _sfDbClearVoice(voiceId),resultsEl.innerHTML="",seedRows.clear(),clearBtn.style.display="none",statusEl.textContent="Saved samples cleared.")}),_loadCached(),pinBtn.addEventListener("click",async()=>{const seed=parseInt(pinInput.value,10);if(isNaN(seed)||seed<0){pinStatus.textContent="Enter a seed number.";return}pinBtn.disabled=!0,pinStatus.textContent="Saving\u2026";try{await _saveSeed(seed),pinStatus.textContent=`\u2713 Pinned seed ${seed}`,typeof toast=="function"&&toast(`Voice ${voiceId} fixed to seed ${seed}`,"success")}catch(e){pinStatus.textContent="",typeof toast=="function"&&toast("Pin failed: "+e.message,"error")}finally{pinBtn.disabled=!1}}),pinClear.addEventListener("click",async()=>{pinClear.disabled=!0,pinStatus.textContent="Clearing\u2026";try{await _saveSeed(null),pinInput.value="",pinStatus.textContent="\u2713 Unpinned (random seed)",resultsEl.querySelectorAll(".sfr-saved").forEach(el=>el.textContent=""),resultsEl.querySelectorAll(".seed-finder-result-row").forEach(r=>r.classList.remove("sfr-active")),typeof toast=="function"&&toast(`Voice ${voiceId} unpinned \u2014 random seed`,"success")}catch(e){pinStatus.textContent="",typeof toast=="function"&&toast("Unpin failed: "+e.message,"error")}finally{pinClear.disabled=!1}})}function seedFinderBatchAll(){var _a2;const _sfUseSelected=typeof _bulkSelected!="undefined"&&_bulkSelected.size>0,ids=_sfUseSelected?[..._bulkSelected]:typeof activeVoiceIds=="function"?activeVoiceIds():(window._voices||[]).filter(v=>v.enabled!==!1).map(v=>v.id);if(!ids.length){typeof toast=="function"&&toast("No voices selected","error");return}(_a2=document.getElementById("seedbatch-overlay"))==null||_a2.remove();const ov=document.createElement("div");ov.className="audiobook-overlay",ov.id="seedbatch-overlay",ov.innerHTML=`
Batch seed generation
Generate & cache Seed Finder samples for ${ids.length} ${_sfUseSelected?"selected":"active"} voice${ids.length!==1?"s":""}. Already-cached seeds are skipped, so it's resumable; you can cancel anytime.
`,document.body.appendChild(ov);const box=ov.querySelector(".audiobook-box"),fromI=ov.querySelector(".sb-from"),toI=ov.querySelector(".sb-to"),est=ov.querySelector(".sb-estimate"),updEst=()=>{const f=Math.max(0,parseInt(fromI.value,10)||0),t=Math.max(f,parseInt(toI.value,10)||f);est.textContent=`${ids.length} voices \xD7 ${t-f+1} seeds = up to ${ids.length*(t-f+1)} samples`};fromI.addEventListener("input",updEst),toI.addEventListener("input",updEst),updEst(),ov.querySelector("#sb-cancel-cfg").addEventListener("click",()=>ov.remove()),ov.querySelector("#sb-start").addEventListener("click",()=>{const from=Math.max(0,parseInt(fromI.value,10)||1),to=Math.max(from,parseInt(toI.value,10)||8);_seedBatchRun(ids,from,to,box,ov)})}async function _seedBatchRun(ids,from,to,box,ov){const backend="voice_clone",totalJobs=ids.length*(to-from+1);box.innerHTML=`
Batch seed generation
Starting\u2026
`;let cancel=!1;box.querySelector("#sb-cancel").addEventListener("click",()=>{cancel=!0});const fill=box.querySelector("#sb-fill"),msg=box.querySelector("#sb-msg");let done=0,made=0,cached=0,failed=0;for(let vi=0;vi0?headerDur:Math.max(0,(blob.size-44)/(24e3*2)),clipped=String(resp.headers.get("X-TTS-Audio-Clipped")||"").toLowerCase()==="true",rtf=dur>0?genSec/dur:0;await _sfDbPut({key,cacheVersion:SF_CACHE_VERSION,voiceId,backend,textHash:th,seed,blob,dur,genSec,rtf,clipped,ts:Date.now()}),made++}catch{failed++}done++,fill.style.width=done/totalJobs*100+"%"}}ov.remove(),typeof toast=="function"&&toast(cancel?`Stopped \u2014 ${made} generated, ${cached} already cached`:`Batch done \u2014 ${made} generated, ${cached} cached${failed?`, ${failed} failed`:""}`,cancel?"error":"success")}typeof $=="function"&&((_a=$("seed-batch-all-btn"))==null||_a.addEventListener("click",seedFinderBatchAll));const DEFAULT_VOICE_SOURCE_URLS=["https://aiartes.com/voiceai","https://sample-files.com/downloads/audio/wav/voice-sample.wav","https://freesound.org/people/Scott%20Simpson/","https://lanceblairvo.com/raw-voiceover-samples/","https://github.com/yaph/tts-samples/tree/main/mp3","https://github.com/jim-schwoebel/voice_datasets"],VOICE_SOURCE_STORAGE_KEY="ttsvc-getvoices-sources";let _voiceSourcePayload=null,_voiceSourceItems=[];function sourceTextareaValueFromDefaults(){return DEFAULT_VOICE_SOURCE_URLS.join(` `)}function initGetVoiceSourcesEditor(){const box=$("getvoices-sources");!box||box.dataset.ready||(box.value=localStorage.getItem(VOICE_SOURCE_STORAGE_KEY)||sourceTextareaValueFromDefaults(),box.dataset.ready="1",box.addEventListener("input",()=>{localStorage.setItem(VOICE_SOURCE_STORAGE_KEY,box.value),_voiceSourcePayload=null,$("getvoices-status").textContent="Source list changed."}))}function getEditableVoiceSourceUrls(){var _a2;return initGetVoiceSourcesEditor(),(((_a2=$("getvoices-sources"))==null?void 0:_a2.value)||"").split(/\r?\n/).map(line=>line.trim()).filter(line=>line&&!line.startsWith("#"))}function getVoiceSourceItems(){return(_voiceSourcePayload&&_voiceSourcePayload.sources||[]).flatMap(src=>(src.items||[]).map(item=>({...item,_sourceName:src.name,_sourceHomepage:src.homepage})))}function voiceSourceSearchText(item){return[item.name,item.kind,item.category,item.language,item.gender,item.description,item.source,item._sourceName].join(" ").toLowerCase()}function setOptions(selectId,values,allLabel){const sel=$(selectId);if(!sel)return;const current=sel.value||"all";sel.innerHTML=``+values.map(value=>``).join(""),sel.value=values.includes(current)?current:"all"}function renderGetVoices(){var _a2,_b2,_c2,_d2,_e2,_f2;initGetVoiceSourcesEditor();const list=$("getvoices-list"),summary=$("getvoices-summary");if(!list||!summary)return;const payload=_voiceSourcePayload||{sources:[],total:0,direct_audio:0,errors:[]},sources=payload.sources||[],sourceFilter=((_a2=$("getvoices-source-filter"))==null?void 0:_a2.value)||"all",languageFilter=((_b2=$("getvoices-language-filter"))==null?void 0:_b2.value)||"all",genderFilter=((_c2=$("getvoices-gender-filter"))==null?void 0:_c2.value)||"all",filetypeFilter=((_d2=$("getvoices-filetype-filter"))==null?void 0:_d2.value)||"all",q=(((_e2=$("getvoices-search"))==null?void 0:_e2.value)||"").trim().toLowerCase(),directOnly=!!((_f2=$("getvoices-direct-only"))!=null&&_f2.checked);_voiceSourceItems=getVoiceSourceItems(),summary.innerHTML=[[`${payload.total||0}`,"Items found"],[`${payload.direct_audio||0}`,"Direct audio"],[`${sources.length}`,"Sources OK"],[`${(payload.errors||[]).length}`,"Errors"]].map(([value,label])=>`
${escHtml(value)}${escHtml(label)}
`).join(""),setOptions("getvoices-source-filter",sources.map(src=>src.id).filter(Boolean),"Source: all");const sourceSelect=$("getvoices-source-filter");sourceSelect&&[...sourceSelect.options].forEach(option=>{if(option.value==="all")return;const src=sources.find(s=>s.id===option.value);src&&(option.textContent=src.name||src.id)});const languages=[...new Set(_voiceSourceItems.map(item=>item.language||"Unknown"))].sort((a,b)=>a.localeCompare(b)),genders=[...new Set(_voiceSourceItems.map(item=>item.gender||"Unknown"))].sort((a,b)=>a.localeCompare(b)),filetypes=[...new Set(_voiceSourceItems.map(item=>(item.file_type||(item.direct_audio?"audio":"page")).toUpperCase()))].sort((a,b)=>a.localeCompare(b));setOptions("getvoices-language-filter",languages,"Language: all"),setOptions("getvoices-gender-filter",genders,"Sex: all"),setOptions("getvoices-filetype-filter",filetypes,"Filetype: all");let items=_voiceSourceItems.filter(item=>{if(sourceFilter!=="all"&&item.source_id!==sourceFilter||languageFilter!=="all"&&(item.language||"Unknown")!==languageFilter||genderFilter!=="all"&&(item.gender||"Unknown")!==genderFilter)return!1;const itemFiletype=(item.file_type||(item.direct_audio?"audio":"page")).toUpperCase();return!(filetypeFilter!=="all"&&itemFiletype!==filetypeFilter||directOnly&&!item.direct_audio||q&&!voiceSourceSearchText(item).includes(q))});const shown=items.slice(0,240),more=items.length-shown.length;if(!shown.length){const errorText=(payload.errors||[]).map(e=>`${e.source}: ${e.detail}`).join(" | ");list.innerHTML=`

No matching sources found.${errorText?" Source errors: "+escHtml(errorText):""}

`;return}list.innerHTML=shown.map(item=>{const thumb=item.image_url?``:'
',audio=item.audio_url?``:"",audioLink=item.audio_url?`Open audio`:"",canGetVoice=!!(item.import_url||item.audio_url),importUrl=item.import_url||item.audio_url,getVoice=canGetVoice?``:"",type=item.file_type?`${escHtml(String(item.file_type).toUpperCase())}`:"",language=item.language?`${escHtml(item.language)}`:"",gender=item.gender?`${escHtml(item.gender)}`:"";return`
${thumb}
${escHtml(item.name||"Untitled")} ${escHtml(item._sourceName||item.source||"")}${item.category?" \xB7 "+escHtml(item.category):""}
${escHtml(item.kind||"")}${item.description?" - "+escHtml(item.description):""}
${audio}
${type}${language}${gender} ${audioLink} Source page
${getVoice?``:""}
`}).join("")+(more>0?`

${more} more matches. Narrow the search or filters to see them.

`:"")}async function loadGetVoices(force=!1){if(initGetVoiceSourcesEditor(),_voiceSourcePayload&&!force){renderGetVoices();return}const urls=getEditableVoiceSourceUrls(),list=$("getvoices-list");if(!urls.length){list&&(list.innerHTML='

Add at least one source URL, then scrape again.

'),$("getvoices-status").textContent="No sources.";return}list&&(list.innerHTML=loadingMarkup("Scraping voice sources",`Fetching ${urls.length} source${urls.length===1?"":"s"} from the editable list.`,6)),$("getvoices-status").textContent="Scraping...",$("getvoices-refresh-btn").disabled=!0;try{const r=await fetch("/api/voice-sources",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({urls})});if(!r.ok){const text=await r.text().catch(()=>"");let message=r.statusText||`HTTP ${r.status}`;try{message=JSON.parse(text).detail||message}catch{text&&(message=text.slice(0,160))}throw new Error(message)}_voiceSourcePayload=await r.json(),renderGetVoices();const errors=(_voiceSourcePayload.errors||[]).length;$("getvoices-status").textContent=`${_voiceSourcePayload.total||0} found${errors?`, ${errors} source errors`:""}`}catch(e){list&&(list.innerHTML=`

Scrape failed: ${escHtml(e.message)}

Check that the TTS Voice Creator backend is restarted and that at least one source URL is reachable.

`),$("getvoices-status").textContent="Scrape failed",toast("Voice source scrape failed: "+e.message,"error")}finally{$("getvoices-refresh-btn").disabled=!1}}["getvoices-search","getvoices-source-filter","getvoices-language-filter","getvoices-gender-filter","getvoices-filetype-filter","getvoices-direct-only"].forEach(id=>{const el=$(id);el&&el.addEventListener(id==="getvoices-search"?"input":"change",renderGetVoices)}),(_b=$("getvoices-refresh-btn"))==null||_b.addEventListener("click",()=>loadGetVoices(!0)),(_c=$("getvoices-reset-sources-btn"))==null||_c.addEventListener("click",()=>{const box=$("getvoices-sources");box&&(box.value=sourceTextareaValueFromDefaults(),localStorage.setItem(VOICE_SOURCE_STORAGE_KEY,box.value),_voiceSourcePayload=null,renderGetVoices(),$("getvoices-status").textContent="Source list reset.")});const SOURCE_LANGUAGE_CODES={english:"EN",german:"DE",deutsch:"DE",french:"FR",spanish:"ES",japanese:"JA",korean:"KO",italian:"IT",portuguese:"PT",russian:"RU",arabic:"AR",polish:"PL",dutch:"NL",swedish:"SV",turkish:"TR",hindi:"HI",chinese:"ZH"};function sourceLanguageCode(language){const raw=String(language||"").trim();return/^[A-Z]{2}$/.test(raw)?raw:SOURCE_LANGUAGE_CODES[raw.toLowerCase()]||"EN"}function sourceGenderCode(gender,name=""){const text=`${gender||""} ${name||""}`.toLowerCase();return/female|woman|girl|\bf\b/.test(text)?"F":/male|man|boy|\bm\b/.test(text)?"M":"N"}function suggestedVoiceIdFromSourceName(name,language="EN",gender="N"){const base=String(name||"SourceVoice").normalize("NFKD").replace(/[\u0300-\u036f]/g,"").replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"").slice(0,48)||"SourceVoice";return`${language||"EN"}_${gender||"N"}_${base}`}function setLibAddSourcePreview(meta={}){const sourceBox=document.querySelector(".lib-add-source-box");if(!sourceBox)return;let preview=$("lib-add-source-preview");if(preview||(preview=document.createElement("div"),preview.id="lib-add-source-preview",preview.className="lib-add-source-preview",sourceBox.appendChild(preview)),!meta.name&&!meta.imageUrl){preview.classList.remove("open"),preview.innerHTML="";return}const image=meta.imageUrl?``:'
';preview.innerHTML=`${image}
${escHtml(meta.name||"Source voice")}${escHtml([meta.language,meta.gender,meta.kind].filter(Boolean).join(" \xB7 ")||"Source metadata will be saved with the voice")}
`,preview.classList.add("open")}async function getSourceVoiceInLibrary(meta){if(!meta.url){toast("This source has no direct audio URL","error");return}const lang=sourceLanguageCode(meta.language),gender=sourceGenderCode(meta.gender,meta.name),voiceId=suggestedVoiceIdFromSourceName(meta.name,lang,gender);toast(`Importing ${meta.name||"voice"}\u2026`,"info");try{const r=await fetch("/api/quick-import-voice",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({audio_url:meta.url,voice_id:voiceId,transcript:""})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();let srcTag="online";try{meta.pageUrl&&(srcTag=new URL(meta.pageUrl).hostname.replace(/^www\./,"").split(".")[0]||"online")}catch{}typeof saveMeta=="function"&&await saveMeta(d.voice_id,{name:meta.name||d.voice_id,tag:srcTag,group:srcTag,origin:"cloned",gender:(gender||"").toUpperCase(),note:(meta.description||"").slice(0,180)}).catch(()=>{}),meta.imageUrl&&await fetch("/api/voice/picture-url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:d.voice_id,image_url:meta.imageUrl})}).catch(()=>{}),_pendingSelectId=d.voice_id,navTo("s-voices"),_libraryLoadPromise=null,await loadVoiceLibrary(),toast(`Saved as ${d.voice_id}`,"success")}catch(e){toast("Import failed: "+e.message,"error")}}(_d=$("getvoices-list"))==null||_d.addEventListener("click",async e=>{const getBtn=e.target.closest(".get-source-voice");if(getBtn){await getSourceVoiceInLibrary({url:getBtn.dataset.url||"",name:getBtn.dataset.name||"",imageUrl:getBtn.dataset.image||"",language:getBtn.dataset.language||"",gender:getBtn.dataset.gender||"",kind:getBtn.dataset.kind||"",description:getBtn.dataset.description||"",pageUrl:getBtn.dataset.page||""});return}const btn=e.target.closest(".copy-source-url");btn&&(await copyText(btn.dataset.url||""),toast("Source URL copied","success"))}),function(){var _a2,_b2,_c2,_d2,_e2,_f2,_g2,_h2,_i2,_j2,_k2,_l2;const $2=id=>document.getElementById(id),grid=$2("fa-grid");if(!grid)return;let _page=1,_audio=null,_playingCard=null;const esc=s=>String(s==null?"":s).replace(/&/g,"&").replace(//g,">").replace(/"/g,""");function fmtCount(n){return n=+n||0,n>=1e6?(n/1e6).toFixed(1).replace(/\.0$/,"")+"M":n>=1e3?(n/1e3).toFixed(1).replace(/\.0$/,"")+"K":String(n)}const FLAG={EN:"\u{1F1EC}\u{1F1E7}",DE:"\u{1F1E9}\u{1F1EA}",FR:"\u{1F1EB}\u{1F1F7}",ES:"\u{1F1EA}\u{1F1F8}",IT:"\u{1F1EE}\u{1F1F9}",PT:"\u{1F1F5}\u{1F1F9}",NL:"\u{1F1F3}\u{1F1F1}",JA:"\u{1F1EF}\u{1F1F5}",ZH:"\u{1F1E8}\u{1F1F3}",KO:"\u{1F1F0}\u{1F1F7}",RU:"\u{1F1F7}\u{1F1FA}",AR:"\u{1F1F8}\u{1F1E6}",PL:"\u{1F1F5}\u{1F1F1}"};function status2(msg,show=!0){const bar=$2("fa-status-bar"),txt=$2("fa-status-text");bar&&(bar.hidden=!show),txt&&(txt.textContent=msg||"")}function activeVals(group){return[...document.querySelectorAll(`#fa-filter-pop .fa-pchips[data-group="${group}"] .fa-pchip.active`)].map(c=>c.dataset.val).filter(Boolean)}function filterCount(){var _a3;return activeVals("gender").length+activeVals("age").length+activeVals("tag").length+((_a3=$2("fa-tag"))!=null&&_a3.value.trim()?1:0)}function updateFilterBadge(){const n=filterCount(),b=$2("fa-filter-badge"),btn=$2("fa-filter-btn");b&&(b.textContent=String(n),b.hidden=n===0),btn==null||btn.classList.toggle("active",n>0)}function params(){var _a3,_b3,_c3,_d3;const p=new URLSearchParams({page:String(_page),page_size:"24"}),s=(_a3=$2("fa-search"))==null?void 0:_a3.value.trim();s&&p.set("search",s);const l=(_b3=$2("fa-lang"))==null?void 0:_b3.value;l&&p.set("language",l);const sort=(_c3=$2("fa-sort"))==null?void 0:_c3.value;sort&&p.set("sort_by",sort);const g=activeVals("gender")[0];g&&p.set("gender",g);const a=activeVals("age")[0];a&&p.set("age",a),activeVals("tag").forEach(t2=>p.append("tag",t2));const t=(_d3=$2("fa-tag"))==null?void 0:_d3.value.trim();return t&&p.append("tag",t),p}function card(v){const can=!!v.sample_audio,meta=[v.language?`${FLAG[v.language]||"\u{1F310}"} ${esc(v.language)}`:"",v.gender?`${esc(v.gender)}`:"",v.age?`${esc(v.age)}`:""].join(""),avatar=v.image?``:"";return`
${avatar} ${can?'':""}
${esc(v.title)||"Untitled"} ${v.author?`\xB7 ${esc(v.author)}`:""}
${esc(v.description||v.sample_text||"")}
${meta}
${fmtCount(v.plays)} ${fmtCount(v.likes)} ${can?'':""}
`}function render(data){const items=data.items||[];items.length?(grid.innerHTML=items.map(card).join(""),grid.querySelectorAll(".fa-card").forEach((el,i)=>{var _a3,_b3;const v=items[i];(_a3=el.querySelector(".fa-play"))==null||_a3.addEventListener("click",e=>{e.stopPropagation(),preview(v,el)}),(_b3=el.querySelector(".fa-import"))==null||_b3.addEventListener("click",e=>{e.stopPropagation(),importVoice(v,el)})})):grid.innerHTML='
No voices found \u2014 try a different search, language or filter.
';const pager=$2("fa-pager");if(pager){pager.hidden=!1,$2("fa-prev").disabled=_page<=1;const pages=Math.max(1,Math.ceil((data.total||0)/(data.page_size||24)));$2("fa-next").disabled=_page>=pages,$2("fa-pager-info").textContent=`Page ${_page} \xB7 ${(data.total||0).toLocaleString()} voices`}}async function browse(){status2("Loading voices from fish.audio\u2026"),grid.innerHTML='
Loading\u2026
';try{const d=await fetch("/api/fishaudio/voices?"+params().toString()).then(r=>{if(!r.ok)throw new Error("HTTP "+r.status);return r.json()});render(d),status2(d.offline?"\u26A0 fish.audio unreachable \u2014 showing cached results":`${(d.total||0).toLocaleString()} matching voices`,!!d.offline)}catch(e){grid.innerHTML=`
Failed to load: ${esc(e.message)}
`,status2("")}}function stopPreview(){_audio&&(_audio.pause(),_audio=null),_playingCard==null||_playingCard.classList.remove("fa-card-playing"),_playingCard=null}function preview(v,el){if(_playingCard===el){stopPreview();return}stopPreview(),v.sample_audio&&(_audio=new Audio(v.sample_audio),_playingCard=el,el.classList.add("fa-card-playing"),_audio.addEventListener("ended",stopPreview),_audio.play().catch(()=>{stopPreview(),typeof toast=="function"&&toast("Could not play preview","error")}))}async function importVoice(v,el){const btn=el.querySelector(".fa-import"),orig=btn.innerHTML;btn.disabled=!0,btn.innerHTML='';const lang=(v.language||"EN").slice(0,2).toUpperCase(),base=(v.title||"fishaudio").replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"").slice(0,40)||"Voice",voiceId=`${lang}_${base}`;try{const r=await fetch("/api/quick-import-voice",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:voiceId,audio_url:v.sample_audio,transcript:v.sample_text||v.default_text||""})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();typeof saveMeta=="function"&&await saveMeta(d.voice_id,{name:v.title||base,tag:"fish-audio",group:"fish-audio",origin:"cloned",gender:(v.gender||"").charAt(0).toUpperCase(),note:(v.description||"").slice(0,180)}).catch(()=>{}),v.image&&await fetch("/api/voice/picture-url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:d.voice_id,image_url:v.image})}).catch(()=>{}),btn.innerHTML=' Imported',el.classList.add("fa-card-imported"),typeof toast=="function"&&toast(`Imported "${v.title}" as ${d.voice_id}`,"success"),typeof loadVoiceLibrary=="function"&&loadVoiceLibrary().catch(()=>{})}catch(e){btn.disabled=!1,btn.innerHTML=orig,typeof toast=="function"&&toast("Import failed: "+e.message,"error")}}(_a2=$2("fa-fetch"))==null||_a2.addEventListener("click",()=>{_page=1,browse()}),(_b2=$2("fa-search"))==null||_b2.addEventListener("keydown",e=>{e.key==="Enter"&&(_page=1,browse())}),(_c2=$2("fa-tag"))==null||_c2.addEventListener("keydown",e=>{e.key==="Enter"&&(_page=1,browse(),applyFilters())}),(_d2=$2("fa-lang"))==null||_d2.addEventListener("change",()=>{_page=1,browse()}),(_e2=$2("fa-sort"))==null||_e2.addEventListener("change",()=>{_page=1,browse()}),(_f2=$2("fa-prev"))==null||_f2.addEventListener("click",()=>{_page>1&&(_page--,browse())}),(_g2=$2("fa-next"))==null||_g2.addEventListener("click",()=>{_page++,browse()});const pop=$2("fa-filter-pop");function positionPop(){const btn=$2("fa-filter-btn");if(!pop||!btn||pop.hidden)return;const r=btn.getBoundingClientRect(),w=pop.offsetWidth||320,margin=8;let left=Math.min(r.right-w,window.innerWidth-w-margin);left{e.stopPropagation(),openPop()}),(_i2=$2("fa-filter-close"))==null||_i2.addEventListener("click",e=>{e.stopPropagation(),openPop(!1)}),pop==null||pop.addEventListener("click",e=>e.stopPropagation()),document.addEventListener("click",()=>openPop(!1)),document.addEventListener("keydown",e=>{e.key==="Escape"&&openPop(!1)}),window.addEventListener("resize",positionPop),window.addEventListener("scroll",positionPop,!0),document.querySelectorAll("#fa-filter-pop .fa-pchip").forEach(chip=>{chip.addEventListener("click",()=>{const wrap=chip.closest(".fa-pchips");wrap.classList.contains("fa-pchips-multi")?chip.classList.toggle("active"):(wrap.querySelectorAll(".fa-pchip").forEach(c=>c.classList.remove("active")),chip.classList.add("active")),updateFilterBadge()})}),(_j2=$2("fa-tag"))==null||_j2.addEventListener("input",updateFilterBadge);function applyFilters(){_page=1,openPop(!1),browse()}(_k2=$2("fa-filter-apply"))==null||_k2.addEventListener("click",applyFilters),(_l2=$2("fa-filter-reset"))==null||_l2.addEventListener("click",()=>{document.querySelectorAll('#fa-filter-pop .fa-pchips[data-group="tag"] .fa-pchip').forEach(c=>c.classList.remove("active")),["gender","age"].forEach(g=>{const wrap=document.querySelector(`#fa-filter-pop .fa-pchips[data-group="${g}"]`);wrap==null||wrap.querySelectorAll(".fa-pchip").forEach((c,i)=>c.classList.toggle("active",i===0))}),$2("fa-tag")&&($2("fa-tag").value=""),updateFilterBadge(),applyFilters()})}(),function(){const tabs=document.getElementById("gvo-tabs");if(!tabs)return;const map={direct:"tab-getvoices",fish:"fa-browser-card",eleven:"el-browser-card"};function show(src){map[src]||(src="direct"),Object.entries(map).forEach(([k,id])=>{const el=document.getElementById(id);el&&(k===src?el.style.removeProperty("display"):el.style.setProperty("display","none","important"))}),tabs.querySelectorAll(".gvo-tab").forEach(t=>t.classList.toggle("active",t.dataset.src===src));try{localStorage.setItem("gvo-src",src)}catch{}}tabs.querySelectorAll(".gvo-tab").forEach(t=>t.addEventListener("click",()=>show(t.dataset.src)));let saved="direct";try{saved=localStorage.getItem("gvo-src")||"direct"}catch{}show(saved)}();function cleanBaseUrl(url){return String(url||"").trim().replace(/\/+$/,"")}function getTtsBaseUrl(){var _a2;return cleanBaseUrl((_a2=$("s-tts-url"))==null?void 0:_a2.value)||"http://localhost:8020"}function getTtsV1Url(){const base=getTtsBaseUrl();return base.endsWith("/v1")?base:base+"/v1"}function getTtsStreamBaseUrl(){var _a2;return cleanBaseUrl(((_a2=$("s-tts-stream-url"))==null?void 0:_a2.value)||_appSettings.tts_stream_url)||"http://localhost:8023"}function getTtsStreamV1Url(){const base=getTtsStreamBaseUrl();return base.endsWith("/v1")?base:base+"/v1"}function getCreatorV1Url(){const loc=window.location,protocol=loc.protocol||"http:",port=loc.port?":"+loc.port:"",host=loc.hostname==="0.0.0.0"?"localhost":loc.hostname;return`${protocol}//${host}${port}/v1`}function updateCreatorUrlHints(){const warning=$("routing-url-warning"),badBindHost=window.location.hostname==="0.0.0.0";warning&&warning.classList.toggle("show",badBindHost)}function activeVoiceIds(){return(_voices||[]).filter(v=>v.enabled!==!1).slice().sort((a,b)=>a.id.localeCompare(b.id)).map(v=>v.id)}function integrationVoiceExample(){return activeVoiceIds()[0]||"EN_F_ExampleVoice"}function integrationVoiceList(){const ids=activeVoiceIds();return ids.length?ids.join(", "):"EN_F_ExampleVoice, DE_M_ExampleVoice"}function virtualDesignVoiceIds(){return Object.keys(loadDesignPresets?loadDesignPresets():{}).sort((a,b)=>a.localeCompare(b)).map(name=>"vd_"+name.replace(/[^A-Za-z0-9_.-]+/g,"_").replace(/^_+|_+$/g,""))}function renderIntegrationSnippets(){if(!$("snippet-sillytavern"))return;const base=getTtsBaseUrl(),v1=getTtsV1Url(),streamV1=getTtsStreamV1Url(),proxyV1=getCreatorV1Url(),proxyBase=proxyV1.replace(/\/v1$/,"");updateCreatorUrlHints();const voice=integrationVoiceExample(),voices=integrationVoiceList(),vdVoices=virtualDesignVoiceIds(),vdVoice=vdVoices[0]||"vd_EN_F_Warm_Narrator";$("integration-url-label").textContent="TTS backend: "+base,$("snippet-sillytavern").textContent=`Provider: OpenAI compatible TTS API base URL: ${v1} API key: dummy Model: qwen3-tts Voice: ${voice} Custom voices: ${voices} Streaming backend, if your SillyTavern TTS extension supports progressive playback: API base URL: ${streamV1} Endpoint: /audio/speech Format: wav`,$("snippet-streaming-howto").textContent=`Direct streaming backend, no creator routing: API base URL: ${streamV1} Endpoint: /audio/speech Model: tts-1 Voice: ${voice} Response format: wav Requirement: the app must start playback while the HTTP response is still arriving. Current 8023 streaming service ignores per-request instruct/style text. Streaming through TTS Voice Creator routing: API base URL: ${proxyV1} Voice: default or another incoming route voice Routing tab: set Backend = Streaming for the matching rule Response format: wav Avoid before/after sounds for true streaming; route sounds and MP3 require buffering. SillyTavern note: Use OpenAI-compatible TTS if your extension supports progressive audio responses. If it waits for the whole file before playing, streaming works technically but will feel like buffered TTS.`,$("snippet-open-webui").textContent=`Admin settings -> Audio -> Text-to-Speech Engine/provider: OpenAI compatible API base URL: ${proxyV1} API key: dummy Model: tts-1 Voice: default Routing tab example: default + EN -> ${voice} default + DE -> DE_M_YourGermanVoice`,$("snippet-home-assistant").textContent=`Home Assistant OpenAI TTS agent: Base URL: ${proxyV1} API key: dummy Model: tts-1 Voice: default Extra JSON payload: {"app":"Home Assistant"} Audio format: mp3 or wav REST command example using routing: rest_command: routed_tts: url: "${proxyV1}/audio/speech" method: POST content_type: "application/json" headers: Authorization: "Bearer dummy" payload: > {"model":"tts-1","voice":"default","input":"{{ text }}","response_format":"mp3","app":"Home Assistant"} Important: use this Creator proxy URL, not the direct Qwen3 backend URL ${v1}. Routing only runs through the Creator proxy.`,$("snippet-curl").textContent=`curl -s "${v1}/models" curl -s "${v1}/audio/speech" \\ -H "Authorization: Bearer dummy" \\ -H "Content-Type: application/json" \\ -d '{"model":"qwen3-tts","voice":"${voice}","input":"Hello from Qwen3 TTS","response_format":"wav"}' \\ --output qwen3-tts-test.wav`,$("snippet-voice-design-proxy").textContent=`Virtual VoiceDesign mode - no WAV export needed Use this app as the OpenAI-compatible TTS endpoint: API base URL: ${proxyV1} API key: dummy Model: tts-1 Voice: ${vdVoice} Available virtual voices: ${vdVoices.length?vdVoices.join(", "):"Save a Voice Design prompt preset first."} curl -s "${proxyV1}/models" curl -s "${proxyV1}/audio/speech" \\ -H "Authorization: Bearer dummy" \\ -H "Content-Type: application/json" \\ -d '{"model":"tts-1","voice":"${vdVoice}","input":"This line is generated through the VoiceDesign container.","response_format":"wav"}' \\ --output voicedesign-virtual.wav`,$("snippet-mcp-claude-cmd")&&($("snippet-mcp-claude-cmd").textContent=`claude mcp add voice-creator \\ --transport http \\ --url ${proxyBase}/mcp \\ --header "X-Voice-Creator-Client-Id: claude-code"`),$("snippet-mcp-claude-config")&&($("snippet-mcp-claude-config").textContent=`{ "mcpServers": { "voice-creator": { "url": "${proxyBase}/mcp", "headers": { "X-Voice-Creator-Client-Id": "my-agent" } } } }`),$("snippet-hotkey")&&($("snippet-hotkey").textContent=`pip install pynput sounddevice soundfile pyperclip requests python hotkey_daemon.py --server ${proxyBase}`),$("snippet-speak")&&($("snippet-speak").textContent=`# Bind your client ID to a voice once (persisted in Settings) curl -X PUT ${proxyBase}/speak/bindings/my-script \\ -H "Content-Type: application/json" \\ -d '{"voice":"${voice}"}' # Generate speech \u2014 voice from binding, persona rewrite optional curl -X POST ${proxyBase}/speak \\ -H "Content-Type: application/json" \\ -H "X-Voice-Creator-Client-Id: my-script" \\ -d '{"text":"Hello world","apply_persona":true}' \\ --output speech.wav # Or specify voice directly curl -X POST ${proxyBase}/speak \\ -H "Content-Type: application/json" \\ -d '{"text":"Hello world","voice":"${voice}"}' \\ --output speech.wav`)}document.querySelectorAll(".copy-snippet").forEach(btn=>btn.addEventListener("click",async()=>{const el=$(btn.dataset.snippet);el&&(await copyText(el.textContent),toast("Snippet copied","success"))})),(_e=$("show-api-btn"))==null||_e.addEventListener("click",()=>{window.open("/docs","_blank","noopener")}),(_f=$("integration-refresh-btn"))==null||_f.addEventListener("click",()=>{renderIntegrationSnippets(),toast("Integration examples refreshed","success")}),(_g=$("copy-active-voices-btn-integrations"))==null||_g.addEventListener("click",async()=>{const active=activeVoiceIds();if(!active.length){toast("No active voices to copy","error");return}await copyText(active.join(", ")),toast("Copied "+active.length+" active voices","success"),status("Copied active voices to clipboard")});let _ttsRoutes=[];const ROUTE_LANGS=[["*","Any"],["AUTO","Auto"],["EN","English"],["DE","German"],["FR","French"],["ES","Spanish"],["IT","Italian"],["PT","Portuguese"],["NL","Dutch"],["PL","Polish"]];function routeSelectOptions(value){return ROUTE_LANGS.map(([code,label])=>``).join("")}const ROUTE_BACKENDS=[["voice_clone","Voice Clone"],["streaming","Streaming"],["voice_design","Voice Design"],["nvidia_magpie","NVIDIA Magpie"],["nvidia_zeroshot","NVIDIA Zeroshot"],["nvidia_flow","NVIDIA Flow"]];let _routeSounds=[];function routeSoundOptions(value=""){const current=String(value||""),listed=new Set(_routeSounds.map(s=>String(s.path||""))),options=[''];return current&&!listed.has(current)&&options.push(``),_routeSounds.forEach(sound=>{const path=String(sound.path||"");if(!path)return;const duration=sound.duration!=null?` \xB7 ${Number(sound.duration).toFixed(1)}s`:"";options.push(``)}),options.join("")}async function loadRouteSounds(){try{const r=await fetch("/api/route-sounds");if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();_routeSounds=Array.isArray(d.sounds)?d.sounds:[]}catch{_routeSounds=[]}}let _routeSoundPickerTarget=null,_routeSoundPlayingButton=null;function routeSoundUrl(path){return"/api/route-sounds/file/"+String(path||"").split("/").map(encodeURIComponent).join("/")}function setRouteSoundField(row,target,path){const field=row==null?void 0:row.querySelector(target==="before"?".route-before-sound":".route-after-sound");field&&(field.value=path||"",readRoutingForm())}function renderRouteSoundBrowser(){var _a2;const list=$("routing-sound-list");if(!list)return;const query=String(((_a2=$("routing-sound-search"))==null?void 0:_a2.value)||"").trim().toLowerCase(),sounds=query?_routeSounds.filter(sound=>String(sound.path||"").toLowerCase().includes(query)||String(sound.name||"").toLowerCase().includes(query)):_routeSounds;if(!_routeSounds.length){list.innerHTML='
No sounds found yet. Upload one in a route row; only the selected file is imported.
';return}if(!sounds.length){list.innerHTML='
No sounds match this search.
';return}list.innerHTML=sounds.map(sound=>{const path=String(sound.path||""),size=sound.size!=null?Math.max(1,Math.round(Number(sound.size)/1024))+" KB":"",duration=sound.duration!=null?Number(sound.duration).toFixed(1)+"s":size,type=sound.type?String(sound.type).toUpperCase():"",targetLabel=(_routeSoundPickerTarget==null?void 0:_routeSoundPickerTarget.target)==="after"?"Use after":"Use before";return`
${escHtml(path)}
${escHtml(duration||"-")} ${type?"\xB7 "+escHtml(type):""}
`}).join("")}async function openRouteSoundBrowser(row,target){_routeSoundPickerTarget={row,target},await loadRouteSounds(),renderRouteSoundBrowser();const panel=$("routing-sound-browser");panel&&(panel.hidden=!1,panel.scrollIntoView({block:"nearest",behavior:"smooth"}));const label=target==="before"?"before sound":"after sound",note=$("routing-sound-browser-note");note&&(note.textContent=`Preview uploaded route sounds, then choose one for this ${label}. Upload imports only the selected file; this list also shows sounds already present in the sounds folders. ${_routeSounds.length} sounds available.`)}function closeRouteSoundBrowser(){const panel=$("routing-sound-browser");panel&&(panel.hidden=!0);const audio=$("routing-sound-preview");audio&&(audio.pause(),audio.hidden=!0,audio.removeAttribute("src")),_routeSoundPlayingButton&&(_routeSoundPlayingButton.textContent="\u25B6"),_routeSoundPlayingButton=null,_routeSoundPickerTarget=null}function playRouteSound(path,btn){const audio=$("routing-sound-preview");!audio||!path||(_routeSoundPlayingButton&&_routeSoundPlayingButton!==btn&&(_routeSoundPlayingButton.textContent="\u25B6"),_routeSoundPlayingButton=btn,btn.textContent="\u275A\u275A",audio.hidden=!1,audio.src=routeSoundUrl(path),audio.onended=()=>{btn.textContent="\u25B6"},audio.onpause=()=>{_routeSoundPlayingButton===btn&&(btn.textContent="\u25B6")},audio.onplay=()=>{btn.textContent="\u275A\u275A"},audio.play().catch(e=>{btn.textContent="\u25B6",toast("Sound preview failed: "+e.message,"error")}))}function useRouteSound(path,target){const selected=_routeSoundPickerTarget||{},row=selected.row||document.querySelector(".routing-row"),useTarget=target||selected.target||"before";setRouteSoundField(row,useTarget,path),toast(`${useTarget==="before"?"Before":"After"} sound selected`,"success")}function routeBackendOptions(value){const current=value||"voice_clone";return ROUTE_BACKENDS.map(([code,label])=>``).join("")}function refreshRoutingVoiceOptions(){const dl=$("routing-voice-options");if(dl){const ids=[...activeVoiceIds(),...virtualDesignVoiceIds()];dl.innerHTML=[...new Set(ids)].map(id=>``).join("")}const soundsDl=$("routing-sound-options");soundsDl&&(soundsDl.innerHTML=_routeSounds.map(sound=>``).join(""))}function newRoute(app="Open WebUI",inputVoice="default",language="*",outputVoice=""){return{id:"route_"+Date.now().toString(36)+"_"+Math.random().toString(36).slice(2,6),enabled:!0,app,input_voice:inputVoice,language,backend:"voice_clone",output_voice:outputVoice,before_sound:"",after_sound:""}}function renderRoutingList(){if($("routing-list")){if(refreshRoutingVoiceOptions(),$("routing-proxy-url").textContent=getCreatorV1Url(),updateCreatorUrlHints(),$("routing-status").textContent=_ttsRoutes.length?`${_ttsRoutes.length} route${_ttsRoutes.length===1?"":"s"}`:"No routes yet.",!_ttsRoutes.length){$("routing-list").innerHTML='
No routing rules yet. Add a route or add the Open WebUI default examples.
';return}$("routing-list").innerHTML=_ttsRoutes.map((r,i)=>`
`).join(""),typeof VoicePicker!="undefined"&&document.querySelectorAll(".route-output").forEach(inp=>VoicePicker.attachTextPicker(inp))}}function readRoutingForm(){_ttsRoutes=[...document.querySelectorAll(".routing-row")].map((row,i)=>({id:(_ttsRoutes[Number(row.dataset.index)]||{}).id||`route_${i+1}`,enabled:row.querySelector(".route-enabled").checked,app:row.querySelector(".route-app").value.trim()||"*",input_voice:row.querySelector(".route-input").value.trim()||"default",language:row.querySelector(".route-lang").value||"*",backend:row.querySelector(".route-backend").value||"voice_clone",output_voice:row.querySelector(".route-output").value.trim(),before_sound:row.querySelector(".route-before-sound").value.trim(),after_sound:row.querySelector(".route-after-sound").value.trim()}))}async function loadRoutingTab(){if($("routing-list")){$("routing-proxy-url").textContent=getCreatorV1Url(),updateCreatorUrlHints(),$("routing-status").textContent="Loading routing\u2026",$("routing-list").innerHTML=loadingMarkup("Loading routing","Loading active voices and routing rules for the proxy.",5),setBusyButton("routing-refresh-btn",!0),await Promise.allSettled([_voices.length?Promise.resolve():loadVoiceLibrary().catch(()=>{}),loadRouteSounds()]);try{const r=await fetch("/api/tts-routes");if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();_ttsRoutes=Array.isArray(d.routes)?d.routes:[],renderRoutingList(),status("Routing loaded"),loadRoutingLog()}catch(e){$("routing-status").textContent="Load failed",$("routing-list").innerHTML=`
Failed to load routes: ${escHtml(e.message)}
`,status("Routing load failed")}finally{setBusyButton("routing-refresh-btn",!1)}}}async function saveRoutingTab(){readRoutingForm(),$("routing-save-btn").disabled=!0;try{const r=await fetch("/api/tts-routes",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({routes:_ttsRoutes})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}_ttsRoutes=(await r.json()).routes||_ttsRoutes,renderRoutingList(),toast("Routing saved","success"),status("TTS routing saved")}catch(e){toast("Save routes failed: "+e.message,"error")}finally{$("routing-save-btn").disabled=!1}}function renderRouteTestResult(d){const el=$("routing-test-result");if(!el)return;const health=d.voice_health||{},warnings=Array.isArray(health.warnings)?health.warnings:[];el.className="routing-test-result "+(warnings.length?"warn":"ok");const parts=[`${escHtml(d.requested_voice||"")} \u2192 ${escHtml(d.routed_voice||"")}`,`app ${escHtml(d.app||"-")}`,`backend ${escHtml(d.backend||"voice_clone")}`,`language ${escHtml(d.detected_language||"-")}`,d.matched?"matched route":"no route matched"];health.duration&&parts.push(`reference ${health.duration}s`),health.word_count!=null&&parts.push(`${health.word_count} words`),health.words_per_sec&&parts.push(`${health.words_per_sec} words/s`),warnings.length&&parts.push("Warning: "+warnings.map(escHtml).join("; "));const sounds=d.sounds||{};for(const[key,sound]of Object.entries(sounds)){const label=key==="before_sound"?"before sound":"after sound";parts.push(sound.ok?`${label} OK`:`${label}: ${escHtml(sound.error||"not found")}`),sound.ok||(el.className="routing-test-result warn")}el.innerHTML=parts.join(" \xB7 ")}function routingLogTime(ts){if(!ts)return"--:--:--";const d=new Date(ts);return Number.isNaN(d.getTime())?String(ts).slice(11,19)||"--:--:--":d.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"})}function routingLogBadge(item){const status2=String(item.status||item.kind||"log");return item.kind==="test"&&status2==="matched"?"test ok":item.kind==="test"&&status2==="no_match"?"test miss":status2.replace(/_/g," ")}function routingLogMeta(item){const parts=[];return item.backend&&parts.push(item.backend),item.language&&parts.push("lang "+item.language),item.response_format&&parts.push(item.response_format),item.duration!=null&&parts.push(Number(item.duration).toFixed(2)+"s"),item.bytes!=null&&parts.push(Math.round(Number(item.bytes)/1024)+" KB"),item.sounds&&Array.isArray(item.sounds)&&item.sounds.length&&parts.push("sounds "+item.sounds.join(",")),item.route_id&&parts.push("route "+item.route_id),item.client&&parts.push(item.client),parts.join(" \xB7 ")}let _routingLogItems=[],_routingLogFilter="all";function routingLogPassesFilter(item){const status2=String((item==null?void 0:item.status)||"").toLowerCase(),isError=status2==="error"||!!(item!=null&&item.error),isNoMatch=status2==="no_match"||(item==null?void 0:item.matched)===!1;return _routingLogFilter==="error"?isError:_routingLogFilter==="no_match"?isNoMatch:_routingLogFilter==="attention"?isError||isNoMatch:!0}function renderCurrentRoutingLog(){renderRoutingLog(_routingLogItems.filter(routingLogPassesFilter))}function renderRoutingLog(items=[]){const el=$("routing-log-list");if(el){if(!items.length){const filtered=_routingLogItems.length&&_routingLogFilter!=="all";el.innerHTML=`
${filtered?"No routing log entries match this filter.":"No routing log entries yet. Test a route or send a TTS request through the Creator proxy."}
`;return}el.innerHTML=items.map(item=>{const status2=String(item.status||"log").replace(/[^a-z0-9_-]/gi,"_"),requested=item.requested_voice||"-",routed=item.routed_voice||"-",voice=requested===routed?requested:`${requested} \u2192 ${routed}`,text=item.error?`Error: ${item.error}`:item.text_preview||"";return`
${escHtml(routingLogTime(item.ts))}
${escHtml(routingLogBadge(item))}
${escHtml(item.app||"-")}
${escHtml(voice)}
${escHtml(routingLogMeta(item)||"-")}
${escHtml(text||"-")}
`}).join("")}}async function loadRoutingLog(){const el=$("routing-log-list");if(el)try{const r=await fetch("/api/tts-routing-log?limit=80");if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();_routingLogItems=Array.isArray(d.items)?d.items:[],renderCurrentRoutingLog()}catch(e){el.innerHTML=`
Routing log unavailable: ${escHtml(e.message)}
`}}async function clearRoutingLog(){const btn=$("routing-log-clear-btn");btn&&(btn.disabled=!0);try{const r=await fetch("/api/tts-routing-log",{method:"DELETE"});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}_routingLogItems=[],renderRoutingLog([]),toast("Routing log cleared","success")}catch(e){toast("Clear log failed: "+e.message,"error")}finally{btn&&(btn.disabled=!1)}}async function testRouting(){readRoutingForm();const btn=$("routing-test-btn"),el=$("routing-test-result");btn.disabled=!0,el.className="routing-test-result",el.textContent="Testing route...";try{const r=await fetch("/api/tts-route-test",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app:$("routing-test-app").value.trim()||"Open WebUI",voice:$("routing-test-voice").value.trim()||"default",input:$("routing-test-text").value.trim()})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}renderRouteTestResult(await r.json()),loadRoutingLog()}catch(e){el.className="routing-test-result warn",el.textContent="Route test failed: "+e.message}finally{btn.disabled=!1}}async function uploadRouteSoundForRow(row,target){const input=document.createElement("input");input.type="file",input.accept="audio/*",input.multiple=!1,input.onchange=async()=>{if(!input.files||!input.files.length)return;const btn=row.querySelector(`.route-sound-upload[data-target="${target}"]`),field=row.querySelector(target==="before"?".route-before-sound":".route-after-sound");btn&&(btn.disabled=!0);try{const fd=new FormData;fd.append("file",input.files[0]);const r=await fetch("/api/route-sounds/upload",{method:"POST",body:fd});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();field.value=d.path||"",await loadRouteSounds(),readRoutingForm(),renderRoutingList(),toast(`${target==="before"?"Before":"After"} sound uploaded`,"success"),status(`Uploaded route sound: ${d.path}`)}catch(e){toast("Sound upload failed: "+e.message,"error"),status("Sound upload failed")}finally{btn&&(btn.disabled=!1)}},input.click()}(_h=$("routing-refresh-btn"))==null||_h.addEventListener("click",loadRoutingTab),(_i=$("routing-add-btn"))==null||_i.addEventListener("click",()=>{readRoutingForm(),_ttsRoutes.push(newRoute()),renderRoutingList()}),(_j=$("routing-add-openwebui-btn"))==null||_j.addEventListener("click",()=>{readRoutingForm();const voices=activeVoiceIds(),firstByLang=lang=>voices.find(v=>v.toUpperCase().startsWith(lang+"_"))||"";_ttsRoutes.push(newRoute("Open WebUI","default","EN",firstByLang("EN"))),_ttsRoutes.push(newRoute("Open WebUI","default","DE",firstByLang("DE"))),renderRoutingList()}),(_k=$("routing-save-btn"))==null||_k.addEventListener("click",saveRoutingTab),(_l=$("routing-test-btn"))==null||_l.addEventListener("click",testRouting),(_m=$("routing-log-refresh-btn"))==null||_m.addEventListener("click",loadRoutingLog),(_n=$("routing-log-clear-btn"))==null||_n.addEventListener("click",clearRoutingLog),(_o=$("routing-log-filter"))==null||_o.addEventListener("change",e=>{_routingLogFilter=e.target.value||"all",renderCurrentRoutingLog()}),(_p=$("routing-list"))==null||_p.addEventListener("change",e=>{const picker=e.target.closest(".route-sound-picker");if(!picker)return;const field=picker.closest(".routing-row").querySelector(picker.dataset.target==="before"?".route-before-sound":".route-after-sound");field&&(field.value=picker.value||""),readRoutingForm()}),(_q=$("routing-list"))==null||_q.addEventListener("click",e=>{const pickBtn=e.target.closest(".route-sound-pick");if(pickBtn){const row2=pickBtn.closest(".routing-row");openRouteSoundBrowser(row2,pickBtn.dataset.target);return}const uploadBtn=e.target.closest(".route-sound-upload");if(uploadBtn){const row2=uploadBtn.closest(".routing-row");uploadRouteSoundForRow(row2,uploadBtn.dataset.target);return}const btn=e.target.closest(".routing-delete");if(!btn)return;readRoutingForm();const row=btn.closest(".routing-row");_ttsRoutes.splice(Number(row.dataset.index),1),renderRoutingList()}),(_r=$("routing-sound-search"))==null||_r.addEventListener("input",debounce(renderRouteSoundBrowser,120)),(_s=$("routing-sound-refresh-btn"))==null||_s.addEventListener("click",async()=>{await loadRouteSounds(),renderRouteSoundBrowser()}),(_t=$("routing-sound-close-btn"))==null||_t.addEventListener("click",closeRouteSoundBrowser),(_u=$("routing-sound-list"))==null||_u.addEventListener("click",e=>{const item=e.target.closest(".routing-sound-item");if(!item)return;const path=item.dataset.path||"",playBtn=e.target.closest(".sound-play");if(playBtn){playRouteSound(path,playBtn);return}e.target.closest(".sound-use-current")&&useRouteSound(path,(_routeSoundPickerTarget==null?void 0:_routeSoundPickerTarget.target)||"before")});const CLONE_SAMPLE_TEXTS={EN:"Hello! My name is Sam, and this is my voice. I can speak softly or with great strength. The crisp winter air, warm firelight, and the gentle sound of rain \u2014 these are the things I love. Can you hear how clearly I speak?",DE:"Hallo! Ich hei\xDFe Alex und das ist meine Stimme. Ich kann leise fl\xFCstern oder mit voller Kraft sprechen. Klare Winterluft, warmes Kerzenlicht und der Klang des Regens am Fenster \u2014 das liebe ich. H\xF6rst du, wie deutlich ich spreche?",IT:"Ciao! Mi chiamo Marco e questa \xE8 la mia voce. Posso parlare dolcemente o con grande forza. L'aria fresca d'inverno, la luce calda del fuoco e il suono della pioggia \u2014 queste sono le cose che amo. Senti come parlo chiaramente?",ES:"\xA1Hola! Me llamo Carlos y esta es mi voz. Puedo hablar suavemente o con gran fuerza. El aire fr\xEDo del invierno, la c\xE1lida luz del fuego y el suave sonido de la lluvia \u2014 estas son las cosas que amo. \xBFPuedes o\xEDr lo claramente que hablo?",FR:"Bonjour ! Je m'appelle Sophie et voici ma voix. Je peux parler doucement ou avec grande force. L'air vif de l'hiver, la douce lumi\xE8re du feu et le son de la pluie \u2014 voil\xE0 ce que j'aime. Entends-tu comme je parle clairement ?",PT:"Ol\xE1! Meu nome \xE9 Ana e esta \xE9 a minha voz. Posso falar suavemente ou com grande for\xE7a. O ar fresco do inverno, a luz quente do fogo e o som da chuva \u2014 estas s\xE3o as coisas que amo. Consegues ouvir como falo claramente?",NL:"Hoi! Mijn naam is Laura en dit is mijn stem. Ik kan zacht fluisteren of met volle kracht spreken. De frisse winterlucht, het warme kaarslicht en het geluid van de regen \u2014 dat zijn de dingen die ik liefheb. Hoor je hoe helder ik spreek?",PL:"Cze\u015B\u0107! Mam na imi\u0119 Anna i to jest m\xF3j g\u0142os. Mog\u0119 m\xF3wi\u0107 cicho lub z ca\u0142\u0105 moc\u0105. Mro\u017Ane zimowe powietrze, ciep\u0142e \u015Bwiat\u0142o ognia i d\u017Awi\u0119k deszczu za oknem \u2014 to s\u0105 rzeczy, kt\xF3re kocham. Czy s\u0142yszysz, jak wyra\u017Anie m\xF3wi\u0119?"},CLONE_SAMPLE_NAMES={EN:"Sam",DE:"Alex",IT:"Marco",ES:"Carlos",FR:"Sophie",PT:"Ana",NL:"Laura",PL:"Anna"};function cloneSampleForLang(lang){var _a2;const txt=$("clone-sample-text"),def=CLONE_SAMPLE_NAMES[lang]||"";let t=CLONE_SAMPLE_TEXTS[lang]||CLONE_SAMPLE_TEXTS.EN;const name=(((_a2=$("clone-your-name"))==null?void 0:_a2.value)||"").trim();name&&def&&(t=t.replace(new RegExp("\\b"+def.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"\\b"),name)),txt&&(txt.value=t,txt.dataset.sampleName=name||def)}window.initCloneSampleText=function(){const sel=$("clone-sample-lang"),txt=$("clone-sample-text");!sel||!txt||(cloneSampleForLang(sel.value),sel._cloneSampleBound||(sel.addEventListener("change",()=>cloneSampleForLang(sel.value)),sel._cloneSampleBound=!0))},initCloneSampleText();let ws=null,wsRegions=null,currentFileId=null,trimmedFileId=null,designedFileId=null,editingVoiceId=null,editingVoicePath=null;function initWaveSurfer(){ws&&(ws.destroy(),ws=null,wsRegions=null),wsRegions=WaveSurfer.Regions.create(),ws=WaveSurfer.create({container:"#waveform",waveColor:"#45475a",progressColor:"#89b4fa",cursorColor:"#cba6f7",height:90,normalize:!0,plugins:[wsRegions]}),ws.on("ready",()=>{const dur=ws.getDuration();$("trim-end").value=dur.toFixed(2),$("trim-end").max=dur.toFixed(2),$("trim-start").max=dur.toFixed(2),updateRegion()}),wsRegions.on("region-updated",r=>{$("trim-start").value=r.start.toFixed(2),$("trim-end").value=r.end.toFixed(2),updateDurationLabel()})}function updateRegion(){wsRegions.clearRegions();const s=parseFloat($("trim-start").value)||0,e=parseFloat($("trim-end").value)||(ws?ws.getDuration():0);wsRegions.addRegion({start:s,end:e,color:"rgba(137,180,250,0.25)",drag:!0,resize:!0}),updateDurationLabel()}function updateDurationLabel(){const d=Math.max(0,(parseFloat($("trim-end").value)||0)-(parseFloat($("trim-start").value)||0)),el=$("trim-duration");el.textContent=d.toFixed(1)+" s",el.className=d>=5&&d<=20?"dur-ok":d>20?"dur-warn":"dur-bad"}["trim-start","trim-end"].forEach(id=>$(id).addEventListener("input",()=>{ws&&updateRegion()})),$("play-btn").addEventListener("click",()=>{ws&&ws.playPause()}),$("play-selection-btn").addEventListener("click",()=>{ws&&ws.play(parseFloat($("trim-start").value)||0,parseFloat($("trim-end").value)||ws.getDuration())}),$("auto-trim-btn").addEventListener("click",async()=>{if(!currentFileId){toast("No audio loaded","error");return}$("auto-trim-btn").disabled=!0,status("Finding best TTS reference segment\u2026");try{const r=await fetch("/api/auto-trim",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:currentFileId})});let d;if(r.ok)d=await r.json();else if(r.status===404||r.status===405)status("Backend auto trim unavailable; analysing audio in browser\u2026"),d=await clientAutoTrimBounds(currentFileId);else{const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText||"Auto trim failed")}$("trim-start").value=Number(d.start).toFixed(2),$("trim-end").value=Number(d.end).toFixed(2),ws&&updateRegion(),toast("Auto trim set: "+Number(d.duration).toFixed(1)+" s","success"),status(d.reason||"Auto trim ready")}catch(e){toast("Auto trim failed: "+e.message,"error"),status("Auto trim failed")}finally{$("auto-trim-btn").disabled=!1}});function loadAudioId(id,dur,opts={}){currentFileId=id,trimmedFileId=null,designedFileId=null,editingVoiceId=opts.editingVoiceId||null,editingVoicePath=opts.editingVoicePath||null,$("trim-start").value="0",$("trim-end").value=dur.toFixed(2),$("waveform-card").style.display="",initWaveSurfer(),ws.load("/api/audio/"+id),$("save-result").style.display="none",$("trim-audio").style.display="none",$("no-audio-hint").style.display="",editingVoiceId&&($("voice-id-input").value=editingVoiceId,$("voice-id-input").dispatchEvent(new Event("input")),$("transcript-area").value=opts.transcript||"",status("Editing existing voice: "+editingVoiceId))}const dropZone=$("drop-zone"),fileInput=$("file-input");dropZone.addEventListener("click",()=>fileInput.click()),dropZone.addEventListener("dragover",e=>{e.preventDefault(),dropZone.classList.add("drag-over")}),dropZone.addEventListener("dragleave",()=>dropZone.classList.remove("drag-over")),dropZone.addEventListener("drop",e=>{e.preventDefault(),dropZone.classList.remove("drag-over"),e.dataTransfer.files.length&&uploadFile(e.dataTransfer.files[0])}),fileInput.addEventListener("change",()=>{fileInput.files.length&&uploadFile(fileInput.files[0])});async function uploadFile(file){status("Uploading "+file.name+"\u2026");const fd=new FormData;fd.append("file",file);try{const r=await fetch("/api/upload",{method:"POST",body:fd});if(!r.ok){const e=await r.json();throw new Error(e.detail||r.statusText)}const d=await r.json();loadAudioId(d.id,d.duration),status("Loaded: "+file.name+" ("+d.duration.toFixed(1)+" s)"),toast("File loaded","success")}catch(e){toast("Upload failed: "+e.message,"error"),status("Upload failed")}}async function loadLibraryVoiceAudio(v){const audioResp=await fetch(voiceFileUrl(v),{cache:"no-store"});if(!audioResp.ok){const e=await audioResp.json().catch(()=>({}));throw new Error(e.detail||audioResp.statusText)}const blob=await audioResp.blob(),ext=(v.file_type||"wav").toLowerCase(),fd=new FormData;fd.append("file",new File([blob],`${v.id}.${ext}`,{type:blob.type||"audio/wav"}));const upload=await fetch("/api/upload",{method:"POST",body:fd});if(!upload.ok){const e=await upload.json().catch(()=>({}));throw new Error(e.detail||upload.statusText)}const d=await upload.json();return{id:d.id,voice_id:v.id,duration:d.duration,transcript:v.transcript||"",file_type:ext,path:v.path}}$("yt-btn").addEventListener("click",()=>{const url=$("yt-url").value.trim();if(!url)return;$("yt-btn").disabled=!0,$("yt-progress").textContent="Starting download\u2026";const es=new EventSource("/api/download-yt?url="+encodeURIComponent(url));es.onmessage=e=>{const d=JSON.parse(e.data);d.error?(toast("Download failed: "+d.error,"error"),$("yt-progress").textContent=d.error,$("yt-btn").disabled=!1,es.close()):d.done?(es.close(),$("yt-btn").disabled=!1,$("yt-progress").textContent="Done!",loadAudioId(d.id,d.duration),toast("YouTube audio loaded","success")):($("yt-progress").textContent=d.msg||"",d.pct&&status("Downloading\u2026 "+d.pct+"%"))},es.onerror=()=>{es.close(),$("yt-btn").disabled=!1}});const RAW_MIC_CONSTRAINTS={echoCancellation:!1,noiseSuppression:!1,autoGainControl:!1};async function visibleMicrophoneCount(){var _a2;if(!((_a2=navigator.mediaDevices)!=null&&_a2.enumerateDevices))return null;try{return(await navigator.mediaDevices.enumerateDevices()).filter(device=>device.kind==="audioinput").length}catch{return null}}async function microphoneErrorMessage(error){const name=(error==null?void 0:error.name)||"",message=(error==null?void 0:error.message)||"",lowerMessage=message.toLowerCase(),micCount=await visibleMicrophoneCount();return name==="NotFoundError"||lowerMessage.includes("requested device not found")?micCount===0?"No microphone is visible to this browser. Connect or enable an input device in your OS/browser settings, then reload.":"The browser can see a microphone, but cannot open the selected/default input. Check the site permission and OS input selection, then reload.":name==="NotAllowedError"||name==="PermissionDeniedError"?"Microphone permission is blocked for this site. Allow microphone access in the address bar, then reload.":name==="NotReadableError"?"The microphone is busy or unavailable. Close other apps using it, then try again.":name==="SecurityError"?"Microphone access requires localhost or HTTPS.":message||"Microphone failed."}async function requestMicrophoneStream(options={}){var _a2;if(!((_a2=navigator.mediaDevices)!=null&&_a2.getUserMedia))throw new Error("Microphone requires HTTPS. Open the app via https://... or access it on localhost.");if(!options.raw)return navigator.mediaDevices.getUserMedia({audio:!0});try{return await navigator.mediaDevices.getUserMedia({audio:RAW_MIC_CONSTRAINTS})}catch(e){if((e==null?void 0:e.name)==="OverconstrainedError"||(e==null?void 0:e.name)==="NotFoundError")return navigator.mediaDevices.getUserMedia({audio:!0});throw e}}let _cloneMonState={stream:null,recordStream:null,audioCtx:null,sourceNode:null,gainNode:null,analyser:null,meterRaf:null,waveRing:null,monitoring:!1};function _cloneRenderMeter(level=0,db=-1/0,clipped=!1){const meter=$("clone-mic-meter");if(!meter)return;if(!meter.children.length)for(let i=0;i<18;i++){const b=document.createElement("div");b.className="bar",meter.appendChild(b)}const active=Math.round(Math.max(0,Math.min(1,level))*meter.children.length);[...meter.children].forEach((bar,i)=>{bar.className="bar",bar.style.height=7+Math.min(i,active)*1.55+"px",i-12&&i>11&&bar.classList.add("hot"),clipped&&i>14&&bar.classList.add("clip"))});const el=$("clone-db-readout");el&&(el.textContent=Number.isFinite(db)?db.toFixed(1)+" dB":"-\u221E dB")}function _cloneStartMeter(){if(!_cloneMonState.analyser)return;_cloneMonState.meterRaf&&cancelAnimationFrame(_cloneMonState.meterRaf);const data=new Float32Array(_cloneMonState.analyser.fftSize),canvas=$("clone-live-wave"),RING=300,ADD=10;_cloneMonState.waveRing=new Float32Array(RING);const tick=()=>{_cloneMonState.analyser.getFloatTimeDomainData(data);let sum=0,peak=0;for(const s of data)sum+=s*s,peak=Math.max(peak,Math.abs(s));const rms=Math.sqrt(sum/data.length),db=rms>0?20*Math.log10(rms):-1/0,level=Number.isFinite(db)?(db+60)/60:0;if(_cloneRenderMeter(level,db,peak>.98),canvas&&_cloneMonState.waveRing){const ring=_cloneMonState.waveRing;ring.copyWithin(0,ADD);for(let i=0;i.98?"#f38ba8":db>-12?"#f9e2af":"#a6e3a1",ctx.lineWidth=1.5;const mid=h/2;for(let i=0;i{try{n&&n.disconnect()}catch{}}),_cloneMonState.stream&&_cloneMonState.stream.getTracks().forEach(t=>t.stop()),_cloneMonState.recordStream&&_cloneMonState.recordStream.getTracks().forEach(t=>t.stop()),_cloneMonState.audioCtx&&_cloneMonState.audioCtx.close().catch(()=>{}),Object.assign(_cloneMonState,{stream:null,recordStream:null,sourceNode:null,gainNode:null,analyser:null,audioCtx:null,monitoring:!1,waveRing:null}),_cloneRenderMeter(0,-1/0,!1);const wc=$("clone-live-wave");wc&&wc.getContext("2d").clearRect(0,0,wc.width,wc.height),$("clone-monitor-btn")&&($("clone-monitor-btn").disabled=!1),$("clone-monitor-stop")&&($("clone-monitor-stop").disabled=!0)}(_v=$("clone-monitor-btn"))==null||_v.addEventListener("click",async()=>{try{await _cloneStartMonitor(),status("Mic level monitor active")}catch(e){const m=await microphoneErrorMessage(e);toast(m,"error")}}),(_w=$("clone-monitor-stop"))==null||_w.addEventListener("click",()=>{_cloneStopMonitor(),status("Mic level monitor stopped")}),(_x=$("clone-mic-gain"))==null||_x.addEventListener("input",()=>{const v=parseFloat($("clone-mic-gain").value)||0;$("clone-mic-gain-value")&&($("clone-mic-gain-value").textContent=v.toFixed(2)+"x"),_cloneMonState.gainNode&&(_cloneMonState.gainNode.gain.value=v)}),_cloneRenderMeter();let mediaRec=null,recChunks=[],recTimer=null,recSecs=0;$("rec-start-btn").addEventListener("click",async()=>{try{await _cloneStartMonitor(),recChunks=[],recSecs=0,$("rec-time").textContent="0:00",$("rec-indicator").classList.add("active"),$("rec-start-btn").disabled=!0,$("rec-stop-btn").disabled=!1,recTimer=setInterval(()=>{recSecs++,$("rec-time").textContent=Math.floor(recSecs/60)+":"+String(recSecs%60).padStart(2,"0")},1e3),mediaRec=new MediaRecorder(_cloneMonState.recordStream||_cloneMonState.stream,{audioBitsPerSecond:256e3}),mediaRec.ondataavailable=e=>{e.data.size&&recChunks.push(e.data)},mediaRec.onstop=async()=>{clearInterval(recTimer),$("rec-indicator").classList.remove("active");const blob=new Blob(recChunks,{type:mediaRec.mimeType||"audio/webm"}),ext=(mediaRec.mimeType||"").includes("ogg")?".ogg":".webm";_cloneStopMonitor(),await uploadFile(new File([blob],"recording"+ext,{type:blob.type}))},mediaRec.start(100),status("Recording\u2026")}catch(e){_cloneStopMonitor(),toast(await microphoneErrorMessage(e),"error")}}),$("rec-stop-btn").addEventListener("click",()=>{mediaRec&&mediaRec.state!=="inactive"&&mediaRec.stop(),$("rec-start-btn").disabled=!1,$("rec-stop-btn").disabled=!0}),$("trim-btn").addEventListener("click",async()=>{var _a2,_b2,_c2;if(!currentFileId){toast("No audio loaded","error");return}try{const r=await fetch("/api/process",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:currentFileId,start:parseFloat($("trim-start").value)||0,end:parseFloat($("trim-end").value)||0})});if(!r.ok){const e=await r.json();throw new Error(e.detail)}const d=await r.json();trimmedFileId=d.id,designedFileId=null,$("trim-audio").src="/api/audio/"+d.id,$("trim-audio").style.display="",$("no-audio-hint").style.display="none",switchTab("save"),toast("Trim done","success"),(_b2=(_a2=$("transcript-area"))==null?void 0:_a2.closest(".card"))==null||_b2.scrollIntoView({behavior:"smooth",block:"center"}),(_c2=window._cloneAutoTranscribe)==null||_c2.call(window)}catch(e){toast("Trim failed: "+e.message,"error")}});const DESIGN_LANG_CODE={Auto:"EN",English:"EN",Chinese:"ZH",Japanese:"JA",Korean:"KO",German:"DE",French:"FR",Spanish:"ES",Italian:"IT",Portuguese:"PT",Russian:"RU"},DESIGN_GENDER_WORD={F:"female",M:"male",N:"neutral"},DESIGN_PRESET_KEY="vcf-design-presets",DESIGN_PRESET_SEEDED_KEY="vcf-design-presets-seeded-v2",DEFAULT_DESIGN_PRESETS={EN_M_Young_Energetic:{description:"Young adult male voice, clear English, bright and energetic, moderately high pitch, quick but controlled speaking rate, confident and friendly, suitable for tutorials or streaming.",sample_text:"Hey everyone, welcome back. Today we are going to move quickly, keep it clear, and make this setup feel easy.",language:"English",gender:"M"},EN_F_Warm_Narrator:{description:"Adult female English narrator, warm and smooth, medium pitch, calm pace, gentle emotion, clear articulation, suited for audiobooks and voice assistant responses.",sample_text:"The room grew quiet as the morning light touched the window, and for a moment everything felt simple and kind.",language:"English",gender:"F"},DE_M_Elderly_Documentary:{description:"Aeltere maennliche deutsche Stimme, tief und resonant, langsam und gelassen, klar artikuliert, ruhig und dokumentarisch, mit serioeser und vertrauensvoller Praesenz.",sample_text:"Seit vielen Jahren beobachten wir diesen Ort, seine Geschichte und die Menschen, die ihn mit Leben fuellen.",language:"German",gender:"M"},DE_F_Young_Friendly:{description:"Junge weibliche deutsche Stimme, hell und freundlich, natuerliche Sprechgeschwindigkeit, klare Aussprache, leicht optimistisch und nahbar, passend fuer Assistenten und kurze Erklaerungen.",sample_text:"Hallo, schoen dass du da bist. Ich zeige dir kurz, wie alles funktioniert, Schritt fuer Schritt.",language:"German",gender:"F"},EN_N_Old_Wise_Assistant:{description:"Older neutral English voice, gentle and wise, slightly low pitch, slow measured pace, soothing tone, very clear pronunciation, calm personality for guidance and reflective narration.",sample_text:"Take a slow breath. We will look at the facts carefully, choose the next step, and keep moving.",language:"English",gender:"N"}},QWEN_DESIGN_SAMPLES={"qwen-timbre-reuse":{title:"Qwen Timbre Reuse",summary:"Reference clip for designing a reusable teen character timbre.",description:"Male, 17 years old, tenor range, gaining confidence - deeper breath support now, though vowels still tighten when nervous",text:"H-hey! You dropped your... uh... calculus notebook? I mean, I think it's yours? Maybe?",language:"English",gender:"M"},"acoustic-sausage-announcer":{title:"Acoustic Attribute Control - British announcer",summary:"Fast, loud, articulate British male delivery with excitement and performative authority.",description:`gender: Male. pitch: Low male pitch with significant upward inflections for emphasis and excitement. speed: Fast-paced delivery with deliberate pauses for dramatic effect. volume: Loud and projecting, increasing notably during moments of praise and announcements. age: Young adult to middle-aged adult. clarity: Highly articulate and distinct pronunciation. fluency: Very fluent speech with no hesitations. accent: British English. texture: Bright and clear vocal texture. emotion: Enthusiastic and excited, especially when complimenting. tone: Upbeat, authoritative, and performative. personality: Confident, extroverted, and engaging.`,text:"Nine different, exciting ways of cooking sausage. Incredible. There were three outstanding deliveries in terms of the sausage being the hero. The first dish that we want to dissect, this individual smartly combined different proteins in their sausage. Great seasoning. The blend was absolutely spot on. Congratulations. Please step forward. Natasha.",language:"English",gender:"M"},"acoustic-character-laugh":{title:"Acoustic Attribute Control - theatrical character",summary:"Artificially high male character voice shifting from loud forced amusement to deliberate resignation.",description:`gender: Male. pitch: Artificially high-pitched, slightly lowering after the initial laugh. speed: Rapid during the laugh, then slowing to a deliberate pace. volume: Loud laugh transitioning to a standard conversational level. age: Young adult to middle-aged, performing a character voice. clarity: Clear and distinct articulation. fluency: Fluent delivery without hesitation. accent: American English. texture: Slightly strained and somewhat nasal quality. emotion: Forced amusement shifting to feigned resignation. tone: Initially playful, then shifts to a slightly put-upon tone. personality: Theatrical and expressive.`,text:"Good one. Okay, fine, I'm just gonna leave this sock monkey here. Goodbye.",language:"English",gender:"M"},"age-control-surly-elvis":{title:"Age Control - middle-aged gravel",summary:"Low, resonant, slightly gravelly American male voice with a commanding opening.",description:`gender: Male. pitch: Low male pitch, generally stable. speed: Deliberate pace, slowing slightly after the initial exclamation. volume: Starts loud, then transitions to a projected conversational volume. age: Middle-aged adult. clarity: High clarity with distinct pronunciation. fluency: Highly fluent. accent: American English. texture: Resonant and slightly gravelly. emotion: Initially commanding, shifting to narrative amusement. tone: Authoritative start, moving to an engaging, descriptive tone. personality: Confident and performative.`,text:"Older gentleman, 110, maybe 111 years old, sort of a surly Elvis thing happening with him. He smiles like this. Seen him around?",language:"English",gender:"M"},"gradual-control-anger":{title:"Gradual Control - emotional escalation",summary:"Female voice that begins neutral and quickly escalates into sharp anger and accusation.",description:`gender: Female. pitch: Mid-range female pitch, rising sharply with frustration. speed: Starts measured, then accelerates rapidly during emotional outburst. volume: Begins conversational, escalates quickly to loud and forceful. age: Young adult to middle-aged. clarity: High clarity and distinct articulation throughout. fluency: Highly fluent with no significant pauses or fillers. accent: General American English. texture: Bright and clear vocal quality. emotion: Shifts abruptly from neutral acceptance to intense resentment and anger. tone: Initially accepting, becomes sharply accusatory and confrontational. personality: Assertive and emotionally expressive when provoked.`,text:"Okay. Yeah. I resent you. I love you. I respect you. But you know what? You blew it! And thanks to you-",language:"English",gender:"F"},"human-likeness-digital-nomad":{title:"Human-likeness - casual self-aware monologue",summary:"Warm male conversational voice with natural laughter, hesitations, and self-deprecating humor.",description:"A relaxed, naturally expressive male voice in his late twenties to early thirties, with a moderately low pitch, casual speaking rate, and conversational volume; deliver lines with a light, self-deprecating tone, breaking into genuine, easygoing laughter at moments of embarrassment, while maintaining clear articulation and an overall warm, approachable clarity.",text:`Yeah, so--uh--I'm a digital nomad, right? So... pretty much all my communication is just, like, texts and messages. And now, you know, there's these AI agents that can, uh... reply for you? Which is--heh--convenient, sure, I guess? But also... kinda delicate, you know? Like, you'll type something super short--like, "Yep, sounds good"--and it'll turn that into this whole... warm, polished paragraph. Like, way nicer than I'd ever write myself. huh... ha Seriously, I sound like a Hallmark card all of a sudden. But then... once you outsource that... what's the other person actually hearing? Are they hearing me... or just some... generic, friendly-bot voice? Man, that's weird to even say out loud.`,language:"English",gender:"M"},"background-marcus-cole":{title:"Background Information - Marcus Cole",summary:"Broadcast booth announcer profile with bright, agile, urgent delivery.",description:`Character Name: Marcus Cole Voice Profile: A bright, agile male voice with a natural upward lift, delivering lines at a brisk, energetic pace. Pitch leans high with spark, volume projects clearly--near-shouting at peaks--to convey urgency and excitement. Speech flows seamlessly, fluently, each word sharply defined, riding a current of dynamic rhythm. Background: Longtime broadcast booth announcer for national television, specializing in live interstitials and public engagement spots. His voice bridges segments, rallies action, and keeps momentum alive--from voter drives to entertainment news. Presence: Late 50s, neatly groomed, dressed in a crisp shirt under studio lights. Moves with practiced ease, eyes locked on the script, energy coiled and ready. Personality: Energetic, precise, inherently engaging. He doesn't just read--he propels. Behind the speed is intent: to inform fast, to move people to act. Whether it's "text VOTE to 5703" or a star-studded tease, he makes it feel immediate, vital.`,text:"Lot being you watching. 1-866-IDLE-03 for JPL. That's 1-866-436-5703. Or text the word VOTE to 5703. Diana DeGarmo's next with more from the movies right after this brief intermission on American Idol.",language:"English",gender:"M"},"timbre-reuse-lucas-mia":{title:"Timbre Reuse - Lucas and Mia",summary:"Two-character teen dialogue using native VoiceDesign speaker-profile switching.",description:`"Lucas": "Male, 17 years old, tenor range, gaining confidence - deeper breath support now, though vowels still tighten when nervous" "Mia": "Female, 16 years old, mezzo-soprano range, softening - lowering register to intimate speaking voice, consonants softening"`,text:`Lucas:H-hey! You dropped your... uh... calculus notebook? I mean, I think it's yours? Maybe? Mia:Oh wow, my mortal enemy - Mr. Thompson's problem sets. Thanks for rescuing me from that F. Lucas:No problem! I actually... kinda finished those already? If you want to compare answers or something... Mia:Is this your sneaky way of saying you want to study together, Lucas? Because I saw you staring during lab partners sign-up. Lucas:What? No! I mean yes but not like... I just think you're... your titration technique is really precise! Mia:That's the nerdiest compliment I've ever gotten. Tell you what - help me survive pre-calc and I'll teach you how to actually flirt. Lucas:Wow, harsh. And here I thought my titration line was smooth. Mia:It was adorable. Like when you tripped over your shoelaces in the hall yesterday. Or that time you- Lucas:Okay okay! I get it, I'm a disaster. So... library after school? I'll bring the graphing calculators? Mia:Only if you promise not to spill coffee on my notes again... though I guess watching you panic-clean was pretty cute.`,language:"English",gender:"N",dialogue:!0}};let currentDesignSource=null;function loadDesignPresets(){try{return JSON.parse(localStorage.getItem(DESIGN_PRESET_KEY)||"{}")}catch{return{}}}function saveDesignPresets(presets){localStorage.setItem(DESIGN_PRESET_KEY,JSON.stringify(presets))}async function syncDesignPresetsToServer(){try{await fetch("/api/voice-design-presets",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(loadDesignPresets())}),renderIntegrationSnippets()}catch(e){status("Voice Design preset sync failed: "+e.message)}}function seedDesignPresets(){const presets=loadDesignPresets();let changed=!1;Object.entries(DEFAULT_DESIGN_PRESETS).forEach(([name,preset])=>{if(!presets[name]){presets[name]=preset,changed=!0;return}["description","sample_text","language","gender"].forEach(key=>{!presets[name][key]&&preset[key]&&(presets[name][key]=preset[key],changed=!0)})}),(changed||!localStorage.getItem(DESIGN_PRESET_SEEDED_KEY))&&saveDesignPresets(presets),localStorage.setItem(DESIGN_PRESET_SEEDED_KEY,"1"),changed&&syncDesignPresetsToServer()}function refreshDesignPresetSelect(){const presets=loadDesignPresets(),sel=$("design-preset-select"),prev=sel.value;sel.innerHTML='',Object.keys(presets).sort((a,b)=>a.localeCompare(b)).forEach(name=>{const opt=document.createElement("option");opt.value=opt.textContent=name,sel.appendChild(opt)}),presets[prev]&&(sel.value=prev),renderDesignPresetLibrary()}function applyDesignPreset(name){const preset=loadDesignPresets()[name];if(!preset){toast("Preset not found","error");return}$("design-instruct").value=preset.description||"",$("design-sample-text").value=preset.sample_text||preset.text||$("design-sample-text").value||"",$("design-language").value=preset.language||"Auto",$("design-gender").value=preset.gender||"N",$("design-preset-name").value=name,$("design-preset-select").value=name,currentDesignSource={name,gender:preset.gender||"N",language:preset.language||"Auto",text:$("design-sample-text").value||"",description:preset.description||""},toast("Preset loaded: "+name,"success")}function renderDesignPresetLibrary(){const lib=$("design-preset-library");if(!lib)return;const presets=loadDesignPresets(),names=Object.keys(presets).sort((a,b)=>a.localeCompare(b));if(!names.length){lib.innerHTML='
No saved prompt presets yet.
';return}lib.innerHTML="",names.forEach(name=>{const p=presets[name],row=document.createElement("div");row.className="design-preset-row",row.innerHTML=` ${escHtml(name)} ${escHtml(p.gender||"N")} ${escHtml(p.language||"Auto")} ${escHtml(p.description||"")} ${escHtml(p.sample_text||p.text||"")} `,row.querySelector(".preset-use").addEventListener("click",()=>applyDesignPreset(name)),row.querySelector(".preset-delete").addEventListener("click",()=>{const all=loadDesignPresets();delete all[name],saveDesignPresets(all),syncDesignPresetsToServer(),refreshDesignPresetSelect(),toast("Preset deleted: "+name,"success")}),row.querySelector(".preset-preview").addEventListener("click",async e=>{e.currentTarget.disabled=!0;try{const sampleText=p.sample_text||p.text||$("design-sample-text").value,r=await fetch("/api/voice-design",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(voiceDesignPayload(p.description||"",sampleText,p.language||"Auto",p))});if(!r.ok){const err=await r.json().catch(()=>({}));throw new Error(err.detail||r.statusText)}const d=await r.json();$("design-audio").src="/api/audio/"+d.id,$("design-result").style.display="flex",$("design-audio").play().catch(()=>{})}catch(err){toast("Preset preview failed: "+err.message,"error")}finally{e.currentTarget.disabled=!1}}),lib.appendChild(row)})}function renderQwenSampleCards(){const list=$("qwen-sample-list");list&&(list.innerHTML="",Object.entries(QWEN_DESIGN_SAMPLES).forEach(([key,sample])=>{const card=document.createElement("div");card.className="qwen-sample design-sample-grid",card.dataset.qwenSample=key,card.innerHTML=` ${escHtml(sample.title||key)} ${escHtml(sample.gender||"N")} ${escHtml(sample.language||"Auto")} ${escHtml(sample.description||"")} ${escHtml(sample.text||"")}
`,list.appendChild(card)}))}function applyQwenSample(sample){$("design-instruct").value=sample.description,$("design-sample-text").value=sample.text,$("design-language").value=sample.language,$("design-gender").value=sample.gender,currentDesignSource=sample,$("design-result").style.display="none",$("design-save-result").style.display="none",$("design-instruct").scrollIntoView({behavior:"smooth",block:"nearest"})}function isDialogueDesign(instruct,text,source=null){if(source&&source.dialogue)return!0;const speakers=new Set;if(String(instruct||"").split(/\n+/).forEach(line=>{const match=line.trim().match(/^"?([^":]+)"?\s*:\s*"?(.+?)"?$/);match&&speakers.add(match[1].trim())}),speakers.size<2)return!1;const turnSpeakers=new Set;return String(text||"").split(/\n+/).forEach(line=>{const match=line.trim().match(/^([^:]{1,40}):\s*(.+)$/);match&&speakers.has(match[1].trim())&&turnSpeakers.add(match[1].trim())}),turnSpeakers.size>=2}function voiceDesignPayload(instruct,sampleText,language,source=null,gender=null){var _a2;return{instruct,sample_text:sampleText,language,gender:gender||(source==null?void 0:source.gender)||((_a2=$("design-gender"))==null?void 0:_a2.value)||"",dialogue:isDialogueDesign(instruct,sampleText,source)}}let _dVoiceIdManual=!1;function designSafeName(name){return String(name||"VoiceDesign").replace(/^[A-Z]{2}_[FMN]_/,"").replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"").slice(0,42)||"VoiceDesign"}function voiceIdSafePart(value,fallback="style"){return String(value||fallback).replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"").slice(0,32)||fallback}function suggestedStyleVoiceId(baseId,style){const suffix=voiceIdSafePart(style||"style");return`${baseId}_${suffix}`.slice(0,96)}function _updateDVoiceId(){if(_dVoiceIdManual)return;const lang=$("d-lang").value,gender=$("d-gender").value,name=$("d-name").value.trim();$("d-voice-id").value=name?`${lang}_${gender}_${name}`:""}["d-lang","d-gender"].forEach(id=>$(id).addEventListener("change",_updateDVoiceId)),$("d-name").addEventListener("input",()=>{_dVoiceIdManual=!1,_updateDVoiceId()}),$("d-voice-id").addEventListener("input",()=>{_dVoiceIdManual=!0}),seedDesignPresets(),refreshDesignPresetSelect(),renderQwenSampleCards(),syncDesignPresetsToServer(),$("design-preset-select").addEventListener("change",()=>{$("design-preset-select").value&&applyDesignPreset($("design-preset-select").value)}),$("design-preset-load").addEventListener("click",()=>{const name=$("design-preset-select").value||$("design-preset-name").value.trim();if(!name){toast("Select a preset first","error");return}applyDesignPreset(name)}),$("design-preset-save").addEventListener("click",()=>{const name=$("design-preset-name").value.trim()||$("design-preset-select").value;if(!name){toast("Enter a preset name","error"),$("design-preset-name").focus();return}const presets=loadDesignPresets();presets[name]={description:$("design-instruct").value,sample_text:$("design-sample-text").value,language:$("design-language").value,gender:$("design-gender").value,dialogue:isDialogueDesign($("design-instruct").value,$("design-sample-text").value,currentDesignSource)},saveDesignPresets(presets),syncDesignPresetsToServer(),refreshDesignPresetSelect(),$("design-preset-select").value=name,toast("Preset saved: "+name,"success")}),$("design-preset-delete").addEventListener("click",()=>{const name=$("design-preset-select").value||$("design-preset-name").value.trim();if(!name){toast("Select a preset first","error");return}const presets=loadDesignPresets();if(!presets[name]){toast("Preset not found","error");return}delete presets[name],saveDesignPresets(presets),syncDesignPresetsToServer(),refreshDesignPresetSelect(),$("design-preset-name").value="",toast("Preset deleted: "+name,"success")}),["design-instruct","design-sample-text"].forEach(id=>$(id).addEventListener("input",()=>{currentDesignSource=null,id==="design-sample-text"&&($("d-transcript").value=$("design-sample-text").value)})),document.querySelectorAll(".qwen-sample").forEach(card=>{const sample=QWEN_DESIGN_SAMPLES[card.dataset.qwenSample],state=card.querySelector(".qwen-state"),audio=card.querySelector("audio");card.querySelector(".qwen-use").addEventListener("click",()=>{applyQwenSample(sample),toast("Voice Design sample loaded","success")}),card.querySelector(".qwen-preview").addEventListener("click",async e=>{const btn=e.currentTarget;btn.disabled=!0,state.textContent="Generating preview\u2026";try{const r=await fetch("/api/voice-design",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(voiceDesignPayload(sample.description,sample.text,sample.language,sample))});if(!r.ok){const err=await r.json().catch(()=>({}));throw new Error(err.detail||r.statusText)}const d=await r.json();audio.src="/api/audio/"+d.id,audio.style.display="",audio.play().catch(()=>{}),state.textContent="Preview ready"}catch(err){state.textContent="Preview failed",toast("Sample preview failed: "+err.message,"error")}finally{btn.disabled=!1}})});async function runVoiceDesign(){const baseInstruct=$("design-instruct").value.trim(),sample=$("design-sample-text").value.trim(),dialogue=isDialogueDesign(baseInstruct,sample,currentDesignSource),instruct=baseInstruct;if(!instruct){toast("Enter a voice description first","error");return}$("design-generate-btn").disabled=!0,$("design-status").textContent="Generating\u2026",$("design-result").style.display="none",$("design-save-result").style.display="none",status("Generating voice design\u2026");try{const r=await fetch("/api/voice-design",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(voiceDesignPayload(instruct,sample,$("design-language").value,currentDesignSource,$("design-gender").value))});if(!r.ok){const e=await r.json();throw new Error(e.detail||r.statusText)}const d=await r.json();designedFileId=d.id,trimmedFileId=null,editingVoiceId=null,$("design-audio").src="/api/audio/"+d.id,$("design-result").style.display="flex",$("design-status").textContent="Done ("+d.duration.toFixed(1)+" s)";const langCode=DESIGN_LANG_CODE[$("design-language").value]||"EN";$("d-lang").value=langCode,$("d-gender").value=$("design-gender").value,$("d-name").value=designSafeName((currentDesignSource==null?void 0:currentDesignSource.title)||(currentDesignSource==null?void 0:currentDesignSource.name)||$("design-preset-name").value||"VoiceDesign"),_dVoiceIdManual=!1,_updateDVoiceId(),$("d-transcript").value=sample,$("trim-audio").src="/api/audio/"+d.id,$("trim-audio").style.display="",$("no-audio-hint").style.display="none",$("transcript-area").value||($("transcript-area").value=sample),$("design-audio").play().catch(()=>{}),$("design-result").scrollIntoView({behavior:"smooth",block:"nearest"}),toast("Voice generated and export fields filled.","success"),status("Voice design ready")}catch(e){$("design-status").textContent="Failed: "+e.message,toast("Voice design failed: "+e.message,"error"),status("Voice design failed")}finally{$("design-generate-btn").disabled=!1}}$("design-generate-btn").addEventListener("click",runVoiceDesign),$("design-retry-btn").addEventListener("click",runVoiceDesign),$("design-save-btn").addEventListener("click",async()=>{if(!designedFileId){toast("No voice generated yet","error");return}const voiceId=$("d-voice-id").value.trim();if(!voiceId){toast("Enter a Voice ID first","error"),$("d-name").focus();return}if(!validateVoiceId(voiceId)){toast("Voice ID contains invalid characters","error");return}$("design-save-btn").disabled=!0;try{const r=await fetch("/api/save",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:designedFileId,voice_id:voiceId,transcript:$("d-transcript").value})});if(!r.ok){const e=await r.json();throw new Error(e.detail)}const saved=await r.json();await saveMeta(saved.voice_id,{gender:$("d-gender").value,flag:LANG_FLAG_DEFAULT[$("d-lang").value]||void 0,transcript:$("d-transcript").value,note:"Voice Design: "+$("design-instruct").value.slice(0,240)}).catch(()=>{}),await loadVoiceLibrary().catch(()=>{}),$("design-save-result").style.display="flex",$("design-save-result").scrollIntoView({behavior:"smooth",block:"nearest"}),toast("Exported to Voice Clone Library: "+saved.voice_id,"success"),status("Exported to Voice Clone Library: "+saved.voice_id)}catch(e){toast("Save failed: "+e.message,"error")}finally{$("design-save-btn").disabled=!1}}),$("design-download-btn").addEventListener("click",()=>{if(!designedFileId)return;const a=document.createElement("a");a.href="/api/audio/"+designedFileId,a.download=($("d-voice-id").value.trim()||"voice_design")+".wav",a.click()}),(_y=$("clone-refresh-stt-btn"))==null||_y.addEventListener("click",async()=>{var _a2;$("clone-refresh-stt-btn").disabled=!0;try{await refreshSttBackends((_a2=$("clone-stt-backend"))==null?void 0:_a2.value)}finally{$("clone-refresh-stt-btn").disabled=!1}}),$("transcribe-btn").addEventListener("click",async()=>{var _a2,_b2;const id=trimmedFileId||designedFileId||currentFileId;if(!id){toast("No audio to transcribe","error");return}const btn=$("transcribe-btn"),status2=$("transcribe-status"),area=$("transcript-area"),orig=btn.innerHTML;btn.disabled=!0,btn.innerHTML=' Transcribing\u2026',status2&&(status2.className="clone-tr-status working",status2.innerHTML=' Listening to your recording\u2026'),area&&(area.classList.add("transcribing"),area.placeholder="Transcribing your audio \u2014 please wait\u2026");try{const backend=((_a2=$("clone-stt-backend"))==null?void 0:_a2.value)||"configured",r=await fetch("/api/transcribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id,backend})});if(!r.ok){const e=await r.json();throw new Error(e.detail)}const d=await r.json();area&&(area.value=d.text),status2&&(status2.className="clone-tr-status done",status2.innerHTML=' Transcribed'),toast("Transcription complete","success"),(_b2=window._cloneScheduleAutoSave)==null||_b2.call(window)}catch(e){status2&&(status2.className="clone-tr-status error",status2.innerHTML=` Failed: ${escHtml(e.message||String(e))}`),toast("Transcription failed: "+e.message,"error")}finally{btn.disabled=!1,btn.innerHTML=orig,area&&(area.classList.remove("transcribing"),area.placeholder="Type or auto-transcribe the spoken text\u2026")}}),function(){var _a2,_b2,_c2;const g=id=>document.getElementById(id);let _idManual=!1,_prevName="Sam",_autoSaveTimer=null,_lastAutoSaved="";function buildVoiceId(){var _a3,_b3,_c3;if(_idManual){scheduleAutoSave();return}const lang=((_a3=g("lang-select"))==null?void 0:_a3.value)||"EN",gender=((_b3=g("gender-select"))==null?void 0:_b3.value)||"N",name=(((_c3=g("name-input"))==null?void 0:_c3.value)||"").trim().replace(/\s+/g,""),vid=g("voice-id-input");vid&&name&&(vid.value=`${lang}_${gender}_${name}`,vid.dispatchEvent(new Event("input"))),scheduleAutoSave()}const nameField=g("clone-your-name");nameField==null||nameField.addEventListener("input",()=>{const name=nameField.value.trim();if(!name)return;const sample=g("clone-sample-text");if(sample){const prev=sample.dataset.sampleName||_prevName,re=new RegExp("\\b"+prev.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"\\b");re.test(sample.value)&&(sample.value=sample.value.replace(re,name)),sample.dataset.sampleName=name}_prevName=name;const ni=g("name-input");ni&&(ni.value=name.replace(/\s+/g,"")),buildVoiceId()}),["lang-select","gender-select"].forEach(id=>{var _a3;return(_a3=g(id))==null?void 0:_a3.addEventListener("change",buildVoiceId)}),(_a2=g("name-input"))==null||_a2.addEventListener("input",buildVoiceId),(_b2=g("voice-id-input"))==null||_b2.addEventListener("input",e=>{e.isTrusted&&(_idManual=!0),scheduleAutoSave()}),(_c2=g("transcript-area"))==null||_c2.addEventListener("input",scheduleAutoSave),window._cloneAutoTranscribe=function(){var _a3;const ta=g("transcript-area");if(ta&&ta.value.trim()){scheduleAutoSave();return}(_a3=g("transcribe-btn"))==null||_a3.click()};function canAutoSave(){var _a3,_b3;const vid=(((_a3=g("voice-id-input"))==null?void 0:_a3.value)||"").trim(),tr=(((_b3=g("transcript-area"))==null?void 0:_b3.value)||"").trim();return!!((typeof trimmedFileId!="undefined"&&trimmedFileId||typeof designedFileId!="undefined"&&designedFileId)&&vid&&tr&&(typeof validateVoiceId!="function"||validateVoiceId(vid)))}function scheduleAutoSave(){const toggle=g("clone-autosave-toggle");!toggle||!toggle.checked||(clearTimeout(_autoSaveTimer),_autoSaveTimer=setTimeout(()=>{var _a3;if(!canAutoSave())return;const sig=(g("voice-id-input").value+"|"+g("transcript-area").value).trim();sig!==_lastAutoSaved&&(_lastAutoSaved=sig,(_a3=g("save-btn"))==null||_a3.click())},1600))}window._cloneScheduleAutoSave=scheduleAutoSave}(),function(){const picker=document.getElementById("clone-src-picker");if(!picker)return;const cards=[...document.querySelectorAll(".clone-src-card")],tabs=[...picker.querySelectorAll(".clone-src-tab")],KEY="clone-src-choice";function show(src){cards.forEach(c=>{c.hidden=c.dataset.src!==src}),tabs.forEach(t=>t.classList.toggle("active",t.dataset.src===src));try{localStorage.setItem(KEY,src)}catch{}}tabs.forEach(t=>t.addEventListener("click",()=>show(t.dataset.src))),show(localStorage.getItem(KEY)||"mic")}(),$("save-btn").addEventListener("click",async()=>{const id=trimmedFileId||designedFileId||currentFileId;if(!id){toast("No audio ready","error");return}const voiceId=$("voice-id-input").value.trim();if(!voiceId){toast("Enter a Voice ID","error");return}if(!validateVoiceId(voiceId)){toast("Voice ID contains invalid characters","error");return}$("save-btn").disabled=!0;try{const payload={id,voice_id:voiceId,path:editingVoicePath,transcript:$("transcript-area").value},sendSave=endpoint=>fetch(endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)});let fallbackSave=!1,r=await sendSave(editingVoiceId?"/api/voice-replace":"/api/save");if(editingVoiceId&&(r.status===404||r.status===405)&&(fallbackSave=!0,status("Update endpoint unavailable; saving as a regular voice\u2026"),r=await sendSave("/api/save")),!r.ok){const e=await r.json();throw new Error(e.detail)}const d=await r.json();$("save-result").style.display="",toast((editingVoiceId?"Voice updated: ":"Voice saved: ")+d.voice_id,"success"),fallbackSave&&editingVoiceId&&voiceId!==editingVoiceId&&((await fetch("/api/voice/"+encodeURIComponent(editingVoiceId),{method:"DELETE"})).ok||status("Saved renamed voice; old library entry may need manual deletion.")),editingVoiceId=null,editingVoicePath=null}catch(e){toast("Save failed: "+e.message,"error")}finally{$("save-btn").disabled=!1}});let _voices=[],_pendingSelectId=null,_sortField="id",_sortDir=1,_libraryIssueFilter="",_activePlayButton=null,_activePlayVoiceId=null,_activePlayUrl=null,_libraryLoadPromise=null;const BENCHMARK_SAMPLE_STORAGE_KEY="vcf-benchmark-sample-text",_VL_CACHE_KEY="ttsvc_vc";function _vlCacheRead(){try{return JSON.parse(sessionStorage.getItem(_VL_CACHE_KEY)||"null")}catch{return null}}function _vlCacheWrite(voices){try{sessionStorage.setItem(_VL_CACHE_KEY,JSON.stringify(voices))}catch{}}function _vlCacheClear(){try{sessionStorage.removeItem(_VL_CACHE_KEY)}catch{}}window._vlCacheClear=_vlCacheClear;const _libraryFilters={text:"",lang:"",sex:"",type:"",rating:""};let _libraryFilterOptionsSig="";const DEFAULT_BENCHMARK_SAMPLE_TEXT="Hello, how are you today? Please read this sample clearly for a fair voice benchmark.",BENCHMARK_PRESETS={de:"Die Welt ist voller Geschichten, die darauf warten, erz\xE4hlt zu werden \u2014 von mutigen Helden und stillen Tr\xE4umern.",en:"The old lighthouse stood firm against the crashing waves, its beam sweeping silently across the dark and restless sea.",de2:"Victor jagt zw\xF6lf Boxk\xE4mpfer quer \xFCber den gro\xDFen Sylter Deich. Im Winter ist es kalt und die Tage sind kurz.",en2:"She sells seashells by the seashore. Peter Piper picked a peck of pickled peppers on a perfectly pleasant afternoon.",reset:DEFAULT_BENCHMARK_SAMPLE_TEXT};function benchmarkSampleText(){const el=$("benchmark-sample-text");return el&&el.value.trim()||DEFAULT_BENCHMARK_SAMPLE_TEXT}function initBenchmarkSampleControls(){var _a2,_b2;const sample=$("benchmark-sample-text");if(!sample)return;sample.value=localStorage.getItem(BENCHMARK_SAMPLE_STORAGE_KEY)||DEFAULT_BENCHMARK_SAMPLE_TEXT,sample.addEventListener("input",debounce(()=>{localStorage.setItem(BENCHMARK_SAMPLE_STORAGE_KEY,sample.value.trim()),status("Benchmark sample sentence saved")},500)),(_a2=$("benchmark-reset-sample-btn"))==null||_a2.addEventListener("click",()=>{sample.value=DEFAULT_BENCHMARK_SAMPLE_TEXT,localStorage.setItem(BENCHMARK_SAMPLE_STORAGE_KEY,sample.value),status("Benchmark sample sentence reset")});const presetSel=$("benchmark-preset-select");presetSel&&presetSel.addEventListener("change",()=>{const key=presetSel.value;if(!key||!BENCHMARK_PRESETS[key]){presetSel.value="";return}sample.value=BENCHMARK_PRESETS[key],localStorage.setItem(BENCHMARK_SAMPLE_STORAGE_KEY,sample.value),presetSel.value="",status("Benchmark sample sentence loaded")}),(_b2=$("benchmark-use-preview-btn"))==null||_b2.addEventListener("click",()=>{var _a3;const text=(_a3=$("preview-text-area"))==null?void 0:_a3.value.trim();if(!text){toast("Preview text is empty","error");return}sample.value=text,localStorage.setItem(BENCHMARK_SAMPLE_STORAGE_KEY,text),status("Benchmark sample sentence copied from TTS preview")})}function _displaySource(v){if(v.origin)return v.origin;if(v.note){const m=v.note.match(/^Rehearser\s*·\s*(.+?)\s*·/);if(m)return m[1].trim()}if(v.tag){const tags=v.tag.split(",").map(t=>t.trim().toLowerCase());if(tags.includes("fish-audio")||tags.includes("fishaudio"))return"fish-audio"}return""}function getSortValue(v,field){var _a2,_b2,_c2,_d2,_e2,_f2;switch(field){case"has_picture":return v.has_picture?1:0;case"flag":return(v.flag||"").toLowerCase();case"gender":return(_a2={F:0,M:1,N:2}[v.gender])!=null?_a2:3;case"id":return v.id.toLowerCase();case"file_type":return voiceFileType(v);case"duration":return v.duration||0;case"dbfs":return(_b2=voiceDbfs(v))!=null?_b2:-999;case"benchmark":case"factor":return-((_c2=voiceFactor(v))!=null?_c2:-999);case"elapsed":return(_d2=voiceBenchmarkElapsed(v))!=null?_d2:999;case"bench_audio":return(_e2=voiceBenchmarkAudioSec(v))!=null?_e2:999;case"wpm":return(_f2=voiceWpm(v))!=null?_f2:-1;case"transcript":return(v.transcript||"").toLowerCase();case"note":return(v.note||"").toLowerCase();case"source":return(_displaySource(v)||"").toLowerCase();case"seed":return v.seed!=null?v.seed:9999999;case"tag":return(v.tag||"").toLowerCase();case"rating":return v.rating||0;case"enabled":return v.enabled===!1?0:1;default:return""}}function setSort(field){_sortDir=_sortField===field?_sortDir*-1:1,_sortField=field,syncSortHeaders(),renderVoiceList()}function toggleSortDir(){_sortDir*=-1,syncSortHeaders(),renderVoiceList()}function syncSortHeaders(){document.querySelectorAll(".vl-header [data-sort], .vl-table-header [data-sort]").forEach(el=>{el.classList.remove("sort-asc","sort-desc"),el.dataset.sort===_sortField&&el.classList.add(_sortDir===1?"sort-asc":"sort-desc")});const sel=document.getElementById("voice-sort-field");sel&&sel.value!==_sortField&&(sel.value=_sortField);const dirBtn=document.getElementById("voice-sort-dir");if(dirBtn){const icon=dirBtn.querySelector(".mdi");icon&&(icon.className=_sortDir===1?"mdi mdi-arrow-up":"mdi mdi-arrow-down"),dirBtn.title=_sortDir===1?"Ascending \u2014 click to reverse":"Descending \u2014 click to reverse"}}document.addEventListener("click",e=>{var _a2,_b2,_c2;if(e.target.closest("#voice-sort-dir")&&toggleSortDir(),e.target.closest("#voice-group-tag-btn")){window._voiceGroupByTag=!window._voiceGroupByTag;try{localStorage.setItem("vl-group-by-tag",window._voiceGroupByTag?"1":"0")}catch{}const btn=document.getElementById("voice-group-tag-btn");btn==null||btn.classList.toggle("active",window._voiceGroupByTag);const icon=btn==null?void 0:btn.querySelector(".mdi");icon&&(icon.className="mdi mdi-folder"+(window._voiceGroupByTag?"-open":"")+"-outline"),renderVoiceList()}else if(e.target.closest("#voice-table-view-btn")){window._voiceTableView=!window._voiceTableView,window._voiceTableView||(window._voiceTableEditMode=!1);try{localStorage.setItem("vl-table-view",window._voiceTableView?"1":"0")}catch{}const btn=document.getElementById("voice-table-view-btn");btn==null||btn.classList.toggle("active",window._voiceTableView);const editBtn=document.getElementById("voice-table-edit-btn");if(editBtn&&(editBtn.style.display=window._voiceTableView?"inline-flex":"none",editBtn.classList.toggle("active",!!window._voiceTableEditMode)),(_a2=document.querySelector(".voices-workbench"))==null||_a2.classList.toggle("table-view",window._voiceTableView),(_b2=document.querySelector(".voices-workbench"))==null||_b2.classList.toggle("table-edit-mode",!!window._voiceTableEditMode),window._voiceTableView){document.querySelectorAll(".edit-open").forEach(r=>r.classList.remove("edit-open"));const inspector=document.getElementById("voices-inspector");inspector&&(inspector.innerHTML='

Table View Mode
Click a row to exit table view and edit.

')}renderVoiceList()}else if(e.target.closest("#voice-table-edit-btn")){window._voiceTableEditMode=!window._voiceTableEditMode;const editBtn=document.getElementById("voice-table-edit-btn");editBtn==null||editBtn.classList.toggle("active",window._voiceTableEditMode),(_c2=document.querySelector(".voices-workbench"))==null||_c2.classList.toggle("table-edit-mode",window._voiceTableEditMode),renderVoiceList()}});try{window._voiceGroupByTag=localStorage.getItem("vl-group-by-tag")==="1"}catch{}try{window._voiceTableView=localStorage.getItem("vl-table-view")==="1"}catch{}document.addEventListener("click",e=>{const th=e.target.closest(".vl-th-sortable");if(th){const field=th.dataset.sort;if(field){const sel=document.getElementById("voice-sort-field");sel&&(sel.value=field),setSort(field)}}}),document.addEventListener("change",e=>{e.target.id==="voice-sort-field"&&setSort(e.target.value)}),document.addEventListener("change",async e=>{if(e.target.classList.contains("vl-inline-edit")){const row=e.target.closest(".vl-row");if(!row)return;const voiceId=row.dataset.id,field=e.target.dataset.field;let value=e.target.type==="checkbox"?e.target.checked:e.target.value;field==="rating"&&(value=parseInt(value)||0);const payload={};payload[field]=value;try{await saveMeta(voiceId,payload);const v=_voices.find(vv=>vv.id===voiceId);v&&(v[field]=value),typeof toast=="function"&&toast("Saved "+field,"success")}catch{typeof toast=="function"&&toast("Failed to save "+field,"error")}}});const FLAG_LANGUAGE_CANDIDATES={GB:["EN"],US:["EN"],AU:["EN"],NZ:["EN"],IE:["EN"],ZA:["EN"],NG:["EN"],KE:["EN"],GH:["EN"],JM:["EN"],TT:["EN"],CA:["EN","FR"],IN:["EN","HI"],SG:["EN","ZH"],PH:["EN","FIL"],MT:["EN","MT"],DE:["DE"],AT:["DE"],CH:["DE","FR","IT"],FR:["FR"],BE:["FR","NL"],LU:["FR","DE"],ES:["ES"],MX:["ES"],AR:["ES"],CO:["ES"],CL:["ES"],PE:["ES"],VE:["ES"],UY:["ES"],EC:["ES"],BO:["ES"],CR:["ES"],CU:["ES"],DO:["ES"],PT:["PT"],BR:["PT"],IT:["IT"],NL:["NL"],PL:["PL"],SE:["SV"],DK:["DA"],NO:["NO"],FI:["FI"],IS:["IS"],GR:["EL"],CY:["EL","TR"],CZ:["CS"],SK:["SK"],HU:["HU"],RO:["RO"],BG:["BG"],HR:["HR"],SI:["SL"],RS:["SR"],BA:["BS"],ME:["SR"],MK:["MK"],AL:["SQ"],EE:["ET"],LV:["LV"],LT:["LT"],UA:["UK"],RU:["RU"],BY:["RU"],MD:["RO"],TR:["TR"],CN:["ZH"],TW:["ZH"],HK:["ZH"],MO:["ZH"],JP:["JA"],KR:["KO"],VN:["VI"],TH:["TH"],ID:["ID"],MY:["MS"],PK:["UR"],BD:["BN"],LK:["SI"],NP:["NE"],SA:["AR"],EG:["AR"],AE:["AR"],MA:["AR"],QA:["AR"],KW:["AR"],OM:["AR"],JO:["AR"],LB:["AR"],IQ:["AR"],IR:["FA"],IL:["HE"]},FLAG_LANGUAGE=Object.fromEntries(Object.entries(FLAG_LANGUAGE_CANDIDATES).map(([cc,langs])=>[cc,langs[0]])),LANGUAGE_LABELS={EN:"English",DE:"German",FR:"French",ES:"Spanish",PT:"Portuguese",IT:"Italian",NL:"Dutch",PL:"Polish",SV:"Swedish",DA:"Danish",NO:"Norwegian",FI:"Finnish",IS:"Icelandic",EL:"Greek",MT:"Maltese",CS:"Czech",SK:"Slovak",HU:"Hungarian",RO:"Romanian",BG:"Bulgarian",HR:"Croatian",SL:"Slovenian",SR:"Serbian",BS:"Bosnian",MK:"Macedonian",SQ:"Albanian",ET:"Estonian",LV:"Latvian",LT:"Lithuanian",UK:"Ukrainian",RU:"Russian",ZH:"Chinese",JA:"Japanese",KO:"Korean",VI:"Vietnamese",TH:"Thai",ID:"Indonesian",MS:"Malay",FIL:"Filipino",HI:"Hindi",UR:"Urdu",BN:"Bengali",SI:"Sinhala",NE:"Nepali",AR:"Arabic",FA:"Persian",HE:"Hebrew",TR:"Turkish"},SEX_FILTER_LABELS={F:"\u2640 Female",M:"\u2642 Male",N:"\u26A5 Diverse / neutral"};function voiceLangFromName(v){return(v.lang||String(v.id||"").split("_")[0]||"").toUpperCase()}function libraryVoiceLang(v){const fromName=voiceLangFromName(v),candidates=FLAG_LANGUAGE_CANDIDATES[String(v.flag||"").toUpperCase()];return candidates!=null&&candidates.length?candidates.includes(fromName)?fromName:candidates[0]:fromName}function libraryLanguageLabel(code){return LANGUAGE_LABELS[code]||code}function populateLibraryFilters(){const langSel=$("library-filter-lang"),sexSel=$("library-filter-sex"),typeSel=$("library-filter-type"),tagSel=$("library-filter-tag"),groupSel=$("library-filter-group");if(!langSel||!sexSel||!typeSel)return;const langSet=new Set,sexSet=new Set,typeSet=new Set,tagSet=new Set,groupSet=new Set;(_voices||[]).forEach(v=>{const lang=libraryVoiceLang(v);lang&&langSet.add(lang),v.gender&&sexSet.add(v.gender);const type=voiceFileType(v);type&&typeSet.add(type),String(v.tag||"").split(",").map(t=>t.trim()).filter(Boolean).forEach(t=>tagSet.add(t));const g=(v.group||"").trim();g&&groupSet.add(g)});const langs=[...langSet].sort((a,b)=>libraryLanguageLabel(a).localeCompare(libraryLanguageLabel(b))),sexOrder=["F","M","N"],sexes=[...sexSet].sort((a,b)=>(sexOrder.indexOf(a)<0?99:sexOrder.indexOf(a))-(sexOrder.indexOf(b)<0?99:sexOrder.indexOf(b))),types=[...typeSet].sort(),tags=[...tagSet].sort((a,b)=>a.localeCompare(b)),groups=[...groupSet].sort((a,b)=>a.localeCompare(b)),sig=JSON.stringify([langs,sexes,types,tags,groups]);if(sig===_libraryFilterOptionsSig)return;_libraryFilterOptionsSig=sig;const keep={lang:langSel.value,sex:sexSel.value,type:typeSel.value,tag:tagSel==null?void 0:tagSel.value,group:groupSel==null?void 0:groupSel.value};langSel.innerHTML=''+langs.map(x=>``).join(""),sexSel.innerHTML=''+sexes.map(x=>``).join(""),typeSel.innerHTML=''+types.map(x=>``).join(""),tagSel&&(tagSel.innerHTML=''+tags.map(x=>``).join("")),groupSel&&(groupSel.innerHTML=''+groups.map(x=>``).join("")),langSel.value=langs.includes(keep.lang)?keep.lang:"",sexSel.value=sexes.includes(keep.sex)?keep.sex:"",typeSel.value=types.includes(keep.type)?keep.type:"",tagSel&&(tagSel.value=tags.includes(keep.tag)?keep.tag:""),groupSel&&(groupSel.value=groups.includes(keep.group)?keep.group:"")}function readLibraryFilters(){var _a2,_b2,_c2,_d2,_e2;_libraryFilters.text=(((_a2=$("library-filter-text"))==null?void 0:_a2.value)||"").trim().toLowerCase(),_libraryFilters.lang=((_b2=$("library-filter-lang"))==null?void 0:_b2.value)||"",_libraryFilters.sex=((_c2=$("library-filter-sex"))==null?void 0:_c2.value)||"",_libraryFilters.type=((_d2=$("library-filter-type"))==null?void 0:_d2.value)||"",_libraryFilters.rating=((_e2=$("library-filter-rating"))==null?void 0:_e2.value)||""}function libraryFilterMatch(v){const f=_libraryFilters;if(f.lang&&libraryVoiceLang(v)!==f.lang||f.sex&&(v.gender||"")!==f.sex||f.type&&voiceFileType(v)!==f.type)return!1;if(f.rating){const r=Number(v.rating||0),wanted=Number(f.rating);if(wanted===0&&r!==0||wanted===1&&r<1||wanted>1&&rString(x||"").toLowerCase()).join(" ").includes(f.text))}function clearLibraryFilters(){["library-filter-text","library-filter-lang","library-filter-sex","library-filter-type","library-filter-rating"].forEach(id=>{const el=$(id);el&&(el.value="")}),readLibraryFilters(),renderVoiceList()}function libraryTtsBackend(){var _a2;return((_a2=$("library-tts-backend-select"))==null?void 0:_a2.value)||"voice_clone"}function needsDuration(v){return v.duration==null||Number.isNaN(Number(v.duration))}function voiceFileType(v){if(v.file_type)return String(v.file_type).replace(/^\./,"").toLowerCase();const match=String(v.path||v.filename||"").match(/\.([A-Za-z0-9]+)(?:$|[?#])/);return match?match[1].toLowerCase():"wav"}function voiceDbfs(v){var _a2;const value=v.loudness&&((_a2=v.loudness.dbfs)!=null?_a2:v.loudness.after_dbfs);return value==null||Number.isNaN(Number(value))?null:Number(value)}function fmtDbfs(v){const db=voiceDbfs(v);return db==null?"-":db.toFixed(1)}function voiceBenchmark(v){return v.benchmark&&typeof v.benchmark=="object"&&Object.keys(v.benchmark).length>0?v.benchmark:null}function voiceBenchmarkElapsed(v){const b=voiceBenchmark(v),value=b&&b.elapsed_sec;return value==null||Number.isNaN(Number(value))?null:Number(value)}function voiceFactor(v){const b=voiceBenchmark(v);return b&&b.ok&&b.speed!=null?Number(b.speed):null}function fmtFactor(v){const f=voiceFactor(v);return f!=null?f.toFixed(2)+"x":"-"}function fmtElapsed(v){const e=voiceBenchmarkElapsed(v),b=voiceBenchmark(v);return!b||!b.ok?b&&!b.ok?"ERR":"-":e!=null?e.toFixed(1)+"s":"-"}function fmtBenchmark(v){const elapsed=fmtElapsed(v),factor=fmtFactor(v);return elapsed==="-"&&factor==="-"?"-":[elapsed,factor].filter(x=>x!=="-").join(" \xB7 ")}function benchmarkClass(v){const b=voiceBenchmark(v);if(!b)return"";if(!b.ok||b.clipped||b.realtime_ok===!1)return"bench-bad";const elapsed=voiceBenchmarkElapsed(v);return elapsed!=null&&elapsed<=4?"bench-ok":"bench-warn"}function voiceBenchmarkAudioSec(v){const b=voiceBenchmark(v);return b&&b.ok&&b.audio_sec!=null?Number(b.audio_sec):null}function fmtBenchmarkAudio(v){const sec=voiceBenchmarkAudioSec(v);return sec!=null?sec.toFixed(1)+"s":"-"}function voiceWpm(v){const b=voiceBenchmark(v);if(!b||!b.ok||!b.audio_sec||!b.text)return null;const words=b.text.trim().split(/\s+/).length;return Math.round(words/(b.audio_sec/60))}function fmtWpm(v){const wpm=voiceWpm(v);return wpm!=null?wpm+" wpm":"-"}function voiceFileUrl(v){const bust=v._audioVersion||v.updated_at||v.benchmarked_at||""||Date.now();return`/api/voice-file?path=${encodeURIComponent(v.path)}&v=${encodeURIComponent(bust)}`}function markVoiceAudioChanged(v){v._audioVersion=Date.now()}function benchmarkTitle(v){const b=voiceBenchmark(v);if(!b)return"Not benchmarked yet";const parts=[];return b.ok?(parts.push(`total ${Number(b.elapsed_sec||0).toFixed(2)}s`),b.ttfa_ms!=null&&parts.push(`TTFA ${Number(b.ttfa_ms).toFixed(0)}ms`),b.audio_sec!=null&&parts.push(`audio ${Number(b.audio_sec).toFixed(2)}s`),b.rtf!=null&&parts.push(`RTF ${Number(b.rtf).toFixed(2)}`),b.speed!=null&&parts.push(`speed ${Number(b.speed).toFixed(2)}x real-time`),b.clipped&&parts.push("output clipped")):(parts.push("benchmark failed"),b.error&&parts.push(b.error)),Array.isArray(b.advice)&&b.advice.length&&parts.push(b.advice.join(" | ")),b.benchmarked_at&&parts.push(`saved ${b.benchmarked_at}`),parts.join(" \xB7 ")}async function clientVoiceLoudness(v){if(!v.path)throw new Error("No audio path");const resp=await fetch(voiceFileUrl(v),{cache:"no-store"});if(!resp.ok)throw new Error(resp.statusText||"Audio not found");const audioData=await resp.arrayBuffer(),buffer=await new(window.AudioContext||window.webkitAudioContext)().decodeAudioData(audioData.slice(0));let sum=0,peak=0,count=0;for(let ch=0;ch0?20*Math.log10(rms):null,peakDbfs=peak>0?20*Math.log10(peak):null;return{dbfs:dbfs==null?null:Number(dbfs.toFixed(2)),peak_dbfs:peakDbfs==null?null:Number(peakDbfs.toFixed(2))}}async function clientCalculateVoiceDb(){const voices=_bulkSelected&&_bulkSelected.size>0?(_voices||[]).filter(v=>_bulkSelected.has(v.id)):visibleLibraryVoices(),errors=[];let calculated=0;const stats={startedAt:Date.now(),ok:0,slow:0,errors:0,middleLabel:"Skipped"};setBenchmarkProgress(0,voices.length,"Preparing dB scan...",stats);for(const v of voices){setBenchmarkProgress(calculated+errors.length,voices.length,`Calculating dB: ${v.id}`,stats);try{v.loudness=await clientVoiceLoudness(v),await saveMeta(v.id,{loudness:v.loudness}).catch(()=>{}),calculated++,stats.ok++,stats.last=`${v.id}: ${fmtDbfs(v)} dBFS`,status(`Calculated dB: ${calculated} / ${voices.length}`)}catch(e){errors.push({voice_id:v.id,detail:e.message}),stats.errors++,stats.last=`${v.id}: ${e.message}`}setBenchmarkProgress(calculated+errors.length,voices.length,`Calculating dB: ${v.id}`,stats),await new Promise(resolve=>setTimeout(resolve,0))}return setBenchmarkProgress(voices.length,voices.length,"dB scan complete",stats),{calculated,errors,voices:voices.map(v=>({voice_id:v.id,loudness:v.loudness}))}}async function hydrateVoiceDuration(v,el){if(!(!v.path||!needsDuration(v)||v._durationLoading)){v._durationLoading=!0;try{const audio=new Audio;audio.preload="metadata",audio.src=voiceFileUrl(v),await new Promise((resolve,reject)=>{audio.onloadedmetadata=resolve,audio.onerror=()=>reject(new Error("Could not read duration"))}),Number.isFinite(audio.duration)&&audio.duration>0&&(v.duration=audio.duration,el&&document.body.contains(el)&&(el.textContent=fmtDuration(v.duration),el.title=String(v.duration.toFixed(2)))),audio.removeAttribute("src"),audio.load()}catch(e){el&&document.body.contains(el)&&(el.title=e.message)}finally{v._durationLoading=!1}}}document.querySelectorAll(".vl-header [data-sort]").forEach(el=>el.addEventListener("click",()=>setSort(el.dataset.sort)));function dominantLanguages(limit=3){const counts=new Map;return(_voices||[]).forEach(v=>{const lang=(v.lang||String(v.id||"").split("_")[0]||"?").toUpperCase();counts.set(lang,(counts.get(lang)||0)+1)}),[...counts.entries()].sort((a,b)=>b[1]-a[1]||a[0].localeCompare(b[0])).slice(0,limit).map(([lang,count])=>`${lang} ${count}`).join(" \xB7 ")||"-"}function updateLibraryInsights(state="ready"){const el=$("library-insights");if(!el)return;if(state==="loading"){el.innerHTML=[["\u2026","Loading"],["\u2026","Active"],["\u2026","Languages"],["\u2026","Benchmarks"],["\u2026","Quality"],["\u2026","Actions"]].map(([value,label])=>`
${value}${label}
`).join("");return}if(state==="error"){el.innerHTML='
FailedLibrary load
';return}const total=_voices.length,active=_voices.filter(v=>v.enabled!==!1).length,hidden=total-active,bench=_voices.map(voiceBenchmark).filter(Boolean),slow=bench.filter(b=>b&&b.ok&&b.realtime_ok===!1).length,dbValues=_voices.map(voiceDbfs).filter(v=>v!=null),avgDb=dbValues.length?(dbValues.reduce((a,b)=>a+b,0)/dbValues.length).toFixed(1):"-",missingRef=_voices.filter(v=>!v.transcript).length,restart=_voices.filter(v=>v.needs_tts_restart).length,tiles=[{value:`${_voices.filter(v=>$("show-disabled-cb").checked||v.enabled!==!1).length}/${total}`,label:"Visible"},{value:`${active} on`,label:hidden?`${hidden} hidden`:"Active"},{value:dominantLanguages(),label:"Languages"},{value:bench.length?`${bench.length} done`:"-",label:slow?`${slow} slow`:"Benchmarks",filter:slow?"slow":"",title:slow?describeIssueVoices("slow"):"No slow voices"},{value:avgDb==="-"?"-":`${avgDb} dB`,label:missingRef?`${missingRef} no text`:"Avg loudness",filter:missingRef?"no_text":"",title:missingRef?describeIssueVoices("no_text"):"All visible voices have reference text"},{value:restart||"-",label:restart?"Need restart":"Restart flags",filter:restart?"restart":"",title:restart?describeIssueVoices("restart"):"No voices need restart"}];el.innerHTML=tiles.map(item=>{const filter=item.filter?` data-filter="${escHtml(item.filter)}" role="button" tabindex="0"`:"",activeCls=item.filter&&item.filter===_libraryIssueFilter?" active":"",title=item.title?` title="${escHtml(item.title)}"`:"";return`
${escHtml(item.value)}${escHtml(item.label)}
`}).join(""),el.querySelectorAll("[data-filter]").forEach(tile=>{const activate=()=>setLibraryIssueFilter(tile.dataset.filter||"");tile.addEventListener("click",activate),tile.addEventListener("keydown",e=>{(e.key==="Enter"||e.key===" ")&&(e.preventDefault(),activate())})})}function shouldRenderVoiceLibrary(){const section=$("s-voices");return!section||section.classList.contains("is-active")}async function loadVoiceLibrary(options={}){const forceRefresh=!!(options&&options.refresh);return _libraryLoadPromise?_libraryLoadPromise.then(()=>{shouldRenderVoiceLibrary()&&_voices.length&&renderVoiceList()}):(_libraryLoadPromise=(async()=>{setBusyButton("refresh-voices-btn",!0);const list=$("voice-list"),renderVisibleList=shouldRenderVoiceLibrary(),cached=_voices.length===0?_vlCacheRead():null;cached&&Array.isArray(cached)&&cached.length&&(_voices=cached,window._voices=_voices,typeof window.updateVoiceTree=="function"&&window.updateVoiceTree(_voices),renderVisibleList&&renderVoiceList(),updatePreviewVoiceMatchPanel(),status(`Loaded ${_voices.length} voices`));const silent=_voices.length>0;list&&!silent&&renderVisibleList&&(list.innerHTML=loadingMarkup("Loading voice library","Scanning voices, reference text, metadata, ratings, and benchmark results.",8)),!silent&&renderVisibleList&&($("voice-count").textContent="Loading voices\u2026",updateLibraryInsights("loading"),status("Loading voice library\u2026"));try{const r=await fetch("/api/voices"+(forceRefresh?"?refresh=1":""));if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const fresh=await r.json();_vlCacheWrite(fresh),_voices=fresh,window._voices=_voices,typeof window.updateVoiceTree=="function"&&window.updateVoiceTree(_voices),shouldRenderVoiceLibrary()&&renderVoiceList(),updatePreviewVoiceMatchPanel(),typeof renderPerfHistory=="function"&&renderPerfHistory(),status(`Loaded ${_voices.length} voices`)}catch(e){if(!silent&&renderVisibleList&&(list&&(list.innerHTML='
Failed to load voices: '+(e.message||String(e))+"
"),$("voice-count").textContent="Load failed",updateLibraryInsights("error")),status("Voice library load failed: "+e.message),!silent)throw e}finally{setBusyButton("refresh-voices-btn",!1),_libraryLoadPromise=null}})(),_libraryLoadPromise)}$("refresh-voices-btn").addEventListener("click",()=>{_vlCacheClear(),loadVoiceLibrary({refresh:!0})}),$("sync-voice-folders-btn").addEventListener("click",async()=>{$("sync-voice-folders-btn").disabled=!0,status("Syncing active_voices and hidden_voices\u2026");try{const r=await fetch("/api/voices/sync-folders",{method:"POST"});if(!r.ok){const e=await r.json();throw new Error(e.detail||r.statusText)}const d=await r.json();await loadVoiceLibrary();const conflicts=d.conflicts&&d.conflicts.length?`, ${d.conflicts.length} conflicts`:"";toast(`Synced: ${d.moved.active} active, ${d.moved.hidden} hidden${conflicts}`,d.conflicts&&d.conflicts.length?"error":"success"),status("Synced folders. Restart Qwen3-TTS after changing active voices.")}catch(e){toast("Sync failed: "+e.message,"error"),status("Folder sync failed")}finally{$("sync-voice-folders-btn").disabled=!1}});function visibleLibraryVoices(){const showDisabled=$("show-disabled-cb").checked;return _voices.filter(v=>showDisabled||v.enabled!==!1)}function libraryIssueMatch(v,filter=_libraryIssueFilter){const b=voiceBenchmark(v);return filter==="slow"?!!(b&&b.ok&&b.realtime_ok===!1):filter==="no_text"?!String(v.transcript||"").trim():filter==="restart"?!!v.needs_tts_restart:!0}function libraryIssueLabel(filter=_libraryIssueFilter){return{slow:"slow benchmark voices",no_text:"voices without reference text",restart:"voices needing TTS restart"}[filter]||"all voices"}function libraryIssueVoices(filter=_libraryIssueFilter){return visibleLibraryVoices().filter(v=>libraryIssueMatch(v,filter))}function describeIssueVoices(filter=_libraryIssueFilter,limit=12){const voices=libraryIssueVoices(filter).map(v=>v.id);if(!voices.length)return"No matching voices";const extra=voices.length>limit?`, +${voices.length-limit} more`:"";return voices.slice(0,limit).join(", ")+extra}function setLibraryIssueFilter(filter=""){_libraryIssueFilter=_libraryIssueFilter===filter?"":filter,renderVoiceList(),status(_libraryIssueFilter?`${libraryIssueLabel()}: ${describeIssueVoices()}`:"Showing all visible voices")}function libraryTargetDb(){var _a2;const input=$("library-target-db"),raw=Number((_a2=input==null?void 0:input.value)!=null?_a2:-20),value=Number.isFinite(raw)?Math.min(-1,Math.max(-60,raw)):-20;return input&&(input.value=String(value)),value}$("calculate-db-btn").addEventListener("click",async()=>{$("calculate-db-btn").disabled=!0;const _calcTarget=_bulkSelected&&_bulkSelected.size>0?`${_bulkSelected.size} selected`:"visible";status(`Calculating voice loudness (${_calcTarget})\u2026`);try{const d=await clientCalculateVoiceDb();renderVoiceList();const extra=d.errors&&d.errors.length?`, ${d.errors.length} errors`:"";toast(`Calculated dB for ${d.calculated} voices${extra}`,d.errors&&d.errors.length?"error":"success"),status("Calculated voice loudness. Use Normalize volume for visible WAV voices.")}catch(e){toast("Calculate dB failed: "+e.message,"error"),status("dB calculation failed")}finally{$("calculate-db-btn").disabled=!1}}),$("normalize-volume-btn").addEventListener("click",async()=>{var _a2;const target=libraryTargetDb(),visible=visibleLibraryVoices(),voices=visible.filter(v=>voiceFileType(v)==="wav"),skipped=visible.length-voices.length;if(!voices.length){toast("No visible WAV voices to normalize","error");return}if(!confirm(`Normalize ${voices.length} visible WAV voices to ${target} dBFS?${skipped?` ${skipped} non-WAV voices will be skipped.`:""}`))return;$("normalize-volume-btn").disabled=!0,$("calculate-db-btn").disabled=!0;const stats={startedAt:Date.now(),ok:0,slow:skipped,errors:0,middleLabel:"Skipped"},errors=[];let normalized=0;setBenchmarkProgress(0,voices.length,`Normalizing to ${target} dBFS...`,stats),status(`Normalizing ${voices.length} voices to ${target} dBFS...`);try{for(const v of voices){setBenchmarkProgress(normalized+errors.length,voices.length,`Normalizing: ${v.id}`,stats);try{const r=await fetch("/api/voice/normalize",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:v.id,path:v.path,target_dbfs:target})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();v.loudness=d.loudness||v.loudness,v.duration=(_a2=d.duration)!=null?_a2:v.duration,v.file_type=d.file_type||v.file_type,v.path=d.path||v.path,v.needs_tts_restart=!0,markVoiceAudioChanged(v),normalized++,stats.ok++,stats.last=`${v.id}: ${fmtDbfs(v)} dBFS`}catch(e){errors.push({voice_id:v.id,detail:e.message}),stats.errors++,stats.last=`${v.id}: ${e.message}`}setBenchmarkProgress(normalized+errors.length,voices.length,`Normalizing: ${v.id}`,stats),status(`Normalized ${normalized} / ${voices.length}`),await new Promise(resolve=>setTimeout(resolve,0))}setBenchmarkProgress(voices.length,voices.length,"Volume normalization complete",stats),renderVoiceList(),updateLibraryInsights();const extra=`${skipped?`, ${skipped} skipped`:""}${errors.length?`, ${errors.length} errors`:""}`;toast(`Normalized ${normalized} voices${extra}`,errors.length?"error":"success"),status("Volume normalized. Restart TTS before rebenchmarking these voices.")}catch(e){toast("Normalize volume failed: "+e.message,"error"),status("Normalize volume failed")}finally{$("normalize-volume-btn").disabled=!1,$("calculate-db-btn").disabled=!1}});function fmtClock(ms){if(!Number.isFinite(ms)||ms<0)return"-";const total=Math.round(ms/1e3),m=Math.floor(total/60),s=total%60;return`${m}:${String(s).padStart(2,"0")}`}function setBenchmarkProgress(done,total,label="",stats={}){const panel=$("benchmark-progress"),track=panel.querySelector(".benchmark-progress-track"),pct=total?Math.round(done/total*100):0;panel.hidden=!1,$("benchmark-progress-label").textContent=label||(done>=total?"Benchmark complete":"Benchmarking voices..."),$("benchmark-progress-count").textContent=`${done} / ${total}`,$("benchmark-progress-bar").style.width=pct+"%",track.setAttribute("aria-valuenow",String(pct));const live=$("benchmark-live-stats");if(live){const elapsed=stats.startedAt?Date.now()-stats.startedAt:0,avg=done>0?elapsed/done:0,eta=done>0&&total>done?avg*(total-done):0;live.innerHTML=[`Elapsed ${fmtClock(elapsed)}`,`Avg ${done?(avg/1e3).toFixed(1)+"s":"-"}`,`ETA ${done&&total>done?fmtClock(eta):"-"}`,`OK ${stats.ok||0}`,`${stats.middleLabel||"Slow"} ${stats.slow||0}`,`${stats.errorLabel||"Errors"} ${stats.errors||0}`].map(x=>`${escHtml(x)}`).join("")}const last=$("benchmark-live-last");last&&stats.last&&(last.textContent=stats.last)}function hideBenchmarkProgress(){$("benchmark-progress").hidden=!0,$("benchmark-progress-bar").style.width="0%",$("benchmark-live-last")&&($("benchmark-live-last").textContent="")}async function clearTtsRestartFlags(){const r=await fetch("/api/tts/restart-flags/clear",{method:"POST"});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();return _voices.forEach(voice=>{voice.needs_tts_restart=!1}),document.querySelectorAll(".vl-row.edit-open").forEach(row=>row.classList.remove("opt-restart-needed")),updateLibraryInsights(),d}async function runVoiceBenchmark(voiceId="",opts={}){var _a2;const text=(_a2=opts.text)!=null?_a2:benchmarkSampleText();if(!text)return toast("Enter a benchmark sample sentence","error"),null;const payload={active_only:!0,text};voiceId&&(payload.voice_id=voiceId);const r=await fetch("/api/voices/benchmark",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}return r.json()}async function runVoiceBenchmarkBatch(){const voices=benchmarkTargetVoices(),text=benchmarkSampleText();if(!text)return toast("Enter a benchmark sample sentence","error"),null;if(!voices.length)return toast("No voices to benchmark","error"),null;const total=voices.length,aggregate={benchmarked:0,errors:[],voices:[],text,active_only:!0},stats={startedAt:Date.now(),ok:0,slow:0,errors:0,last:""};voices.forEach(v=>{const row=document.querySelector(`.vl-row[data-id="${CSS.escape(v.id)}"]`);row&&(row.classList.remove("benchmarking-active","benchmarking-done"),row.classList.add("benchmarking-pending"))}),setBenchmarkProgress(0,total,"Starting benchmark...",stats);for(let i=0;ix.voice_id===voice.id),b=hit&&hit.benchmark;if(b&&b.ok){if(stats.ok++,b.realtime_ok===!1&&stats.slow++,stats.last=`${voice.id}: ${Number(b.elapsed_sec||0).toFixed(1)}s${b.speed!=null?` \xB7 ${Number(b.speed).toFixed(2)}x`:""}${b.realtime_ok===!1?" \xB7 slow":""}`,row){const bc=benchmarkClass(voice),btitle=benchmarkTitle(voice),durCell=row.querySelector(".vl-tbl-dur"),factorCell=row.querySelector(".vl-tbl-factor"),timeCell=row.querySelector(".vl-tbl-time"),wpmCell=row.querySelector(".vl-tbl-wpm");if(durCell&&(durCell.textContent=fmtBenchmarkAudio(voice),durCell.title=`${fmtBenchmarkAudio(voice)} \u2014 length of synthesised benchmark audio`),factorCell&&(factorCell.textContent=fmtFactor(voice),factorCell.className=`vl-tbl-factor ${bc}`,factorCell.title=btitle),timeCell&&(timeCell.textContent=fmtElapsed(voice),timeCell.className=`vl-tbl-time ${bc}`,timeCell.title=btitle),wpmCell){const wpm=voiceWpm(voice);wpmCell.textContent=fmtWpm(voice),wpmCell.title=wpm!=null?`${wpm} wpm \u2014 130\u2013180 wpm is natural for long listening`:""}}}else stats.errors++,stats.last=`${voice.id}: failed${b&&b.error?" \xB7 "+b.error:""}`}}catch(e){aggregate.errors.push({voice_id:voice.id,detail:e.message}),stats.errors++,stats.last=`${voice.id}: failed \xB7 ${e.message}`}row&&(row.classList.remove("benchmarking-active"),row.classList.add("benchmarking-done")),setBenchmarkProgress(i+1,total,`Finished ${voice.id}`,stats)}return voices.forEach(v=>{const row=document.querySelector(`.vl-row[data-id="${CSS.escape(v.id)}"]`);row&&row.classList.remove("benchmarking-pending")}),setBenchmarkProgress(total,total,"Benchmark complete",stats),aggregate}function mergeBenchmarkResults(d){const byId=new Map((d.voices||[]).map(x=>[x.voice_id,x]));_voices.forEach(v=>{const hit=byId.get(v.id);hit&&hit.benchmark&&(v.benchmark=hit.benchmark)})}window.loadVoiceLibrary=loadVoiceLibrary,window.mergeBenchmarkResults=mergeBenchmarkResults;function activeBenchmarkVoices(){return(_voices||[]).filter(v=>v.enabled!==!1)}function benchmarkTargetVoices(){return typeof _bulkSelected!="undefined"&&_bulkSelected.size>0?(_voices||[]).filter(v=>_bulkSelected.has(v.id)):activeBenchmarkVoices()}function showBenchmarkConfirm(){const voices=benchmarkTargetVoices(),onlySelected=typeof _bulkSelected!="undefined"&&_bulkSelected.size>0;if(!benchmarkSampleText()){toast("Enter a benchmark sample sentence","error");return}if(!voices.length){toast("No voices to benchmark","error");return}const staleCount=voices.filter(v=>v.needs_tts_restart).length;$("benchmark-confirm-title").textContent=`Benchmark ${voices.length} ${onlySelected?"selected":"active"} voice${voices.length===1?"":"s"}?`,$("benchmark-confirm-text").textContent="This sends the sample sentence to each active voice and can keep the GPU busy for a while. Progress updates after every voice."+(staleCount?` ${staleCount} edited voice${staleCount===1?"":"s"} should be restarted first, otherwise cached old voices may be benchmarked.`:""),$("benchmark-confirm").hidden=!1,$("benchmark-confirm-start").focus()}function hideBenchmarkConfirm(){const panel=$("benchmark-confirm");panel&&(panel.hidden=!0)}$("benchmark-voices-btn").addEventListener("click",showBenchmarkConfirm),(_z=$("benchmark-confirm-cancel"))==null||_z.addEventListener("click",hideBenchmarkConfirm),(_A=$("benchmark-confirm-start"))==null||_A.addEventListener("click",async()=>{hideBenchmarkConfirm(),$("benchmark-voices-btn").disabled=!0,$("benchmark-confirm-start").disabled=!0,status("Benchmarking active voices...");try{const d=await runVoiceBenchmarkBatch();if(!d)return;mergeBenchmarkResults(d),await loadVoiceLibrary();const slow=(d.voices||[]).filter(x=>x.benchmark&&x.benchmark.realtime_ok===!1).length,extra=d.errors&&d.errors.length?`, ${d.errors.length} errors`:"";toast(`Benchmarked ${d.benchmarked} voices${slow?`, ${slow} slow`:""}${extra}`,d.errors&&d.errors.length?"error":"success"),status("Benchmark saved with TTFA, total time, RTF, and speed.")}catch(e){toast("Benchmark failed: "+e.message,"error"),status("Benchmark failed")}finally{$("benchmark-voices-btn").disabled=!1,$("benchmark-confirm-start").disabled=!1}}),$("copy-active-voices-btn").addEventListener("click",async()=>{const useSelected=_bulkSelected&&_bulkSelected.size>0,ids=useSelected?[..._bulkSelected]:activeVoiceIds(),label=useSelected?`${ids.length} selected`:`${ids.length} active`;if(!ids.length){toast("No voices to copy","error");return}await copyText(ids.join(", ")),toast("Copied "+label+" voices","success"),status("Copied "+label+" voices to clipboard")}),(_B=$("precompute-embeddings-btn"))==null||_B.addEventListener("click",async()=>{const backend=libraryTtsBackend(),_useSelected=_bulkSelected&&_bulkSelected.size>0,ids=_useSelected?[..._bulkSelected]:activeVoiceIds(),_scopeLabel=_useSelected?`${ids.length} selected`:`${ids.length} active`;if(!ids.length){toast("No voices to precompute","error");return}if(!confirm(`Precompute speaker embeddings for ${_scopeLabel} voice(s) via \u201C${backend}\u201D? This warms each voice so the engine caches its .pt and first playback is instant.`))return;const btn=$("precompute-embeddings-btn");btn&&(btn.disabled=!0);const ov=document.createElement("div");ov.className="audiobook-overlay",ov.id="precompute-overlay",ov.innerHTML=`
Precomputing embeddings
0 / ${ids.length}
`,document.body.appendChild(ov);let cancel=!1;ov.querySelector("#pc-cancel").addEventListener("click",()=>{cancel=!0});const fill=ov.querySelector("#pc-fill"),msg=ov.querySelector("#pc-msg");let done=0,ok=0,failed=0;const queue=ids.slice(),worker=async()=>{for(;queue.length&&!cancel;){const id=queue.shift();msg&&(msg.textContent=`${done} / ${ids.length} \xB7 ${id}`);try{await fetchTtsPreviewBlob(id,"Hallo.","wav","",backend),ok++}catch{failed++}done++,fill&&(fill.style.width=done/ids.length*100+"%")}};try{await Promise.all(Array.from({length:Math.min(2,ids.length)},worker))}finally{ov.remove(),btn&&(btn.disabled=!1)}toast(cancel?`Cancelled \u2014 ${ok} warmed`:`Precomputed ${ok} embedding(s)${failed?`, ${failed} skipped/failed`:""}`,!ok&&failed?"error":"success")});const LIB_ADD_SAMPLE_TEXTS={EN:"The clear morning light warmed the quiet studio as I described a silver train, a bright red apple, and the gentle rhythm of rain on the window.",DE:"Das klare Morgenlicht waermte das ruhige Studio, waehrend ich einen silbernen Zug, einen roten Apfel und den sanften Rhythmus des Regens am Fenster beschrieb.",IT:"La luce chiara del mattino scaldava lo studio tranquillo mentre descrivevo un treno d argento, una mela rossa e il ritmo leggero della pioggia alla finestra.",ES:"La clara luz de la manana calentaba el estudio tranquilo mientras describia un tren plateado, una manzana roja y el suave ritmo de la lluvia en la ventana.",FR:"La lumiere claire du matin rechauffait le studio calme pendant que je decrivais un train argente, une pomme rouge et le doux rythme de la pluie sur la fenetre.",PT:"A luz clara da manha aquecia o estudio tranquilo enquanto eu descrevia um comboio prateado, uma maca vermelha e o ritmo suave da chuva na janela.",NL:"Het heldere ochtendlicht verwarmde de stille studio terwijl ik een zilveren trein, een rode appel en het zachte ritme van regen op het raam beschreef.",PL:"Jasne poranne swiatlo ogrzewalo ciche studio, gdy opisywalem srebrny pociag, czerwone jablko i lagodny rytm deszczu na oknie."},LIB_ADD_SAMPLE_STORAGE_KEY="vcf-lib-add-sample-texts";function libAddSampleOverrides(){try{return JSON.parse(localStorage.getItem(LIB_ADD_SAMPLE_STORAGE_KEY)||"{}")||{}}catch{return{}}}function getLibAddSampleText(code){return libAddSampleOverrides()[code]||LIB_ADD_SAMPLE_TEXTS[code]||LIB_ADD_SAMPLE_TEXTS.EN}function saveLibAddSampleText(){const code=$("lib-add-sample-lang").value,text=$("lib-add-sample-text").value.trim(),overrides=libAddSampleOverrides();text&&text!==LIB_ADD_SAMPLE_TEXTS[code]?overrides[code]=text:delete overrides[code],localStorage.setItem(LIB_ADD_SAMPLE_STORAGE_KEY,JSON.stringify(overrides)),setLibAddStatus("Sample sentence saved")}function resetLibAddSampleText(){const code=$("lib-add-sample-lang").value,overrides=libAddSampleOverrides();delete overrides[code],localStorage.setItem(LIB_ADD_SAMPLE_STORAGE_KEY,JSON.stringify(overrides)),$("lib-add-sample-text").value=LIB_ADD_SAMPLE_TEXTS[code]||LIB_ADD_SAMPLE_TEXTS.EN,setLibAddStatus("Sample sentence reset")}function updateLibAddSampleLanguage(lang){const code=LIB_ADD_SAMPLE_TEXTS[lang]?lang:"EN";$("lib-add-sample-lang").value=code,$("lib-add-lang").value=code,$("lib-add-sample-text").value=getLibAddSampleText(code);const voiceId=$("lib-add-voice-id").value.trim();voiceId&&/^[A-Z]{2}_/.test(voiceId)&&($("lib-add-voice-id").value=voiceId.replace(/^[A-Z]{2}_/,code+"_"))}function renderLibAddMeter(level=0,db=-1/0,clipped=!1){const meter=$("lib-add-mic-meter");if(!meter.children.length)for(let i=0;i<18;i++){const bar=document.createElement("div");bar.className="bar",meter.appendChild(bar)}const active=Math.round(Math.max(0,Math.min(1,level))*meter.children.length);[...meter.children].forEach((bar,i)=>{bar.className="bar",bar.style.height=7+Math.min(i,active)*1.55+"px",i-12&&i>11&&bar.classList.add("hot"),clipped&&i>14&&bar.classList.add("clip"))}),$("lib-add-db-readout").textContent=Number.isFinite(db)?db.toFixed(1)+" dB":"-\u221E dB"}function syncLibAddMicGain(){const gain=parseFloat($("lib-add-mic-gain").value)||0;$("lib-add-mic-gain-value").textContent=gain.toFixed(2)+"x",libAddState.gainNode&&(libAddState.gainNode.gain.value=gain)}function startLibAddMeter(){if(!libAddState.analyser)return;libAddState.meterRaf&&cancelAnimationFrame(libAddState.meterRaf);const data=new Float32Array(libAddState.analyser.fftSize),tick=()=>{libAddState.analyser.getFloatTimeDomainData(data);let sum=0,peak=0;for(const sample of data)sum+=sample*sample,peak=Math.max(peak,Math.abs(sample));const rms=Math.sqrt(sum/data.length),db=rms>0?20*Math.log10(rms):-1/0,level=Number.isFinite(db)?(db+60)/60:0;renderLibAddMeter(level,db,peak>.98),libAddState.meterRaf=requestAnimationFrame(tick)};tick()}async function ensureLibAddMicMonitor(){if(libAddState.recordStream)return;const AudioCtx=window.AudioContext||window.webkitAudioContext;if(libAddState.stream=await requestMicrophoneStream({raw:!0}),AudioCtx){libAddState.audioCtx=new AudioCtx,libAddState.sourceNode=libAddState.audioCtx.createMediaStreamSource(libAddState.stream),libAddState.gainNode=libAddState.audioCtx.createGain(),libAddState.analyser=libAddState.audioCtx.createAnalyser(),libAddState.analyser.fftSize=1024;const dest=libAddState.audioCtx.createMediaStreamDestination();syncLibAddMicGain(),libAddState.sourceNode.connect(libAddState.gainNode),libAddState.gainNode.connect(libAddState.analyser),libAddState.gainNode.connect(dest),libAddState.recordStream=dest.stream,startLibAddMeter()}else libAddState.recordStream=libAddState.stream;libAddState.monitoring=!0,$("lib-add-monitor-btn").disabled=!0,$("lib-add-monitor-stop").disabled=!1}function stopLibAddMic(){libAddState.meterRaf&&cancelAnimationFrame(libAddState.meterRaf),libAddState.meterRaf=null,[libAddState.sourceNode,libAddState.gainNode,libAddState.analyser].forEach(node=>{try{node&&node.disconnect()}catch{}}),libAddState.stream&&libAddState.stream.getTracks().forEach(t=>t.stop()),libAddState.recordStream&&libAddState.recordStream.getTracks().forEach(t=>t.stop()),libAddState.audioCtx&&libAddState.audioCtx.close().catch(()=>{}),libAddState.stream=null,libAddState.recordStream=null,libAddState.sourceNode=null,libAddState.gainNode=null,libAddState.analyser=null,libAddState.audioCtx=null,libAddState.monitoring=!1,$("lib-add-monitor-btn").disabled=!1,$("lib-add-monitor-stop").disabled=!0,renderLibAddMeter(0,-1/0,!1)}let libAddState={id:null,duration:0,audio:null,buffer:null,recorder:null,chunks:[],pendingSource:null,stream:null,recordStream:null,timer:null,secs:0,audioCtx:null,sourceNode:null,gainNode:null,analyser:null,meterRaf:null,monitoring:!1};window.libAddState=libAddState,$("add-new-voice-btn").addEventListener("click",()=>{$("lib-add-panel").classList.toggle("open")}),$("lib-add-sample-lang").addEventListener("change",()=>updateLibAddSampleLanguage($("lib-add-sample-lang").value)),$("lib-add-lang").addEventListener("change",()=>updateLibAddSampleLanguage($("lib-add-lang").value)),$("lib-add-sample-text").addEventListener("input",debounce(saveLibAddSampleText,500)),$("lib-add-use-sample").addEventListener("click",()=>{$("lib-add-transcript").value=$("lib-add-sample-text").value.trim(),setLibAddStatus("Sample sentence copied to transcript")}),$("lib-add-reset-sample").addEventListener("click",resetLibAddSampleText),$("lib-add-mic-help-btn").addEventListener("click",()=>{$("lib-add-mic-help").classList.toggle("open")}),$("lib-add-monitor-btn").addEventListener("click",async()=>{try{await ensureLibAddMicMonitor(),setLibAddStatus("Mic level monitor active")}catch(e){stopLibAddMic(),$("lib-add-mic-help").classList.add("open");const message=await microphoneErrorMessage(e);toast(message,"error"),setLibAddStatus(message)}}),$("lib-add-monitor-stop").addEventListener("click",()=>{stopLibAddMic(),setLibAddStatus("Mic level monitor stopped")}),$("lib-add-mic-gain").addEventListener("input",syncLibAddMicGain),renderLibAddMeter(),syncLibAddMicGain(),updateLibAddSampleLanguage("EN");function setLibAddStatus(msg){$("lib-add-status").textContent=msg,status(msg)}function suggestLibVoiceId(filename){if($("lib-add-voice-id").value.trim())return;const base=String(filename||"NewVoice").replace(/\.[^.]+$/,"").replace(/[^A-Za-z0-9_-]+/g,"_").replace(/^_+|_+$/g,"").slice(0,60)||"NewVoice";$("lib-add-voice-id").value=`${$("lib-add-lang").value||"EN"}_${$("lib-add-gender").value||"N"}_${base}`}function loadLibAddAudio(id,duration,label="Audio"){libAddState.id=id,libAddState.duration=Number(duration)||0,libAddState.buffer=null,$("lib-add-start").value="0.00",$("lib-add-end").value=libAddState.duration?Math.min(libAddState.duration,20).toFixed(2):"0.00",$("lib-add-audio").src="/api/audio/"+id,$("lib-add-audio").style.display="",$("lib-add-wave").style.display="",attachLibAddWaveSelection(),decodeTempAudio(id).then(buffer=>{libAddState.id===id&&(libAddState.buffer=buffer,drawLibAddWave())}).catch(()=>{}),setLibAddStatus(`${label} loaded${libAddState.duration?" ("+libAddState.duration.toFixed(1)+" s)":""}`)}async function decodeTempAudio(id){const resp=await fetch("/api/audio/"+encodeURIComponent(id));if(!resp.ok)throw new Error(resp.statusText||"Audio not found");const data=await resp.arrayBuffer();return new(window.AudioContext||window.webkitAudioContext)().decodeAudioData(data.slice(0))}function clampLibAddTime(value){var _a2;const duration=libAddState.duration||((_a2=libAddState.buffer)==null?void 0:_a2.duration)||0;return Math.max(0,Math.min(duration,Number(value)||0))}function setLibAddCropRange(start,end){var _a2;const duration=libAddState.duration||((_a2=libAddState.buffer)==null?void 0:_a2.duration)||0;let a=clampLibAddTime(start),b=clampLibAddTime(end);Math.abs(b-a)<.05&&(b=Math.min(duration,a+Math.min(1,duration||1))),b=3&&dur<=20?"ok":dur?"warn":"")}function drawLibAddWave(){libAddState.buffer&&(drawOptimizerWave($("lib-add-wave"),libAddState.buffer,parseFloat($("lib-add-start").value)||0,parseFloat($("lib-add-end").value)||libAddState.duration||libAddState.buffer.duration),updateLibAddCropHint())}function libAddWaveSelectionPixels(e){var _a2;const rect=$("lib-add-wave").getBoundingClientRect(),duration=libAddState.duration||((_a2=libAddState.buffer)==null?void 0:_a2.duration)||0,start=clampLibAddTime(parseFloat($("lib-add-start").value)||0),end=clampLibAddTime(parseFloat($("lib-add-end").value)||duration),sx=duration&&rect.width?start/duration*rect.width:0,ex=duration&&rect.width?end/duration*rect.width:rect.width;return{x:Math.max(0,Math.min(rect.width,e.clientX-rect.left)),sx,ex,start,end,duration}}function libAddWaveDragMode(e){const{x,sx,ex}=libAddWaveSelectionPixels(e),hit=16;return Math.abs(x-sx)<=hit?"start":Math.abs(x-ex)<=hit?"end":"new"}function attachLibAddWaveSelection(){const canvas=$("lib-add-wave");if(!canvas||canvas.dataset.cropReady)return;canvas.dataset.cropReady="1";let drag=null;canvas.addEventListener("pointerdown",e=>{var _a2;if(!libAddState.buffer)return;e.preventDefault();const mode=libAddWaveDragMode(e),t=libAddWaveTimeFromEvent(e),currentStart=parseFloat($("lib-add-start").value)||0,currentEnd=parseFloat($("lib-add-end").value)||libAddState.duration||0;drag={mode,anchor:t,start:currentStart,end:currentEnd},(_a2=canvas.setPointerCapture)==null||_a2.call(canvas,e.pointerId),mode==="start"?setLibAddCropRange(t,currentEnd):setLibAddCropRange(mode==="end"?currentStart:t,t),setLibAddStatus(mode==="start"?"Dragging crop start handle":mode==="end"?"Dragging crop end handle":"Drag to choose a new crop range")}),canvas.addEventListener("pointermove",e=>{if(!libAddState.buffer)return;if(!drag){const mode=libAddWaveDragMode(e);canvas.style.cursor=mode==="start"||mode==="end"?"ew-resize":"crosshair";return}e.preventDefault();const t=libAddWaveTimeFromEvent(e);drag.mode==="start"?setLibAddCropRange(t,drag.end):drag.mode==="end"?setLibAddCropRange(drag.start,t):setLibAddCropRange(drag.anchor,t)});const finish=e=>{if(!drag)return;e.preventDefault();const t=libAddWaveTimeFromEvent(e);drag.mode==="start"?setLibAddCropRange(t,drag.end):drag.mode==="end"?setLibAddCropRange(drag.start,t):setLibAddCropRange(drag.anchor,t),drag=null;const start=parseFloat($("lib-add-start").value)||0,end=parseFloat($("lib-add-end").value)||0;setLibAddStatus(`Crop range ${start.toFixed(2)}s to ${end.toFixed(2)}s (${Math.max(0,end-start).toFixed(1)}s) selected`)};canvas.addEventListener("pointerup",finish),canvas.addEventListener("pointerleave",()=>{drag||(canvas.style.cursor="crosshair")}),canvas.addEventListener("pointercancel",()=>{drag=null,canvas.style.cursor="crosshair"})}async function uploadLibAddFile(file){if(!file)return;const fd=new FormData;fd.append("file",file),setLibAddStatus("Uploading audio\u2026");try{const r=await fetch("/api/upload",{method:"POST",body:fd});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();suggestLibVoiceId(file.name),loadLibAddAudio(d.id,d.duration,file.name||"Audio"),toast("Audio loaded","success")}catch(e){toast("Load failed: "+e.message,"error"),setLibAddStatus("Load failed")}}const libAddDrop=$("lib-add-drop");libAddDrop.addEventListener("click",()=>$("lib-add-file").click()),libAddDrop.addEventListener("dragover",e=>{e.preventDefault(),libAddDrop.classList.add("drag-over")}),libAddDrop.addEventListener("dragleave",()=>libAddDrop.classList.remove("drag-over")),libAddDrop.addEventListener("drop",e=>{e.preventDefault(),libAddDrop.classList.remove("drag-over"),e.dataTransfer.files.length&&uploadLibAddFile(e.dataTransfer.files[0])}),$("lib-add-file").addEventListener("change",async()=>{$("lib-add-file").files.length&&await uploadLibAddFile($("lib-add-file").files[0]),$("lib-add-file").value=""}),$("lib-add-url-btn").addEventListener("click",()=>{const url=$("lib-add-url").value.trim();if(!url){toast("Enter a YouTube or audio URL","error");return}$("lib-add-url-btn").disabled=!0,setLibAddStatus("Starting download\u2026");const es=new EventSource("/api/download-yt?url="+encodeURIComponent(url));es.onmessage=e=>{const d=JSON.parse(e.data);d.error?(toast("Download failed: "+d.error,"error"),setLibAddStatus(d.error),$("lib-add-url-btn").disabled=!1,es.close()):d.done?(es.close(),$("lib-add-url-btn").disabled=!1,suggestLibVoiceId(url.split("/").pop()||"DownloadedVoice"),loadLibAddAudio(d.id,d.duration,"Downloaded audio"),toast("URL audio loaded","success")):setLibAddStatus(d.msg||"Downloading\u2026")},es.onerror=()=>{es.close(),$("lib-add-url-btn").disabled=!1,setLibAddStatus("Download connection closed")}}),$("lib-add-rec-start").addEventListener("click",async()=>{try{await ensureLibAddMicMonitor(),libAddState.chunks=[],libAddState.secs=0,$("lib-add-rec-time").textContent="0:00",$("lib-add-rec-start").disabled=!0,$("lib-add-rec-stop").disabled=!1,$("lib-add-monitor-stop").disabled=!0,libAddState.timer=setInterval(()=>{libAddState.secs++,$("lib-add-rec-time").textContent=Math.floor(libAddState.secs/60)+":"+String(libAddState.secs%60).padStart(2,"0")},1e3),libAddState.recorder=new MediaRecorder(libAddState.recordStream),libAddState.recorder.ondataavailable=e=>{e.data.size&&libAddState.chunks.push(e.data)},libAddState.recorder.onstop=async()=>{clearInterval(libAddState.timer),$("lib-add-rec-start").disabled=!1,$("lib-add-rec-stop").disabled=!0;const blob=new Blob(libAddState.chunks,{type:libAddState.recorder.mimeType||"audio/webm"}),ext=(libAddState.recorder.mimeType||"").includes("ogg")?".ogg":".webm";stopLibAddMic(),suggestLibVoiceId("recording"),await uploadLibAddFile(new File([blob],"recording"+ext,{type:blob.type}))},libAddState.recorder.start(100),setLibAddStatus("Recording\u2026")}catch(e){stopLibAddMic(),$("lib-add-mic-help").classList.add("open");const message=await microphoneErrorMessage(e);toast(message,"error"),setLibAddStatus(message),$("lib-add-rec-start").disabled=!1,$("lib-add-rec-stop").disabled=!0}}),$("lib-add-rec-stop").addEventListener("click",()=>{libAddState.recorder&&libAddState.recorder.state!=="inactive"&&libAddState.recorder.stop()}),$("lib-add-auto-trim").addEventListener("click",async()=>{if(!libAddState.id){toast("Load audio first","error");return}$("lib-add-auto-trim").disabled=!0;try{const r=await fetch("/api/auto-trim",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:libAddState.id})});let d;if(r.ok)d=await r.json();else if(r.status===404||r.status===405)d=await clientAutoTrimBounds(libAddState.id);else{const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}$("lib-add-start").value=Number(d.start).toFixed(2),$("lib-add-end").value=Number(d.end).toFixed(2),drawLibAddWave(),setLibAddStatus(d.reason||"Auto trim ready")}catch(e){toast("Auto trim failed: "+e.message,"error"),setLibAddStatus("Auto trim failed")}finally{$("lib-add-auto-trim").disabled=!1}});async function transcribeLibAddCurrent(successMessage="Text recognised",audioId=libAddState.id){if(!audioId)throw new Error("Load audio first");setLibAddStatus("Recognising text...");const r=await fetch("/api/transcribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:audioId})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const text=(await r.json()).text||"";return $("lib-add-transcript").value=text,setLibAddStatus(successMessage),text}function openSavedLibraryVoice(voiceId){const openRow=()=>{var _a2;const row=Array.from(document.querySelectorAll(".vl-row")).find(r=>r.dataset.id===voiceId);return row?(row.scrollIntoView({behavior:"smooth",block:"center"}),row.classList.contains("edit-open")||(_a2=row.querySelector(".edit-audio-btn"))==null||_a2.click(),!0):!1};openRow()||setTimeout(openRow,150)}async function applyLibAddCrop(){if(!libAddState.id){toast("Load audio first","error");return}const start=clampLibAddTime(parseFloat($("lib-add-start").value)||0),end=clampLibAddTime(parseFloat($("lib-add-end").value)||libAddState.duration),duration=end-start;if(end<=start+.1){toast("Crop range is too short","error"),setLibAddStatus("Crop range is too short");return}(duration<3||duration>20)&&toast("Best clone references are 3-20 seconds; cropping anyway.","error"),["lib-add-save-crop","lib-add-save-crop-bottom"].forEach(id=>{$(id)&&($(id).disabled=!0)}),setLibAddStatus(`Cropping ${start.toFixed(2)}s to ${end.toFixed(2)}s...`);try{const r=await fetch("/api/process",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:libAddState.id,start,end})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();loadLibAddAudio(d.id,d.duration,"Cropped audio"),toast("Crop applied","success");try{await transcribeLibAddCurrent("Cropped audio loaded and text recognised",d.id)}catch(e){toast("Crop applied, but recognition failed: "+e.message,"error"),setLibAddStatus("Cropped audio loaded; recognition failed")}}catch(e){toast("Crop failed: "+e.message,"error"),setLibAddStatus("Crop failed")}finally{["lib-add-save-crop","lib-add-save-crop-bottom"].forEach(id=>{$(id)&&($(id).disabled=!1)})}}$("lib-add-save-crop").addEventListener("click",applyLibAddCrop),$("lib-add-save-crop-bottom").addEventListener("click",applyLibAddCrop),["lib-add-start","lib-add-end"].forEach(id=>$(id).addEventListener("input",drawLibAddWave)),$("lib-add-play").addEventListener("click",()=>{if(!libAddState.id)return;libAddState.audio&&libAddState.audio.pause(),libAddState.audio=new Audio("/api/audio/"+libAddState.id);const start=parseFloat($("lib-add-start").value)||0,end=parseFloat($("lib-add-end").value)||libAddState.duration;libAddState.audio.currentTime=start,libAddState.audio.ontimeupdate=()=>{libAddState.audio.currentTime>=end&&libAddState.audio.pause()},libAddState.audio.play()}),$("lib-add-recognize").addEventListener("click",async()=>{if(!libAddState.id){toast("Load audio first","error");return}try{await transcribeLibAddCurrent("Text recognised")}catch(e){toast("Recognition failed: "+e.message,"error"),setLibAddStatus("Recognition failed")}}),$("lib-add-save").addEventListener("click",async()=>{var _a2,_b2;if(!libAddState.id){toast("Load audio first","error");return}const voiceId=$("lib-add-voice-id").value.trim()||`${$("lib-add-lang").value}_${$("lib-add-gender").value}_NewVoice`;if(!validateVoiceId(voiceId)){toast("Voice ID contains invalid characters","error");return}setLibAddStatus("Saving voice..."),$("lib-add-save").disabled=!0;try{const pr=await fetch("/api/process",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:libAddState.id,start:parseFloat($("lib-add-start").value)||0,end:parseFloat($("lib-add-end").value)||libAddState.duration})});if(!pr.ok){const e=await pr.json().catch(()=>({}));throw new Error(e.detail||pr.statusText)}const p=await pr.json();let transcript=$("lib-add-transcript").value.trim();if(!transcript&&(transcript=await transcribeLibAddCurrent("Final clip recognised; saving voice...",p.id),!transcript.trim()))throw new Error("Recognition returned no transcript; add text or try recognising again.");const sr=await fetch("/api/save",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:p.id,voice_id:voiceId,transcript})});if(!sr.ok){const e=await sr.json().catch(()=>({}));throw new Error(e.detail||sr.statusText)}if((_a2=libAddState.pendingSource)!=null&&_a2.imageUrl)try{await fetch("/api/voice/picture-url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:voiceId,image_url:libAddState.pendingSource.imageUrl})})}catch{}libAddState.pendingSource=null,setLibAddSourcePreview({}),(_b2=$("lib-add-panel"))==null||_b2.classList.remove("open"),toast("Voice saved: "+voiceId,"success"),setLibAddStatus("Voice saved"),await loadVoiceLibrary(),openSavedLibraryVoice(voiceId)}catch(e){toast("Save failed: "+e.message,"error"),setLibAddStatus("Save failed")}finally{$("lib-add-save").disabled=!1}}),$("show-disabled-cb").addEventListener("change",()=>{$("disabled-info").style.display=$("show-disabled-cb").checked?"":"none",renderVoiceList()}),["library-filter-lang","library-filter-sex","library-filter-type","library-filter-rating"].forEach(id=>{var _a2;(_a2=$(id))==null||_a2.addEventListener("change",()=>{readLibraryFilters(),renderVoiceList()})}),(_C=$("library-filter-text"))==null||_C.addEventListener("input",debounce(()=>{readLibraryFilters(),renderVoiceList()},180)),(_D=$("library-filter-tag"))==null||_D.addEventListener("change",function(){window._voiceTagFilter=this.value||null,renderVoiceList()}),(_E=$("library-filter-group"))==null||_E.addEventListener("change",function(){window._voiceGroupFilter=this.value||null,renderVoiceList()}),(_F=$("library-clear-filters"))==null||_F.addEventListener("click",()=>{window._voiceTagFilter=null,window._voiceGroupFilter=null;const tagSel=$("library-filter-tag"),groupSel=$("library-filter-group");tagSel&&(tagSel.value=""),groupSel&&(groupSel.value=""),clearLibraryFilters()}),(_G=$("library-tts-backend-select"))==null||_G.addEventListener("change",()=>{var _a2;status("Library TTS engine: "+(((_a2=backendById(libraryTtsBackend()))==null?void 0:_a2.label)||libraryTtsBackend()))});function renderVoiceList(){var _a2,_b2,_c2,_d2,_e2,_f2;const showDisabled=$("show-disabled-cb").checked,list=$("voice-list");list.innerHTML="",renderVoiceGroupsBar(),populateLibraryFilters(),readLibraryFilters();const _gtBtn=$("voice-group-tag-btn");if(_gtBtn){_gtBtn.classList.toggle("active",!!window._voiceGroupByTag);const ic=_gtBtn.querySelector(".mdi");ic&&(ic.className="mdi mdi-folder"+(window._voiceGroupByTag?"-open":"")+"-outline")}const _tvBtn=$("voice-table-view-btn");if(_tvBtn){_tvBtn.classList.toggle("active",!!window._voiceTableView),(_a2=document.querySelector(".voices-workbench"))==null||_a2.classList.toggle("table-view",!!window._voiceTableView);const editBtn=document.getElementById("voice-table-edit-btn");editBtn&&(editBtn.style.display=window._voiceTableView?"inline-flex":"none",editBtn.classList.toggle("active",!!window._voiceTableEditMode))}const cat=window._voiceSidebarCat||"all",enabledOk=v=>showDisabled||v.enabled!==!1;let filtered=_voices.filter(v=>cat==="cloned"?enabledOk(v)&&v.has_ref&&v.origin!=="designed":cat==="designed"?enabledOk(v)&&(v.origin==="designed"||!v.has_ref):cat==="favorites"?enabledOk(v)&&(v.rating||0)>=4:cat==="hidden"?v.enabled===!1:enabledOk(v));window._voiceGroupFilter&&(filtered=filtered.filter(v=>(v.group||"").trim()===window._voiceGroupFilter)),window._voiceTagFilter&&(filtered=filtered.filter(v=>String(v.tag||"").split(",").map(t=>t.trim()).includes(window._voiceTagFilter)));const visibleCount=filtered.length;filtered=filtered.filter(libraryFilterMatch);const filterCount=filtered.length;if(_libraryIssueFilter&&(filtered=filtered.filter(v=>libraryIssueMatch(v))),$("voice-count").textContent=filtered.length+" / "+_voices.length+" voices",filtered=filtered.slice().sort((a,b)=>{const av=getSortValue(a,_sortField),bv=getSortValue(b,_sortField);return avbv?_sortDir:0}),updateLibraryInsights(),_libraryIssueFilter){const note=document.createElement("div");note.className="library-filter-note",note.innerHTML=`${escHtml(filtered.length)} / ${escHtml(filterCount)} ${escHtml(libraryIssueLabel())}: ${escHtml(describeIssueVoices())}`,note.querySelector("button").addEventListener("click",()=>setLibraryIssueFilter("")),list.appendChild(note)}if(!filtered.length){if(_voices.length===0){const emptyEl=document.createElement("div");emptyEl.className="voices-empty-state",emptyEl.innerHTML=`

Wrong folder path?

Set the path where your .wav voice files live inside the container.

Map a host folder via docker-compose:
- /your/host/path:/voices:rw
or set VOICE_HOST_DIR=/your/host/path in the stack env.

or

Folder is empty?

Create your first voice from a recording or download ready-made voices.

`,list.appendChild(emptyEl),fetch("/api/settings").then(r=>r.json()).then(s=>{const inp=document.getElementById("voices-empty-scan-dir");inp&&(inp.value=s.voices_scan_dir||"/voices")}).catch(()=>{});let _vefCurrentPath="/";const vefBrowser=document.getElementById("vef-dir-browser"),vefDirList=document.getElementById("vef-dir-list"),vefCrumb=document.getElementById("vef-breadcrumb"),vefSelPath=document.getElementById("vef-selected-path");async function vefNavigate(path){_vefCurrentPath=path,vefDirList.innerHTML='Loading\u2026',vefSelPath&&(vefSelPath.textContent=path);try{const data=await fetch("/api/browse-dirs?path="+encodeURIComponent(path)).then(r=>r.json()),parts=data.path.split("/").filter(Boolean),crumbs=[{label:"/",path:"/"}];parts.forEach((p,i)=>crumbs.push({label:p,path:"/"+parts.slice(0,i+1).join("/")})),vefCrumb.innerHTML=crumbs.map((c,i)=>i${escHtml(c.label)}/`:`${escHtml(c.label)}`).join(""),vefCrumb.querySelectorAll(".vef-crumb-btn").forEach(b=>b.addEventListener("click",()=>vefNavigate(b.dataset.path))),data.dirs.length?(vefDirList.innerHTML=data.dirs.map(d=>``).join(""),vefDirList.querySelectorAll(".vef-dir-item").forEach(b=>b.addEventListener("click",()=>vefNavigate(b.dataset.path)))):vefDirList.innerHTML='No subdirectories here.',vefSelPath&&(vefSelPath.textContent=data.path),_vefCurrentPath=data.path}catch(e){vefDirList.innerHTML=`Error: ${escHtml(e.message)}`}}(_b2=document.getElementById("voices-empty-browse-btn"))==null||_b2.addEventListener("click",()=>{var _a3,_b3;const open=vefBrowser.hidden;if(vefBrowser.hidden=!open,open){const cur=((_b3=(_a3=document.getElementById("voices-empty-scan-dir"))==null?void 0:_a3.value)==null?void 0:_b3.trim())||"/voices";vefNavigate(cur)}}),(_c2=document.getElementById("vef-select-btn"))==null||_c2.addEventListener("click",()=>{const inp=document.getElementById("voices-empty-scan-dir");inp&&(inp.value=_vefCurrentPath),vefBrowser&&(vefBrowser.hidden=!0)}),(_d2=document.getElementById("voices-empty-save-btn"))==null||_d2.addEventListener("click",async()=>{var _a3;const inp=document.getElementById("voices-empty-scan-dir"),dir=(_a3=inp==null?void 0:inp.value)==null?void 0:_a3.trim();if(!dir)return;const btn=document.getElementById("voices-empty-save-btn");btn.disabled=!0,btn.textContent="Saving\u2026";try{const s=await fetch("/api/settings").then(r2=>r2.json());s.voices_scan_dir=dir;const r=await fetch("/api/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok)throw new Error((await r.json().catch(()=>({}))).detail||r.statusText);toast("Folder saved \u2014 reloading voices\u2026","success"),_libraryLoadPromise=null,await loadVoiceLibrary()}catch(e){toast("Failed: "+e.message,"error"),btn.disabled=!1,btn.textContent="Set & reload"}}),(_e2=document.getElementById("voices-goto-clone"))==null||_e2.addEventListener("click",()=>navTo("s-clone")),(_f2=document.getElementById("voices-goto-studio"))==null||_f2.addEventListener("click",()=>navTo("s-studio"))}else list.innerHTML+='
No voices match the current filters.
';return}if(window._voiceGroupByTag)renderVoicesByTag(list,filtered);else{const frag=document.createDocumentFragment();filtered.forEach(v=>frag.appendChild(makeVoiceRow(v))),list.appendChild(frag)}if(_pendingSelectId){const wrap=list.querySelector(`[data-id="${CSS.escape(_pendingSelectId)}"]`);wrap&&(_pendingSelectId=null,selectVoice(wrap))}syncSortHeaders()}function renderVoicesByTag(list,filtered){const UNTAG="\u2014 Untagged \u2014",groups=new Map;filtered.forEach(v=>{const tags=String(v.tag||"").split(",").map(t=>t.trim()).filter(Boolean);(tags.length?tags:[UNTAG]).forEach(t=>{groups.has(t)||groups.set(t,[]),groups.get(t).push(v)})});let collapsed={};try{collapsed=JSON.parse(localStorage.getItem("vl-tag-collapsed")||"{}")}catch{}const names=[...groups.keys()].sort((a,b)=>a===UNTAG?1:b===UNTAG?-1:a.toLowerCase().localeCompare(b.toLowerCase())),outer=document.createDocumentFragment();names.forEach(tag=>{const voices=groups.get(tag),isCol=!!collapsed[tag],sec=document.createElement("div");sec.className="vl-tag-group";const head=document.createElement("button");head.className="vl-tag-head"+(isCol?" collapsed":""),head.type="button",head.innerHTML=`${escHtml(tag)}${voices.length}`;const body=document.createElement("div");body.className="vl-tag-body",isCol&&(body.style.display="none");const rows=document.createDocumentFragment();voices.forEach(v=>rows.appendChild(makeVoiceRow(v))),body.appendChild(rows),head.addEventListener("click",()=>{const nowCol=body.style.display!=="none";body.style.display=nowCol?"none":"",head.classList.toggle("collapsed",nowCol),head.querySelector(".vl-tag-caret").className="mdi mdi-"+(nowCol?"chevron-right":"chevron-down")+" vl-tag-caret",head.querySelector(".mdi:nth-child(2)").className="mdi mdi-folder"+(nowCol?"":"-open")+"-outline";let c={};try{c=JSON.parse(localStorage.getItem("vl-tag-collapsed")||"{}")}catch{}c[tag]=nowCol,localStorage.setItem("vl-tag-collapsed",JSON.stringify(c))}),sec.appendChild(head),sec.appendChild(body),outer.appendChild(sec)}),list.appendChild(outer)}async function decodeVoiceAudio(v){const resp=await fetch(voiceFileUrl(v),{cache:"no-store"});if(!resp.ok)throw new Error(resp.statusText||"Audio not found");const data=await resp.arrayBuffer();return new(window.AudioContext||window.webkitAudioContext)().decodeAudioData(data.slice(0))}function drawOptimizerWave(canvas,buffer,start=0,end=buffer.duration){const dpr=window.devicePixelRatio||1,width=Math.max(1,canvas.clientWidth),height=Math.max(1,canvas.clientHeight);canvas.width=Math.round(width*dpr),canvas.height=Math.round(height*dpr);const ctx=canvas.getContext("2d");ctx.setTransform(dpr,0,0,dpr,0,0),ctx.clearRect(0,0,width,height),ctx.fillStyle=getComputedStyle(document.documentElement).getPropertyValue("--bg")||"#111",ctx.fillRect(0,0,width,height);const data=buffer.getChannelData(0),step=Math.max(1,Math.floor(data.length/width)),mid=height/2;ctx.strokeStyle="#89b4fa",ctx.lineWidth=1,ctx.beginPath();for(let x=0;xmax&&(max=v)}ctx.moveTo(x,mid+min*mid*.92),ctx.lineTo(x,mid+max*mid*.92)}ctx.stroke();const sx=Math.max(0,Math.min(width,start/buffer.duration*width)),ex=Math.max(sx,Math.min(width,end/buffer.duration*width));ctx.fillStyle="rgba(166,227,161,.18)",ctx.fillRect(sx,0,ex-sx,height),ctx.strokeStyle="#a6e3a1",ctx.lineWidth=2,ctx.strokeRect(sx+.5,.5,Math.max(1,ex-sx-1),height-1);const selectedSec=Math.max(0,end-start);if(ex-sx>54&&selectedSec>0){const label=`${selectedSec.toFixed(1)}s`;ctx.font="12px ui-monospace, Menlo, Consolas, monospace";const textW=ctx.measureText(label).width+14,tx=Math.max(sx+6,Math.min(ex-textW-6,sx+(ex-sx-textW)/2));ctx.fillStyle="rgba(30,30,46,.78)",ctx.fillRect(tx,6,textW,22),ctx.fillStyle="#ffffff",ctx.fillText(label,tx+7,21)}const handleW=12;ctx.fillStyle="#3d5ce8",ctx.strokeStyle="#ffffff",[sx,ex].forEach((x,idx)=>{const hx=Math.max(0,Math.min(width-handleW,x-handleW/2));ctx.fillRect(hx,0,handleW,height),ctx.strokeRect(hx+.5,.5,Math.max(1,handleW-1),height-1),ctx.fillStyle="#ffffff",ctx.fillRect(hx+3,Math.max(12,height/2-11),2,22),ctx.fillRect(hx+7,Math.max(12,height/2-11),2,22),ctx.fillStyle="#3d5ce8"})}function makeVoiceRow(v){var _a2;const wrap=document.createElement("div");wrap.className="vl-row"+(v.enabled===!1?" vr-disabled":""),wrap.dataset.id=v.id;const color=avatarColor(v.id);wrap.dataset.color=color,wrap.dataset.hasRef=v.has_ref?"true":"false",wrap.dataset.dbfs=fmtDbfs(v),wrap.dataset.lang=v.lang||v.id.split("_")[0].toUpperCase();const langCode=v.lang||v.id.split("_")[0].toUpperCase(),_idParts=String(v.id).split("_"),dispName=v.name&&String(v.name).trim()?String(v.name).trim():_idParts.length>1?_idParts[_idParts.length-1]:v.id,langOpts=FLAG_OPTIONS[langCode]||[],flagOpts=langOpts.length>1?langOpts:ALL_FLAGS,currentFlag=v.flag||LANG_FLAG_DEFAULT[langCode]||"",flagEmoji=currentFlag?cc2flag(currentFlag):'',flagCode=currentFlag?ccDisplay(currentFlag):"?";wrap.dataset.flagEmoji=flagEmoji,wrap.dataset.flagCc=currentFlag||"",wrap.dataset.rating=v.rating||0,wrap.dataset.hasPicture=v.has_picture?"true":"false";const fileType=voiceFileType(v),dbfs=fmtDbfs(v),dbTitle=v.loudness?`avg ${dbfs} dBFS${v.loudness.peak_dbfs!=null?", peak "+Number(v.loudness.peak_dbfs).toFixed(1)+" dBFS":""}`:"",benchText=fmtBenchmark(v),benchTitle=benchmarkTitle(v),benchCls=benchmarkClass(v),genderMap={F:"\u2640",M:"\u2642",N:"\u26A5","":"?"},genderMapLetter={F:"F",M:"M",N:"D","":"?"},genderLabel={F:"Female",M:"Male",N:"Diverse","":"\u2014"},genderClass={F:"g-f",M:"g-m",N:"g-n","":"g-n"},gender=v.gender||"",starsHtml=[1,2,3,4,5].map(i=>``).join(""),picSrc=v.has_picture?`/api/voice/picture/${encodeURIComponent(v.id)}`:null,pickerHtml=flagOpts.length>1?flagOpts.map(([cc,label])=>`${cc2flag(cc)}${ccDisplay(cc)}`).join(""):'No regional variants',isClone=v.has_ref&&v.origin!=="designed",initial=v.id[0]?v.id[0].toUpperCase():"?",flagIconHtml=currentFlag?``:null,silHtml=``,iconHtml=window.voiceAvatarIcon?window.voiceAvatarIcon(v.avatar,34):null,avatarClass=picSrc?"vl-avatar vl-avatar-photo":iconHtml?"vl-avatar vl-avatar-icon":"vl-avatar vl-avatar-sil",avatarStyle=picSrc||iconHtml?"":`style="background:${color}"`,avatarContent=picSrc?``:iconHtml||silHtml;wrap.innerHTML=`
${avatarContent}
${window._voiceTableEditMode?``:`${escHtml(dispName)}`}
${flagEmoji} ${gender?`${genderMapLetter[gender]||"?"}`:""} ${benchText!=="-"?` ${escHtml(benchText.split(" ")[0])}`:""}
${starsHtml}
${isClone?"Clone":"Design"}
${window._voiceTableEditMode?``:`${flagEmoji} ${escHtml(langCode)}`}
${window._voiceTableEditMode?``:`${genderMap[gender]||"?"} ${escHtml(genderLabel[gender]||"\u2014")}`}
${escHtml(fmtFactor(v))}
${escHtml(fmtWpm(v))}
${dbfs} dB
${isClone?"Clone":"Design"}
${window._voiceTableEditMode?``:starsHtml}
${escHtml(_displaySource(v)||"-")}
${v.seed!=null?"#"+v.seed:"Auto"}
${window._voiceTableEditMode?``:escHtml(v.note||"")}
${window._voiceTableEditMode?``:escHtml(v.tag||"")}
${window._voiceTableEditMode?``:``}
${picSrc?``:'
'}
${flagEmoji} ${flagCode}
${pickerHtml}
${genderMap[gender]||"?"} ${genderLabel[gender]||"\u2014"}
${escHtml(v.id)}
${escHtml(fileType.toUpperCase())}
${fmtDuration(v.duration)}
${escHtml(dbfs)}
${escHtml(benchText)}
${starsHtml}
Delete? ${escHtml(v.id)}
Reference audio · crop
Crop the saved WAV to 3–20 s of clean speech.
Reference transcript
The spoken text that matches this voice recording.
Voice match
Compare the saved WAV with a fresh TTS synthesis.

Compare the saved reference WAV with a fresh synthesis of the same reference text. Restart TTS first after editing a voice, otherwise the backend may still use a cached version.

WAV file
Synthesized reference text
${isClone?"":`
Style variation
Create a styled variant and save it as a new voice (CustomVoice only).

Preview first. Saving creates a new active WAV voice from the current reference text. Same-voice style only works when the selected backend knows this voice and honors instruct; CustomVoice is style-aware; Base/Streaming are fastest but often ignore style.

`}
Loudness
Normalize the volume of the reference audio file.
Click the pencil to load waveform and tools.
`;const vrLengthEl=wrap.querySelector(".vr-length");hydrateVoiceDuration(v,vrLengthEl);const photoCell=wrap.querySelector(".vr-photo"),photoInput=wrap.querySelector(".photo-input"),updatePhotoImg=()=>{const ts=Date.now(),imgSrc=`/api/voice/picture/${encodeURIComponent(v.id)}?t=${ts}`,img=document.createElement("img");img.src=imgSrc,img.alt="",photoCell.innerHTML="",photoCell.appendChild(img),photoCell.appendChild(photoInput);const compactAvatar=wrap.querySelector(".vl-avatar");compactAvatar&&(compactAvatar.className="vl-avatar vl-avatar-photo",compactAvatar.style.background="",compactAvatar.innerHTML=``);const inspectorAvatar=document.querySelector(".inspector-avatar");inspectorAvatar&&wrap.classList.contains("vr-selected")&&(inspectorAvatar.classList.add("insp-avatar-photo"),inspectorAvatar.style.background="",inspectorAvatar.innerHTML=``),v.has_picture=!0};photoCell.addEventListener("update-photo",updatePhotoImg),photoCell.addEventListener("click",()=>photoInput.click()),photoInput.addEventListener("change",async()=>{if(!photoInput.files.length)return;const fd=new FormData;fd.append("voice_id",v.id),fd.append("file",photoInput.files[0]);try{const r=await fetch("/api/voice/picture",{method:"POST",body:fd});if(!r.ok)throw new Error((await r.json()).detail);updatePhotoImg(),toast("Photo uploaded","success")}catch(e){toast("Photo upload failed: "+e.message,"error")}}),photoCell.addEventListener("dragenter",e=>{e.preventDefault(),photoCell.classList.add("drag-over")}),photoCell.addEventListener("dragover",e=>{e.preventDefault(),photoCell.classList.add("drag-over")}),photoCell.addEventListener("dragleave",()=>photoCell.classList.remove("drag-over")),photoCell.addEventListener("drop",async e=>{if(e.preventDefault(),photoCell.classList.remove("drag-over"),e.dataTransfer.files&&e.dataTransfer.files.length>0){photoInput.files=e.dataTransfer.files,photoInput.dispatchEvent(new Event("change"));return}let url=e.dataTransfer.getData("text/uri-list");if(!url){const html=e.dataTransfer.getData("text/html");if(html){const match=html.match(/src=["'](.*?)["']/);match&&(url=match[1])}}if(url||(url=e.dataTransfer.getData("text/plain")),url&&/^https?:\/\//i.test(url)){status("Downloading picture from URL...");try{const r=await fetch("/api/voice/picture-url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:v.id,image_url:url})});if(!r.ok)throw new Error((await r.json()).detail);updatePhotoImg(),toast("Photo saved from URL","success"),status("Photo saved successfully")}catch(err){toast("Photo URL download failed: "+err.message,"error"),status("Photo URL download failed")}}});const normalizeBtn=wrap.querySelector(".normalize-voice-btn"),dbValue=wrap.querySelector(".vr-db-value"),dbCell=wrap.querySelector(".vr-db");normalizeBtn.addEventListener("click",async()=>{var _a3;const target=libraryTargetDb();if(confirm(`Normalize "${v.id}" to ${target} dBFS?`)){normalizeBtn.disabled=!0,status("Normalizing "+v.id+"\u2026");try{const r=await fetch("/api/voice/normalize",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:v.id,path:v.path,target_dbfs:target})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();v.loudness=d.loudness||v.loudness,v.duration=(_a3=d.duration)!=null?_a3:v.duration,v.file_type=d.file_type||v.file_type,v.path=d.path||v.path,v.needs_tts_restart=!0,markVoiceAudioChanged(v),dbValue.textContent=fmtDbfs(v),dbCell.title=v.loudness?`avg ${fmtDbfs(v)} dBFS${v.loudness.peak_dbfs!=null?", peak "+Number(v.loudness.peak_dbfs).toFixed(1)+" dBFS":""}`:"",vrLengthEl&&(vrLengthEl.textContent=fmtDuration(v.duration)),toast("Normalized: "+v.id,"success"),status(`Normalized ${v.id} to ${target} dBFS. Restart TTS before rebenchmarking.`)}catch(e){toast("Normalize failed: "+e.message,"error"),status("Normalize failed")}finally{normalizeBtn.disabled=!1}}});const flagEmojiEl=wrap.querySelector(".flag-emoji"),flagCodeEl=wrap.querySelector(".flag-code"),flagPicker=wrap.querySelector(".flag-picker");wrap.querySelector(".vr-flag").addEventListener("click",e=>{e.stopPropagation(),document.querySelectorAll(".flag-picker.open").forEach(fp=>{fp!==flagPicker&&fp.classList.remove("open")}),flagPicker.classList.toggle("open")}),flagPicker.querySelectorAll(".flag-opt").forEach(opt=>{opt.addEventListener("click",async e=>{e.stopPropagation();const cc=opt.dataset.cc;flagPicker.classList.remove("open"),flagEmojiEl.textContent=cc2flag(cc),flagCodeEl.textContent=ccDisplay(cc),flagPicker.querySelectorAll(".flag-opt").forEach(o=>o.classList.toggle("active",o.dataset.cc===cc)),v.flag=cc,await saveMeta(v.id,{flag:cc})})});const gBadge=wrap.querySelector(".gender-badge");gBadge.addEventListener("click",async()=>{const cycle=["F","M","N"];v.gender=cycle[(cycle.indexOf(v.gender||"F")+1)%3],gBadge.innerHTML=`${genderMap[v.gender]||"?"}${genderLabel[v.gender]||"\u2014"}`,gBadge.className="gender-badge "+genderClass[v.gender],await saveMeta(v.id,{gender:v.gender})});const nameText=wrap.querySelector(".vr-name-text"),renameConf=wrap.querySelector(".rename-confirm"),nameInput=wrap.querySelector(".vr-name-input"),renameOk=wrap.querySelector(".rename-ok"),renameCancel=wrap.querySelector(".rename-cancel"),startRename=()=>{nameText.style.display="none",renameConf.classList.add("show"),nameInput.focus(),nameInput.select()};nameText.addEventListener("dblclick",startRename);const cancelRename=()=>{nameText.style.display="",renameConf.classList.remove("show"),nameInput.value=v.id};renameCancel.addEventListener("click",cancelRename);const doRename=async()=>{const newId=nameInput.value.trim();if(!newId||newId===v.id){cancelRename();return}if(!/^[A-Za-z0-9_\-\.]+$/.test(newId)){toast("Invalid characters in name","error");return}try{const r=await fetch("/api/voice/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({old_id:v.id,new_id:newId})});if(!r.ok){const e=await r.json();throw new Error(e.detail)}const d=await r.json();v.id=newId,d.path&&(v.path=d.path),d.file_type&&(v.file_type=d.file_type),nameText.textContent=newId,nameText.title=newId,nameText.style.display="",renameConf.classList.remove("show"),wrap.dataset.id=newId,nameInput.value=newId,toast("Renamed to "+newId,"success")}catch(e){toast("Rename failed: "+e.message,"error")}};renameOk.addEventListener("click",doRename),nameInput.addEventListener("keydown",e=>{e.key==="Enter"&&doRename(),e.key==="Escape"&&cancelRename()}),wrap.querySelector(".vl-compact").addEventListener("click",function(e){e.target.closest("button")||selectVoice(wrap)});const editAudioBtn=wrap.querySelector(".edit-audio-btn"),optPanel=wrap.querySelector(".vr-optimizer"),vrTypeEl=wrap.querySelector(".vr-type"),optCanvas=wrap.querySelector(".opt-wave"),optStart=wrap.querySelector(".opt-start"),optEnd=wrap.querySelector(".opt-end"),optTranscript=wrap.querySelector(".opt-transcript"),optTargetDb=wrap.querySelector(".opt-target-db"),optStyleInstruct=wrap.querySelector(".opt-style-instruct"),optStyleBackend=wrap.querySelector(".opt-style-backend"),optStyleVoiceId=wrap.querySelector(".opt-style-voice-id"),optCompareBackend=wrap.querySelector(".opt-compare-backend"),optPlayReferenceBtn=wrap.querySelector(".opt-play-reference"),optSynthReferenceBtn=wrap.querySelector(".opt-synth-reference"),optCompareRefAudio=wrap.querySelector(".opt-compare-ref-audio"),optCompareSynthAudio=wrap.querySelector(".opt-compare-synth-audio"),optPreviewStyleBtn=wrap.querySelector(".opt-preview-style"),optSaveStyleBtn=wrap.querySelector(".opt-save-style"),optStyleAudio=wrap.querySelector(".opt-style-audio"),optStatus=wrap.querySelector(".opt-status"),optSaveTextBtn=wrap.querySelector(".opt-save-text"),optRestartTtsBtn=wrap.querySelector(".opt-restart-tts"),optRebenchmarkBtn=wrap.querySelector(".opt-rebenchmark"),optRestartNote=wrap.querySelector(".opt-restart-note");let optState={loaded:!1,id:null,duration:0,buffer:null,audio:null,compareSynthUrl:null};const setOptStatus=msg=>{optStatus.textContent=msg,status(msg)},setVoiceRestartState=(required,msg="")=>{v.needs_tts_restart=required,optPanel.classList.toggle("opt-restart-needed",required),optRestartNote.hidden=!required,optRestartNote.textContent=required?"Restart TTS before benchmarking; the backend may still have the old voice cached.":"",optRebenchmarkBtn.title=required?"Restart TTS first, otherwise the benchmark may use a cached voice":"Benchmark this voice",benchmarkOneBtn.title=required?"Restart TTS first, otherwise the benchmark may use a cached voice":"Benchmark this voice",msg&&setOptStatus(msg)},markTtsRestartRequired=msg=>setVoiceRestartState(!0,msg),refreshOptimizerFromVoice=async()=>{var _a3;markVoiceAudioChanged(v),optState.loaded=!1,optState.buffer=null,optState.id=null,await loadOptimizer(),vrLengthEl&&(vrLengthEl.textContent=fmtDuration(v.duration)),vrLengthEl&&(vrLengthEl.title=String((_a3=v.duration)!=null?_a3:"")),dbValue.textContent=fmtDbfs(v),dbCell.title=v.loudness?`avg ${fmtDbfs(v)} dBFS${v.loudness.peak_dbfs!=null?", peak "+Number(v.loudness.peak_dbfs).toFixed(1)+" dBFS":""}`:""},saveOptimizerText=async()=>{const transcript=optTranscript.value.trim();setOptStatus("Saving reference text..."),v.transcript=transcript,refInput.value=transcript,refInput.title=transcript,refTranscribeBtn.style.display=transcript?"none":"",await saveMeta(v.id,{transcript}),markTtsRestartRequired("Reference text saved. Restart TTS before rebenchmarking."),toast("Reference text saved: "+v.id,"success")},redrawOpt=()=>{!optState.buffer||optCanvas.clientWidth<4||drawOptimizerWave(optCanvas,optState.buffer,parseFloat(optStart.value)||0,parseFloat(optEnd.value)||optState.duration)};new ResizeObserver(()=>redrawOpt()).observe(optCanvas);const syncCompareReferenceAudio=()=>{if(!optState.id||!optCompareRefAudio)return;const src="/api/audio/"+optState.id;optCompareRefAudio.src.endsWith(src)||(optCompareRefAudio.src=src,optCompareRefAudio.load());const stopAtEnd=()=>{const end=parseFloat(optEnd.value)||optState.duration;optCompareRefAudio.currentTime>=end&&optCompareRefAudio.pause()};optCompareRefAudio.ontimeupdate=stopAtEnd},optWaveTimeFromEvent=e=>{const rect=optCanvas.getBoundingClientRect(),x=Math.max(0,Math.min(rect.width,e.clientX-rect.left));return optState.duration?x/Math.max(1,rect.width)*optState.duration:0},setOptCropRange=(start,end)=>{start=Math.max(0,Math.min(optState.duration||0,Number(start)||0)),end=Math.max(0,Math.min(optState.duration||0,Number(end)||0)),end{const rect=optCanvas.getBoundingClientRect(),duration=Math.max(.01,optState.duration||.01),sx=(parseFloat(optStart.value)||0)/duration*rect.width,ex=(parseFloat(optEnd.value)||optState.duration||0)/duration*rect.width;return{x:e.clientX-rect.left,sx,ex}},optWaveDragMode=e=>{const{x,sx,ex}=optWaveSelectionPixels(e),hit=18,nearStart=Math.abs(x-sx)<=hit,nearEnd=Math.abs(x-ex)<=hit;return nearStart&&nearEnd?Math.abs(x-sx)<=Math.abs(x-ex)?"start":"end":nearStart?"start":nearEnd?"end":x>sx&&x{let drag=null;optCanvas.addEventListener("pointerdown",e=>{var _a3;if(!optState.buffer||!optState.duration)return;e.preventDefault(),(_a3=optCanvas.setPointerCapture)==null||_a3.call(optCanvas,e.pointerId);const mode=optWaveDragMode(e),currentStart=parseFloat(optStart.value)||0,currentEnd=parseFloat(optEnd.value)||optState.duration;drag={mode,anchor:optWaveTimeFromEvent(e),start:currentStart,end:currentEnd,length:Math.max(.05,currentEnd-currentStart)},optCanvas.style.cursor=mode==="move"?"grabbing":"ew-resize",mode==="new"&&setOptCropRange(drag.anchor,drag.anchor)}),optCanvas.addEventListener("pointermove",e=>{if(!optState.buffer||!optState.duration)return;if(!drag){const mode=optWaveDragMode(e);optCanvas.style.cursor=mode==="move"?"grab":mode==="start"||mode==="end"?"ew-resize":"crosshair";return}e.preventDefault();const t=optWaveTimeFromEvent(e);if(drag.mode==="start")setOptCropRange(Math.min(t,drag.end-.05),drag.end);else if(drag.mode==="end")setOptCropRange(drag.start,Math.max(t,drag.start+.05));else if(drag.mode==="move"){let start=t-(drag.anchor-drag.start);start=Math.max(0,Math.min((optState.duration||0)-drag.length,start)),setOptCropRange(start,start+drag.length)}else setOptCropRange(drag.anchor,t)});const finish=e=>{var _a3;drag&&((_a3=optCanvas.releasePointerCapture)==null||_a3.call(optCanvas,e.pointerId),drag=null,optCanvas.style.cursor="crosshair")};optCanvas.addEventListener("pointerup",finish),optCanvas.addEventListener("pointercancel",finish),optCanvas.addEventListener("pointerleave",()=>{drag||(optCanvas.style.cursor="crosshair")})})();const loadOptimizer=async()=>{if(optState.loaded)return;setOptStatus("Loading voice optimizer\u2026");const d=await loadLibraryVoiceAudio(v);optState.id=d.id,optState.duration=d.duration,v.duration=d.duration,optState.buffer=await decodeVoiceAudio(v),optState.loaded=!0,optStart.value="0.00",optEnd.value=d.duration.toFixed(2),optEnd.max=d.duration.toFixed(2),optTranscript.value=d.transcript||v.transcript||"",redrawOpt(),syncCompareReferenceAudio(),setVoiceRestartState(!!v.needs_tts_restart),setOptStatus(v.needs_tts_restart?"Optimizer ready. Restart TTS before benchmarking this edit.":"Optimizer ready")};if(wrap._loadOptimizer=loadOptimizer,wrap._redrawOpt=redrawOpt,editAudioBtn.addEventListener("click",async()=>{editAudioBtn.disabled=!0;try{const opening=!wrap.classList.contains("edit-open");document.querySelectorAll(".vl-row.edit-open").forEach(r=>{r!==wrap&&r.classList.remove("edit-open")}),wrap.classList.toggle("edit-open",opening),opening&&(await loadOptimizer(),wrap.scrollIntoView({behavior:"smooth",block:"nearest"}))}catch(e){toast("Edit load failed: "+e.message,"error"),status("Edit load failed")}finally{editAudioBtn.disabled=!1}}),[optStart,optEnd].forEach(inp=>inp.addEventListener("input",()=>{redrawOpt(),syncCompareReferenceAudio()})),optStyleInstruct==null||optStyleInstruct.addEventListener("input",()=>{optStyleVoiceId.value.trim()||(optStyleVoiceId.value=suggestedStyleVoiceId(v.id,optStyleInstruct.value))}),optStyleBackend==null||optStyleBackend.addEventListener("change",()=>updateStyleBackendHelp(wrap)),optCompareBackend.addEventListener("change",()=>{var _a3;return setOptStatus(`Comparison backend: ${((_a3=optCompareBackend.options[optCompareBackend.selectedIndex])==null?void 0:_a3.textContent)||optCompareBackend.value}`)}),optCompareBackend.value===""&&(optCompareBackend.innerHTML=styleBackendOptions("voice_clone"),optCompareBackend.disabled=!availableTtsBackends().length),optStyleBackend&&updateStyleBackendHelp(wrap),wrap.querySelector(".opt-db-minus").addEventListener("click",()=>{optTargetDb.value=(Number(optTargetDb.value||-20)-1).toFixed(1)}),wrap.querySelector(".opt-db-plus").addEventListener("click",()=>{optTargetDb.value=(Number(optTargetDb.value||-20)+1).toFixed(1)}),wrap.querySelector(".opt-db-auto").addEventListener("click",()=>{optTargetDb.value="-20.0"}),wrap.querySelector(".opt-play").addEventListener("click",async()=>{try{await loadOptimizer(),optState.audio&&optState.audio.pause(),optState.audio=new Audio("/api/audio/"+optState.id),optState.audio.currentTime=parseFloat(optStart.value)||0;const end=parseFloat(optEnd.value)||optState.duration;optState.audio.ontimeupdate=()=>{optState.audio.currentTime>=end&&optState.audio.pause()},optState.audio.play()}catch(e){toast("Preview failed: "+e.message,"error")}}),optPlayReferenceBtn.addEventListener("click",async()=>{try{await loadOptimizer(),syncCompareReferenceAudio(),optCompareRefAudio.currentTime=parseFloat(optStart.value)||0,await optCompareRefAudio.play().catch(()=>{}),setOptStatus("Playing reference WAV selection for comparison.")}catch(e){toast("Reference playback failed: "+e.message,"error")}}),optSynthReferenceBtn.addEventListener("click",async()=>{const text=optTranscript.value.trim();if(!text){toast("Enter reference text first","error"),optTranscript.focus();return}if(v.needs_tts_restart){if(!confirm("This voice is still marked as needing a TTS restart. If you already restarted TTS manually, clear the restart flags and synthesize now?")){setOptStatus("Restart TTS before synthesizing this comparison, or clear the flag after a manual restart.");return}try{const d=await clearTtsRestartFlags();setVoiceRestartState(!1,`Restart flags cleared (${d.cleared_restart_flags||0}). Synthesizing comparison...`),toast("Restart flags cleared","success")}catch(e){toast("Could not clear restart flags: "+e.message,"error"),setOptStatus("Could not clear restart flags");return}}optSynthReferenceBtn.disabled=!0;try{await loadOptimizer(),setOptStatus("Synthesizing reference text for comparison...");const source=await createTtsAudioSource(v.id,text,optCompareBackend.value,"settings","");optState.compareSynthUrl&&URL.revokeObjectURL(optState.compareSynthUrl),optCompareSynthAudio.src=source.url,optState.compareSynthUrl=source.streaming?null:source.url,await optCompareSynthAudio.play().catch(()=>{}),setOptStatus(source.streaming?"Streaming synthesized comparison.":"Synthesized comparison ready.")}catch(e){toast("Synthesis comparison failed: "+e.message,"error"),setOptStatus("Synthesis comparison failed")}finally{optSynthReferenceBtn.disabled=!1}}),wrap.querySelector(".opt-auto-trim").addEventListener("click",async()=>{try{await loadOptimizer();const d=await clientAutoTrimBounds(optState.id);optStart.value=Number(d.start).toFixed(2),optEnd.value=Number(d.end).toFixed(2),redrawOpt(),setOptStatus(d.reason||"Auto trim ready")}catch(e){toast("Auto trim failed: "+e.message,"error")}}),wrap.querySelector(".opt-recognize").addEventListener("click",async()=>{try{await loadOptimizer();const r=await fetch("/api/transcribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:optState.id})});if(!r.ok){const e=await r.json();throw new Error(e.detail||r.statusText)}const d=await r.json();optTranscript.value=d.text||"",setOptStatus("Reference text recognised. Review it, then Save text.")}catch(e){toast("Recognition failed: "+e.message,"error")}}),optSaveTextBtn.addEventListener("click",async()=>{optSaveTextBtn.disabled=!0;try{await saveOptimizerText()}catch(e){toast("Save text failed: "+e.message,"error"),setOptStatus("Save text failed")}finally{optSaveTextBtn.disabled=!1}}),wrap.querySelector(".opt-save-crop").addEventListener("click",async()=>{var _a3;try{await loadOptimizer();const cropStart=Math.max(0,parseFloat(optStart.value)||0),cropEnd=Math.min(optState.duration,parseFloat(optEnd.value)||optState.duration);if(cropStart<=.01&&cropEnd>=optState.duration-.05){setOptStatus("No crop range selected. Adjust Start or End first, then Save crop."),toast("No crop range selected","error");return}if(cropEnd<=cropStart+.1){setOptStatus("Crop range is too short."),toast("Crop range is too short","error");return}setOptStatus(`Saving crop ${cropStart.toFixed(2)}s -> ${cropEnd.toFixed(2)}s...`);const pr=await fetch("/api/process",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:optState.id,start:cropStart,end:cropEnd})});if(!pr.ok){const e=await pr.json();throw new Error(e.detail||pr.statusText)}const p=await pr.json(),rr=await fetch("/api/voice-replace",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:p.id,voice_id:v.id,path:v.path,transcript:optTranscript.value})});if(!rr.ok){const e=await rr.json().catch(()=>({}));throw new Error(e.detail||rr.statusText)}const saved=await rr.json();v.transcript=optTranscript.value,v.duration=(_a3=saved.duration)!=null?_a3:p.duration,saved.loudness&&(v.loudness=saved.loudness),saved.path&&(v.path=saved.path),saved.file_type&&(v.file_type=saved.file_type),markVoiceAudioChanged(v),refInput.value=v.transcript,refInput.title=v.transcript,refTranscribeBtn.style.display=v.transcript?"none":"",vrTypeEl&&(vrTypeEl.textContent=voiceFileType(v).toUpperCase(),vrTypeEl.title=voiceFileType(v)),await refreshOptimizerFromVoice(),toast("Voice crop saved: "+v.id,"success"),markTtsRestartRequired(saved.backup?"Crop saved and loaded. Restart TTS before rebenchmarking; undo is available.":"Crop saved and loaded. Restart TTS before rebenchmarking.")}catch(e){toast("Save crop failed: "+e.message,"error"),setOptStatus("Save crop failed")}}),optStyleInstruct){const styleVariationInput=()=>{const style=optStyleInstruct.value.trim(),text=optTranscript.value.trim()||benchmarkSampleText(),newId=optStyleVoiceId.value.trim()||suggestedStyleVoiceId(v.id,style);return style?text?/^[A-Za-z0-9_\-.]+$/.test(newId)?{style,text,newId,backend:optStyleBackend.value}:(toast("Invalid characters in new voice ID","error"),optStyleVoiceId.focus(),null):(toast("Enter reference text first","error"),optTranscript.focus(),null):(toast("Enter a style instruction first","error"),optStyleInstruct.focus(),null)};optPreviewStyleBtn.addEventListener("click",async()=>{const input=styleVariationInput();if(input){optPreviewStyleBtn.disabled=!0;try{setOptStatus("Synthesizing style preview...");const blob=await fetchTtsPreviewBlob(v.id,input.text,"wav",input.style,input.backend);optStyleAudio.src&&URL.revokeObjectURL(optStyleAudio.src),optStyleAudio.src=URL.createObjectURL(blob),optStyleAudio.style.display="",await optStyleAudio.play().catch(()=>{}),setOptStatus("Style preview ready. If it sounds right, save it as a new voice.")}catch(e){toast("Style preview failed: "+e.message,"error"),setOptStatus("Style preview failed")}finally{optPreviewStyleBtn.disabled=!1}}}),optSaveStyleBtn.addEventListener("click",async()=>{const input=styleVariationInput();if(input){optSaveStyleBtn.disabled=!0;try{setOptStatus(`Synthesizing style variation ${input.newId}...`);const r=await fetch("/api/tts-style-variation",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({source_voice:v.id,voice_id:input.newId,text:input.text,instruct:input.style,backend:input.backend})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();toast("Style variation saved: "+d.voice_id,"success"),setOptStatus("Style variation saved. Restart TTS so the backend scans the new voice."),await loadVoiceLibrary(),renderIntegrationSnippets()}catch(e){toast("Style variation failed: "+e.message,"error"),setOptStatus("Style variation failed")}finally{optSaveStyleBtn.disabled=!1}}})}wrap.querySelector(".opt-undo").addEventListener("click",async()=>{var _a3;if(confirm(`Restore the original backup for "${v.id}"?`))try{setOptStatus("Restoring original\u2026");const r=await fetch("/api/voice/undo",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:v.id,path:v.path})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();v.duration=(_a3=d.duration)!=null?_a3:v.duration,v.loudness=d.loudness||v.loudness,v.path=d.path||v.path,v.file_type=d.file_type||v.file_type,markVoiceAudioChanged(v),vrTypeEl&&(vrTypeEl.textContent=voiceFileType(v).toUpperCase(),vrTypeEl.title=voiceFileType(v)),await refreshOptimizerFromVoice(),toast("Original restored: "+v.id,"success"),markTtsRestartRequired("Original restored. Restart TTS before rebenchmarking.")}catch(e){toast("Undo failed: "+e.message,"error"),setOptStatus("Undo failed")}}),wrap.querySelector(".opt-save-volume").addEventListener("click",async()=>{var _a3;try{setOptStatus("Saving volume\u2026");const r=await fetch("/api/voice/normalize",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:v.id,path:v.path,target_dbfs:Number(optTargetDb.value||-20)})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();v.loudness=d.loudness||v.loudness,v.duration=(_a3=d.duration)!=null?_a3:v.duration,d.path&&(v.path=d.path),d.file_type&&(v.file_type=d.file_type),markVoiceAudioChanged(v),dbValue.textContent=fmtDbfs(v),dbCell.title=v.loudness?`avg ${fmtDbfs(v)} dBFS${v.loudness.peak_dbfs!=null?", peak "+Number(v.loudness.peak_dbfs).toFixed(1)+" dBFS":""}`:"",await refreshOptimizerFromVoice(),toast("Volume saved: "+v.id,"success"),markTtsRestartRequired("Volume saved. Restart TTS before rebenchmarking this voice.")}catch(e){toast("Volume save failed: "+e.message,"error"),setOptStatus("Volume save failed")}}),optRestartTtsBtn.addEventListener("click",async()=>{optRestartTtsBtn.disabled=!0;try{setOptStatus("Restarting WAV backends (Voice Clone + Streaming)\u2026");const r=await fetch("/api/tts/restart",{method:"POST"});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();_voices.forEach(voice=>{voice.needs_tts_restart=!1}),updateLibraryInsights();const names=(d.restarted||[]).join(", ")||"containers",errTxt=(d.errors||[]).length?` (errors: ${d.errors.join("; ")})`:"";setVoiceRestartState(!1,`Restarted: ${names}${errTxt}. Rebenchmark now uses the edited voice.`),toast(`TTS restarted: ${names}`,"success")}catch(e){toast("Restart TTS failed: "+e.message,"error"),setOptStatus("Restart TTS failed: "+e.message)}finally{optRestartTtsBtn.disabled=!1}});const refInput=wrap.querySelector(".vr-ref input"),refTranscribeBtn=wrap.querySelector(".ref-transcribe-btn");refInput.addEventListener("input",debounce(async()=>{v.transcript=refInput.value,refInput.title=v.transcript,refTranscribeBtn.style.display=v.transcript?"none":"",await saveMeta(v.id,{transcript:v.transcript}),wrap.classList.contains("edit-open")?markTtsRestartRequired("Reference text saved. Restart TTS before rebenchmarking."):v.needs_tts_restart=!0},800)),refTranscribeBtn.addEventListener("click",async()=>{refTranscribeBtn.disabled=!0;try{const d=await loadLibraryVoiceAudio(v),tr=await fetch("/api/transcribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:d.id})});if(!tr.ok){const e=await tr.json();throw new Error(e.detail)}const text=await tr.json();v.transcript=text.text||"",refInput.value=v.transcript,refInput.title=v.transcript,refTranscribeBtn.style.display=v.transcript?"none":"",await saveMeta(v.id,{transcript:v.transcript}),v.needs_tts_restart=!0,toast("Reference text recognised; restart TTS before benchmarking","success")}catch(e){toast("Recognition failed: "+e.message,"error")}finally{refTranscribeBtn.disabled=!1}});const noteInput=wrap.querySelector(".vr-note input");noteInput.addEventListener("input",debounce(async()=>{v.note=noteInput.value,await saveMeta(v.id,{note:v.note})},800));const sourceInput=wrap.querySelector(".vr-source input");sourceInput.addEventListener("input",debounce(async()=>{v.origin=sourceInput.value.trim(),await saveMeta(v.id,{origin:v.origin});const tblCell=document.querySelector(`.vl-row[data-id="${CSS.escape(v.id)}"] .vl-tbl-source`);tblCell&&(tblCell.textContent=_displaySource(v)||"-")},800));const starSpans=wrap.querySelectorAll(".star");starSpans.forEach(s=>{s.addEventListener("click",async()=>{const val=parseInt(s.dataset.val),newRating=val===v.rating?0:val;v.rating=newRating,starSpans.forEach((ss,i)=>ss.classList.toggle("on",i{const val=parseInt(s.dataset.val);starSpans.forEach((ss,i)=>ss.classList.toggle("on",i{starSpans.forEach((ss,i)=>ss.classList.toggle("on",i<(v.rating||0)))})});const benchmarkOneBtn=wrap.querySelector(".benchmark-one-btn"),benchmarkThisVoice=async triggerBtn=>{if(v.needs_tts_restart&&!confirm("This voice changed since the last TTS restart. Benchmarking now may use the cached old voice. Continue anyway?")){setOptStatus("Restart TTS first, then rebenchmark this voice.");return}triggerBtn.disabled=!0,status("Benchmarking "+v.id+"...");try{setBenchmarkProgress(0,1,`Benchmarking ${v.id}`);const d=await runVoiceBenchmark(v.id);mergeBenchmarkResults(d);const hit=(d.voices||[]).find(x=>x.voice_id===v.id);hit&&hit.benchmark&&(v.benchmark=hit.benchmark);const benchCell=wrap.querySelector(".vr-bench");benchCell.className="vr-bench "+benchmarkClass(v),benchCell.title=benchmarkTitle(v),benchCell.querySelector(".vr-bench-value").textContent=fmtBenchmark(v);const tblRow=document.querySelector(`.vl-row[data-id="${CSS.escape(v.id)}"]`);if(tblRow){const bc=benchmarkClass(v),btitle=benchmarkTitle(v),tblDur=tblRow.querySelector(".vl-tbl-dur"),tblFactor=tblRow.querySelector(".vl-tbl-factor"),tblTime=tblRow.querySelector(".vl-tbl-time"),tblWpm=tblRow.querySelector(".vl-tbl-wpm");if(tblDur&&(tblDur.textContent=fmtBenchmarkAudio(v),tblDur.title=`${fmtBenchmarkAudio(v)} \u2014 length of synthesised benchmark audio`),tblFactor&&(tblFactor.textContent=fmtFactor(v),tblFactor.className=`vl-tbl-factor ${bc}`,tblFactor.title=btitle),tblTime&&(tblTime.textContent=fmtElapsed(v),tblTime.className=`vl-tbl-time ${bc}`,tblTime.title=btitle),tblWpm){const wpm=voiceWpm(v);tblWpm.textContent=fmtWpm(v),tblWpm.title=wpm!=null?`${wpm} wpm \u2014 130\u2013180 wpm is natural for long listening`:""}}setBenchmarkProgress(1,1,`Finished ${v.id}`),toast("Benchmarked "+v.id,"success"),setVoiceRestartState(!1,"Benchmark saved for "+v.id)}catch(e){toast("Benchmark failed: "+e.message,"error"),setOptStatus("Benchmark failed")}finally{triggerBtn.disabled=!1}};benchmarkOneBtn.addEventListener("click",()=>benchmarkThisVoice(benchmarkOneBtn)),optRebenchmarkBtn.addEventListener("click",()=>benchmarkThisVoice(optRebenchmarkBtn));const originalPlayBtn=wrap.querySelector(".vr-play-original button"),synthPlayBtn=wrap.querySelector(".vr-play-synth button"),playIcon='',pauseIcon='',generatingIcon='';function setLibraryPlayButtonState(btn,state){btn.classList.toggle("is-generating",state==="generating"),btn.innerHTML=state==="playing"?pauseIcon:state==="generating"?generatingIcon:playIcon,btn.title=state==="generating"?"Generating synthesized sample...":state==="playing"?"Pause playback":btn.dataset.playKind==="synth"?"Generate and play synthesized sample":"Play original recording"}async function playLibraryVoice(kind,playBtn){var _a3,_b2,_c2;const bar=$("lib-audio-bar"),audio=$("lib-audio"),playKey=v.id+":"+kind;if(playBtn.dataset.playKind=kind,_activePlayVoiceId===playKey&&!audio.paused){audio.pause(),setLibraryPlayButtonState(playBtn,"idle");return}if(_activePlayVoiceId===playKey&&audio.paused&&audio.src){_activePlayButton=playBtn;try{await audio.play()}catch(e){toast("Play failed: "+e.message,"error")}return}_activePlayButton&&_activePlayButton!==playBtn&&setLibraryPlayButtonState(_activePlayButton,"idle"),_activePlayButton=playBtn,_activePlayVoiceId=playKey,_activePlayUrl&&(URL.revokeObjectURL(_activePlayUrl),_activePlayUrl=null),kind==="synth"&&setLibraryPlayButtonState(playBtn,"generating"),playBtn.disabled=!0;try{if(kind==="synth"){v.needs_tts_restart&&toast("This voice changed since backend refresh; synthesized playback may use a cached voice.","error");const synthMode=((_a3=document.querySelector("#vl-synth-mode-seg .vl-synth-seg-btn.active"))==null?void 0:_a3.dataset.mode)||"preview",text=synthMode==="transcript"&&((_b2=v.transcript)==null?void 0:_b2.trim())||benchmarkSampleText(),textLabel=synthMode==="transcript"?"reference transcript":"preview text",backend=libraryTtsBackend(),source=await createTtsAudioSource(v.id,text,backend,"settings","");audio.src=source.url,source.streaming||(_activePlayUrl=source.url),$("lib-audio-label").textContent=v.id+" \xB7 synthesized "+textLabel+" \xB7 "+(((_c2=backendById(backend))==null?void 0:_c2.label)||backend)}else audio.src=voiceFileUrl(v),$("lib-audio-label").textContent=v.id+" \xB7 original recording";bar.style.display="",audio.onended=()=>{setLibraryPlayButtonState(playBtn,"idle"),_activePlayVoiceId=null},audio.onpause=()=>{_activePlayButton===playBtn&&setLibraryPlayButtonState(playBtn,"idle")},audio.onplay=()=>{setLibraryPlayButtonState(playBtn,"playing")},await audio.play()}catch(e){setLibraryPlayButtonState(playBtn,"idle"),toast("Play failed: "+e.message,"error")}finally{playBtn.disabled=!1}}originalPlayBtn.dataset.playKind="original",synthPlayBtn.dataset.playKind="synth",setLibraryPlayButtonState(originalPlayBtn,"idle"),setLibraryPlayButtonState(synthPlayBtn,"idle"),originalPlayBtn.addEventListener("click",()=>playLibraryVoice("original",originalPlayBtn)),synthPlayBtn.addEventListener("click",()=>playLibraryVoice("synth",synthPlayBtn));const toggleCb=wrap.querySelector(".toggle input");toggleCb.addEventListener("change",async()=>{const nextEnabled=toggleCb.checked,previousEnabled=v.enabled!==!1;toggleCb.disabled=!0;try{const saved=await saveMeta(v.id,{enabled:nextEnabled});v.enabled=nextEnabled,saved&&saved.path&&(v.path=saved.path),wrap.classList.toggle("vr-disabled",!v.enabled),toast(nextEnabled?"Moved to active_voices":"Moved to hidden_voices","success"),!v.enabled&&!$("show-disabled-cb").checked&&(wrap.style.transition="opacity .4s",wrap.style.opacity="0",setTimeout(()=>wrap.remove(),400))}catch(e){toggleCb.checked=previousEnabled,v.enabled=previousEnabled,wrap.classList.toggle("vr-disabled",!v.enabled),toast("Move failed: "+e.message,"error")}finally{toggleCb.disabled=!1}});const deleteBtn=wrap.querySelector(".delete-btn"),deleteConfirm=wrap.querySelector(".delete-confirm"),deleteCancelBtn=wrap.querySelector(".delete-confirm-cancel"),deleteGoBtn=wrap.querySelector(".delete-confirm-go"),closeDeleteConfirm=()=>wrap.classList.remove("delete-pending");return deleteBtn.addEventListener("click",e=>{e.stopPropagation(),document.querySelectorAll(".vl-row.delete-pending").forEach(row=>{row!==wrap&&row.classList.remove("delete-pending")}),wrap.classList.add("delete-pending"),deleteGoBtn.focus()}),deleteCancelBtn.addEventListener("click",e=>{e.stopPropagation(),closeDeleteConfirm()}),deleteConfirm.addEventListener("click",e=>e.stopPropagation()),deleteGoBtn.addEventListener("click",async e=>{e.stopPropagation(),deleteGoBtn.disabled=!0,deleteCancelBtn.disabled=!0;try{const r=await fetch(`/api/voice/${encodeURIComponent(v.id)}`,{method:"DELETE"});if(!r.ok){const e2=await r.json();throw new Error(e2.detail)}_voices=_voices.filter(x=>x.id!==v.id),wrap.style.transition="opacity .3s",wrap.style.opacity="0",setTimeout(()=>{wrap.remove(),$("voice-count").textContent=_voices.filter(x=>$("show-disabled-cb").checked||x.enabled!==!1).length+" / "+_voices.length+" voices"},300),toast(`Deleted: ${v.id}`,"success")}catch(e2){toast("Delete failed: "+e2.message,"error"),deleteGoBtn.disabled=!1,deleteCancelBtn.disabled=!1,closeDeleteConfirm()}}),wrap}function renderVoiceGroupsBar(){var _a2;const bar=$("voice-groups-bar");if(!bar)return;const groups={};(_voices||[]).forEach(v=>{const g=(v.group||"").trim();g&&(groups[g]=(groups[g]||0)+1)});const names=Object.keys(groups).sort();if(!names.length){bar.hidden=!0,bar.innerHTML="";return}bar.hidden=!1;const active=window._voiceGroupFilter||"";bar.innerHTML=' Groups'+names.map(g=>` ${escHtml(g)} ${groups[g]} `).join("")+(active?'':""),bar.querySelectorAll(".vg-chip").forEach(chip=>{chip.addEventListener("click",e=>{if(e.target.closest(".vg-del"))return;const g=chip.dataset.group;window._voiceGroupFilter=window._voiceGroupFilter===g?"":g;const groupSel=$("library-filter-group");groupSel&&(groupSel.value=window._voiceGroupFilter||""),renderVoiceList()})}),bar.querySelectorAll(".vg-del").forEach(btn=>{btn.addEventListener("click",e=>{e.stopPropagation(),deleteVoiceGroup(btn.dataset.group)})}),(_a2=bar.querySelector(".vg-clear"))==null||_a2.addEventListener("click",()=>{window._voiceGroupFilter="";const groupSel=$("library-filter-group");groupSel&&(groupSel.value=""),renderVoiceList()})}async function deleteVoiceGroup(group){const count=(_voices||[]).filter(v=>(v.group||"").trim()===group).length;if(confirm(`Delete all ${count} voice${count!==1?"s":""} in group "${group}"? This cannot be undone.`))try{const r=await fetch("/api/voices/delete-group",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({group})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();window._voiceGroupFilter===group&&(window._voiceGroupFilter=""),toast(`Deleted ${d.count} voice${d.count!==1?"s":""} from "${group}"`,"success"),await loadVoiceLibrary()}catch(e){toast("Delete failed: "+e.message,"error")}}async function saveMeta(voiceId,patch){const r=await fetch("/api/voice/meta",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:voiceId,...patch})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}return r.json()}document.addEventListener("click",()=>{document.querySelectorAll(".flag-picker.open").forEach(fp=>fp.classList.remove("open")),document.querySelectorAll(".vl-row.delete-pending").forEach(row=>row.classList.remove("delete-pending"))},{passive:!0});const _bulkSelected=new Set;function _bulkUpdate(){const bar=$("vl-bulk-bar"),count=$("vl-bulk-count"),n=_bulkSelected.size;bar&&(bar.hidden=n===0),count&&(count.textContent=`${n} selected`),document.querySelectorAll(".vl-bulk-cb").forEach(cb=>{cb.checked=_bulkSelected.has(cb.dataset.id)});const headerCb=document.getElementById("vl-select-all-header");if(headerCb){const rows=[...document.querySelectorAll("#voice-list .vl-row")].filter(r=>r.dataset.id),allSelected=rows.length>0&&rows.every(r=>_bulkSelected.has(r.dataset.id));headerCb.checked=allSelected}}function _bulkToggle(id,checked){checked?_bulkSelected.add(id):_bulkSelected.delete(id),_bulkUpdate()}const _origRenderVoiceList=renderVoiceList;renderVoiceList=function(){_origRenderVoiceList.apply(this,arguments),_bulkInjectCheckboxes()};function _bulkInjectCheckboxes(){document.querySelectorAll("#voice-list .vl-row").forEach(row=>{if(row.querySelector(".vl-bulk-cb"))return;const id=row.dataset.id,cb=document.createElement("input");cb.type="checkbox",cb.className="vl-bulk-cb",cb.dataset.id=id,cb.checked=_bulkSelected.has(id),cb.title="Select for bulk edit",cb.addEventListener("change",e=>{e.stopPropagation(),_bulkToggle(id,cb.checked)}),cb.addEventListener("click",e=>e.stopPropagation());const compact=row.querySelector(".vl-compact");compact&&compact.prepend(cb)})}(_H=$("vl-bulk-select-all"))==null||_H.addEventListener("click",()=>{document.querySelectorAll("#voice-list .vl-row").forEach(row=>{row.dataset.id&&_bulkSelected.add(row.dataset.id)}),_bulkUpdate()}),(_I=$("vl-select-all-header"))==null||_I.addEventListener("change",e=>{const rows=[...document.querySelectorAll("#voice-list .vl-row")].filter(r=>r.dataset.id);e.target.checked?rows.forEach(r=>_bulkSelected.add(r.dataset.id)):rows.forEach(r=>_bulkSelected.delete(r.dataset.id)),_bulkUpdate()}),(_J=$("vl-bulk-deselect"))==null||_J.addEventListener("click",()=>{_bulkSelected.clear(),_bulkUpdate()}),(_K=$("vl-bulk-hide"))==null||_K.addEventListener("click",async()=>{if(!_bulkSelected.size)return;const ids=[..._bulkSelected],r=await _bulkSetEnabled(ids,!1);toast(`Hidden ${r} voice${r!==1?"s":""}`,"success"),_bulkSelected.clear(),await loadVoiceLibrary()}),(_L=$("vl-bulk-unhide"))==null||_L.addEventListener("click",async()=>{if(!_bulkSelected.size)return;const ids=[..._bulkSelected],r=await _bulkSetEnabled(ids,!0);toast(`Unhidden ${r} voice${r!==1?"s":""}`,"success"),_bulkSelected.clear(),await loadVoiceLibrary()}),(_M=$("vl-bulk-tag"))==null||_M.addEventListener("click",()=>{if(!_bulkSelected.size)return;const ids=[..._bulkSelected],bar=$("vl-bulk-bar");let inp=bar.querySelector(".vl-bulk-tag-inp");if(!inp){inp=document.createElement("input"),inp.type="text",inp.className="vl-bulk-tag-inp",inp.placeholder="tag1, tag2\u2026",inp.autocomplete="off";const applyBtn=document.createElement("button");applyBtn.className="vl-bulk-btn",applyBtn.innerHTML=' Apply',applyBtn.addEventListener("click",async()=>{const newTags=inp.value.split(",").map(t=>t.trim()).filter(Boolean);let done=0;for(const id of ids){const v=(window._voices||[]).find(vv=>vv.id===id),merged=String((v==null?void 0:v.tag)||"").split(",").map(t=>t.trim()).filter(Boolean).slice();for(const t of newTags)merged.some(e=>e.toLowerCase()===t.toLowerCase())||merged.push(t);await saveMeta(id,{tag:merged.join(", ")}).catch(()=>{}),done++}toast(`Tag added on ${done} voice${done!==1?"s":""}`,"success"),inp.remove(),applyBtn.remove(),_bulkSelected.clear(),await loadVoiceLibrary()}),bar.appendChild(inp),bar.appendChild(applyBtn)}inp.focus()}),(_N=$("vl-bulk-source"))==null||_N.addEventListener("click",()=>{if(!_bulkSelected.size)return;const ids=[..._bulkSelected],bar=$("vl-bulk-bar");let inp=bar.querySelector(".vl-bulk-source-inp");if(!inp){inp=document.createElement("input"),inp.type="text",inp.className="vl-bulk-source-inp",inp.placeholder="e.g. fish-audio, cloned\u2026",inp.autocomplete="off";const applyBtn=document.createElement("button");applyBtn.className="vl-bulk-btn",applyBtn.innerHTML=' Apply',applyBtn.addEventListener("click",async()=>{const val=inp.value.trim();let done=0;for(const id of ids)await saveMeta(id,{origin:val}).catch(()=>{}),done++;toast(`Source set on ${done} voice${done!==1?"s":""}`,"success"),inp.remove(),applyBtn.remove(),_bulkSelected.clear(),await loadVoiceLibrary()}),bar.appendChild(inp),bar.appendChild(applyBtn)}inp.focus()}),(_O=$("vl-bulk-rating"))==null||_O.addEventListener("click",()=>{if(!_bulkSelected.size)return;const ids=[..._bulkSelected],bar=$("vl-bulk-bar");let sel=bar.querySelector(".vl-bulk-rating-sel");sel||(sel=document.createElement("select"),sel.className="vl-bulk-rating-sel",sel.innerHTML=''+[1,2,3,4,5].map(n=>``).join(""),sel.addEventListener("change",async()=>{const rating=Number(sel.value);if(!rating)return;let done=0;for(const id of ids)await saveMeta(id,{rating}).catch(()=>{}),done++;toast(`Rated ${done} voice${done!==1?"s":""}`,"success"),sel.remove(),_bulkSelected.clear(),await loadVoiceLibrary()}),bar.appendChild(sel),sel.focus())}),(_P=$("vl-bulk-delete"))==null||_P.addEventListener("click",()=>{_bulkSelected.size&&_showBulkDeleteConfirm([..._bulkSelected])});function _showBulkDeleteConfirm(ids){var _a2;(_a2=document.querySelector(".vl-bdc-overlay"))==null||_a2.remove();const plural=ids.length!==1?"s":"",names=ids.slice(0,12).map(id=>`${escHtml(id)}`).join(""),more=ids.length>12?`+${ids.length-12} more`:"",ov=document.createElement("div");ov.className="vl-bdc-overlay",ov.innerHTML=`
Delete ${ids.length} voice${plural}?

This permanently removes the selected voice${plural} from your library. This cannot be undone.

${names}${more}
`;const close=()=>{ov.remove(),document.removeEventListener("keydown",onKey)};function onKey(e){e.key==="Escape"&&close()}ov.addEventListener("click",e=>{e.target===ov&&close()}),ov.querySelector(".vl-bdc-cancel").addEventListener("click",close),document.addEventListener("keydown",onKey),ov.querySelector(".vl-bdc-go").addEventListener("click",async()=>{const goBtn=ov.querySelector(".vl-bdc-go"),cancelBtn=ov.querySelector(".vl-bdc-cancel");goBtn.disabled=cancelBtn.disabled=!0;let done=0,errors=0;await runPool(ids,async id=>{try{(await fetch(`/api/voice/${encodeURIComponent(id)}`,{method:"DELETE"})).ok?done++:errors++}catch{errors++}},5,n=>{goBtn.innerHTML=` Deleting ${n}/${ids.length}\u2026`}),close(),toast(`Deleted ${done} voice${done!==1?"s":""}${errors?` (${errors} errors)`:""}`,errors?"error":"success"),_bulkSelected.clear(),await loadVoiceLibrary()}),document.body.appendChild(ov),ov.querySelector(".vl-bdc-cancel").focus()}async function _bulkSetEnabled(ids,enabled){let done=0;for(const id of ids)await saveMeta(id,{enabled}).catch(()=>{}),done++;return done}function backendVoiceId(value){return typeof value=="string"?value:(value==null?void 0:value.id)||(value==null?void 0:value.voice)||(value==null?void 0:value.name)||JSON.stringify(value)}function shouldFilterBackendVoices(backend){return["voice_clone","streaming","nvidia_zeroshot","nvidia_flow"].includes(backend||"")}async function activeLibraryVoiceIds(){return _voices.length||await loadVoiceLibrary(),new Set((_voices||[]).filter(v=>v.enabled!==!1).map(v=>v.id))}function cleanReferenceText(text){return String(text||"").trim()}function selectedPreviewLibraryVoice(){var _a2;const id=((_a2=$("tts-voice-select"))==null?void 0:_a2.value)||"";return id?(_voices||[]).find(v=>v.id===id):null}function previewVoiceWarnings(v){var _a2;const warnings=[],backend=backendById(((_a2=$("tts-backend-select"))==null?void 0:_a2.value)||"");backend&&backend.id&&!["voice_clone","streaming","nvidia_zeroshot","nvidia_flow"].includes(backend.id)&&warnings.push(backend.id==="nvidia_magpie"?"NVIDIA Magpie uses fixed speaker voices, not saved WAV clone identity.":"This backend may follow style/model voice more than the saved WAV identity."),backend&&backend.id==="nvidia_zeroshot"&&v.duration&&(Number(v.duration)<3||Number(v.duration)>10)&&warnings.push("NVIDIA Zeroshot works best with a clear 3-10 second prompt."),backend&&backend.id==="nvidia_flow"&&!v.transcript&&warnings.push("NVIDIA Flow requires the exact saved reference transcript for this voice."),v.transcript||warnings.push("No reference transcript is saved; cloned identity is harder to judge."),v.duration&&(Number(v.duration)<3||Number(v.duration)>20)&&warnings.push("Reference clip length is outside the 3-20 second sweet spot."),v.needs_tts_restart&&warnings.push("This voice changed since the last backend refresh; restart or clear restart flags before judging it.");const healthWarnings=v.health&&Array.isArray(v.health.warnings)?v.health.warnings:[];return warnings.push(...healthWarnings.slice(0,3)),warnings}function updatePreviewVoiceMatchPanel(){const panel=$("preview-match-panel");if(!panel)return;const v=selectedPreviewLibraryVoice();if(!v){panel.hidden=!0;return}panel.hidden=!1;const lang=v.language||v.lang||(v.id||"").split("_")[0]||"-",gender=v.gender||(v.id||"").split("_")[1]||"-",db=fmtDbfs(v),dur=v.duration?fmtDuration(v.duration):"-";$("preview-match-title").textContent=v.id,$("preview-match-detail").textContent=`${lang} \xB7 ${gender} \xB7 ${dur} \xB7 ${db} dBFS`;const warnings=previewVoiceWarnings(v);$("preview-match-warning").textContent=warnings.length?warnings.join(" "):"For a fair voice match check, play the WAV and synthesize the exact saved reference text.";const transcript=cleanReferenceText(v.transcript||"");$("preview-match-transcript").textContent=transcript||"No reference text saved for this voice.",$("preview-ref-use-text").disabled=!transcript,$("preview-ref-synth").disabled=!transcript;const audio=$("preview-ref-audio"),expected=voiceFileUrl(v);audio.dataset.src!==expected&&(audio.pause(),audio.src=expected,audio.dataset.src=expected);const actionsEl=panel.querySelector(".preview-match-actions");let personaBtn=panel.querySelector(".preview-persona-btn");v.persona?personaBtn||(personaBtn=document.createElement("button"),personaBtn.className="btn-secondary preview-persona-btn",personaBtn.type="button",personaBtn.textContent="Rewrite with persona",actionsEl==null||actionsEl.appendChild(personaBtn),personaBtn.addEventListener("click",async()=>{const text=$("preview-text-area").value.trim();if(!text){toast("Enter text to rewrite","error");return}const lv=selectedPreviewLibraryVoice();if(!(lv!=null&&lv.persona)){toast("This voice has no persona","error");return}personaBtn.disabled=!0,personaBtn.textContent="Rewriting\u2026";try{const llmUrl=localStorage.getItem("refine-llm-url")||(_appSettings==null?void 0:_appSettings.llm_url)||"http://localhost:11434/v1",r=await fetch("/api/rewrite-with-persona",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text,persona:lv.persona,llm_url:llmUrl,model:(_appSettings==null?void 0:_appSettings.llm_model)||"",mode:"rewrite"})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();$("preview-text-area").value=d.text,toast("Text rewritten in persona style","success")}catch(e){toast("Persona rewrite failed: "+e.message,"error")}finally{personaBtn.disabled=!1,personaBtn.textContent="Rewrite with persona"}})):personaBtn==null||personaBtn.remove()}async function synthesizeSelectedReferenceText(){const v=selectedPreviewLibraryVoice();if(!v){toast("Select a library voice first","error");return}let text=cleanReferenceText(v.transcript||"");if(!text){toast("This voice has no reference text","error");return}if(v.needs_tts_restart){if(!confirm("This voice is marked as needing a TTS restart. If you already restarted the backend, clear the flag and synthesize anyway?"))return;await clearTtsRestartFlags(),v.needs_tts_restart=!1,updatePreviewVoiceMatchPanel()}const backend=$("tts-backend-select").value;if(!backend){toast("No available TTS backend","error");return}const btn=$("preview-ref-synth");btn.disabled=!0;try{$("preview-text-area").value=text;const source=await createTtsAudioSource(v.id,text,backend,$("preview-playback-mode").value,$("preview-style-instruction").value.trim());previewBlob=source.blob;const audio=$("preview-audio");audio.src=source.url,audio.style.display="",await audio.play(),$("save-preview-mp3-btn").disabled=!1,$("save-preview-btn").disabled=source.streaming,toast(source.streaming?"Reference text streaming":"Reference text synthesized","success")}catch(e){toast("Reference synthesis failed: "+e.message,"error")}finally{btn.disabled=!1}}$("fetch-tts-voices-btn").addEventListener("click",async()=>{var _a2;$("fetch-tts-voices-btn").disabled=!0;try{const backend=(_a2=$("tts-backend-select"))==null?void 0:_a2.value;if(!backend)throw new Error("No available TTS backend");const rawVoices=await fetch("/api/tts-voices?backend="+encodeURIComponent(backend)).then(r=>r.json());let voices=Array.isArray(rawVoices)?rawVoices:[];if(shouldFilterBackendVoices(backend)){const activeIds=await activeLibraryVoiceIds();voices=voices.filter(v=>activeIds.has(backendVoiceId(v)))}const sel=$("tts-voice-select"),prev=sel.value,ids=voices.map(backendVoiceId);window.VoicePicker?(VoicePicker.upgrade("tts-voice-select"),VoicePicker.populate("tts-voice-select",ids),prev&&ids.includes(prev)&&VoicePicker.setValue("tts-voice-select",prev)):(sel.innerHTML='',ids.forEach(id=>{const o=document.createElement("option");o.value=o.textContent=id,sel.appendChild(o)}),prev&&ids.includes(prev)&&(sel.value=prev)),updatePreviewVoiceMatchPanel();const suffix=shouldFilterBackendVoices(backend)?" active voices":" voices";toast("Fetched "+voices.length+suffix,"success")}catch(e){toast("Fetch failed: "+e.message,"error")}finally{$("fetch-tts-voices-btn").disabled=!1}}),$("tts-backend-select").addEventListener("change",()=>{const sel=$("tts-voice-select");sel.innerHTML='',updateBackendHelp(),updatePreviewVoiceMatchPanel(),previewBlob=null,$("save-preview-mp3-btn").disabled=!0,$("save-preview-btn").disabled=!0}),$("tts-voice-select").addEventListener("change",updatePreviewVoiceMatchPanel),$("preview-ref-play").addEventListener("click",async()=>{updatePreviewVoiceMatchPanel();const audio=$("preview-ref-audio");try{await audio.play()}catch(e){toast("Reference playback failed: "+e.message,"error")}}),$("preview-ref-use-text").addEventListener("click",()=>{const v=selectedPreviewLibraryVoice(),text=cleanReferenceText((v==null?void 0:v.transcript)||"");if(!text){toast("This voice has no reference text","error");return}$("preview-text-area").value=text,toast("Reference text copied to target text","success")}),$("preview-ref-synth").addEventListener("click",synthesizeSelectedReferenceText);let _ttsStreamHealth=null;function effectiveTtsPlaybackMode(override="settings"){return override&&override!=="settings"?override:_appSettings.tts_stream_mode||"auto"}async function isTtsStreamAvailable(force=!1){if(_ttsStreamHealth&&!force)return _ttsStreamHealth.ok;try{return _ttsStreamHealth=await fetch("/api/tts-stream-health").then(r=>r.json()),!!_ttsStreamHealth.ok}catch{return _ttsStreamHealth={ok:!1},!1}}async function createTtsStreamUrl(voice,text,instruct=""){if(!await isTtsStreamAvailable())throw new Error("streaming backend unavailable");const r=await fetch("/api/tts-stream-session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text,voice,instruct})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}return(await r.json()).url}async function fetchTtsPreviewBlob(voice,text,responseFormat="wav",instruct="",backend="voice_clone",applyPersona=!1,extra=null){const body={text,voice,response_format:responseFormat,instruct,backend};applyPersona&&(body.apply_persona=!0),extra&&typeof extra=="object"&&Object.assign(body,extra);const r=await fetch("/api/tts-preview",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}return await r.blob()}async function createTtsAudioSource(voice,text,backend="voice_clone",modeOverride="settings",instruct="",applyPersona=!1,extra=null){const mode=effectiveTtsPlaybackMode(modeOverride);if(backend!=="streaming"||mode==="buffered"){const blob=await fetchTtsPreviewBlob(voice,text,"wav",instruct,backend,applyPersona,extra);return{url:URL.createObjectURL(blob),blob,streaming:!1,label:"buffered"}}try{return{url:await createTtsStreamUrl(voice,text,instruct),blob:null,streaming:!0,label:"streaming"}}catch(e){if(mode==="streaming")throw e;const blob=await fetchTtsPreviewBlob(voice,text,"wav",instruct,backend,applyPersona,extra);return{url:URL.createObjectURL(blob),blob,streaming:!1,label:"buffered"}}}let previewBlob=null;const PREVIEW_SAMPLE_TEXT="Hello! This is a voice preview from TTS Voice Creator - Clone and Design.";$("preview-text-area").addEventListener("focus",()=>{$("preview-text-area").value===PREVIEW_SAMPLE_TEXT&&($("preview-text-area").value="")},{once:!0});function _onPreviewGenerated(source,voice,text,backend,instruct){typeof effectsSourceBlob!="undefined"&&(window._effectsSourceBlob=null),window._effectsSynthArgs={voice,text,instruct:instruct||"",backend};const ea=$("effects-apply-btn");ea&&(ea.disabled=!1);const ap=$("add-to-playlist-btn");ap&&source.blob&&(ap.disabled=!1),typeof historyPush=="function"&&source.blob&&historyPush(voice,text,backend,source.blob,source.url)}const _TRYOUT_SPEED_KEY="ttsvc_tryout_native_speed";(function(){const saved=localStorage.getItem(_TRYOUT_SPEED_KEY);if(saved){const el=$("preview-native-speed");el&&(el.value=saved)}})(),(_Q=$("preview-native-speed"))==null||_Q.addEventListener("change",function(){localStorage.setItem(_TRYOUT_SPEED_KEY,this.value)}),$("preview-btn").addEventListener("click",async()=>{var _a2,_b2,_c2;const voice=$("tts-voice-select").value,backend=$("tts-backend-select").value,text=$("preview-text-area").value.trim(),instruct=$("preview-style-instruction").value.trim(),applyPersona=((_a2=$("preview-persona-toggle"))==null?void 0:_a2.checked)||!1;if(!backend){toast("No available TTS backend","error");return}if(!voice){toast("Select a TTS voice","error");return}if(!text){toast("Enter preview text","error");return}$("preview-btn").disabled=!0,$("save-preview-mp3-btn").disabled=!0,$("save-preview-btn").disabled=!0,$("add-to-playlist-btn")&&($("add-to-playlist-btn").disabled=!0),$("effects-apply-btn")&&($("effects-apply-btn").disabled=!0);const _nspd=parseFloat((_b2=$("preview-native-speed"))==null?void 0:_b2.value),_extra=!isNaN(_nspd)&&_nspd!==1?{speed:_nspd}:null;try{const audio=$("preview-audio"),source=((_c2=$("preview-chunked-toggle"))==null?void 0:_c2.checked)&&text.length>200&&typeof generateChunkedTts=="function"?await generateChunkedTts(voice,text,backend,instruct,_extra):await createTtsAudioSource(voice,text,backend,$("preview-playback-mode").value,instruct,applyPersona,_extra);previewBlob=source.blob,window._previewVoice=voice,window._previewBackend=backend,window._previewText=text,audio.src=source.url,audio.style.display="",await audio.play(),$("save-preview-mp3-btn").disabled=!1,$("save-preview-btn").disabled=source.streaming,_onPreviewGenerated(source,voice,text,backend,instruct),toast(source.streaming?"Streaming preview playing":source.label==="chunked"?`Chunked (${text.length} chars) playing`:"Preview playing","success")}catch(e){toast("TTS failed: "+e.message,"error")}finally{$("preview-btn").disabled=!1}}),$("save-preview-mp3-btn").addEventListener("click",async()=>{const voice=$("tts-voice-select").value,backend=$("tts-backend-select").value,text=$("preview-text-area").value.trim(),instruct=$("preview-style-instruction").value.trim();if(!backend){toast("No available TTS backend","error");return}if(!voice||!text)return;const btn=$("save-preview-mp3-btn");btn.disabled=!0;try{const blob=await fetchTtsPreviewBlob(voice,text,"mp3",instruct,backend),a=document.createElement("a");a.href=URL.createObjectURL(blob),a.download=(voice||"preview")+"_preview.mp3",a.click(),toast("MP3 saved","success")}catch(e){toast("MP3 save failed: "+e.message,"error")}finally{btn.disabled=!1}}),$("save-preview-btn").addEventListener("click",()=>{if(!previewBlob)return;const a=document.createElement("a");a.href=URL.createObjectURL(previewBlob),a.download=($("tts-voice-select").value||"preview")+"_preview.wav",a.click()});const PERF_HISTORY_KEY="vcf-perf-history",PERF_HISTORY_MAX=50,PERF_HISTORY_SORT={key:"ts",dir:"desc"};function perfHistoryLoad(){try{return JSON.parse(localStorage.getItem(PERF_HISTORY_KEY)||"[]")}catch{return[]}}function perfHistorySave(entries){try{localStorage.setItem(PERF_HISTORY_KEY,JSON.stringify(entries.slice(-PERF_HISTORY_MAX)))}catch{}}function perfHistoryAdd(entry){const h=perfHistoryLoad();h.push(entry),perfHistorySave(h)}function perfSparklineSvg(rtfValues){if(!rtfValues.length)return"";const W=120,H=32,PAD=2,barW=Math.max(4,Math.floor((W-PAD*2)/rtfValues.length)-1),maxV=Math.max(...rtfValues,1),bars=rtfValues.map((v,i)=>{const bh=Math.max(3,Math.round(v/maxV*(H-PAD*2))),x=PAD+i*(barW+1),y=H-PAD-bh,col=v<1?"var(--green)":"var(--yellow)";return``}).join("");return``}function perfHistoryVoiceLookup(voiceId){return(Array.isArray(window._voices)&&window._voices.length?window._voices:typeof _voices!="undefined"&&Array.isArray(_voices)?_voices:[]).find(v=>v&&(v.id===voiceId||v.name===voiceId||v.voice_id===voiceId))||null}function perfHistoryVoiceMeta(entry){var _a2,_b2,_c2;const voice=(entry==null?void 0:entry.voice)||"",saved=(entry==null?void 0:entry.voiceMeta)||{},lib=perfHistoryVoiceLookup(voice)||{},language=saved.lang||saved.language||(entry==null?void 0:entry.lang)||(entry==null?void 0:entry.language)||lib.lang||lib.language||"",gender=String(saved.gender||(entry==null?void 0:entry.gender)||lib.gender||"").trim().toUpperCase().charAt(0);return{id:voice,label:saved.label||saved.display_name||(entry==null?void 0:entry.voiceLabel)||lib.display_name||lib.name||voice,lang:language,gender:["F","M","N"].includes(gender)?gender:"",flag:saved.flag||(entry==null?void 0:entry.flag)||lib.flag||"",avatar:saved.avatar||(entry==null?void 0:entry.avatar)||lib.avatar||"",hasPicture:!!((_c2=(_b2=(_a2=saved.has_picture)!=null?_a2:saved.hasPicture)!=null?_b2:entry==null?void 0:entry.hasPicture)!=null?_c2:lib.has_picture)}}function perfHistoryExtraFields(voice,backend){const lib=perfHistoryVoiceLookup(voice)||{};return{voiceMeta:{label:lib.display_name||lib.name||voice,lang:lib.lang||lib.language||"",gender:lib.gender||"",flag:lib.flag||"",avatar:lib.avatar||"",has_picture:!!lib.has_picture},device:typeof backendComputeDevice=="function"?backendComputeDevice(backend):""}}function perfHistoryGenderLabel(gender){return{F:"Female",M:"Male",N:"Diverse"}[gender]||""}function perfHistoryDeviceLabel(entry){return(entry==null?void 0:entry.device)||(typeof backendComputeDevice=="function"?backendComputeDevice((entry==null?void 0:entry.backend)||""):"")||"Unknown"}function perfHistoryDeviceHtml(entry){const label=perfHistoryDeviceLabel(entry),cls=typeof backendComputeDeviceClass=="function"?backendComputeDeviceClass((entry==null?void 0:entry.backend)||""):label.toLowerCase().includes("gpu")?"gpu":label.toLowerCase().includes("cpu")?"cpu":"";return`${escHtml(label)}`}function perfHistoryAvatarHtml(entry){var _a2;const meta=perfHistoryVoiceMeta(entry),title=meta.label||meta.id||"Voice";if(meta.hasPicture)return``;const icon=window.voiceAvatarIcon?window.voiceAvatarIcon(meta.avatar,24):null;if(icon)return`${icon.replace(/vp-avatar/g,"perf-history-avatar-icon")}`;const color=typeof avatarColor=="function"?avatarColor(meta.lang||meta.id||title):"#6b7280",init=((_a2=(title||"?").trim()[0])==null?void 0:_a2.toUpperCase())||"?";return`${escHtml(init)}`}function perfHistorySortValue(entry,key){switch(key){case"backend":return String(entry.backend||"").toLowerCase();case"language":return String(perfHistoryVoiceMeta(entry).lang||"").toLowerCase();case"gender":return String(perfHistoryGenderLabel(perfHistoryVoiceMeta(entry).gender)||"").toLowerCase();case"voice":return String(entry.voice||"").toLowerCase();case"device":return String(perfHistoryDeviceLabel(entry)||"").toLowerCase();case"avgLatencyMs":return Number(entry.avgLatencyMs);case"minLatencyMs":return Number(entry.minLatencyMs);case"avgRtf":return Number(entry.avgRtf);case"ts":default:return Number(entry.ts)}}function perfHistoryCompare(a,b){const av=perfHistorySortValue(a,PERF_HISTORY_SORT.key),bv=perfHistorySortValue(b,PERF_HISTORY_SORT.key);let result=0;if(typeof av=="string"||typeof bv=="string")result=String(av).localeCompare(String(bv),void 0,{numeric:!0,sensitivity:"base"});else{const an=Number.isFinite(av)?av:-1/0,bn=Number.isFinite(bv)?bv:-1/0;result=an===bn?0:an-bn}return PERF_HISTORY_SORT.dir==="asc"?result:-result}function perfHistoryHeadButton(key,label){const active=PERF_HISTORY_SORT.key===key,icon=active?PERF_HISTORY_SORT.dir==="asc"?"mdi-arrow-up":"mdi-arrow-down":"mdi-swap-vertical";return``}function renderPerfHistory(){var _a2,_b2;const histList=$("perf-history-list");if(!histList)return;const filterEl=$("perf-history-filter-current"),filterOn=filterEl==null?void 0:filterEl.checked,curBack=(_a2=$("perf-backend-select"))==null?void 0:_a2.value,curVoice=(_b2=$("perf-voice-select"))==null?void 0:_b2.value;let entries=perfHistoryLoad().slice();if(filterOn&&curBack&&(entries=entries.filter(e=>e.backend===curBack&&e.voice===curVoice)),!entries.length){histList.innerHTML='
'+(filterOn?"No history for this backend/voice yet.":"No benchmark history yet. Run a benchmark above to start tracking.")+"
";return}entries.sort((a,b)=>perfHistoryCompare(a,b)||Number(b.ts)-Number(a.ts));const head=`
${perfHistoryHeadButton("ts","Date / Time")} ${perfHistoryHeadButton("backend","Backend")} Profile ${perfHistoryHeadButton("language","Language")} ${perfHistoryHeadButton("gender","Gender")} ${perfHistoryHeadButton("voice","Voice")} ${perfHistoryHeadButton("device","CPU / GPU")} ${perfHistoryHeadButton("avgLatencyMs","Avg latency")} ${perfHistoryHeadButton("minLatencyMs","Min")} ${perfHistoryHeadButton("avgRtf","Avg RTF")}
`,rows=entries.map(e=>{const dt=new Date(e.ts).toLocaleString([],{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"}),rtfCls=e.avgRtf<1?"perf-good":"perf-slow",meta=perfHistoryVoiceMeta(e);return`
${escHtml(dt)} ${escHtml(e.backend)} ${perfHistoryAvatarHtml(e)} ${escHtml(meta.lang||"-")} ${escHtml(perfHistoryGenderLabel(meta.gender)||"-")} ${escHtml(e.voice)} ${perfHistoryDeviceHtml(e)} ${Math.round(e.avgLatencyMs)} ms ${Math.round(e.minLatencyMs)} ms ${e.avgRtf.toFixed(2)}
`}).join("");histList.innerHTML=head+rows,histList.querySelectorAll(".perf-history-sort").forEach(btn=>{btn.addEventListener("click",()=>{const key=btn.dataset.historySort||"ts";PERF_HISTORY_SORT.key===key?PERF_HISTORY_SORT.dir=PERF_HISTORY_SORT.dir==="asc"?"desc":"asc":(PERF_HISTORY_SORT.key=key,PERF_HISTORY_SORT.dir=key==="backend"||key==="voice"?"asc":"desc"),renderPerfHistory()})}),histList.querySelectorAll(".perf-history-del").forEach(btn=>{btn.addEventListener("click",()=>{const ts=Number(btn.dataset.ts);perfHistorySave(perfHistoryLoad().filter(e=>e.ts!==ts)),renderPerfHistory()})})}(function(){var _a2,_b2;const perfBackendSel=$("perf-backend-select"),perfVoiceSel=$("perf-voice-select"),perfFetchBtn=$("perf-fetch-voices-btn"),perfRunBtn=$("perf-run-btn"),perfClearBtn=$("perf-clear-btn"),perfProgress=$("perf-progress"),perfResultsCard=$("perf-results-card"),perfSummary=$("perf-summary"),perfTbody=$("perf-tbody"),perfText=$("perf-text"),perfRunsSel=$("perf-runs");if(!perfRunBtn)return;let perfRows=[];function populatePerfBackends(){if(!perfBackendSel)return;const cur=perfBackendSel.value;perfBackendSel.innerHTML=availableTtsBackends().map(b=>``).join("")||''}populatePerfBackends(),window.BenchmarkVoicePicker&&BenchmarkVoicePicker.upgrade("perf-voice-select",{placeholder:"-- select after fetch --",empty:"No voices"}),perfFetchBtn.addEventListener("click",async()=>{const backend=perfBackendSel.value;if(!backend){toast("Select a backend first","error");return}perfFetchBtn.disabled=!0;try{const rawVoices=await fetch("/api/tts-voices?backend="+encodeURIComponent(backend)).then(r=>r.json()),cur=perfVoiceSel.value,items=rawVoices.map(v=>{const id=backendVoiceId(v);return{id,label:id,meta:(window._voices||[]).find(x=>x&&x.id===id)||(typeof v=="object"?v:null)}}).filter(v=>v.id);window.BenchmarkVoicePicker?(BenchmarkVoicePicker.populate("perf-voice-select",items,{placeholder:"-- select after fetch --",empty:"No voices"}),cur&&items.some(v=>v.id===cur)&&BenchmarkVoicePicker.set("perf-voice-select",cur)):perfVoiceSel.innerHTML=items.map(v=>``).join("")||'',window.dispatchEvent(new CustomEvent("benchmark:tts-voices-fetched",{detail:{backend,voices:items}}))}catch(e){toast("Fetch voices failed: "+e.message,"error")}finally{perfFetchBtn.disabled=!1}});function updateTrendDisplay(backend,voice,currentAvgRtf){const trendRow=$("perf-trend-row"),trendBadge=$("perf-trend-badge"),sparkWrap=$("perf-sparkline-wrap")||(trendRow==null?void 0:trendRow.querySelector(".perf-sparkline-wrap"));if(!trendRow)return;const history=perfHistoryLoad().filter(e=>e.backend===backend&&e.voice===voice&&typeof e.avgRtf=="number");if(history.length===0){trendRow.style.display="none";return}const prevRtf=history[history.length-1].avgRtf,delta=currentAvgRtf-prevRtf,pct=Math.abs(delta/Math.max(prevRtf,.01))*100;let cls,label;pct<5?(cls="perf-trend-stable",label=' Stable'):delta<0?(cls="perf-trend-better",label=` ${pct.toFixed(0)}% faster`):(cls="perf-trend-worse",label=` ${pct.toFixed(0)}% slower`),trendBadge.className="perf-trend-badge "+cls,trendBadge.innerHTML=label;const rtfValues=[...history.slice(-9).map(e=>e.avgRtf),currentAvgRtf];sparkWrap&&(sparkWrap.innerHTML=perfSparklineSvg(rtfValues)),trendRow.style.display=""}function renderPerfTable(sessionDone=!1){if(!perfRows.length){perfResultsCard.style.display="none";return}perfResultsCard.style.display="",perfTbody.innerHTML=perfRows.map((r,i)=>{const rtf=r.audioDuration>0?(r.latencyMs/1e3/r.audioDuration).toFixed(2):"\u2014",ok2=r.ok;return` ${i+1} ${escHtml(r.backend)} ${escHtml(typeof backendComputeDevice=="function"?backendComputeDevice(r.backend):"Unknown")} ${escHtml(r.voice)} ${ok2?r.latencyMs:"\u2014"} ${ok2&&r.audioDuration>0?r.audioDuration.toFixed(2):"\u2014"} ${ok2?rtf:"\u2014"} ${ok2?'OK':`${escHtml(r.error||"Error")}`} `}).join("");const ok=perfRows.filter(r=>r.ok);if(ok.length){const avg=ok.reduce((s,r)=>s+r.latencyMs,0)/ok.length,minL=Math.min(...ok.map(r=>r.latencyMs)),maxL=Math.max(...ok.map(r=>r.latencyMs)),rtfArr=ok.filter(r=>r.audioDuration>0).map(r=>r.latencyMs/1e3/r.audioDuration),avgRtf=rtfArr.length?rtfArr.reduce((s,v)=>s+v,0)/rtfArr.length:0,labelEl=$("perf-results-label");labelEl&&(labelEl.textContent=`${ok.length} run${ok.length>1?"s":""} \u2014 ${perfBackendSel.value} / ${perfVoiceSel.value}`),perfSummary.innerHTML=` ${Math.round(avg)} ms avg latency ${minL} ms best ${maxL} ms worst ${avgRtf.toFixed(2)} avg RTF ${avgRtf<1?' Real-time capable':' Slower than real-time'} `,sessionDone&&rtfArr.length&&(updateTrendDisplay(perfBackendSel.value,perfVoiceSel.value,avgRtf),perfHistoryAdd({ts:Date.now(),backend:perfBackendSel.value,voice:perfVoiceSel.value,...perfHistoryExtraFields(perfVoiceSel.value,perfBackendSel.value),textLen:perfText.value.trim().length,avgLatencyMs:avg,minLatencyMs:minL,maxLatencyMs:maxL,avgRtf,runCount:ok.length,allOk:ok.length===perfRows.length}),renderPerfHistory())}else perfSummary.innerHTML='All runs failed'}perfClearBtn.addEventListener("click",()=>{perfRows=[],renderPerfTable(),$("perf-trend-row")&&($("perf-trend-row").style.display="none"),perfProgress.style.display="none"}),perfRunBtn.addEventListener("click",async()=>{const backend=perfBackendSel.value,voice=perfVoiceSel.value,text=perfText.value.trim(),runs=parseInt(perfRunsSel.value)||3;if(!backend){toast("Select a backend first","error");return}if(!voice){toast("Fetch and select a voice first","error");return}if(!text){toast("Enter sample text","error");return}perfRows=[],perfRunBtn.disabled=!0,perfProgress.style.display="";for(let i=0;i1?"s":""} completed.`,renderPerfTable(!0),perfRunBtn.disabled=!1}),(_a2=$("perf-history-filter-current"))==null||_a2.addEventListener("change",renderPerfHistory),(_b2=$("perf-history-clear-btn"))==null||_b2.addEventListener("click",()=>{perfHistorySave([]),renderPerfHistory(),toast("Benchmark history cleared","success")}),renderPerfHistory()})();async function mergeWavBlobs(blobs){if(!blobs||blobs.length===0)return null;if(blobs.length===1)return blobs[0];function parseWav(bytes){const v=new DataView(bytes.buffer);let off=12,fmt=null,dataOff=0,dataSize=0;for(;off+8<=bytes.length;){const id=v.getUint32(off,!1),sz=v.getUint32(off+4,!0);id===1718449184?fmt={channels:v.getUint16(off+10,!0),sampleRate:v.getUint32(off+12,!0),bitDepth:v.getUint16(off+22,!0)}:id===1684108385&&(dataOff=off+8,dataSize=sz),off+=8+sz}return{fmt,dataOff,dataSize}}const parsed=[];for(const b of blobs){const bytes=new Uint8Array(await b.arrayBuffer()),p=parseWav(bytes);if(!p.fmt)throw new Error("Invalid WAV in chunk");parsed.push({bytes,...p})}const ref=parsed[0].fmt,totalPcm=parsed.reduce((s,p)=>s+p.dataSize,0),out=new Uint8Array(44+totalPcm),dv=new DataView(out.buffer);dv.setUint32(0,1380533830,!1),dv.setUint32(4,36+totalPcm,!0),dv.setUint32(8,1463899717,!1),dv.setUint32(12,1718449184,!1),dv.setUint32(16,16,!0),dv.setUint16(20,1,!0),dv.setUint16(22,ref.channels,!0),dv.setUint32(24,ref.sampleRate,!0),dv.setUint32(28,ref.sampleRate*ref.channels*(ref.bitDepth>>3),!0),dv.setUint16(32,ref.channels*(ref.bitDepth>>3),!0),dv.setUint16(34,ref.bitDepth,!0),dv.setUint32(36,1684108385,!1),dv.setUint32(40,totalPcm,!0);let pos=44;for(const p of parsed)out.set(p.bytes.slice(p.dataOff,p.dataOff+p.dataSize),pos),pos+=p.dataSize;return new Blob([out],{type:"audio/wav"})}function splitTextIntoChunks(text,maxLen=800){const abbrev=/\b(Mr|Mrs|Ms|Dr|Prof|Sr|Jr|vs|etc|e\.g|i\.e)\.\s/g,safe=text.replace(abbrev,m=>m.replace(".","\0")),restore=s=>s.replace(/\x00/g,"."),segments=[];for(const line of safe.split(` `)){if(!line.trim()){segments.length&&segments.push("");continue}const sentences=line.match(/[^.!?]+[.!?]+\s*/g)||[],rest=line.replace(/[^.!?]+[.!?]+\s*/g,"").trim();segments.push(...sentences),rest&&segments.push(rest)}if(!segments.length)return[text];const chunks=[];let cur="",paraPending=!1;for(const seg of segments){if(seg===""){paraPending=!0;continue}const joined=cur?cur+(paraPending?` `:" ")+seg.trim():seg.trim();joined.length>maxLen&&cur?(chunks.push(restore(cur.trim())),cur=seg.trim()):cur=joined,paraPending=!1}return cur.trim()&&chunks.push(restore(cur.trim())),chunks.length?chunks:[text]}async function generateChunkedTts(voice,text,backend,instruct,extra=null){const chunks=splitTextIntoChunks(text),prog=$("preview-chunk-progress");prog&&(prog.hidden=!1,prog.textContent=`Chunk 1 / ${chunks.length}\u2026`);const blobs=[];for(let i=0;i20&&_genHistory.pop(),renderHistory()}function _histAvatarColor(id){const palette=["#3b82f6","#10b981","#8b5cf6","#f59e0b","#ef4444","#ec4899","#06b6d4","#84cc16"];let h=0;for(let i=0;i<(id||"").length;i++)h=h*31+id.charCodeAt(i)>>>0;return palette[h%palette.length]}function renderHistory(){const list=$("history-list");if(list){if(!_genHistory.length){list.innerHTML='
No generations yet.
';return}list.innerHTML=_genHistory.map(item=>{const t=new Date(item.ts),ts=String(t.getHours()).padStart(2,"0")+":"+String(t.getMinutes()).padStart(2,"0"),preview=escHtml(item.text.length>90?item.text.slice(0,90)+"\u2026":item.text),v=(window._voices||[]).find(vx=>vx.id===item.voice),avatar=v!=null&&v.has_picture?``:`${(item.voice||"?")[0].toUpperCase()}`;return`
${avatar} ${escHtml(item.voice)} ${escHtml(item.backend)} ${ts}
${preview}
`}).join(""),list.querySelectorAll(".hist-del-btn").forEach(btn=>{btn.addEventListener("click",e=>{var _a2;e.stopPropagation();const hid=(_a2=btn.closest("[data-hid]"))==null?void 0:_a2.dataset.hid,idx=_genHistory.findIndex(h=>h.id===hid);idx!==-1&&(_genHistory.splice(idx,1),renderHistory())})}),list.querySelectorAll(".hist-play-btn").forEach(btn=>{btn.addEventListener("click",()=>{const item=_genHistory.find(h=>{var _a2;return h.id===((_a2=btn.closest("[data-hid]"))==null?void 0:_a2.dataset.hid)});if(!(item!=null&&item.url))return;const audio=$("preview-audio");audio.src=item.url,audio.style.display="",audio.play().catch(()=>{})})}),list.querySelectorAll(".hist-reuse-btn").forEach(btn=>{btn.addEventListener("click",()=>{const item=_genHistory.find(h=>{var _a2;return h.id===((_a2=btn.closest("[data-hid]"))==null?void 0:_a2.dataset.hid)});if(!item)return;$("preview-text-area").value=item.text;const bSel=$("tts-backend-select");bSel&&[...bSel.options].forEach(o=>{o.value===item.backend&&(bSel.value=item.backend)});const vSel=$("tts-voice-select");vSel&&[...vSel.options].forEach(o=>{o.value===item.voice&&(vSel.value=item.voice)}),toast("Settings restored from history","success")})}),list.querySelectorAll(".hist-playlist-btn").forEach(btn=>{btn.addEventListener("click",()=>{const item=_genHistory.find(h=>{var _a2;return h.id===((_a2=btn.closest("[data-hid]"))==null?void 0:_a2.dataset.hid)});item!=null&&item.blob&&playlistAdd(item.voice,item.text,item.blob,item.url)})})}}(_R=$("history-clear-btn"))==null||_R.addEventListener("click",()=>{_genHistory.length=0,renderHistory(),toast("History cleared","success")});const _playlist=[];function playlistAdd(voice,text,blob,url){const id="pl-"+Date.now()+"-"+Math.random().toString(36).slice(2,5);_playlist.push({id,voice,text:text.slice(0,120),blob,url}),renderPlaylist(),$("playlist-export-btn")&&($("playlist-export-btn").disabled=!1),toast("Added to playlist","success")}function renderPlaylist(){const list=$("playlist-list");if(list){if(!_playlist.length){list.innerHTML='
No clips in playlist. Use + Playlist after generating.
',$("playlist-export-btn")&&($("playlist-export-btn").disabled=!0);return}list.innerHTML=_playlist.map((item,i)=>`
${i+1}
${escHtml(item.voice)} ${escHtml(item.text.length>70?item.text.slice(0,70)+"\u2026":item.text)}
`).join(""),list.querySelectorAll(".pl-rm").forEach(btn=>{btn.addEventListener("click",()=>{var _a2;const pid=(_a2=btn.closest("[data-pid]"))==null?void 0:_a2.dataset.pid,idx=_playlist.findIndex(p=>p.id===pid);idx>=0&&(_playlist.splice(idx,1),renderPlaylist())})}),list.querySelectorAll(".pl-up").forEach(btn=>{btn.addEventListener("click",()=>{var _a2;const pid=(_a2=btn.closest("[data-pid]"))==null?void 0:_a2.dataset.pid,idx=_playlist.findIndex(p=>p.id===pid);idx>0&&([_playlist[idx-1],_playlist[idx]]=[_playlist[idx],_playlist[idx-1]],renderPlaylist())})}),list.querySelectorAll(".pl-dn").forEach(btn=>{btn.addEventListener("click",()=>{var _a2;const pid=(_a2=btn.closest("[data-pid]"))==null?void 0:_a2.dataset.pid,idx=_playlist.findIndex(p=>p.id===pid);idx<_playlist.length-1&&([_playlist[idx],_playlist[idx+1]]=[_playlist[idx+1],_playlist[idx]],renderPlaylist())})})}}(_S=$("add-to-playlist-btn"))==null||_S.addEventListener("click",()=>{var _a2,_b2,_c2;if(!previewBlob){toast("Generate audio first","error");return}playlistAdd(window._previewVoice||((_a2=$("tts-voice-select"))==null?void 0:_a2.value)||"",window._previewText||((_b2=$("preview-text-area"))==null?void 0:_b2.value)||"",previewBlob,((_c2=$("preview-audio"))==null?void 0:_c2.src)||"")}),(_T=$("playlist-export-btn"))==null||_T.addEventListener("click",async()=>{if(!_playlist.length)return;const btn=$("playlist-export-btn"),orig=btn.textContent;btn.disabled=!0,btn.textContent="Merging\u2026";try{const merged=await mergeWavBlobs(_playlist.map(p=>p.blob).filter(Boolean));if(!merged)throw new Error("No audio to export");const a=document.createElement("a");a.href=URL.createObjectURL(merged),a.download="playlist_"+Date.now()+".wav",a.click(),toast("Playlist exported as WAV","success")}catch(e){toast("Export failed: "+e.message,"error")}finally{btn.disabled=_playlist.length===0,btn.textContent=orig}}),(_U=$("playlist-clear-btn"))==null||_U.addEventListener("click",()=>{_playlist.length=0,renderPlaylist(),toast("Playlist cleared","success")});const _FX_PRESETS={studio:{reverb:{on:!0,room_size:.6,wet:.35},compressor:{on:!0,threshold_db:-18,ratio:3}},broadcast:{compressor:{on:!0,threshold_db:-12,ratio:6}},telephone:{compressor:{on:!0,threshold_db:-10,ratio:8}},warm:{reverb:{on:!0,room_size:.2,wet:.15},compressor:{on:!0,threshold_db:-20,ratio:2}},radio:{compressor:{on:!0,threshold_db:-14,ratio:5}}};function fxSliderBind(sliderId,labelId,fmt){const s=$(sliderId),l=$(labelId);if(!s||!l)return;const upd=()=>{l.textContent=fmt(s.value)};upd(),s.addEventListener("input",upd)}fxSliderBind("fx-reverb-room","fx-reverb-room-val",v=>parseFloat(v).toFixed(2)),fxSliderBind("fx-reverb-wet","fx-reverb-wet-val",v=>parseFloat(v).toFixed(2)),fxSliderBind("fx-comp-thresh","fx-comp-thresh-val",v=>v+" dB"),fxSliderBind("fx-comp-ratio","fx-comp-ratio-val",v=>v+":1"),fxSliderBind("fx-chorus-rate","fx-chorus-rate-val",v=>parseFloat(v).toFixed(1)+" Hz"),fxSliderBind("fx-chorus-mix","fx-chorus-mix-val",v=>parseFloat(v).toFixed(2)),fxSliderBind("fx-pitch-semi","fx-pitch-semi-val",v=>(parseFloat(v)>=0?"+":"")+v+" st"),(_V=$("effects-preset"))==null||_V.addEventListener("change",()=>{const preset=_FX_PRESETS[$("effects-preset").value];preset&&(["fx-reverb-on","fx-compressor-on","fx-chorus-on","fx-pitch-on"].forEach(id=>{const el=$(id);el&&(el.checked=!1)}),preset.reverb&&($("fx-reverb-on").checked=!!preset.reverb.on,preset.reverb.room_size!=null&&($("fx-reverb-room").value=preset.reverb.room_size),preset.reverb.wet!=null&&($("fx-reverb-wet").value=preset.reverb.wet)),preset.compressor&&($("fx-compressor-on").checked=!!preset.compressor.on,preset.compressor.threshold_db!=null&&($("fx-comp-thresh").value=preset.compressor.threshold_db),preset.compressor.ratio!=null&&($("fx-comp-ratio").value=preset.compressor.ratio)),["fx-reverb-room","fx-reverb-wet","fx-comp-thresh","fx-comp-ratio","fx-chorus-rate","fx-chorus-mix","fx-pitch-semi"].forEach(id=>{var _a2;return(_a2=$(id))==null?void 0:_a2.dispatchEvent(new Event("input"))}))}),(_W=$("effects-reset-btn"))==null||_W.addEventListener("click",()=>{$("effects-preset").value="",["fx-reverb-on","fx-compressor-on","fx-chorus-on","fx-pitch-on"].forEach(id=>{const el=$(id);el&&(el.checked=!1)}),$("fx-reverb-room").value="0.35",$("fx-reverb-wet").value="0.25",$("fx-comp-thresh").value="-20",$("fx-comp-ratio").value="4",$("fx-chorus-rate").value="1",$("fx-chorus-mix").value="0.5",$("fx-pitch-semi").value="0",["fx-reverb-room","fx-reverb-wet","fx-comp-thresh","fx-comp-ratio","fx-chorus-rate","fx-chorus-mix","fx-pitch-semi"].forEach(id=>{var _a2;return(_a2=$(id))==null?void 0:_a2.dispatchEvent(new Event("input"))})});let _effectsSourceBlob=null;(_X=$("effects-apply-btn"))==null||_X.addEventListener("click",async()=>{var _a2,_b2,_c2,_d2;const chain=[];if((_a2=$("fx-reverb-on"))!=null&&_a2.checked&&chain.push({type:"reverb",params:{room_size:+$("fx-reverb-room").value,wet:+$("fx-reverb-wet").value,dry:1-+$("fx-reverb-wet").value}}),(_b2=$("fx-compressor-on"))!=null&&_b2.checked&&chain.push({type:"compressor",params:{threshold_db:+$("fx-comp-thresh").value,ratio:+$("fx-comp-ratio").value}}),(_c2=$("fx-chorus-on"))!=null&&_c2.checked&&chain.push({type:"chorus",params:{rate_hz:+$("fx-chorus-rate").value,mix:+$("fx-chorus-mix").value}}),(_d2=$("fx-pitch-on"))!=null&&_d2.checked&&chain.push({type:"pitch_shift",params:{semitones:+$("fx-pitch-semi").value}}),!chain.length){toast("Enable at least one effect","error");return}const btn=$("effects-apply-btn"),st=$("effects-status");btn.disabled=!0,st&&(st.textContent="Processing\u2026");try{let blob=previewBlob||_effectsSourceBlob;if(!blob){const args=window._effectsSynthArgs;if(!args||typeof fetchTtsPreviewBlob!="function"){toast("Generate audio first","error");return}st&&(st.textContent="Synthesizing audio\u2026"),blob=await fetchTtsPreviewBlob(args.voice,args.text,"wav",args.instruct,args.backend)}const fd=new FormData;fd.append("audio",blob,"audio.wav"),fd.append("effects",JSON.stringify(chain));const resp=await fetch("/api/audio/effects",{method:"POST",body:fd});if(!resp.ok){const e=await resp.json().catch(()=>({}));throw new Error(e.detail||resp.statusText)}const out=await resp.blob();_effectsSourceBlob||(_effectsSourceBlob=previewBlob),previewBlob=out;const audio=$("preview-audio");audio.src=URL.createObjectURL(out),audio.style.display="",audio.play().catch(()=>{}),st&&(st.textContent="Applied."),toast("Effects applied","success")}catch(e){st&&(st.textContent=""),toast("Effects failed: "+e.message,"error")}finally{btn.disabled=!1}});let _refineOriginal=null;(function(){const inp=$("refine-llm-url");if(!inp)return;const saved=_appSettings&&_appSettings.refine_llm_url||localStorage.getItem("refine-llm-url");saved?inp.value=saved:inp.value=_appSettings&&_appSettings.llm_url||_appSettings&&_appSettings.engine_local_urls&&_appSettings.engine_local_urls.ollama||localStorage.getItem("llm-local-url-ollama")||"http://localhost:11434/v1",inp.addEventListener("input",()=>{localStorage.setItem("refine-llm-url",inp.value),_patchSettings({refine_llm_url:inp.value}),_appSettings&&(_appSettings.refine_llm_url=inp.value)})})();function updateRefineButtonState(){var _a2,_b2;const btn=$("refine-btn");btn&&(btn.disabled=!((_b2=(_a2=$("stt-tts-text"))==null?void 0:_a2.value)!=null&&_b2.trim()))}(_Y=$("stt-tts-text"))==null||_Y.addEventListener("input",updateRefineButtonState),(_Z=$("refine-btn"))==null||_Z.addEventListener("click",async()=>{var _a2,_b2,_c2,_d2,_e2,_f2,_g2,_h2,_i2,_j2,_k2,_l2,_m2,_n2,_o2,_p2;const text=(_b2=(_a2=$("stt-tts-text"))==null?void 0:_a2.value)==null?void 0:_b2.trim();if(!text)return;const btn=$("refine-btn"),st=$("refine-status");btn.disabled=!0,st&&(st.textContent="Refining\u2026");try{const r=await fetch("/api/refine-text",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text,llm_url:((_d2=(_c2=$("refine-llm-url"))==null?void 0:_c2.value)==null?void 0:_d2.trim())||(_appSettings==null?void 0:_appSettings.llm_url)||"http://localhost:11434/v1",model:((_f2=(_e2=$("refine-model"))==null?void 0:_e2.value)==null?void 0:_f2.trim())||(_appSettings==null?void 0:_appSettings.refine_model)||(_appSettings==null?void 0:_appSettings.llm_model)||"",toggles:{fillers:(_h2=(_g2=$("refine-fillers"))==null?void 0:_g2.checked)!=null?_h2:!0,repetitions:(_j2=(_i2=$("refine-repetitions"))==null?void 0:_i2.checked)!=null?_j2:!0,corrections:(_l2=(_k2=$("refine-corrections"))==null?void 0:_k2.checked)!=null?_l2:!0,punctuation:(_n2=(_m2=$("refine-punctuation"))==null?void 0:_m2.checked)!=null?_n2:!0}})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();_refineOriginal=text,$("stt-tts-text").value=d.text,$("refine-restore-btn")&&($("refine-restore-btn").disabled=!1),st&&(st.textContent="Done."),toast("Transcription refined","success")}catch(e){st&&(st.textContent=""),toast("Refinement failed: "+e.message,"error")}finally{btn.disabled=!((_p2=(_o2=$("stt-tts-text"))==null?void 0:_o2.value)!=null&&_p2.trim())}}),(__=$("refine-restore-btn"))==null||__.addEventListener("click",()=>{_refineOriginal&&($("stt-tts-text").value=_refineOriginal,_refineOriginal=null,$("refine-restore-btn")&&($("refine-restore-btn").disabled=!0),$("refine-status")&&($("refine-status").textContent=""),toast("Original transcription restored","success"))}),(_$=$("preview-style-instruction"))==null||_$.addEventListener("input",()=>{typeof updateBackendHelp=="function"&&updateBackendHelp()}),function(){var _a2;const batchBackendSel=$("batch-backend-select"),batchRunsSel=$("batch-runs"),batchLoadBtn=$("batch-load-voices-btn"),batchSearchInput=$("batch-voice-search"),batchSelectAllBtn=$("batch-select-all-btn"),batchSelectNoneBtn=$("batch-select-none-btn"),batchVoiceList=$("batch-voice-list"),batchSelCount=$("batch-selected-count"),batchRunBtn=$("batch-run-btn"),batchStopBtn=$("batch-stop-btn"),perfRunSelectedBtn=$("perf-run-selected-btn"),batchProgress=$("batch-progress"),batchProgLabel=$("batch-progress-label"),batchProgCount=$("batch-progress-count"),batchProgBar=$("batch-progress-bar"),batchResultsCard=$("batch-results-card"),batchResultsLabel=$("batch-results-label"),batchTbody=$("batch-tbody");if(!batchRunBtn)return;let batchStopped=!1,batchResults=[],batchVoices=[],batchSelected=new Set,batchSortField="factor",batchSortDir=-1;function populateBatchBackends(){if(!batchBackendSel)return;const cur=batchBackendSel.value;batchBackendSel.innerHTML=availableTtsBackends().map(b=>``).join("")||''}populateBatchBackends();function libraryVoice(id){return(window._voices||[]).find(v=>v&&v.id===id)||null}function normalizeBatchVoice(value){const id=typeof value=="string"?value:(value==null?void 0:value.id)||(value==null?void 0:value.voice)||(value==null?void 0:value.name)||(value==null?void 0:value.voice_id)||"",meta=typeof value=="object"?value.meta||libraryVoice(id)||value:libraryVoice(id)||null;return id?{id,label:(meta==null?void 0:meta.display_name)||(value==null?void 0:value.label)||(meta==null?void 0:meta.name)||(value==null?void 0:value.name)||id,meta}:null}function batchVoiceStats(v){var _a3,_b2;const meta=(v==null?void 0:v.meta)||libraryVoice(v==null?void 0:v.id)||{},b=meta.benchmark||{},parts=[],dur=Number(meta.duration),speed=Number((_a3=b.speed)!=null?_a3:b.avg_speed),rtf=Number((_b2=b.rtf)!=null?_b2:b.avg_rtf);return Number.isFinite(dur)&&dur>0&&parts.push(`${dur.toFixed(1)}s`),Number.isFinite(speed)&&speed>0?parts.push(`${speed.toFixed(2)}x`):Number.isFinite(rtf)&&rtf>0&&parts.push(`RTF ${rtf.toFixed(2)}`),parts.join(" \xB7 ")}function batchColor(id){const palette=["#3b82f6","#10b981","#8b5cf6","#f59e0b","#ef4444","#ec4899","#06b6d4","#84cc16"];let h=0;for(let i=0;i>>0;return palette[h%palette.length]}function batchAvatar(v){const meta=(v==null?void 0:v.meta)||libraryVoice(v==null?void 0:v.id)||{};if(meta.has_picture)return``;const icon=window.voiceAvatarIcon?window.voiceAvatarIcon(meta.avatar,28):null;if(icon)return icon.replace(/vp-avatar/g,"batch-voice-avatar");const init=(v.label||v.id||"?")[0].toUpperCase();return`${escHtml(init)}`}function batchHistoryExtraFields(voice,backend){const item=batchVoices.find(v=>v.id===voice)||{},meta=item.meta||libraryVoice(voice)||{};return{voiceMeta:{label:meta.display_name||meta.name||item.label||voice,lang:meta.lang||meta.language||"",gender:meta.gender||"",flag:meta.flag||"",avatar:meta.avatar||"",has_picture:!!meta.has_picture},device:typeof backendComputeDevice=="function"?backendComputeDevice(backend):""}}function batchSearchText(v){const meta=v.meta||{};return[v.id,v.label,meta.display_name,meta.name,meta.lang,meta.language,Array.isArray(meta.tags)?meta.tags.join(" "):meta.tags,batchVoiceStats(v)].filter(Boolean).join(" ").toLowerCase()}function syncRunSelectedLabel(){const count=batchSelected.size;perfRunSelectedBtn&&(perfRunSelectedBtn.disabled=count===0,perfRunSelectedBtn.innerHTML=` ${count?`Run ${count} selected voice${count===1?"":"s"}`:"Run selected voices"}`)}function updateSelCount(){const total=batchVoices.length,checked=batchSelected.size,visible=filteredBatchVoices().length;batchSelCount&&(batchSelCount.textContent=total?`${checked} of ${total} selected${visible!==total?` \xB7 ${visible} shown`:""}`:""),batchRunBtn.disabled=checked===0,syncRunSelectedLabel()}function filteredBatchVoices(){const q=((batchSearchInput==null?void 0:batchSearchInput.value)||"").trim().toLowerCase();return q?batchVoices.filter(v=>batchSearchText(v).includes(q)):batchVoices}function renderBatchVoiceList(){if(!batchVoiceList)return;const visible=filteredBatchVoices();if(!batchVoices.length){batchVoiceList.innerHTML='
No voices found. Fetch voices above or reload from backend.
',updateSelCount();return}if(!visible.length){batchVoiceList.innerHTML='
No matching voices.
',updateSelCount();return}const activeIds=new Set(activeVoiceIds());batchVoiceList.innerHTML=visible.map(v=>{const checked=batchSelected.has(v.id),isActive=activeIds.has(v.id),stats=batchVoiceStats(v),meta=v.meta||libraryVoice(v.id)||{},flag=meta.flag||"",lang=meta.lang||meta.language||"",gender={M:"\u2642",F:"\u2640",N:"\u26AC"}[meta.gender]||"",langGender=[flag||lang,gender].filter(Boolean).join(" ");return``}).join(""),batchVoiceList.querySelectorAll(".batch-voice-cb").forEach(cb=>cb.addEventListener("change",()=>{cb.checked?batchSelected.add(cb.value):batchSelected.delete(cb.value),renderBatchVoiceList()})),updateSelCount()}function buildVoiceList(voices,opts={}){const previous=opts.preserve?new Set(batchSelected):null,byId=new Map;if((voices||[]).map(normalizeBatchVoice).filter(Boolean).forEach(v=>byId.set(v.id,v)),batchVoices=Array.from(byId.values()).sort((a,b)=>(a.label||a.id).localeCompare(b.label||b.id)),previous)batchSelected=new Set(batchVoices.filter(v=>previous.has(v.id)).map(v=>v.id));else if(opts.keepSelection)batchSelected=new Set(Array.from(batchSelected).filter(id=>byId.has(id)));else{const activeIds=new Set(activeVoiceIds());batchSelected=new Set(batchVoices.filter(v=>activeIds.has(v.id)).map(v=>v.id))}renderBatchVoiceList()}async function populateFromLibrary(){try{(!window._voices||!window._voices.length)&&typeof loadVoiceLibrary=="function"&&await loadVoiceLibrary();const voices=(window._voices||[]).filter(v=>v.enabled!==!1).map(v=>({id:v.id,label:v.display_name||v.name||v.id,meta:v}));buildVoiceList(voices)}catch(e){batchVoiceList&&(batchVoiceList.innerHTML=`
Voice library unavailable: ${escHtml(e.message)}
`),updateSelCount()}}window.populateBatchBenchmarkFromLibrary=populateFromLibrary;async function loadBatchVoicesFromBackend(){const backend=batchBackendSel.value;if(!backend){toast("Select a backend first","error");return}batchLoadBtn.disabled=!0,batchVoiceList.innerHTML='
Loading from backend...
';try{const voices=(await fetch("/api/tts-voices?backend="+encodeURIComponent(backend)).then(r=>r.json())).map(v=>{const id=backendVoiceId(v);return{id,label:id,meta:libraryVoice(id)||(typeof v=="object"?v:null)}}).filter(v=>v.id);buildVoiceList(voices)}catch(e){batchVoiceList.innerHTML=`
Failed: ${escHtml(e.message)}
`,updateSelCount()}finally{batchLoadBtn.disabled=!1}}batchLoadBtn.addEventListener("click",loadBatchVoicesFromBackend),batchSearchInput==null||batchSearchInput.addEventListener("input",renderBatchVoiceList),batchSelectAllBtn.addEventListener("click",()=>{batchVoices.forEach(v=>batchSelected.add(v.id)),renderBatchVoiceList()}),batchSelectNoneBtn.addEventListener("click",()=>{batchSelected.clear(),renderBatchVoiceList()}),window.addEventListener("benchmark:tts-voices-fetched",e=>{const detail=e.detail||{};detail.backend&&batchBackendSel&&(batchBackendSel.value=detail.backend),buildVoiceList(detail.voices||[])});function batchSortValue(r,field){const factor=r.ok&&r.avgRtf>0?1/r.avgRtf:null;switch(field){case"voice":return(r.voice||"").toLowerCase();case"lang":return(r.flag||r.lang||"").toLowerCase();case"gender":return(r.gender||"").toLowerCase();case"device":return typeof backendComputeDevice=="function"?backendComputeDevice(r.backend||"").toLowerCase():"";case"latency":return r.ok?r.avgLatency:999999;case"best":return r.ok?r.minLatency:999999;case"duration":return r.ok&&r.avgAudio>0?r.avgAudio:999999;case"factor":return factor!=null?factor:-1;case"time":return r.ok?r.avgLatency/1e3:999999;case"wpm":return r.ok&&r.avgAudio>0&&r.wordCount>0?Math.round(r.wordCount/(r.avgAudio/60)):-1;default:return 0}}function renderBatchResults(){if(!batchResults.length){batchResultsCard.style.display="none";return}batchResultsCard.style.display="";const sorted=batchResults.slice().sort((a,b)=>{if(a.ok&&!b.ok)return-1;if(!a.ok&&b.ok)return 1;const av=batchSortValue(a,batchSortField),bv=batchSortValue(b,batchSortField);return typeof av=="string"?av.localeCompare(bv)*batchSortDir:(av-bv)*batchSortDir}),okCount=batchResults.filter(r=>r.ok).length;batchResultsLabel&&(batchResultsLabel.textContent=`${okCount} / ${batchResults.length} voices \u2014 ${batchBackendSel.value}`),document.querySelectorAll("#batch-table th[data-sort]").forEach(th=>{th.classList.remove("perf-sort-asc","perf-sort-desc"),th.dataset.sort===batchSortField&&th.classList.add(batchSortDir===1?"perf-sort-asc":"perf-sort-desc")}),batchTbody.innerHTML=sorted.map(r=>{const factor=r.ok&&r.avgRtf>0?1/r.avgRtf:null,timeSec=r.ok&&r.avgLatency>0?r.avgLatency/1e3:null,wpm=r.ok&&r.avgAudio>0&&r.wordCount>0?Math.round(r.wordCount/(r.avgAudio/60)):null,factorCls=factor==null?"":factor>=1?"perf-good":"perf-slow",trend=r.trend?`${r.trend.label}`:"",device=typeof backendComputeDevice=="function"?backendComputeDevice(r.backend||batchBackendSel.value):"Unknown",deviceCls=typeof backendComputeDeviceClass=="function"?backendComputeDeviceClass(r.backend||batchBackendSel.value):"",genderMap={M:"\u2642",F:"\u2640",N:"\u26AC"};return` ${escHtml(r.voice)} ${escHtml(r.flag||r.lang||"\u2014")} ${escHtml(genderMap[r.gender]||"\u2014")} ${escHtml(device)} ${r.ok?Math.round(r.avgLatency)+" ms":"\u2014"} ${r.ok?r.minLatency+" ms":"\u2014"} ${r.ok&&r.avgAudio>0?r.avgAudio.toFixed(1)+"s":"\u2014"} ${factor!=null?factor.toFixed(2)+"\xD7":"\u2014"}${trend} ${timeSec!=null?timeSec.toFixed(1)+"s":"\u2014"} ${wpm!=null?wpm+" wpm":"\u2014"} ${r.ok?'OK':`${escHtml(r.error||"Failed")}`} `}).join("")}(_a2=document.querySelector("#batch-table"))==null||_a2.addEventListener("click",e=>{const th=e.target.closest("th[data-sort]");if(!th)return;const field=th.dataset.sort;field===batchSortField?batchSortDir*=-1:(batchSortField=field,batchSortDir=field==="voice"||field==="lang"||field==="gender"?1:-1),renderBatchResults()});async function runBatchBenchmark(){var _a3,_b2,_c2,_d2,_e2;const topBackend=((_a3=$("perf-backend-select"))==null?void 0:_a3.value)||"";topBackend&&batchBackendSel&&batchBackendSel.value!==topBackend&&(batchBackendSel.value=topBackend);const topRuns=((_b2=$("perf-runs"))==null?void 0:_b2.value)||"";topRuns&&batchRunsSel&&Array.from(batchRunsSel.options).some(o=>o.value===topRuns)&&(batchRunsSel.value=topRuns);const singleVoice=((_c2=$("perf-voice-select"))==null?void 0:_c2.value)||"";!batchSelected.size&&singleVoice&&batchVoices.some(v=>v.id===singleVoice)&&(batchSelected.add(singleVoice),renderBatchVoiceList());const backend=batchBackendSel.value,text=(_d2=$("perf-text"))==null?void 0:_d2.value.trim(),runs=parseInt(batchRunsSel.value)||1,validIds=new Set(batchVoices.map(v=>v.id)),selected=Array.from(batchSelected).filter(id=>validIds.has(id));if(!backend){toast("Select a backend first","error");return}if(!text){toast("Enter sample text in the single-voice form above","error");return}if(!selected.length){toast("Select one or more voices first","error");return}batchStopped=!1,batchResults=[],batchRunBtn.disabled=!0,perfRunSelectedBtn&&(perfRunSelectedBtn.disabled=!0),batchStopBtn.disabled=!1,batchProgress.style.display="",batchResultsCard.style.display="none";for(let vi=0;vibv.id===voice),_meta=(_vm==null?void 0:_vm.meta)||libraryVoice(voice)||{},entry={voice,backend,ok:!1,avgLatency:0,minLatency:0,avgRtf:0,avgAudio:0,wordCount:text?text.trim().split(/\s+/).length:0,lang:_meta.lang||_meta.language||"",flag:_meta.flag||"",gender:_meta.gender||"",error:""},rowRunResults=[];for(let ri=0;risum+r.lat,0)/rowRunResults.length,entry.minLatency=Math.min(...rowRunResults.map(r=>r.lat));const durRows=rowRunResults.filter(r=>r.dur>0);entry.avgAudio=durRows.length?durRows.reduce((sum,r)=>sum+r.dur,0)/durRows.length:0;const rtfArr=durRows.map(r=>r.lat/1e3/r.dur);if(entry.avgRtf=rtfArr.length?rtfArr.reduce((sum,value)=>sum+value,0)/rtfArr.length:0,entry.avgRtf>0){const prev=perfHistoryLoad().filter(e=>e.backend===backend&&e.voice===voice&&typeof e.avgRtf=="number");if(prev.length){const prevRtf=prev[prev.length-1].avgRtf,delta=entry.avgRtf-prevRtf,pct=Math.abs(delta/Math.max(prevRtf,.01))*100;pct<5?entry.trend={cls:"perf-trend-stable",label:"stable"}:delta<0?entry.trend={cls:"perf-trend-better",label:`${pct.toFixed(0)}% faster`}:entry.trend={cls:"perf-trend-worse",label:`${pct.toFixed(0)}% slower`}}perfHistoryAdd({ts:Date.now(),backend,voice,textLen:text.length,...batchHistoryExtraFields(voice,backend),avgLatencyMs:entry.avgLatency,minLatencyMs:entry.minLatency,maxLatencyMs:Math.max(...rowRunResults.map(r=>r.lat)),avgRtf:entry.avgRtf,runCount:rowRunResults.length,allOk:!0})}}batchResults.push(entry),renderBatchResults()}batchProgBar.style.width="100%",batchProgLabel.textContent=batchStopped?`Stopped after ${batchResults.length} voice${batchResults.length!==1?"s":""}.`:`Done - ${batchResults.length} voice${batchResults.length!==1?"s":""} benchmarked.`,batchStopBtn.disabled=!0,batchRunBtn.disabled=!1,syncRunSelectedLabel(),renderPerfHistory();const ok=batchResults.filter(r=>r.ok),best=ok.slice().sort((a,b)=>a.avgRtf-b.avgRtf)[0];toast(`Batch done: ${ok.length}/${batchResults.length} OK`+(best?`, best RTF ${best.avgRtf.toFixed(2)} (${best.voice})`:""),ok.lengthr.ok&&r.avgRtf>0&&r.avgAudio>0);if(syncable.length&&typeof window.loadVoiceLibrary=="function"){const benchText=(((_e2=$("perf-text"))==null?void 0:_e2.value)||"").trim();await Promise.allSettled(syncable.map(r=>fetch("/api/voice/meta",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:r.voice,benchmark:{ok:!0,text:benchText,elapsed_sec:r.avgLatency/1e3,audio_sec:r.avgAudio,speed:1/r.avgRtf,rtf:r.avgRtf,ttfa_ms:r.minLatency}})}))),await window.loadVoiceLibrary()}}batchRunBtn.addEventListener("click",runBatchBenchmark),perfRunSelectedBtn==null||perfRunSelectedBtn.addEventListener("click",runBatchBenchmark),batchStopBtn.addEventListener("click",()=>{batchStopped=!0,batchStopBtn.disabled=!0,batchProgLabel.textContent="Stopping after current voice..."})}(),function(){let initialized=!1,benchmarkDataLoaded=!1,sttEngines=[],turnHistoryCount=0;const q=id=>document.getElementById(id),fmtMs=ms=>ms==null?"\u2014":ms>=1e3?(ms/1e3).toFixed(2)+"s":Math.round(ms)+"ms",fmtSec=sec=>sec==null?"\u2014":Number(sec).toFixed(Number(sec)<10?2:1)+"s",esc=s=>typeof escHtml=="function"?escHtml(s):String(s!=null?s:"").replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[c]),say=(msg,type="")=>typeof toast=="function"?toast(msg,type):console.log(msg);function setBenchTab(name){document.querySelectorAll(".bench-tab").forEach(btn=>btn.classList.toggle("active",btn.dataset.benchTab===name)),document.querySelectorAll(".bench-pane").forEach(pane=>pane.classList.toggle("active",pane.dataset.benchPane===name))}async function fetchJson(url,opts){const r=await fetch(url,opts);if(!r.ok){let detail=r.statusText||`HTTP ${r.status}`;try{const d=await r.json();detail=typeof d.detail=="string"?d.detail:JSON.stringify(d.detail||d)}catch{try{detail=await r.text()}catch{}}throw new Error(detail||`HTTP ${r.status}`)}return r.json()}function dedupeEngines(items){const seen=new Set,out=[];for(const item of items){const key=`${item.url||""}|${item.model||""}|${item.id||item.label||""}`;seen.has(key)||!item.url||(seen.add(key),out.push(item))}return out}function benchmarkModelList(engine){const values=[],add=v=>{const s=String(v||"").trim();s&&!values.includes(s)&&values.push(s)};return add(engine.model),(engine.models||[]).forEach(add),values.length||add("whisper-1"),values}async function fetchBenchmarkModels(engine){if(!(engine!=null&&engine.url))return[];try{const d=await fetchJson("/api/engine-models?type=stt&url="+encodeURIComponent(engine.url));return Array.isArray(d.models)?d.models.filter(Boolean):[]}catch(e){return console.warn("[benchmark models]",engine.label||engine.id,e),[]}}async function hydrateBenchmarkModels(engines){await Promise.all(engines.map(async engine=>{const found=await fetchBenchmarkModels(engine);engine.models=benchmarkModelList({...engine,models:found.length?found:engine.models}),engine.models.includes(engine.model)||(engine.model=engine.models[0]||engine.model||"whisper-1")}))}function renderBenchmarkModelOptions(index,filter=""){const engine=sttEngines[index],picker=document.querySelector(`.bench-model-picker[data-index="${index}"]`),list=picker==null?void 0:picker.querySelector(".bench-model-options");if(!engine||!list)return;const qv=String(filter||"").trim().toLowerCase(),models=benchmarkModelList(engine).filter(m=>!qv||m.toLowerCase().includes(qv));list.innerHTML=models.length?models.map(m=>` `).join(""):'
No matching models
'}function setBenchmarkModel(index,model){var _a2,_b2;const engine=sttEngines[index];if(!engine)return;engine.model=String(model||"").trim()||engine.model||"whisper-1";const picker=document.querySelector(`.bench-model-picker[data-index="${index}"]`),hidden=picker==null?void 0:picker.querySelector(".bench-engine-model"),label=picker==null?void 0:picker.querySelector(".bench-model-label");hidden&&(hidden.value=engine.model),label&&(label.textContent=engine.model),(_a2=picker==null?void 0:picker.querySelector(".bench-model-menu"))==null||_a2.setAttribute("hidden",""),(_b2=picker==null?void 0:picker.querySelector(".bench-model-trigger"))==null||_b2.classList.remove("open")}function closeBenchmarkModelPickers(except=null){document.querySelectorAll(".bench-model-picker").forEach(picker=>{var _a2,_b2;except&&picker===except||((_a2=picker.querySelector(".bench-model-menu"))==null||_a2.setAttribute("hidden",""),(_b2=picker.querySelector(".bench-model-trigger"))==null||_b2.classList.remove("open"))})}function bindBenchmarkModelPickers(){document.querySelectorAll(".bench-model-picker").forEach(picker=>{var _a2;picker.addEventListener("click",ev=>ev.stopPropagation());const index=Number(picker.dataset.index),trigger=picker.querySelector(".bench-model-trigger"),menu=picker.querySelector(".bench-model-menu"),search=picker.querySelector(".bench-model-search");renderBenchmarkModelOptions(index),trigger==null||trigger.addEventListener("click",ev=>{ev.preventDefault(),ev.stopPropagation();const opening=menu==null?void 0:menu.hasAttribute("hidden");closeBenchmarkModelPickers(picker),opening?(menu==null||menu.removeAttribute("hidden"),trigger.classList.add("open"),search.value="",renderBenchmarkModelOptions(index),setTimeout(()=>search==null?void 0:search.focus(),0)):(menu==null||menu.setAttribute("hidden",""),trigger.classList.remove("open"))}),search==null||search.addEventListener("input",()=>renderBenchmarkModelOptions(index,search.value)),(_a2=picker.querySelector(".bench-model-options"))==null||_a2.addEventListener("click",ev=>{const opt=ev.target.closest(".bench-model-option");opt&&setBenchmarkModel(index,opt.dataset.model||"")})})}async function loadBenchmarkSttEngines(){const list=q("bench-stt-engines"),count=q("bench-stt-engine-count");if(!list)return;list.innerHTML='
Checking STT engines...
';let settings={};const items=[];try{settings=await fetchJson("/api/settings")}catch{}try{((await fetchJson("/api/stt-backends")).backends||[]).forEach(b=>items.push({id:b.id,backend:b.id,label:b.label||b.id,url:b.url,model:b.models&&b.models[0]||b.model||"whisper-1",models:b.models||[],available:!!b.available,source:"configured"}))}catch(e){console.warn("[benchmark stt backends]",e)}try{((await fetchJson("/api/local-containers")).containers||[]).filter(c=>c.role==="stt"||c.role==="stt+tts").forEach(c=>{var _a2;const url=c.port?`http://host.docker.internal:${c.port}`:"",model=((_a2=settings==null?void 0:settings.engine_local_models)==null?void 0:_a2["dc-"+c.name])||c.model||"whisper-1";items.push({id:c.name,label:c.label||c.name,url,model,models:c.models||[],available:c.running===!0||c.status==="running",source:c.stack||"docker"})})}catch(e){console.warn("[benchmark local containers]",e)}if(sttEngines=dedupeEngines(items),count&&(count.textContent=sttEngines.length?`Loading models for ${sttEngines.length} engine${sttEngines.length===1?"":"s"}...`:"No engines found."),await hydrateBenchmarkModels(sttEngines),count&&(count.textContent=sttEngines.length?`${sttEngines.length} engine${sttEngines.length===1?"":"s"} loaded.`:"No engines found."),!sttEngines.length){list.innerHTML='
No STT engines found. Check Engines -> Speech to Text.
';return}list.innerHTML=sttEngines.map((e,i)=>` `).join(""),bindBenchmarkModelPickers()}async function loadBenchmarkVoiceLibrary(){const sel=q("bench-stt-library-voice");if(!sel)return;const picker=window.BenchmarkVoicePicker;picker?picker.populate("bench-stt-library-voice",[],{placeholder:"Loading voices...",empty:"Loading voices..."}):sel.innerHTML='';try{const usable=(await fetchJson("/api/voices")||[]).filter(v=>v&&v.enabled!==!1&&v.has_ref&&(v.transcript||"").trim()).sort((a,b)=>String(a.display_name||a.id).localeCompare(String(b.display_name||b.id))),items=usable.map(v=>({id:v.id,label:v.display_name||v.name||v.id,meta:v}));picker?picker.populate("bench-stt-library-voice",items,{placeholder:"-- choose from voice library --",empty:"No library voices with reference transcripts found"}):sel.innerHTML=''+usable.map(v=>``).join("");const note=q("bench-stt-source-note");note&&(note.textContent=usable.length?`${usable.length} voices with reference text available.`:"No library voices with reference transcripts found.")}catch(e){picker?picker.populate("bench-stt-library-voice",[],{placeholder:"Voice library unavailable",empty:"Voice library unavailable"}):sel.innerHTML='';const note=q("bench-stt-source-note");note&&(note.textContent="Could not load voice library: "+e.message)}}async function useBenchmarkLibraryVoice(){const sel=q("bench-stt-library-voice"),voiceId=(sel==null?void 0:sel.value)||"";if(!voiceId){say("Choose a voice library sample first","error");return}const btn=q("bench-stt-load-voice"),note=q("bench-stt-source-note");btn&&(btn.disabled=!0),note&&(note.textContent="Loading voice sample...");try{const d=await fetchJson("/api/voice-load",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:voiceId})});q("bench-stt-source-id")&&(q("bench-stt-source-id").value=d.id||""),q("bench-stt-ref-text")&&(q("bench-stt-ref-text").value=d.transcript||""),q("bench-stt-audio")&&(q("bench-stt-audio").value=""),q("bench-stt-ref-file")&&(q("bench-stt-ref-file").value=""),note&&(note.textContent=`${d.voice_id||voiceId} loaded from library${d.duration?` (${Number(d.duration).toFixed(1)}s)`:""}.`),say("Voice library sample loaded","success")}catch(e){note&&(note.textContent="Voice load failed."),say("Voice load failed: "+e.message,"error")}finally{btn&&(btn.disabled=!1)}}function selectedSttEngines(){return[...document.querySelectorAll(".bench-stt-engine-check:checked")].map(cb=>{const i=Number(cb.dataset.index),item={...sttEngines[i]},modelInput=document.querySelector(`.bench-engine-model[data-index="${i}"]`);return item.model=(modelInput==null?void 0:modelInput.value.trim())||item.model||"whisper-1",item}).filter(Boolean)}async function runSttBenchmark(){var _a2,_b2,_c2,_d2,_e2,_f2,_g2,_h2;const audio=(_b2=(_a2=q("bench-stt-audio"))==null?void 0:_a2.files)==null?void 0:_b2[0],sourceId=((_c2=q("bench-stt-source-id"))==null?void 0:_c2.value)||"",refFile=(_e2=(_d2=q("bench-stt-ref-file"))==null?void 0:_d2.files)==null?void 0:_e2[0],refText=((_f2=q("bench-stt-ref-text"))==null?void 0:_f2.value)||"",engines=selectedSttEngines();if(!audio&&!sourceId){say("Choose an audio file or load a voice library sample first","error");return}if(!refFile&&!refText.trim()){say("Add a reference .txt or paste reference text","error");return}if(!engines.length){say("Select at least one STT engine","error");return}const btn=q("bench-stt-run"),st=q("bench-stt-status"),tbody=(_g2=q("bench-stt-table"))==null?void 0:_g2.querySelector("tbody");btn&&(btn.disabled=!0),st&&(st.textContent=`Running ${engines.length} STT benchmark${engines.length===1?"":"s"}...`),tbody&&(tbody.innerHTML=`Benchmarking ${engines.length} engines...`);try{const fd=new FormData;audio&&fd.append("audio",audio,audio.name),sourceId&&fd.append("source_id",sourceId),refFile&&fd.append("reference_file",refFile,refFile.name),fd.append("reference_text",refText),fd.append("engines_json",JSON.stringify(engines));const d=await fetchJson("/api/stt-benchmark",{method:"POST",body:fd});renderSttResults(d),st&&(st.textContent=`Finished ${((_h2=d.results)==null?void 0:_h2.length)||0} STT engines.`),say("STT benchmark complete","success")}catch(e){st&&(st.textContent="STT benchmark failed"),tbody&&(tbody.innerHTML=`${esc(e.message)}`),say("STT benchmark failed: "+e.message,"error")}finally{btn&&(btn.disabled=!1)}}function renderSttResults(d){var _a2;const tbody=(_a2=q("bench-stt-table"))==null?void 0:_a2.querySelector("tbody"),summary=q("bench-stt-summary"),rows=d.results||[];summary&&(summary.innerHTML=[["Engines",String(rows.length)],["Reference words",String(d.reference_words||0)],["Best time",d.best_time_sec==null?"\u2014":fmtSec(d.best_time_sec)],["Best accuracy",d.best_accuracy==null?"\u2014":d.best_accuracy.toFixed(1)+"%"]].map(([label,val])=>`${esc(val)} ${esc(label)}`).join("")),tbody&&(tbody.innerHTML=rows.map(r=>{const acc=r.accuracy==null?"\u2014":`${Number(r.accuracy).toFixed(1)}%`,out=r.ok?r.output||"":`\u26A0 ${r.error||"failed"}`;return` ${esc(r.engine)}${esc(r.url||"")} ${esc(r.model||"")} ${esc(r.device||"Unknown")} ${fmtSec(r.time_sec)} ${esc(acc)} ${esc(out)} `}).join(""))}async function runTtsBenchmark(){var _a2,_b2,_c2,_d2;const text=((_a2=q("bench-tts-text"))==null?void 0:_a2.value.trim())||"";if(!text){say("Enter a sample sentence first","error");return}const activeOnly=(((_b2=q("bench-tts-scope"))==null?void 0:_b2.value)||"active")==="active",btn=q("bench-tts-run"),st=q("bench-tts-status"),tbody=(_c2=q("bench-tts-table"))==null?void 0:_c2.querySelector("tbody");btn&&(btn.disabled=!0),st&&(st.textContent="Running TTS benchmark..."),tbody&&(tbody.innerHTML='Benchmarking voices...');try{const d=await fetchJson("/api/voices/benchmark",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text,active_only:activeOnly})});renderTtsResults(d),st&&(st.textContent=`Finished ${d.benchmarked||0} voices.`),say("TTS benchmark complete",(_d2=d.errors)!=null&&_d2.length?"error":"success")}catch(e){st&&(st.textContent="TTS benchmark failed"),tbody&&(tbody.innerHTML=`${esc(e.message)}`),say("TTS benchmark failed: "+e.message,"error")}finally{btn&&(btn.disabled=!1)}}function renderTtsResults(d){var _a2;const tbody=(_a2=q("bench-tts-table"))==null?void 0:_a2.querySelector("tbody"),summary=q("bench-tts-summary"),rows=d.voices||[],okRows=rows.map(r=>r.benchmark).filter(b=>b&&b.ok),avg=key=>{const vals=okRows.map(b=>Number(b[key])).filter(Number.isFinite);return vals.length?vals.reduce((a,b)=>a+b,0)/vals.length:null};summary&&(summary.innerHTML=[["Voices",String(rows.length)],["OK",String(okRows.length)],["Avg TTFA",avg("ttfa_ms")==null?"\u2014":Math.round(avg("ttfa_ms"))+"ms"],["Avg total",avg("elapsed_sec")==null?"\u2014":fmtSec(avg("elapsed_sec"))]].map(([label,val])=>`${esc(val)} ${esc(label)}`).join("")),tbody&&(tbody.innerHTML=rows.length?rows.map(row=>{const b=row.benchmark||{},status2=b.ok?b.realtime_ok===!1?"Slow":"OK":b.error||"Failed";return` ${esc(row.voice_id)} ${b.ttfa_ms==null?"\u2014":Math.round(b.ttfa_ms)+"ms"} ${fmtSec(b.elapsed_sec)} ${fmtSec(b.audio_sec)} ${b.rtf==null?"\u2014":Number(b.rtf).toFixed(2)} ${b.speed==null?"\u2014":Number(b.speed).toFixed(2)+"x"} ${esc(status2)} `}).join(""):'No voices benchmarked.')}async function initTurnControls(){try{const settings=await fetchJson("/api/settings");q("bench-turn-llm-url")&&(q("bench-turn-llm-url").value=settings.conv_llm_url||settings.llm_url||"")}catch{}try{const d=await fetchJson("/api/stt-backends"),sel=q("bench-turn-stt");sel&&(sel.innerHTML=(d.backends||[]).map(b=>``).join("")||'')}catch{}try{typeof refreshTtsBackendAvailability=="function"&&await refreshTtsBackendAvailability();const sel=q("bench-turn-tts-backend"),backends=(window._ttsBackends||(typeof _ttsBackends!="undefined"?_ttsBackends:[])||[]).filter(Boolean);sel&&(sel.innerHTML=backends.map(b=>``).join("")||'')}catch{}}async function fetchTurnModels(){var _a2;const btn=q("bench-turn-fetch-llm"),sel=q("bench-turn-llm-model"),url=((_a2=q("bench-turn-llm-url"))==null?void 0:_a2.value.trim())||"";btn&&(btn.disabled=!0);try{const models=(await fetchJson("/api/conversation/llm-models"+(url?"?url="+encodeURIComponent(url):""))).models||[];sel&&(sel.innerHTML=models.length?models.map(m=>``).join(""):'')}catch(e){sel&&(sel.innerHTML=''),say("Model fetch failed: "+e.message,"error")}finally{btn&&(btn.disabled=!1)}}async function fetchTurnVoices(){var _a2;const btn=q("bench-turn-fetch-voices"),sel=q("bench-turn-voice"),backend=((_a2=q("bench-turn-tts-backend"))==null?void 0:_a2.value)||"voice_clone",picker=window.BenchmarkVoicePicker;btn&&(btn.disabled=!0);try{const raw=await fetchJson("/api/tts-voices?backend="+encodeURIComponent(backend)),items=(Array.isArray(raw)?raw:[]).map(v=>{const id=typeof backendVoiceId=="function"?backendVoiceId(v):typeof v=="string"?v:v.id||v.voice||v.name;return id?{id,label:id,meta:(window._voices||[]).find(x=>x&&x.id===id)||(typeof v=="object"?v:null)}:null}).filter(Boolean);picker?picker.populate("bench-turn-voice",items,{placeholder:"Fetch voices",empty:"No voices"}):sel&&(sel.innerHTML=items.length?items.map(v=>``).join(""):'')}catch(e){picker?picker.populate("bench-turn-voice",[],{placeholder:"Fetch failed",empty:"Fetch failed"}):sel&&(sel.innerHTML=''),say("Voice fetch failed: "+e.message,"error")}finally{btn&&(btn.disabled=!1)}}function updateTurnStats(stats){const max=stats.total_ms||1;[["stt",stats.stt_ms],["ttft",stats.llm_ttft_ms],["llm",stats.llm_total_ms],["tts",stats.tts_ms],["total",stats.total_ms]].forEach(([key,ms])=>{const val=q("bench-turn-val-"+key),fill=q("bench-turn-fill-"+key);val&&(val.textContent=fmtMs(ms)),fill&&(fill.style.width=max>0?Math.min(100,(ms||0)/max*100)+"%":"0%")})}function addTurnLog(role,text){var _a2;const log=q("bench-turn-log");if(!log)return null;(_a2=log.querySelector(".conv-chat-welcome"))==null||_a2.remove();const wrap=document.createElement("div");wrap.className=`conv-bubble-wrap conv-bubble-wrap--${role}`;const bubble=document.createElement("div");return bubble.className=`conv-bubble conv-bubble--${role}`,bubble.textContent=text||"",wrap.appendChild(bubble),log.appendChild(wrap),log.scrollTop=log.scrollHeight,bubble}function addTurnHistory(total,ok){var _a2;const hist=q("bench-turn-history");if(!hist)return;(_a2=hist.querySelector(".conv-history-empty"))==null||_a2.remove(),turnHistoryCount++;const item=document.createElement("div");item.className="conv-hist-item",item.innerHTML=`#${turnHistoryCount}${fmtMs(total)}`,hist.prepend(item)}async function runTurnBenchmark(){var _a2,_b2,_c2,_d2,_e2,_f2,_g2,_h2,_i2,_j2,_k2;const audio=(_b2=(_a2=q("bench-turn-audio"))==null?void 0:_a2.files)==null?void 0:_b2[0],text=((_c2=q("bench-turn-text"))==null?void 0:_c2.value.trim())||"";if(!audio&&!text){say("Choose turn audio or enter fallback text","error");return}const btn=q("bench-turn-run"),st=q("bench-turn-status");btn&&(btn.disabled=!0),st&&(st.textContent="Running conversation turn...");const t0=Date.now(),userBubble=addTurnLog("user",text||"Transcribing audio..."),assistantBubble=addTurnLog("assistant","...");let assistantText="",lastStats=null;try{const fd=new FormData;audio&&fd.append("audio",audio,audio.name),text&&fd.append("text",text),fd.append("stt_backend",((_d2=q("bench-turn-stt"))==null?void 0:_d2.value)||"configured"),fd.append("llm_url",((_e2=q("bench-turn-llm-url"))==null?void 0:_e2.value.trim())||""),fd.append("llm_model",((_f2=q("bench-turn-llm-model"))==null?void 0:_f2.value)||""),fd.append("tts_backend",((_g2=q("bench-turn-tts-backend"))==null?void 0:_g2.value)||"voice_clone"),fd.append("tts_voice",((_h2=q("bench-turn-voice"))==null?void 0:_h2.value)||""),fd.append("system_prompt",((_i2=q("bench-turn-system"))==null?void 0:_i2.value.trim())||"You are a helpful voice assistant."),fd.append("history","[]");const resp=await fetch("/api/conversation/turn",{method:"POST",body:fd});if(!resp.ok)throw new Error("Server error "+resp.status);const reader=resp.body.getReader(),dec=new TextDecoder;let buf="";for(;;){const{done,value}=await reader.read();if(done)break;buf+=dec.decode(value,{stream:!0});const lines=buf.split(` `);buf=lines.pop();for(const line of lines){if(!line.startsWith("data:"))continue;let evt;try{evt=JSON.parse(line.slice(5).trim())}catch{continue}if(evt.type==="transcript"&&userBubble&&(userBubble.textContent=evt.text||"(empty)"),evt.type==="token"&&(assistantText+=evt.delta||"",assistantBubble&&(assistantBubble.textContent=assistantText)),evt.type==="llm_done"&&(assistantText=evt.text||assistantText,assistantBubble&&(assistantBubble.textContent=assistantText)),evt.type==="audio"&&evt.b64){const bytes=Uint8Array.from(atob(evt.b64),c=>c.charCodeAt(0)),url=URL.createObjectURL(new Blob([bytes],{type:evt.mime||"audio/wav"})),audioEl=document.createElement("audio");audioEl.controls=!0,audioEl.src=url,audioEl.addEventListener("ended",()=>URL.revokeObjectURL(url),{once:!0}),(_j2=q("bench-turn-log"))==null||_j2.appendChild(audioEl)}if(evt.type==="stats"&&(lastStats=evt,updateTurnStats(evt)),evt.type==="error")throw new Error(`[${evt.stage||"turn"}] ${evt.message||"Unknown error"}`)}}const total=(_k2=lastStats==null?void 0:lastStats.total_ms)!=null?_k2:Date.now()-t0;addTurnHistory(total,!0),st&&(st.textContent=`Finished in ${fmtMs(total)}.`),say("Turn benchmark complete","success")}catch(e){assistantBubble&&(assistantBubble.textContent=e.message),addTurnHistory(Date.now()-t0,!1),st&&(st.textContent="Turn benchmark failed"),say("Turn benchmark failed: "+e.message,"error")}finally{btn&&(btn.disabled=!1)}}function bindBenchmarkSection(){var _a2,_b2,_c2,_d2,_e2,_f2,_g2,_h2,_i2;initialized||!q("bench-stt-run")||(initialized=!0,document.querySelectorAll(".bench-tab").forEach(btn=>btn.addEventListener("click",()=>setBenchTab(btn.dataset.benchTab))),window.BenchmarkVoicePicker&&(BenchmarkVoicePicker.upgrade("bench-stt-library-voice",{placeholder:"-- choose from voice library --",empty:"No library voices with reference transcripts found"}),BenchmarkVoicePicker.upgrade("perf-voice-select",{placeholder:"-- select after fetch --",empty:"No voices"}),BenchmarkVoicePicker.upgrade("bench-turn-voice",{placeholder:"Fetch voices",empty:"No voices"})),document.addEventListener("click",()=>closeBenchmarkModelPickers()),(_a2=q("bench-stt-refresh"))==null||_a2.addEventListener("click",loadBenchmarkSttEngines),(_b2=q("bench-stt-load-voice"))==null||_b2.addEventListener("click",useBenchmarkLibraryVoice),(_c2=q("bench-stt-audio"))==null||_c2.addEventListener("change",()=>{q("bench-stt-source-id")&&(q("bench-stt-source-id").value="")}),(_d2=q("bench-stt-run"))==null||_d2.addEventListener("click",runSttBenchmark),(_e2=q("bench-tts-run"))==null||_e2.addEventListener("click",runTtsBenchmark),(_f2=q("bench-turn-fetch-llm"))==null||_f2.addEventListener("click",fetchTurnModels),(_g2=q("bench-turn-fetch-voices"))==null||_g2.addEventListener("click",fetchTurnVoices),(_h2=q("bench-turn-run"))==null||_h2.addEventListener("click",runTurnBenchmark),(_i2=q("bench-turn-tts-backend"))==null||_i2.addEventListener("change",()=>{window.BenchmarkVoicePicker?BenchmarkVoicePicker.populate("bench-turn-voice",[],{placeholder:"Fetch voices",empty:"No voices"}):q("bench-turn-voice")&&(q("bench-turn-voice").innerHTML='')}))}function loadBenchmarkSectionData(){bindBenchmarkSection(),!(benchmarkDataLoaded||!q("bench-stt-run"))&&(benchmarkDataLoaded=!0,loadBenchmarkSttEngines(),loadBenchmarkVoiceLibrary(),initTurnControls())}window.loadBenchmarkSectionData=loadBenchmarkSectionData,bindBenchmarkSection(),!initialized&&document.body&&new MutationObserver(()=>bindBenchmarkSection()).observe(document.body,{childList:!0,subtree:!0})}();let sttTtsSourceId=null,sttTtsOutputBlob=null,_sttBackends=[],sttTtsRecorder=null,sttTtsRecordStream=null,sttTtsRecordChunks=[],sttTtsRecordTimer=null,sttTtsRecordSecs=0;function sttTtsSelectedSttBackend(){var _a2;return((_a2=$("stt-tts-stt-backend"))==null?void 0:_a2.value)||"configured"}function sttBackendOptionHtml(selected="configured"){var _a2;if(!_sttBackends.length)return'';const preferred=_sttBackends.some(b=>b.id===selected&&b.available)?selected:((_a2=_sttBackends.find(b=>b.available))==null?void 0:_a2.id)||selected;return _sttBackends.map(b=>{const suffix=b.available?"":" (unavailable)",disabled=b.available?"":" disabled";return``}).join("")}function updateSttBackendHelp(){const selected=sttTtsSelectedSttBackend(),b=_sttBackends.find(item=>item.id===selected)||_sttBackends.find(item=>item.available)||null,help=$("stt-tts-stt-help");if(help){if(!b){help.textContent="No STT engine status loaded yet.";return}help.innerHTML=sttBackendHelpHtml(b)}}async function refreshSttBackends(selected=""){try{_sttBackends=((await fetch("/api/stt-backends").then(r=>r.json())).backends||[]).filter(b=>b&&b.id)}catch{_sttBackends=[]}["stt-tts-stt-backend","clone-stt-backend"].forEach(id=>{const sel=$(id);if(!sel)return;const prev=selected||sel.value||"configured";sel.innerHTML=sttBackendOptionHtml(prev),sel.disabled=!_sttBackends.some(b=>b.available)}),updateSttBackendHelp(),typeof window.updateStatusBar=="function"&&window.updateStatusBar(),typeof window.refreshStatusBarEngines=="function"&&window.refreshStatusBarEngines()}function sttTtsSelectedBackend(){var _a2;return((_a2=$("stt-tts-backend-select"))==null?void 0:_a2.value)||""}function sttTtsDownload(blob,name){if(!blob)return;const a=document.createElement("a");a.href=URL.createObjectURL(blob),a.download=name,a.click()}async function sttTtsUploadFile(file){if(!file)return;$("stt-tts-source-status").textContent="Uploading "+file.name+"...",sttTtsSourceId=null,sttTtsOutputBlob=null,$("stt-tts-transcribe-btn").disabled=!0,$("stt-tts-copy-preview-btn").disabled=!0,$("stt-tts-save-mp3-btn").disabled=!0,$("stt-tts-save-wav-btn").disabled=!0;const fd=new FormData;fd.append("file",file);try{const r=await fetch("/api/upload",{method:"POST",body:fd});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();sttTtsSourceId=d.id;const audio=$("stt-tts-source-audio");audio.src="/api/audio/"+encodeURIComponent(d.id),audio.style.display="",$("stt-tts-source-status").textContent=`${d.filename||file.name} loaded (${Number(d.duration||0).toFixed(1)} s).`,$("stt-tts-transcribe-btn").disabled=!1,toast("Speech audio loaded","success")}catch(e){$("stt-tts-source-status").textContent="Upload failed.",toast("STT source upload failed: "+e.message,"error")}}async function sttTtsFetchVoices(){const backend=sttTtsSelectedBackend();if(!backend)throw new Error("No available TTS backend");const rawVoices=await fetch("/api/tts-voices?backend="+encodeURIComponent(backend)).then(r=>r.json());let voices=Array.isArray(rawVoices)?rawVoices:[];if(shouldFilterBackendVoices(backend)){const activeIds=await activeLibraryVoiceIds();voices=voices.filter(v=>activeIds.has(backendVoiceId(v)))}const sel=$("stt-tts-voice-select"),prev=sel.value;return sel.innerHTML='',voices.forEach(v=>{const id=backendVoiceId(v),opt=document.createElement("option");opt.value=opt.textContent=id,sel.appendChild(opt)}),prev&&voices.some(v=>backendVoiceId(v)===prev)&&(sel.value=prev),voices.length}(_aa=$("stt-tts-file"))==null||_aa.addEventListener("change",async()=>{const input=$("stt-tts-file");input.files&&input.files.length&&await sttTtsUploadFile(input.files[0]),input.value=""}),(_ba=$("stt-tts-refresh-stt-btn"))==null||_ba.addEventListener("click",async()=>{const btn=$("stt-tts-refresh-stt-btn");btn.disabled=!0;try{await refreshSttBackends(sttTtsSelectedSttBackend()),toast("STT engines refreshed","success")}finally{btn.disabled=!1}}),(_ca=$("stt-tts-stt-backend"))==null||_ca.addEventListener("change",updateSttBackendHelp);function sttTtsSetRecording(on){$("stt-tts-rec-start").disabled=on,$("stt-tts-rec-stop").disabled=!on}function sttTtsStopTracks(){sttTtsRecordStream&&sttTtsRecordStream.getTracks().forEach(t=>t.stop()),sttTtsRecordStream=null}(_da=$("stt-tts-rec-start"))==null||_da.addEventListener("click",async()=>{try{sttTtsRecordStream=await requestMicrophoneStream(),sttTtsRecordChunks=[],sttTtsRecordSecs=0,$("stt-tts-rec-time").textContent="0:00",$("stt-tts-source-status").textContent="Recording...",sttTtsSetRecording(!0),sttTtsRecordTimer=setInterval(()=>{sttTtsRecordSecs++,$("stt-tts-rec-time").textContent=Math.floor(sttTtsRecordSecs/60)+":"+String(sttTtsRecordSecs%60).padStart(2,"0")},1e3),sttTtsRecorder=new MediaRecorder(sttTtsRecordStream,{audioBitsPerSecond:256e3}),sttTtsRecorder.ondataavailable=e=>{e.data.size&&sttTtsRecordChunks.push(e.data)},sttTtsRecorder.onstop=async()=>{clearInterval(sttTtsRecordTimer),sttTtsRecordTimer=null,sttTtsSetRecording(!1),sttTtsStopTracks();const mime=sttTtsRecorder.mimeType||"audio/webm",blob=new Blob(sttTtsRecordChunks,{type:mime}),ext=mime.includes("ogg")?".ogg":".webm";if(!blob.size){$("stt-tts-source-status").textContent="Recording was empty.",toast("Recording was empty","error");return}await sttTtsUploadFile(new File([blob],"stt-recording"+ext,{type:mime}))},sttTtsRecorder.start(100),toast("Recording started","success")}catch(e){sttTtsSetRecording(!1),sttTtsStopTracks();const message=await microphoneErrorMessage(e);$("stt-tts-source-status").textContent=message,toast(message,"error")}}),(_ea=$("stt-tts-rec-stop"))==null||_ea.addEventListener("click",()=>{sttTtsRecorder&&sttTtsRecorder.state!=="inactive"&&sttTtsRecorder.stop()}),(_fa=$("stt-tts-transcribe-btn"))==null||_fa.addEventListener("click",async()=>{if(!sttTtsSourceId){toast("Load speech audio first","error");return}const btn=$("stt-tts-transcribe-btn");btn.disabled=!0,$("stt-tts-source-status").textContent="Transcribing...";try{const r=await fetch("/api/transcribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:sttTtsSourceId,backend:sttTtsSelectedSttBackend()})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json();$("stt-tts-text").value=d.text||"",$("stt-tts-copy-preview-btn").disabled=!(d.text||"").trim();const used=d.backend?" via "+d.backend:"";$("stt-tts-source-status").textContent="Transcription ready"+used+".",typeof updateRefineButtonState=="function"&&updateRefineButtonState(),toast("Transcription ready","success")}catch(e){$("stt-tts-source-status").textContent="Transcription failed.",toast("STT failed: "+e.message,"error")}finally{btn.disabled=!1}}),(_ga=$("stt-tts-copy-preview-btn"))==null||_ga.addEventListener("click",()=>{const text=$("stt-tts-text").value.trim();text&&($("preview-text-area").value=text,switchTab("generation"),toast("Copied transcription to TTS Generation","success"))}),(_ha=$("stt-tts-backend-select"))==null||_ha.addEventListener("change",()=>{$("stt-tts-voice-select").innerHTML='',sttTtsOutputBlob=null,$("stt-tts-save-mp3-btn").disabled=!0,$("stt-tts-save-wav-btn").disabled=!0,updateBackendHelp()}),(_ia=$("stt-tts-fetch-voices-btn"))==null||_ia.addEventListener("click",async()=>{const btn=$("stt-tts-fetch-voices-btn");btn.disabled=!0;try{const count=await sttTtsFetchVoices();toast("Fetched "+count+" voices","success")}catch(e){toast("Fetch failed: "+e.message,"error")}finally{btn.disabled=!1}}),(_ja=$("stt-tts-generate-btn"))==null||_ja.addEventListener("click",async()=>{const backend=sttTtsSelectedBackend(),voice=$("stt-tts-voice-select").value,text=$("stt-tts-text").value.trim(),instruct=$("stt-tts-style-instruction").value.trim();if(!backend){toast("No available TTS backend","error");return}if(!voice){toast("Select a TTS voice","error");return}if(!text){toast("Transcribe or enter text first","error");return}const btn=$("stt-tts-generate-btn");btn.disabled=!0,$("stt-tts-save-mp3-btn").disabled=!0,$("stt-tts-save-wav-btn").disabled=!0;try{const source=await createTtsAudioSource(voice,text,backend,$("stt-tts-playback-mode").value,instruct);sttTtsOutputBlob=source.blob;const audio=$("stt-tts-output-audio");audio.src=source.url,audio.style.display="",await audio.play(),$("stt-tts-save-mp3-btn").disabled=!1,$("stt-tts-save-wav-btn").disabled=source.streaming,toast(source.streaming?"Streaming synthesized speech":"Synthesized speech ready","success")}catch(e){toast("TTS failed: "+e.message,"error")}finally{btn.disabled=!1}}),(_ka=$("stt-tts-save-mp3-btn"))==null||_ka.addEventListener("click",async()=>{const backend=sttTtsSelectedBackend(),voice=$("stt-tts-voice-select").value,text=$("stt-tts-text").value.trim(),instruct=$("stt-tts-style-instruction").value.trim();if(!backend||!voice||!text)return;const btn=$("stt-tts-save-mp3-btn");btn.disabled=!0;try{const blob=await fetchTtsPreviewBlob(voice,text,"mp3",instruct,backend);sttTtsDownload(blob,(voice||"stt_tts")+"_stt_tts.mp3"),toast("MP3 saved","success")}catch(e){toast("MP3 save failed: "+e.message,"error")}finally{btn.disabled=!1}}),(_la=$("stt-tts-save-wav-btn"))==null||_la.addEventListener("click",()=>{sttTtsOutputBlob&&sttTtsDownload(sttTtsOutputBlob,($("stt-tts-voice-select").value||"stt_tts")+"_stt_tts.wav")});function parseScript(text){const rawLines=text.split(` `),result=[];let state="action",currentSpeaker=null,dialogBuffer=[],actionBuffer=[];const FADE_IN_RE=/^FADE\s+IN[\s:.\-]*$/i,FIRST_SCENE_RE=/^(?:[A-Z]{0,3}\d{1,4}[A-Z]?\s+)?(INT\.|EXT\.|INT\.\/EXT\.|EXT\.\/INT\.|I\/E\.)/i;let scanLines=rawLines;const firstIdx=rawLines.findIndex(l=>{const s=l.trim();return FADE_IN_RE.test(s)||FIRST_SCENE_RE.test(s)});firstIdx>0&&(scanLines=rawLines.slice(firstIdx));function flushDialog(){if(currentSpeaker&&dialogBuffer.length){const t=dialogBuffer.join(" ").trim();t&&result.push({type:"dialog",speaker:currentSpeaker,text:t,isDirection:!1,emotion:""})}dialogBuffer=[]}function flushAction(){if(actionBuffer.length){const t=actionBuffer.join(" ").trim();t&&result.push({type:"action",speaker:"",text:t,isDirection:!0}),actionBuffer=[]}}for(const rawLine of scanLines){const line=rawLine.trim();if(!line){flushDialog(),flushAction(),state==="dialog"&&(state="action",currentSpeaker=null);continue}if(/\d{1,2}\/\d{1,2}\/\d{2,4}/.test(line)&&line.length<=60||/^[A-Z]{0,3}\d{1,4}[A-Z]?$/.test(line)||/^\(?CONTINUED\)?:?$/i.test(line)||/^[A-Z]{0,3}\d{1,4}[A-Z]?\s+CONTINUED:?(\s*[A-Z]{0,3}\d{1,4}[A-Z]?)?$/i.test(line)||/^(?:[A-Z]{0,3}\d{1,4}[A-Z]?\s+)?OMITTED(?:\s*[A-Z]{0,3}\d{1,4}[A-Z]?)?$/i.test(line)){flushDialog(),flushAction(),state==="dialog"&&(state="action",currentSpeaker=null);continue}if(line.startsWith("\f")){flushDialog(),flushAction();const pnStr=line.slice(1).trim(),pageNum=pnStr&&/^\d+$/.test(pnStr)?parseInt(pnStr,10):null;result.push({type:"pagebreak",speaker:"",text:"",page:pageNum,isDirection:!0}),state="action",currentSpeaker=null;continue}if(line.startsWith("#")){flushDialog(),flushAction();const dir=line.slice(1).trim();dir&&result.push({type:"direction",speaker:"",text:dir,isDirection:!0}),state="action",currentSpeaker=null;continue}if(/^\[.*\]$/.test(line)){state==="dialog"&&(flushDialog(),state="character"),result.push({type:"direction",speaker:currentSpeaker||"",text:line.slice(1,-1),isDirection:!0});continue}{const m=line.match(/^(?:([A-Z]{0,3}\d{1,4}[A-Z]?)\s+)?((?:INT\.\/EXT\.|EXT\.\/INT\.|I\/E\.|INT\.|EXT\.).*)$/i);if(m){flushDialog(),flushAction();let head=m[2].trim();m[1]&&head.toUpperCase().endsWith(m[1].toUpperCase())&&(head=head.slice(0,head.length-m[1].length).trim()),result.push({type:"scene",speaker:"",text:head.toUpperCase(),isDirection:!0}),state="action",currentSpeaker=null;continue}}if(/^ACT\s+(I{1,4}|V?I{0,3}|[1-9][0-9]?|ONE|TWO|THREE|FOUR|FIVE|SIX|SEVEN|EIGHT|NINE|TEN)(\b.*)?$/i.test(line)){flushDialog(),flushAction(),result.push({type:"act",speaker:"",text:line.toUpperCase(),isDirection:!0}),state="action",currentSpeaker=null;continue}if(/^SCENE\s+(I{1,4}|V?I{0,3}|[1-9][0-9]?|ONE|TWO|THREE|FOUR|FIVE|SIX|SEVEN|EIGHT|NINE|TEN)(\b.*)?$/i.test(line)){flushDialog(),flushAction(),result.push({type:"scene",speaker:"",text:line.toUpperCase(),isDirection:!0}),state="action",currentSpeaker=null;continue}if(/^(FADE\s+(IN|OUT|TO)|CUT\s+TO|SMASH\s+CUT|MATCH\s+CUT|DISSOLVE\s+TO|BLACKOUT|LIGHTS\s+(UP|DOWN|FADE)|CURTAIN|INTERMISSION|END\s+OF\s+(PLAY|ACT))[.:]?\s*$/i.test(line)){flushDialog(),flushAction(),result.push({type:"transition",speaker:"",text:line,isDirection:!0}),state="action",currentSpeaker=null;continue}const colonMatch=line.match(/^([A-Z][A-Z0-9 _\-]{0,39}):\s+(.+)$/);if(colonMatch&&colonMatch[1].trim().length<=24&&colonMatch[1].trim().split(/\s+/).length<=3){flushDialog(),flushAction(),currentSpeaker=colonMatch[1].trim(),dialogBuffer=[colonMatch[2].trim()],state="dialog";continue}if(/^\(.*\)$/.test(line)){state==="dialog"&&(flushDialog(),state="character"),result.push({type:"direction",speaker:currentSpeaker||"",text:line,isDirection:!0});continue}const nameRaw=line.replace(/\s*\([^)]*\)\s*$/,"").trim();if(nameRaw.length>=2&&nameRaw.length<=42&&nameRaw===nameRaw.toUpperCase()&&/^[A-Z][A-Z0-9 '.\-]+$/.test(nameRaw)&&!/^\d+$/.test(nameRaw)&&!/\.$/.test(nameRaw)){flushDialog(),flushAction(),currentSpeaker=nameRaw,state="character";continue}if(state==="character"){dialogBuffer=[line],state="dialog";continue}if(state==="dialog"){dialogBuffer.push(line);continue}actionBuffer.push(line),state="action"}return flushDialog(),flushAction(),result}function detectCharacters(lines){const speakers=[...new Set(lines.filter(l=>l.type==="dialog").map(l=>l.speaker))],cast={};return speakers.forEach((sp,i)=>{cast[sp]={voice:"",color:SPEAKER_COLORS[i%SPEAKER_COLORS.length],instruct:"",voiceData:null}}),cast}const SPEAKER_COLORS=["#89b4fa","#a6e3a1","#f38ba8","#fab387","#f9e2af","#cba6f7","#89dceb","#74c7ec"],REH_EMOTIONS=[{value:"",emoji:"\u{1F610}",label:"Neutral"},{value:"happy, cheerful and upbeat",emoji:"\u{1F60A}",label:"Happy"},{value:"sad, melancholy, somber",emoji:"\u{1F622}",label:"Sad"},{value:"angry, forceful, aggressive",emoji:"\u{1F620}",label:"Angry"},{value:"whisper, hushed and intimate",emoji:"\u{1F92B}",label:"Whisper"},{value:"excited, enthusiastic, energetic",emoji:"\u{1F929}",label:"Excited"},{value:"scared, nervous, trembling voice",emoji:"\u{1F628}",label:"Scared"},{value:"sarcastic, dry, ironic delivery",emoji:"\u{1F60F}",label:"Sarcastic"},{value:"dramatic, theatrical, intense",emoji:"\u{1F3AD}",label:"Dramatic"},{value:"gentle, warm, tender",emoji:"\u{1F970}",label:"Gentle"},{value:"confused, uncertain, hesitant",emoji:"\u{1F615}",label:"Confused"},{value:"bored, flat, disinterested",emoji:"\u{1F611}",label:"Bored"},{value:"surprised, shocked, astonished",emoji:"\u{1F632}",label:"Surprised"},{value:"confident, authoritative, bold",emoji:"\u{1F4AA}",label:"Confident"},{value:"mysterious, dark, ominous",emoji:"\u{1F311}",label:"Mysterious"},{value:"romantic, loving, passionate",emoji:"\u2764\uFE0F",label:"Romantic"},{value:"playful, teasing, mischievous",emoji:"\u{1F608}",label:"Playful"},{value:"calm, composed, measured",emoji:"\u{1F9D8}",label:"Calm"},{value:"commanding, authoritative, military",emoji:"\u2694\uFE0F",label:"Commanding"},{value:"grieving, tearful, broken",emoji:"\u{1F62D}",label:"Grieving"}];let rehCustomEmotions=[];try{rehCustomEmotions=JSON.parse(localStorage.getItem("reh-custom-emotions")||"[]")}catch{}function getEmotionInfo(value){if(!value)return{emoji:"",label:"Pick tone"};const found=[...REH_EMOTIONS,...rehCustomEmotions].find(e=>e.value===value);return found?{emoji:found.emoji,label:found.label}:{emoji:"\u2728",label:value.length>14?value.slice(0,13)+"\u2026":value}}function renderMarkdownInline(text){let s=escHtml(text);return s=s.replace(/\*\*([^*\n]+?)\*\*/g,"$1"),s=s.replace(/\*([^*\n]+?)\*/g,"$1"),s=s.replace(/__([^_\n]+?)__/g,"$1"),s=s.replace(/~~([^~\n]+?)~~/g,"$1"),s=s.replace(/==([^=\n]+?)==/g,'$1'),s}function stripMarkdown(text){return text.replace(/\*\*([^*\n]+?)\*\*/g,"$1").replace(/\*([^*\n]+?)\*/g,"$1").replace(/__([^_\n]+?)__/g,"$1").replace(/~~([^~\n]+?)~~/g,"$1").replace(/==([^=\n]+?)==/g,"$1")}const rehState={lines:[],cast:{},lineIndex:0,clips:[],voices:[],backend:"",playing:!1,repeat:!1,savedId:null,synthCache:new Map,staleLines:new Set,synthCancelled:!1,synthRunning:!1,skipDescriptions:!0,narratorVoice:"",practiceStart:null,practiceEnd:null,bulkMode:!1,bulkSel:new Set,bulkAnchor:null,showHidden:!1,recStream:null,recAudioCtx:null,recAnalyser:null,recSourceNode:null,recGainNode:null,recDestStream:null,recMeterRaf:null,recWaveRing:null,mediaRec:null,recChunks:[],recTimer:null,recSecs:0,lastRecBlob:null};window.rehState=rehState;async function rehDbGetAll(){const r=await fetch("/api/rehearsals");if(!r.ok)throw new Error("rehDbGetAll failed: "+r.status);return(await r.json()).rehearsals||[]}async function rehDbGetById(id){const r=await fetch("/api/rehearsals/"+id);if(r.status!==404){if(!r.ok)throw new Error("rehDbGetById failed: "+r.status);return r.json()}}async function rehDbAdd(record){const r=await fetch("/api/rehearsals",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(_rehSanitize(record))});if(!r.ok)throw new Error("rehDbAdd failed: "+r.status);return(await r.json()).id}async function rehDbPut(record){if(!record.id)return rehDbAdd(record);const r=await fetch("/api/rehearsals/"+record.id,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(_rehSanitize(record))});if(!r.ok)throw new Error("rehDbPut failed: "+r.status)}async function rehDbDelete(id){const r=await fetch("/api/rehearsals/"+id,{method:"DELETE"});if(!r.ok)throw new Error("rehDbDelete failed: "+r.status)}function _rehSanitize(rec){const out={...rec};return out.clips&&(out.clips=(out.clips||[]).map(c=>({lineIndex:c.lineIndex,speaker:c.speaker,type:c.type}))),out}(async function(){try{if((await rehDbGetAll()).length>0)return;const idbRecs=await _rehIdbGetAll().catch(()=>[]);if(!idbRecs.length)return;const r=await fetch("/api/rehearsals/migrate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(idbRecs.map(_rehSanitize))});if(r.ok){const d=await r.json();console.log(`[rehearser] migrated ${d.imported} records from IndexedDB \u2192 SQLite`)}}catch(e){console.warn("[rehearser] migration skipped:",e)}})();function _rehIdbGetAll(){return new Promise(resolve=>{const req=indexedDB.open("reh-library",1);req.onerror=()=>resolve([]),req.onsuccess=e=>{const db=e.target.result;if(!db.objectStoreNames.contains("rehearsals")){db.close(),resolve([]);return}const all=db.transaction("rehearsals","readonly").objectStore("rehearsals").getAll();all.onsuccess=ev=>{db.close(),resolve(ev.target.result||[])},all.onerror=()=>{db.close(),resolve([])}}})}window.rehDbGetById=rehDbGetById,window.rehLoadRecord=loadRecord;async function clipsToJson(clips){return Promise.all(clips.map(async c=>{if(!c.blob)return{lineIndex:c.lineIndex,speaker:c.speaker,type:c.type};const ab=await c.blob.arrayBuffer(),u8=new Uint8Array(ab);let bin="";const CHUNK=8192;for(let i=0;i{if(!c.b64)return c;const bin=atob(c.b64),u8=new Uint8Array(bin.length);for(let i=0;i{cast[sp]={voice:c.voice,color:c.color,instruct:c.instruct||"",lang:c.lang||"",gender:c.gender||"",tags:c.tags||"",soul:c.soul||"",ignored:!!c.ignored,hidden:!!c.hidden}});const emotions={},notes={},ignored={},hidden={};return rehState.lines.forEach((l,i)=>{l.type==="dialog"&&l.emotion&&(emotions[i]=l.emotion),l.type==="dialog"&&l.note&&(notes[i]=l.note),l.ignored&&(ignored[i]=!0),l.hidden&&(hidden[i]=!0)}),{title:((_a2=$("reh-script-title"))==null?void 0:_a2.value.trim())||((_b2=$("reh-page-title"))==null?void 0:_b2.textContent)||"Untitled",script:rehState.lines.length?linesToScriptText():((_c2=$("reh-script-text"))==null?void 0:_c2.value.trim())||"",cast,emotions,notes,ignored,hidden,backend:rehState.backend,narratorVoice:rehState.narratorVoice,lineIndex:rehState.lineIndex,clips:rehState.clips.map(c=>({lineIndex:c.lineIndex,speaker:c.speaker,type:c.type,blob:c.blob||null})),updated:new Date}}function linesToScriptText(){return rehState.lines.map(line=>{switch(line.type){case"act":case"transition":return` `+line.text+` `;case"scene":return` `+line.text+` `;case"action":return` `+line.text+` `;case"dialog":return` `+line.speaker+` `+line.text+` `;case"direction":return line.speaker?line.text+` `:` `+line.text+` `;case"pagebreak":return line.page?` \f${line.page} `:` \f `;default:return""}}).join("").trim()}async function saveToLibrary(){const rec=rehCurrentRecord();if(rehState.savedId)rec.id=rehState.savedId,rec.created||(rec.created=new Date),await rehDbPut(rec);else{rec.created=new Date;const newId=await rehDbAdd(rec);rehState.savedId=newId}if(toast("Saved to library","success"),rehState._keepSavedIdOnce=!0,typeof castWriteBack=="function")try{await castWriteBack(rec.title,rehState.cast)}catch{}await renderLibraryList()}async function rehAutoSaveImport(title){var _a2,_b2;const text=(((_a2=$("reh-script-text"))==null?void 0:_a2.value)||"").trim();if(!text)return null;title&&$("reh-script-title")&&!$("reh-script-title").value.trim()&&($("reh-script-title").value=title);const rec={title:((_b2=$("reh-script-title"))==null?void 0:_b2.value.trim())||title||"Untitled",script:text,cast:{},emotions:{},notes:{},ignored:{},hidden:{},backend:rehState.backend||"",narratorVoice:rehState.narratorVoice||"",lineIndex:0,clips:[],created:new Date,updated:new Date};try{const id=await rehDbAdd(rec);return rehState.savedId=id,rehState._keepSavedIdOnce=!0,await renderLibraryList(),id}catch{return null}}async function exportToFile(){const rec=rehCurrentRecord();rec.version=1,rec.created=rec.created||new Date,rec.clips=await clipsToJson(rec.clips);const json=JSON.stringify(rec,null,2),blob=new Blob([json],{type:"application/json"}),a=document.createElement("a"),title=(rec.title||"rehearsal").replace(/[^a-z0-9_\- ]/gi,"_").slice(0,40);a.href=URL.createObjectURL(blob),a.download=title+".reh",a.click()}function exportFountain(){var _a2,_b2;const title=((_a2=$("reh-script-title"))==null?void 0:_a2.value.trim())||((_b2=$("reh-page-title"))==null?void 0:_b2.textContent)||"Untitled";let out=`Title: ${title} `;rehState.lines.forEach(line=>{const t=stripMarkdown(line.text);switch(line.type){case"act":out+=` `+t+` `;break;case"scene":out+=` `+t+` `;break;case"action":out+=` `+t+` `;break;case"transition":out+=` `+t+` `;break;case"dialog":out+=` `+line.speaker+` `+t+` `;break;case"direction":out+=(line.speaker?"":` `)+t+` `;break}});const blob=new Blob([out.trim()],{type:"text/plain"}),a=document.createElement("a");a.href=URL.createObjectURL(blob),a.download=(title.replace(/[^a-z0-9_\- ]/gi,"_")||"script")+".fountain",a.click(),toast("Exported as .fountain","success")}function exportFDX(){var _a2;const title=((_a2=$("reh-script-title"))==null?void 0:_a2.value.trim())||"Untitled",x=s=>(s||"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""");let xml=` `;xml+=` `,rehState.lines.forEach(line=>{const t=x(stripMarkdown(line.text));switch(line.type){case"act":case"scene":xml+=` ${t} `;break;case"action":xml+=` ${t} `;break;case"transition":xml+=` ${t} `;break;case"direction":xml+=` ${t} `;break;case"dialog":xml+=` ${x(line.speaker)} `,xml+=` ${t} `;break}}),xml+=` `;const blob=new Blob([xml],{type:"text/xml"}),a=document.createElement("a");a.href=URL.createObjectURL(blob),a.download=(title.replace(/[^a-z0-9_\- ]/gi,"_")||"script")+".fdx",a.click(),toast("Exported as .fdx (Final Draft XML)","success")}async function importFDX(file){const text=await file.text(),paras=new DOMParser().parseFromString(text,"text/xml").querySelectorAll("Paragraph");let result="";return paras.forEach(para=>{const type=para.getAttribute("Type")||"",t=[...para.querySelectorAll("Text")].map(el=>el.textContent).join("");if(t.trim())switch(type){case"Scene Heading":result+=` `+t+` `;break;case"Action":result+=` `+t+` `;break;case"Character":result+=` `+t+` `;break;case"Dialogue":result+=t+` `;break;case"Parenthetical":result+=t+` `;break;case"Transition":result+=` `+t+` `;break}}),result.trim()}function exportOSF(){var _a2;const title=((_a2=$("reh-script-title"))==null?void 0:_a2.value.trim())||"Untitled",x=s=>(s||"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""");let xml=` `;xml+=` ${x(title)} `,rehState.lines.forEach(line=>{const t=x(stripMarkdown(line.text));switch(line.type){case"act":case"scene":xml+=` ${t} `;break;case"action":xml+=` ${t} `;break;case"transition":xml+=` ${t} `;break;case"direction":xml+=` ${t} `;break;case"dialog":xml+=` ${x(line.speaker)} ${t} `;break}}),xml+=` `;const blob=new Blob([xml],{type:"text/xml"}),a=document.createElement("a");a.href=URL.createObjectURL(blob),a.download=(title.replace(/[^a-z0-9_\- ]/gi,"_")||"script")+".osf",a.click(),toast("Exported as .osf (Open Screenplay Format)","success")}async function loadPdfJs(){window.pdfjsLib||(await new Promise((resolve,reject)=>{const s=document.createElement("script");s.src="/static/js/pdf/pdf.min.js",s.onload=resolve,s.onerror=reject,document.head.appendChild(s)}),pdfjsLib.GlobalWorkerOptions.workerSrc="/static/js/pdf/pdf.worker.min.js")}async function importPDFScript(file){toast("Loading PDF reader\u2026","success"),await loadPdfJs();const ab=await file.arrayBuffer(),pdf=await pdfjsLib.getDocument({data:ab}).promise,allLinesByPage=[],allLines=[];for(let p=1;p<=pdf.numPages;p++){const page=await pdf.getPage(p),content=await page.getTextContent(),pageW2=page.getViewport({scale:1}).width,rows=new Map;content.items.forEach(item=>{if(!item.str)return;const y=Math.round(item.transform[5]/3)*3;rows.has(y)||rows.set(y,[]),rows.get(y).push({x:item.transform[4],w:item.width||0,str:item.str})});const pageLines=[];[...rows.entries()].sort((a,b)=>b[0]-a[0]).forEach(([,items])=>{items.sort((a,b)=>a.x-b.x);const text=items.map(i=>i.str).join("").replace(/\s+/g," ").trim();if(!text)return;const x=items[0].x,last=items[items.length-1],right=last.x+(last.w||0);pageLines.push({x,right,pageW:pageW2,text}),allLines.push({x,right,pageW:pageW2,text})}),allLinesByPage.push(pageLines)}if(!allLines.length)return"";const leftMargin=Math.min(...allLines.map(l=>l.x)),pageW=allLines[0].pageW||612,isNoise=t=>{const s=t.trim();return!!(/^\d{1,4}\.?$/.test(s)||/^\(?CONTINUED\)?:?$/i.test(s)||/^\d+\.$/.test(s)||/^(rev\.|revised|draft)\b/i.test(s)&&s.length<24)},out=[];let prevBlank=!0,prevWasAction=!1;const push=(line,blankBefore)=>{blankBefore&&!prevBlank&&out.push(""),out.push(line),prevBlank=!1},blank=()=>{prevBlank||(out.push(""),prevBlank=!0)},nonAction=()=>{prevWasAction=!1};for(let pageIdx=0;pageIdx0&&(blank(),out.push(`\f${pageIdx+1}`),blank());const pageLns=allLinesByPage[pageIdx]||[];for(const ln of pageLns){const t=ln.text.trim();if(!t||isNoise(t))continue;const indent=(ln.x-leftMargin)/pageW,rightFrac=ln.right/pageW,isCaps=t===t.toUpperCase()&&/[A-Z]/.test(t),wordCount=t.split(/\s+/).length;if(/^(INT|EXT|INT\.\/EXT|EXT\.\/INT|I\/E)[\. ]/i.test(t)){blank(),push(t.toUpperCase().replace(/\s+\d+[A-Z]?\.?$/,"")),blank(),nonAction();continue}if(/^(ACT|SCENE)\s+(\d+|[IVXLC]+|ONE|TWO|THREE|FOUR|FIVE)\b/i.test(t)){blank(),push(t.toUpperCase()),blank(),nonAction();continue}if(isCaps&&(rightFrac>.72||indent>.45)&&/(TO:|CUT|FADE|DISSOLVE|SMASH|MATCH|WIPE|BLACKOUT|INTERMISSION)\b/.test(t)){blank(),push(t),blank(),nonAction();continue}if(/^\(.*\)?$/.test(t)||indent>.18&&indent<.3&&/^\(/.test(t)){push(t.startsWith("(")?t:"("+t+")",!1),nonAction();continue}const nameCore=t.replace(/\s*\([^)]*\)\s*$/,"").trim();if(indent>.22&&isCaps&&wordCount<=6&&nameCore.length<=40&&/^[A-Z0-9 .'#\-]+$/.test(nameCore)){blank(),push(t.toUpperCase()),prevBlank=!1,nonAction();continue}if(indent>.1&&indent<.34){push(t,!1),nonAction();continue}prevWasAction?out.push(t):(blank(),push(t)),prevWasAction=!0}}return out.join(` `).replace(/\n{3,}/g,` `).trim()}async function importFromFile(file){try{const text=await file.text(),data=JSON.parse(text);data.clips=clipsFromJson(data.clips||[]),data.created=data.created?new Date(data.created):new Date,data.updated=new Date,delete data.id;const newId=await rehDbAdd(data);toast("Imported: "+(data.title||"Untitled"),"success"),await renderLibraryList(),loadRecord({...data,id:newId})}catch(e){toast("Import failed: "+e.message,"error")}}function loadRecord(rec){typeof navTo=="function"&&navTo("s-rehearser"),$("reh-script-text")&&($("reh-script-text").value=rec.script||""),$("reh-script-title")&&($("reh-script-title").value=rec.title||""),rehState.savedId=rec.id||null,rehState._keepSavedIdOnce=!0,rehState.backend=rec.backend||"",rehState.lineIndex=rec.lineIndex||0,rehState.clips=(rec.clips||[]).map(c=>({...c})),rehState.synthCache.clear(),rehDecodedBuffers.clear(),rehState.narratorVoice=rec.narratorVoice||"";const lines=parseScript(rec.script||"");rec.emotions&&lines.forEach((l,i)=>{l.type==="dialog"&&rec.emotions[i]&&(l.emotion=rec.emotions[i])}),rec.notes&&lines.forEach((l,i)=>{l.type==="dialog"&&rec.notes[i]&&(l.note=rec.notes[i])}),rec.ignored&&lines.forEach((l,i)=>{rec.ignored[i]&&(l.ignored=!0)}),rec.hidden&&lines.forEach((l,i)=>{rec.hidden[i]&&(l.hidden=!0)}),rehState.lines=lines,rehState.cast={};const detected=detectCharacters(lines);Object.entries(detected).forEach(([sp,def])=>{var _a2,_b2,_c2;const saved=(_a2=rec.cast)==null?void 0:_a2[sp];rehState.cast[sp]={voice:(_b2=saved==null?void 0:saved.voice)!=null?_b2:def.voice,color:(_c2=saved==null?void 0:saved.color)!=null?_c2:def.color,instruct:(saved==null?void 0:saved.instruct)||"",lang:(saved==null?void 0:saved.lang)||"",gender:(saved==null?void 0:saved.gender)||"",tags:(saved==null?void 0:saved.tags)||"",soul:(saved==null?void 0:saved.soul)||"",ignored:!!(saved!=null&&saved.ignored),hidden:!!(saved!=null&&saved.hidden),voiceData:saved!=null&&saved.voice&&saved.voice!=="me"?getVoiceData(saved.voice):null}}),renderCastList(),rehApplySharedCast(rec.title),refreshRehBackends().then(()=>{rec.backend&&$("reh-backend-select")&&($("reh-backend-select").value=rec.backend),rec.narratorVoice&&$("reh-narrator-voice")&&($("reh-narrator-voice").value=rec.narratorVoice)}),showPhase(2)}async function rehApplySharedCast(title){if(typeof castForProduction!="function"||!title)return;let roster;try{roster=await castForProduction(title)}catch{return}if(!roster)return;let changed=!1;Object.keys(rehState.cast||{}).forEach(sp=>{if(String(sp).includes("NARRATOR"))return;const shared=roster[String(sp).toLowerCase()];if(!shared)return;const slot=rehState.cast[sp];shared.voice&&!slot.voice&&(slot.voice=shared.voice,slot.voiceData=shared.voice!=="me"&&typeof getVoiceData=="function"?getVoiceData(shared.voice):null,changed=!0),shared.gender&&!slot.gender&&(slot.gender=shared.gender,changed=!0),shared.soul&&!slot.soul&&(slot.soul=shared.soul,changed=!0)}),changed&&renderCastList()}function bookCover(title){let h=0;for(let i=0;inew Date(b.updated||0)-new Date(a.updated||0)),list.classList.toggle("list-view",rehLibView==="list");const vt=$("reh-lib-view-toggle");if(vt&&(vt.innerHTML=``),!all.length){list.innerHTML='

No saved rehearsals yet.
Parse a script below or drop a file to start.

';return}list.innerHTML=all.map(rec=>{const speakers=Object.keys(rec.cast||{}),meCount=speakers.filter(sp=>{var _a2;return((_a2=rec.cast[sp])==null?void 0:_a2.voice)==="me"}).length,clipCount=(rec.clips||[]).filter(c=>c.type==="me"&&c.blob).length,total=parseScript(rec.script||"").filter(l=>l.type==="dialog").length,pct=total?Math.round((rec.lineIndex||0)/total*100):0,date=rec.updated?new Date(rec.updated).toLocaleDateString():"\u2014",isCurrent=rec.id===rehState.savedId,title=rec.title||"Untitled",{c1,c2}=bookCover(title),avatars=speakers.slice(0,5).map(sp=>`${sp[0].toUpperCase()}`).join("");return`
${isCurrent?'active':""}
${escHtml(title)}
${avatars}
${total} lines \xB7 ${speakers.length} cast${meCount?" \xB7 "+meCount+" me":""}${clipCount?" \xB7 "+clipCount+"\u{1F3A4}":""}
${pct}% \xB7 ${date}
`}).join(""),list.querySelectorAll(".reh-book").forEach(book=>book.addEventListener("click",async e=>{if(e.target.closest(".reh-book-act"))return;const id=parseInt(book.dataset.id),rec=await rehDbGetById(id);rec?loadRecord(rec):toast("Rehearsal not found","error")})),list.querySelectorAll(".reh-lib-export").forEach(btn=>btn.addEventListener("click",async e=>{e.stopPropagation();const id=parseInt(btn.dataset.id),rec=await rehDbGetById(id);if(!rec){toast("Rehearsal not found","error");return}rec.version=1,rec.clips=await clipsToJson(rec.clips||[]);const json=JSON.stringify(rec,null,2),a=document.createElement("a"),t=(rec.title||"rehearsal").replace(/[^a-z0-9_\- ]/gi,"_").slice(0,40);a.href=URL.createObjectURL(new Blob([json],{type:"application/json"})),a.download=t+".reh",a.click()})),list.querySelectorAll(".reh-lib-delete").forEach(btn=>btn.addEventListener("click",async e=>{e.stopPropagation();const bookEl=btn.closest(".reh-book");if(bookEl.querySelector(".reh-book-del-confirm"))return;const confirmOverlay=document.createElement("div");confirmOverlay.className="reh-book-del-confirm",confirmOverlay.style.cssText="position:absolute; inset:0; background:rgba(0,0,0,0.85); color:#fff; display:flex; flex-direction:column; justify-content:center; align-items:center; border-radius:inherit; z-index:10; padding:12px; text-align:center; box-sizing:border-box;",confirmOverlay.innerHTML=`
Delete Rehearsal?
This cannot be undone.
`,confirmOverlay.addEventListener("click",ce=>ce.stopPropagation()),confirmOverlay.querySelector("#btn-cancel-del").addEventListener("click",ce=>{ce.stopPropagation(),confirmOverlay.remove()}),confirmOverlay.querySelector("#btn-confirm-del").addEventListener("click",async ce=>{ce.stopPropagation(),await rehDbDelete(parseInt(btn.dataset.id)),rehState.savedId===parseInt(btn.dataset.id)&&(rehState.savedId=null),renderLibraryList()}),bookEl.appendChild(confirmOverlay)}))}(_ma=$("reh-lib-view-toggle"))==null||_ma.addEventListener("click",()=>{rehLibView=rehLibView==="shelf"?"list":"shelf",localStorage.setItem("reh-lib-view",rehLibView),renderLibraryList()});function getVoiceData(voiceId){return(window._voices||[]).find(v=>v.id===voiceId)||null}function _rehVoiceVisibleId(voiceId,include=""){if(!voiceId)return!1;if(include&&voiceId===include)return!0;const v=getVoiceData(voiceId);return!v||v.enabled!==!1}function voiceAvatarHtml(voiceId,color,size=32){const v=getVoiceData(voiceId),s=size+"px",radius=Math.round(size/2);if(v!=null&&v.has_picture)return`${escHtml(voiceId)}`;const ic=v!=null&&v.avatar&&window.VOICE_AVATAR_ICONS?window.VOICE_AVATAR_ICONS[v.avatar]:null;if(ic)return``;const initial=(voiceId||"?")[0].toUpperCase();return`${initial}`}function showPhase(n){const impex=$("reh-impex-panel");impex&&(impex.hidden=!0);for(let i=1;i<=4;i++){const el=$("reh-phase-"+i);el&&(el.hidden=i!==n)}syncPhaseTabs(n),typeof window.onRehearserPhaseChange=="function"&&window.onRehearserPhaseChange(n)}window.showRehImpEx=function(){var _a2;stopPlay(),stopRehMic();for(let i=1;i<=4;i++){const el=$("reh-phase-"+i);el&&(el.hidden=!0)}const panel=$("reh-impex-panel");panel&&(panel.hidden=!1);const note=$("reh-impex-export-note");note&&(note.textContent=rehState.lines.length?`Current session: "${((_a2=$("reh-script-title"))==null?void 0:_a2.value)||"Untitled"}" \xB7 ${rehState.lines.filter(l=>l.type==="dialog").length} lines`:"No active session \u2014 load a script first to export.")};function syncPhaseTabs(n){const hasScript=rehState.lines.length>0,hasClips=rehState.clips.length>0;document.querySelectorAll(".reh-subtab").forEach(tab=>{const p=parseInt(tab.dataset.phase);tab.classList.toggle("active",p===n);let enabled=p===1||p===2&&hasScript||p===3&&hasScript||p===4&&(hasClips||n===4);tab.disabled=!enabled})}document.querySelectorAll(".reh-subtab").forEach(tab=>{tab.addEventListener("click",()=>{if(tab.disabled)return;const p=parseInt(tab.dataset.phase);if(p!==3&&(stopPlay(),stopRehMic(),hideRecOverlay()),p===1&&renderLibraryList(),p===3&&rehState.lines.length){buildScriptPage(),showPhase(3),highlightCurrentLine();return}p===4&&renderSummary(),showPhase(p)})}),(_na=$("reh-file-input"))==null||_na.addEventListener("change",function(){var _a2;const f=(_a2=this.files)==null?void 0:_a2[0];if(!f)return;const guess=f.name.replace(/\.[^.]+$/,"").replace(/[-_]+/g," ").trim(),r=new FileReader;r.onload=async e=>{$("reh-script-text").value=e.target.result,await rehAutoSaveImport(guess),toast("Script loaded & saved to your library \u2014 click Parse & cast","success")},r.readAsText(f),this.value=""}),(_oa=$("reh-pdf-input"))==null||_oa.addEventListener("change",async function(){var _a2;const f=(_a2=this.files)==null?void 0:_a2[0];if(f){this.value="";try{const text=await importPDFScript(f);$("reh-script-text")&&($("reh-script-text").value=text);const guess=f.name.replace(/\.pdf$/i,"").replace(/[-_]+/g," ").trim();await rehAutoSaveImport(guess),toast("PDF imported & saved to your library \u2014 check formatting, then click Parse & cast","success")}catch(e){toast("PDF import failed: "+e.message,"error")}}}),(_pa=$("reh-fdx-input"))==null||_pa.addEventListener("change",async function(){var _a2,_b2;const f=(_a2=this.files)==null?void 0:_a2[0];if(f){this.value="";try{const text=await importFDX(f);if($("reh-script-text")&&($("reh-script-text").value=text),!((_b2=$("reh-script-title"))!=null&&_b2.value.trim())){const guess=f.name.replace(/\.fdx$/i,"").replace(/[-_]+/g," ").trim();$("reh-script-title")&&($("reh-script-title").value=guess)}toast("FDX imported \u2014 click Parse & cast","success")}catch(e){toast("FDX import failed: "+e.message,"error")}}});async function rehImportAnyFile(f){if(!f)return;const name=f.name.toLowerCase(),setTitle=strip=>{var _a2;if(!((_a2=$("reh-script-title"))!=null&&_a2.value.trim())){const guess=f.name.replace(strip,"").replace(/[-_]+/g," ").trim();$("reh-script-title")&&($("reh-script-title").value=guess)}};try{if(name.endsWith(".reh")||name.endsWith(".json")){await importFromFile(f);return}if(name.endsWith(".pdf")){toast("Reading PDF\u2026","success");const text2=await importPDFScript(f);$("reh-script-text")&&($("reh-script-text").value=text2),setTitle(/\.pdf$/i),await rehAutoSaveImport(),toast("PDF imported & saved to your library \u2014 check formatting, then Parse & cast","success");return}if(name.endsWith(".fdx")||name.endsWith(".osf")||name.endsWith(".xml")){const text2=await importFDX(f);$("reh-script-text")&&($("reh-script-text").value=text2),setTitle(/\.(fdx|osf|xml)$/i),await rehAutoSaveImport(),toast("Script imported & saved to your library \u2014 click Parse & cast","success");return}const text=await f.text();$("reh-script-text")&&($("reh-script-text").value=text),setTitle(/\.(txt|md|fountain)$/i),await rehAutoSaveImport(),toast("Script loaded & saved to your library \u2014 click Parse & cast","success")}catch(e){toast("Import failed: "+e.message,"error")}}(function(){const dz=$("reh-dropzone"),phase=$("reh-phase-1");if(!dz||!phase)return;let depth=0;const isFileDrag=e=>e.dataTransfer&&[...e.dataTransfer.types||[]].includes("Files");phase.addEventListener("dragenter",e=>{isFileDrag(e)&&(e.preventDefault(),depth++,dz.classList.add("dragover"),dz.scrollIntoView({behavior:"smooth",block:"nearest"}))}),phase.addEventListener("dragover",e=>{isFileDrag(e)&&e.preventDefault()}),phase.addEventListener("dragleave",e=>{isFileDrag(e)&&(depth=Math.max(0,depth-1),depth===0&&dz.classList.remove("dragover"))}),phase.addEventListener("drop",async e=>{var _a2;if(!isFileDrag(e))return;e.preventDefault(),depth=0,dz.classList.remove("dragover");const f=(_a2=e.dataTransfer.files)==null?void 0:_a2[0];await rehImportAnyFile(f)})})(),(_qa=$("reh-parse-btn"))==null||_qa.addEventListener("click",()=>{var _a2;const text=(_a2=$("reh-script-text"))==null?void 0:_a2.value.trim();if(!text){toast("Paste or upload a script first","error");return}if(rehState.lines=parseScript(text),!rehState.lines.filter(l=>l.type==="dialog").length){toast("No dialog found. Use screenplay format (CAPS name + dialog) or CHAR: text.","error");return}const detected=detectCharacters(rehState.lines);Object.entries(detected).forEach(([sp])=>{rehState.cast[sp]&&(detected[sp]={...detected[sp],...rehState.cast[sp]})}),rehState.cast=detected,rehState._keepSavedIdOnce?rehState._keepSavedIdOnce=!1:rehState.savedId=null,rehState.clips=[],rehState.lineIndex=0,rehState.synthCache.clear(),rehDecodedBuffers.clear(),renderCastList(),showPhase(2),refreshRehBackends()});function castAvatarHtml(sp){const c=rehState.cast[sp];if(!c)return"";if(c.voice&&c.voice!=="me"){const vd=getVoiceData(c.voice);if(vd!=null&&vd.has_picture)return``;const ic=vd!=null&&vd.avatar&&window.VOICE_AVATAR_ICONS?window.VOICE_AVATAR_ICONS[vd.avatar]:null;if(ic)return``}const initial=sp[0].toUpperCase();return`${initial}`}let _rehAudEl=null,_rehAudBtn=null;function _rehStopAudition(){if(_rehAudEl)try{_rehAudEl.pause()}catch{}if(_rehAudBtn){_rehAudBtn.classList.remove("playing");const i=_rehAudBtn.querySelector(".mdi");i&&(i.className="mdi mdi-play")}_rehAudBtn=null}function _rehAudition(url,btn){if(!url){toast("No preview audio for this voice","error");return}if(_rehAudBtn===btn&&_rehAudEl&&!_rehAudEl.paused){_rehStopAudition();return}_rehStopAudition(),_rehAudEl||(_rehAudEl=new Audio,_rehAudEl.addEventListener("ended",_rehStopAudition)),_rehAudEl.src=url,_rehAudEl.play().then(()=>{_rehAudBtn=btn,btn.classList.add("playing");const i=btn.querySelector(".mdi");i&&(i.className="mdi mdi-stop")}).catch(()=>toast("Could not play preview","error"))}const _REH_PREVIEW_TEXT="Hello \u2014 this is how this voice sounds.",_rehSearchCache={};function _rehVoiceRow({name,meta,playUrl,playVoice,isCurrent,useAttrs}){const play=``,right=isCurrent?' Selected':``;return`
${play}
${escHtml(name)}${escHtml(meta)}
${right}
`}function castOnlinePanelHtml(sp,c){const o=c.online;if(!o||!Array.isArray(o.candidates)||c.voice==="me")return"";const curCand=o.candidates.find(x=>x.voice_id&&x.voice_id===c.voice);let curRow;if(curCand)curRow=_rehVoiceRow({name:curCand.title||c.voice,meta:[curCand.gender,curCand.language].filter(Boolean).join(" \xB7 ")||"fish.audio",playUrl:curCand.sample_audio,isCurrent:!0});else if(c.voice){const vd=getVoiceData(c.voice);curRow=_rehVoiceRow({name:(vd==null?void 0:vd.name)||c.voice,meta:(vd==null?void 0:vd.group)||(vd==null?void 0:vd.tag)||"library voice",playVoice:c.voice,isCurrent:!0})}else curRow='
No voice assignedpick one below
';const alts=o.candidates.map((cand,i)=>({cand,i})).filter(({cand})=>cand.voice_id!==c.voice||!cand.voice_id),altRows=alts.map(({cand,i})=>_rehVoiceRow({name:cand.title||"Voice",meta:[cand.gender,cand.language].filter(Boolean).join(" \xB7 ")||"fish.audio",playUrl:cand.sample_audio,useAttrs:` data-act="alt" data-idx="${i}"`})).join("");return`
Voice \u2014 try & change ${alts.length?``:""}
${curRow}
${alts.length?``:""}
`}async function _rehPreviewLocal(voiceId,btn){var _a2;const backend=(_a2=$("reh-backend-select"))==null?void 0:_a2.value;if(!backend){toast("Select a TTS backend first","error");return}if(_rehAudBtn===btn&&_rehAudEl&&!_rehAudEl.paused){_rehStopAudition();return}_rehStopAudition();const icon=btn.querySelector(".mdi");btn.classList.add("loading"),icon&&(icon.className="mdi mdi-loading");try{const blob=await fetchTtsPreviewBlob(voiceId,_REH_PREVIEW_TEXT,"wav","",backend);_rehAudEl||(_rehAudEl=new Audio,_rehAudEl.addEventListener("ended",_rehStopAudition)),_rehAudEl.src=URL.createObjectURL(blob),await _rehAudEl.play(),btn.classList.remove("loading"),_rehAudBtn=btn,btn.classList.add("playing"),icon&&(icon.className="mdi mdi-stop")}catch(e){btn.classList.remove("loading"),icon&&(icon.className="mdi mdi-play"),toast("Preview failed: "+(e.message||e),"error")}}function _rehPickOneLiner(sp){const isNarr=sp===REH_NARRATOR_KEY,pool=rehState.lines.filter(l=>isNarr?l.type!=="dialog"&&l.type!=="pagebreak"&&!l.ignored:l.type==="dialog"&&l.speaker===sp).map(l=>({text:stripMarkdown(l.text||"").trim(),emotion:l.emotion||""})).filter(l=>l.text.length>=4);if(!pool.length)return null;const best=pool.map(l=>{const len=l.text.length;let score=len>=20&&len<=110?3:len<20?1:0;return/[.!?]["']?$/.test(l.text)&&(score+=1),score-=Math.abs(len-60)/120,{l,score}}).sort((a,b)=>b.score-a.score)[0].l;return{text:best.text.slice(0,220),emotion:best.emotion}}async function _rehPreviewCastLine(sp,btn){var _a2;const c=rehState.cast[sp];if(!c||!c.voice||c.voice==="me"){toast("Assign a voice first","error");return}const backend=(_a2=$("reh-backend-select"))==null?void 0:_a2.value;if(!backend){toast("Select a TTS backend first","error");return}if(_rehAudBtn===btn&&_rehAudEl&&!_rehAudEl.paused){_rehStopAudition();return}_rehStopAudition();const pick=_rehPickOneLiner(sp);if(!pick){toast("No lines to preview yet","error");return}const icon=btn.querySelector(".mdi");btn.classList.add("loading"),icon&&(icon.className="mdi mdi-loading");try{const blob=await fetchTtsPreviewBlob(c.voice,_rehInlineTone(pick.text,pick.emotion),"wav",_buildInstruct(c.instruct,pick.emotion),backend);_rehAudEl||(_rehAudEl=new Audio,_rehAudEl.addEventListener("ended",_rehStopAudition)),_rehAudEl.src=URL.createObjectURL(blob),await _rehAudEl.play(),btn.classList.remove("loading"),_rehAudBtn=btn,btn.classList.add("playing"),icon&&(icon.className="mdi mdi-stop")}catch(e){btn.classList.remove("loading"),icon&&(icon.className="mdi mdi-play"),toast("Preview failed: "+(e.message||e),"error")}}function _rehAssignLocal(sp,voiceId){const c=rehState.cast[sp];c&&(_rehStopAudition(),c.voice=voiceId,c.voiceData=getVoiceData(voiceId),c.online&&(c.online.picked=c.online.candidates.findIndex(x=>x.voice_id===voiceId)),renderCastList(),typeof populateNarratorSelect=="function"&&populateNarratorSelect())}function _rehRenderLocalResults(card,sp,q){var _a2;const box=card.querySelector(".reh-vo-local-results");if(!box)return;const cur=(_a2=rehState.cast[sp])==null?void 0:_a2.voice,ql=(q||"").trim().toLowerCase(),matches=rehState.voices.filter(v=>{if(!_rehVoiceVisibleId(v,cur)||v===cur)return!1;if(!ql)return!0;const vd=getVoiceData(v);return v.toLowerCase().includes(ql)||((vd==null?void 0:vd.name)||"").toLowerCase().includes(ql)||((vd==null?void 0:vd.group)||"").toLowerCase().includes(ql)}).slice(0,40);if(!matches.length){box.innerHTML='
No library voices match.
';return}box.innerHTML=matches.map(v=>{const vd=getVoiceData(v);return _rehVoiceRow({name:(vd==null?void 0:vd.name)||v,meta:(vd==null?void 0:vd.group)||(vd==null?void 0:vd.tag)||"library voice",playVoice:v,useAttrs:` data-act="local" data-voice="${escHtml(v)}"`})}).join("")}async function _rehSearchOnline(card,sp,q){var _a2,_b2;const box=card.querySelector(".reh-vo-search-results");if(!box)return;if(q=(q||"").trim(),!q){box.innerHTML='
Enter a search term first.
';return}box.innerHTML='
Searching fish.audio\u2026
';const lang=(_b2=REH_FISH_LANG[((_a2=$("reh-design-lang"))==null?void 0:_a2.value)||"English"])!=null?_b2:"en";let items=[];try{const qs=[`search=${encodeURIComponent(q)}`,`language=${lang}`,"page_size=10","sort_by=score"];items=(await fetch("/api/fishaudio/voices?"+qs.join("&")).then(r=>r.json())).items||[]}catch{}const cands=items.filter(v=>v.sample_audio).slice(0,10).map(v=>({title:v.title,sample_audio:v.sample_audio,image:v.image||"",gender:v.gender||"",language:v.language||lang||"",description:v.description||"",sample_text:v.sample_text||v.default_text||""}));if(_rehSearchCache[sp]=cands,!cands.length){box.innerHTML=`
No playable voices found for \u201C${escHtml(q)}\u201D.
`;return}box.innerHTML=cands.map((cand,i)=>_rehVoiceRow({name:cand.title||"Voice",meta:[cand.gender,cand.language].filter(Boolean).join(" \xB7 ")||"fish.audio",playUrl:cand.sample_audio,useAttrs:` data-act="search" data-idx="${i}"`})).join("")}async function _rehUseSearchResult(sp,idx,btn){const c=rehState.cast[sp];if(!c)return;const cand=(_rehSearchCache[sp]||[])[idx];if(!cand)return;_rehStopAudition();const orig=btn.innerHTML;btn.disabled=!0,btn.innerHTML='';try{const vid=await _rehImportFishCandidate(sp,cand);cand.voice_id=vid,c.voice=vid,c.voiceData=getVoiceData(vid),c.online||(c.online={candidates:[],picked:0}),c.online.candidates.push(cand),c.online.picked=c.online.candidates.length-1,typeof loadVoiceLibrary=="function"&&await loadVoiceLibrary().catch(()=>{}),renderCastList(),typeof populateNarratorSelect=="function"&&populateNarratorSelect(),toast(`Switched ${sp} to ${cand.title||"voice"}`,"success")}catch(err){btn.disabled=!1,btn.innerHTML=orig,toast("Import failed: "+err.message,"error")}}const _rehFishImported={};async function _rehImportFishCandidate(sp,cand){const key=(cand.sample_audio||cand.title||"").toLowerCase();if(key&&_rehFishImported[key]){const id=_rehFishImported[key];return rehState.voices.includes(id)||rehState.voices.push(id),id}const title=(cand.title||"").toLowerCase().trim(),existing=title&&(window._voices||[]).find(v=>(v.group==="fish-audio"||v.tag==="fish-audio")&&(v.name||"").toLowerCase().trim()===title);if(existing)return key&&(_rehFishImported[key]=existing.id),rehState.voices.includes(existing.id)||rehState.voices.push(existing.id),existing.id;const vid=`${(cand.language||"EN").slice(0,2).toUpperCase()}_${(cand.title||sp).replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"").slice(0,36)||"Voice"}`,d=await fetch("/api/quick-import-voice",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:vid,audio_url:cand.sample_audio,transcript:cand.sample_text||""})}).then(r=>r.json());if(!d.voice_id)throw new Error("import returned no id");return typeof saveMeta=="function"&&await saveMeta(d.voice_id,{name:cand.title||sp,tag:"fish-audio",group:"fish-audio",origin:"cloned",gender:(cand.gender||"").charAt(0).toUpperCase(),note:(cand.description||"").slice(0,180)}).catch(e=>logErr("fish saveMeta",e)),cand.image&&await fetch("/api/voice/picture-url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({voice_id:d.voice_id,image_url:cand.image})}).catch(e=>logErr("fish picture",e)),rehState.voices.includes(d.voice_id)||rehState.voices.push(d.voice_id),key&&(_rehFishImported[key]=d.voice_id),d.voice_id}async function _rehUseCandidate(sp,idx,btn){const c=rehState.cast[sp],o=c&&c.online;if(!o)return;const cand=o.candidates[idx];if(!cand)return;if(_rehStopAudition(),cand.voice_id){c.voice=cand.voice_id,c.voiceData=getVoiceData(cand.voice_id),o.picked=idx,renderCastList(),typeof populateNarratorSelect=="function"&&populateNarratorSelect();return}const orig=btn.innerHTML;btn.disabled=!0,btn.innerHTML='';try{const vid=await _rehImportFishCandidate(sp,cand);cand.voice_id=vid,c.voice=vid,c.voiceData=getVoiceData(vid),o.picked=idx,typeof loadVoiceLibrary=="function"&&await loadVoiceLibrary().catch(()=>{}),renderCastList(),typeof populateNarratorSelect=="function"&&populateNarratorSelect(),toast(`Switched ${sp} to ${cand.title||"voice"}`,"success")}catch(err){btn.disabled=!1,btn.innerHTML=orig,toast("Import failed: "+err.message,"error")}}const REH_NARRATOR_KEY="\u{1F4D6}NARRATOR";function _ensureNarrator(){rehState.cast[REH_NARRATOR_KEY]||(rehState.cast[REH_NARRATOR_KEY]={voice:rehState.narratorVoice||"",color:"#6b7280",instruct:"",voiceData:rehState.narratorVoice?getVoiceData(rehState.narratorVoice):null,_isNarrator:!0});const n=rehState.cast[REH_NARRATOR_KEY];n.voice?(rehState.narratorVoice=n.voice,n.voiceData||(n.voiceData=getVoiceData(n.voice))):rehState.narratorVoice&&(n.voice=rehState.narratorVoice,n.voiceData=getVoiceData(rehState.narratorVoice))}const REH_CAST_LANGS=["English","German","Auto","French","Spanish","Italian","Portuguese","Dutch","Polish"],REH_CAST_GENDERS=[["","\u2014"],["F","\u2640 Female"],["M","\u2642 Male"],["N","\u26A5 Diverse"]];function _castApplyToLines(sp,fn){rehState.lines.forEach((l,i)=>{l.speaker===sp&&l.type==="dialog"&&fn(l,i)})}function _safeDomId(value){let out="";const s=String(value||"voice");for(let i=0;irenderCastList()).catch(()=>{})),_ensureNarrator();const narr=REH_NARRATOR_KEY,lineCount=sp=>rehState.lines.filter(l=>l.speaker===sp&&l.type==="dialog").length,allOthers=Object.keys(rehState.cast).filter(s=>s!==narr),others=_castSortFilter(allOthers,lineCount),speakers=[narr,...others],scriptTitle=((_a2=$("reh-script-title"))==null?void 0:_a2.value.trim())||"";list.innerHTML=speakers.map(sp=>{var _a3;const c=rehState.cast[sp],isNarr=sp===narr,isMe=c.voice==="me",label=isNarr?"Narrator":sp,n=lineCount(sp),sub=isNarr?"scene headings & descriptions":`${n} line${n!==1?"s":""}${scriptTitle?` \xB7 ${escHtml(scriptTitle)}`:""}`,cardCls="reh-cast-card"+(isNarr?" reh-cast-narrator":"")+(c.ignored?" reh-cast-ignored":"")+(c.hidden?" reh-cast-hidden-c":""),langSel=REH_CAST_LANGS.map(l=>`${l}`).join(""),genSel=REH_CAST_GENDERS.map(([v,t])=>``).join(""),voiceSel=``,voiceName=!isMe&&c.voice?((_a3=getVoiceData(c.voice))==null?void 0:_a3.name)||c.voice:isMe?"Your mic":"";return`
${castAvatarHtml(sp)}
${isNarr?' ':""}${escHtml(label)}
${sub}
${escHtml(voiceName||"No voice assigned")}
${!isMe&&c.voice?``:""}
${isNarr?"":`
`}
`}).join("");const countEl=$("reh-cast-count");if(countEl){const shown=others.length,total=allOthers.length;countEl.textContent=shown===total?`${total} ${total===1?"character":"characters"} + narrator`:`${shown} of ${total} characters`}_wireCastControls(),applyCastView();const card=el=>el.closest(".reh-cast-card"),spOf=el=>card(el).dataset.speaker;window.VoicePicker&&list.querySelectorAll(".reh-voice-sel[id]").forEach(sel=>{const cur=sel.value;VoicePicker.upgrade(sel.id),cur&&VoicePicker.setValue(sel.id,cur)}),list.querySelectorAll(".reh-me-check").forEach(cb=>cb.addEventListener("change",function(){var _a3;const sp=spOf(this);rehState.cast[sp].voice=this.checked?"me":((_a3=card(this).querySelector(".reh-voice-sel"))==null?void 0:_a3.value)||"",rehState.cast[sp].voiceData=this.checked?null:getVoiceData(rehState.cast[sp].voice),renderCastList()})),list.querySelectorAll(".reh-voice-sel").forEach(sel=>sel.addEventListener("change",function(){const sp=this.dataset.speaker;rehState.cast[sp].voice=this.value,rehState.cast[sp].voiceData=getVoiceData(this.value),delete rehState.cast[sp].online,sp===REH_NARRATOR_KEY&&(rehState.narratorVoice=this.value),renderCastList()})),list.querySelectorAll(".reh-cast-instruct").forEach(inp=>inp.addEventListener("input",function(){rehState.cast[spOf(this)].instruct=this.value})),list.querySelectorAll(".reh-cc-lang").forEach(s=>s.addEventListener("change",function(){rehState.cast[spOf(this)].lang=this.value})),list.querySelectorAll(".reh-cc-gender").forEach(s=>s.addEventListener("change",function(){rehState.cast[spOf(this)].gender=this.value})),list.querySelectorAll(".reh-cc-tags").forEach(i=>i.addEventListener("input",function(){rehState.cast[spOf(this)].tags=this.value})),list.querySelectorAll(".reh-cc-soul-text").forEach(t=>t.addEventListener("input",function(){rehState.cast[spOf(this)].soul=this.value})),list.querySelectorAll(".reh-cc-develop").forEach(b=>b.addEventListener("click",function(e){e.preventDefault(),_castDevelop(spOf(this),this)})),list.querySelectorAll(".reh-cc-iconbtn").forEach(b=>b.addEventListener("click",function(){const sp=spOf(this),act=this.dataset.act,c=rehState.cast[sp];act==="ignore"?(c.ignored=!c.ignored,_castApplyToLines(sp,l=>l.ignored=c.ignored),renderCastList()):act==="hide"?(c.hidden=!c.hidden,_castApplyToLines(sp,l=>l.hidden=c.hidden),renderCastList()):act==="delete"&&_castDeleteCharacter(sp)})),list._voDelegated||(list._voDelegated=!0,list.addEventListener("click",e=>{var _a3,_b2,_c2,_d2,_e2,_f2;const sampleBtn=e.target.closest(".reh-cc-sample-btn");if(sampleBtn){e.preventDefault(),_rehPreviewCastLine(sampleBtn.dataset.speaker,sampleBtn);return}const playBtn=e.target.closest(".reh-vo-play");if(playBtn){e.preventDefault(),playBtn.dataset.voice?_rehPreviewLocal(playBtn.dataset.voice,playBtn):_rehAudition(playBtn.dataset.url,playBtn);return}const toggle=e.target.closest(".reh-vo-toggle");if(toggle){e.preventDefault();const alts=(_a3=toggle.closest(".reh-cc-online"))==null?void 0:_a3.querySelector(".reh-vo-alts");alts&&(alts.hidden=!alts.hidden,toggle.textContent=toggle.textContent.replace(/[▾▴]\s*$/,"")+(alts.hidden?"\u25BE":"\u25B4"));return}const tool=e.target.closest(".reh-vo-tool");if(tool){e.preventDefault();const panel=tool.closest(".reh-cc-online"),want=tool.dataset.tool,localP=panel.querySelector(".reh-vo-local-panel"),searchP=panel.querySelector(".reh-vo-search-panel"),showLocal=want==="local"&&((_b2=localP==null?void 0:localP.hidden)!=null?_b2:!0),showSearch=want==="search"&&((_c2=searchP==null?void 0:searchP.hidden)!=null?_c2:!0);if(localP&&(localP.hidden=!showLocal),searchP&&(searchP.hidden=!showSearch),panel.querySelectorAll(".reh-vo-tool").forEach(t=>t.classList.toggle("active",t.dataset.tool==="local"&&showLocal||t.dataset.tool==="search"&&showSearch)),showLocal){const card2=e.target.closest(".reh-cast-card");_rehRenderLocalResults(card2,spOf(tool),""),(_d2=card2.querySelector(".reh-vo-local-input"))==null||_d2.focus()}showSearch&&((_e2=panel.querySelector(".reh-vo-search-input"))==null||_e2.focus());return}const goBtn=e.target.closest(".reh-vo-search-go");if(goBtn){e.preventDefault();const card2=e.target.closest(".reh-cast-card");_rehSearchOnline(card2,spOf(goBtn),(_f2=card2.querySelector(".reh-vo-search-input"))==null?void 0:_f2.value);return}const use=e.target.closest(".reh-vo-use");if(use){e.preventDefault();const sp=spOf(use),act=use.dataset.act;act==="local"?_rehAssignLocal(sp,use.dataset.voice):act==="search"?_rehUseSearchResult(sp,parseInt(use.dataset.idx,10),use):_rehUseCandidate(sp,parseInt(use.dataset.idx,10),use)}}),list.addEventListener("input",e=>{const li=e.target.closest(".reh-vo-local-input");li&&_rehRenderLocalResults(e.target.closest(".reh-cast-card"),spOf(li),li.value)}),list.addEventListener("keydown",e=>{const si=e.target.closest(".reh-vo-search-input");si&&e.key==="Enter"&&(e.preventDefault(),_rehSearchOnline(e.target.closest(".reh-cast-card"),spOf(si),si.value))}))}function applyCastView(){const list=$("reh-cast-list");if(!list)return;const view=rehState.castView==="list"?"list":"card";list.classList.toggle("reh-cast-view-card",view==="card"),list.classList.toggle("reh-cast-view-list",view==="list"),document.querySelectorAll("#reh-cast-view-toggle .reh-view-btn").forEach(b=>b.classList.toggle("active",b.dataset.view===view))}try{rehState.castView=localStorage.getItem("reh-cast-view")||"card"}catch{rehState.castView="card"}rehState.castFilter={search:"",gender:"",lang:""};try{rehState.castSort=JSON.parse(localStorage.getItem("reh-cast-sort"))||{by:"name",dir:"asc"}}catch{rehState.castSort={by:"name",dir:"asc"}}function _wireCastControls(){const toggle=$("reh-cast-view-toggle");if(!toggle||toggle._wired)return;toggle._wired=!0,toggle.querySelectorAll(".reh-view-btn").forEach(b=>b.addEventListener("click",()=>{rehState.castView=b.dataset.view;try{localStorage.setItem("reh-cast-view",b.dataset.view)}catch{}applyCastView()}));const search=$("reh-cast-search"),fg=$("reh-cast-filter-gender"),fl=$("reh-cast-filter-lang"),sortSel=$("reh-cast-sort"),dirBtn=$("reh-cast-sort-dir"),setDirIcon=()=>{dirBtn&&(dirBtn.dataset.dir=rehState.castSort.dir,dirBtn.querySelector(".mdi").className="mdi mdi-sort-"+(rehState.castSort.dir==="desc"?"descending":"ascending"))},persist=()=>{try{localStorage.setItem("reh-cast-sort",JSON.stringify(rehState.castSort))}catch{}};sortSel&&(sortSel.value=rehState.castSort.by),setDirIcon(),search==null||search.addEventListener("input",()=>{rehState.castFilter.search=search.value.trim(),renderCastList(),search.focus()}),fg==null||fg.addEventListener("change",()=>{rehState.castFilter.gender=fg.value,renderCastList()}),fl==null||fl.addEventListener("change",()=>{rehState.castFilter.lang=fl.value,renderCastList()}),sortSel==null||sortSel.addEventListener("change",()=>{rehState.castSort.by=sortSel.value,rehState.castSort.dir=sortSel.value==="lines"?"desc":"asc",setDirIcon(),persist(),renderCastList()}),dirBtn==null||dirBtn.addEventListener("click",()=>{rehState.castSort.dir=rehState.castSort.dir==="desc"?"asc":"desc",setDirIcon(),persist(),renderCastList()})}function _castSortFilter(speakers,lineCount){const f=rehState.castFilter||{},s=rehState.castSort||{by:"name",dir:"asc"},out=speakers.filter(sp=>{const c=rehState.cast[sp]||{};if(f.search){const q=f.search.toLowerCase();if(!sp.toLowerCase().includes(q)&&!(c.tags||"").toLowerCase().includes(q))return!1}return!(f.gender&&(c.gender||"")!==f.gender||f.lang&&(c.lang||"")!==f.lang)}),byName=(a,b)=>a.localeCompare(b,void 0,{sensitivity:"base"}),cmp={name:byName,gender:(a,b)=>(rehState.cast[a].gender||"~").localeCompare(rehState.cast[b].gender||"~")||byName(a,b),lang:(a,b)=>(rehState.cast[a].lang||"~").localeCompare(rehState.cast[b].lang||"~")||byName(a,b),lines:(a,b)=>lineCount(a)-lineCount(b)||byName(a,b),tag:(a,b)=>(rehState.cast[a].tags||"~").localeCompare(rehState.cast[b].tags||"~")||byName(a,b)}[s.by]||byName;return out.sort(cmp),s.dir==="desc"&&out.reverse(),out}function _castDeleteCharacter(sp){const n=rehState.lines.filter(l=>l.speaker===sp&&l.type==="dialog").length;if(confirm(`Delete \u201C${sp}\u201D and their ${n} line${n!==1?"s":""}? This cannot be undone.`)){for(let i=rehState.lines.length-1;i>=0;i--)rehState.lines[i].speaker===sp&&rehState.lines[i].type==="dialog"&&(rehState.lines.splice(i,1),_reindexLineState(i));delete rehState.cast[sp],renderCastList(),rehState.lines.length&&buildScriptPage(),toast(`Removed ${sp}`,"success")}}async function _castDevelop(sp,btn){var _a2,_b2,_c2,_d2,_e2;const script=((_a2=$("reh-script-text"))==null?void 0:_a2.value.trim())||linesToScriptText();if(!script){toast("Load a script first","error");return}const c=rehState.cast[sp],orig=btn.innerHTML;btn.disabled=!0,btn.innerHTML=' Developing\u2026';try{const r=await fetch("/api/analyze-characters",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({script,names:[sp],llm_url:((_b2=$("reh-llm-url"))==null?void 0:_b2.value.trim())||rehDefaultLlmUrl(),model:((_c2=$("reh-llm-model"))==null?void 0:_c2.value)||"",language:c.lang||((_d2=$("reh-design-lang"))==null?void 0:_d2.value)||"English"})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const info=((_e2=(await r.json()).characters)==null?void 0:_e2[0])||{};info.gender&&(c.gender=String(info.gender).toUpperCase().charAt(0).replace(/[^MFN]/,"N")),info.description&&(c.soul=info.description,c.instruct=c.instruct||info.description),renderCastList(),toast(`Developed ${sp}`,"success")}catch(e){btn.disabled=!1,btn.innerHTML=orig,toast("Develop failed: "+e.message,"error")}}async function refreshRehBackends(){rehInitLlmField();const sel=$("reh-backend-select");if(!sel)return;let backends=typeof availableTtsBackends=="function"?availableTtsBackends():[];!backends.length&&typeof refreshTtsBackendAvailability=="function"&&(await refreshTtsBackendAvailability().catch(()=>{}),backends=typeof availableTtsBackends=="function"?availableTtsBackends():[]),!backends.length&&typeof _ttsBackends!="undefined"&&Array.isArray(_ttsBackends)&&(backends=_ttsBackends);const prev=sel.value||rehState.backend;if(sel.innerHTML=backends.length?backends.map(b=>``).join(""):'',prev&&[...sel.options].some(o=>o.value===prev))sel.value=prev;else{const pick=["fishspeech","voice_clone","customvoice","voice_design"].find(id=>[...sel.options].some(o=>o.value===id));pick&&(sel.value=pick)}rehState.backend=sel.value||"",_checkToneStyleSupport()}(window._ttsRefreshHooks=window._ttsRefreshHooks||[]).push(()=>{const sel=$("reh-backend-select");if(!sel)return;const backends=typeof availableTtsBackends=="function"?availableTtsBackends():[];if(!backends.length)return;const prev=sel.value||rehState.backend;sel.innerHTML=backends.map(b=>``).join(""),prev&&[...sel.options].some(o=>o.value===prev)?sel.value=prev:sel.options.length&&(sel.value=sel.options[0].value),rehState.backend=sel.value||""});function _rehAllVoiceIds(include){const ids=new Set((rehState.voices||[]).filter(id=>_rehVoiceVisibleId(id,include)));return(window._voices||[]).forEach(v=>{v&&v.id&&(v.enabled!==!1||v.id===include)&&ids.add(v.id)}),include&&ids.add(include),[...ids].sort((a,b)=>a.localeCompare(b,void 0,{sensitivity:"base"}))}function populateNarratorSelect(){const sel=$("reh-narrator-voice");if(!sel)return;const cur=rehState.narratorVoice||sel.value;sel.innerHTML=''+_rehAllVoiceIds(cur).map(v=>``).join("")}(_ra=$("reh-fetch-voices-btn"))==null||_ra.addEventListener("click",async()=>{var _a2;const backend=(_a2=$("reh-backend-select"))==null?void 0:_a2.value;if(!backend){toast("Select a backend first","error");return}$("reh-fetch-voices-btn").disabled=!0;try{(!window._voices||!window._voices.length)&&typeof loadVoiceLibrary=="function"&&await loadVoiceLibrary().catch(()=>{});const raw=await fetch("/api/tts-voices?backend="+encodeURIComponent(backend)).then(r=>r.json());rehState.voices=(Array.isArray(raw)?raw.map(v=>typeof v=="string"?v:v.id||String(v)):[]).filter(id=>_rehVoiceVisibleId(id)),renderCastList(),populateNarratorSelect(),toast("Fetched "+rehState.voices.length+" voices","success")}catch(e){toast("Fetch failed: "+e.message,"error")}finally{$("reh-fetch-voices-btn").disabled=!1}}),(_sa=$("reh-narrator-voice"))==null||_sa.addEventListener("change",function(){rehState.narratorVoice=this.value}),(_ta=$("reh-back-1-btn"))==null||_ta.addEventListener("click",()=>showPhase(1));function _buildInstruct(voiceProfile,emotion){const p=(voiceProfile||"").trim(),e=(emotion||"").trim();return!e&&!p?"":e?p?`Speak in a ${e} manner. ${p}`:`Speak in a ${e} manner.`:p}function _rehBackendIsFish(){var _a2;let id="";try{id=typeof backendById=="function"&&((_a2=backendById(rehState.backend))==null?void 0:_a2.id)||rehState.backend||""}catch{id=rehState.backend||""}return/fish/i.test(id)}function _rehInlineTone(text,emotion){const e=(emotion||"").trim();return!e||!_rehBackendIsFish()||/^\s*\[/.test(text)?text:`[${e.toLowerCase()}] ${text}`}const REH_LANG_CODE={English:"EN",German:"DE",French:"FR",Spanish:"ES",Italian:"IT",Auto:"EN"};function rehDefaultLlmUrl(){try{if(typeof _appSettings!="undefined"&&_appSettings&&_appSettings.llm_url)return _appSettings.llm_url}catch{}return"http://localhost:11434/v1"}function rehCollectLlmEndpoints(){const seen=new Set,results=[],add=(url,label)=>{url&&(url=url.trim(),!(!url||seen.has(url))&&(seen.add(url),results.push({url,label:label||url})))};return add(rehDefaultLlmUrl(),"Active LLM"),document.querySelectorAll(".llm-local-url-inp, [data-llm-local-key]").forEach(inp=>{var _a2,_b2,_c2;const v=(_a2=inp.value)==null?void 0:_a2.trim(),def=inp.dataset.llmLocalDefault,key=inp.dataset.llmLocalKey||inp.dataset.dcUrlKey||"",card=inp.closest('[class*="llm-local-card"], [class*="llm-local"]'),name=((_c2=(_b2=card==null?void 0:card.querySelector(".llm-local-name"))==null?void 0:_b2.textContent)==null?void 0:_c2.trim())||key;add(v||def,name)}),document.querySelectorAll(".dc-url-inp").forEach(inp=>{var _a2,_b2,_c2;const card=inp.closest('[class*="llm-local-card"]');if(!card)return;const name=((_b2=(_a2=card.querySelector(".llm-local-name"))==null?void 0:_a2.textContent)==null?void 0:_b2.trim())||"";add(((_c2=inp.value)==null?void 0:_c2.trim())||inp.dataset.dcDefault,name)}),[["http://localhost:11434/v1","Ollama"],["http://localhost:8000/v1","vLLM"],["http://localhost:1234/v1","LM Studio"],["http://localhost:28080/v1","llama-swap"],["http://localhost:14000/v1","LiteLLM"]].forEach(([u,l])=>add(u,l)),results}function rehInitLlmField(){const u=$("reh-llm-url");if(!u)return;u.value||(u.value=rehDefaultLlmUrl());const dl=$("reh-llm-url-list");dl&&(dl.innerHTML=rehCollectLlmEndpoints().map(e=>``).join(""))}(_ua=$("reh-llm-refresh"))==null||_ua.addEventListener("click",async()=>{var _a2;const url=((_a2=$("reh-llm-url"))==null?void 0:_a2.value.trim())||rehDefaultLlmUrl(),sel=$("reh-llm-model");if(sel){sel.innerHTML='';try{const models=(await(await fetch("/api/conversation/llm-models?url="+encodeURIComponent(url))).json()).models||[];sel.innerHTML=''+models.map(m=>``).join("");const want=typeof _appSettings!="undefined"&&_appSettings?_appSettings.llm_model:"";want&&models.includes(want)&&(sel.value=want),toast(models.length?`Found ${models.length} models`:"No models found",models.length?"success":"error")}catch(e){sel.innerHTML='',toast("Could not list models: "+e.message,"error")}}});const REH_AVATAR_ICONS={male:"mdi-face-man",female:"mdi-face-woman",neutral:"mdi-account",robot:"mdi-robot-outline",animal:"mdi-paw"};function _pickVoiceAvatar(gender,desc,speaker){const d=((desc||"")+" "+(speaker||"")).toLowerCase();return/\b(robot|android|synthetic|artificial|computer|machine|cyborg|a\.?i\.?|operating system|\bos\b|digital|hologram|drone)\b/.test(d)?"robot":/\b(animal|creature|beast|dragon|monster|cat|dog|wolf|lion|bird|horse|dino|dinosaur|alien)\b/.test(d)?"animal":gender==="M"?"male":gender==="F"?"female":"neutral"}let rehDesignCancelled=!1;(_va=$("reh-autodesign-cancel"))==null||_va.addEventListener("click",()=>{rehDesignCancelled=!0}),(_wa=$("reh-autodesign-btn"))==null||_wa.addEventListener("click",async()=>{var _a2,_b2,_c2,_d2,_e2,_f2,_g2;if(!((_a2=$("reh-backend-select"))==null?void 0:_a2.value)){toast("Select a TTS backend first","error");return}const speakers=Object.keys(rehState.cast);if(!speakers.length){toast("No characters to design for","error");return}const script=((_b2=$("reh-script-text"))==null?void 0:_b2.value.trim())||linesToScriptText(),llmUrl=((_c2=$("reh-llm-url"))==null?void 0:_c2.value.trim())||rehDefaultLlmUrl(),llmModel=((_d2=$("reh-llm-model"))==null?void 0:_d2.value)||"",language=((_e2=$("reh-design-lang"))==null?void 0:_e2.value)||"English",langCode=REH_LANG_CODE[language]||"EN",scriptTitle=((_f2=$("reh-script-title"))==null?void 0:_f2.value.trim())||"Script",tag=scriptTitle.replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"").slice(0,24)||"Script",btn=$("reh-autodesign-btn"),prog=$("reh-autodesign-progress"),fill=$("reh-autodesign-fill"),label=$("reh-autodesign-label");btn.disabled=!0,rehDesignCancelled=!1,prog&&(prog.hidden=!1);const setProg=(d,t,msg)=>{fill&&(fill.style.width=(t?d/t*100:0)+"%"),label&&(label.textContent=msg||`${d} / ${t}`)};setProg(0,speakers.length,"Analyzing script with LLM\u2026");let characters;try{const r=await fetch("/api/analyze-characters",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({script,names:speakers,llm_url:llmUrl,model:llmModel,language})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}characters=(await r.json()).characters||[]}catch(e){toast("Character analysis failed: "+e.message,"error"),btn.disabled=!1,prog&&(prog.hidden=!0);return}const byName={};characters.forEach(c=>{c.name&&(byName[String(c.name).toUpperCase().trim()]=c)});let done=0;try{const designOnly=speakers.filter(sp=>{var _a3;return((_a3=rehState.cast[sp])==null?void 0:_a3.voice)!=="me"});for(const sp of designOnly){if(rehDesignCancelled){toast("Cancelled","error");break}if(!rehState.cast[sp]){done++;continue}const info=byName[sp.toUpperCase().trim()]||{},gender=(info.gender||"N").toUpperCase().charAt(0).replace(/[^MFN]/,"N")||"N",desc=info.description||`A ${info.age||"adult"} ${gender==="M"?"male":gender==="F"?"female":""} character named ${sp}, natural expressive voice.`,sampleLine=((_g2=rehState.lines.find(l=>l.type==="dialog"&&l.speaker===sp))==null?void 0:_g2.text)||`Hello, I am ${sp}.`,safeName=sp.replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"").slice(0,24)||"Char",voiceId=`${langCode}_${gender}_${safeName}_${tag}`.slice(0,90);rehMarkCastDesigning(sp,"designing",null,{gender,language,voiceId,desc,age:info.age||"",step:"Generating voice audio\u2026"}),setProg(done,designOnly.length,`Designing ${sp}\u2026 (${done+1}/${designOnly.length})`);try{const dr=await fetch("/api/voice-design",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({instruct:desc,sample_text:stripMarkdown(sampleLine).slice(0,300),language,gender,dialogue:!1})});if(!dr.ok){const e=await dr.json().catch(()=>({}));throw new Error(e.detail||dr.statusText)}const dd=await dr.json(),sr=await fetch("/api/save",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:dd.id,voice_id:voiceId,transcript:stripMarkdown(sampleLine).slice(0,300)})});if(!sr.ok){const e=await sr.json().catch(()=>({}));throw new Error(e.detail||sr.statusText)}const saved=await sr.json(),charName=sp===REH_NARRATOR_KEY?"Narrator":sp;typeof saveMeta=="function"&&await saveMeta(saved.voice_id,{gender,name:charName,avatar:_pickVoiceAvatar(gender,desc,sp),origin:"designed",group:`Rehearser: ${scriptTitle}`,note:`Rehearser \xB7 ${scriptTitle} \xB7 ${charName} \u2014 ${desc.slice(0,180)}`,transcript:stripMarkdown(sampleLine).slice(0,300),tag:scriptTitle}).catch(()=>{}),rehState.cast[sp].voice=saved.voice_id,rehState.cast[sp].instruct=rehState.cast[sp].instruct||desc,rehState.cast[sp].soul=rehState.cast[sp].soul||[info.age?`Age: ${info.age}`:"",desc].filter(Boolean).join(" \xB7 "),rehState.cast[sp].voiceData=null,rehState.voices.includes(saved.voice_id)||rehState.voices.push(saved.voice_id),rehMarkCastDesigning(sp,"done")}catch(e){rehMarkCastDesigning(sp,"err",e.message)}done++,setProg(done,designOnly.length)}typeof loadVoiceLibrary=="function"&&await loadVoiceLibrary().catch(()=>{}),rehDesignCancelled||toast(`Designed ${done} voice${done!==1?"s":""} \u2014 tagged "${tag}" + Rehearser`,"success")}catch(e){toast("Design all failed: "+((e==null?void 0:e.message)||e),"error")}finally{renderCastList(),populateNarratorSelect(),prog&&(prog.hidden=!0),btn.disabled=!1}});function rehWriteCharacterNote(sp,info){const c=rehState.cast[sp];if(!c||!info)return;info.gender&&!c.gender&&(c.gender=String(info.gender).toUpperCase().charAt(0).replace(/[^MFN]/,"N"));const bits=[];info.age&&bits.push(`Age: ${info.age}`),info.description&&bits.push(info.description);const note=bits.join(" \xB7 ");note&&!c.soul&&(c.soul=note),info.description&&!c.instruct&&(c.instruct=info.description)}async function rehResearchCast(speakers){var _a2,_b2,_c2,_d2;const names=speakers.filter(sp=>sp!==REH_NARRATOR_KEY);if(!names.length)return{};let chars=[];try{const r=await fetch("/api/analyze-characters",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({script:((_a2=$("reh-script-text"))==null?void 0:_a2.value.trim())||linesToScriptText(),names,llm_url:((_b2=$("reh-llm-url"))==null?void 0:_b2.value.trim())||rehDefaultLlmUrl(),model:((_c2=$("reh-llm-model"))==null?void 0:_c2.value)||"",language:((_d2=$("reh-design-lang"))==null?void 0:_d2.value)||"English"})});r.ok&&(chars=(await r.json()).characters||[])}catch{}const byName={};return chars.forEach(c=>{c.name&&(byName[String(c.name).toUpperCase().trim()]=c)}),speakers.forEach(sp=>rehWriteCharacterNote(sp,byName[sp.toUpperCase().trim()])),byName}(_xa=$("reh-matchlib-btn"))==null||_xa.addEventListener("click",async()=>{var _a2,_b2,_c2,_d2;const btn=$("reh-matchlib-btn"),speakers=Object.keys(rehState.cast).filter(sp=>rehState.cast[sp].voice!=="me");if(!speakers.length){toast("No characters to match","error");return}let lib=(window._voices||[]).filter(v=>v.enabled!==!1);if(!lib.length)try{lib=(await fetch("/api/voices").then(r=>r.json())).filter(v=>v.enabled!==!1)}catch{}if(!lib.length){toast("Your voice library is empty \u2014 clone, design or import some voices first","error");return}const candidates=lib.map(v=>({id:v.id,gender:v.gender||"",language:v.lang||"",tags:v.tag||"",description:(v.note||v.name||"").slice(0,140)})),nameFor=sp=>sp===REH_NARRATOR_KEY?"Narrator":sp,byDisplay={};speakers.forEach(sp=>{byDisplay[nameFor(sp).toUpperCase().trim()]=sp});const orig=btn.innerHTML;btn.disabled=!0,btn.innerHTML=' Matching\u2026';try{const r=await fetch("/api/match-characters-voices",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({script:((_a2=$("reh-script-text"))==null?void 0:_a2.value.trim())||linesToScriptText(),names:speakers.map(nameFor),voices:candidates,llm_url:((_b2=$("reh-llm-url"))==null?void 0:_b2.value.trim())||rehDefaultLlmUrl(),model:((_c2=$("reh-llm-model"))==null?void 0:_c2.value)||"",language:((_d2=$("reh-design-lang"))==null?void 0:_d2.value)||"English"})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const assignments=(await r.json()).assignments||[],validIds=new Set(candidates.map(c=>c.id));let n=0;assignments.forEach(a=>{const sp=byDisplay[String(a.name||"").toUpperCase().trim()];sp&&a.voice_id&&validIds.has(a.voice_id)&&(rehState.cast[sp].voice=a.voice_id,rehState.cast[sp].voiceData=getVoiceData(a.voice_id),rehState.voices.includes(a.voice_id)||rehState.voices.push(a.voice_id),sp===REH_NARRATOR_KEY&&(rehState.narratorVoice=a.voice_id),n++)}),btn.innerHTML=' Researching characters\u2026',await rehResearchCast(speakers),renderCastList(),populateNarratorSelect(),toast(n?`Matched ${n} character${n!==1?"s":""} & added notes`:"No good matches \u2014 try \u201CDesign all voices\u201D instead",n?"success":"error")}catch(e){toast("Match failed: "+e.message,"error")}finally{btn.disabled=!1,btn.innerHTML=orig}});const REH_FISH_LANG={English:"en",German:"de",French:"fr",Spanish:"es",Italian:"it",Portuguese:"pt",Dutch:"nl",Auto:""};(_ya=$("reh-matchonline-btn"))==null||_ya.addEventListener("click",async()=>{var _a2,_b2,_c2,_d2,_e2,_f2;const btn=$("reh-matchonline-btn"),speakers=Object.keys(rehState.cast).filter(sp=>rehState.cast[sp].voice!=="me"&&sp!==REH_NARRATOR_KEY);if(!speakers.length){toast("No characters to match","error");return}const lang=(_b2=REH_FISH_LANG[((_a2=$("reh-design-lang"))==null?void 0:_a2.value)||"English"])!=null?_b2:"en",prog=$("reh-autodesign-progress"),fill=$("reh-autodesign-fill"),label=$("reh-autodesign-label"),setProg=(d,t,msg)=>{fill&&(fill.style.width=(t?d/t*100:0)+"%"),label&&(label.textContent=msg||`${d} / ${t}`)},orig=btn.innerHTML;btn.disabled=!0,btn.innerHTML=' Analyzing\u2026',prog&&(prog.hidden=!1),rehDesignCancelled=!1;try{let chars=[];try{const ar=await fetch("/api/analyze-characters",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({script:((_c2=$("reh-script-text"))==null?void 0:_c2.value.trim())||linesToScriptText(),names:speakers,llm_url:((_d2=$("reh-llm-url"))==null?void 0:_d2.value.trim())||rehDefaultLlmUrl(),model:((_e2=$("reh-llm-model"))==null?void 0:_e2.value)||"",language:((_f2=$("reh-design-lang"))==null?void 0:_f2.value)||"English"})});ar.ok&&(chars=(await ar.json()).characters||[])}catch{}const byName={};chars.forEach(c=>{c.name&&(byName[String(c.name).toUpperCase().trim()]=c)});const G={M:"male",F:"female",N:"neutral"};let done=0,n=0;for(let i=0;ir.json())).items||[]}catch{}let pick=items.find(v=>v.sample_audio),nameHit=!!pick;if(!pick){const fb=[`language=${lang}`,gender?`gender=${gender}`:"","page_size=12","sort_by=score",`page=${i%4+1}`].filter(Boolean);try{items=(await fetch("/api/fishaudio/voices?"+fb.join("&")).then(r=>r.json())).items||[]}catch{}pick=items.find(v=>v.sample_audio)}let cands=items.filter(v=>v.sample_audio).slice(0,6).map(v=>({title:v.title,sample_audio:v.sample_audio,image:v.image||"",gender:v.gender||"",language:v.language||lang||"",description:v.description||"",sample_text:v.sample_text||v.default_text||""}));if(!nameHit&&cands.length>1){const taken=new Set(Object.values(rehState.cast).map(c=>{var _a3,_b3,_c3;return(_c3=(_b3=(_a3=c.online)==null?void 0:_a3.candidates)==null?void 0:_b3[c.online.picked])==null?void 0:_c3.sample_audio}).filter(Boolean));cands=[...cands.slice(i%cands.length),...cands.slice(0,i%cands.length)].sort((a,b)=>(taken.has(a.sample_audio)?1:0)-(taken.has(b.sample_audio)?1:0))}if(cands.length)try{const vid=await _rehImportFishCandidate(sp,cands[0]);cands[0].voice_id=vid,rehState.cast[sp].voice=vid,rehState.cast[sp].voiceData=getVoiceData(vid),rehState.cast[sp].online={candidates:cands,picked:0},n++}catch(e){logErr("match-online import "+sp,e)}done++,setProg(done,speakers.length)}typeof loadVoiceLibrary=="function"&&await loadVoiceLibrary().catch(()=>{}),renderCastList(),populateNarratorSelect(),toast(n?`Imported & matched ${n} online voice${n!==1?"s":""}`:"No online matches found",n?"success":"error")}catch(e){toast("Online match failed: "+e.message,"error")}finally{btn.disabled=!1,btn.innerHTML=orig,prog&&(prog.hidden=!0)}});function rehMarkCastDesigning(sp,state,msg,info){var _a2;const row=document.querySelector(`.reh-cast-row[data-speaker="${CSS.escape(sp)}"]`);if(!row)return;let badge=row.querySelector(".reh-cast-design-badge");badge||(badge=document.createElement("span"),badge.className="reh-cast-design-badge",(_a2=row.querySelector("strong"))==null||_a2.after(badge)),badge.className="reh-cast-design-badge"+(state==="done"?" done":state==="err"?" err":""),badge.textContent=state==="designing"?"\u2728 designing\u2026":state==="done"?"\u2713 designed":"\u2717 failed",msg&&(badge.title=msg),row.classList.toggle("reh-cast-designing",state==="designing");const wrap=row.closest("div");let panel=wrap==null?void 0:wrap.querySelector(".reh-cast-design-panel");if(state==="designing"&&info){panel||(panel=document.createElement("div"),panel.className="reh-cast-design-panel",wrap.appendChild(panel));const genderIcon=info.gender==="M"?"\u2642":info.gender==="F"?"\u2640":"\u26A7",genderLabel=info.gender==="M"?"Male":info.gender==="F"?"Female":"Neutral";panel.innerHTML=`
${genderIcon} ${escHtml(genderLabel)} ${escHtml(info.language||"EN")} ${escHtml(info.voiceId||sp)} ${info.age?`${escHtml(info.age)}`:""}
${escHtml(info.desc||"")}
${escHtml(info.step||"Designing voice\u2026")}
`}else if(state==="done"&&panel){const descEl=panel.querySelector(".reh-cdp-desc"),metaEl=panel.querySelector(".reh-cdp-meta"),stepEl=panel.querySelector(".reh-cdp-step");if(stepEl&&stepEl.remove(),descEl){const t=descEl.textContent;t.length>160&&(descEl.textContent=t.slice(0,157)+"\u2026")}panel.classList.add("done")}else state==="err"&&panel&&panel.remove()}(_za=$("reh-start-btn"))==null||_za.addEventListener("click",()=>{var _a2,_b2;const backend=(_a2=$("reh-backend-select"))==null?void 0:_a2.value;if(!backend){toast("Select a backend first","error");return}rehState.backend=backend,rehState.narratorVoice=((_b2=$("reh-narrator-voice"))==null?void 0:_b2.value)||"",rehState.lineIndex=0,rehState.clips=[],rehState.playing=!1,rehState.synthCache.clear(),rehDecodedBuffers.clear(),rehState.practiceStart=null,rehState.practiceEnd=null,buildScriptPage(),showPhase(3),highlightCurrentLine()});const REH_STAGE_FONT_KEY="ttsvc_reh_stage_scale",REH_CAST_COLLAPSED_KEY="ttsvc_reh_cast_collapsed";function rehStageScale(){const v=parseFloat(localStorage.getItem(REH_STAGE_FONT_KEY)||"1");return isNaN(v)?1:Math.max(.7,Math.min(2,v))}function rehApplyStageFont(){const scale=String(rehStageScale()),wrap=document.querySelector(".reh-page-wrap");wrap&&wrap.style.setProperty("--reh-stage-scale",scale);const page=$("reh-a4-page");page&&page.style.setProperty("--reh-stage-scale",scale)}function rehStageFontStep(delta){const next=Math.max(.7,Math.min(2,Math.round((rehStageScale()+delta)*100)/100));localStorage.setItem(REH_STAGE_FONT_KEY,String(next)),rehApplyStageFont()}function rehApplyCastCollapsed(){const row=$("reh-cast-row"),stage=$("reh-stage-area");if(!row)return;const collapsed=localStorage.getItem(REH_CAST_COLLAPSED_KEY)==="1";row.classList.toggle("is-collapsed",collapsed),stage&&stage.classList.toggle("side-collapsed",collapsed);const btn=$("reh-cast-toggle");if(btn){btn.setAttribute("aria-expanded",String(!collapsed)),btn.title=collapsed?"Expand character list":"Collapse to avatars";const icon=btn.querySelector(".mdi");icon&&(icon.className="mdi "+(collapsed?"mdi-chevron-right":"mdi-chevron-left"))}}function rehToggleCast(){const collapsed=localStorage.getItem(REH_CAST_COLLAPSED_KEY)==="1";localStorage.setItem(REH_CAST_COLLAPSED_KEY,collapsed?"0":"1"),rehApplyCastCollapsed()}(_Aa=$("reh-font-inc"))==null||_Aa.addEventListener("click",()=>rehStageFontStep(.1)),(_Ba=$("reh-font-dec"))==null||_Ba.addEventListener("click",()=>rehStageFontStep(-.1)),(_Ca=$("reh-cast-toggle"))==null||_Ca.addEventListener("click",rehToggleCast);function buildScriptPage(){var _a2,_b2;const titleEl=$("reh-page-title");titleEl&&(titleEl.textContent=((_a2=$("reh-script-title"))==null?void 0:_a2.value.trim())||"Script");const castStrip=$("reh-cast-strip");if(castStrip){const names=Object.keys(rehState.cast),lineCountFor=sp=>rehState.lines.filter(l=>l.speaker===sp&&l.type==="dialog").length;castStrip.innerHTML=names.map(sp=>{const c=rehState.cast[sp],isMe=c.voice==="me",displayName=sp===REH_NARRATOR_KEY?"Narrator":sp;return`
${escHtml((displayName||"?")[0].toUpperCase())} ${escHtml(displayName)} ${isMe?'':""} ${lineCountFor(sp)}
`}).join(""),castStrip.querySelectorAll(".ab-char-item").forEach(item=>{item.addEventListener("click",()=>{const line=document.querySelector(`.reh-block[data-speaker="${CSS.escape(item.dataset.speaker)}"]`);line&&line.scrollIntoView({behavior:"smooth",block:"center"})})});const lbl=$("reh-cast-toggle-label");lbl&&(lbl.textContent=`Characters (${names.length})`)}rehApplyStageFont(),rehApplyCastCollapsed();const linesEl=$("reh-script-lines");linesEl&&(linesEl.innerHTML=rehState.lines.map((line,i)=>{const isCached=rehState.synthCache.has(i),isStale=rehState.staleLines.has(i),synthDot=``,reSynthBtn=``,editBtn=``,note=rehState.lines[i].note||"",noteArea=`
${editBtn}
`;if(line.hidden&&!(rehState.bulkMode&&rehState.showHidden))return"";const _bSel=rehState.bulkSel.has(i),bulkCheck=rehState.bulkMode?``:"",lineFlags=(line.ignored?" reh-line-ignored":"")+(line.hidden?" reh-line-hidden":"")+(_bSel?" reh-selected":"");switch(line.type){case"act":return`
${bulkCheck}${escHtml(line.text)} ${editBtn} ${synthDot}
`;case"scene":return`
${bulkCheck} ${escHtml(line.text)} ${editBtn} ${synthDot}
`;case"action":return`
${bulkCheck}${renderMarkdownInline(line.text)} ${editBtn} ${synthDot}
`;case"pagebreak":{const pbLabel=line.page?`\u2014 Page ${line.page} \u2014`:"\u2014 Page break \u2014";return`
${bulkCheck}${pbLabel}
`}case"transition":return`
${bulkCheck}${escHtml(line.text)}
`;case"direction":{const indent=line.speaker?"text-align:center;":"";return`
${bulkCheck}${escHtml(line.text)}
`}case"dialog":{const c=rehState.cast[line.speaker]||{voice:"",color:"#89b4fa"},isMe=c.voice==="me",emoInfo=getEmotionInfo(line.emotion||"");return`
${bulkCheck}
${escHtml(line.speaker)} ${isMe?' Me':' TTS'} ${isMe?"":``} ${synthDot} ${reSynthBtn}
${renderMarkdownInline(line.text)}
${noteArea}
`}default:return""}}).join(""),linesEl.querySelectorAll(".reh-emo-btn").forEach(btn=>{btn.addEventListener("click",e=>{e.stopPropagation(),openEmoPicker(parseInt(btn.dataset.index),btn)})}),linesEl.querySelectorAll(".reh-edit-btn").forEach(btn=>{btn.addEventListener("click",e=>{e.stopPropagation(),startInlineEdit(parseInt(btn.dataset.index))})}),linesEl.querySelectorAll(".reh-block-dialog").forEach(el=>{el.addEventListener("dblclick",e=>{e.stopPropagation(),startInlineEdit(parseInt(el.closest("[data-index]").dataset.index))})}),linesEl.querySelectorAll(".reh-action-text").forEach(el=>{el.addEventListener("dblclick",e=>{e.stopPropagation(),startInlineEdit(parseInt(el.closest("[data-index]").dataset.index))})}),linesEl.querySelectorAll(".reh-line-play-avatar").forEach(btn=>{btn.addEventListener("click",e=>{e.stopPropagation();const idx=parseInt(btn.dataset.index);if(rehState.playing&&rehState.lineIndex===idx){pausePlay();return}stopPlay(),hideRecOverlay(),rehState.lineIndex=idx,highlightCurrentLine(),startPlay()})}),linesEl.querySelectorAll(".reh-gutter-btn").forEach(btn=>{btn.addEventListener("click",e=>{e.stopPropagation();const idx=parseInt(btn.dataset.index);e.shiftKey?rehState.practiceEnd=rehState.practiceEnd===idx?null:idx:(stopPlay(),hideRecOverlay(),rehState.lineIndex=idx,highlightCurrentLine(),startPlay()),updatePracticeRange()})}),linesEl.querySelectorAll("[data-index]").forEach(el=>{el.addEventListener("click",e=>{if(e.target.closest(".reh-emo-btn, .reh-edit-btn, .reh-gutter-btn, .reh-note-btn, .reh-resynth-btn, .reh-line-play-avatar, textarea, select, .vp-root"))return;const idx=parseInt(el.dataset.index);if(rehState.bulkMode){_toggleBulkSel(idx,e.shiftKey);return}stopPlay(),hideRecOverlay(),rehState.lineIndex=idx,highlightCurrentLine()})}),linesEl.querySelectorAll(".reh-resynth-btn").forEach(btn=>{btn.addEventListener("click",e=>{e.stopPropagation(),synthOneLine(parseInt(btn.id.replace("reh-rsb-","")))})}),linesEl.querySelectorAll(".reh-note-area").forEach(area=>{const idx=parseInt(area.dataset.index),btn=area.querySelector(".reh-note-btn"),ta=area.querySelector(".reh-note-ta");!btn||!ta||(ta.value.trim()&&ta.classList.add("open"),btn.addEventListener("click",e=>{e.stopPropagation(),ta.classList.toggle("open"),ta.classList.contains("open")&&ta.focus()}),ta.addEventListener("input",()=>{rehState.lines[idx].note=ta.value,btn.classList.toggle("has-note",!!ta.value.trim())}),ta.addEventListener("click",e=>e.stopPropagation()))}),linesEl.classList.toggle("reh-bulk-mode",rehState.bulkMode),(_b2=document.querySelector(".reh-page-wrap"))==null||_b2.classList.toggle("reh-bulk-mode",rehState.bulkMode),rehState.bulkMode&&linesEl.querySelectorAll(".reh-bulk-check").forEach(cb=>{cb.addEventListener("click",e=>{e.stopPropagation(),_toggleBulkSel(parseInt(cb.dataset.bulk),e.shiftKey)})}),applyPageMode(),_checkToneStyleSupport())}function _toggleBulkSel(i,range=!1){if(Number.isFinite(i)){if(range&&rehState.bulkAnchor!==null&&Number.isFinite(rehState.bulkAnchor)){const a=Math.min(rehState.bulkAnchor,i),b=Math.max(rehState.bulkAnchor,i),indices=[];for(let n=a;n<=b;n++)rehState.lines[n]&&!rehState.lines[n].hidden&&indices.push(n);const deselect=indices.length&&indices.every(n=>rehState.bulkSel.has(n));indices.forEach(n=>{deselect?rehState.bulkSel.delete(n):rehState.bulkSel.add(n),_refreshBulkLine(n)}),rehState.bulkAnchor=i}else rehState.bulkSel.has(i)?rehState.bulkSel.delete(i):rehState.bulkSel.add(i),rehState.bulkAnchor=i,_refreshBulkLine(i);_updateBulkCount()}}function _refreshBulkLine(i){const sel=rehState.bulkSel.has(i);document.querySelectorAll(`.reh-bulk-check[data-bulk="${i}"]`).forEach(cb=>{cb.classList.toggle("checked",sel);const icon=cb.querySelector(".mdi");icon&&(icon.className="mdi "+(sel?"mdi-checkbox-marked":"mdi-checkbox-blank-outline"))}),document.querySelectorAll(`[data-index="${i}"]`).forEach(el=>el.classList.toggle("reh-selected",sel))}function _updateBulkCount(){const el=$("reh-bulk-count");el&&(el.textContent=`${rehState.bulkSel.size} selected`)}function setBulkMode(on){rehState.bulkMode=on,on||(rehState.bulkSel.clear(),rehState.bulkAnchor=null);const bar=$("reh-bulk-bar");bar&&(bar.hidden=!on);const btn=$("reh-bulk-toggle");btn&&btn.classList.toggle("active",on),_updateBulkCount(),rehState.lines.length&&buildScriptPage()}function _bulkApply(fn,{keepSelection=!1}={}){if(!rehState.bulkSel.size){toast("No lines selected","error");return}[...rehState.bulkSel].forEach(i=>{const l=rehState.lines[i];l&&fn(l,i)}),keepSelection||rehState.bulkSel.clear(),_updateBulkCount(),buildScriptPage()}function _bulkDelete(){if(!rehState.bulkSel.size){toast("No lines selected","error");return}const victims=[...rehState.bulkSel].sort((a,b)=>b-a);victims.forEach(i=>{rehState.lines.splice(i,1),_reindexLineState(i)}),rehState.bulkSel.clear(),rehState.lineIndex>=rehState.lines.length&&(rehState.lineIndex=Math.max(0,rehState.lines.length-1)),_updateBulkCount(),buildScriptPage(),highlightCurrentLine(),toast(`Deleted ${victims.length} line${victims.length!==1?"s":""}`,"success")}function _reindexLineState(d){const shift=(collection,isMap)=>{const out=isMap?new Map:new Set;for(const entry of collection){const k=isMap?entry[0]:entry;if(k===d)continue;const nk=k>d?k-1:k;isMap?out.set(nk,entry[1]):out.add(nk)}return out};rehState.synthCache=shift(rehState.synthCache,!0),rehState.staleLines=shift(rehState.staleLines,!1);const sel=shift(rehState.bulkSel,!1);rehState.bulkSel.clear(),sel.forEach(v=>rehState.bulkSel.add(v)),rehState.practiceStart!=null&&rehState.practiceStart>d&&rehState.practiceStart--,rehState.practiceEnd!=null&&rehState.practiceEnd>d&&rehState.practiceEnd--,rehState.lineIndex>d&&rehState.lineIndex--}const PAGE_MODES=["auto","scroll","pdf"];let _pageMode=localStorage.getItem("reh-page-mode")||"auto";function applyPageMode(){const wrap=document.querySelector(".reh-page-wrap"),a4=$("reh-a4-page");wrap&&(_pageMode==="scroll"?(wrap.querySelectorAll(".reh-paper").forEach(p=>{[...p.children].forEach(c=>{c.classList.contains("reh-paper-num")||a4==null||a4.appendChild(c)}),p.remove()}),wrap.classList.remove("paginated"),a4&&(a4.style.display=""),document.querySelectorAll(".reh-block-pagebreak").forEach(el=>el.style.display="none")):paginateScript(_pageMode==="pdf"?{respectBreaks:!0}:{respectBreaks:!1}),_syncPageModeBtn())}function _syncPageModeBtn(){const btn=$("reh-page-mode-btn");if(!btn)return;const icons={auto:"mdi-file-document-outline",scroll:"mdi-format-align-justify",pdf:"mdi-book-open-page-variant"},labels={auto:"A4 pages",scroll:"Scroll",pdf:"PDF pages"};btn.innerHTML=` ${labels[_pageMode]||labels.auto}`,btn.title="Switch view: "+labels[_pageMode]}function cyclePageMode(){const idx=PAGE_MODES.indexOf(_pageMode);_pageMode=PAGE_MODES[(idx+1)%PAGE_MODES.length],localStorage.setItem("reh-page-mode",_pageMode),applyPageMode(),rehState.lines.length&&buildScriptPage()}function paginateScript({respectBreaks=!1}={}){const wrap=document.querySelector(".reh-page-wrap"),linesEl=$("reh-script-lines"),titleEl=$("reh-page-title");if(!wrap||!linesEl)return;const blocks=[...linesEl.children];if(!blocks.length)return;wrap.classList.add("paginated");const a4=$("reh-a4-page");a4&&(a4.style.display="none"),wrap.querySelectorAll(".reh-paper").forEach(p=>p.remove());const PAGE_CONTENT=1027,TITLE_SPACE=64;let pageNum=0,page=null,used=0;const newPage=()=>{pageNum++,page=document.createElement("div"),page.className="reh-paper";const num=document.createElement("div");if(num.className="reh-paper-num",num.textContent=pageNum+".",page.appendChild(num),used=0,pageNum===1&&titleEl){const t=titleEl.cloneNode(!0);t.style.display="",page.appendChild(t),used+=TITLE_SPACE}wrap.appendChild(page)};newPage();for(const block of blocks){if(respectBreaks&&block.classList.contains("reh-block-pagebreak")){newPage();continue}page.appendChild(block);const cs=getComputedStyle(block),h=block.offsetHeight+(parseFloat(cs.marginTop)||0)+(parseFloat(cs.marginBottom)||0);!respectBreaks&&used+h>PAGE_CONTENT&&used>(pageNum===1?TITLE_SPACE:0)&&(newPage(),page.appendChild(block)),used+=h}}function isPracticeRange(idx){const s=rehState.practiceStart,e=rehState.practiceEnd;return s===null?!1:idx>=s&&(e===null||idx<=e)}function startInlineEdit(idx){const line=rehState.lines[idx];if(!line)return;const dialogEl=document.getElementById("reh-diag-"+idx),actionEl=document.querySelector(`[data-index="${idx}"] .reh-action-text`),sceneEl=document.querySelector(`.reh-scene[data-index="${idx}"] span[style*="flex:1"]`),targetEl=dialogEl||actionEl||sceneEl;if(!targetEl||targetEl.tagName==="TEXTAREA")return;const orig=line.text,ta=document.createElement("textarea");ta.value=orig,ta.style.cssText="width:100%;min-height:54px;font-size:14px;line-height:1.6;padding:5px 8px;border:1px solid var(--accent);border-top:none;border-radius:0 0 3px 3px;resize:vertical;background:#fffff8;font-family:inherit;display:block;box-sizing:border-box;outline:none;";const toolbar=document.createElement("div");toolbar.className="reh-fmt-toolbar",toolbar.innerHTML=` Ctrl+Enter save \xB7 Esc cancel `,toolbar.querySelectorAll(".reh-fmt-btn").forEach(btn=>{btn.addEventListener("mousedown",e=>{e.preventDefault();const wrap=btn.dataset.b,s=ta.selectionStart,end=ta.selectionEnd,sel=ta.value.slice(s,end)||"text";ta.value=ta.value.slice(0,s)+wrap+sel+wrap+ta.value.slice(end),ta.setSelectionRange(s+wrap.length,s+wrap.length+sel.length),ta.focus()})}),targetEl.style.display="none",targetEl.before(toolbar),toolbar.after(ta),ta.focus(),ta.select();const save=()=>{const newText=ta.value.trim()||orig;line.text=newText,targetEl.innerHTML=renderMarkdownInline(newText),targetEl.style.display="",toolbar.remove(),ta.remove(),rehState.synthCache.delete(idx);const dot=document.getElementById("reh-syd-"+idx);dot&&(dot.style.display="none")};ta.addEventListener("keydown",e=>{e.key==="Escape"&&(ta.value=orig,save()),e.key==="Enter"&&(e.ctrlKey||e.metaKey)&&(e.preventDefault(),save())}),ta.addEventListener("blur",save)}function openScriptEditorModal(){var _a2,_b2,_c2;const currentTitle=((_a2=$("reh-page-title"))==null?void 0:_a2.textContent)||((_b2=$("reh-script-title"))==null?void 0:_b2.value)||"Script",currentScript=linesToScriptText()||((_c2=$("reh-script-text"))==null?void 0:_c2.value)||"",modal=document.createElement("div");modal.className="reh-modal-overlay",modal.innerHTML=`

Edit Script

`,document.body.appendChild(modal);const close=()=>modal.remove();modal.querySelector("#_reh-mc").onclick=close,modal.querySelector("#_reh-mcan").onclick=close,modal.addEventListener("click",e=>{e.target===modal&&close()}),modal.querySelector("#_reh-mapp").onclick=()=>{const newTitle=modal.querySelector("#_reh-mt").value.trim()||"Script",newScript=modal.querySelector("#_reh-ms").value,newLines=parseScript(newScript);if(!newLines.filter(l=>l.type==="dialog").length){toast("No dialog found in the edited script","error");return}newLines.forEach(nl=>{if(nl.type!=="dialog")return;const old=rehState.lines.find(ol=>ol.type==="dialog"&&ol.speaker===nl.speaker&&ol.text===nl.text);old&&(nl.emotion=old.emotion)});const detected=detectCharacters(newLines);Object.entries(detected).forEach(([sp])=>{rehState.cast[sp]&&(detected[sp]={...detected[sp],...rehState.cast[sp]})}),rehState.lines=newLines,rehState.cast=detected,rehState.lineIndex=Math.min(rehState.lineIndex,newLines.length-1),rehState.synthCache.clear(),rehDecodedBuffers.clear(),rehState.practiceStart=null,rehState.practiceEnd=null,$("reh-script-title")&&($("reh-script-title").value=newTitle),$("reh-page-title")&&($("reh-page-title").textContent=newTitle),buildScriptPage(),highlightCurrentLine(),close(),toast("Script updated","success")}}function updatePracticeRange(){const start=rehState.practiceStart,end=rehState.practiceEnd;document.querySelectorAll(".reh-block").forEach(el=>{const idx=parseInt(el.dataset.index);el.classList.toggle("reh-in-range",start!==null&&idx>=start&&(end===null||idx<=end)),el.classList.toggle("reh-range-start",start!==null&&idx===start),el.classList.toggle("reh-range-end",end!==null&&idx===end);const gb=el.querySelector(".reh-gutter-btn");gb&&(gb.textContent=idx===start?"\u25B6":idx===end?"\u25A0":"")});const info=$("reh-practice-info");if(info){const show=end!==null;if(info.classList.toggle("visible",show),info.hidden=!show,show){const f=$("reh-practice-from"),t=$("reh-practice-to");f&&(f.textContent=(start!==null?start:0)+1),t&&(t.textContent=end+1)}}}function clearPracticeRange(){rehState.practiceStart=null,rehState.practiceEnd=null,updatePracticeRange()}function setPracticeRangeFromSelection(){const picked=[...rehState.bulkSel].sort((a,b)=>a-b);if(!picked.length){toast("Select the lines you want to rehearse first","error");return}rehState.practiceStart=picked[0],rehState.practiceEnd=picked[picked.length-1],rehState.lineIndex=rehState.practiceStart,setBulkMode(!1),updatePracticeRange(),highlightCurrentLine(),toast(`Practice range set: lines ${rehState.practiceStart+1}\u2013${rehState.practiceEnd+1}`,"success")}(_Da=$("reh-practice-clear"))==null||_Da.addEventListener("click",clearPracticeRange),(_Ea=$("reh-bulk-range"))==null||_Ea.addEventListener("click",setPracticeRangeFromSelection);let rehEmoPicker=null;function openEmoPicker(idx,anchorBtn){closeEmoPicker();const line=rehState.lines[idx],allEmos=[...REH_EMOTIONS,...rehCustomEmotions],rect=anchorBtn.getBoundingClientRect(),left=Math.min(rect.left,window.innerWidth-265),topBelow=rect.bottom+4,topAbove=rect.top-4,top=window.innerHeight-topBelow>=240?topBelow:topAbove-260,pop=document.createElement("div");pop.className="reh-emo-popover",pop.style.cssText=`position:fixed;z-index:1000;top:${top}px;left:${left}px;width:258px;`,pop.innerHTML=`
`,document.body.appendChild(pop),rehEmoPicker=pop;const searchEl=pop.querySelector(".reh-emo-search"),listEl=pop.querySelector(".reh-emo-list"),applyBtn=pop.querySelector("#_emo-custom-apply");function renderList(q=""){const lq=q.toLowerCase(),filtered=allEmos.filter(e=>!q||e.label.toLowerCase().includes(lq)||e.value.toLowerCase().includes(lq));listEl.innerHTML=filtered.map(e=>`
${e.emoji} ${escHtml(e.label)} ${line.emotion===e.value?'':""}
`).join(""),listEl.querySelectorAll(".reh-emo-item").forEach(item=>{item.addEventListener("mousedown",e=>{e.preventDefault(),selectEmotion(idx,item.dataset.value,anchorBtn),closeEmoPicker()})})}searchEl.addEventListener("input",()=>renderList(searchEl.value)),searchEl.addEventListener("keydown",e=>{if(e.key==="Escape"&&closeEmoPicker(),e.key==="Enter"){const val=searchEl.value.trim();val&&(selectEmotion(idx,val,anchorBtn),closeEmoPicker())}}),applyBtn.addEventListener("mousedown",e=>{e.preventDefault();const val=searchEl.value.trim();if(!val)return;if(![...REH_EMOTIONS,...rehCustomEmotions].find(e2=>e2.value===val)){rehCustomEmotions.push({emoji:"\u2728",label:val,value:val,custom:!0});try{localStorage.setItem("reh-custom-emotions",JSON.stringify(rehCustomEmotions))}catch{}}selectEmotion(idx,val,anchorBtn),closeEmoPicker()}),renderList(),searchEl.focus(),setTimeout(()=>document.addEventListener("mousedown",_closePickerOnOutside),50)}function _closePickerOnOutside(e){rehEmoPicker&&!rehEmoPicker.contains(e.target)&&closeEmoPicker()}function closeEmoPicker(){rehEmoPicker&&(rehEmoPicker.remove(),rehEmoPicker=null),document.removeEventListener("mousedown",_closePickerOnOutside)}function _markSynthDot(idx,state){const dot=document.getElementById("reh-syd-"+idx);if(!dot)return;dot.className="reh-synth-dot"+(state==="stale"?" stale":"")+(state==="synthesizing"?" synthesizing":""),dot.style.display=state?"":"none",dot.title=state==="stale"?"Tone changed \u2014 needs re-synthesis":state==="synthesizing"?"Synthesizing\u2026":"Pre-synthesized";const block=dot.closest("[data-index]");block&&block.classList.toggle("reh-line-synthesizing",state==="synthesizing")}function _showReSynthBtn(idx,show){const btn=document.getElementById("reh-rsb-"+idx);btn&&(btn.hidden=!show)}async function synthOneLine(idx){const line=rehState.lines[idx];if(!line||line.type!=="dialog")return;const c=rehState.cast[line.speaker];if(!c||!c.voice||c.voice==="me")return;const instruct=_buildInstruct(c.instruct,line.emotion);_showReSynthBtn(idx,!1),_markSynthDot(idx,"synthesizing");try{const blob=await fetchTtsPreviewBlob(c.voice,_rehInlineTone(stripMarkdown(line.text),line.emotion),"wav",instruct,rehState.backend);rehState.synthCache.set(idx,blob),rehState.staleLines.delete(idx),preDecodeBlob(idx,blob),_markSynthDot(idx,"ok"),toast("Re-synthesized line "+(idx+1),"success")}catch(e){_markSynthDot(idx,null),_showReSynthBtn(idx,!0),toast("Synthesis failed: "+e.message,"error")}}function selectEmotion(idx,value,anchorBtn){rehState.lines[idx].emotion=value,rehState.synthCache.has(idx)&&(rehState.synthCache.delete(idx),rehState.staleLines.add(idx),_markSynthDot(idx,"stale"),_updateStaleBatchBtn()),_showReSynthBtn(idx,!0);const info=getEmotionInfo(value);anchorBtn.className="reh-emo-btn"+(value?" has-emotion":""),anchorBtn.innerHTML=`${info.emoji?info.emoji+" ":""}${escHtml(info.label)} `,value&&_checkToneStyleSupport()}function _checkToneStyleSupport(){const warn=$("reh-tone-warn");if(!warn)return;const txtEl=$("reh-tone-warn-txt"),b=typeof backendById=="function"?backendById(rehState.backend):null;if(!b){warn.hidden=!0;return}const all=typeof availableTtsBackends=="function"?availableTtsBackends():[],hasTone=rehState.lines.some(l=>l.type==="dialog"&&l.emotion);if(_rehBackendIsFish())txtEl&&(txtEl.innerHTML=`${escHtml(b.label)} keeps each character\u2019s voice consistent and applies tone. Per-line tones are sent as inline [tags] (e.g. [whisper], [excited], [laughing]). You can also type a custom tone like [professional broadcast tone] \u2014 S2 supports free-form descriptions. Fish-Speech S2 \u2197`),warn.hidden=!1;else if(!b.style_aware&&hasTone){const styleAware=all.find(x=>x.style_aware),suggest=styleAware?` Switch to ${escHtml(styleAware.label)} for reliable tone \u2014 but expect each voice to drift between lines.`:"";txtEl&&(txtEl.innerHTML=`${escHtml(b.label)} keeps each character\u2019s voice consistent but has weak tone control \u2014 tone picks may have little effect.${suggest}`),warn.hidden=!1}else if(b.style_aware&&!b.uses_wav){const wavBackend=all.find(x=>x.uses_wav),suggest=wavBackend?` Switch to ${escHtml(wavBackend.label)} to keep each character\u2019s voice identical throughout.`:"",qwenHint=/qwen|voice design|custom/i.test((b.id||"")+" "+(b.label||""))?" Qwen3TTS tone is sent as the per-line style/instruct text, so this is the right path for directed delivery.":"";txtEl&&(txtEl.innerHTML=`${escHtml(b.label)} gives strong tone but re-generates a fresh voice each line, so a character won\u2019t sound the same throughout.${qwenHint}${suggest}`),warn.hidden=!1}else warn.hidden=!0}(_Fa=$("reh-tone-warn-close"))==null||_Fa.addEventListener("click",()=>{const w=$("reh-tone-warn");w&&(w.hidden=!0)}),(_Ga=$("reh-tb-play"))==null||_Ga.addEventListener("click",()=>{rehState.playing?pausePlay():startPlay()}),(_Ha=$("reh-tb-stop"))==null||_Ha.addEventListener("click",()=>{var _a2;stopPlay(),rehState.lineIndex=(_a2=rehState.practiceStart)!=null?_a2:0,highlightCurrentLine(),hideRecOverlay()}),(_Ia=$("reh-tb-prev"))==null||_Ia.addEventListener("click",()=>{stopPlay(),rehState.lineIndex=Math.max(0,rehState.lineIndex-1),highlightCurrentLine(),hideRecOverlay()}),(_Ja=$("reh-tb-next"))==null||_Ja.addEventListener("click",()=>{stopPlay(),rehState.lineIndex=Math.min(rehState.lines.length-1,rehState.lineIndex+1),highlightCurrentLine(),hideRecOverlay()}),(_Ka=$("reh-tb-repeat"))==null||_Ka.addEventListener("click",()=>{var _a2;rehState.repeat=!rehState.repeat,(_a2=$("reh-tb-repeat"))==null||_a2.classList.toggle("reh-btn-active",rehState.repeat)}),(_La=$("reh-skip-desc-toggle"))==null||_La.addEventListener("change",function(){rehState.skipDescriptions=this.checked}),(_Ma=$("reh-edit-script-btn"))==null||_Ma.addEventListener("click",openScriptEditorModal),(_Na=$("reh-fountain-btn"))==null||_Na.addEventListener("click",exportFountain),(_Oa=$("reh-fountain-export-p4"))==null||_Oa.addEventListener("click",exportFountain),(_Pa=$("reh-fdx-export-btn"))==null||_Pa.addEventListener("click",exportFDX),(_Qa=$("reh-osf-export-btn"))==null||_Qa.addEventListener("click",exportOSF),(_Ra=$("reh-exit-btn"))==null||_Ra.addEventListener("click",()=>{stopPlay(),stopRehMic(),rehState.clips.length?(renderSummary(),showPhase(4)):showPhase(2)}),(_Sa=$("reh-page-title"))==null||_Sa.addEventListener("dblclick",function(){this.contentEditable="true",this.style.outline="2px solid var(--accent)",this.style.borderRadius="3px",this.focus();const range=document.createRange();range.selectNodeContents(this),window.getSelection().removeAllRanges(),window.getSelection().addRange(range)}),(_Ta=$("reh-page-title"))==null||_Ta.addEventListener("blur",function(){if(this.contentEditable==="true"){this.contentEditable="false",this.style.outline="";const v=this.textContent.trim()||"Script";this.textContent=v,$("reh-script-title")&&($("reh-script-title").value=v)}}),(_Ua=$("reh-page-title"))==null||_Ua.addEventListener("keydown",function(e){var _a2;e.key==="Enter"&&(e.preventDefault(),this.blur()),e.key==="Escape"&&(this.textContent=((_a2=$("reh-script-title"))==null?void 0:_a2.value)||"Script",this.blur())});let _rehPlayCtx=null;const rehDecodedBuffers=new Map;let rehCurrentSource=null,rehWordHighlightRaf=null;function rehPlayCtx(){return _rehPlayCtx||(_rehPlayCtx=new(window.AudioContext||window.webkitAudioContext)),_rehPlayCtx.state==="suspended"&&_rehPlayCtx.resume().catch(()=>{}),_rehPlayCtx}const REH_DECODE_WINDOW=8;function _rehEvictDecoded(keepIdx){if(rehDecodedBuffers.size<=REH_DECODE_WINDOW*2+4)return;const lo=keepIdx-REH_DECODE_WINDOW,hi=keepIdx+REH_DECODE_WINDOW;for(const k of rehDecodedBuffers.keys())(khi)&&rehDecodedBuffers.delete(k)}async function preDecodeBlob(lineIdx,blob){if(!rehDecodedBuffers.has(lineIdx))try{const ab=await blob.arrayBuffer(),buf=await rehPlayCtx().decodeAudioData(ab);rehDecodedBuffers.set(lineIdx,buf),_rehEvictDecoded(lineIdx)}catch{}}function computeWordTimings(text,durationSec){const words=stripMarkdown(text).split(/\s+/).filter(Boolean);if(words.length<2)return[];const totalChars=words.reduce((s,w)=>s+w.length,0)||1;let t=0;return words.map(w=>{const start=t;return t+=w.length/totalChars*durationSec,{word:w,start,end:t}})}function stopAudioSource(){if(rehCurrentSource){try{rehCurrentSource.stop(0)}catch{}rehCurrentSource=null}rehWordHighlightRaf&&(cancelAnimationFrame(rehWordHighlightRaf),rehWordHighlightRaf=null)}async function playPreDecoded(lineIdx,blob,text){rehDecodedBuffers.has(lineIdx)||await preDecodeBlob(lineIdx,blob),_rehEvictDecoded(lineIdx);const buf=rehDecodedBuffers.get(lineIdx);if(!buf){await playAudioBlobFallback(blob);return}const timings=computeWordTimings(text,buf.duration),dialogEl=document.getElementById("reh-diag-"+lineIdx);return dialogEl&&timings.length>=2&&(dialogEl.innerHTML=timings.map((t,i)=>`${escHtml(t.word)}`).join(" ")),new Promise(resolve=>{stopAudioSource();const ctx=rehPlayCtx(),src=ctx.createBufferSource();src.buffer=buf,src.connect(ctx.destination),rehCurrentSource=src;const t0=ctx.currentTime;src.onended=()=>{rehCurrentSource=null,rehWordHighlightRaf&&(cancelAnimationFrame(rehWordHighlightRaf),rehWordHighlightRaf=null),dialogEl&&timings.length>=2&&(dialogEl.innerHTML=renderMarkdownInline(text)),resolve()},src.start(0);const nextI=findNextCachedLine(lineIdx+1);if(nextI>=0&&preDecodeBlob(nextI,rehState.synthCache.get(nextI)),dialogEl&&timings.length>=2){const tick=()=>{if(rehCurrentSource!==src)return;const elapsed=ctx.currentTime-t0;let active=0;for(let i=timings.length-1;i>=0;i--)if(elapsed>=timings[i].start){active=i;break}dialogEl.querySelectorAll(".reh-word").forEach((span,i)=>{span.classList.toggle("reh-word-active",i===active)}),rehWordHighlightRaf=requestAnimationFrame(tick)};rehWordHighlightRaf=requestAnimationFrame(tick)}})}function findNextCachedLine(fromIdx){for(let i=fromIdx;i{audio.addEventListener("canplay",r,{once:!0}),setTimeout(r,3e3)}),await audio.play().catch(()=>{}),await waitForAudioEnd(audio))}function startPlay(){rehPlayCtx(),_ensureNarrator(),rehState.playing=!0,updatePlayBtn(),playNextLine()}function pausePlay(){rehState.playing=!1,updatePlayBtn(),stopAudioSource();const audio=$("reh-tts-audio");audio&&!audio.paused&&audio.pause(),hideStatusBar()}function stopPlay(){rehState.playing=!1,updatePlayBtn(),stopAudioSource();const audio=$("reh-tts-audio");audio&&(audio.pause(),audio.src=""),hideStatusBar()}function hideStatusBar(){const bar=$("reh-tts-status-bar");bar&&(bar.hidden=!0)}async function playNextLine(){var _a2,_b2,_c2;if(!rehState.playing)return;if(rehState.practiceEnd!==null&&rehState.lineIndex>rehState.practiceEnd){rehState.playing=!1,updatePlayBtn(),rehState.repeat?(rehState.lineIndex=(_a2=rehState.practiceStart)!=null?_a2:0,startPlay()):(rehState.lineIndex=(_b2=rehState.practiceStart)!=null?_b2:0,highlightCurrentLine());return}if(((_c2=rehState.lines[rehState.lineIndex])==null?void 0:_c2.type)==="pagebreak")return rehState.lineIndex++,playNextLine();const _cur=rehState.lines[rehState.lineIndex];if(_cur&&(_cur.ignored||_cur.hidden))return rehState.lineIndex++,playNextLine();if(rehState.skipDescriptions&&!rehState.narratorVoice)for(;rehState.lineIndexrehState.practiceEnd);)rehState.lineIndex++;if(rehState.lineIndex>=rehState.lines.length){if(rehState.playing=!1,updatePlayBtn(),rehState.repeat){rehState.lineIndex=0,startPlay();return}toast("Script finished","success");return}const line=rehState.lines[rehState.lineIndex];if(highlightCurrentLine(),line.type!=="dialog"){const hasText=!!(line.text||"").trim();if(rehState.narratorVoice&&hasText){showStatusBar("Narrator: "+line.text.slice(0,50)+(line.text.length>50?"\u2026":""));const cached2=rehState.synthCache.get(rehState.lineIndex);if(cached2)await playPreDecoded(rehState.lineIndex,cached2,line.text);else try{const blob=await fetchTtsPreviewBlob(rehState.narratorVoice,stripMarkdown(line.text),"wav","",rehState.backend);if(!rehState.playing)return;rehState.synthCache.set(rehState.lineIndex,blob),_markSynthDot(rehState.lineIndex,"ok"),await playPreDecoded(rehState.lineIndex,blob,line.text)}catch{await new Promise(r=>setTimeout(r,200))}}else rehState.skipDescriptions||await new Promise(r=>setTimeout(r,line.type==="direction"?150:250));if(!rehState.playing)return;rehState.lineIndex++,playNextLine();return}const cast=rehState.cast[line.speaker]||{voice:""};if(cast.voice==="me"){rehState.playing=!1,updatePlayBtn(),showRecOverlay(line);return}if(!cast.voice){if(showStatusBar(line.speaker+" has no voice \u2014 skipping\u2026"),await new Promise(r=>setTimeout(r,350)),!rehState.playing)return;rehState.lineIndex++,playNextLine();return}const profile=(cast.instruct||"").trim(),instruct=_buildInstruct(profile,line.emotion),cleanTxt=stripMarkdown(line.text),cached=rehState.synthCache.get(rehState.lineIndex);if(cached)showStatusBar(line.speaker+" is speaking\u2026"),await playPreDecoded(rehState.lineIndex,cached,line.text),rehState.clips.push({lineIndex:rehState.lineIndex,speaker:line.speaker,type:"tts",blob:cached});else{showStatusBar("Synthesizing\u2026");try{const blob=await fetchTtsPreviewBlob(cast.voice,_rehInlineTone(cleanTxt,line.emotion),"wav",instruct,rehState.backend);if(!rehState.playing)return;rehState.synthCache.set(rehState.lineIndex,blob),showStatusBar(line.speaker+" is speaking\u2026"),await playPreDecoded(rehState.lineIndex,blob,line.text),rehState.clips.push({lineIndex:rehState.lineIndex,speaker:line.speaker,type:"tts",blob})}catch(e){showStatusBar("TTS failed: "+e.message),await new Promise(r=>setTimeout(r,1e3))}}rehState.playing&&(rehState.lineIndex++,playNextLine())}function waitForAudioEnd(audio){return new Promise(resolve=>{if(!audio||audio.paused||audio.ended){resolve();return}audio.addEventListener("ended",resolve,{once:!0}),audio.addEventListener("pause",resolve,{once:!0}),audio.addEventListener("error",resolve,{once:!0})})}function showStatusBar(msg){const bar=$("reh-tts-status-bar");if(!bar)return;bar.hidden=!1;const txt=$("reh-tts-status-txt");txt&&(txt.textContent=msg)}function highlightCurrentLine(){const i=rehState.lineIndex,total=rehState.lines.length,prog=$("reh-tb-progress");prog&&(prog.style.width=(total?i/total*100:0)+"%");const lbl=$("reh-tb-label");lbl&&(lbl.textContent=`${i+1} / ${total}`),document.querySelectorAll("[data-index]").forEach(el=>{el.classList.toggle("reh-line-active",parseInt(el.dataset.index)===i)});const active=document.querySelector(`[data-index="${i}"]`);active&&active.scrollIntoView({behavior:"smooth",block:"center"}),updatePlayBtn()}function updatePlayBtn(){const btn=$("reh-tb-play");btn&&(btn.innerHTML=rehState.playing?'':'',btn.title=rehState.playing?"Pause":"Play all")}async function synthAll(){var _a2;if(rehState.synthRunning)return;if(!rehState.backend){toast("Select a TTS backend first","error");return}_ensureNarrator();const ttsLines=rehState.lines.map((l,i)=>({line:l,idx:i})).filter(({line})=>{if(line.ignored||line.hidden)return!1;if(line.type==="dialog"){const c=rehState.cast[line.speaker];return c&&c.voice&&c.voice!=="me"}return!!(rehState.narratorVoice&&(line.text||"").trim())});if(!ttsLines.length){toast("No TTS lines to synthesize","error");return}rehState.synthRunning=!0,rehState.synthCancelled=!1;const synthBar=$("reh-synth-bar"),fill=$("reh-synth-fill"),label=$("reh-synth-label");synthBar&&(synthBar.hidden=!1);const prog=(d,t)=>{fill&&(fill.style.width=(t?d/t*100:0)+"%"),label&&(label.textContent=`${d} / ${t} synthesized`)};prog(0,ttsLines.length);let done=0;for(const{line,idx}of ttsLines){if(rehState.synthCancelled)break;let voice,instruct;if(line.type==="dialog"){const c=rehState.cast[line.speaker];voice=c.voice,instruct=_buildInstruct(c.instruct,line.emotion)}else voice=rehState.narratorVoice,instruct="";_markSynthDot(idx,"synthesizing"),(_a2=document.getElementById("reh-syd-"+idx))==null||_a2.scrollIntoView({behavior:"smooth",block:"nearest"});try{const blob=await fetchTtsPreviewBlob(voice,_rehInlineTone(stripMarkdown(line.text),line.emotion),"wav",instruct,rehState.backend);rehState.synthCache.set(idx,blob),rehState.staleLines.delete(idx),_markSynthDot(idx,"ok"),_showReSynthBtn(idx,!1),preDecodeBlob(idx,blob)}catch{_markSynthDot(idx,null)}prog(++done,ttsLines.length)}synthBar&&(synthBar.hidden=!0),rehState.synthRunning=!1,rehState.synthCancelled||toast(`Pre-synthesized ${done} of ${ttsLines.length} lines \u2014 ready for instant playback`,"success")}function _updateStaleBatchBtn(){const btn=$("reh-tb-resynth-stale");btn&&(btn.hidden=rehState.staleLines.size===0)}(_Va=$("reh-tb-synth-all"))==null||_Va.addEventListener("click",()=>synthAll()),(_Wa=$("reh-tb-resynth-stale"))==null||_Wa.addEventListener("click",async()=>{if(rehState.synthRunning)return;const stale=[...rehState.staleLines];if(!stale.length)return;rehState.synthRunning=!0,rehState.synthCancelled=!1;const synthBar=$("reh-synth-bar"),fill=$("reh-synth-fill"),label=$("reh-synth-label");synthBar&&(synthBar.hidden=!1);let done=0;for(const idx of stale){if(rehState.synthCancelled)break;const line=rehState.lines[idx];if(!line||line.type!=="dialog"){rehState.staleLines.delete(idx);continue}const c=rehState.cast[line.speaker];if(!c||!c.voice||c.voice==="me"){rehState.staleLines.delete(idx);continue}const instruct=[c.instruct||"",line.emotion||""].filter(Boolean).join(". ");_markSynthDot(idx,"synthesizing");try{const blob=await fetchTtsPreviewBlob(c.voice,_rehInlineTone(stripMarkdown(line.text),line.emotion),"wav",instruct,rehState.backend);rehState.synthCache.set(idx,blob),rehState.staleLines.delete(idx),preDecodeBlob(idx,blob),_markSynthDot(idx,"ok"),_showReSynthBtn(idx,!1)}catch{_markSynthDot(idx,"stale")}fill&&(fill.style.width=++done/stale.length*100+"%"),label&&(label.textContent=`${done} / ${stale.length} synthesized`)}synthBar&&(synthBar.hidden=!0),rehState.synthRunning=!1,_updateStaleBatchBtn(),rehState.synthCancelled||toast(`Re-synthesized ${done} stale line${done!==1?"s":""}`,"success")}),(_Xa=$("reh-synth-cancel"))==null||_Xa.addEventListener("click",()=>{rehState.synthCancelled=!0,rehState.synthRunning=!1});function showRecOverlay(line){const overlay=$("reh-rec-overlay");if(!overlay)return;overlay.hidden=!1;const cue=$("reh-rec-cue"),c=rehState.cast[line.speaker]||{color:"#89b4fa"};cue&&(cue.innerHTML=`${escHtml(line.speaker)} \u2014 your line:
${escHtml(stripMarkdown(line.text))}
`),$("reh-rec-preview")&&($("reh-rec-preview").style.display="none",$("reh-rec-preview").src=""),$("reh-rec-confirm-row")&&($("reh-rec-confirm-row").hidden=!0),$("reh-rec-start")&&($("reh-rec-start").disabled=!1),$("reh-rec-stop")&&($("reh-rec-stop").disabled=!0),$("reh-rec-time")&&($("reh-rec-time").textContent="0:00"),rehState.lastRecBlob=null}function hideRecOverlay(){const overlay=$("reh-rec-overlay");overlay&&(overlay.hidden=!0),stopRehMic()}function rehRenderMeter(level=0,db=-1/0,clipped=!1){const meter=$("reh-mic-meter");if(!meter)return;if(!meter.children.length)for(let i=0;i<18;i++){const b=document.createElement("div");b.className="bar",meter.appendChild(b)}const active=Math.round(Math.max(0,Math.min(1,level))*meter.children.length);[...meter.children].forEach((bar,i)=>{bar.className="bar",bar.style.height=7+Math.min(i,active)*1.55+"px",i-12&&i>11&&bar.classList.add("hot"),clipped&&i>14&&bar.classList.add("clip"))});const el=$("reh-db-readout");el&&(el.textContent=Number.isFinite(db)?db.toFixed(1)+" dB":"-\u221E dB")}function rehStartMeter(){if(!rehState.recAnalyser)return;rehState.recMeterRaf&&cancelAnimationFrame(rehState.recMeterRaf);const data=new Float32Array(rehState.recAnalyser.fftSize),canvas=$("reh-live-wave"),RING=300,ADD=10;rehState.recWaveRing=new Float32Array(RING);const tick=()=>{rehState.recAnalyser.getFloatTimeDomainData(data);let sum=0,peak=0;for(const s of data)sum+=s*s,peak=Math.max(peak,Math.abs(s));const rms=Math.sqrt(sum/data.length),db=rms>0?20*Math.log10(rms):-1/0;if(rehRenderMeter((db+60)/60,db,peak>.98),canvas&&rehState.recWaveRing){const ring=rehState.recWaveRing;ring.copyWithin(0,ADD);for(let i=0;i.98?"#f38ba8":db>-12?"#f9e2af":"#a6e3a1",ctx.lineWidth=1.5;const mid=h/2;for(let i=0;i{try{n&&n.disconnect()}catch{}}),rehState.recStream&&rehState.recStream.getTracks().forEach(t=>t.stop()),rehState.recDestStream&&rehState.recDestStream.getTracks().forEach(t=>t.stop()),rehState.recAudioCtx&&rehState.recAudioCtx.close().catch(()=>{}),Object.assign(rehState,{recStream:null,recDestStream:null,recSourceNode:null,recGainNode:null,recAnalyser:null,recAudioCtx:null,recWaveRing:null}),rehRenderMeter();const wc=$("reh-live-wave");wc&&wc.getContext("2d").clearRect(0,0,wc.width,wc.height)}(_Ya=$("reh-rec-start"))==null||_Ya.addEventListener("click",async()=>{try{await startRehMic(),rehState.recChunks=[],rehState.recSecs=0,$("reh-rec-time")&&($("reh-rec-time").textContent="0:00"),$("reh-rec-start")&&($("reh-rec-start").disabled=!0),$("reh-rec-stop")&&($("reh-rec-stop").disabled=!1),$("reh-rec-confirm-row")&&($("reh-rec-confirm-row").hidden=!0),rehState.recTimer=setInterval(()=>{rehState.recSecs++,$("reh-rec-time")&&($("reh-rec-time").textContent=Math.floor(rehState.recSecs/60)+":"+String(rehState.recSecs%60).padStart(2,"0"))},1e3),rehState.mediaRec=new MediaRecorder(rehState.recDestStream||rehState.recStream,{audioBitsPerSecond:256e3}),rehState.mediaRec.ondataavailable=e=>{e.data.size&&rehState.recChunks.push(e.data)},rehState.mediaRec.onstop=()=>{clearInterval(rehState.recTimer),$("reh-rec-start")&&($("reh-rec-start").disabled=!1),$("reh-rec-stop")&&($("reh-rec-stop").disabled=!0);const blob=new Blob(rehState.recChunks,{type:rehState.mediaRec.mimeType||"audio/webm"}),url=URL.createObjectURL(blob),p=$("reh-rec-preview");p&&(p.src=url,p.style.display=""),$("reh-rec-confirm-row")&&($("reh-rec-confirm-row").hidden=!1),rehState.lastRecBlob=blob},rehState.mediaRec.start(100)}catch(e){stopRehMic(),toast(await microphoneErrorMessage(e),"error")}}),(_Za=$("reh-rec-stop"))==null||_Za.addEventListener("click",()=>{var _a2;((_a2=rehState.mediaRec)==null?void 0:_a2.state)!=="inactive"&&rehState.mediaRec.stop()}),(__a=$("reh-rec-keep"))==null||__a.addEventListener("click",()=>{var _a2;rehState.lastRecBlob&&rehState.clips.push({lineIndex:rehState.lineIndex,speaker:(_a2=rehState.lines[rehState.lineIndex])==null?void 0:_a2.speaker,type:"me",blob:rehState.lastRecBlob}),stopRehMic(),hideRecOverlay(),rehState.lineIndex++,startPlay()}),(_$a=$("reh-rec-redo"))==null||_$a.addEventListener("click",()=>{stopRehMic();const l=rehState.lines[rehState.lineIndex];l&&showRecOverlay(l)}),(_ab=$("reh-skip-line"))==null||_ab.addEventListener("click",()=>{var _a2;rehState.clips.push({lineIndex:rehState.lineIndex,speaker:(_a2=rehState.lines[rehState.lineIndex])==null?void 0:_a2.speaker,type:"skip"}),stopRehMic(),hideRecOverlay(),rehState.lineIndex++,startPlay()});function renderSummary(){const list=$("reh-summary-list");if(list){if(!rehState.clips.length){list.innerHTML='

No clips in this session.

';return}list.innerHTML=rehState.clips.map((clip,idx)=>{const line=rehState.lines[clip.lineIndex]||{text:"\u2014",speaker:clip.speaker},c=rehState.cast[clip.speaker]||{color:"#89b4fa"},typeLabel=clip.type==="me"?"\u{1F3A4} Recorded":clip.type==="tts"?"\u{1F50A} Synthesized":"\u23ED Skipped",audioHtml=clip.blob?``:"",dlHtml=clip.blob?``:"";return`
${escHtml(clip.speaker||"\u2014")} ${typeLabel}
${escHtml(stripMarkdown(line.text))}
${audioHtml}
${dlHtml}
`}).join("")}}(_bb=$("reh-new-session-btn"))==null||_bb.addEventListener("click",()=>{stopPlay(),stopRehMic(),rehState.lines=[],rehState.cast={},rehState.clips=[],rehState.lineIndex=0,rehState.savedId=null,rehState.synthCache.clear(),rehDecodedBuffers.clear(),rehState.narratorVoice="",rehState.practiceStart=null,rehState.practiceEnd=null,$("reh-script-text")&&($("reh-script-text").value=""),$("reh-script-title")&&($("reh-script-title").value=""),renderLibraryList(),showPhase(1)}),(_cb=$("reh-resume-btn"))==null||_cb.addEventListener("click",()=>{showPhase(3),highlightCurrentLine()}),(_db=$("reh-save-session-btn"))==null||_db.addEventListener("click",saveToLibrary),(_eb=$("reh-cast-save-btn"))==null||_eb.addEventListener("click",saveToLibrary),(_fb=$("reh-export-session-btn"))==null||_fb.addEventListener("click",exportToFile),(_gb=$("reh-tb-save"))==null||_gb.addEventListener("click",saveToLibrary),(_hb=$("reh-tb-export"))==null||_hb.addEventListener("click",exportToFile),(_ib=$("reh-lib-import-file"))==null||_ib.addEventListener("change",async function(){var _a2;const f=(_a2=this.files)==null?void 0:_a2[0];f&&(await importFromFile(f),this.value="")}),document.querySelectorAll(".reh-impex-file").forEach(inp=>{inp.addEventListener("change",async function(){var _a2;const f=(_a2=this.files)==null?void 0:_a2[0];f&&(await importFromFile(f),this.value="",navRehearserPhase(1))})}),(_jb=$("reh-impex-reh-btn"))==null||_jb.addEventListener("click",exportToFile),(_kb=$("reh-impex-fountain-btn"))==null||_kb.addEventListener("click",()=>{var _a2;(_a2=$("reh-fountain-btn"))==null||_a2.click()}),(_lb=$("reh-impex-fdx-btn"))==null||_lb.addEventListener("click",()=>{var _a2;(_a2=$("reh-fdx-export-btn"))==null||_a2.click()}),(_mb=$("reh-impex-osf-btn"))==null||_mb.addEventListener("click",()=>{var _a2;(_a2=$("reh-osf-export-btn"))==null||_a2.click()});function _downloadText(content,filename){const a=document.createElement("a");a.href=URL.createObjectURL(new Blob([content],{type:"text/plain;charset=utf-8"})),a.download=filename,a.click(),setTimeout(()=>URL.revokeObjectURL(a.href),3e3)}function exportAsTxt(){var _a2,_b2;const title=((_a2=$("reh-script-title"))==null?void 0:_a2.value.trim())||((_b2=$("reh-page-title"))==null?void 0:_b2.textContent)||"script";_downloadText(linesToScriptText(),title.replace(/[^a-z0-9]/gi,"_")+".txt")}function exportAsMd(){var _a2;const title=((_a2=$("reh-script-title"))==null?void 0:_a2.value.trim())||"Script",lines=rehState.lines.map(l=>{switch(l.type){case"act":return` # ${l.text} `;case"scene":return` ## ${l.text} `;case"transition":return` *${l.text}* `;case"direction":return`*(${l.text})*`;case"action":return` ${l.text} `;case"dialog":return` **${l.speaker}** ${l.text} `;default:return""}}).join(` `);_downloadText(`# ${title} ${lines.trim()} `,title.replace(/[^a-z0-9]/gi,"_")+".md")}(_nb=$("reh-impex-txt-btn"))==null||_nb.addEventListener("click",exportAsTxt),(_ob=$("reh-impex-md-btn"))==null||_ob.addEventListener("click",exportAsMd),function(){const inp=$("reh-impex-url"),btn=$("reh-impex-url-btn"),status2=$("reh-impex-url-status");if(!inp||!btn)return;async function fetchUrl(){const url=inp.value.trim();if(!url){toast("Paste a URL first","error");return}btn.disabled=!0,status2&&(status2.textContent="Fetching\u2026",status2.style.color="");try{const r=await fetch("/api/fetch-web-script",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json(),ta=$("reh-script-text");ta&&(ta.value=d.text);const titleInp=$("reh-script-title");titleInp&&d.title&&(titleInp.value=d.title.replace(/-/g," ").replace(/\b\w/g,c=>c.toUpperCase())),status2&&(status2.textContent=`\u2713 Fetched ${(d.chars/1e3).toFixed(0)} K chars \u2014 scroll down to Parse & cast`,status2.style.color="var(--green)"),navRehearserPhase(1),setTimeout(()=>ta==null?void 0:ta.scrollIntoView({behavior:"smooth",block:"nearest"}),300),toast(`Fetched "${d.title}" \u2014 click "Parse & cast" to continue`,"success")}catch(e){status2&&(status2.textContent="\u2717 "+e.message,status2.style.color="var(--red)"),toast("Fetch failed: "+e.message,"error")}finally{btn.disabled=!1}}btn.addEventListener("click",fetchUrl),inp.addEventListener("keydown",e=>{e.key==="Enter"&&fetchUrl()})}(),function(){const modal=$("reh-imsdb-modal"),grid=$("reh-imsdb-grid"),search=$("reh-imsdb-search"),countEl=$("reh-imsdb-count"),closeBtn=$("reh-imsdb-close"),coverBtn=$("reh-imsdb-cover-btn"),listBtn=$("reh-imsdb-list-btn"),urlInp=$("reh-imsdb-url"),urlBtn=$("reh-imsdb-url-fetch");if(!modal||!grid)return;const CACHE_KEY="reh-imsdb-cat-v1",CACHE_TTL=6*3600*1e3;let _catalogue=null,_posterObserver=null,_view=localStorage.getItem("reh-imsdb-view")||"list";function _applyView(){grid.classList.toggle("list-view",_view==="list"),coverBtn&&coverBtn.classList.toggle("active",_view==="cover"),listBtn&&listBtn.classList.toggle("active",_view==="list")}function _ensurePosterObserver(){return _posterObserver||(_posterObserver=new IntersectionObserver(entries=>{entries.forEach(async ent=>{if(!ent.isIntersecting)return;const card=ent.target;_posterObserver.unobserve(card);const title=card.dataset.title;try{const d=await fetch("/api/movie-poster?title="+encodeURIComponent(title)).then(r=>r.json());if(d.poster){const img=document.createElement("img");img.className="reh-imsdb-poster",img.loading="lazy",img.src=d.poster,img.alt=title,img.onload=()=>{var _a2;const wrap=card.querySelector(".reh-imsdb-poster-wrap");wrap&&((_a2=wrap.querySelector(".reh-imsdb-fallback"))==null||_a2.remove(),wrap.appendChild(img))}}d.year&&card.querySelectorAll(".reh-imsdb-year").forEach(y=>{y.textContent=d.year})}catch{}})},{root:grid,rootMargin:"300px"}),_posterObserver)}function _bookColor(title){let h=0;for(let i=0;i>>0;return`hsl(${h%360}, 45%, 42%)`}function renderGrid(items){const obs=_ensurePosterObserver();if(grid.innerHTML="",!items.length){grid.innerHTML='
No matches.
';return}const frag=document.createDocumentFragment();items.slice(0,400).forEach(it=>{const card=document.createElement("div");card.className="reh-imsdb-card",card.dataset.title=it.title,card.dataset.url=it.fetch_url;const color1=_bookColor(it.title),color2=_bookColor(it.title+"_");card.innerHTML=`
${escHtml(it.title)}
${escHtml(it.title)}
`,card.addEventListener("click",()=>importImsdb(it)),frag.appendChild(card),obs.observe(card)}),grid.appendChild(frag),countEl&&(countEl.textContent=`${items.length} script${items.length!==1?"s":""}${items.length>400?" (showing 400)":""}`),_applyView()}async function importAnyWebUrl(url,label="script"){if(url=String(url||"").trim(),!url){toast("Paste a script URL first","error");return}if(!/^https?:\/\//i.test(url)){toast("URL must start with http:// or https://","error");return}urlBtn&&(urlBtn.disabled=!0),grid.innerHTML=`
Fetching ${escHtml(label)}\u2026
`;try{const r=await fetch("/api/fetch-web-script",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url})});if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||r.statusText)}const d=await r.json(),ta=$("reh-script-text");ta&&(ta.value=d.text);const ti=$("reh-script-title");ti&&(ti.value=d.title||label),closeModal(),navRehearserPhase(1),setTimeout(()=>ta==null?void 0:ta.scrollIntoView({behavior:"smooth",block:"center"}),250),toast(`Loaded "${d.title||label}" (${(d.chars/1e3).toFixed(0)} K) \u2014 click "Parse & cast"`,"success")}catch(e){grid.innerHTML=`
Fetch failed: ${escHtml(e.message)}
`,_catalogue&&renderGrid(_searchFilter(_catalogue)),toast("Script fetch failed: "+e.message,"error")}finally{urlBtn&&(urlBtn.disabled=!1)}}async function importImsdb(it){await importAnyWebUrl(it.fetch_url,it.title||"IMSDb script")}async function _loadCatalogue(){try{const raw=localStorage.getItem(CACHE_KEY);if(raw){const cached=JSON.parse(raw);if(Date.now()-cached.tsr.json())).items||[];try{localStorage.setItem(CACHE_KEY,JSON.stringify({ts:Date.now(),items:_catalogue}))}catch{}}async function openModal(){if(modal.hidden=!1,urlInp&&(urlInp.value=""),_applyView(),!_catalogue){grid.innerHTML='
Loading catalogue\u2026
';try{await _loadCatalogue()}catch(e){grid.innerHTML=`
Failed to load: ${escHtml(e.message)}
`;return}}renderGrid(_searchFilter(_catalogue)),search==null||search.focus()}function closeModal(){modal.hidden=!0}function _searchFilter(items){const q=(search==null?void 0:search.value.trim().toLowerCase())||"";return q?items.filter(it=>it.title.toLowerCase().includes(q)):items}let _searchTimer=null;search==null||search.addEventListener("input",()=>{clearTimeout(_searchTimer),_searchTimer=setTimeout(()=>{_catalogue&&renderGrid(_searchFilter(_catalogue))},150)}),search==null||search.addEventListener("keydown",e=>{e.key==="Enter"&&/^https?:\/\//i.test(search.value.trim())&&(e.preventDefault(),importAnyWebUrl(search.value.trim(),"pasted URL"))}),urlBtn==null||urlBtn.addEventListener("click",()=>importAnyWebUrl((urlInp==null?void 0:urlInp.value)||"","pasted URL")),urlInp==null||urlInp.addEventListener("keydown",e=>{e.key==="Enter"&&(e.preventDefault(),importAnyWebUrl(urlInp.value,"pasted URL"))}),coverBtn==null||coverBtn.addEventListener("click",()=>{_view="cover",localStorage.setItem("reh-imsdb-view",_view),_applyView()}),listBtn==null||listBtn.addEventListener("click",()=>{_view="list",localStorage.setItem("reh-imsdb-view",_view),_applyView()}),[$("reh-browse-imsdb-btn"),$("reh-browse-imsdb-btn-impex")].forEach(btn=>{btn==null||btn.addEventListener("click",openModal)}),closeBtn==null||closeBtn.addEventListener("click",closeModal),modal.addEventListener("click",e=>{e.target===modal&&closeModal()})}();const trainState={seq:[],turn:0,phase:"idle",cueSource:null,mediaRec:null,recChunks:[],recRaf:null,recStream:null,recCtx:null,recAnalyser:null,recTimer:null,recSecs:0};function _trainNormWords(str){return(str||"").toLowerCase().replace(/[^a-zäöüàáâãèéêëìíîïòóôõùúûüýÿæœßа-яёА-ЯЁ0-9\s]/g,"").trim().split(/\s+/).filter(Boolean)}function _trainLcs(a,b){const R=a.length,C=b.length,dp=Array.from({length:R+1},()=>new Int16Array(C+1));for(let i2=1;i2<=R;i2++)for(let j2=1;j2<=C;j2++)dp[i2][j2]=a[i2-1]===b[j2-1]?dp[i2-1][j2-1]+1:Math.max(dp[i2-1][j2],dp[i2][j2-1]);let i=R,j=C;const seq=[];for(;i>0&&j>0;)a[i-1]===b[j-1]?(seq.unshift([i-1,j-1]),i--,j--):dp[i-1][j]>=dp[i][j-1]?i--:j--;return seq}function _trainCompare(expected,actual){const ew=_trainNormWords(expected),aw=_trainNormWords(actual);if(!ew.length)return{html_exp:"",html_act:"",score:1};const pairs=_trainLcs(ew,aw),matchedE=new Set(pairs.map(p=>p[0])),matchedA=new Set(pairs.map(p=>p[1])),expHtml=ew.map((w,i)=>matchedE.has(i)?`${escHtml(w)}`:`${escHtml(w)}`).join(" "),actHtml=aw.map((w,i)=>matchedA.has(i)?`${escHtml(w)}`:`${escHtml(w)}`).join(" "),score=ew.length?Math.round(matchedE.size/ew.length*100):100;return{html_exp:expHtml,html_act:actHtml,score}}function buildTrainSeq(){const seq=[],lines=rehState.lines;for(let i=0;i=0&&cueLines.length<3;j--){const prev=lines[j];if(prev.type==="scene"||prev.type==="act")break;if(prev.type!=="dialog")continue;const pc=rehState.cast[prev.speaker];if(!pc||pc.voice==="me")break;cueLines.unshift({index:j,speaker:prev.speaker,text:prev.text,emotion:prev.emotion||"",voice:pc.voice,color:pc.color,instruct:_buildInstruct(pc.instruct,prev.emotion),voiceData:pc.voiceData})}seq.push({cueLines,myLine:{index:i,speaker:line.speaker,text:line.text,color:cast.color}})}return seq}function _trainSetState(phase){trainState.phase=phase;const stateEl=$("reh-train-rec-state"),playBtn=$("reh-train-play"),recBtn=$("reh-train-record"),stopBtn=$("reh-train-stop-rec"),cueState=$("reh-train-cue-state"),map={idle:{state:"Read the cue above \xB7 press \u25B6 to hear it",play:!0,rec:!1,stop:!1},playing_cue:{state:"Playing cue\u2026",play:!1,rec:!1,stop:!1},ready:{state:"Press \u{1F3A4} to record your line",play:!1,rec:!0,stop:!1},recording:{state:"Recording\u2026",play:!1,rec:!1,stop:!0},transcribing:{state:"Transcribing\u2026",play:!1,rec:!1,stop:!1},done:{state:"Done \u2014 press \u25B6 to replay or \u23ED for next",play:!0,rec:!0,stop:!1}},m=map[phase]||map.idle;stateEl&&(stateEl.textContent=m.state),cueState&&phase==="playing_cue"?cueState.innerHTML='':cueState&&(cueState.innerHTML=""),playBtn&&(playBtn.disabled=!m.play),recBtn&&(recBtn.disabled=!m.rec,recBtn.hidden=!!m.stop),stopBtn&&(stopBtn.disabled=!m.stop,stopBtn.hidden=!m.stop);const timerEl=$("reh-train-rec-timer"),metersEl=$("reh-train-meters");phase==="recording"?(timerEl&&(timerEl.hidden=!1),metersEl&&(metersEl.hidden=!1)):phase!=="done"&&(timerEl&&(timerEl.hidden=!0),metersEl&&(metersEl.hidden=!0))}async function _trainPlayCueLines(cueLines){_trainSetState("playing_cue");for(const cue of cueLines){if(trainState.phase!=="playing_cue")break;let blob=rehState.synthCache.get(cue.index);if(!blob)try{blob=await fetchTtsPreviewBlob(cue.voice,_rehInlineTone(stripMarkdown(cue.text),cue.emotion),"wav",cue.instruct,rehState.backend)}catch(e){toast("Cue TTS failed: "+e.message,"error");break}if(trainState.phase!=="playing_cue")break;await new Promise(resolve=>{const url=URL.createObjectURL(blob),audio=new Audio(url);audio.onended=()=>{URL.revokeObjectURL(url),resolve()},audio.onerror=()=>{URL.revokeObjectURL(url),resolve()},trainState.cueAudio=audio,audio.play().catch(resolve)})}trainState.phase==="playing_cue"&&_trainSetState("ready")}async function _trainTranscribe(blob){_trainSetState("transcribing");try{const fd=new FormData;fd.append("file",blob,"train_rec.webm"),fd.append("backend",(_appSettings==null?void 0:_appSettings.stt_preferred_backend)||"configured");const r=await fetch("/api/transcribe-bytes",{method:"POST",body:fd});if(!r.ok)throw new Error(r.statusText);return((await r.json()).text||"").trim()}catch(e){return toast("Transcription failed: "+e.message,"error"),""}}async function trainGoTo(idx){trainState.cueAudio&&(trainState.cueAudio.pause(),trainState.cueAudio=null),_trainStopMic(),trainState.turn=Math.max(0,Math.min(idx,trainState.seq.length-1));const turn=trainState.seq[trainState.turn];if(!turn)return;const progEl=$("reh-train-progress");progEl&&(progEl.textContent=`Turn ${trainState.turn+1} / ${trainState.seq.length}`);const cueCard=$("reh-train-cue"),cueAvEl=$("reh-train-cue-avatar"),cueSpeaker=$("reh-train-cue-speaker"),cueText=$("reh-train-cue-text");if(turn.cueLines.length){const lastCue=turn.cueLines[turn.cueLines.length-1];cueAvEl&&(cueAvEl.innerHTML=voiceAvatarHtml(lastCue.voice,lastCue.color,32)),cueSpeaker&&(cueSpeaker.textContent=lastCue.speaker),cueText&&(cueText.innerHTML=turn.cueLines.map(c=>`
${escHtml(c.speaker)} ${escHtml(c.text)}
`).join("")),cueCard==null||cueCard.classList.remove("no-cue")}else cueAvEl&&(cueAvEl.innerHTML=''),cueSpeaker&&(cueSpeaker.textContent="No preceding cue"),cueText&&(cueText.textContent="This is the first line \u2014 speak when ready."),cueCard==null||cueCard.classList.add("no-cue");const mySpeaker=$("reh-train-my-speaker"),myText=$("reh-train-my-text");mySpeaker&&(mySpeaker.textContent=turn.myLine.speaker),myText&&(myText.textContent=turn.myLine.text);const resultEl=$("reh-train-result");resultEl&&(resultEl.hidden=!0);const prevAudio=$("reh-train-audio-preview");prevAudio&&(prevAudio.src="",prevAudio.style.display="none"),_trainSetState(turn.cueLines.length?"idle":"ready")}function _trainStartMeter(){const meterEl=$("reh-train-meter"),dbEl=$("reh-train-db"),waveEl=$("reh-train-wave");if(!trainState.recAnalyser||!meterEl)return;const analyser=trainState.recAnalyser,fft=new Uint8Array(analyser.frequencyBinCount),wCtx=waveEl==null?void 0:waveEl.getContext("2d"),W=(waveEl==null?void 0:waveEl.width)||300,H=(waveEl==null?void 0:waveEl.height)||36,BARS=18;function tick(){analyser.getByteFrequencyData(fft);const rms=fft.reduce((s,v)=>s+v*v,0)/fft.length,db=rms>0?20*Math.log10(Math.sqrt(rms)/128):-1/0;dbEl&&(dbEl.textContent=isFinite(db)?db.toFixed(1)+" dB":"-\u221E dB");const slots=Array.from({length:BARS},(_,i)=>{const s=Math.floor(i/BARS*fft.length),e=Math.floor((i+1)/BARS*fft.length);return fft.slice(s,e).reduce((a,b)=>a+b,0)/(e-s)/255});meterEl.innerHTML=slots.map(v=>{const h=Math.max(2,Math.round(v*28)),col=v>.85?"var(--red)":v>.6?"#f59e0b":"var(--green)";return``}).join(""),wCtx&&(analyser.getByteTimeDomainData(fft),wCtx.clearRect(0,0,W,H),wCtx.beginPath(),wCtx.strokeStyle="var(--accent)",wCtx.lineWidth=1.5,fft.forEach((v,i)=>{const x=i/fft.length*W,y=v/255*H;i?wCtx.lineTo(x,y):wCtx.moveTo(x,y)}),wCtx.stroke()),trainState.recRaf=requestAnimationFrame(tick)}trainState.recRaf=requestAnimationFrame(tick)}function _trainStopMic(){if(trainState.recRaf&&(cancelAnimationFrame(trainState.recRaf),trainState.recRaf=null),trainState.recTimer&&(clearInterval(trainState.recTimer),trainState.recTimer=null),trainState.mediaRec&&trainState.mediaRec.state!=="inactive")try{trainState.mediaRec.stop()}catch{}if(trainState.mediaRec=null,trainState.recCtx){try{trainState.recCtx.close()}catch{}trainState.recCtx=null}trainState.recStream&&(trainState.recStream.getTracks().forEach(t=>t.stop()),trainState.recStream=null),trainState.recAnalyser=null,trainState.recChunks=[];const timerEl=$("reh-train-rec-timer");timerEl&&(timerEl.hidden=!0,timerEl.textContent="0:00");const metersEl=$("reh-train-meters");metersEl&&(metersEl.hidden=!0)}async function _trainStartRecording(){if(trainState.phase==="ready")try{const stream=await requestMicrophoneStream({raw:!0});trainState.recStream=stream;const ctx=new AudioContext;trainState.recCtx=ctx;const src=ctx.createMediaStreamSource(stream),analyser=ctx.createAnalyser();analyser.fftSize=256,trainState.recAnalyser=analyser;const dst=ctx.createMediaStreamDestination();src.connect(analyser),analyser.connect(dst),_trainSetState("recording"),_trainStartMeter();const timerEl=$("reh-train-rec-timer");timerEl&&(timerEl.hidden=!1),trainState.recSecs=0,trainState.recTimer=setInterval(()=>{trainState.recSecs++;const m=Math.floor(trainState.recSecs/60),s=trainState.recSecs%60;timerEl&&(timerEl.textContent=`${m}:${String(s).padStart(2,"0")}`)},1e3),trainState.recChunks=[];const mr=new MediaRecorder(dst.stream,{audioBitsPerSecond:128e3});trainState.mediaRec=mr,mr.ondataavailable=e=>{e.data.size>0&&trainState.recChunks.push(e.data)},mr.onstop=async()=>{var _a2;_trainStopMic();const blob=new Blob(trainState.recChunks,{type:"audio/webm"});trainState.recChunks=[];const url=URL.createObjectURL(blob),prevAudio=$("reh-train-audio-preview");prevAudio&&(prevAudio.src=url,prevAudio.style.display="");const transcript=await _trainTranscribe(blob),turn=trainState.seq[trainState.turn],{html_exp,html_act,score}=_trainCompare(((_a2=turn==null?void 0:turn.myLine)==null?void 0:_a2.text)||"",transcript),resultEl=$("reh-train-result"),expEl=$("reh-train-expected-text"),actEl=$("reh-train-actual-text"),scoreEl=$("reh-train-score");if(expEl&&(expEl.innerHTML=html_exp),actEl&&(actEl.innerHTML=html_act||'Nothing detected'),scoreEl){const col=score>=90?"var(--green)":score>=60?"#f59e0b":"var(--red)";scoreEl.innerHTML=`${score}%`}resultEl&&(resultEl.hidden=!1),_trainSetState("done")},mr.start()}catch(e){toast("Microphone error: "+e.message,"error"),_trainSetState("ready")}}function _trainStopRecording(){trainState.mediaRec&&trainState.mediaRec.state==="recording"&&trainState.mediaRec.stop()}function _trainHideStage(hide){[".reh-stage-area","#reh-tts-status-bar","#reh-rec-overlay","#reh-synth-bar"].forEach(sel=>{const el=document.querySelector(sel)||document.getElementById(sel.replace("#",""));el&&(el.style.display=hide?"none":"")})}function enterTrainMode(){const seq=buildTrainSeq();if(!seq.length){toast('No "I play this" lines found \u2014 check "I play this" on at least one character in the Cast tab.',"error");return}trainState.seq=seq,trainState.turn=0,trainState.phase="idle",_trainHideStage(!0);const panel=$("reh-train-panel");panel&&(panel.hidden=!1),trainGoTo(0)}function exitTrainMode(){trainState.cueAudio&&(trainState.cueAudio.pause(),trainState.cueAudio=null),_trainStopMic(),trainState.phase="idle",trainState.seq=[],_trainHideStage(!1);const panel=$("reh-train-panel");panel&&(panel.hidden=!0)}if((_pb=$("reh-bulk-toggle"))==null||_pb.addEventListener("click",()=>setBulkMode(!rehState.bulkMode)),(_qb=$("reh-bulk-done"))==null||_qb.addEventListener("click",()=>setBulkMode(!1)),(_rb=$("reh-bulk-all"))==null||_rb.addEventListener("click",()=>{document.querySelectorAll("#reh-script-lines .reh-bulk-check").forEach(cb=>rehState.bulkSel.add(parseInt(cb.dataset.bulk))),rehState.bulkAnchor=rehState.bulkSel.size?Math.min(...rehState.bulkSel):null,document.querySelectorAll("#reh-script-lines .reh-bulk-check").forEach(cb=>_refreshBulkLine(parseInt(cb.dataset.bulk))),_updateBulkCount()}),(_sb=$("reh-bulk-none"))==null||_sb.addEventListener("click",()=>{const had=[...rehState.bulkSel];rehState.bulkSel.clear(),rehState.bulkAnchor=null,had.forEach(_refreshBulkLine),_updateBulkCount()}),(_tb=$("reh-bulk-ignore"))==null||_tb.addEventListener("click",()=>_bulkApply(l=>{l.ignored=!0})),(_ub=$("reh-bulk-unignore"))==null||_ub.addEventListener("click",()=>_bulkApply(l=>{l.ignored=!1})),(_vb=$("reh-bulk-hide"))==null||_vb.addEventListener("click",()=>_bulkApply(l=>{l.hidden=!0})),(_wb=$("reh-bulk-delete"))==null||_wb.addEventListener("click",_bulkDelete),(_xb=$("reh-bulk-show-hidden"))==null||_xb.addEventListener("change",e=>{rehState.showHidden=e.target.checked,buildScriptPage()}),(_yb=$("reh-page-mode-btn"))==null||_yb.addEventListener("click",cyclePageMode),(_zb=$("reh-train-open-btn"))==null||_zb.addEventListener("click",enterTrainMode),(_Ab=$("reh-train-exit-btn"))==null||_Ab.addEventListener("click",exitTrainMode),(_Bb=$("reh-train-play"))==null||_Bb.addEventListener("click",()=>{const turn=trainState.seq[trainState.turn];turn&&trainState.phase!=="playing_cue"&&(trainState.cueAudio&&(trainState.cueAudio.pause(),trainState.cueAudio=null),turn.cueLines.length?_trainPlayCueLines(turn.cueLines):_trainSetState("ready"))}),(_Cb=$("reh-train-record"))==null||_Cb.addEventListener("click",()=>{(trainState.phase==="ready"||trainState.phase==="done")&&_trainStartRecording()}),(_Db=$("reh-train-stop-rec"))==null||_Db.addEventListener("click",_trainStopRecording),(_Eb=$("reh-train-prev"))==null||_Eb.addEventListener("click",()=>{trainState.turn>0&&trainGoTo(trainState.turn-1)}),(_Fb=$("reh-train-next"))==null||_Fb.addEventListener("click",()=>{trainState.turn{trainState.cueAudio&&(trainState.cueAudio.pause(),trainState.cueAudio=null),_trainStopMic();const turn=trainState.seq[trainState.turn];if(!turn)return;const resultEl=$("reh-train-result");resultEl&&(resultEl.hidden=!0),turn.cueLines.length?_trainPlayCueLines(turn.cueLines):_trainSetState("ready")}),rehRenderMeter(),_syncPageModeBtn(),renderLibraryList().catch(()=>{}),$("reh-skip-desc-toggle")&&($("reh-skip-desc-toggle").checked=rehState.skipDescriptions),window._rehearserStartImpEx)delete window._rehearserStartImpEx,showRehImpEx();else{const _initPhase=window._rehearserStartPhase||1;delete window._rehearserStartPhase,showPhase(_initPhase)}const readerState={mode:null,title:"",pdfDoc:null,pages:[],sentences:[],idx:0,readingIdx:-1,playing:!1,speed:1,scale:1,zoomMode:"fit-width",audioCtx:null,currentSource:null,raf:null,blobCache:new Map,bufCache:new Map,gainCache:new Map,unitsByPage:new Map,baseSentences:[],chunkMode:"sentence",normalize:!0,io:null,_seq:0,synthRunning:!1,synthCancel:!1,selecting:!1,selStart:null,selEnd:null,selAnchored:!1,fileBlob:null,docText:"",savedId:null,savedAudioIdx:new Set,sourceUploaded:!1,ocrWorker:null,synthStarted:!1};window.readerState=readerState;const READER_RESUME_KEY="reader-resume",READER_FMT="mp3",READER_BUF_WINDOW=3,READER_CANVAS_MARGIN=2200,READER_IMPORT_YIELD_EVERY=350,READER_PDF_INITIAL_PAGES=16,READER_PDF_INITIAL_MAX_WITHOUT_TEXT=64,READER_OCR_LANGS="deu+eng",READER_OCR_MIN_GAP_RATIO=.08,READER_OCR_MIN_GAP_PX=40,READER_OCR_RENDER_SCALE=2.5,READER_OCR_MIN_CONFIDENCE=40;function readerCtx(){return readerState.audioCtx||(readerState.audioCtx=new(window.AudioContext||window.webkitAudioContext)),readerState.audioCtx.state==="suspended"&&readerState.audioCtx.resume().catch(()=>{}),readerState.audioCtx}function readerYield(){return new Promise(resolve=>setTimeout(resolve,0))}window.readerOnShow=async function(){const sel=$("reader-backend-select");if(sel&&(!sel.value||sel.options.length<=1)){if(typeof availableTtsBackends=="function"&&!availableTtsBackends().length&&typeof refreshTtsBackendAvailability=="function")try{await refreshTtsBackendAvailability()}catch{}typeof availableTtsBackends=="function"&&availableTtsBackends().length&&(sel.innerHTML=ttsBackendOptions(sel.value),sel.disabled=!1)}readerUpdateBackendHint(),window.VoicePicker&&VoicePicker.upgrade("reader-voice-select"),readerRenderLibrary()},window.readerStop=function(){readerSaveResume(),readerPersistProgress(),readerStopPlayback()};async function readerFetchVoices(){var _a2;const btn=$("reader-fetch-voices-btn"),backend=(_a2=$("reader-backend-select"))==null?void 0:_a2.value;if(!backend){toast("No available TTS backend","error");return}btn&&(btn.disabled=!0);try{const raw=await fetch("/api/tts-voices?backend="+encodeURIComponent(backend)).then(r=>r.json()),ids=(Array.isArray(raw)?raw:[]).map(v=>typeof v=="string"?v:v.id||v.name||v.voice_id).filter(Boolean),sel=$("reader-voice-select");if(!sel)return;const prev=sel.value;window.VoicePicker?(VoicePicker.upgrade("reader-voice-select"),VoicePicker.populate("reader-voice-select",ids),prev&&ids.includes(prev)?VoicePicker.setValue("reader-voice-select",prev):ids.length&&VoicePicker.setValue("reader-voice-select",ids[0])):(sel.innerHTML='',ids.forEach(id=>{const o=document.createElement("option");o.value=o.textContent=id,sel.appendChild(o)}),prev&&ids.includes(prev)&&(sel.value=prev)),toast("Fetched "+ids.length+" voices","success")}catch(e){toast("Fetch failed: "+(e.message||e),"error")}finally{btn&&(btn.disabled=!1)}}function readerUpdateBackendHint(){var _a2;const el=$("reader-backend-hint");if(!el)return;const id=((_a2=$("reader-backend-select"))==null?void 0:_a2.value)||"",b=typeof backendById=="function"?backendById(id):null,steady=id&&(b&&b.style_aware||/design|custom|kokoro|magpie/i.test(id));el.innerHTML=steady?' Good for long-form narration. For maximum steadiness, increase \u201CVoice consistency\u201D.':' Cloned / zero-shot voices re-sample each chunk, so the voice can drift between sections. To steady it: raise \u201CVoice consistency\u201D to paragraph/page, set a fixed Seed with a low Temperature (e.g. 0.3), keep \u201CNormalise loudness\u201D on, or pick a style-aware backend.',el.classList.toggle("reader-hint-warn",!steady)}function readerTitleFromText(text){return(String(text||"").split(/\r?\n/).map(x=>x.trim()).find(Boolean)||"Pasted text").replace(/\s+/g," ").slice(0,80)||"Pasted text"}function readerUpdateToolbarVisibility(loaded=!!readerState.sentences.length){const toolbar=$("reader-toolbar");toolbar&&(toolbar.hidden=!loaded);const pdfOnly=readerState.mode==="pdf",zoom=$("reader-zoom");zoom&&(zoom.hidden=!pdfOnly);const legend=document.querySelector(".reader-legend");legend&&(legend.hidden=!pdfOnly)}function readerFinishLoadedDocument({resume=!0,successPrefix="Loaded"}={}){if(!readerState.sentences.length)return toast("No readable text found in this document","error"),!1;readerState.mode==="text"&&readerState.sentences.forEach((s,i)=>readerSetStatus(i,"pending")),$("reader-transport").hidden=!1,$("reader-dropzone").hidden=!0;const importArea=$("reader-import-area");importArea&&(importArea.hidden=!0),readerUpdateToolbarVisibility(!0),$("reader-synthbar").hidden=!1;const pr=$("reader-page-range");if(pr&&(pr.hidden=readerState.mode!=="pdf",readerState.mode==="pdf")){const n=readerState.pages.length,pf=$("reader-page-from"),pt=$("reader-page-to");pf&&(pf.max=n,pf.value=1),pt&&(pt.max=n,pt.value=n)}readerUpdateScopeLabel();const resumed=resume&&readerLoadResume();return readerUpdateProgress(),readerHighlightSentence(readerState.sentences[readerState.idx]),toast(resumed?"Resumed at sentence "+(readerState.idx+1)+" / "+readerState.sentences.length:successPrefix+" \xB7 "+readerState.sentences.length+" sentences \u2014 press play","success"),typeof refreshWorkflowCrumbs=="function"&&refreshWorkflowCrumbs("source"),!0}async function readerImportFile(file){if(!file)return;const name=(file.name||"").toLowerCase();readerResetDoc(),readerState.title=file.name.replace(/\.[^.]+$/,""),readerState.fileBlob=file;const titleEl=$("reader-doc-title");titleEl&&(titleEl.textContent=readerState.title);try{if(name.endsWith(".pdf")){toast("Loading PDF\u2026","success");const loaded=await readerLoadPdfSkeleton(file);if(!loaded)return;readerShowExtractPrompt(loaded)}else{const text=await file.text();readerState.docText=text,await readerLoadText(text),readerFinishLoadedDocument({successPrefix:"Loaded"})}}catch(e){toast("Import failed: "+(e.message||e),"error")}}function readerShowExtractPrompt(loaded){const dz=$("reader-dropzone");dz&&(dz.hidden=!0);const importArea=$("reader-import-area");importArea&&(importArea.hidden=!0),readerUpdateToolbarVisibility(!0);const banner=$("reader-extract-banner");if(banner){banner.hidden=!1;const pc=$("reader-extract-pagecount");pc&&(pc.textContent=String(loaded.nPages))}const btn=$("reader-extract-btn");btn&&(btn.onclick=()=>readerRunExtraction(loaded)),typeof refreshWorkflowCrumbs=="function"&&refreshWorkflowCrumbs("source"),toast("PDF loaded \xB7 "+loaded.nPages+' pages \u2014 click "Extract Text" when ready',"success")}async function readerRunExtraction(loaded){const btn=$("reader-extract-btn"),original=btn?btn.innerHTML:"";btn&&(btn.disabled=!0,btn.innerHTML=' Extracting\u2026');try{await readerExtractPdfText(loaded);const banner=$("reader-extract-banner");banner&&(banner.hidden=!0),readerFinishLoadedDocument({resume:!1,successPrefix:"Extracted"})}catch(e){toast("Text extraction failed: "+(e.message||e),"error")}finally{btn&&(btn.disabled=!1,btn.innerHTML=original)}}async function readerImportPastedText(){const ta=$("reader-paste-text"),text=((ta==null?void 0:ta.value)||"").trim();if(!text){toast("Paste some text first","error"),ta==null||ta.focus();return}const btn=$("reader-paste-load");btn&&(btn.disabled=!0),readerResetDoc(),readerState.title=readerTitleFromText(text),readerState.docText=text,readerState.fileBlob=null,readerState.sourceUploaded=!1;const titleEl=$("reader-doc-title");titleEl&&(titleEl.textContent=readerState.title);try{await readerLoadText(text),readerFinishLoadedDocument({resume:!1,successPrefix:"Loaded pasted text"})}catch(e){toast("Import failed: "+(e.message||e),"error")}finally{btn&&(btn.disabled=!1)}}function readerResetDoc(){readerStopPlayback(),readerState._seq++,readerState.mode=null,readerState.pdfDoc=null,readerState.pages=[],readerState.sentences=[],readerState.idx=0,readerState.readingIdx=-1,readerState.scale=1,readerState.zoomMode="fit-width",readerState.blobCache.clear(),readerState.bufCache.clear(),readerState.gainCache.clear(),readerState.unitsByPage=new Map,readerState.baseSentences=[],readerState.fileBlob=null,readerState.docText="",readerState.savedId=null,readerState.savedAudioIdx=new Set,readerState.sourceUploaded=!1;const extractBanner=$("reader-extract-banner");extractBanner&&(extractBanner.hidden=!0),readerState.io&&(readerState.io.disconnect(),readerState.io=null),readerState.synthRunning=!1,readerState.synthCancel=!1,readerState.synthStarted=!1,readerSetSelecting(!1),readerState.selStart=readerState.selEnd=null,readerState.selAnchored=!1;const sb=$("reader-synthbar");sb&&(sb.hidden=!0);const tr=$("reader-transport");tr&&(tr.hidden=!0),readerUpdateToolbarVisibility(!1);const sp=$("reader-synth-prog");sp&&(sp.hidden=!0);const si=$("reader-sel-info");si&&(si.hidden=!0);const doc=$("reader-doc");doc&&(doc.innerHTML="",doc.classList.remove("two-page","reader-selecting","reader-synth-started")),typeof refreshWorkflowCrumbs=="function"&&refreshWorkflowCrumbs("source");const importArea=$("reader-import-area");importArea&&(importArea.hidden=!1),readerClearSearch({clearInput:!0})}function readerBuildSentences(words){const sentences=[];let cur=null;const endRe=/[.!?]["'”’)\]]?$/,abbrev=/^(mr|mrs|ms|dr|prof|sr|jr|vs|etc|e\.g|i\.e|no|vol|st|fig)\.?$/i;for(const w of words)w.para&&cur&&cur.words.length&&(sentences.push(cur),cur=null),cur||(cur={text:"",words:[],status:"pending",_stat:null,paraStart:!!w.para}),cur.words.push(w),cur.text+=(cur.text?" ":"")+w.text,(endRe.test(w.text)&&!abbrev.test(w.text.replace(/[^a-z.]/gi,""))&&cur.words.length>=2||cur.words.length>=45)&&(sentences.push(cur),cur=null);return cur&&cur.words.length&&sentences.push(cur),sentences}function readerGroupUnits(base,mode){if(mode==="sentence"||!base.length)return base.map(s=>({text:s.text,words:s.words,status:"pending",_stat:null,paraStart:!!s.paraStart}));const maxChars=mode==="page"?2e3:500,isPdf=readerState.mode==="pdf",keyOf=s=>{var _a2,_b2,_c2,_d2;return isPdf?(_b2=(_a2=s.words[0])==null?void 0:_a2.page)!=null?_b2:0:(_d2=(_c2=s.words[0])==null?void 0:_c2.para)!=null?_d2:0},units=[];let cur=null;for(const s of base){const k=keyOf(s),boundary=cur&&(mode==="paragraph"&&k!==cur._key||mode==="page"&&isPdf&&k!==cur._key),tooLong=cur&&cur.words.length&&cur.text.length+s.text.length+1>maxChars;(boundary||tooLong)&&(units.push(cur),cur=null),cur||(cur={text:"",words:[],status:"pending",_stat:null,_key:k,paraStart:!!s.paraStart}),cur.text+=cur.text?(s.paraStart?` `:" ")+s.text:s.text,cur.words.push(...s.words)}return cur&&units.push(cur),units}function readerMarkParagraphBreaks(pageWords){if(!pageWords.length)return;const lines=[];let curLine=null,curTop=null;for(const w of pageWords)curLine&&Math.abs(w.top-curTop)a-b),median=sorted[Math.floor(sorted.length/2)]||0;if(!(median<=0))for(let i=1;imedian*1.35&&(lines[i][0].para=!0)}async function loadTesseract(){window.Tesseract||await new Promise((resolve,reject)=>{const s=document.createElement("script");s.src="/static/js/tesseract/tesseract.min.js",s.onload=resolve,s.onerror=reject,document.head.appendChild(s)})}async function readerGetOcrWorker(){return readerState.ocrWorker||(await loadTesseract(),readerState.ocrWorker=await Tesseract.createWorker(READER_OCR_LANGS,1,{workerPath:"/static/js/tesseract/worker.min.js",corePath:"/static/js/tesseract/tesseract-core-simd-lstm.wasm.js",langPath:"/static/js/tesseract/lang",gzip:!0})),readerState.ocrWorker}async function readerOcrPageHeading(page,base,pageIdx,gapPx){var _a2;let crop;try{const worker=await readerGetOcrWorker(),viewport=page.getViewport({scale:READER_OCR_RENDER_SCALE}),cropH=Math.max(Math.round(gapPx*READER_OCR_RENDER_SCALE),1);crop=document.createElement("canvas"),crop.width=viewport.width,crop.height=cropH,await page.render({canvasContext:crop.getContext("2d"),viewport}).promise;const{data}=await worker.recognize(crop),text=(data.text||"").replace(/\s+/g," ").trim();if(!text||((_a2=data.confidence)!=null?_a2:0)Math.max(base.height*READER_OCR_MIN_GAP_RATIO,READER_OCR_MIN_GAP_PX)){const ocrWords=await readerOcrPageHeading(page,base,pageIdx,gapPx);if(seq!==readerState._seq)return;ocrWords.length&&pageWords.unshift(...ocrWords)}}readerMarkParagraphBreaks(pageWords);const pageBase=readerBuildSentences(pageWords),pageUnits=readerGroupUnits(pageBase,readerState.chunkMode),startUnit=readerState.sentences.length;readerState.baseSentences.push(...pageBase),readerState.sentences.push(...pageUnits);const idxs=[];for(let u=startUnit;usetTimeout(r,0))}if(progress.done(),readerState.ocrWorker){try{readerState.ocrWorker.terminate()}catch{}readerState.ocrWorker=null}}async function readerLoadPdf(file){const loaded=await readerLoadPdfSkeleton(file);loaded&&await readerExtractPdfText(loaded)}function readerShowParseProgress(total){const doc=$("reader-doc");if(!doc)return{update(){},done(){}};const el=document.createElement("div");el.className="reader-parsing",el.innerHTML=' Reading PDF\u2026 page 0 / '+total+"",el.style.cssText="position:fixed; top:80px; left:50%; transform:translateX(-50%); z-index:100; background:var(--accent); color:#fff; padding:14px 28px; border-radius:32px; font-size:18px; display:inline-flex; align-items:center; gap:12px; box-shadow:0 6px 24px rgba(0,0,0,.35); font-weight:700;",doc.insertBefore(el,doc.firstChild);const label=el.querySelector("span:last-child");return{update(p){label&&(label.textContent="Reading PDF\u2026 page "+p+" / "+total)},done(){el.remove()}}}function readerComputeScale(mode){const doc=$("reader-doc");if(!doc||!readerState.pages.length)return 1;let maxW=0,maxH=0;for(const p of readerState.pages)p.base&&(p.base.width>maxW&&(maxW=p.base.width),p.base.height>maxH&&(maxH=p.base.height));if(!maxW)return 1;const padW=36,gap=16,availW=(doc.clientWidth||900)-padW,availH=(doc.clientHeight||600)-36;return mode==="fit-width"?Math.max(.2,availW/maxW):mode==="fit-height"?Math.max(.2,availH/maxH):mode==="two"?Math.max(.2,(availW-gap)/2/maxW):readerState.scale}function readerApplyZoom(mode){if(!readerState.pages.length)return;mode&&(readerState.zoomMode=mode),mode&&mode!=="custom"&&(readerState.scale=readerComputeScale(mode));const scale=readerState.scale;$("reader-doc").classList.toggle("two-page",readerState.zoomMode==="two"),readerState.pages.forEach(pg=>{pg.pageDiv.style.width=pg.base.width*scale+"px",pg.pageDiv.style.height=pg.base.height*scale+"px";const old=pg.pageDiv.querySelector("canvas.reader-canvas");if(old&&old.remove(),pg.renderTask){try{pg.renderTask.cancel()}catch{}pg.renderTask=null}pg.rendered=!1}),readerState.pages.forEach(pg=>pg.overlay.querySelectorAll(".reader-stat").forEach(el=>{const i=parseInt(el.dataset.si);isNaN(i)||readerPaintStatus(i)})),readerPaintSearchHighlights(),readerState.idx{entries.forEach(en=>{if(!en.isIntersecting)return;const idx=readerState.pages.findIndex(p=>p.pageDiv===en.target);idx>=0&&readerRenderPage(idx)})},{root:$("reader-doc"),rootMargin:"600px 0px"}),readerState.pages.forEach(p=>readerState.io.observe(p.pageDiv))}function readerRenderVisible(){$("reader-doc")&&(readerState.pages.forEach((pg,i)=>{readerIsPageNearView(pg,800)&&readerRenderPage(i)}),readerEvictCanvases())}function readerIsPageNearView(pg,margin=800){const doc=$("reader-doc");if(!doc||!(pg!=null&&pg.pageDiv))return!1;const dr=doc.getBoundingClientRect(),r=pg.pageDiv.getBoundingClientRect();return r.bottom>dr.top-margin&&r.top{if(!pg.rendered)return;const r=pg.pageDiv.getBoundingClientRect();if(r.bottomdr.bottom+READER_CANVAS_MARGIN){if(pg.renderTask){try{pg.renderTask.cancel()}catch{}pg.renderTask=null}const c=pg.pageDiv.querySelector("canvas.reader-canvas");c&&c.remove(),pg.rendered=!1}}))}async function readerRenderPage(idx){const pg=readerState.pages[idx];if(!pg||pg.rendered||!pg.page)return;pg.rendered=!0;const scale=readerState.scale,viewport=pg.page.getViewport({scale}),canvas=document.createElement("canvas");canvas.className="reader-canvas",canvas.width=Math.floor(viewport.width),canvas.height=Math.floor(viewport.height),pg.pageDiv.insertBefore(canvas,pg.overlay),readerCreatePageStatus(idx);try{pg.renderTask=pg.page.render({canvasContext:canvas.getContext("2d"),viewport}),await pg.renderTask.promise}catch{pg.rendered=!1,canvas.remove()}finally{pg.renderTask=null}}async function readerLoadText(text){readerState.mode="text",readerState.docText=text;const doc=$("reader-doc"),pane=document.createElement("div");pane.className="reader-text",doc.appendChild(pane);const words=[],paras=text.replace(/\r\n/g,` `).split(/\n{2,}/),frag=document.createDocumentFragment();let renderedWords=0;for(let pi=0;pi{const span=e.target.closest(".reader-word");if(!span)return;const si=readerState.sentences.findIndex(s=>s.words.some(w=>w.el===span));si<0||(readerState.selecting?readerPickSentence(si):readerJumpTo(si))})}function readerSentBBox(sentence){var _a2,_b2;const pageIdx=(_b2=(_a2=sentence.words[0])==null?void 0:_a2.page)!=null?_b2:0,on=sentence.words.filter(w=>w.page===pageIdx);return{page:pageIdx,x:Math.min(...on.map(w=>w.x)),top:Math.min(...on.map(w=>w.top)),w:Math.max(...on.map(w=>w.x+w.w))-Math.min(...on.map(w=>w.x)),h:Math.max(...on.map(w=>w.top+w.h))-Math.min(...on.map(w=>w.top))}}function readerBuildUnitIndex(){readerState.unitsByPage=new Map,readerState.mode==="pdf"&&readerState.sentences.forEach((s,i)=>{var _a2,_b2;const pg=(_b2=(_a2=s.words[0])==null?void 0:_a2.page)!=null?_b2:0;readerState.unitsByPage.has(pg)||readerState.unitsByPage.set(pg,[]),readerState.unitsByPage.get(pg).push(i)})}function readerCreatePageStatus(pageIdx){var _a2;if(readerState.mode!=="pdf")return;const pg=readerState.pages[pageIdx];if(!pg)return;const idxs=((_a2=readerState.unitsByPage)==null?void 0:_a2.get(pageIdx))||[];for(const i of idxs){const s=readerState.sentences[i];if(!s||s._stat)continue;const el=document.createElement("div");el.className="reader-stat",el.dataset.si=i,pg.overlay.appendChild(el),s._stat=el,readerPaintStatus(i)}}function readerEffectiveStatus(s){return s.status}function readerSetStatus(idx,status2){var _a2;const s=readerState.sentences[idx];s&&(s.status=status2,status2!=="pending"&&!readerState.synthStarted&&(readerState.synthStarted=!0,(_a2=$("reader-doc"))==null||_a2.classList.add("reader-synth-started")),readerPaintStatus(idx))}function readerPaintStatus(idx){const s=readerState.sentences[idx];if(!s)return;const eff=readerEffectiveStatus(s);if(readerState.mode==="pdf"){if(!s._stat)return;const b=readerSentBBox(s),scale=readerState.scale;Object.assign(s._stat.style,{left:b.x*scale+"px",top:b.top*scale+"px",width:b.w*scale+"px",height:b.h*scale+"px"}),s._stat.className="reader-stat reader-stat-"+eff}else s.words.forEach(w=>{w.el&&(w.el.classList.remove("stat-pending","stat-synth","stat-ready","stat-reading"),w.el.classList.add("stat-"+eff))})}let _readerWordBox=null;function readerEnsureBox(){_readerWordBox||(_readerWordBox=document.createElement("div"),_readerWordBox.className="reader-hl-word")}function readerClearHighlights(){readerState.mode==="text"?document.querySelectorAll(".reader-word.is-word").forEach(el=>el.classList.remove("is-word")):_readerWordBox&&(_readerWordBox.hidden=!0)}function readerEnsureVisible(el){const doc=$("reader-doc");if(!doc||!el)return;const dr=doc.getBoundingClientRect(),er=el.getBoundingClientRect();if(er.topdr.bottom-50){const delta=er.top-dr.top-doc.clientHeight*.38;doc.scrollTo({top:doc.scrollTop+delta,behavior:"smooth"})}}function readerHighlightSentence(sentence){var _a2;sentence&&(readerState.mode==="text"?readerEnsureVisible((_a2=sentence.words[0])==null?void 0:_a2.el):sentence._stat&&readerEnsureVisible(sentence._stat))}function readerHighlightWord(sentence,wi){const w=sentence.words[wi];if(!w)return;if(readerState.mode==="text"){document.querySelectorAll(".reader-word.is-word").forEach(el=>el.classList.remove("is-word")),w.el&&(w.el.classList.add("is-word"),readerEnsureVisible(w.el));return}readerEnsureBox();const pg=readerState.pages[w.page];if(!pg)return;const scale=readerState.scale;_readerWordBox.parentNode!==pg.overlay&&pg.overlay.appendChild(_readerWordBox),Object.assign(_readerWordBox.style,{left:w.x*scale+"px",top:w.top*scale+"px",width:w.w*scale+"px",height:w.h*scale+"px"}),_readerWordBox.hidden=!1,readerEnsureVisible(_readerWordBox)}function readerSetSelecting(on){readerState.selecting=!!on,readerState.selAnchored=!1;const doc=$("reader-doc");doc&&doc.classList.toggle("reader-selecting",readerState.selecting);const btn=$("reader-select-toggle");btn&&btn.classList.toggle("active",readerState.selecting);const lbl=$("reader-select-label");lbl&&(lbl.textContent=readerState.selecting?"Done":"Select range");const ic=$("reader-select-icon");ic&&(ic.className="mdi "+(readerState.selecting?"mdi-check":"mdi-cursor-default-click-outline")),readerSelHint(readerState.selecting?"Click the start sentence\u2026":""),readerState.selecting||readerPaintSelection()}function readerSelHint(html){const h=$("reader-sel-hint");h&&(h.innerHTML=html,h.hidden=!html)}function readerPickSentence(si){if(!readerState.selAnchored)readerState.selStart=si,readerState.selEnd=si,readerState.selAnchored=!0,readerSelHint("Start at sentence "+(si+1)+" \u2014 now click the end sentence");else{readerState.selEnd=si,readerState.selAnchored=!1;const r=readerSelRange();readerSelHint("Selected "+(r[1]-r[0]+1)+" sentences \u2014 click a new start, or Done")}readerPaintSelection(),readerUpdateScopeLabel()}function readerClearSelection(){readerState.selStart=readerState.selEnd=null,readerState.selAnchored=!1,readerPaintSelection(),readerUpdateScopeLabel(),readerState.selecting&&readerSelHint("Click the start sentence\u2026")}function readerSelRange(){return readerState.selStart===null||readerState.selEnd===null?null:[Math.min(readerState.selStart,readerState.selEnd),Math.max(readerState.selStart,readerState.selEnd)]}function readerPaintSelection(){const range=readerSelRange(),inSel=i=>range&&i>=range[0]&&i<=range[1],anchorI=readerState.selAnchored&&range&&range[0]===range[1]?range[0]:-1;readerState.sentences.forEach((s,i)=>{const sel=inSel(i)&&i!==anchorI,anchor=i===anchorI;readerState.mode==="pdf"?s._stat&&(s._stat.classList.toggle("sel",sel),s._stat.classList.toggle("sel-anchor",anchor)):s.words.forEach(w=>{w.el&&(w.el.classList.toggle("in-sel",sel),w.el.classList.toggle("sel-anchor",anchor))})})}function readerScopeIndices(){var _a2,_b2;const all=readerState.sentences.map((_,i)=>i),range=readerSelRange();if(range)return all.filter(i=>i>=range[0]&&i<=range[1]);if(readerState.mode==="pdf"){const n=readerState.pages.length;let from=parseInt((_a2=$("reader-page-from"))==null?void 0:_a2.value)||1,to=parseInt((_b2=$("reader-page-to"))==null?void 0:_b2.value)||n;return from=Math.max(1,Math.min(from,n)),to=Math.max(from,Math.min(to,n)),all.filter(i=>{var _a3,_b3;const p=((_b3=(_a3=readerState.sentences[i].words[0])==null?void 0:_a3.page)!=null?_b3:0)+1;return p>=from&&p<=to})}return all}function readerUpdateScopeLabel(){const scopeEl=$("reader-synth-scope"),range=readerSelRange();scopeEl&&(range?scopeEl.textContent="selection":readerState.mode==="pdf"?scopeEl.textContent="pages":scopeEl.textContent="all");const info=$("reader-sel-info"),txt=$("reader-sel-text");info&&(info.hidden=!range),txt&&range&&(txt.textContent="sentences "+(range[0]+1)+"\u2013"+(range[1]+1)+" ("+(range[1]-range[0]+1)+")")}async function readerSynthIndices(targets){var _a2,_b2,_c2;if(targets=targets.filter(i=>!readerState.blobCache.has(i)),!targets.length||readerState.synthRunning)return 0;const voice=(_a2=$("reader-voice-select"))==null?void 0:_a2.value,backend=(_b2=$("reader-backend-select"))==null?void 0:_b2.value;if(!voice)return toast("Pick a voice first","error"),0;if(!backend)return toast("No TTS backend selected","error"),0;const instruct=((_c2=$("reader-instruct"))==null?void 0:_c2.value.trim())||"";readerState.synthRunning=!0,readerState.synthCancel=!1;const prog=$("reader-synth-prog");prog&&(prog.hidden=!1);const total=targets.length;let done=0;const update=()=>{const f=$("reader-synth-fill");f&&(f.style.width=done/total*100+"%");const l=$("reader-synth-label");l&&(l.textContent=done+" / "+total)};update();const queue=targets.slice(),worker=async()=>{for(;queue.length&&!readerState.synthCancel;){const i=queue.shift();if(readerState.blobCache.has(i)){done++,update();continue}readerState.sentences[i].status==="pending"&&readerSetStatus(i,"synth");try{const blob=await fetchTtsPreviewBlob(voice,readerState.sentences[i].text,READER_FMT,instruct,backend,!1,readerGenParams());readerState.blobCache.set(i,blob),readerState.sentences[i].status==="synth"&&readerSetStatus(i,"ready")}catch{readerState.sentences[i].status==="synth"&&readerSetStatus(i,"pending")}done++,update()}},N=Math.min(2,targets.length);return await Promise.all(Array.from({length:N},worker)),readerState.synthRunning=!1,prog&&(prog.hidden=!0),done}async function readerSynthAll(){const targets=readerScopeIndices().filter(i=>!readerState.blobCache.has(i));if(!targets.length){toast("Selected range is already synthesised","success");return}const done=await readerSynthIndices(targets);(done||!readerState.synthCancel)&&toast(readerState.synthCancel?"Synthesis cancelled ("+done+" done)":"Synthesised "+done+" sentences",readerState.synthCancel?"error":"success")}function readerSafeName(s){return(s||"audio").replace(/[\/\\:*?"<>|]+/g,"_").replace(/\s+/g," ").trim().slice(0,60)||"audio"}function readerPad(n){return String(n).padStart(2,"0")}function readerPageOf(i){var _a2,_b2;return readerState.mode==="pdf"?((_b2=(_a2=readerState.sentences[i].words[0])==null?void 0:_a2.page)!=null?_b2:0)+1:1}function readerDownload(blob,filename){const a=document.createElement("a");a.href=URL.createObjectURL(blob),a.download=filename,document.body.appendChild(a),a.click(),setTimeout(()=>{URL.revokeObjectURL(a.href),a.remove()},1500)}const _readerDelay=ms=>new Promise(r=>setTimeout(r,ms));async function readerExport(mode){if(!readerState.sentences.length){toast("Import a document first","error");return}const indices=readerScopeIndices(),missing=indices.filter(i=>!readerState.blobCache.has(i));if(missing.length&&(toast("Synthesising "+missing.length+" missing sentence(s) before export\u2026","success"),await readerSynthIndices(missing),readerState.synthCancel)){toast("Export cancelled","error");return}const ready=indices.filter(i=>readerState.blobCache.has(i));if(!ready.length){toast("Nothing to export","error");return}const title=readerSafeName(readerState.title);if(mode==="sentence"){const perPage={};for(const i of ready){const pg=readerPageOf(i);perPage[pg]=(perPage[pg]||0)+1;const name=readerState.mode==="pdf"?`${title} - p${readerPad(pg)} - ${readerPad(perPage[pg])}.mp3`:`${title} - ${readerPad(perPage[pg])}.mp3`;readerDownload(readerState.blobCache.get(i),name),await _readerDelay(350)}toast("Exported "+ready.length+" MP3 files","success");return}const byPage=new Map;ready.forEach(i=>{const pg=readerPageOf(i);byPage.has(pg)||byPage.set(pg,[]),byPage.get(pg).push(i)});let files=0;for(const[pg,idxs]of[...byPage.entries()].sort((a,b)=>a[0]-b[0])){const blob=new Blob(idxs.map(i=>readerState.blobCache.get(i)),{type:"audio/mpeg"}),name=readerState.mode==="pdf"?`${title} - p${readerPad(pg)}.mp3`:`${title}.mp3`;readerDownload(blob,name),files++,await _readerDelay(400)}toast("Exported "+files+(readerState.mode==="pdf"?" page MP3 file(s)":" MP3"),"success")}function readerRechunk(mode){readerState.chunkMode=mode,readerState.baseSentences.length&&(readerStopPlayback(),readerState.blobCache.clear(),readerState.bufCache.clear(),readerState.gainCache.clear(),readerState.savedAudioIdx=new Set,readerState.pages.forEach(p=>p.overlay.querySelectorAll(".reader-stat").forEach(e=>e.remove())),readerClearSelection(),readerState.sentences=readerGroupUnits(readerState.baseSentences,mode),readerBuildUnitIndex(),readerState.pages.forEach((p,i)=>{p.rendered&&readerCreatePageStatus(i)}),readerState.mode==="text"&&readerState.sentences.forEach((s,i)=>readerSetStatus(i,"pending")),readerState.idx=0,readerUpdateScopeLabel(),readerUpdateProgress(),readerHighlightSentence(readerState.sentences[0]),toast("Voice consistency: "+mode+" \u2014 audio cleared, re-synthesise","success"))}function readerGenParams(){var _a2,_b2,_c2;const out={},seed=(_a2=$("reader-seed"))==null?void 0:_a2.value.trim(),temp=(_b2=$("reader-temp"))==null?void 0:_b2.value.trim(),nspd=(_c2=$("reader-tts-speed"))==null?void 0:_c2.value.trim();return seed!==""&&seed!=null&&!isNaN(+seed)&&(out.seed=parseInt(seed,10)),temp!==""&&temp!=null&&!isNaN(+temp)&&(out.temperature=parseFloat(temp)),nspd!==""&&nspd!=null&&!isNaN(+nspd)&&parseFloat(nspd)!==1&&(out.speed=parseFloat(nspd)),Object.keys(out).length?out:null}function readerComputeGain(idx,buf){if(readerState.gainCache.has(idx))return readerState.gainCache.get(idx);const ch=buf.getChannelData(0),step=Math.max(1,Math.floor(ch.length/8e3));let sum=0,n=0,peak=1e-6;for(let i=0;ipeak&&(peak=Math.abs(v))}let gain=.1/(Math.sqrt(sum/Math.max(1,n))||1e-4);return gain=Math.max(.5,Math.min(gain,4)),gain=Math.min(gain,.99/peak),readerState.gainCache.set(idx,gain),gain}async function readerFetchServerAudio(idx){if(!readerState.savedId||!readerState.savedAudioIdx.has(idx))return null;try{const r=await fetch(`${READER_API}/${readerState.savedId}/audio/${idx}`);if(r.ok){const b=await r.blob();return readerState.blobCache.set(idx,b),b}}catch{}return null}async function readerGetBuffer(idx){var _a2,_b2,_c2;if(readerState.bufCache.has(idx))return readerState.bufCache.get(idx);let blob=readerState.blobCache.get(idx)||await readerFetchServerAudio(idx);if(!blob){const voice=(_a2=$("reader-voice-select"))==null?void 0:_a2.value,backend=(_b2=$("reader-backend-select"))==null?void 0:_b2.value;if(!voice)throw new Error("Pick a voice first");if(!backend)throw new Error("No TTS backend selected");const instruct=((_c2=$("reader-instruct"))==null?void 0:_c2.value.trim())||"";readerState.sentences[idx].status!=="reading"&&readerSetStatus(idx,"synth"),blob=await fetchTtsPreviewBlob(voice,readerState.sentences[idx].text,READER_FMT,instruct,backend,!1,readerGenParams()),readerState.blobCache.set(idx,blob),readerState.sentences[idx].status==="synth"&&readerSetStatus(idx,"ready")}const buf=await readerCtx().decodeAudioData(await blob.arrayBuffer());return readerState.bufCache.set(idx,buf),buf}function readerEvictBuffers(center){if(!(readerState.bufCache.size<=READER_BUF_WINDOW*2+1))for(const i of readerState.bufCache.keys())Math.abs(i-center)>READER_BUF_WINDOW&&readerState.bufCache.delete(i)}async function readerPrefetch(idx){var _a2,_b2,_c2;if(!(idx<0||idx>=readerState.sentences.length)){if(!readerState.blobCache.has(idx)&&!await readerFetchServerAudio(idx)){const voice=(_a2=$("reader-voice-select"))==null?void 0:_a2.value,backend=(_b2=$("reader-backend-select"))==null?void 0:_b2.value;if(!voice||!backend)return;const instruct=((_c2=$("reader-instruct"))==null?void 0:_c2.value.trim())||"";readerState.sentences[idx].status==="pending"&&readerSetStatus(idx,"synth");try{const b=await fetchTtsPreviewBlob(voice,readerState.sentences[idx].text,READER_FMT,instruct,backend,!1,readerGenParams());readerState.blobCache.set(idx,b),readerState.sentences[idx].status==="synth"&&readerSetStatus(idx,"ready")}catch{readerState.sentences[idx].status==="synth"&&readerSetStatus(idx,"pending");return}}if(!readerState.bufCache.has(idx)&&Math.abs(idx-readerState.idx)<=READER_BUF_WINDOW)try{readerState.bufCache.set(idx,await readerCtx().decodeAudioData(await readerState.blobCache.get(idx).arrayBuffer()))}catch{}}}function readerClearReading(){const i=readerState.readingIdx;i>=0&&i=readerState.sentences.length){readerStopPlayback(),readerState.idx=0,readerUpdateProgress();return}const idx=readerState.idx,sentence=readerState.sentences[idx];readerUpdateProgress(),readerSaveResume(),readerState.readingIdx=idx,readerSetStatus(idx,"reading"),readerHighlightSentence(sentence);let buf;try{buf=await readerGetBuffer(idx)}catch(e){toast(e.message||String(e),"error"),readerSetStatus(idx,"pending"),readerStopPlayback();return}if(!readerState.playing||readerState.idx!==idx)return;readerEvictBuffers(idx);const timings=computeWordTimings(sentence.text,buf.duration),ctx=readerCtx();readerStopSource();const src=ctx.createBufferSource();if(src.buffer=buf,src.playbackRate.value=readerState.speed,readerState.normalize){const g=ctx.createGain();g.gain.value=readerComputeGain(idx,buf),src.connect(g),g.connect(ctx.destination)}else src.connect(ctx.destination);readerState.currentSource=src;const t0=ctx.currentTime;readerPrefetch(idx+1),src.onended=()=>{readerState.currentSource===src&&(readerState.currentSource=null,readerState.raf&&(cancelAnimationFrame(readerState.raf),readerState.raf=null),readerSetStatus(idx,"ready"),readerState.readingIdx===idx&&(readerState.readingIdx=-1),readerState.playing&&(readerState.idx++,readerPlayCurrent()))},src.start(0);const tick=()=>{if(readerState.currentSource!==src)return;const elapsed=(ctx.currentTime-t0)*readerState.speed;let active=0;for(let i=timings.length-1;i>=0;i--)if(elapsed>=timings[i].start){active=i;break}readerHighlightWord(sentence,Math.min(active,sentence.words.length-1)),readerState.raf=requestAnimationFrame(tick)};readerState.raf=requestAnimationFrame(tick)}function readerStopSource(){if(readerState.currentSource){try{readerState.currentSource.onended=null,readerState.currentSource.stop(0)}catch{}readerState.currentSource=null}readerState.raf&&(cancelAnimationFrame(readerState.raf),readerState.raf=null)}function readerStopPlayback(){readerState.playing=!1,readerStopSource(),readerClearHighlights(),readerClearReading(),readerUpdatePlayBtn(),typeof window.setNavBusy=="function"&&window.setNavBusy("s-reader",!1)}function readerPlay(){var _a2;if(!readerState.sentences.length){toast("Import a document first","error");return}if(!((_a2=$("reader-voice-select"))!=null&&_a2.value)){toast("Pick a voice first","error");return}readerCtx(),readerState.playing=!0,readerUpdatePlayBtn(),typeof window.setNavBusy=="function"&&window.setNavBusy("s-reader",!0),readerPlayCurrent()}function readerPause(){readerState.playing=!1,readerStopSource(),readerSaveResume(),readerPersistProgress(),readerClearReading(),readerUpdatePlayBtn()}function readerJumpTo(idx){readerState.idx=Math.max(0,Math.min(idx,readerState.sentences.length-1)),readerStopSource(),readerClearHighlights(),readerClearReading(),readerUpdateProgress(),readerSaveResume(),readerState.playing?readerPlayCurrent():readerHighlightSentence(readerState.sentences[readerState.idx])}function readerUpdatePlayBtn(){const btn=$("reader-play");if(!btn)return;const ic=btn.querySelector(".mdi");ic&&(ic.className="mdi "+(readerState.playing?"mdi-pause":"mdi-play"))}function readerUpdateProgress(){const total=readerState.sentences.length,cur=total?readerState.idx+1:0,lbl=$("reader-progress-label");lbl&&(lbl.textContent=cur+" / "+total);const fill=$("reader-progress-fill");fill&&(fill.style.width=(total?readerState.idx/total*100:0)+"%")}function readerSaveResume(){if(!(!readerState.title||!readerState.sentences.length))try{localStorage.setItem(READER_RESUME_KEY,JSON.stringify({title:readerState.title,idx:readerState.idx,total:readerState.sentences.length}))}catch{}}function readerLoadResume(){try{const r=JSON.parse(localStorage.getItem(READER_RESUME_KEY)||"null");if(r&&r.title===readerState.title&&r.idx>0&&r.idx({}))).detail||r.statusText);const fresh=!readerState.savedId;if(readerState.savedId=(await r.json()).id,fresh||!readerState.sourceUploaded){const ext=readerState.mode==="pdf"?"pdf":"txt",body=readerState.mode==="pdf"?readerState.fileBlob:new Blob([readerState.docText||""],{type:"text/plain"});(await fetch(`${READER_API}/${readerState.savedId}/source?ext=${ext}`,{method:"PUT",body})).ok&&(readerState.sourceUploaded=!0)}let uploaded=0;for(let i=0;i{const file=inp.files[0];if(!file)return;if(!(await fetch(`${READER_API}/${id}/source?ext=pdf`,{method:"PUT",body:file})).ok){toast("Re-upload failed","error");return}toast("PDF restored \u2014 opening\u2026","success"),readerState.savedId=id,readerState.sourceUploaded=!0,readerState.fileBlob=file,await readerLoadPdf(file)},inp.click(),toast("PDF source missing \u2014 please re-select the original file","error");return}readerState.fileBlob=sourceBlob,await readerLoadPdf(sourceBlob)}else{const text=sourceBlob?await sourceBlob.text():rec.text||"";readerState.docText=text,await readerLoadText(text)}}catch(e){toast("Open failed: "+(e.message||e),"error");return}if(!readerState.sentences.length){toast("Document had no readable text","error");return}readerState.mode==="text"&&readerState.sentences.forEach((s,i)=>readerSetStatus(i,"pending")),readerState.savedId=id,readerState.sourceUploaded=!0,readerState.savedAudioIdx=new Set,rec.sentenceCount===readerState.sentences.length?(rec.audioIdx||[]).forEach(i=>{io.value===value)){const o=document.createElement("option");o.value=o.textContent=value,sel.appendChild(o)}sel.value=value,id==="reader-voice-select"&&window.VoicePicker&&VoicePicker.setValue(id,value)}}async function readerRenderLibrary(){const card=$("reader-library-card"),list=$("reader-lib-list");if(!card||!list)return;let all=[];try{const r=await fetch(READER_API);r.ok&&(all=(await r.json()).docs||[])}catch{all=[]}if(!all.length){list.innerHTML='
No saved books yet.
';return}list.innerHTML=all.map(rec=>{const total=rec.sentenceCount||0,synth=rec.synthCount||0,readPct=total?Math.round((rec.idx||0)/total*100):0,synthPct=total?Math.round(synth/total*100):0,date=rec.updated?new Date(rec.updated).toLocaleDateString():"";let h=0;const titleStr=rec.title||"Untitled";for(let i=0;i
${escHtml(rec.title)}
${total} sentences${rec.pageCount?" \xB7 "+rec.pageCount+" pg":""}
${readPct}% read \xB7 ${date}
`}).join(""),list.querySelectorAll(".reader-book").forEach(el=>{el.addEventListener("click",e=>{e.target.closest(".reader-book-del")||readerOpenLibraryDoc(el.dataset.id)});const delBtn=el.querySelector(".reader-book-del");delBtn&&delBtn.addEventListener("click",e=>{if(e.stopPropagation(),el.querySelector(".reader-book-del-confirm"))return;const confirmOverlay=document.createElement("div");confirmOverlay.className="reader-book-del-confirm",confirmOverlay.style.cssText="position:absolute; inset:0; background:rgba(0,0,0,0.85); color:#fff; display:flex; flex-direction:column; justify-content:center; align-items:center; border-radius:inherit; z-index:10; padding:12px; text-align:center; box-sizing:border-box;",confirmOverlay.innerHTML=`
Delete Book?
Audio files will be removed.
`,confirmOverlay.addEventListener("click",ce=>ce.stopPropagation()),confirmOverlay.querySelector("#btn-cancel-del").addEventListener("click",ce=>{ce.stopPropagation(),confirmOverlay.remove()}),confirmOverlay.querySelector("#btn-confirm-del").addEventListener("click",async ce=>{ce.stopPropagation(),confirmOverlay.innerHTML='';try{if(!(await fetch(`${READER_API}/${el.dataset.id}`,{method:"DELETE"})).ok)throw new Error("Failed to delete book");toast("Book deleted","success"),readerRenderLibrary()}catch(err){toast(err.message,"error"),confirmOverlay.remove()}}),el.appendChild(confirmOverlay)}),el.addEventListener("dragover",e=>{e.dataTransfer&&[...e.dataTransfer.types||[]].includes("Files")&&(e.preventDefault(),el.style.borderColor="var(--accent)")}),el.addEventListener("dragleave",()=>{el.style.borderColor=""}),el.addEventListener("drop",async e=>{var _a2;if(!(e.dataTransfer&&[...e.dataTransfer.types||[]].includes("Files")))return;e.preventDefault(),e.stopPropagation(),el.style.borderColor="";const f=(_a2=e.dataTransfer.files)==null?void 0:_a2[0];if(!f||!f.type.startsWith("image/")){toast("Please drop an image file","error");return}try{const id=el.dataset.id,r=await fetch(`${READER_API}/${id}/cover`,{method:"PUT",body:f});if(!r.ok)throw new Error(r.statusText);await fetch(`${READER_API}/${id}/progress`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({updated:new Date().toISOString()})}),toast("Cover image updated","success"),readerRenderLibrary()}catch(err){toast("Failed to upload cover: "+(err.message||err),"error")}})}),list.querySelectorAll(".reader-book-del").forEach(btn=>btn.addEventListener("click",e=>{e.stopPropagation(),confirm("Delete this saved document and its audio?")&&readerDeleteLibraryDoc(btn.dataset.del)}))}(_Hb=$("reader-file-input"))==null||_Hb.addEventListener("change",function(){var _a2;const f=(_a2=this.files)==null?void 0:_a2[0];this.value="",readerImportFile(f)}),(_Ib=$("reader-paste-load"))==null||_Ib.addEventListener("click",readerImportPastedText),(_Jb=$("reader-paste-text"))==null||_Jb.addEventListener("keydown",e=>{(e.ctrlKey||e.metaKey)&&e.key==="Enter"&&(e.preventDefault(),readerImportPastedText())}),(_Kb=$("reader-fetch-voices-btn"))==null||_Kb.addEventListener("click",readerFetchVoices),(_Lb=$("reader-backend-select"))==null||_Lb.addEventListener("change",()=>{const sel=$("reader-voice-select");sel&&(sel.innerHTML=''),window.VoicePicker&&(VoicePicker.upgrade("reader-voice-select"),VoicePicker.populate("reader-voice-select",[])),readerUpdateBackendHint()}),(_Mb=$("reader-chunk-mode"))==null||_Mb.addEventListener("change",function(){readerRechunk(this.value)}),(_Nb=$("reader-normalize"))==null||_Nb.addEventListener("change",function(){readerState.normalize=this.checked}),(_Ob=$("reader-play"))==null||_Ob.addEventListener("click",()=>{readerState.playing?readerPause():readerPlay()}),(_Pb=$("reader-stop"))==null||_Pb.addEventListener("click",()=>{readerStopPlayback(),readerState.idx=0,readerSaveResume(),readerUpdateProgress()}),(_Qb=$("reader-prev"))==null||_Qb.addEventListener("click",()=>readerJumpTo(readerState.idx-1)),(_Rb=$("reader-next"))==null||_Rb.addEventListener("click",()=>readerJumpTo(readerState.idx+1)),(_Sb=$("reader-speed"))==null||_Sb.addEventListener("input",function(){readerState.speed=parseFloat(this.value)||1;const lbl=$("reader-speed-label");lbl&&(lbl.textContent=readerState.speed.toFixed(2).replace(/0$/,"")+"\xD7"),readerState.currentSource&&(readerState.currentSource.playbackRate.value=readerState.speed)}),(_Tb=$("reader-zoom-fitw"))==null||_Tb.addEventListener("click",()=>readerApplyZoom("fit-width")),(_Ub=$("reader-zoom-fith"))==null||_Ub.addEventListener("click",()=>readerApplyZoom("fit-height")),(_Vb=$("reader-zoom-two"))==null||_Vb.addEventListener("click",()=>readerApplyZoom("two")),(_Wb=$("reader-zoom-in"))==null||_Wb.addEventListener("click",()=>{readerState.scale=Math.min(4,readerState.scale*1.2),readerApplyZoom("custom")}),(_Xb=$("reader-zoom-out"))==null||_Xb.addEventListener("click",()=>{readerState.scale=Math.max(.2,readerState.scale/1.2),readerApplyZoom("custom")});let _readerSearchTerm="",_readerSearchHits=[],_readerSearchCurrent=-1,_readerSearchTimer=null;const READER_SEARCH_MAX_HITS=2500;function readerSearchTextMap(words){var _a2;let text="";const spans=[];for(let i=0;isp.end>pos&&sp.startsp.wordIndex);idxs.length&&groups.push(idxs),pos=Math.max(pos+1,end)}return groups}function readerRemoveSearchHighlights(){document.querySelectorAll(".reader-search-hit").forEach(el=>el.remove()),document.querySelectorAll(".reader-word.search-hit, .reader-word.search-current").forEach(el=>{el.classList.remove("search-hit","search-current")})}function readerSetSearchStatus(text){const st=$("reader-search-status");st&&(st.textContent=text||"")}function readerClearSearch({clearInput=!1}={}){if(_readerSearchTerm="",_readerSearchHits=[],_readerSearchCurrent=-1,readerRemoveSearchHighlights(),readerSetSearchStatus(""),clearInput){const inp=$("reader-search");inp&&(inp.value="")}}function readerCollectSearchHits(term){var _a2,_b2,_c2;const hits=[],sents=readerState.sentences||[];for(let si=0;si=READER_SEARCH_MAX_HITS)return hits}}return hits}function readerPaintSearchHighlights(){if(readerRemoveSearchHighlights(),!_readerSearchTerm||!_readerSearchHits.length)return;const scale=readerState.scale||1;_readerSearchHits.forEach((hit,hi)=>{var _a2;const s=readerState.sentences[hit.sentenceIndex];if(s){hit.nodes=[];for(const wi of hit.wordIndexes){const w=(_a2=s.words)==null?void 0:_a2[wi];if(w)if(readerState.mode==="pdf"){const pg=readerState.pages[w.page];if(!(pg!=null&&pg.overlay))continue;const el=document.createElement("div");el.className="reader-search-hit"+(hi===_readerSearchCurrent?" search-current":""),Object.assign(el.style,{left:w.x*scale+"px",top:w.top*scale+"px",width:Math.max(w.w,2)*scale+"px",height:Math.max(w.h,2)*scale+"px"}),pg.overlay.appendChild(el),hit.nodes.push(el)}else w.el&&(w.el.classList.add("search-hit"),hi===_readerSearchCurrent&&w.el.classList.add("search-current"),hit.nodes.push(w.el))}}})}function readerUpdateSearchStatus(){var _a2,_b2;if(!_readerSearchTerm){readerSetSearchStatus("");return}if(!((_a2=readerState.sentences)!=null&&_a2.length)){readerSetSearchStatus("No text loaded");return}if(!_readerSearchHits.length){readerSetSearchStatus("0/0");return}const current=_readerSearchCurrent>=0?_readerSearchCurrent+1:1,hit=_readerSearchHits[Math.max(0,_readerSearchCurrent)],capped=_readerSearchHits.length>=READER_SEARCH_MAX_HITS?"+":"",page=readerState.mode==="pdf"?" \xB7 p."+(((_b2=hit==null?void 0:hit.page)!=null?_b2:0)+1):"";readerSetSearchStatus(current+"/"+_readerSearchHits.length+capped+page)}function readerJumpToSearchHit(idx){var _a2;if(!_readerSearchHits.length)return;_readerSearchCurrent=(idx+_readerSearchHits.length)%_readerSearchHits.length,readerPaintSearchHighlights(),readerUpdateSearchStatus();const hit=_readerSearchHits[_readerSearchCurrent],node=(_a2=hit==null?void 0:hit.nodes)==null?void 0:_a2[0];if(node)readerEnsureVisible(node);else if(readerState.mode==="pdf"){const pg=readerState.pages[hit.page];pg!=null&&pg.pageDiv&&(readerRenderPage(hit.page),pg.pageDiv.scrollIntoView({behavior:"smooth",block:"center"}))}}function readerSearchApply(term,{jump=!0}={}){if(term=String(term||"").trim(),!term){readerClearSearch();return}_readerSearchTerm=term,_readerSearchHits=readerCollectSearchHits(term),_readerSearchCurrent=_readerSearchHits.length?0:-1,readerPaintSearchHighlights(),readerUpdateSearchStatus(),jump&&_readerSearchHits.length&&readerJumpToSearchHit(0)}function readerSearchStep(direction){var _a2;const term=((_a2=$("reader-search"))==null?void 0:_a2.value.trim())||_readerSearchTerm;if(!_readerSearchHits.length||term!==_readerSearchTerm){readerSearchApply(term,{jump:!0});return}readerJumpToSearchHit(_readerSearchCurrent+(direction===-1?-1:1))}(_Yb=$("reader-search"))==null||_Yb.addEventListener("keydown",e=>{e.key==="Enter"&&(e.preventDefault(),readerSearchStep(e.shiftKey?-1:1)),e.key==="Escape"&&readerClearSearch({clearInput:!0})}),(_Zb=$("reader-search"))==null||_Zb.addEventListener("input",e=>{clearTimeout(_readerSearchTimer);const term=e.target.value.trim();if(!term){readerClearSearch();return}_readerSearchTimer=setTimeout(()=>readerSearchApply(term,{jump:!0}),160)}),(__b=$("reader-search-prev"))==null||__b.addEventListener("click",()=>readerSearchStep(-1)),(_$b=$("reader-search-next"))==null||_$b.addEventListener("click",()=>readerSearchStep(1)),(_ac=$("reader-search-clear"))==null||_ac.addEventListener("click",()=>readerClearSearch({clearInput:!0})),(_bc=$("reader-save-lib"))==null||_bc.addEventListener("click",readerSaveLibrary),(_cc=$("reader-export-btn"))==null||_cc.addEventListener("click",()=>{var _a2;return readerExport(((_a2=$("reader-export-mode"))==null?void 0:_a2.value)||"page")}),(_dc=$("reader-synth-all"))==null||_dc.addEventListener("click",readerSynthAll),(_ec=$("reader-synth-cancel"))==null||_ec.addEventListener("click",()=>{readerState.synthCancel=!0}),(_fc=$("reader-select-toggle"))==null||_fc.addEventListener("click",()=>{readerState.selecting?readerSetSelecting(!1):(readerClearSelection(),readerSetSelecting(!0))}),(_gc=$("reader-sel-clear"))==null||_gc.addEventListener("click",readerClearSelection),document.addEventListener("keydown",e=>{e.key==="Escape"&&readerState.selecting&&readerSetSelecting(!1)}),(_hc=$("reader-page-from"))==null||_hc.addEventListener("change",readerUpdateScopeLabel),(_ic=$("reader-page-to"))==null||_ic.addEventListener("change",readerUpdateScopeLabel),(_jc=$("reader-doc"))==null||_jc.addEventListener("click",e=>{if(!readerState.selecting||readerState.mode!=="pdf")return;const stat=e.target.closest(".reader-stat");if(!stat)return;const si=parseInt(stat.dataset.si);isNaN(si)||readerPickSentence(si)}),(_kc=$("reader-doc"))==null||_kc.addEventListener("scroll",function(){let t=null;return()=>{t||(t=setTimeout(()=>{t=null,readerRenderVisible()},120))}}()),window.addEventListener("resize",function(){let t=null;return()=>{readerState.mode!=="pdf"||readerState.zoomMode==="custom"||(clearTimeout(t),t=setTimeout(()=>readerApplyZoom(readerState.zoomMode),200))}}()),function(){["reader-dropzone","reader-doc"].map(id=>$(id)).filter(Boolean).forEach(el=>{el.addEventListener("dragover",e=>{e.dataTransfer&&[...e.dataTransfer.types||[]].includes("Files")&&(e.preventDefault(),el.classList.add("dragover"))}),el.addEventListener("dragleave",()=>el.classList.remove("dragover")),el.addEventListener("drop",e=>{var _a2;e.dataTransfer&&[...e.dataTransfer.types||[]].includes("Files")&&(e.preventDefault(),el.classList.remove("dragover"),readerImportFile((_a2=e.dataTransfer.files)==null?void 0:_a2[0]))})})}(),window.showReaderView=function(view){const main=$("reader-main-view"),cast=$("reader-audiobook-panel"),chars=$("reader-charsheets-panel"),lib=$("reader-library-card");main&&(main.hidden=view!=="main"),cast&&(cast.hidden=view!=="cast",view!=="cast"&&(cast.style.display="",cast.style.flexDirection="",cast.style.minHeight=""),view==="cast"&&!cast.innerHTML.trim()&&(cast.innerHTML=`

No active casting session

Open a document in the Reader tab and click "Cast as audiobook" to start extracting characters.


`,cast.className="ab-castpanel-inline card")),chars&&(chars.hidden=view!=="chars",view==="chars"&&!chars.innerHTML.trim()&&(chars.innerHTML=`
Character sheets

This stage will show the live passage-by-passage extraction view, prompt editor, and generated sheets once you start a cast.

`,chars.className="")),lib&&(lib.hidden=view!=="library",view==="library"&&(lib.classList.remove("card"),lib.style.boxShadow="none",lib.style.border="none",lib.style.background="transparent",readerRenderLibrary())),typeof refreshWorkflowCrumbs=="function"&&refreshWorkflowCrumbs(view==="cast"?"cast":view==="chars"?"chars":"source")},window.navReaderView=function(view){window.showReaderView(view)},window.readerJumpToPage=function(pageNum){const pg=parseInt(pageNum,10);if(!pg)return Promise.resolve(!1);typeof navTo=="function"&&navTo("s-reader"),window.showReaderView("main");let tries=0;const waitFrame=()=>new Promise(resolve=>requestAnimationFrame(resolve)),jump=async()=>{var _a2;await waitFrame();const pageState=(_a2=readerState.pages)==null?void 0:_a2[pg-1],pageDiv=pageState==null?void 0:pageState.pageDiv;return pageDiv&&!pageDiv.hidden?(await readerRenderPage(pg-1),pageDiv.scrollIntoView({behavior:"smooth",block:"start",inline:"nearest"}),readerRenderVisible(),!0):tries++<20?(await new Promise(resolve=>setTimeout(resolve,100)),jump()):(typeof toast=="function"&&toast("Source page is not loaded in Reader yet","info"),!1)};return jump()},window.readerOnShow=async function(){const sel=$("reader-backend-select");if(sel&&(!sel.value||sel.options.length<=1)){if(typeof availableTtsBackends=="function"&&!availableTtsBackends().length&&typeof refreshTtsBackendAvailability=="function")try{await refreshTtsBackendAvailability()}catch{}typeof availableTtsBackends=="function"&&availableTtsBackends().length&&(sel.innerHTML=ttsBackendOptions(sel.value),sel.disabled=!1)}readerUpdateBackendHint(),window.VoicePicker&&VoicePicker.upgrade("reader-voice-select"),readerRenderLibrary(),window._readerStartView&&(window.showReaderView(window._readerStartView),window._readerStartView=null)};const AUDIOBOOK_CHUNK_CHARS=3e3,AUDIOBOOK_WARMUP_TIMEOUT_MS=24e4,AUDIOBOOK_ATTRIBUTION_TIMEOUT_MS=6e5,AUDIOBOOK_ATTRIBUTION_RETRY_TIMEOUT_MS=3e5,AUDIOBOOK_RECAST_TIMEOUT_MS=24e4,AUDIOBOOK_RECAST_CONTEXT_CHARS=4200,AUDIOBOOK_RECAST_TARGETS_PER_CALL=6,AUDIOBOOK_DRAFT_AUTOSAVE_MS=5*60*1e3,_audiobook={running:!1,cancel:!1};window._audiobook=_audiobook;let _abDraftAutosaveTimer=null,_abLastServerDraftErrorAt=0;async function audiobookFetchWithTimeout(url,options={},timeoutMs=AUDIOBOOK_ATTRIBUTION_TIMEOUT_MS){const parentSignal=options.signal,ac=new AbortController;let timedOut=!1;const timer=setTimeout(()=>{timedOut=!0,ac.abort()},timeoutMs),onParentAbort=()=>ac.abort(parentSignal==null?void 0:parentSignal.reason);parentSignal&&(parentSignal.aborted?onParentAbort():parentSignal.addEventListener("abort",onParentAbort,{once:!0}));try{return await fetch(url,{...options,signal:ac.signal})}catch(err){if(timedOut){const timeoutErr=new Error(`Timed out after ${Math.ceil(timeoutMs/1e3)}s`);throw timeoutErr.name="TimeoutError",timeoutErr}throw err}finally{clearTimeout(timer),parentSignal&&parentSignal.removeEventListener("abort",onParentAbort)}}function audiobookTimeoutSeconds(timeoutMs){return Math.max(5,Math.round(timeoutMs/1e3))}async function audiobookAttributeStream(body,view,outerSignal,idleTimeoutMs=AUDIOBOOK_ATTRIBUTION_TIMEOUT_MS){const ctl=new AbortController,onAbort=()=>ctl.abort();outerSignal&&(outerSignal.aborted?ctl.abort():outerSignal.addEventListener("abort",onAbort,{once:!0}));let idleTimer=null;const armIdle=()=>{clearTimeout(idleTimer),idleTimer=setTimeout(()=>ctl.abort(),idleTimeoutMs)};try{armIdle();const r=await fetch("/api/attribute-dialogue/stream",{method:"POST",headers:{"Content-Type":"application/json"},signal:ctl.signal,body:JSON.stringify({...body,want_reasoning:!0})});if(!r.ok||!r.body)throw new Error("stream HTTP "+r.status);const reader=r.body.getReader(),dec=new TextDecoder;let buf="",result=null;for(;;){const{done,value}=await reader.read();if(done)break;armIdle(),buf+=dec.decode(value,{stream:!0});let at;for(;(at=buf.indexOf(` `))>=0;){const line=buf.slice(0,at).trim();if(buf=buf.slice(at+2),!line.startsWith("data:"))continue;let d;try{d=JSON.parse(line.slice(5))}catch{continue}if(d.t&&(view!=null&&view.thinking)&&view.thinking(d.t),d.error)throw new Error(d.error);d.done&&(result=d.result||null)}}if(!result)throw new Error("stream ended without result");return result}catch(err){throw(err==null?void 0:err.name)==="AbortError"&&!(outerSignal&&outerSignal.aborted)?new Error("stream idle timeout"):err}finally{clearTimeout(idleTimer),outerSignal&&outerSignal.removeEventListener("abort",onAbort)}}function audiobookDialogueKey(text){return String(text||"").normalize("NFKC").replace(/[»«„“”"‘’'`]+/g,"").replace(/\s+/g," ").trim().toLowerCase()}function audiobookSegmentText(seg){return seg?seg.type==="narration"||!seg.speaker||/^Unknown|Unbekannt/i.test(seg.speaker)?seg.text||"":`"${seg.text||""}"`:""}function _abWordRangeAtPoint(x,y){let node=null,offset=0;if(document.caretRangeFromPoint){const r=document.caretRangeFromPoint(x,y);if(!r)return null;node=r.startContainer,offset=r.startOffset}else if(document.caretPositionFromPoint){const p=document.caretPositionFromPoint(x,y);if(!p)return null;node=p.offsetNode,offset=p.offset}else return null;if(!node||node.nodeType!==3)return null;const text=node.nodeValue||"",isW=ch=>ch!=null&&/[\p{L}\p{N}'’-]/u.test(ch);if(offset>=text.length&&(offset=text.length-1),!isW(text[offset]))if(offset>0&&isW(text[offset-1]))offset--;else return null;let a=offset,b=offset;for(;a>0&&isW(text[a-1]);)a--;for(;b+1!s||s.type==="narration"||/^Unknown|Unbekannt/i.test(s.speaker||""));!cur.length||shortGap&&cur.lengthsegs.slice(start,end).map(audiobookSegmentText).join(" ").trim();let text=render();for(;text.length>AUDIOBOOK_RECAST_CONTEXT_CHARS&&(starttargetEnd)&&(end>targetEnd&&end--,text=render(),!(text.length<=AUDIOBOOK_RECAST_CONTEXT_CHARS));)start=end?"after":"near"}: ${s.speaker}: ${(s.text||"").slice(0,100)}`)}return lines.slice(-12).join(` `)}function audiobookFindReturnedSegment(targetSeg,returned,used){const targetKey=audiobookDialogueKey(targetSeg==null?void 0:targetSeg.text);if(!targetKey)return null;let loose=null;for(let i=0;i=18&&(candKey.includes(targetKey)||targetKey.includes(candKey))&&(loose=loose||{idx:i,seg:cand})}return loose}function audiobookFindReturnedSegmentSequence(targetSeg,returned,used){const targetKey=audiobookDialogueKey(targetSeg==null?void 0:targetSeg.text);if(!targetKey)return null;for(let i=0;ireturned[k])};if(key.length>targetKey.length*1.35+40)break}}return null}function _abStr(v){return v==null?"":typeof v=="string"?v:Array.isArray(v)?v.filter(Boolean).join(", "):String(v)}const _AB_DRAFT_KEY="ttsvc_ab_draft";function _abBookId(){return window.readerState&&readerState.savedId||_audiobook.bookId||null}function _abDraftKey(bookId){return bookId?_AB_DRAFT_KEY+"_"+bookId:_AB_DRAFT_KEY}function _abTextId(text){const s=(text.slice(0,300)+text.slice(-300)).replace(/\s+/g,"");let h=5381;for(let i=0;i>>0;return h.toString(36)+"_"+text.length}function _abSafeFilename(name,fallback="cast"){return(String(name||fallback||"cast").replace(/[\\/:*?"<>|]+/g,"_").replace(/\s+/g,"_").replace(/^_+|_+$/g,"")||fallback||"cast").slice(0,120)}function _abCastMarkdown(data){const payload=data||{},segments=Array.isArray(payload.segments)?payload.segments:[],speakers=[...new Set(segments.filter(s=>(s==null?void 0:s.type)==="dialogue"&&s.speaker).map(s=>String(s.speaker)))].sort(),lines=[`# ${payload.title||"Cast Script"}`,""],metaBits=[];payload.savedAt&&metaBits.push("exported "+new Date(payload.savedAt).toISOString().slice(0,16).replace("T"," ")),metaBits.push(`${speakers.length} character${speakers.length!==1?"s":""}`),metaBits.push(`${segments.length} segment${segments.length!==1?"s":""}`),lines.push(`*${metaBits.join(" \xB7 ")}*`,""),speakers.length&&lines.push(`**Characters:** ${speakers.join(", ")}`,"");let lastPage=null;for(const seg of segments){const text=String((seg==null?void 0:seg.text)||"").trim();if(text){if(seg.page!=null&&seg.page!==lastPage&&(lines.push("---","",`## Page ${seg.page}`,""),lastPage=seg.page),seg.type==="dialogue"){const speaker=String(seg.speaker||"Narrator"),tag=seg.emotion?` *(${seg.emotion})*`:"";lines.push(`**${speaker.toUpperCase()}**${tag}: "${text}"`)}else lines.push(text);lines.push("")}}return lines.join(` `)}function _abStampSegmentPages(segs,pageMarks,text){if(!Array.isArray(segs)||!segs.length||segs.some(s=>s&&s.page!=null))return;const marks=(pageMarks||[]).slice().sort((a,b)=>(a.offset||0)-(b.offset||0)),src=text||"";if(!marks.length||!src)return;let markIdx=0,searchPos=0,cur=null;marks[0].offset<=2&&(cur=marks[0].page+1,markIdx=1);for(const s of segs){const probe=String(s.text||"").trim().slice(0,24),at=probe?src.indexOf(probe,searchPos):-1,pos=at>=0?at:searchPos;for(at>=0&&(searchPos=at+probe.length);markIdx=marks[markIdx].offset;)cur=marks[markIdx].page+1,markIdx++;cur!=null&&(s.page=cur)}}function _abSaveDraft(segs,roster,text,done,total){var _a2;const bookId=_abBookId(),payload={bookId,title:((_a2=window.readerState)==null?void 0:_a2.title)||"",textId:_abTextId(text),segments:segs,roster,pageMarks:_audiobook.pageMarks||[],rehId:_audiobook.rehId||null,done,total,savedAt:Date.now()};try{localStorage.setItem(_abDraftKey(bookId),JSON.stringify(payload))}catch{}bookId&&fetch(`/api/reader/docs/${encodeURIComponent(bookId)}/scripts/cast`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)}).then(r=>{if(!r.ok)throw new Error(r.statusText||`HTTP ${r.status}`)}).catch(err=>{console.warn("[audiobook] server draft autosave failed:",err);const now=Date.now();now-_abLastServerDraftErrorAt>6e4&&(_abLastServerDraftErrorAt=now,typeof toast=="function"&&toast("Server autosave failed. Browser draft is still saved.","error"))})}let _abExportBusy=!1;async function audiobookExportCastMd(){var _a2;if(_abExportBusy)return;_abExportBusy=!0,setTimeout(()=>{_abExportBusy=!1},2e3);const segs=_audiobook.segments||[];if(!segs.length){toast("No cast to export","error");return}const bookId=_abBookId();_audiobook.lastText&&_abSaveDraft(segs,_audiobook.roster||[],_audiobook.lastText,_audiobook.completedChunks||0,_audiobook.completedTotal||0);const title=((_a2=window.readerState)==null?void 0:_a2.title)||_audiobook.title||"audiobook";if(bookId)try{const r=await fetch(`/api/reader/docs/${encodeURIComponent(bookId)}/scripts/cast/export`);if(!r.ok)throw new Error((await r.json().catch(()=>({}))).detail||r.statusText);const blob2=await r.blob(),match=(r.headers.get("Content-Disposition")||"").match(/filename="([^"]+)"/i),name=(match==null?void 0:match[1])||`${_abSafeFilename(title)}_cast.zip`;if(typeof readerDownload=="function")readerDownload(blob2,name);else{const a=document.createElement("a");a.href=URL.createObjectURL(blob2),a.download=name,a.click(),setTimeout(()=>URL.revokeObjectURL(a.href),500)}toast("Exported cast + character sheets (.zip)","success");return}catch{toast("Server export failed, downloading local cast copy","error")}const payload={bookId,title,textId:_audiobook.lastText?_abTextId(_audiobook.lastText):"",segments:segs,roster:_audiobook.roster||[],pageMarks:_audiobook.pageMarks||[],rehId:_audiobook.rehId||null,done:_audiobook.completedChunks||0,total:_audiobook.completedTotal||0,savedAt:Date.now()},blob=new Blob([_abCastMarkdown(payload)],{type:"text/markdown;charset=utf-8"});if(typeof readerDownload=="function")readerDownload(blob,`${_abSafeFilename(title)}_cast.md`);else{const a=document.createElement("a");a.href=URL.createObjectURL(blob),a.download=`${_abSafeFilename(title)}_cast.md`,a.click(),setTimeout(()=>URL.revokeObjectURL(a.href),500)}}function _abStartDraftAutosave(saveNow){if(_abStopDraftAutosave(),typeof saveNow!="function")return;_abDraftAutosaveTimer=setInterval(saveNow,AUDIOBOOK_DRAFT_AUTOSAVE_MS);const saveOnLeave=()=>saveNow();_audiobook._draftSaveOnLeave=saveOnLeave,window.addEventListener("pagehide",saveOnLeave),window.addEventListener("beforeunload",saveOnLeave)}function _abStopDraftAutosave(){_abDraftAutosaveTimer&&clearInterval(_abDraftAutosaveTimer),_abDraftAutosaveTimer=null,_audiobook._draftSaveOnLeave&&(window.removeEventListener("pagehide",_audiobook._draftSaveOnLeave),window.removeEventListener("beforeunload",_audiobook._draftSaveOnLeave),_audiobook._draftSaveOnLeave=null)}async function _abEnsureLibraryBook(){var _a2,_b2,_c2,_d2,_e2,_f2,_g2,_h2;const rs=window.readerState;if(!rs||rs.savedId||!((_a2=rs.sentences)!=null&&_a2.length))return!!(rs!=null&&rs.savedId);if(rs.mode==="pdf"&&!rs.fileBlob)return!1;const meta={title:rs.title||"Untitled",kind:rs.mode,idx:rs.idx,voice:((_b2=$("reader-voice-select"))==null?void 0:_b2.value)||"",backend:((_c2=$("reader-backend-select"))==null?void 0:_c2.value)||"",speed:rs.speed,instruct:((_d2=$("reader-instruct"))==null?void 0:_d2.value.trim())||"",chunkMode:rs.chunkMode,seed:((_e2=$("reader-seed"))==null?void 0:_e2.value.trim())||"",temperature:((_f2=$("reader-temp"))==null?void 0:_f2.value.trim())||"",tts_speed:parseFloat((_g2=$("reader-tts-speed"))==null?void 0:_g2.value)||1,normalize:rs.normalize,sentenceCount:rs.sentences.length,pageCount:((_h2=rs.pages)==null?void 0:_h2.length)||0,synthCount:0,updated:new Date().toISOString()};try{const r=await fetch("/api/reader/docs",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(meta)});if(!r.ok)throw new Error((await r.json().catch(()=>({}))).detail||r.statusText);rs.savedId=(await r.json()).id,_audiobook.bookId=rs.savedId;const ext=rs.mode==="pdf"?"pdf":"txt",body=rs.mode==="pdf"?rs.fileBlob:new Blob([rs.docText||audiobookScopeText()||""],{type:"text/plain"});return(await fetch(`/api/reader/docs/${encodeURIComponent(rs.savedId)}/source?ext=${ext}`,{method:"PUT",body})).ok&&(rs.sourceUploaded=!0),typeof readerRenderLibrary=="function"&&readerRenderLibrary(),!0}catch(err){return console.warn("[audiobook] could not create reader-library autosave target:",err),!1}}async function _abLoadDraftServer(bookId){if(!bookId)return null;for(let attempt=0;attempt<2;attempt++)try{const r=await fetch(`/api/reader/docs/${encodeURIComponent(bookId)}/scripts/cast`);if(!r.ok){if(attempt===0&&(r.status===404||r.status>=500)){await new Promise(resolve=>setTimeout(resolve,650));continue}return null}const d=await r.json();return d&&Array.isArray(d.segments)&&d.segments.length?d:null}catch{attempt===0&&await new Promise(resolve=>setTimeout(resolve,650))}return null}function _abLoadDraft(text){var _a2;const bookId=_abBookId(),textId=_abTextId(text),title=String(((_a2=window.readerState)==null?void 0:_a2.title)||"").trim().toLowerCase();try{if(bookId){const raw2=localStorage.getItem(_abDraftKey(bookId));if(raw2){const d=JSON.parse(raw2);if(d&&Array.isArray(d.segments)&&d.segments.length)return d}}const raw=localStorage.getItem(_AB_DRAFT_KEY);if(raw){const d=JSON.parse(raw);if(d&&Array.isArray(d.segments)&&d.segments.length&&d.textId===textId)return d}let best=null;for(let i=0;ibest.score)&&(best={score,draft:cand})}catch{}}if(best!=null&&best.draft)return best.draft}catch{return null}return null}function _abClearDraft(){const bookId=_abBookId();try{localStorage.removeItem(_abDraftKey(bookId))}catch{}bookId&&fetch(`/api/reader/docs/${encodeURIComponent(bookId)}/scripts/cast`,{method:"DELETE"}).catch(()=>{})}const AB_DIALOGUE_RE=/[«»„“”"‟‚‘’›‹『「<]|(?:^|\n)\s*[—–]\s/;function audiobookHasDialogue(t){return AB_DIALOGUE_RE.test(t||"")}function audiobookDehyphenate(t){return(t||"").replace(/([a-zäöüß])-\s+(?=[a-zäöüßA-ZÄÖÜ])/g,"$1")}const AB_SPEECH_VERBS="(?:sagte|fragte|rief|antwortete|erwiderte|entgegnete|meinte|fl\xFCsterte|wisperte|raunte|murmelte|brummte|knurrte|br\xFCllte|schrie|stammelte|fauchte|zischte|seufzte|lachte|kicherte|befahl|wiederholte|fuhr\\s+fort|said|asked|replied|answered|whispered|murmured|muttered|shouted|cried|called|exclaimed|added|continued)",AB_NOTNAME=new Set(["Der","Die","Das","Den","Dem","Ein","Eine","Einen","Er","Sie","Es","Ich","Du","Wir","Ihr","Man","Und","Aber","Da","Dann","Doch","So","Nun","Jetzt","The","He","She","It","They","A","An","And","But","Then","Now","Sofort","Pl\xF6tzlich","Endlich","Schlie\xDFlich","Stille","Schweigen","Stimme","Stimmen","Frage","Antwort","Gel\xE4chter","Wieder","Gleich","Sogleich","Langsam","Leise","Laut","Kaum","Vielleicht","Nat\xFCrlich","Wirklich","Ja","Nein","Komm","Warte","Halt","Geh","Hier","Dort","Oben","Unten","Schon","Noch","Auch","Nur","Immer","Nie"]),_AB_NAME="([A-Z\xC4\xD6\xDC][A-Za-z\xE4\xF6\xFC\xDF'\\-]+)",AB_PERSON_NOUNS=new Set(["Mann","Frau","Junge","M\xE4dchen","Alte","Alter","Fremde","Fremder","Krieger","Kriegerin","W\xE4chter","Wache","Soldat","Hauptmann","Ork","Ritter","Magier","Magierin","Zwerg","Elf","Elfe","H\xE4ndler","Wirt","Wirtin","Bauer","Priester","Priesterin","Nachbar","Nachbarin","Sklave","Sklavin","Verweser","Inquisitor","General","K\xF6nig","K\xF6nigin","Prinz","Prinzessin","F\xFCrst","F\xFCrstin","Baron","Baronin","Bote","Diener","Dienerin","Knabe","Kind","Reiter","Reiterin","Bogensch\xFCtze","Schmied","Schmiedin","Heiler","Heilerin","Gelehrte","Gelehrter","Kapit\xE4n","Anf\xFChrer","Anf\xFChrerin"]);function audiobookResolveUnknowns(segs,prevTail,roster){var _a2,_b2;const isUnknown=s=>(s==null?void 0:s.type)==="dialogue"&&(!s.speaker||/^Unknown|Unbekannt/i.test(s.speaker)),isNamed=s=>(s==null?void 0:s.type)==="dialogue"&&s.speaker&&!/^Unknown|Unbekannt|Narrator$/i.test(s.speaker),names=(roster||[]).filter(n=>n&&!/^(Narrator|Unknown|Unbekannt)/i.test(n)).sort((a,b)=>b.length-a.length),lastNameIn=text=>{let best=null,bestAt=-1;for(const n of names){const at=text.lastIndexOf(n);at>bestAt&&(bestAt=at,best=n)}if(best)return best;const clause=text.match(new RegExp(_AB_NAME+"[^.!?:]{0,80}\\b(?:und\\s+)?(?:"+AB_SPEECH_VERBS+")[^:]{0,60}:\\s*$"));if(clause&&!AB_NOTNAME.has(clause[1])&&!AB_PERSON_NOUNS.has(clause[1]))return clause[1];const m=[...text.matchAll(/\b(?:[Dd]er|[Dd]ie|[Dd]en|[Dd]em|[Ee]in|[Ee]ine)\s+([A-ZÄÖÜ][a-zäöüß]{2,})\b/g)].map(x=>x[1]).filter(n=>AB_PERSON_NOUNS.has(n));return m.length?m[m.length-1]:null},all=[...prevTail||[],...segs],offset=(prevTail||[]).length,resolved=[];for(let i=offset;i=0&&j>=i-6&&prevDialogues.length<2&&!(((_a2=all[j])==null?void 0:_a2.page)!=null&&s.page!=null&&all[j].page!==s.page);j--)if(((_b2=all[j])==null?void 0:_b2.type)==="dialogue"){if(!isNamed(all[j])){prevDialogues.length=0;break}prevDialogues.push(all[j].speaker)}prevDialogues.length===2&&prevDialogues[0]!==prevDialogues[1]&&(who=prevDialogues[1])}who&&!/^(Narrator|Unknown|Unbekannt)$/i.test(who)&&(s.speaker=who,resolved.push(who))}return resolved}const _AB_TAG_NAME="[A-Z\xC4\xD6\xDC][A-Za-z\xE4\xF6\xFC\xDF'\\-]+",AB_SPEECH_TAG_ONLY_RE=new RegExp("^\\s*[,.;:!?\u2013-]*\\s*(?:(?:"+AB_SPEECH_VERBS+")\\b\\s+(?:er|sie|es|ich|du|wir|ihr|he|she|it|they|"+_AB_TAG_NAME+")|(?:er|sie|es|ich|du|wir|ihr|he|she|it|they|"+_AB_TAG_NAME+")\\s+(?:"+AB_SPEECH_VERBS+")\\b)(?:\\s+(?:mit|in|leise|laut|kalt|heiser|erstickt|ruhig|zornig|w\xFCtend|\xE4ngstlich|sp\xF6ttisch|ver\xE4chtlich|fragend|fl\xFCsternd|schrill|dumpf|slowly|coldly|quietly|softly|angrily|hoarsely)\\b[\\s\\S]*)?[.!?\u2026]*\\s*$","i");function audiobookIsSpeechTagOnly(text){const t=String(text||"").trim();return!t||t.length>180||/[»«„“”"‟‚‘’›‹『「]/.test(t)?!1:AB_SPEECH_TAG_ONLY_RE.test(t)}function audiobookGuessSpeaker(after,before){let m;const ok=n=>n&&!AB_NOTNAME.has(n)?n:null;if(m=new RegExp("^[\\s,;\u2013-]*"+AB_SPEECH_VERBS+"\\s+(?:der|die|das|ein|eine)?\\s*"+_AB_NAME).exec(after||"")){const r=ok(m[1]);if(r)return r}if(m=new RegExp("^[\\s,;\u2013-]*"+_AB_NAME+"\\s+"+AB_SPEECH_VERBS).exec(after||"")){const r=ok(m[1]);if(r)return r}if(m=new RegExp(_AB_NAME+"\\s+"+AB_SPEECH_VERBS+"[\\s:,\u2013-]*$").exec(before||"")){const r=ok(m[1]);if(r)return r}return null}const AB_QUOTE_PAIRS={"\xBB":"\xAB","\xAB":"\xBB","\u201E":"\u201C","\u201C":"\u201D",'"':'"',"\u300C":"\u300D","\u300E":"\u300F","\u2018":"\u2019","\u201A":"\u2018","\u203A":"\u2039","\u2039":"\u203A"},_AB_UNCLOSED_TAG_RE=new RegExp("^\\s*(?:[,;:\u2013-]\\s*)?(?:"+AB_SPEECH_VERBS+"|(?:er|sie|es|ich|du|wir|ihr|he|she|it|they|I|we|you)\\s+"+AB_SPEECH_VERBS+")\\b","i");function _abUnclosedQuoteEnd(raw,closeIdx){const limit=closeIdx>=0?closeIdx:raw.length;for(let i=0;i40)return m[1].length}return closeIdx>=0?closeIdx:raw.length}function _abOrphanClosingQuoteSpan(text,closeIdx,minStart){var _a2;if(!"\xAB\u201D\u2019\u2039\u300D\u300F".includes(text[closeIdx]))return null;const before=text.slice(minStart,closeIdx),prev=((_a2=before.match(/\S(?=\s*$)/))==null?void 0:_a2[0])||"";if(!/[.!?]/.test(prev))return null;const trimmedLen=before.trimEnd().length;let localStart=0;const boundaryRe=/[\n\r]|[.!?:]\s+/g;let m;for(;m=boundaryRe.exec(before);){const next=m.index+m[0].length;next0&&!/[\s([{—–-]/.test(text[i-1]))continue;const raw=text.slice(i+1),closeIdx=raw.indexOf(close),endInRaw=_abUnclosedQuoteEnd(raw,closeIdx),quote=raw.slice(0,endInRaw).trim();if(!quote)continue;const consumedClose=closeIdx>=0&&endInRaw===closeIdx;spans.push({start:i,end:i+1+endInRaw+(consumedClose?1:0),quote}),i=spans[spans.length-1].end-1}if(!spans.length)return[{speaker:"Narrator",type:"narration",text,emotion:""}];const out=[];let last=0;for(let k=0;k{});const rehModel=$("reh-llm-model");return rehModel&&cleanModel&&[...rehModel.options].some(o=>o.value===cleanModel)&&(rehModel.value=cleanModel),{url:cleanUrl,model:cleanModel}}function audiobookScopeText(){var _a2,_b2,_c2;if(_audiobook.pageMarks=[],typeof readerScopeIndices!="function"||!((_a2=readerState==null?void 0:readerState.sentences)!=null&&_a2.length))return"";const idxs=readerScopeIndices(),anchors=[];let lastPage=null;for(const i of idxs){const u=readerState.sentences[i],pg=(_c2=(_b2=u==null?void 0:u.words)==null?void 0:_b2[0])==null?void 0:_c2.page;pg!=null&&pg!==lastPage&&(anchors.push({page:pg,anchor:(u.text||"").trim().slice(0,40)}),lastPage=pg)}let raw="";for(const i of idxs){const u=readerState.sentences[i];u!=null&&u.text&&(raw+=raw?(u.paraStart?` `:" ")+u.text:u.text)}raw=raw.replace(/[ \t]+/g," ").replace(/\n{3,}/g,` `).trim();const text=audiobookDehyphenate(raw);let from=0;for(const a of anchors){const probe=audiobookDehyphenate(a.anchor).slice(0,24);if(!probe)continue;const pos=text.indexOf(probe,from);pos>=0&&(_audiobook.pageMarks.push({offset:pos,page:a.page}),from=pos)}return text}function audiobookProgress(total){let ov=document.getElementById("audiobook-overlay");ov||(ov=document.createElement("div"),ov.id="audiobook-overlay",ov.className="audiobook-overlay",ov.innerHTML=`
Casting audiobook
Analysing\u2026
`,document.body.appendChild(ov),ov.querySelector("#audiobook-cancel").addEventListener("click",()=>{_audiobook.cancel=!0})),ov.hidden=!1;const fill=ov.querySelector("#audiobook-fill"),msg=ov.querySelector("#audiobook-msg");return{update(done,label){fill&&(fill.style.width=done/total*100+"%"),msg&&label&&(msg.textContent=label)},done(){ov.hidden=!0}}}function _abNormalizeColor(color,fallbackName){return clNormalizeColor(color,fallbackName||"Narrator")}function _abDefaultCharacterColor(name){return/^Unknown|Unbekannt/i.test(name||"")?"#a83232":_abNormalizeColor(null,name)}function _abRecordColor(rec,name){var _a2;return _abNormalizeColor((rec==null?void 0:rec.color)||((_a2=rec==null?void 0:rec.sheet)==null?void 0:_a2.color),name||(rec==null?void 0:rec.name))}function highlightText(text,names){if(!text)return"";let html=escHtml(text);return(names||_audiobook&&_audiobook.roster||[]).filter(n=>n&&n.toLowerCase()!=="narrator"&&!/^Unknown|Unbekannt/i.test(n)).sort((a,b)=>b.length-a.length).forEach(name=>{if(name.length<2)return;const safe=name.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),color=_abDefaultCharacterColor(name);html=html.replace(new RegExp(`\\b(${safe})\\b`,"gi"),m=>`${m}`)}),html}function _abShowBusyOverlay(message,withProgress){const ov=document.createElement("div");return ov.className="audiobook-overlay ab-busy-overlay",ov.innerHTML=`
${escHtml(message)}
${withProgress?'
':""}
`,document.body.appendChild(ov),ov.setProgress=(done,total)=>{const fill=ov.querySelector(".ab-busy-fill");fill&&total&&(fill.style.width=Math.round(done/total*100)+"%");const msgEl=ov.querySelector(".ab-busy-msg");msgEl&&(msgEl.textContent=`${message} (${done} / ${total})`)},ov}function _abOpenRecastCharsMenu(anchorEl,bookTitle,existingChars){document.querySelectorAll(".ab-recast-menu").forEach(el=>el.remove());const menu=document.createElement("div");menu.className="ab-recast-menu",menu.innerHTML='',document.body.appendChild(menu);const rect=anchorEl.getBoundingClientRect(),openUp=rect.bottom+90+12>window.innerHeight;menu.style.top=openUp?"":rect.bottom+4+"px",menu.style.bottom=openUp?window.innerHeight-rect.top+4+"px":"",menu.style.left=Math.max(10,rect.right-190)+"px";const close=()=>{menu.remove(),document.removeEventListener("mousedown",onDoc)},onDoc=e=>{!menu.contains(e.target)&&e.target!==anchorEl&&close()};setTimeout(()=>document.addEventListener("mousedown",onDoc),0),menu.querySelector('[data-action="all"]').addEventListener("click",()=>{close(),typeof window.csForReader=="function"&&window.csForReader()}),menu.querySelector('[data-action="selected"]').addEventListener("click",()=>{close(),_abOpenRecastSelectPopup(bookTitle,existingChars)})}function _abOpenRecastSelectPopup(bookTitle,existingChars){const ov=document.createElement("div");ov.className="audiobook-overlay",ov.innerHTML=`
Define selected characters
Only re-reads the passages that mention the characters you pick below, including known aliases, plus a little surrounding context \u2014 everyone else's sheet is left as-is.
`+existingChars.map(c=>'").join("")+'
',document.body.appendChild(ov),ov.querySelector("#ab-recast-cancel").addEventListener("click",()=>ov.remove()),ov.querySelector("#ab-recast-select-all").addEventListener("click",e=>{const boxes=ov.querySelectorAll(".ab-recast-cb"),allChecked=[...boxes].every(b=>b.checked);boxes.forEach(b=>{b.checked=!allChecked}),e.currentTarget.textContent=allChecked?"Select all":"Select none"}),ov.querySelector("#ab-recast-confirm").addEventListener("click",async()=>{const names=[...ov.querySelectorAll(".ab-recast-cb:checked")].map(b=>b.value);if(!names.length){toast("Select at least one character","error");return}ov.remove(),typeof window.audiobookRecastSelectedCharacters=="function"?await window.audiobookRecastSelectedCharacters(names):typeof window.csForReaderSelective=="function"&&await window.csForReaderSelective(names)})}async function audiobookRecastSelectedCharacters(selectedNames){return typeof window.csForReaderSelective=="function"?window.csForReaderSelective(selectedNames):(toast("Character-sheet recast is unavailable right now","error"),null)}window.audiobookRecastSelectedCharacters=audiobookRecastSelectedCharacters;function audiobookCastView(total,llmUrl,defaultModel,isIdle=!1){var _a2,_b2,_c2,_d2,_e2,_f2;const panel=document.getElementById("reader-audiobook-panel");if(!panel)return;if(typeof window.navReaderView=="function")window.navReaderView("cast");else if(typeof window.showReaderView=="function")window.showReaderView("cast");else{const mainView=document.getElementById("reader-main-view");mainView&&(mainView.hidden=!0),panel.hidden=!1}panel.className="ab-castpanel-inline card",panel.style.display="",panel.style.flexDirection="",panel.style.minHeight="",panel.innerHTML=`
Casting audiobook passage 0 / ${total}
Characters found
${[38,62,45,28,54,35].map(w=>`
`).join("")}
`;const AB_DEFAULT_PROMPT=`Du bist ein erfahrener Drehbuchautor und H\xF6rbuch-Regisseur. Deine Aufgabe ist es, einen Auszug aus einem deutschen Roman zu analysieren und ihn perfekt in einzelne Segmente f\xFCr Erz\xE4hler und Dialoge (w\xF6rtliche Rede) zu unterteilen. WICHTIGE DEFINITION VON DIALOG: Text ist NUR dann 'dialogue', wenn er explizit in Anf\xFChrungszeichen steht (z.B. \xBB...\xAB, \u201E...\u201C, "...", \xAB...\xBB, <<...>>) oder mit einem Gedankenstrich (\u2014) beginnt. ALLES ANDERE, einschlie\xDFlich Handlungsbeschreibungen (Inquit-Formeln), inneren Gedanken und Beschreibungen, MUSS als 'narration' (Erz\xE4hler) deklariert werden. Markiere Text NIEMALS als Dialog, nur weil der Name eines Charakters erw\xE4hnt wird! (z.B. "Karyla rannte zur T\xFCr." ist Narration, KEIN Dialog). ABSATZ- UND KAPITELSTRUKTUR: Eine Leerzeile im Text markiert einen echten Absatzwechsel (oder einen Kapitel-/Szenenanfang). Eine sehr kurze, alleinstehende Zeile direkt vor einer Leerzeile (z.B. "1. Kapitel", "Prolog", ein Zahlwort) ist eine Kapitel\xFCberschrift \u2014 immer 'narration'/'Narrator', niemals Dialog. Behalte Leerzeilen als eigenst\xE4ndige narration-Segmente oder als Teil des umgebenden Erz\xE4hler-Segments bei; erfinde daraus keinen Dialog und l\xF6sche sie nicht aus dem rekonstruierten Text. GRAMMATIK-REGELN F\xDCR NARRATION: - Inquit-Formeln / Sprecher-Tags sind IMMER narration: finite Sprechverben wie sagte, fragte, rief, entgegnete, murmelte, fl\xFCsterte, schrie, antwortete + Subjekt/Pronomen/Name (z.B. "murmelte er mit erstickter Stimme.", ", entgegnete Marcian kalt."). - Action Beats sind IMMER narration: Ein Charakter handelt, blickt, geht, lacht, schweigt, hebt die Hand, dreht sich um, usw. \u2014 auch wenn direkt davor/danach Dialog steht. - Grammatische Probe: Wenn der Text eine Erz\xE4hler-Aussage \xDCBER das Sprechen ist (Verb + Sprecher + Art und Weise), ist es narration. Nur die tats\xE4chlich ge\xE4u\xDFerten W\xF6rter innerhalb der Anf\xFChrungszeichen sind dialogue. - Satzfragmente mit f\xFChrendem Komma/Punkt wie ", sagte sie leise." oder ". fragte Uriens." sind niemals eigenst\xE4ndige Dialoge; sie geh\xF6ren als narration zum Erz\xE4hlertext. ANALYSE-REGELN F\xDCR DIE ZUORDNUNG DES SPRECHERS (Sei deduktiv \u2014 arbeite wie ein Lektor): 1. Direkte Zuordnung: "sagte [Name]", "fragte er", "rief sie". L\xF6se Pronomen (er/sie) IMMER zum tats\xE4chlichen Namen auf: Das Pronomen meint die zuletzt genannte Person passenden Geschlechts. 2. Doppelpunkt-Regel: Endet ein Erz\xE4hlersatz mit ":", spricht das Subjekt dieses Satzes das folgende Zitat (z.B. "Dann richtete er sich auf und rief in die Runde:" \u2192 der zuvor genannte Charakter spricht das n\xE4chste Zitat; "Ein anderer fragte verschlafen:" \u2192 dieser andere spricht). 3. Nachgestellte Zuordnung: Der Erz\xE4hlersatz NACH einem Zitat verr\xE4t oft den Sprecher \u2014 auch bei unpers\xF6nlicher Formel: "\xBBWas machst du denn da?\xAB ert\xF6nte es \xFCber ihm. Karyla hatte ihren Hammer weggelegt und war her\xFCbergekommen." \u2192 Karyla sprach das Zitat. 4. Handlungs-Hinweise (Action Beats): Wer unmittelbar vor oder nach einem Zitat handelt, spricht meist (z.B. "Thomas trat ans Fenster. \xBBEs regnet.\xAB"; "Ver\xE4chtlich sah sie ihn an. \xBBDas wirst du schon noch merken.\xAB" \u2192 sie spricht, und "sie" ist die zuletzt genannte Frau). 5. Adressaten-Regel: "X wandte sich an Y" / "X sah Y an" \u2192 X spricht das n\xE4chste Zitat, und Y ist der wahrscheinlichste Antwortende. 6. Das Ping-Pong-Prinzip: Sprechen zwei Personen miteinander, wechseln sie sich strikt ab \u2014 auch \xFCber viele Zitate OHNE Inquit-Formeln hinweg. Verfolge die Kette l\xFCckenlos zur\xFCck zur letzten eindeutigen Nennung und f\xFChre sie bis zum Szenenende fort. In einer Zwei-Personen-Szene ist 'Unknown' fast immer falsch. 7. Gruppen-Dialoge (3+ Personen): An wen richtet sich die Aussage? Passt sie zum Wissen oder Tonfall eines bestimmten Charakters? 8. Namenlose Sprecher: Auch Rollenbezeichnungen aus dem Text sind g\xFCltige Sprecher \u2014 nutze sie statt 'Unknown' (z.B. 'Ork', 'Der Fremde', 'Nachbar', 'W\xE4chter', 'Junge'). 9. Wiederverwendung: Nutze EXAKT die Namen aus der Liste der bekannten Charaktere, FALLS der Name dort aufgef\xFChrt ist. Wenn ein neuer Charakter spricht, extrahiere seinen Namen direkt aus dem Text (z.B. 'Karyla', 'Uriens'). 10. 'Unknown' NUR, wenn eine Zuordnung trotz aller obigen Regeln absolut unm\xF6glich ist. Bevorzuge immer einen genannten Charakter oder eine Rollenbezeichnung gegen\xFCber 'Unknown'. 11. Gedankenstrich-Pause-Regel: Ein " - " MITTEN in einem Zitat ist eine Sprechpause DESSELBEN Sprechers, kein Zitatende \u2014 die Rede geht danach unver\xE4ndert weiter, bis das tats\xE4chliche schlie\xDFende Anf\xFChrungszeichen erscheint. 12. Stimm-Ank\xFCndigung: Erw\xE4hnt ein Erz\xE4hlersatz kurz vor einer noch nicht zugeordneten Zeile explizit die Stimme oder das beginnende Sprechen einer bestimmten Person (z.B. "Marcians Stimme wirkte nicht mehr so fest", "X setzte zum Sprechen an"), geh\xF6rt diese Zeile dieser Person \u2014 nicht 'Unknown'. F\xDCR JEDES SEGMENT GIBST DU FOLGENDES AUS: - speaker: 'Narrator' f\xFCr Narration/Erz\xE4hlertext, oder den EXAKTEN Namen des Charakters f\xFCr gesprochene Dialoge. - type: 'narration' oder 'dialogue' - text: Der EXAKTE, wortw\xF6rtliche Text aus dem Auszug. Bei 'dialogue' ENTFERNST du die umschlie\xDFenden Anf\xFChrungszeichen vollst\xE4ndig (nie nur ein einzelnes \xBB oder \xAB stehen lassen). - emotion: Bei Dialogen 1-2 deutsche W\xF6rter, die den Tonfall beschreiben (z.B. w\xFCtend, fl\xFCsternd, \xE4ngstlich). Bei Narration leer lassen (''). STRIKTE FORMAT- UND TEXTREGELN: - Mische NIEMALS Narration und Dialog im selben Segment! Trenne sie strikt. Wenn ein Zitat durch eine Handlungsanweisung unterbrochen wird (\xBBNein\xAB, sagte sie, \xBBhalt.\xAB), erstelle 3 Segmente: dialogue ("Nein"), narration (", sagte sie, "), dialogue ("halt."). - \xBBText?\xAB und \xBBText!\xAB sind vollst\xE4ndige Dialoge \u2014 das ?\xAB bzw. !\xAB schlie\xDFt das Zitat ab, auch wenn es ungewohnt aussieht. - PDF-/OCR-SCHUTZ: Wenn ein \xBB oder \xAB offensichtlich fehlt, darf dieser eine Fehler NICHT den Rest der Passage als Dialog verschlucken. Schlie\xDFe ein offenes \xBB-Zitat am ersten plausiblen Satzende (? ! .), besonders wenn danach eine Inquit-Formel folgt ("fl\xFCsterte er", "sagte sie", "rief Uriens") oder normale Erz\xE4hlerhandlung weitergeht. - Wenn nur ein schlie\xDFendes \xAB nach einem kurzen Satz steht (z.B. "Der Tod tr\xE4gt rot. \xAB"), behandle den Satz davor als Dialog und entferne das einzelne \xAB aus dem ausgegebenen Text. - Nur wenn ein offenes \xBB wirklich am Ende des Auszugs steht und danach KEINE Erz\xE4hlerhandlung/Inquit-Formel mehr folgt, behandle den Text ab \xBB bis Textende als 'dialogue'. - Lasse NIEMALS W\xF6rter aus, fasse nicht zusammen, dupliziere nichts und erfinde keinen Text. Der kombinierte Text all deiner Segmente MUSS den Originaltext exakt und l\xFCckenlos Wort f\xFCr Wort rekonstruieren \u2014 abgesehen von entfernten \xE4u\xDFeren Dialog-Anf\xFChrungszeichen!`,normalizeCastingPrompt=prompt=>{let p=prompt||AB_DEFAULT_PROMPT;if(p=p.replace("- Wenn ein Auszug mit einem offenen \xBB-Zitat endet (kein schlie\xDFendes \xAB), behandle den Text ab \xBB bis Textende als 'dialogue'.",`- PDF-/OCR-SCHUTZ: Wenn ein \xBB oder \xAB offensichtlich fehlt, darf dieser eine Fehler NICHT den Rest der Passage als Dialog verschlucken. Schlie\xDFe ein offenes \xBB-Zitat am ersten plausiblen Satzende (? ! .), besonders wenn danach eine Inquit-Formel folgt ("fl\xFCsterte er", "sagte sie", "rief Uriens") oder normale Erz\xE4hlerhandlung weitergeht. - Wenn nur ein schlie\xDFendes \xAB nach einem kurzen Satz steht (z.B. "Der Tod tr\xE4gt rot. \xAB"), behandle den Satz davor als Dialog und entferne das einzelne \xAB aus dem ausgegebenen Text. - Nur wenn ein offenes \xBB wirklich am Ende des Auszugs steht und danach KEINE Erz\xE4hlerhandlung/Inquit-Formel mehr folgt, behandle den Text ab \xBB bis Textende als 'dialogue'.`),p=p.replace("- text: Der EXAKTE, wortw\xF6rtliche Text aus dem Auszug. Bei 'dialogue' ENTFERNST du die umschlie\xDFenden Anf\xFChrungszeichen.","- text: Der EXAKTE, wortw\xF6rtliche Text aus dem Auszug. Bei 'dialogue' ENTFERNST du die umschlie\xDFenden Anf\xFChrungszeichen vollst\xE4ndig (nie nur ein einzelnes \xBB oder \xAB stehen lassen)."),p=p.replace("- Lasse NIEMALS W\xF6rter aus, fasse nicht zusammen, dupliziere nichts und erfinde keinen Text. Der kombinierte Text all deiner Segmente MUSS den Originaltext exakt und l\xFCckenlos Wort f\xFCr Wort rekonstruieren!","- Lasse NIEMALS W\xF6rter aus, fasse nicht zusammen, dupliziere nichts und erfinde keinen Text. Der kombinierte Text all deiner Segmente MUSS den Originaltext exakt und l\xFCckenlos Wort f\xFCr Wort rekonstruieren \u2014 abgesehen von entfernten \xE4u\xDFeren Dialog-Anf\xFChrungszeichen!"),/Doppelpunkt-Regel/.test(p)||(p=p.replace(`ANALYSE-REGELN F\xDCR DIE ZUORDNUNG DES SPRECHERS (Sei deduktiv): 1. Direkte Zuordnung: Achte auf W\xF6rter wie "sagte [Name]", "fragte er", "rief sie". L\xF6se Pronomen (er/sie) zum tats\xE4chlichen Namen auf. 2. Handlungs-Hinweise (Action Beats): Wenn ein Charakter eine Handlung ausf\xFChrt und direkt davor/danach w\xF6rtliche Rede steht, spricht meist dieser Charakter (z.B. "Thomas trat ans Fenster. \xBBEs regnet.\xAB"). 3. Das Ping-Pong-Prinzip: Wenn zwei Personen sprechen, wechseln sie sich ab. Verfolge diese Kette l\xFCckenlos zur\xFCck zur letzten eindeutigen Nennung. 4. Gruppen-Dialoge (3+ Personen): An wen richtet sich die Aussage? Passt die Aussage zum Wissen oder Tonfall eines bestimmten Charakters? 5. Wiederverwendung: Nutze EXAKT die Namen aus der Liste der bekannten Charaktere, FALLS der Name dort aufgef\xFChrt ist. Wenn ein neuer Charakter spricht, extrahiere seinen Namen direkt aus dem Text (z.B. 'Karyla', 'Uriens'). 6. Unbekannte Sprecher: Nur wenn eine Zuordnung durch Kontext absolut nicht m\xF6glich ist, verwende 'Unknown'. Rate nicht blind, aber bevorzuge immer einen namentlich genannten Charakter gegen\xFCber 'Unknown'.`,`ANALYSE-REGELN F\xDCR DIE ZUORDNUNG DES SPRECHERS (Sei deduktiv \u2014 arbeite wie ein Lektor): 1. Direkte Zuordnung: "sagte [Name]", "fragte er", "rief sie". L\xF6se Pronomen (er/sie) IMMER zum tats\xE4chlichen Namen auf: Das Pronomen meint die zuletzt genannte Person passenden Geschlechts. 2. Doppelpunkt-Regel: Endet ein Erz\xE4hlersatz mit ":", spricht das Subjekt dieses Satzes das folgende Zitat (z.B. "Dann richtete er sich auf und rief in die Runde:" \u2192 der zuvor genannte Charakter spricht das n\xE4chste Zitat; "Ein anderer fragte verschlafen:" \u2192 dieser andere spricht). 3. Nachgestellte Zuordnung: Der Erz\xE4hlersatz NACH einem Zitat verr\xE4t oft den Sprecher \u2014 auch bei unpers\xF6nlicher Formel: "\xBBWas machst du denn da?\xAB ert\xF6nte es \xFCber ihm. Karyla hatte ihren Hammer weggelegt und war her\xFCbergekommen." \u2192 Karyla sprach das Zitat. 4. Handlungs-Hinweise (Action Beats): Wer unmittelbar vor oder nach einem Zitat handelt, spricht meist (z.B. "Thomas trat ans Fenster. \xBBEs regnet.\xAB"; "Ver\xE4chtlich sah sie ihn an. \xBBDas wirst du schon noch merken.\xAB" \u2192 sie spricht, und "sie" ist die zuletzt genannte Frau). 5. Adressaten-Regel: "X wandte sich an Y" / "X sah Y an" \u2192 X spricht das n\xE4chste Zitat, und Y ist der wahrscheinlichste Antwortende. 6. Das Ping-Pong-Prinzip: Sprechen zwei Personen miteinander, wechseln sie sich strikt ab \u2014 auch \xFCber viele Zitate OHNE Inquit-Formeln hinweg. Verfolge die Kette l\xFCckenlos zur\xFCck zur letzten eindeutigen Nennung und f\xFChre sie bis zum Szenenende fort. In einer Zwei-Personen-Szene ist 'Unknown' fast immer falsch. 7. Gruppen-Dialoge (3+ Personen): An wen richtet sich die Aussage? Passt sie zum Wissen oder Tonfall eines bestimmten Charakters? 8. Namenlose Sprecher: Auch Rollenbezeichnungen aus dem Text sind g\xFCltige Sprecher \u2014 nutze sie statt 'Unknown' (z.B. 'Ork', 'Der Fremde', 'Nachbar', 'W\xE4chter', 'Junge'). 9. Wiederverwendung: Nutze EXAKT die Namen aus der Liste der bekannten Charaktere, FALLS der Name dort aufgef\xFChrt ist. Wenn ein neuer Charakter spricht, extrahiere seinen Namen direkt aus dem Text (z.B. 'Karyla', 'Uriens'). 10. 'Unknown' NUR, wenn eine Zuordnung trotz aller obigen Regeln absolut unm\xF6glich ist. Bevorzuge immer einen genannten Charakter oder eine Rollenbezeichnung gegen\xFCber 'Unknown'.`)),/GRAMMATIK-REGELN FÜR NARRATION/.test(p)||(p=p.replace('Markiere Text NIEMALS als Dialog, nur weil der Name eines Charakters erw\xE4hnt wird! (z.B. "Karyla rannte zur T\xFCr." ist Narration, KEIN Dialog).',`Markiere Text NIEMALS als Dialog, nur weil der Name eines Charakters erw\xE4hnt wird! (z.B. "Karyla rannte zur T\xFCr." ist Narration, KEIN Dialog). GRAMMATIK-REGELN F\xDCR NARRATION: - Inquit-Formeln / Sprecher-Tags sind IMMER narration: finite Sprechverben wie sagte, fragte, rief, entgegnete, murmelte, fl\xFCsterte, schrie, antwortete + Subjekt/Pronomen/Name (z.B. "murmelte er mit erstickter Stimme.", ", entgegnete Marcian kalt."). - Action Beats sind IMMER narration: Ein Charakter handelt, blickt, geht, lacht, schweigt, hebt die Hand, dreht sich um, usw. \u2014 auch wenn direkt davor/danach Dialog steht. - Grammatische Probe: Wenn der Text eine Erz\xE4hler-Aussage \xDCBER das Sprechen ist (Verb + Sprecher + Art und Weise), ist es narration. Nur die tats\xE4chlich ge\xE4u\xDFerten W\xF6rter innerhalb der Anf\xFChrungszeichen sind dialogue. - Satzfragmente mit f\xFChrendem Komma/Punkt wie ", sagte sie leise." oder ". fragte Uriens." sind niemals eigenst\xE4ndige Dialoge; sie geh\xF6ren als narration zum Erz\xE4hlertext.`)),/ABSATZ- UND KAPITELSTRUKTUR/.test(p)||(p=p.replace('Markiere Text NIEMALS als Dialog, nur weil der Name eines Charakters erw\xE4hnt wird! (z.B. "Karyla rannte zur T\xFCr." ist Narration, KEIN Dialog).',`Markiere Text NIEMALS als Dialog, nur weil der Name eines Charakters erw\xE4hnt wird! (z.B. "Karyla rannte zur T\xFCr." ist Narration, KEIN Dialog). ABSATZ- UND KAPITELSTRUKTUR: Eine Leerzeile im Text markiert einen echten Absatzwechsel (oder einen Kapitel-/Szenenanfang). Eine sehr kurze, alleinstehende Zeile direkt vor einer Leerzeile (z.B. "1. Kapitel", "Prolog", ein Zahlwort) ist eine Kapitel\xFCberschrift \u2014 immer 'narration'/'Narrator', niemals Dialog. Behalte Leerzeilen als eigenst\xE4ndige narration-Segmente oder als Teil des umgebenden Erz\xE4hler-Segments bei; erfinde daraus keinen Dialog und l\xF6sche sie nicht aus dem rekonstruierten Text.`)),!/Gedankenstrich-Pause-Regel/.test(p)){const newRules=`11. Gedankenstrich-Pause-Regel: Ein " - " MITTEN in einem Zitat ist eine Sprechpause DESSELBEN Sprechers, kein Zitatende \u2014 die Rede geht danach unver\xE4ndert weiter, bis das tats\xE4chliche schlie\xDFende Anf\xFChrungszeichen erscheint. 12. Stimm-Ank\xFCndigung: Erw\xE4hnt ein Erz\xE4hlersatz kurz vor einer noch nicht zugeordneten Zeile explizit die Stimme oder das beginnende Sprechen einer bestimmten Person (z.B. "Marcians Stimme wirkte nicht mehr so fest", "X setzte zum Sprechen an"), geh\xF6rt diese Zeile dieser Person \u2014 nicht 'Unknown'.`,unknownRuleRe=/(\n\d+\. 'Unknown' NUR,[^\n]*'Unknown'\.)/;unknownRuleRe.test(p)?p=p.replace(unknownRuleRe,`$1 ${newRules}`):p+=` ${newRules}`}return p},rawPrompt=typeof _appSettings!="undefined"&&_appSettings.audiobook_prompt?_appSettings.audiobook_prompt:AB_DEFAULT_PROMPT,globalPrompt=normalizeCastingPrompt(rawPrompt);globalPrompt!==rawPrompt&&typeof _appSettings!="undefined"&&(_appSettings.audiobook_prompt=globalPrompt,fetch("/api/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({audiobook_prompt:globalPrompt})}).catch(()=>{})),panel.querySelector("#ab-cv-prompt-text").value=globalPrompt;const closePanel=()=>{if(panel.hidden=!0,panel.innerHTML="",typeof window.setNavCastingBadge=="function"&&window.setNavCastingBadge(!1),typeof window.navReaderView=="function")window.navReaderView("main");else if(typeof window.showReaderView=="function")window.showReaderView("main");else{const mv=document.getElementById("reader-main-view");mv&&(mv.hidden=!1)}},setStoppingState=()=>{const cancelBtn=panel.querySelector("#ab-cv-cancel");cancelBtn&&(cancelBtn.disabled=!0,cancelBtn.innerHTML=' Stopping...',cancelBtn.title="Stopping the current casting request");const status2=panel.querySelector("#ab-cv-status-msg");status2&&(status2.style.display="inline-block",status2.textContent="Stopping... completed passages will stay saved.")};panel.querySelector("#ab-cv-cancel").addEventListener("click",()=>{if(_audiobook.running){_audiobook.cancel=!0,setStoppingState(),typeof _audiobook.abort=="function"&&_audiobook.abort();return}closePanel()}),panel.querySelector("#ab-cv-prompt-btn").addEventListener("click",()=>{const p=panel.querySelector("#ab-cv-prompt-panel");p.hidden=!p.hidden;const chevron=panel.querySelector("#ab-cv-prompt-chevron");chevron&&(chevron.className=p.hidden?"mdi mdi-chevron-down":"mdi mdi-chevron-up")}),function(){const tBtn=panel.querySelector("#ab-cv-settings-toggle"),sBox=panel.querySelector("#ab-cv-settings"),chev=panel.querySelector("#ab-cv-settings-chevron");function apply(collapsed2){if(sBox&&(sBox.style.display=collapsed2?"none":"flex"),collapsed2){const p=panel.querySelector("#ab-cv-prompt-panel");p&&(p.hidden=!0)}chev&&(chev.className="mdi "+(collapsed2?"mdi-chevron-down":"mdi-chevron-up"))}let collapsed=!1;try{collapsed=localStorage.getItem("ttsvc_ab_settings_collapsed")==="1"}catch{}apply(collapsed),tBtn&&tBtn.addEventListener("click",()=>{collapsed=!collapsed;try{localStorage.setItem("ttsvc_ab_settings_collapsed",collapsed?"1":"0")}catch{}apply(collapsed)})}(),function(){const side=panel.querySelector("#ab-cv-side"),body=panel.querySelector(".ab-cv-body"),cBtn=panel.querySelector("#ab-cv-side-collapse");function apply(collapsed2){side&&side.classList.toggle("is-collapsed",collapsed2),body&&body.classList.toggle("side-collapsed",collapsed2),cBtn&&(cBtn.querySelector(".mdi").className="mdi "+(collapsed2?"mdi-chevron-left":"mdi-chevron-right")),cBtn&&(cBtn.title=collapsed2?"Expand character list":"Collapse to avatars")}let collapsed=!1;try{collapsed=localStorage.getItem("ttsvc_ab_side_collapsed")==="1"}catch{}apply(collapsed),cBtn&&cBtn.addEventListener("click",()=>{collapsed=!collapsed;try{localStorage.setItem("ttsvc_ab_side_collapsed",collapsed?"1":"0")}catch{}apply(collapsed)})}();let savedPrompts=[];try{savedPrompts=JSON.parse(localStorage.getItem("ttsvc_ab_prompts")||"[]")}catch{savedPrompts=[]}const libSelect=panel.querySelector("#ab-cv-prompt-lib"),delBtn=panel.querySelector("#ab-cv-prompt-del"),promptText=panel.querySelector("#ab-cv-prompt-text"),renderPromptLib=(selectedIdx=-1)=>{libSelect.innerHTML=''+savedPrompts.map((p,i)=>``).join(""),selectedIdx>=0?(libSelect.value=selectedIdx,delBtn.style.display="block"):(libSelect.value="",delBtn.style.display="none")};renderPromptLib(),libSelect.addEventListener("change",()=>{const idx=parseInt(libSelect.value),nameInput=panel.querySelector("#ab-cv-prompt-name");!isNaN(idx)&&savedPrompts[idx]?(promptText.value=savedPrompts[idx].prompt,nameInput&&(nameInput.value=savedPrompts[idx].name),delBtn.style.display="block"):(nameInput&&(nameInput.value=""),delBtn.style.display="none")}),delBtn.addEventListener("click",()=>{const idx=parseInt(libSelect.value);isNaN(idx)||confirm("Delete this saved prompt preset?")&&(savedPrompts.splice(idx,1),localStorage.setItem("ttsvc_ab_prompts",JSON.stringify(savedPrompts)),renderPromptLib(),toast("Prompt deleted","success"))}),panel.querySelector("#ab-cv-prompt-save").addEventListener("click",async()=>{const val=promptText.value.trim();if(!val){toast("Prompt is empty","error");return}const name=panel.querySelector("#ab-cv-prompt-name").value.trim()||"Custom Prompt "+(savedPrompts.length+1);let targetIdx=parseInt(libSelect.value);!isNaN(targetIdx)&&savedPrompts[targetIdx]&&savedPrompts[targetIdx].name===name?savedPrompts[targetIdx].prompt=val:(savedPrompts.push({name,prompt:val}),targetIdx=savedPrompts.length-1),localStorage.setItem("ttsvc_ab_prompts",JSON.stringify(savedPrompts)),renderPromptLib(targetIdx),toast("Prompt preset saved","success"),typeof _appSettings!="undefined"&&(_appSettings.audiobook_prompt=val);try{await fetch("/api/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({audiobook_prompt:val})})}catch{}}),(()=>{const localSel=panel.querySelector("#ab-cv-llm-url"),globalSel=document.getElementById("llm-active-url");if(globalSel&&localSel){const currentVal=localSel.value;localSel.innerHTML=globalSel.innerHTML,localSel.value=currentVal||globalSel.value||""}})();const loadModels=()=>{const urlInput2=panel.querySelector("#ab-cv-llm-url"),sel=panel.querySelector("#ab-cv-llm-select");if(!sel||!urlInput2)return;const currentUrl=urlInput2.value.trim(),oldVal=sel.value;sel.innerHTML='',fetch("/api/conversation/llm-models"+(currentUrl?"?url="+encodeURIComponent(currentUrl):"")).then(r=>r.json()).then(d=>{if(d.models&&d.models.length){const preferred=audiobookSafeLlmModel(oldVal||defaultModel);sel.innerHTML=d.models.map(m=>``).join(""),preferred&&d.models.includes(preferred)?sel.value=preferred:oldVal&&d.models.includes(oldVal)&&!audiobookIsRouterModel(oldVal)?sel.value=oldVal:d.models.includes(defaultModel)&&(sel.value=defaultModel)}else sel.innerHTML=``}).catch(()=>{sel.innerHTML=``})};loadModels(),panel.querySelector("#ab-cv-llm-refresh").addEventListener("click",loadModels);const urlInput=panel.querySelector("#ab-cv-llm-url");urlInput&&urlInput.addEventListener("change",loadModels);const applyPromptAndRun=callback=>{const newPrompt=panel.querySelector("#ab-cv-prompt-text").value,choice=audiobookCurrentCastLlm(panel),savedChoice=audiobookSaveLlmChoice(choice.url,choice.model);typeof _appSettings!="undefined"&&(_appSettings.audiobook_prompt=newPrompt),fetch("/api/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({audiobook_prompt:newPrompt})}).finally(()=>{callback&&callback(savedChoice.url,savedChoice.model)})};if(isIdle){const hasSegs=_audiobook.segments&&_audiobook.segments.length>0;if(panel.querySelector("#ab-cv-status-msg").style.display="inline-block",hasSegs){const recastUnkBtn=panel.querySelector("#ab-cv-start-recast-unk"),recastBtn=panel.querySelector("#ab-cv-start-recast");recastUnkBtn.style.display="inline-block",recastBtn.style.display="inline-block",recastUnkBtn.addEventListener("click",()=>applyPromptAndRun(audiobookRecastUnknown)),recastBtn.addEventListener("click",()=>applyPromptAndRun(audiobookCast)),panel.querySelector("#ab-cv-status-msg").textContent="Ready to define characters."}else{const castBtn=panel.querySelector("#ab-cv-start-cast");castBtn.style.display="inline-block",castBtn.addEventListener("click",()=>applyPromptAndRun(audiobookCast))}}else panel.querySelector("#ab-cv-cancel").innerHTML=' Stop Casting';const fill=panel.querySelector("#ab-cv-fill"),count=panel.querySelector("#ab-cv-count"),feed=panel.querySelector("#ab-cv-feed"),chars=panel.querySelector("#ab-cv-chars"),roster=new Map,characterRecords=new Map,identityNames=recOrSheet=>{const s=(recOrSheet==null?void 0:recOrSheet.sheet)||recOrSheet||{},out=new Set,add=(v,opts={})=>clSplitIdentityTokens(v,opts).forEach(x=>out.add(x));return add((recOrSheet==null?void 0:recOrSheet.name)||s.name),["aliases","first_name","last_name","full_name","title"].forEach(k=>add(s[k],{aliases:k==="aliases"})),[...out]};let _hlVer=0,_hlCache={ver:-1,rosterSize:-1,regex:null,byLower:new Map};const registerCharacterRecord=rec=>{if(rec!=null&&rec.name){_hlVer++;for(const n of identityNames(rec))characterRecords.set(n.toLowerCase(),rec)}},recordForName=name=>characterRecords.get(String(name||"").toLowerCase())||null,colorFor=(name,rec)=>{const key=String(name||"").toLowerCase(),stored=rec||characterRecords.get(key),color=stored?_abRecordColor(stored,name):_abDefaultCharacterColor(name);return roster.has(name)?roster.get(name).color=color:roster.set(name,{count:0,color}),roster.get(name).color},_abAvatarHtml=(name,color)=>{var _a3;const img=(_a3=recordForName(name))==null?void 0:_a3.image,c=color||colorFor(name);return img?``:`${escHtml((name||"?")[0].toUpperCase())}`},setCharacterColor=(name,color)=>{const safe=_abNormalizeColor(color,name),info=roster.get(name);info&&(info.color=safe);const key=String(name||"").toLowerCase(),rec=characterRecords.get(key);return rec&&(rec.color=safe,rec.sheet&&(rec.sheet.color=safe)),safe},highlightText2=text=>{if(!text)return"";let html=escHtml(text);if(_hlCache.ver!==_hlVer||_hlCache.rosterSize!==roster.size){const extraNames=[];new Set([...characterRecords.values()]).forEach(rec=>extraNames.push(...identityNames(rec)));const seen=new Set,names=[];for(const n of[...roster.keys(),...extraNames]){const k=n.toLowerCase();n.length<2||k==="narrator"||/^unknown|unbekannt/i.test(k)||seen.has(k)||(seen.add(k),names.push(n))}names.sort((a,b)=>b.length-a.length);const pattern=names.map(n=>n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")).join("|");_hlCache={ver:_hlVer,rosterSize:roster.size,regex:pattern?new RegExp(`\\b(${pattern})\\b`,"gi"):null,byLower:new Map(names.map(n=>[n.toLowerCase(),n]))}}return _hlCache.regex&&(html=html.replace(_hlCache.regex,match=>{const canon=_hlCache.byLower.get(match.toLowerCase())||match;return`${match}`})),html},feedWrap=panel.querySelector(".ab-cv-feed-wrap"),jumpBtn=panel.querySelector("#ab-cv-jump-btn"),_abTopbar=document.createElement("div");_abTopbar.className="ab-cv-topbar",feedWrap.insertBefore(_abTopbar,feedWrap.firstChild);const _abBar=document.createElement("div");_abBar.className="ab-char-bar",_abBar.hidden=!0,_abTopbar.appendChild(_abBar);const _abEditToolbar=document.createElement("div");_abEditToolbar.className="ab-edit-toolbar",_abEditToolbar.innerHTML=` `,_abTopbar.appendChild(_abEditToolbar);const _abPageNav=document.createElement("div");_abPageNav.className="ab-page-nav",_abPageNav.hidden=!0,_abPageNav.innerHTML=` `,_abTopbar.appendChild(_abPageNav);const _abFontCtl=document.createElement("div");_abFontCtl.className="ab-font-ctl",_abFontCtl.innerHTML=` `,_abTopbar.appendChild(_abFontCtl);const _abApplyFont=()=>{let v=1;try{v=parseFloat(localStorage.getItem("ttsvc_reh_stage_scale"))||1}catch{}feed.style.setProperty("--reh-stage-scale",String(v))},_abFontStep=d=>{let v=1;try{v=parseFloat(localStorage.getItem("ttsvc_reh_stage_scale"))||1}catch{}v=Math.round(Math.min(2,Math.max(.7,v+d))*100)/100;try{localStorage.setItem("ttsvc_reh_stage_scale",String(v))}catch{}_abApplyFont(),typeof rehApplyStageFont=="function"&&rehApplyStageFont()};_abFontCtl.querySelector(".ab-font-dec").addEventListener("click",()=>_abFontStep(-.1)),_abFontCtl.querySelector(".ab-font-inc").addEventListener("click",()=>_abFontStep(.1)),_abApplyFont();let _abCharRows=[],_abNavIdx=0,_abDetailEl=null,_abCurrentPageNum=1;const _abClearHL=()=>{feed.querySelectorAll(".ab-cv-row.ab-char-hl, .ab-cv-row.ab-char-focus").forEach(r=>r.classList.remove("ab-char-hl","ab-char-focus"))},_abCloseProfile=()=>{_abDetailEl&&(_abDetailEl.remove(),_abDetailEl=null),feed.style.display="",jumpBtn&&typeof _userScrolled!="undefined"&&(jumpBtn.hidden=!_userScrolled),_abCharRows.forEach(r=>r.classList.add("ab-char-hl"));const btn=_abBar.querySelector(".ab-char-bar-profile");btn&&(btn.innerHTML=' Profil')},_abCloseBar=()=>{_abBar.hidden=!0,_abBar.innerHTML="",_abCloseProfile(),_abClearHL(),_abCharRows=[],chars.querySelectorAll(".ab-char-item").forEach(el=>el.classList.remove("is-active")),typeof _abSyncTopbar=="function"&&_abSyncTopbar()},_abNav=(dir,pool)=>{const rows=pool||_abCharRows;if(!rows.length)return;_abNavIdx=(_abNavIdx+dir+rows.length)%rows.length,feed.querySelectorAll(".ab-cv-row.ab-char-focus").forEach(r=>r.classList.remove("ab-char-focus")),rows[_abNavIdx].classList.add("ab-char-focus"),rows[_abNavIdx].scrollIntoView({behavior:"smooth",block:"center"});const pos=_abBar.querySelector(".ab-char-bar-pos");pos&&(pos.textContent=_abNavIdx+1+" / "+rows.length)},_abRefreshCharacterColors=name=>{const names=name?[name]:[...roster.keys()];for(const n of names)colorFor(n);feed.querySelectorAll(".ab-cv-row").forEach(row=>{const seg=row.__seg;if(!seg)return;const speakerName=seg.type!=="dialogue"||!seg.speaker||seg.speaker.toLowerCase()==="narrator"?"Narrator":seg.speaker,c=colorFor(speakerName),spk=row.querySelector(".ab-cv-spk"),txt=row.querySelector(".ab-cv-txt");spk&&(!name||speakerName.toLowerCase()===name.toLowerCase())&&(spk.style.color=c),txt&&(txt.innerHTML=highlightText2(seg.text||""))}),renderRoster();const selected=_abBar.hidden?null:_abBar.dataset.charName;if(selected){const dot=_abBar.querySelector(".ab-char-bar-dot");dot&&(dot.style.background=colorFor(selected))}},_abShowProfile=async name=>{var _a3,_b3,_c3,_d3,_e3,_f3,_g2;_abCloseProfile(),feed.style.display="none",jumpBtn&&(jumpBtn.hidden=!0);const profileBtn=_abBar.querySelector(".ab-char-bar-profile");profileBtn&&(profileBtn.innerHTML=' Skript');const title=((_a3=window.readerState)==null?void 0:_a3.title)||"";let rec=null;try{const all=typeof clGetAllByTagOrBook=="function"?await clGetAllByTagOrBook(title):typeof clGetAll=="function"?await clGetAll():[];for(const r of all||[])registerCharacterRecord(r);rec=recordForName(name)}catch{}const detail=document.createElement("div");if(detail.className="ab-char-detail-panel",_abDetailEl=detail,!rec){detail.innerHTML='
Kein Charakterblatt \u2013 zuerst \u201ECast Characters" ausf\xFChren.
',feedWrap.appendChild(detail),detail.querySelector(".ab-char-detail-back").addEventListener("click",_abCloseProfile);return}registerCharacterRecord(rec);const sh=rec.sheet||{},charColor=setCharacterColor(rec.name,rec.color||sh.color||colorFor(rec.name,rec)),accent2=clHslToHex((clNameHue(rec.name)+40)%360,58,30),tier=String(sh.tier||"").toLowerCase(),tierLabel=tier==="main"?"Hauptcharakter":tier==="supporting"?"Nebencharakter":"Nebenfigur",voiceId=rec.voice?typeof rec.voice=="object"?rec.voice.id||"":String(rec.voice):"",pct=((_b3=rec.sheet)==null?void 0:_b3.moral_alignment_score)!=null?Math.max(0,Math.min(100,rec.sheet.moral_alignment_score)):null,arcMap={"good-to-bad":"\u2198 Entwicklung zum B\xF6sen","bad-to-good":"\u2197 Wandel zum Guten",complex:"\u2195 Komplex","stable-good":"\u2192 Stabil gut","stable-bad":"\u2192 Stabil b\xF6se",neutral:"\u2192 Neutral"},avatarHtml=rec.image?`
${escHtml(rec.name)}
`:`
${escHtml((rec.name||"?")[0].toUpperCase())}
`,field=(lbl,val,sk)=>{const v=_abStr(val);return!v&&!sk?"":`
${lbl}
${escHtml(v)}
`},srcItems=Array.isArray(rec.sources)?rec.sources:[],sourcesHtml=srcItems.length?`
${srcItems.map(s=>`Seite ${s.page||"?"}`).join("")}
`:"";detail.innerHTML=`
${avatarHtml}
${escHtml(rec.name)}
${_abStr(sh.full_name)&&_abStr(sh.full_name).toLowerCase()!==String(rec.name||"").toLowerCase()?`
${escHtml(_abStr(sh.full_name))}
`:""} ${_abStr(sh.title)?`
${escHtml(_abStr(sh.title))}
`:""} ${_abStr(sh.aliases)?`
auch bekannt als ${escHtml(_abStr(sh.aliases))}
`:""} ${_abStr(sh.archetype)?`
${escHtml(_abStr(sh.archetype))}
`:""}
${tierLabel}
${voiceId?escHtml(voiceId):'Noch keine Stimme'}
${pct!=null?`
B\xF6se
Gut
${arcMap[sh.arc_direction||"neutral"]||"\u2192"} \xB7 ${pct>=70?"Rechtschaffen":pct<=30?"B\xF6se":"Ambivalent"} (${pct}/100)
`:""}
${field("Voller Name",sh.full_name,"full_name")}${field("Vorname",sh.first_name,"first_name")}${field("Nachname",sh.last_name,"last_name")}${field("Geschlecht",sh.gender,"gender")}${field("Titel",sh.title,"title")}${field("Beruf / Rolle",sh.profession,"profession")}${field("Auch bekannt als",sh.aliases,"aliases")}
${field("K\xF6rperlich",sh.physical,"physical")}${field("Kleidung",sh.clothing,"clothing")}
${field("Eigenheiten",sh.mannerisms,"mannerisms")}${field("Stimme & Sprache",sh.voice_pattern,"voice_pattern")}
${field("Hintergrund",sh.backstory,"backstory")}${field("Motivation",sh.motivation,"motivation")}
${field("Fertigkeiten",sh.skills,"skills")}${field("Besondere F\xE4higkeiten",sh.capabilities,"capabilities")}
${field("",sh.relationships,"relationships")}
${sourcesHtml}
`,feedWrap.appendChild(detail),detail.querySelector(".ab-char-detail-back").addEventListener("click",_abCloseProfile),(_c3=detail.querySelector(".ab-cd-color input"))==null||_c3.addEventListener("input",async e=>{var _a4;const color=setCharacterColor(rec.name,e.target.value);rec.color=color,rec.sheet||(rec.sheet={}),rec.sheet.color=color,(_a4=detail.querySelector(".lcd-header"))==null||_a4.style.setProperty("--lc1",color);const av=detail.querySelector(".lcd-avatar");av&&!av.querySelector("img")&&(av.style.background=color),_abRefreshCharacterColors(rec.name),clearTimeout(_audiobook._colorSaveTimer),_audiobook._colorSaveTimer=setTimeout(async()=>{try{typeof clPut=="function"&&await clPut(rec)}catch{}},400)}),(_d3=detail.querySelector(".ab-cd-pick"))==null||_d3.addEventListener("click",()=>{typeof _openVoicePicker=="function"&&_openVoicePicker(detail.querySelector(".lcd-voice-top"),rec,async()=>{const up=(typeof clGetAll=="function"?await clGetAll():[]).find(r=>r.id===rec.id);up&&(_abCloseProfile(),_abShowProfile(up.name))})}),(_e3=detail.querySelector(".ab-cd-auto"))==null||_e3.addEventListener("click",async()=>{if(typeof _autoAssignVoice=="function"){await _autoAssignVoice(rec);const up=(typeof clGetAll=="function"?await clGetAll():[]).find(r=>r.id===rec.id);up&&(_abCloseProfile(),_abShowProfile(up.name))}}),(_f3=detail.querySelector(".ab-cd-online"))==null||_f3.addEventListener("click",()=>{typeof _charSearchOnline=="function"&&_charSearchOnline(rec)}),(_g2=detail.querySelector(".ab-cd-refine-btn"))==null||_g2.addEventListener("click",async()=>{const name2=String(rec.name||"").trim();if(name2){if(typeof window.audiobookRecastSelectedCharacters=="function")return window.audiobookRecastSelectedCharacters([name2]);if(typeof window.csForReaderSelective=="function")return window.csForReaderSelective([name2]);toast("Character refinement is unavailable right now","error")}});let _saveTimer=null;const editBtn=detail.querySelector(".ab-cd-edit-btn");editBtn==null||editBtn.addEventListener("click",function(){var _a4;const editing=detail.classList.toggle("ab-cd-editing");this.innerHTML=editing?'':'',this.title=editing?"Fertig":"Charakterblatt bearbeiten",detail.querySelectorAll(".ab-editable").forEach(el=>{el.contentEditable=editing?"true":"false",editing&&el.addEventListener("input",function(){clearTimeout(_saveTimer),_saveTimer=setTimeout(async()=>{rec.sheet||(rec.sheet={}),rec.sheet[el.dataset.sk]=el.textContent.trim(),["aliases","first_name","last_name","full_name","title"].includes(el.dataset.sk)&®isterCharacterRecord(rec),typeof clPut=="function"&&await clPut(rec)},900)})}),editing&&((_a4=detail.querySelector(".ab-editable"))==null||_a4.focus())}),detail.querySelectorAll(".ab-cd-src").forEach(el=>{el.addEventListener("click",()=>{const pg=parseInt(el.dataset.page,10);pg&&(typeof navTo=="function"&&navTo("s-reader"),setTimeout(()=>{var _a4,_b4;const pages=(_a4=window.readerState)==null?void 0:_a4.pages;pages&&pages.length>=pg&&((_b4=pages[pg-1])!=null&&_b4.pageDiv)?pages[pg-1].pageDiv.scrollIntoView({behavior:"smooth",block:"start"}):typeof toast=="function"&&toast('Buch im \u201EVorlesen"-Bereich \xF6ffnen, dann nochmal klicken',"info")},300))})})},_abSelectChar=name=>{if(!_abBar.hidden&&_abBar.dataset.charName===name){_abCloseBar();return}_abCloseBar(),chars.querySelectorAll(".ab-char-item").forEach(el=>el.classList.toggle("is-active",el.dataset.name===name)),_abCharRows=Array.from(feed.querySelectorAll(".ab-cv-row")).filter(r=>{var _a3;return((_a3=r.__seg)==null?void 0:_a3.speaker)&&r.__seg.speaker.toLowerCase()===name.toLowerCase()}),_abNavIdx=0;const color=colorFor(name);_abBar.dataset.charName=name,_abBar.hidden=!1,_abSyncTopbar(),_abBar.innerHTML=`${(name||"?")[0].toUpperCase()}
${escHtml(name)} ${_abCharRows.length} Zeilen
1 / ${_abCharRows.length}
`,_abCharRows.forEach(r=>r.classList.add("ab-char-hl")),_abCharRows.length&&(_abCharRows[0].classList.add("ab-char-focus"),_abCharRows[0].scrollIntoView({behavior:"smooth",block:"center"})),_abBar.querySelector(".ab-char-bar-prev").addEventListener("click",()=>_abNav(-1)),_abBar.querySelector(".ab-char-bar-next").addEventListener("click",()=>_abNav(1)),_abBar.querySelector(".ab-char-bar-profile").addEventListener("click",()=>{_abDetailEl?_abCloseProfile():_abShowProfile(name)}),_abBar.querySelector(".ab-char-bar-close").addEventListener("click",_abCloseBar);const inp=_abBar.querySelector(".ab-char-bar-search");let _st=null;const _doSearch=jump=>{const q=inp.value.trim().toLowerCase(),pool=q?_abCharRows.filter(r=>{var _a3,_b3;return(((_a3=r.__seg)==null?void 0:_a3.text)||((_b3=r.querySelector(".ab-cv-txt"))==null?void 0:_b3.textContent)||"").toLowerCase().includes(q)}):_abCharRows;feed.querySelectorAll(".ab-cv-row.ab-char-focus").forEach(r=>r.classList.remove("ab-char-focus")),pool.length?(jump?_abNavIdx=(_abNavIdx+1)%pool.length:_abNavIdx=0,pool[_abNavIdx].classList.add("ab-char-focus"),pool[_abNavIdx].scrollIntoView({behavior:"smooth",block:"center"}),_abBar.querySelector(".ab-char-bar-pos").textContent=_abNavIdx+1+" / "+pool.length):_abBar.querySelector(".ab-char-bar-pos").textContent="0 Treffer"};inp.addEventListener("input",()=>{clearTimeout(_st),_st=setTimeout(()=>_doSearch(!1),200)}),inp.addEventListener("keydown",e=>{e.key==="Enter"&&(e.preventDefault(),_doSearch(!0))})};let _abRosterFilter="",_abRosterSort="count";const renderRoster=()=>{const q=_abRosterFilter.trim().toLowerCase();let items=[...roster.entries()].filter(([n,info])=>info.count>0&&(!q||n.toLowerCase().includes(q)));if(items.sort(_abRosterSort==="alpha"?(a,b)=>a[0].localeCompare(b[0]):(a,b)=>b[1].count-a[1].count),!items.length){chars.innerHTML=`${q?"No matches":"reading\u2026"}`;return}const prev=_abBar.hidden?null:_abBar.dataset.charName;chars.innerHTML=items.map(([n,info])=>{var _a3;const color=info.color||colorFor(n);return`
${_abAvatarHtml(n,color)} ${escHtml(n)} ${((_a3=roster.get(n))==null?void 0:_a3.count)||0}
`}).join(""),chars.querySelectorAll(".ab-char-item").forEach(item=>{item.addEventListener("click",()=>_abSelectChar(item.dataset.name))}),chars.querySelectorAll(".ab-char-alias-btn").forEach(btn=>{btn.addEventListener("click",e=>{e.stopPropagation(),_abOpenAliasPopup(btn.dataset.name,btn)})})};(()=>{const searchInp=panel.querySelector("#ab-cv-side-search"),sortSel=panel.querySelector("#ab-cv-side-sort");try{_abRosterSort=localStorage.getItem("ttsvc_ab_side_sort")||"count"}catch{}sortSel&&(sortSel.value=_abRosterSort);let _t2=null;searchInp==null||searchInp.addEventListener("input",()=>{clearTimeout(_t2),_t2=setTimeout(()=>{_abRosterFilter=searchInp.value,renderRoster()},120)}),sortSel==null||sortSel.addEventListener("change",()=>{_abRosterSort=sortSel.value;try{localStorage.setItem("ttsvc_ab_side_sort",_abRosterSort)}catch{}renderRoster()})})();let _abAliasPopup=null;function _abCloseAliasPopup(){_abAliasPopup&&(_abAliasPopup.remove(),_abAliasPopup=null)}function _abOpenAliasPopup(name,anchorEl){_abCloseAliasPopup();const otherNames=[...roster.entries()].filter(([n,info])=>info.count>0&&n.toLowerCase()!==name.toLowerCase()&&!/^Narrator$/i.test(n)&&!/^Unknown|Unbekannt/i.test(n)).sort((a,b)=>b[1].count-a[1].count),listId="ab-alias-roster-list",el=document.createElement("div");el.className="ab-alias-popup",el.innerHTML=`
Also known as \u2014 ${escHtml(name)}
${otherNames.map(([n,info])=>``).join("")} ${otherNames.length?`
Or merge with an already-found character:
${otherNames.slice(0,8).map(([n,info])=>``).join("")}
`:""}
`,document.body.appendChild(el);const rect=anchorEl.getBoundingClientRect();el.style.left=Math.min(rect.left,window.innerWidth-280)+"px",el.style.top=Math.min(rect.bottom+4,window.innerHeight-340)+"px";const inp=el.querySelector(".ab-alias-popup-inp");setTimeout(()=>inp.focus(),30);const otherByLower=new Map(otherNames.map(([n])=>[n.toLowerCase(),n])),doSave=async mergeName=>{var _a3,_b3;const alias=mergeName||inp.value.trim();if(!alias){_abCloseAliasPopup();return}const mergeTarget=otherByLower.get(alias.toLowerCase());if(_abCloseAliasPopup(),mergeTarget){const busy=_abShowBusyOverlay(`Merging "${mergeTarget}" into ${name}\u2026`,!0);await new Promise(r=>requestAnimationFrame(r));try{const book=((_a3=window.readerState)==null?void 0:_a3.title)||"",rec=await clUpsert(book,{name,aliases:alias});rec&&(registerCharacterRecord(rec),_hlCache&&(_hlCache.ver=-1));const{changed:n,arr}=_abMergeCharacters(mergeTarget,name);n&&(await _abRedrawSegmentsChunked(arr,(done,total2)=>busy.setProgress(done,total2)),_abPersistManualEdit()),toast(n?`Merged "${mergeTarget}" into ${name} (${n} line${n!==1?"s":""})`:`"${alias}" added as an alias for ${name}`,"success")}catch(err){toast("Could not merge: "+(err.message||err),"error")}finally{busy.remove()}}else try{const book=((_b3=window.readerState)==null?void 0:_b3.title)||"",rec=await clUpsert(book,{name,aliases:alias});rec&&(registerCharacterRecord(rec),_hlCache&&(_hlCache.ver=-1)),renderRoster(),toast(`"${alias}" added as an alias for ${name}`,"success")}catch(err){toast("Could not save alias: "+(err.message||err),"error")}};el.querySelector(".ab-alias-save").addEventListener("click",()=>doSave()),el.querySelector(".ab-alias-cancel").addEventListener("click",()=>_abCloseAliasPopup()),el.querySelectorAll(".ab-alias-merge-opt").forEach(btn=>{btn.addEventListener("click",()=>doSave(btn.dataset.name))}),inp.addEventListener("keydown",e=>{e.key==="Enter"?(e.preventDefault(),doSave()):e.key==="Escape"&&(e.preventDefault(),_abCloseAliasPopup())}),setTimeout(()=>{document.addEventListener("click",function onDoc(e){!el.contains(e.target)&&e.target!==anchorEl&&(_abCloseAliasPopup(),document.removeEventListener("click",onDoc))})},0),_abAliasPopup=el}function _abMergeCharacters(fromName,intoName){const active=_abActiveSegments();if(!active.arr.length)return{changed:0,arr:active.arr};_abPushEditState(active.key,active.arr);let changed=0;for(const s of active.arr)s.type==="dialogue"&&s.speaker&&s.speaker.toLowerCase()===fromName.toLowerCase()&&(s.speaker=intoName,changed++);return{changed,arr:active.arr}}(async()=>{var _a3;const title=((_a3=window.readerState)==null?void 0:_a3.title)||"";try{const records=typeof clGetAllByTagOrBook=="function"?await clGetAllByTagOrBook(title):typeof clGetAll=="function"?await clGetAll():[];for(const rec of records||[])if(rec!=null&&rec.name){registerCharacterRecord(rec);for(const alias of identityNames(rec)){const rosterName=[...roster.keys()].find(n=>n.toLowerCase()===alias.toLowerCase());rosterName&&setCharacterColor(rosterName,_abRecordColor(rec,rec.name))}}_abRefreshCharacterColors()}catch{}})();const MAXROWS=Number.POSITIVE_INFINITY;let _userScrolled=!1;feed.addEventListener("scroll",()=>{var _a3;const atBottom=feed.scrollTop+feed.clientHeight>=feed.scrollHeight-80;_userScrolled=!atBottom,jumpBtn&&(jumpBtn.hidden=atBottom);const labels=Array.from(feed.querySelectorAll(".ab-cv-page-label[data-page]"));let current=_abCurrentPageNum;for(const label of labels){const r=(_a3=label.closest(".ab-cv-page"))==null?void 0:_a3.getBoundingClientRect(),fr=feed.getBoundingClientRect();if(r&&r.top<=fr.top+80)current=Number(label.dataset.page)||current;else break}current&&_abSetCurrentPage(current)},{passive:!0});const _scrollToBottom=()=>{feed.scrollTop=feed.scrollHeight};jumpBtn&&jumpBtn.addEventListener("click",()=>{_userScrolled=!1,jumpBtn.hidden=!0,_scrollToBottom()});const trim=()=>{if(_userScrolled)return;let rows=feed.querySelectorAll(".ab-cv-row, .ab-cv-note, .ab-cv-divider");for(;rows.length>MAXROWS;){const first=rows[0],page=first.closest(".ab-cv-page");first.remove(),page&&!page.querySelector(".ab-cv-row, .ab-cv-note, .ab-cv-divider")&&page.remove(),rows=feed.querySelectorAll(".ab-cv-row, .ab-cv-note, .ab-cv-divider")}_scrollToBottom()};let _abCurPage=null;const _abJumpToReaderPage=async pageNum=>{const pg=parseInt(pageNum,10);if(pg){if(typeof window.readerJumpToPage=="function"){await window.readerJumpToPage(pg);return}typeof navTo=="function"&&navTo("s-reader"),typeof window.navReaderView=="function"?window.navReaderView("main"):typeof window.showReaderView=="function"&&window.showReaderView("main"),setTimeout(async()=>{var _a3,_b3,_c3;const pageDiv=(_c3=(_b3=(_a3=window.readerState)==null?void 0:_a3.pages)==null?void 0:_b3[pg-1])==null?void 0:_c3.pageDiv;pageDiv?(typeof window.readerRenderPage=="function"&&await window.readerRenderPage(pg-1),pageDiv.scrollIntoView({behavior:"smooth",block:"start",inline:"nearest"})):typeof toast=="function"&&toast("Source page is not loaded in Reader yet","info")},250)}},_abNewPage=(label,pageNum)=>{_abCurPage&&!_abCurPage.querySelector(".ab-cv-row, .ab-cv-note, .ab-cv-divider")&&_abCurPage.remove();const p=document.createElement("div");if(p.className="ab-cv-page",label){const pageAttr=pageNum?` data-page="${escHtml(String(pageNum))}"`:"";p.innerHTML=``}return feed.appendChild(p),_abCurPage=p,pageNum&&(_abSetCurrentPage(pageNum),_abUpdatePageNav()),p},_abPage=()=>_abCurPage||_abNewPage(),_abPageNumbers=()=>{var _a3,_b3,_c3;const set=new Set,readerPages=((_a3=window.readerState)==null?void 0:_a3.mode)==="pdf"&&((_c3=(_b3=window.readerState)==null?void 0:_b3.pages)==null?void 0:_c3.length)||0;if(readerPages>0)for(let i=1;i<=readerPages;i++)set.add(i);else(_audiobook.pageMarks||[]).forEach(m=>set.add((m.page||0)+1));return feed.querySelectorAll(".ab-cv-page-label[data-page]").forEach(el=>{const n=parseInt(el.dataset.page,10);n&&set.add(n)}),[...set].sort((a,b)=>a-b)},_abSetCurrentPage=pageNum=>{const pg=parseInt(pageNum,10);if(!pg)return;_abCurrentPageNum=pg;const sel=_abPageNav.querySelector(".ab-page-select");sel&&[...sel.options].some(o=>Number(o.value)===pg)&&(sel.value=String(pg))},_abUpdatePageNav=()=>{const pages=_abPageNumbers(),sel=_abPageNav.querySelector(".ab-page-select");if(!sel||!pages.length){_abPageNav.hidden=!0,typeof _abSyncTopbar=="function"&&_abSyncTopbar();return}const old=Number(sel.value)||_abCurrentPageNum||pages[0];sel.innerHTML=pages.map(n=>``).join(""),_abPageNav.hidden=pages.length<=1,_abSetCurrentPage(pages.includes(old)?old:pages[0]),typeof _abSyncTopbar=="function"&&_abSyncTopbar()},_abScrollToCastPage=pageNum=>{var _a3;const pg=parseInt(pageNum,10);if(!pg)return;let label=feed.querySelector(`.ab-cv-page-label[data-page="${CSS.escape(String(pg))}"]`);if(!label){const active=_abActiveSegments();active.arr.length&&_abRedrawSegments(active.arr),label=feed.querySelector(`.ab-cv-page-label[data-page="${CSS.escape(String(pg))}"]`)}if(pg===1){_userScrolled=!0,jumpBtn&&(jumpBtn.hidden=!1);const firstPage=(label==null?void 0:label.closest(".ab-cv-page"))||feed.firstElementChild;firstPage?firstPage.scrollIntoView({behavior:"smooth",block:"start"}):feed.scrollTo({top:0,behavior:"smooth"}),_abSetCurrentPage(pg);return}if(!label){toast("This cast page is not available yet. Use the book button to jump to the source page.","info"),_abSetCurrentPage(pg);return}_userScrolled=!0,jumpBtn&&(jumpBtn.hidden=!1),(_a3=label.closest(".ab-cv-page"))==null||_a3.scrollIntoView({behavior:"smooth",block:"start"}),_abSetCurrentPage(pg)},_abStepPage=dir=>{var _a3;const pages=_abPageNumbers();if(!pages.length)return;const current=Number((_a3=_abPageNav.querySelector(".ab-page-select"))==null?void 0:_a3.value)||_abCurrentPageNum||pages[0],idx=Math.max(0,pages.indexOf(current)),next=pages[Math.max(0,Math.min(pages.length-1,idx+dir))];_abScrollToCastPage(next)};(_a2=_abPageNav.querySelector(".ab-page-select"))==null||_a2.addEventListener("change",e=>_abScrollToCastPage(e.target.value)),(_b2=_abPageNav.querySelector(".ab-page-prev"))==null||_b2.addEventListener("click",()=>_abStepPage(-1)),(_c2=_abPageNav.querySelector(".ab-page-next"))==null||_c2.addEventListener("click",()=>_abStepPage(1)),(_d2=_abPageNav.querySelector(".ab-page-source"))==null||_d2.addEventListener("click",()=>{var _a3;const pg=Number((_a3=_abPageNav.querySelector(".ab-page-select"))==null?void 0:_a3.value)||_abCurrentPageNum;_abJumpToReaderPage(pg)});const _abUndoStack=[],_abRedoStack=[],_abHistoryLimit=40,_abSyncTopbar=()=>{const hasSelection=!_abBar.hidden,hasHistory=_abUndoStack.length>0||_abRedoStack.length>0;_abEditToolbar.hidden=!hasHistory,_abTopbar.hidden=!1},_abActiveSegments=()=>_audiobook.running&&Array.isArray(_audiobook.liveSegments)?{key:"liveSegments",arr:_audiobook.liveSegments}:Array.isArray(_audiobook.segments)?{key:"segments",arr:_audiobook.segments}:Array.isArray(_audiobook.liveSegments)?{key:"liveSegments",arr:_audiobook.liveSegments}:{key:"segments",arr:[]},_abCloneSegments=segs=>(segs||[]).map(s=>({...s})),_abSyncRosterList=segs=>{const names=[];for(const s of segs||[]){const sp=(s==null?void 0:s.type)==="dialogue"&&s.speaker?s.speaker:"";!sp||/^Narrator$/i.test(sp)||/^Unknown|Unbekannt/i.test(sp)||names.some(n=>n.toLowerCase()===sp.toLowerCase())||names.push(sp)}_audiobook.roster=names},_abPersistManualEdit=()=>{const active=_abActiveSegments();if(_audiobook.lastText){const done=_audiobook.completedChunks||active.arr.length,total2=_audiobook.completedTotal||done;setTimeout(()=>_abSaveDraft(active.arr||[],_audiobook.roster||[],_audiobook.lastText,done,total2),50)}_audiobookDebouncedSave()},_abUpdateEditButtons=()=>{const undo=_abEditToolbar.querySelector(".ab-edit-undo"),redo=_abEditToolbar.querySelector(".ab-edit-redo");undo&&(undo.disabled=!_abUndoStack.length),redo&&(redo.disabled=!_abRedoStack.length),_abSyncTopbar()},_abPushEditState=(keyOverride,arrOverride)=>{const active=arrOverride?{key:keyOverride||"segments",arr:arrOverride}:_abActiveSegments();if(!active.arr.length)return!1;for(_abUndoStack.push({key:active.key,segments:_abCloneSegments(active.arr)});_abUndoStack.length>_abHistoryLimit;)_abUndoStack.shift();return _abRedoStack.length=0,_abUpdateEditButtons(),!0},_abRowSpeaker=s=>{const isNarrator=s.type!=="dialogue"||!s.speaker||String(s.speaker).toLowerCase()==="narrator";return{isNarrator,speakerName:isNarrator?"Narrator":s.speaker}},_abIsHeadingSeg=s=>{if((s==null?void 0:s.type)==="dialogue")return!1;const t=String((s==null?void 0:s.text)||"").trim();return!t||t.length>60?!1:/^(prolog(ue)?|epilog(ue)?|kapitel|chapter|teil|buch|part|book|akt|szene|scene)\b/i.test(t)||/^\d+[\.\)]?\s*(kapitel|chapter)?$/i.test(t)?!0:t.split(/\s+/).length<=7&&!/[.!?;:,«»"]/.test(t)},_abRowFromSegment=(s,extraClass="")=>{const{isNarrator,speakerName}=_abRowSpeaker(s),c=colorFor(speakerName),row=document.createElement("div");return row.className=`ab-cv-row${extraClass?" "+extraClass:""}${isNarrator?" is-narr":""}${_abIsHeadingSeg(s)?" is-heading":""}`,row.__seg=s,row.innerHTML=` ${escHtml(speakerName)}${s.emotion?' ('+escHtml(s.emotion)+")":""} ${highlightText2(s.text||"")}`,row},_abStartRowEdit=row=>{if(!row||!row.__seg||row.querySelector(".ab-cv-edit-ta"))return;const s=row.__seg,txtEl=row.querySelector(".ab-cv-txt");if(!txtEl)return;const ta=document.createElement("textarea");ta.className="ab-cv-edit-ta",ta.value=s.text||"",txtEl.replaceWith(ta),ta.focus(),ta.setSelectionRange(ta.value.length,ta.value.length);const commit=save=>{if(save){const val=ta.value;val!==s.text&&(_abPushEditState(),s.text=val,_abPersistManualEdit())}ta.replaceWith(txtEl),txtEl.innerHTML=highlightText2(s.text||"")};ta.addEventListener("keydown",e=>{e.key==="Enter"&&(e.ctrlKey||e.metaKey)?(e.preventDefault(),commit(!0)):e.key==="Escape"&&(e.preventDefault(),commit(!1))}),ta.addEventListener("blur",()=>commit(!0))},_abRecountRoster=segs=>{for(const info of roster.values())info.count=0;for(const s of segs||[]){const{speakerName}=_abRowSpeaker(s||{}),c=colorFor(speakerName);roster.has(speakerName)||roster.set(speakerName,{count:0,color:c}),roster.get(speakerName).count++}_abSyncRosterList(segs||[]),renderRoster()},_abRedrawSegments=segs=>{const selectedChar=!_abBar.hidden&&_abBar.dataset.charName?_abBar.dataset.charName:"";feed.innerHTML="",_abCurPage=null;let lastPage=null;for(const s of segs||[])s.page!=null&&s.page!==lastPage&&(_abNewPage("Page "+s.page,s.page),lastPage=s.page),_abPage().appendChild(_abRowFromSegment(s));_abRecountRoster(segs||[]),_abClearHL(),_abUpdatePageNav(),selectedChar&&(_abBar.hidden=!0,_abSelectChar(selectedChar))},_abRedrawSegmentsChunked=(segs,onProgress,batchSize=150)=>new Promise(resolve=>{const selectedChar=!_abBar.hidden&&_abBar.dataset.charName?_abBar.dataset.charName:"";feed.innerHTML="",_abCurPage=null;const list=segs||[];let i=0,lastPage=null;const step=()=>{const end=Math.min(i+batchSize,list.length);for(;i{if(!snap)return;const restored=_abCloneSegments(snap.segments);snap.key==="liveSegments"?_audiobook.liveSegments=restored:_audiobook.segments=restored,_abRedrawSegments(restored),_abPersistManualEdit()},_abUndoEdit=()=>{const active=_abActiveSegments();!_abUndoStack.length||!active.arr.length||(_abRedoStack.push({key:active.key,segments:_abCloneSegments(active.arr)}),_abRestoreEditState(_abUndoStack.pop()),_abUpdateEditButtons())},_abRedoEdit=()=>{const active=_abActiveSegments();!_abRedoStack.length||!active.arr.length||(_abUndoStack.push({key:active.key,segments:_abCloneSegments(active.arr)}),_abRestoreEditState(_abRedoStack.pop()),_abUpdateEditButtons())};(_e2=_abEditToolbar.querySelector(".ab-edit-undo"))==null||_e2.addEventListener("click",_abUndoEdit),(_f2=_abEditToolbar.querySelector(".ab-edit-redo"))==null||_f2.addEventListener("click",_abRedoEdit),_abSyncTopbar();let assignModeSeg=null,assignModeRow=null,assignPopup=document.getElementById("ab-cv-assign-popup");if(!assignPopup){assignPopup=document.createElement("div"),assignPopup.id="ab-cv-assign-popup",assignPopup.style.cssText="position:fixed; z-index:2001; display:none; background:var(--surface); border:1px solid var(--border); border-radius:8px; box-shadow:0 10px 25px rgba(0,0,0,0.4); padding:10px; width:220px; max-height:320px; flex-direction:column; gap:8px;";const header=document.createElement("div");header.style.cssText="display:flex; justify-content:space-between; align-items:center; margin-bottom:-4px; margin-top:-4px;",header.innerHTML='Assign to',assignPopup.appendChild(header);const inp=document.createElement("input");inp.type="text",inp.placeholder="Search or add character...",inp.style.cssText="width:100%; padding:6px; font-size:13px; border:1px solid var(--border); border-radius:4px; background:var(--bg); color:var(--text);",assignPopup.appendChild(inp);const list=document.createElement("div");list.className="ab-cv-popup-list",list.style.cssText="display:flex; flex-direction:column; gap:2px; overflow-y:auto; max-height:220px; margin-right:-4px; padding-right:4px;",assignPopup.appendChild(list),document.body.appendChild(assignPopup),header.querySelector(".mdi-close").addEventListener("click",()=>{var _a3;return(_a3=assignPopup._close)==null?void 0:_a3.call(assignPopup)}),document.addEventListener("mousedown",e=>{var _a3;assignPopup.style.display!=="none"&&!assignPopup.contains(e.target)&&!e.target.closest(".ab-cv-spk")&&!e.target.closest(".ab-cv-txt")&&((_a3=assignPopup._close)==null||_a3.call(assignPopup))}),document.addEventListener("keydown",e=>{var _a3;e.key==="Escape"&&assignPopup.style.display!=="none"&&((_a3=assignPopup._close)==null||_a3.call(assignPopup))}),inp.addEventListener("keydown",e=>{var _a3,_b3;if(e.key==="Enter"){const addBtn=assignPopup.querySelector(".ab-cv-popup-add-btn"),visibleBtns=Array.from(assignPopup.querySelectorAll(".ab-cv-popup-list > div[data-name]")).filter(b=>b.style.display!=="none");if(addBtn&&addBtn.style.display!=="none")addBtn.click();else if(visibleBtns.length===1)visibleBtns[0].click();else{const val=inp.value.trim();val&&((_a3=assignPopup._assignName)==null||_a3.call(assignPopup,val))}}else e.key==="Escape"&&((_b3=assignPopup._close)==null||_b3.call(assignPopup))}),inp.addEventListener("input",()=>{const val=inp.value.trim().toLowerCase(),listItems=assignPopup.querySelectorAll(".ab-cv-popup-list > div[data-name]");let exactMatch=!1;listItems.forEach(item=>{const name=item.dataset.name.toLowerCase();name===val&&(exactMatch=!0),name.includes(val)?item.style.display="flex":item.style.display="none"});let addBtn=assignPopup.querySelector(".ab-cv-popup-add-btn");val&&!exactMatch&&val!=="narrator"?(addBtn||(addBtn=document.createElement("div"),addBtn.className="ab-cv-popup-add-btn",addBtn.style.cssText="padding:6px 8px; font-size:12px; cursor:pointer; border-radius:4px; display:flex; align-items:center; gap:6px; transition:background 0.1s; font-weight:600; color:var(--primary); border-top:1px solid var(--border); margin-top:4px; padding-top:8px;",addBtn.onmouseover=()=>addBtn.style.background="var(--panel)",addBtn.onmouseout=()=>addBtn.style.background="transparent"),addBtn.innerHTML=`+ Add "${escHtml(inp.value.trim())}"`,addBtn.onclick=()=>{var _a3;return(_a3=assignPopup._assignName)==null?void 0:_a3.call(assignPopup,inp.value.trim())},addBtn.style.display="flex",assignPopup.querySelector(".ab-cv-popup-list").appendChild(addBtn)):addBtn&&(addBtn.style.display="none")})}function closeAssignPopup(){assignPopup.style.display="none",assignModeRow&&assignModeRow.classList.remove("is-assigning"),assignModeSeg=null,assignModeRow=null}function assignName(name){if(!assignModeSeg)return;const active=_abActiveSegments();_abPushEditState(active.key,active.arr);const isNarrator=name.toLowerCase()==="narrator",_oldSpk=assignModeSeg.speaker;if(_oldSpk&&roster.has(_oldSpk)){const _old=roster.get(_oldSpk);_old.count>0&&_old.count--}assignModeSeg.speaker=isNarrator?"Narrator":name,assignModeSeg.type=isNarrator?"narration":"dialogue",isNarrator&&(assignModeSeg.emotion=""),assignModeRow.classList.remove("is-assigning");const speakerName=isNarrator?"Narrator":name,c=colorFor(speakerName);roster.has(speakerName)||roster.set(speakerName,{count:0,color:c}),roster.get(speakerName).count++,assignModeRow.className="ab-cv-row"+(isNarrator?" is-narr":""),assignModeRow.querySelector(".ab-cv-spk").innerHTML=`${escHtml(speakerName)}${assignModeSeg.emotion?' ('+escHtml(assignModeSeg.emotion)+")":""}`,assignModeRow.querySelector(".ab-cv-spk").style.color=c,assignModeRow.querySelector(".ab-cv-spk").title="Click to assign character",_abRecountRoster(_abActiveSegments().arr),toast(`Assigned to ${isNarrator?"Narrator":name}`,"success"),closeAssignPopup(),_abPersistManualEdit()}function _abOpenAssignPopup(row,anchorEl,prefill){var _a3;if(!row||!row.__seg||row.classList.contains("is-processing"))return;assignModeRow&&assignModeRow.classList.remove("is-assigning"),assignModeSeg=row.__seg,assignModeRow=row,row.classList.add("is-assigning"),assignPopup._assignName=assignName,assignPopup._close=closeAssignPopup,window.getSelection().removeAllRanges(),assignPopup.style.display="flex";const rect=anchorEl.getBoundingClientRect(),top=Math.min(rect.bottom+4,window.innerHeight-300);assignPopup.style.top=top+"px",assignPopup.style.left=rect.left+"px";const list=assignPopup.querySelector(".ab-cv-popup-list");list.innerHTML="";const narrBtn=document.createElement("div");narrBtn.dataset.name="Narrator",narrBtn.style.cssText="padding:6px 8px; font-size:12px; cursor:pointer; border-radius:4px; display:flex; align-items:center; gap:6px; transition:background 0.1s; font-weight:600; color:var(--subtext); border-bottom:1px solid var(--border); margin-bottom:4px; padding-bottom:8px;",narrBtn.innerHTML='\u{1F4D6} Narrator',narrBtn.onmouseover=()=>narrBtn.style.background="var(--panel)",narrBtn.onmouseout=()=>narrBtn.style.background="transparent",narrBtn.onclick=()=>assignName("Narrator"),list.appendChild(narrBtn);const items=[...roster.entries()].sort((a,b)=>b[1].count-a[1].count);for(const[n,info]of items){const btn=document.createElement("div");btn.dataset.name=n,btn.style.cssText="padding:6px 8px; font-size:12px; cursor:pointer; border-radius:4px; display:flex; align-items:center; gap:6px; transition:background 0.1s; font-weight:600; color:var(--text);";const img=(_a3=recordForName(n))==null?void 0:_a3.image;btn.innerHTML=(img?``:``)+` ${escHtml(n)}`,btn.onmouseover=()=>btn.style.background="var(--panel)",btn.onmouseout=()=>btn.style.background="transparent",btn.onclick=()=>assignName(n),list.appendChild(btn)}const inp=assignPopup.querySelector("input");inp.value=prefill||"",setTimeout(()=>{inp.focus(),prefill&&inp.dispatchEvent(new Event("input"))},50)}const _abIsUnknownSeg=s=>!(s!=null&&s.speaker)||/^Unknown|Unbekannt/i.test(s.speaker),_abIsNarrSeg=s=>(s==null?void 0:s.type)!=="dialogue"||!s.speaker||/^Narrator$/i.test(s.speaker),_abMergedSegment=(a,b)=>{var _a3;let base=a;(_abIsUnknownSeg(a)&&!_abIsUnknownSeg(b)||_abIsNarrSeg(a)&&!_abIsNarrSeg(b))&&(base=b);const type=_abIsNarrSeg(base)?"narration":"dialogue";return{speaker:type==="narration"?"Narrator":base.speaker||"Unknown",type,emotion:type==="dialogue"&&(base.emotion||a.emotion||b.emotion)||"",text:audiobookJoinSegmentText(a.text||"",b.text||""),page:(_a3=a.page)!=null?_a3:b.page}},_abMergeByRow=(row,dir)=>{const active=_abActiveSegments(),idx=active.arr.indexOf(row==null?void 0:row.__seg);if(idx<0)return;const leftIdx=dir<0?idx-1:idx,rightIdx=dir<0?idx:idx+1;if(leftIdx<0||rightIdx>=active.arr.length){toast("No adjacent segment to merge","info");return}_abPushEditState(active.key,active.arr);const merged=_abMergedSegment(active.arr[leftIdx],active.arr[rightIdx]);active.arr.splice(leftIdx,2,merged),active.key==="segments"?_audiobook.segments=active.arr:_audiobook.liveSegments=active.arr,_abRedrawSegments(active.arr),_abPersistManualEdit(),toast("Segments merged","success")};feed.addEventListener("click",e=>{var _a3;const pageLabel=e.target.closest(".ab-cv-page-label[data-page]");if(pageLabel){e.preventDefault(),e.stopPropagation(),_abJumpToReaderPage(pageLabel.dataset.page);return}const editBtn=e.target.closest(".ab-cv-edit-text");if(editBtn){e.preventDefault(),e.stopPropagation(),_abStartRowEdit(editBtn.closest(".ab-cv-row"));return}const mergeBtn=e.target.closest(".ab-cv-row-tool");if(mergeBtn){e.preventDefault(),e.stopPropagation(),_abMergeByRow(mergeBtn.closest(".ab-cv-row"),mergeBtn.classList.contains("ab-cv-merge-prev")?-1:1);return}const spk=e.target.closest(".ab-cv-spk");if(spk){_abOpenAssignPopup(spk.closest(".ab-cv-row"),spk,"");return}if(_abJustHandledSelection){_abJustHandledSelection=!1;return}const txtEl=e.target.closest(".ab-cv-txt");if(txtEl&&window.getSelection().isCollapsed){if(!assignModeRow)return;const row=txtEl.closest(".ab-cv-row"),nameHit=e.target.closest(".ab-name-hit"),clickedWord=nameHit?nameHit.dataset.name||nameHit.textContent.trim():(_a3=_abWordRangeAtPoint(e.clientX,e.clientY))==null?void 0:_a3.word;if(assignModeRow!==row){if(clickedWord){const inp=assignPopup.querySelector("input");inp.value=clickedWord,inp.dispatchEvent(new Event("input")),inp.focus()}return}if(nameHit)_abOpenAssignPopup(row,nameHit,clickedWord);else{const hit=_abWordRangeAtPoint(e.clientX,e.clientY);if(hit){const rect=hit.range.getBoundingClientRect();_abOpenAssignPopup(row,{getBoundingClientRect:()=>rect},hit.word)}}}});let _abJustHandledSelection=!1,_abHoverHL=document.getElementById("ab-word-hover");_abHoverHL||(_abHoverHL=document.createElement("div"),_abHoverHL.id="ab-word-hover",_abHoverHL.className="ab-word-hover",_abHoverHL.hidden=!0,document.body.appendChild(_abHoverHL));let _abHoverRaf=0,_abHoverPt=null;const _abHideHoverHL=()=>{_abHoverHL.hidden=!0};feed.addEventListener("mousemove",e=>{_abHoverPt={x:e.clientX,y:e.clientY,target:e.target},!_abHoverRaf&&(_abHoverRaf=requestAnimationFrame(()=>{var _a3;_abHoverRaf=0;const pt=_abHoverPt;if(!pt||!((_a3=pt.target)!=null&&_a3.closest))return;if(!pt.target.closest(".ab-cv-txt")||pt.target.closest(".ab-name-hit")||feed.querySelector(".ab-cv-edit-ta")){_abHideHoverHL();return}const hit=_abWordRangeAtPoint(pt.x,pt.y);if(!hit){_abHideHoverHL();return}const r=hit.range.getBoundingClientRect();if(!r.width){_abHideHoverHL();return}_abHoverHL.style.left=r.left-2+"px",_abHoverHL.style.top=r.top-1+"px",_abHoverHL.style.width=r.width+4+"px",_abHoverHL.style.height=r.height+2+"px",_abHoverHL.hidden=!1}))}),feed.addEventListener("mouseleave",_abHideHoverHL),feed.addEventListener("scroll",_abHideHoverHL,{passive:!0}),feed.addEventListener("dblclick",e=>{const hit=e.target.closest(".ab-name-hit");if(!hit)return;const row=hit.closest(".ab-cv-row"),name=hit.dataset.name;row&&name&&(_abOpenAssignPopup(row,hit,""),assignName(name))});let splitBtn=document.getElementById("ab-cv-split-btn");splitBtn||(splitBtn=document.createElement("button"),splitBtn.id="ab-cv-split-btn",splitBtn.className="btn-secondary btn-sm",splitBtn.innerHTML=' Split text to Unknown Speaker',splitBtn.style.cssText="position:fixed; z-index:2000; display:none; background:var(--accent); color:#fff; border:none; box-shadow:0 4px 12px rgba(0,0,0,0.3);",document.body.appendChild(splitBtn));let currentSplitState=null;const _abOffsetInEl=(el,node,offset)=>{const r=document.createRange();return r.selectNodeContents(el),r.setEnd(node,offset),r.toString().length};return document.addEventListener("selectionchange",()=>{if(!feed)return;const sel=window.getSelection();if(sel.isCollapsed||!feed.contains(sel.anchorNode)){splitBtn&&(splitBtn.style.display="none"),currentSplitState=null;return}const txtSpan=sel.anchorNode.nodeType===3?sel.anchorNode.parentNode.closest(".ab-cv-txt"):sel.anchorNode.closest(".ab-cv-txt");if(!txtSpan){splitBtn.style.display="none";return}const row=txtSpan.closest(".ab-cv-row");if(!row||!row.__seg)return;const rawText=sel.toString(),text=rawText.trim();if(text.length>0){if(assignModeSeg&&text.length<40&&!text.includes(` `)){splitBtn.style.display="none";return}const range=sel.getRangeAt(0),rect=range.getBoundingClientRect();splitBtn.style.display="block",splitBtn.style.top=rect.bottom+8+"px",splitBtn.style.left=Math.max(10,rect.left+rect.width/2-100)+"px";const startOffset=_abOffsetInEl(txtSpan,range.startContainer,range.startOffset),endOffset=_abOffsetInEl(txtSpan,range.endContainer,range.endOffset);currentSplitState={row,text,rawText,seg:row.__seg,startOffset,endOffset}}else splitBtn.style.display="none",currentSplitState=null}),splitBtn.addEventListener("click",()=>{if(!currentSplitState)return;const{row,seg,startOffset,endOffset}=currentSplitState,fullText=seg.text||"",before=fullText.slice(0,startOffset),selectedText=fullText.slice(startOffset,endOffset),after=fullText.slice(endOffset);if(!selectedText)return;let arr=_audiobook.segments||[],globalIdx=arr.indexOf(seg);globalIdx===-1&&Array.isArray(_audiobook.liveSegments)&&(arr=_audiobook.liveSegments,globalIdx=arr.indexOf(seg)),globalIdx!==-1&&_abPushEditState(arr===_audiobook.liveSegments?"liveSegments":"segments",arr);const newSegs=[];if(before.length&&newSegs.push({speaker:seg.speaker,type:seg.type,emotion:seg.emotion,text:before,page:seg.page}),newSegs.push({speaker:"Unknown",type:"dialogue",emotion:"",text:selectedText,page:seg.page}),after.length&&newSegs.push({speaker:seg.speaker,type:seg.type,emotion:seg.emotion,text:after,page:seg.page}),globalIdx!==-1)arr.splice(globalIdx,1,...newSegs),_abRedrawSegments(arr),_abPersistManualEdit();else{console.warn("[audiobook] split during casting: DOM-only, segment not yet in array");const frag=document.createDocumentFragment();for(const s of newSegs)frag.appendChild(_abRowFromSegment(s));row.parentNode.insertBefore(frag,row),row.remove()}splitBtn.style.display="none",window.getSelection().removeAllRanges(),toast("Segment split! You can now assign a character to the Unknown block.","success")}),feed.addEventListener("mouseup",()=>{const sel=window.getSelection();if(sel.isCollapsed||!feed.contains(sel.anchorNode))return;const text=sel.toString().trim();!text||text.length>=40||text.includes(` `)||/^[»„“"'‘]/.test(text)||assignModeSeg&&(_abJustHandledSelection=!0,assignName(text),sel.removeAllRanges())}),chars.addEventListener("click",e=>{const chip=e.target.closest(".ab-chip");if(chip){let name="";for(const n of chip.childNodes)n.nodeType===3&&(name+=n.nodeValue);if(name=name.trim(),assignModeSeg)name&&assignName(name);else if(name){const rows=Array.from(feed.querySelectorAll(".ab-cv-row")).filter(r=>r.__seg&&r.__seg.speaker===name);if(!rows.length)return;const currentY=feed.scrollTop;let target=rows.find(r=>r.offsetTop-feed.offsetTop>currentY+10);target||(target=rows[0]),target.scrollIntoView({behavior:"smooth",block:"center"}),target.style.transition="background 0.3s",target.style.background="var(--accent-hover, rgba(100, 150, 255, 0.2))",setTimeout(()=>{target.parentNode&&(target.style.background="")},1e3)}}}),{recountRoster(allSegs){_abRecountRoster(allSegs||[])},rebuild(segs){var _a3,_b3;this.clearProcessing(),(_a3=feed.querySelector(".ab-skel-feed"))==null||_a3.remove(),(_b3=chars.querySelector(".ab-skel-chars"))==null||_b3.remove(),_abRedrawSegments(segs||[])},update(done){const castBar=panel.querySelector(".ab-castpanel-bar");castBar&&(castBar.hidden=!1);const pct=Math.round(done/total*100);fill&&(fill.style.width=pct+"%"),count&&(count.hidden=!1,count.textContent=`passage ${done} / ${total}`);const fillText=panel.querySelector("#ab-cv-fill-text");fillText&&(fillText.textContent=`${pct}% (Passage ${done} of ${total})`)},processing(text){this._procRow&&this._procRow.remove(),this._thinkRaw="",this._thinkDone=!1,this._procRow=document.createElement("div"),this._procRow.className="ab-cv-row is-processing ab-cv-llm-row";const isLong=text.length>160,preview=escHtml(text.slice(0,160))+(isLong?"\u2026":"");this._procRow.innerHTML=`
LLM Reading\u2026
${preview}
`,this._procRow.querySelector(".ab-cv-expand-btn").addEventListener("click",function(){const split=this.closest(".ab-cv-llm-row").querySelector(".ab-cv-llm-split"),open=this.getAttribute("aria-expanded")==="true";this.setAttribute("aria-expanded",String(!open)),split.hidden=open;const icon=this.querySelector("span");icon&&(icon.className=open?"mdi mdi-chevron-right":"mdi mdi-chevron-down")}),_abPage().appendChild(this._procRow),trim()},thinking(delta){if(!this._procRow||!delta||this._thinkDone)return;this._thinkRaw=(this._thinkRaw||"")+delta;const think=this._procRow.querySelector(".ab-cv-think"),label=this._procRow.querySelector(".ab-cv-think-label");if(!think)return;const openAt=this._thinkRaw.indexOf("");let shown,isReal=!1,closeAt=-1;if(openAt>=0){const afterOpen=this._thinkRaw.slice(openAt+7);closeAt=afterOpen.indexOf(""),shown=closeAt>=0?afterOpen.slice(0,closeAt):afterOpen,isReal=!0}else shown=this._thinkRaw;label&&(label.innerHTML=isReal?' LLM Thinking\u2026':' Live Output (raw \u2014 no reasoning exposed)'),think.textContent=shown.trim().slice(-2e4),think.scrollTop=think.scrollHeight,closeAt>=0&&(this._thinkDone=!0)},clearProcessing(){this._procRow&&(this._procRow.remove(),this._procRow=null)},addSegments(segs){var _a3,_b3;this.clearProcessing(),(_a3=feed.querySelector(".ab-skel-feed"))==null||_a3.remove(),(_b3=chars.querySelector(".ab-skel-chars"))==null||_b3.remove();const makeRow=s=>{const{speakerName}=_abRowSpeaker(s);return colorFor(speakerName),roster.get(speakerName).count++,_abRowFromSegment(s)},CHUNK=80,first=segs.slice(0,CHUNK),frag0=document.createDocumentFragment();if(first.forEach(s=>frag0.appendChild(makeRow(s))),_abPage().appendChild(frag0),renderRoster(),segs.length>CHUNK){let i=CHUNK;const next=()=>{if(i>=segs.length){trim(),renderRoster();return}const batch=document.createDocumentFragment(),end=Math.min(i+CHUNK,segs.length);for(;i=0?prevIdx+1:0),segsAll=_audiobook.segments||[],gapTo=Math.min(segsAll.length-1,nextIdx>=0?nextIdx-1:segsAll.length-1),gapCount=Math.max(0,gapTo-gapFrom+1);r.innerHTML=`\u22EF ${gapCount>0?gapCount+" line"+(gapCount!==1?"s":"")+" hidden \u2014 ":""}click to expand`,r.title="Click to reveal the text between these passages",r.addEventListener("click",()=>{const gap=segsAll.slice(gapFrom,gapTo+1);if(!gap.length){r.remove();return}const PEEK=20,show=gap.slice(0,PEEK),rest=gap.slice(PEEK),frag=document.createDocumentFragment();for(const s of show){const isNarr=s.type!=="dialogue"||!s.speaker||s.speaker.toLowerCase()==="narrator",spk=isNarr?"Narrator":s.speaker,c=colorFor(spk),row=document.createElement("div");row.className="ab-cv-row ab-cv-ctx-row"+(isNarr?" is-narr":""),row.__seg=s,row.innerHTML=`${escHtml(spk)}${s.emotion?' ('+escHtml(s.emotion)+")":""}${highlightText2(s.text||"")}`,frag.appendChild(row)}if(rest.length){const next=document.createElement("div");next.className="ab-cv-divider ab-cv-divider-expand",next.innerHTML=`\u22EF ${rest.length} line${rest.length!==1?"s":""} hidden \u2014 click to expand`,next.title="Click to reveal more";const restSegs=rest;next.addEventListener("click",function(){const f2=document.createDocumentFragment();for(const s of restSegs){const isNarr=s.type!=="dialogue"||!s.speaker||s.speaker.toLowerCase()==="narrator",spk=isNarr?"Narrator":s.speaker,c=colorFor(spk),row=document.createElement("div");row.className="ab-cv-row ab-cv-ctx-row"+(isNarr?" is-narr":""),row.__seg=s,row.innerHTML=`${escHtml(spk)}${s.emotion?' ('+escHtml(s.emotion)+")":""}${highlightText2(s.text||"")}`,f2.appendChild(row)}next.replaceWith(f2)}),frag.appendChild(next)}r.replaceWith(frag)}),_abPage().appendChild(r),trim()},pagemark(pageNum){this.clearProcessing(),_abNewPage("Page "+pageNum,pageNum),trim()},complete(summary,onOpen,onRecast,onRecastUnknown){var _a3,_b3;count&&(count.hidden=!0);const castBar=panel.querySelector(".ab-castpanel-bar");castBar&&(castBar.hidden=!0);const completedSegments=Array.isArray(_audiobook.segments)&&_audiobook.segments.length?_audiobook.segments:typeof allSegments!="undefined"&&allSegments.length?allSegments:null;completedSegments&&_abRedrawSegments(completedSegments);const cancelBtn=panel.querySelector("#ab-cv-cancel");cancelBtn&&(cancelBtn.hidden=!0);const foot=panel.querySelector("#ab-cv-foot");foot.hidden=!1;const sideEl=panel.querySelector(".ab-cv-side");if(sideEl){let doneEl=sideEl.querySelector(".ab-cv-side-done");doneEl||(doneEl=document.createElement("div"),doneEl.className="ab-cv-side-done",sideEl.appendChild(doneEl)),doneEl.innerHTML=` ${escHtml(summary)}`}const continueBtnHtml=_audiobook.completedChunks>0&&_audiobook.completedTotal>0&&_audiobook.completedChunks<_audiobook.completedTotal?``:"";foot.innerHTML=`${continueBtnHtml}`;const runVerificationPass=()=>{const verificationPrompt=`Du bist ein Qualit\xE4tspr\xFCfer f\xFCr die Analyse eines deutschen H\xF6rbuchs. Eine erste KI hat den Textauszug bereits in Segmente unterteilt. Deine Aufgabe ist es, unbekannte Sprecher zu l\xF6sen und falsche Unknown/Narrator-Zuweisungen zu korrigieren, ohne bereits klare Sprecher unn\xF6tig zu ver\xE4ndern. AUFGABE (2. Qualit\xE4tslauf): 1. L\xD6SE 'Unknown'-Segmente auf: Nutze umgebenden Kontext, Inquit-Formeln wie "sagte X", "fragte sie", "rief er", Handlungsbeschreibungen, Reihenfolge der Sprecher, Ping-Pong-Wechsel in Dialogen und bekannte Figuren. 2. KORRIGIERE ein Segment zu 'Narrator' nur dann, wenn es eindeutig Erz\xE4hlertext, Handlung, Beschreibung oder ein Sprecher-Tag ist. 3. BEHALTE vorhandene klare Sprecher-Zuweisungen im Kontext bei. Nutze sie als Anker f\xFCr die Unknown-Zeilen. 4. 'Unknown' ist NUR erlaubt, wenn der Sprecher trotz Kontext absolut nicht bestimmbar ist. DEDUKTIONS-WERKZEUGE (wende sie in dieser Reihenfolge an): - Doppelpunkt-Regel: Endet der Erz\xE4hlersatz vor dem Zitat mit ":", spricht dessen Subjekt das Zitat ("Dann richtete er sich auf und rief in die Runde:" \u2192 der zuvor genannte Charakter; "Ein anderer fragte verschlafen:" \u2192 dieser andere). - Nachgestellte Zuordnung: Der Erz\xE4hlersatz NACH dem Zitat verr\xE4t den Sprecher \u2014 auch bei unpers\xF6nlicher Formel ("\xBBWas machst du denn da?\xAB ert\xF6nte es \xFCber ihm. Karyla hatte ihren Hammer weggelegt und war her\xFCbergekommen." \u2192 Karyla sprach). - Pronomen-Aufl\xF6sung: er/sie/es in Inquit-Formeln und Action Beats meint die zuletzt genannte Person passenden Geschlechts ("Mit einem Sto\xDF schob sie Uriens zur Seite" \u2192 sie = die zuletzt genannte Frau, und die umliegenden Zitate sind ihre). - Adressaten-Regel: "X wandte sich an Y" \u2192 X spricht das n\xE4chste Zitat, Y ist der wahrscheinlichste Antwortende. - Ping-Pong-Prinzip: Zwei Personen im Gespr\xE4ch wechseln sich strikt ab \u2014 auch \xFCber viele Zitate ohne Tags hinweg. Verfolge die Kette zur letzten eindeutigen Nennung zur\xFCck und f\xFChre sie fort. In einer Zwei-Personen-Szene ist 'Unknown' fast immer falsch. - Rollenbezeichnungen sind g\xFCltige Sprecher \u2014 nutze sie statt 'Unknown' (z.B. 'Ork', 'Der Fremde', 'Nachbar', 'W\xE4chter', 'Junge'). DIALOG-ERKENNUNG BEI PDF/OCR-TEXTEN: Viele PDF-Extraktionen verlieren Anf\xFChrungszeichen oder Guillemets. Ein kurzer Satz kann also trotzdem Dialog sein, auch wenn \xBB...\xAB oder \u201E..." im \xFCbergebenen Segment fehlen. Entscheide nach Satzform, Antwortstruktur, Sprecherwechsel, Inquit-Formeln und Szene. Markiere eine Zeile NICHT allein deshalb als Narration, weil sichtbare Anf\xFChrungszeichen fehlen. ABSATZ- UND KAPITELSTRUKTUR: Eine Leerzeile markiert einen Absatzwechsel oder Kapitel-/Szenenanfang. Eine sehr kurze, alleinstehende Zeile vor einer Leerzeile ist eine Kapitel\xFCberschrift \u2014 'narration'/'Narrator', niemals Dialog. GRAMMATIK-CHECK F\xDCR NARRATION: - Inquit-Formeln / Sprecher-Tags sind narration, niemals dialogue: finite Sprechverben wie sagte, fragte, rief, entgegnete, murmelte, fl\xFCsterte, schrie, antwortete + Subjekt/Pronomen/Name. - Beispiele: "murmelte er mit erstickter Stimme.", ", entgegnete Marcian kalt.", "fragte Uriens leise." sind Narrator/narration. - Action Beats sind narration: blickte, ging, schwieg, lachte, hob die Hand, wandte sich ab, usw. - Nur die tats\xE4chlich gesprochenen W\xF6rter innerhalb der Anf\xFChrungszeichen bleiben dialogue; alle grammatischen Rahmen- und Berichtss\xE4tze sind narration. F\xDCR JEDES SEGMENT AUSGABE: - speaker: 'Narrator' f\xFCr Narration, oder EXAKT der Name des Charakters. - type: 'narration' oder 'dialogue' - text: EXAKT der WORTW\xD6RTLICHE Originaltext \u2014 KEINE \xC4nderungen, KEINE Auslassungen, KEINE Erg\xE4nzungen. - emotion: Bei Dialogen 1-2 deutsche W\xF6rter f\xFCr den Tonfall. Bei Narration leer (''). ABSOLUTE REGELN: - Alle Segmente zusammen M\xDCSSEN den Originaltext exakt, l\xFCckenlos und wortgetreu rekonstruieren. - PDF-/OCR-SCHUTZ: Ein fehlendes \xBB oder \xAB darf niemals bewirken, dass die restliche Passage als Dialog markiert wird. Bei einem offenen \xBB-Zitat vor einer Inquit-Formel oder Erz\xE4hlerhandlung endet der Dialog am ersten plausiblen Satzende (? ! .). Bei einem einzelnen schlie\xDFenden \xAB nach einem kurzen Satz ist dieser Satz davor der Dialog. - Erfinde NIEMALS Text. Lasse NIEMALS W\xF6rter weg. F\xFCge NIEMALS etwas hinzu. - Mische NIEMALS Narration und Dialog in einem Segment.`,choice=audiobookCurrentCastLlm(panel),savedChoice=audiobookSaveLlmChoice(choice.url,choice.model);closePanel(),audiobookRecastUnknown(savedChoice.url,savedChoice.model,{prompt:verificationPrompt})};foot.querySelector("#ab-cv-verify").addEventListener("click",runVerificationPass),foot.querySelector("#ab-cv-open-reh").addEventListener("click",async()=>{closePanel(),await audiobookOpenCurrentInRehearser()}),(_a3=foot.querySelector("#ab-cv-export-md"))==null||_a3.addEventListener("click",audiobookExportCastMd);const applyPromptAndRun2=callback=>{const newPrompt=panel.querySelector("#ab-cv-prompt-text").value,choice=audiobookCurrentCastLlm(panel),savedChoice=audiobookSaveLlmChoice(choice.url,choice.model);typeof _appSettings!="undefined"&&(_appSettings.audiobook_prompt=newPrompt),fetch("/api/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({audiobook_prompt:newPrompt})}).finally(()=>{closePanel(),callback&&callback(savedChoice.url,savedChoice.model)})};foot.querySelector("#ab-cv-recast").addEventListener("click",()=>applyPromptAndRun2(audiobookCast)),foot.querySelector("#ab-cv-recast-unk").addEventListener("click",()=>applyPromptAndRun2(audiobookRecastUnknown)),(_b3=foot.querySelector("#ab-cv-continue"))==null||_b3.addEventListener("click",()=>applyPromptAndRun2((u,m)=>audiobookCast(u,m,{startIndex:_audiobook.completedChunks,segments:_audiobook.segments,roster:_audiobook.roster,narrationOnly:_audiobook.narratedPassages,degraded:_audiobook.degraded}))),foot.querySelector("#ab-cv-cast-chars").addEventListener("click",()=>{typeof window.csForReader=="function"?window.csForReader():typeof csForReader=="function"?csForReader():toast("Character sheets not loaded yet","error")}),(async()=>{var _a4;const bookTitle=((_a4=window.readerState)==null?void 0:_a4.title)||"";if(!bookTitle||typeof clGetAllByTagOrBook!="function")return;let existing=[];try{existing=await clGetAllByTagOrBook(bookTitle)}catch{}if(!existing.length)return;const castBtn=foot.querySelector("#ab-cv-cast-chars");if(!castBtn)return;const wrap=document.createElement("span");wrap.className="ab-cv-castchars-group",wrap.innerHTML=``,castBtn.replaceWith(wrap),wrap.querySelector(".ab-cv-castchars-view").addEventListener("click",()=>{typeof navTo=="function"&&navTo("s-library"),typeof navLibraryView=="function"&&navLibraryView("characters")}),wrap.querySelector(".ab-cv-castchars-menu").addEventListener("click",e=>{e.stopPropagation(),_abOpenRecastCharsMenu(e.currentTarget,bookTitle,existing)})})(),panel.classList.add("ab-castpanel-done"),typeof window.setNavCastingBadge=="function"&&window.setNavCastingBadge(!1)},done(options={}){var _a3,_b3,_c3,_d3,_e3;if(!(options.stopped||_audiobook.cancel)){closePanel();return}this.clearProcessing(),count&&(count.hidden=!0);const castBar=panel.querySelector(".ab-castpanel-bar");castBar&&(castBar.hidden=!0),(_a3=feed.querySelector(".ab-skel-feed"))==null||_a3.remove(),(_b3=chars.querySelector(".ab-skel-chars"))==null||_b3.remove();const message=options.message||"Casting stopped before any passage completed.";if(feed.querySelector(".ab-cv-row, .ab-cv-note, .ab-cv-divider, .ab-cv-page")){if(options.message){const r=document.createElement("div");r.className="ab-cv-note",r.textContent=message,feed.appendChild(r)}}else{feed.innerHTML="",_abCurPage=null;const r=document.createElement("div");r.className="ab-cv-note",r.textContent=message,feed.appendChild(r)}chars.querySelector(".ab-char-item")||(chars.innerHTML='No completed characters yet.');const foot=panel.querySelector("#ab-cv-foot"),hasSegments=Array.isArray(_audiobook.segments)&&_audiobook.segments.length>0;foot.hidden=!1,foot.innerHTML=` ${escHtml(message)}${hasSegments?'':""}`,(_c3=foot.querySelector("#ab-cv-stopped-back"))==null||_c3.addEventListener("click",closePanel),(_d3=foot.querySelector("#ab-cv-stopped-review"))==null||_d3.addEventListener("click",async()=>{closePanel(),await audiobookOpenCurrentInRehearser()}),(_e3=foot.querySelector("#ab-cv-stopped-recast"))==null||_e3.addEventListener("click",()=>{var _a4;const newPrompt=(_a4=panel.querySelector("#ab-cv-prompt-text"))==null?void 0:_a4.value,currentChoice=audiobookCurrentCastLlm(panel),choice=audiobookSaveLlmChoice(currentChoice.url,currentChoice.model);typeof _appSettings!="undefined"&&newPrompt&&(_appSettings.audiobook_prompt=newPrompt),fetch("/api/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({audiobook_prompt:newPrompt||""})}).finally(()=>{closePanel(),audiobookCast(choice.url,choice.model)})}),panel.classList.add("ab-castpanel-done"),typeof window.setNavCastingBadge=="function"&&window.setNavCastingBadge(!1)},loadingRestore(){var _a3,_b3;(_a3=feed.querySelector(".ab-skel-feed"))==null||_a3.remove(),(_b3=chars.querySelector(".ab-skel-chars"))==null||_b3.remove(),feed.innerHTML="",_abCurPage=null;const r=document.createElement("div");r.className="ab-cv-note",r.textContent="Loading saved cast\u2026",feed.appendChild(r),chars.innerHTML='restoring\u2026';const statusMsg=panel.querySelector("#ab-cv-status-msg");statusMsg&&(statusMsg.style.display="inline-block",statusMsg.textContent="Checking saved cast before starting a new one.");const castBtn=panel.querySelector("#ab-cv-start-cast");castBtn&&(castBtn.style.display="none")},setFreshState(message="Ready to cast."){var _a3,_b3;if((_a3=feed.querySelector(".ab-skel-feed"))==null||_a3.remove(),(_b3=chars.querySelector(".ab-skel-chars"))==null||_b3.remove(),!feed.querySelector(".ab-cv-row, .ab-cv-divider, .ab-cv-page")){feed.innerHTML="",_abCurPage=null;const r=document.createElement("div");r.className="ab-cv-note",r.textContent=message,feed.appendChild(r)}chars.querySelector(".ab-char-item")||(chars.innerHTML='No saved cast found.');const statusMsg=panel.querySelector("#ab-cv-status-msg");statusMsg&&(statusMsg.style.display="inline-block",statusMsg.textContent=message);const castBtn=panel.querySelector("#ab-cv-start-cast");castBtn&&(castBtn.style.display="inline-block",castBtn.addEventListener("click",()=>{var _a4;const newPrompt=(_a4=panel.querySelector("#ab-cv-prompt-text"))==null?void 0:_a4.value,currentChoice=audiobookCurrentCastLlm(panel),choice=audiobookSaveLlmChoice(currentChoice.url,currentChoice.model);typeof _appSettings!="undefined"&&newPrompt&&(_appSettings.audiobook_prompt=newPrompt),audiobookCast(choice.url,choice.model)}))}}}async function audiobookRecastUnknown(overrideUrl,overrideModel,options={}){var _a2,_b2,_c2,_d2,_e2;if(_audiobook.running)return;const segs=_audiobook.segments;if(!segs||!segs.length)return;const originalSegments=segs.map(s=>({...s})),countUnknownDialogue=arr=>(arr||[]).filter(s=>(s==null?void 0:s.type)==="dialogue"&&(!s.speaker||/^Unknown|Unbekannt/i.test(s.speaker))).length,beforeUnknownCount=countUnknownDialogue(segs),preResolved=audiobookResolveUnknowns(segs,[],_audiobook.roster||[]);preResolved.length&&toast(`${preResolved.length} Unknown line${preResolved.length!==1?"s":""} resolved by grammar rules`,"success");const unknownIdxs=[];for(let i=0;iac.abort(),overrideUrl&&typeof overrideUrl!="string"&&(overrideUrl=null);const llm_url=overrideUrl||audiobookLlmUrl(),language=audiobookLang();let model=audiobookSafeLlmModel(overrideModel||audiobookLlmModel());const promptOverride=typeof options.prompt=="string"?options.prompt:null,groups=audiobookRecastGroups(unknownIdxs,segs),view=audiobookCastView(unknownIdxs.length,llm_url,model);view.recountRoster(segs),view.processing("Waking up LLM model (this may take a few minutes if cold-booting)\u2026");try{await audiobookFetchWithTimeout("/api/attribute-dialogue",{method:"POST",headers:{"Content-Type":"application/json"},signal:ac.signal,body:JSON.stringify({text:"Wake up.",known_characters:[],recent:"",language,llm_url:((_a2=document.getElementById("ab-cv-llm-url"))==null?void 0:_a2.value.trim())||llm_url,model:audiobookSafeLlmModel(((_b2=document.getElementById("ab-cv-llm-select"))==null?void 0:_b2.value)||model),timeout_seconds:audiobookTimeoutSeconds(AUDIOBOOK_WARMUP_TIMEOUT_MS)})},AUDIOBOOK_WARMUP_TIMEOUT_MS+5e3)}catch(err){if(err.name==="AbortError"){_audiobook.cancel=!0,_audiobook.running=!1,_audiobook.abort=null,view.done({stopped:!0,message:"Character definition stopped before any lines were updated."}),toast("Character definition stopped. Existing cast preserved.","info");return}}let done=0,prevIdx=-2,groupsSinceSave=0;const pendingReplacements=new Map,normalizeRecastSegment=(seg,fallback)=>{const type=(seg==null?void 0:seg.type)==="narration"?"narration":"dialogue";return{speaker:type==="narration"?"Narrator":(seg==null?void 0:seg.speaker)||(fallback==null?void 0:fallback.speaker)||"Unknown",type,emotion:type==="dialogue"&&((seg==null?void 0:seg.emotion)||(fallback==null?void 0:fallback.emotion))||"",text:(seg==null?void 0:seg.text)||(fallback==null?void 0:fallback.text)||""}};try{for(let group of groups){if(_audiobook.cancel)break;const unresolvedGroup=[];for(const idx of group)audiobookIsSpeechTagOnly((_c2=segs[idx])==null?void 0:_c2.text)?(segs[idx].type="narration",segs[idx].speaker="Narrator",segs[idx].emotion=""):unresolvedGroup.push(idx);if(!unresolvedGroup.length){group[0]!==prevIdx+1&&view.divider(prevIdx,group[0]),prevIdx=group[group.length-1],view.update(done,`Correcting narration tags ${done+1}-${done+group.length} / ${unknownIdxs.length}\u2026`);for(const idx of group)view.addSegments([segs[idx]]),done++;continue}group=unresolvedGroup,group[0]!==prevIdx+1&&view.divider(prevIdx,group[0]),prevIdx=group[group.length-1];const recastCtx=audiobookRecastContext(segs,group),passageText=recastCtx.text,lineLabel=group.length>1?`Attributing lines ${done+1}-${done+group.length} / ${unknownIdxs.length}\u2026`:`Attributing line ${done+1} / ${unknownIdxs.length}\u2026`;view.update(done,lineLabel),view.processing(passageText.trim());let data=null;const recastBody=audiobookAttributeBody({text:passageText.trim(),known_characters:_audiobook.roster.slice(-40),recent:recastCtx.recent||"",language,llm_url:((_d2=document.getElementById("ab-cv-llm-url"))==null?void 0:_d2.value.trim())||llm_url,model:audiobookSafeLlmModel(((_e2=document.getElementById("ab-cv-llm-select"))==null?void 0:_e2.value)||model),timeout_seconds:audiobookTimeoutSeconds(AUDIOBOOK_RECAST_TIMEOUT_MS)},promptOverride);try{try{data=await audiobookAttributeStream(recastBody,view,ac.signal,AUDIOBOOK_RECAST_TIMEOUT_MS)}catch(streamErr){if(streamErr.name==="AbortError")throw streamErr;const r=await audiobookFetchWithTimeout("/api/attribute-dialogue",{method:"POST",headers:{"Content-Type":"application/json"},signal:ac.signal,body:JSON.stringify(recastBody)},AUDIOBOOK_RECAST_TIMEOUT_MS+5e3);if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||e.error||r.statusText||"HTTP "+r.status)}data=await r.json()}}catch(err){if(err.name==="AbortError"){_audiobook.cancel=!0;break}view.note(`API Error while checking ${group.length>1?"a group of Unknown lines":"Unknown line"}: ${err.message||err}`);for(const idx of group)view.addSegments([segs[idx]]),done++;continue}if(data&&data.segments){const used=new Set;for(const idx of group){const targetSeg=segs[idx],seqMatch=audiobookFindReturnedSegmentSequence(targetSeg,data.segments,used);if(seqMatch&&seqMatch.segs.length>1){const repl=seqMatch.segs.map(s=>normalizeRecastSegment(s,targetSeg)).filter(s=>(s.text||"").trim()),hasResolved=repl.some(s=>s.type==="narration"||s.speaker&&!/^Unknown|Unbekannt/i.test(s.speaker)),hasUnresolvedDialogue=repl.some(s=>s.type==="dialogue"&&(!s.speaker||/^Unknown|Unbekannt/i.test(s.speaker)));if(repl.length&&hasResolved&&!hasUnresolvedDialogue){seqMatch.idxs.forEach(i=>used.add(i)),pendingReplacements.set(idx,repl),repl.forEach(s=>{s.type==="dialogue"&&s.speaker&&!/^Unknown|Unbekannt/i.test(s.speaker)&&!_audiobook.roster.includes(s.speaker)&&_audiobook.roster.push(s.speaker)});continue}}const match=audiobookFindReturnedSegment(targetSeg,data.segments,used);if(match&&match.seg.speaker){const speaker=match.seg.type==="narration"?"Narrator":match.seg.speaker;if(!speaker||/^Unknown|Unbekannt/i.test(speaker))continue;used.add(match.idx),targetSeg.type=match.seg.type==="narration"?"narration":"dialogue",targetSeg.speaker=speaker,targetSeg.emotion=targetSeg.type==="dialogue"&&(match.seg.emotion||targetSeg.emotion)||"",targetSeg.type==="dialogue"&&!_audiobook.roster.includes(speaker)&&_audiobook.roster.push(speaker)}}}for(const idx of group)view.addSegments(pendingReplacements.get(idx)||[segs[idx]]),done++;groupsSinceSave++,groupsSinceSave>=10&&_audiobook.lastText&&(groupsSinceSave=0,_abSaveDraft(_audiobook.segments||[],_audiobook.roster||[],_audiobook.lastText,_audiobook.completedChunks||0,_audiobook.completedTotal||0),view.recountRoster(segs))}view.update(_audiobook.cancel?done:unknownIdxs.length)}catch(e){e.name!=="AbortError"&&view.note("Error defining unknowns: "+e.message)}finally{_audiobook.running=!1,_audiobook.abort=null}pendingReplacements.size&&[...pendingReplacements.entries()].sort((a,b)=>b[0]-a[0]).forEach(([idx,repl])=>segs.splice(idx,1,...repl));const afterUnknownCount=countUnknownDialogue(segs);!_audiobook.cancel&&afterUnknownCount>beforeUnknownCount&&(segs.splice(0,segs.length,...originalSegments),view.note(`Quality run rolled back: Unknown segments increased from ${beforeUnknownCount} to ${afterUnknownCount}. Existing cast preserved.`),toast("Quality run rolled back because it increased Unknown speakers.","error"));const _rcDone=_audiobook.completedChunks||segs.length,_rcTotal=_audiobook.completedTotal||_rcDone;if(_audiobook.cancel){_audiobook.lastText&&_abSaveDraft(_audiobook.segments||[],_audiobook.roster||[],_audiobook.lastText,_rcDone,_rcTotal),view.done({stopped:!0,message:"Character definition stopped. Existing cast preserved."}),toast("Character definition stopped. Existing cast preserved.","info");return}_audiobook.lastText&&_abSaveDraft(_audiobook.segments||[],_audiobook.roster||[],_audiobook.lastText,_rcDone,_rcTotal);const speakers=new Set(segs.filter(s=>s.type==="dialogue"&&s.speaker).map(s=>s.speaker)),summary=`${speakers.size} character${speakers.size!==1?"s":""} \xB7 ${segs.length} segments`;view.complete(summary,audiobookShowPreview,audiobookCast,audiobookRecastUnknown)}async function audiobookOpenCastView(){var _a2;if(_audiobook.running)return;const text=audiobookScopeText();if(!text){toast("Import a document first","error");return}const _curBook=window.readerState&&readerState.savedId||null;_curBook&&_audiobook.bookId&&_audiobook.bookId!==_curBook&&(_audiobook.segments=null,_audiobook.lastText=null,_audiobook.roster=null),_audiobook.bookId=_curBook;const llm_url=audiobookLlmUrl(),model=audiobookLlmModel(),chunks=typeof splitTextIntoChunks=="function"?splitTextIntoChunks(text,AUDIOBOOK_CHUNK_CHARS):[text];if(_audiobook.segments&&_audiobook.segments.length>0&&_audiobook.lastText===text){const view=audiobookCastView(chunks.length,llm_url,model,!1);view.rebuild(_audiobook.segments);const speakers=new Set(_audiobook.segments.filter(s=>s.type==="dialogue"&&s.speaker).map(s=>s.speaker)),summary=`${speakers.size} character${speakers.size!==1?"s":""} \xB7 ${_audiobook.segments.length} segments`;view.complete(summary,audiobookShowPreview,audiobookCast,audiobookRecastUnknown);return}const _applyDraft=(draft,view,source)=>{const draftDone=Number.isFinite(Number(draft.done))?Number(draft.done):0,draftTotal=Number.isFinite(Number(draft.total))?Number(draft.total):0;_abStampSegmentPages(draft.segments,draft.pageMarks,text),_audiobook.segments=draft.segments,_audiobook.roster=draft.roster||[],_audiobook.lastText=text,_audiobook.pageMarks=draft.pageMarks||[],_audiobook.rehId=draft.rehId||null,_audiobook.completedChunks=draftDone>0?draftDone:0,_audiobook.completedTotal=draftTotal>0?draftTotal:0,view.rebuild(draft.segments);const ageMs=Date.now()-(draft.savedAt||0),ageMins=Math.round(ageMs/6e4),ageStr=ageMins<1?"gerade eben":ageMins<60?`vor ${ageMins} Min.`:`vor ${Math.round(ageMins/60)} Std.`,pct=draftTotal>0?Math.max(0,Math.min(100,Math.round(draftDone/draftTotal*100))):100,wasDone=draftTotal>0&&draftDone>=draftTotal,canContinue=draftDone>0&&draftTotal>0&&draftDones.type==="dialogue"&&s.speaker).map(s=>s.speaker)),summary=`${speakers.size} Charakter${speakers.size!==1?"e":""} \xB7 ${draft.segments.length} Segmente`;view.complete(summary,audiobookShowPreview,audiobookCast,audiobookRecastUnknown)},_localDraft=_abLoadDraft(text);if(_localDraft&&_localDraft.segments&&_localDraft.segments.length>0){const view=audiobookCastView(chunks.length,llm_url,model,!1);if(_applyDraft(_localDraft,view,"local"),_abBookId()){const serverCopy={..._localDraft,bookId:_abBookId(),title:((_a2=window.readerState)==null?void 0:_a2.title)||_localDraft.title||""};fetch(`/api/reader/docs/${encodeURIComponent(_abBookId())}/scripts/cast`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(serverCopy)}).catch(()=>{})}return}const bookId=_abBookId();if(bookId){const view=audiobookCastView(chunks.length,llm_url,model,!1);view.loadingRestore();try{const serverDraft=await _abLoadDraftServer(bookId);if(serverDraft&&serverDraft.segments&&serverDraft.segments.length>0){try{localStorage.setItem(_abDraftKey(bookId),JSON.stringify(serverDraft))}catch{}_applyDraft(serverDraft,view,"server")}else view.setFreshState("No saved cast was found for this saved book. Starting a new cast will create a new draft.")}catch{view.setFreshState("Saved cast could not be checked. Browser autosave was already searched; starting a new cast will create a new draft.")}return}audiobookCastView(chunks.length,llm_url,model,!0)}async function audiobookCast(overrideUrl,overrideModel,resume){var _a2,_b2;if(_audiobook.running)return;const text=audiobookScopeText();if(!text){toast("Import a document first","error");return}if(typeof parseScript!="function"){toast("Rehearser not loaded yet \u2014 try again in a moment","error");return}_audiobook.running=!0,!!_abBookId()||!await _abEnsureLibraryBook()&&typeof toast=="function"&&toast("Autosave will use this browser only until the book is saved to the library.","info");const chunks=typeof splitTextIntoChunks=="function"?splitTextIntoChunks(text,AUDIOBOOK_CHUNK_CHARS):[text],startIndex=resume&&resume.startIndex>0&&resume.startIndex0;_audiobook.cancel=!1,isResume||_abClearDraft(),typeof window.setNavCastingBadge=="function"&&window.setNavCastingBadge(!0);const ac=new AbortController;_audiobook.abort=()=>ac.abort(),overrideUrl&&typeof overrideUrl!="string"&&(overrideUrl=null);const llm_url=overrideUrl||audiobookLlmUrl(),language=audiobookLang(text);let model=audiobookSafeLlmModel(overrideModel||audiobookLlmModel());const view=audiobookCastView(chunks.length,llm_url,model,!1);isResume&&resume.segments&&resume.segments.length&&view.rebuild(resume.segments),view.processing("Waking up LLM model (this may take a few minutes if cold-booting)\u2026");try{await audiobookFetchWithTimeout("/api/attribute-dialogue",{method:"POST",headers:{"Content-Type":"application/json"},signal:ac.signal,body:JSON.stringify({text:"Wake up.",known_characters:[],recent:"",language,llm_url:((_a2=document.getElementById("ab-cv-llm-url"))==null?void 0:_a2.value.trim())||llm_url,model:audiobookSafeLlmModel(((_b2=document.getElementById("ab-cv-llm-select"))==null?void 0:_b2.value)||model),timeout_seconds:audiobookTimeoutSeconds(AUDIOBOOK_WARMUP_TIMEOUT_MS)})},AUDIOBOOK_WARMUP_TIMEOUT_MS+5e3)}catch(err){if(err.name==="AbortError"){_audiobook.cancel=!0,_audiobook.running=!1,_audiobook.abort=null,view.done({stopped:!0,message:"Casting stopped before any passage completed."}),toast("Casting stopped before any passages were saved.","info");return}}const allSegments2=isResume?resume.segments.slice():[];_audiobook.liveSegments=allSegments2;const roster=isResume?(resume.roster||[]).slice():[];let narrationOnly=isResume&&resume.narrationOnly||0,degraded=isResume&&resume.degraded||0,completedChunks=startIndex;_abStartDraftAutosave(()=>{allSegments2.length&&_abSaveDraft(allSegments2,roster,text,completedChunks,chunks.length)});const _pgMarks=(_audiobook.pageMarks||[]).slice();_pgMarks.length&&_pgMarks[0].offset<=2&&_pgMarks.shift();let _pgMarkIdx=0,_pgCharPos=0,_curPageNum=1;if(isResume){for(let k=0;k0&&(_curPageNum=_pgMarks[_pgMarkIdx-1].page+1)}try{for(let i=startIndex;i=_pgMarks[_pgMarkIdx].offset;)_curPageNum=_pgMarks[_pgMarkIdx].page+1,view.pagemark(_curPageNum),_pgMarkIdx++;if(_pgCharPos+=chunks[i].length+1,!audiobookHasDialogue(chunks[i])){const seg={speaker:"Narrator",type:"narration",text:chunks[i],emotion:"",page:_curPageNum};allSegments2.push(seg),narrationOnly++,view.addSegments([seg]),completedChunks=i+1,_abSaveDraft(allSegments2,roster,text,completedChunks,chunks.length);continue}const recent=allSegments2.filter(s=>s.type==="dialogue"&&s.speaker&&!/^Unknown|Unbekannt/i.test(s.speaker)).slice(-6).map(s=>`${s.speaker}: ${(s.text||"").slice(0,80)}`).join(` `);view.processing(chunks[i]);const attributeChunk=async(chunkText,recentCtx,timeoutMs=AUDIOBOOK_ATTRIBUTION_TIMEOUT_MS)=>{var _a3,_b3;const body={text:chunkText,known_characters:roster.slice(-40),recent:recentCtx,language,llm_url:((_a3=document.getElementById("ab-cv-llm-url"))==null?void 0:_a3.value.trim())||llm_url,model:audiobookSafeLlmModel(((_b3=document.getElementById("ab-cv-llm-select"))==null?void 0:_b3.value)||model),timeout_seconds:audiobookTimeoutSeconds(timeoutMs)};try{const d=await audiobookAttributeStream(body,view,ac.signal,timeoutMs);return Array.isArray(d.segments)?d.segments:null}catch(err){if(err.name==="AbortError")throw err}try{const r=await audiobookFetchWithTimeout("/api/attribute-dialogue",{method:"POST",headers:{"Content-Type":"application/json"},signal:ac.signal,body:JSON.stringify(body)},timeoutMs+5e3);if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.detail||e.error||r.statusText||"HTTP "+r.status)}const d=await r.json();return Array.isArray(d.segments)?d.segments:null}catch(err){if(err.name==="AbortError")throw err;return{error:err.message}}};let segs;try{let result=await attributeChunk(chunks[i],recent),data=null;if(result&&!result.error)data=result;else if(result&&result.error){view.note(`\u26A0\uFE0F Passage ${i+1} timed out \u2014 retrying in two halves\u2026`);const half=Math.floor(chunks[i].length/2),splitAt=chunks[i].lastIndexOf(" ",half)||half,chunkA=chunks[i].slice(0,splitAt).trim(),chunkB=chunks[i].slice(splitAt).trim(),resA=await attributeChunk(chunkA,recent,AUDIOBOOK_ATTRIBUTION_RETRY_TIMEOUT_MS),resB=await attributeChunk(chunkB,recent,AUDIOBOOK_ATTRIBUTION_RETRY_TIMEOUT_MS),segsA=Array.isArray(resA)?resA:null,segsB=Array.isArray(resB)?resB:null;if(segsA||segsB){const arrA=segsA||audiobookSplitByQuotes(chunkA),arrB=segsB||audiobookSplitByQuotes(chunkB);data=[...arrA,...arrB],segsA||(degraded++,view.note(`\u26A0\uFE0F Passage ${i+1} (first half) \u2014 auto-detected (retry also failed)`)),segsB||(degraded++,view.note(`\u26A0\uFE0F Passage ${i+1} (second half) \u2014 auto-detected (retry also failed)`))}else view.note(`\u274C Passage ${i+1} \u2014 LLM error: ${result.error}`),data=null}segs=Array.isArray(data)?data:data&&Array.isArray(data.segments)?data.segments:[];const hasDialogue=segs.some(s=>s.type==="dialogue");if(!segs.length||!hasDialogue&&audiobookHasDialogue(chunks[i])){segs=audiobookSplitByQuotes(chunks[i]),degraded++;const named=segs.filter(s=>s.type==="dialogue"&&!/^Unknown|Unbekannt/i.test(s.speaker)).length;view.note(`Passage ${i+1} \u2014 auto-detected dialogue${named?` (${named} speaker${named!==1?"s":""} from tags)`:" (set speakers in review)"}`)}else{let cleanedSegs=[];for(let s of segs){if(s.text=s.text||"",s.type==="dialogue"&&audiobookIsSpeechTagOnly(s.text)&&(s.type="narration",s.speaker="Narrator",s.emotion=""),s.type==="dialogue"&&s.text.trim().length>10){const tText=s.text.trim(),idx=chunks[i].indexOf(tText);if(idx!==-1){const surround=chunks[i].slice(Math.max(0,idx-8),idx)+chunks[i].slice(idx+tText.length,idx+tText.length+8);/[«»„“”"‟‚‘’›‹『「—–]/.test(surround)||(s.type="narration",s.speaker="Narrator",s.emotion="")}}if(cleanedSegs.length>0){let last=cleanedSegs[cleanedSegs.length-1];if(s.text.trim()===last.text.trim())if(s.type==="dialogue"&&last.type!=="dialogue"){cleanedSegs[cleanedSegs.length-1]=s;continue}else{if(s.type!=="dialogue"&&last.type==="dialogue")continue;continue}if(s.type==="dialogue"&&last.type==="narration"){const tS=s.text.trim(),tL=last.text.trim();tL.endsWith(tS)&&(last.text=tL.slice(0,-tS.length).trim(),last.text||cleanedSegs.pop())}}s.text.trim()&&cleanedSegs.push(s)}let mergedSegs=[];for(let s of cleanedSegs){if(mergedSegs.length>0){let last=mergedSegs[mergedSegs.length-1];if(s.type==="narration"&&last.type==="narration"&&(s.speaker||"Narrator").toLowerCase()==="narrator"&&(last.speaker||"Narrator").toLowerCase()==="narrator"){/[.!?]$/.test(last.text.trim())?last.text=last.text.trimEnd()+` `+s.text.trimStart():last.text=last.text.trimEnd()+" "+s.text.trimStart();continue}}mergedSegs.push(s)}segs=mergedSegs}}catch(chunkErr){if(chunkErr.name==="AbortError")throw chunkErr;degraded++,segs=audiobookSplitByQuotes(chunks[i]),view.note(`\u274C Passage ${i+1} \u2014 unexpected error (${chunkErr.message}) \u2014 auto-detected dialogue instead`)}audiobookResolveUnknowns(segs,allSegments2.slice(-2),roster),segs.forEach(s=>{const speakerName=s.type!=="dialogue"||!s.speaker||s.speaker.toLowerCase()==="narrator"?"Narrator":s.speaker;!/^Unknown|Unbekannt/i.test(speakerName)&&!roster.includes(speakerName)&&roster.push(speakerName)}),segs.forEach(s=>{s.page=_curPageNum,allSegments2.push(s)}),view.addSegments(segs),completedChunks=i+1,_abSaveDraft(allSegments2,roster,text,completedChunks,chunks.length)}view.update(_audiobook.cancel?completedChunks:chunks.length)}catch(err){err.name==="AbortError"?(_audiobook.cancel=!0,view.note("Casting stopped. Completed passages were preserved.")):(_audiobook.cancel=!0,view.note(`\u274C Casting stopped \u2014 unexpected error: ${err.message}`))}finally{_abStopDraftAutosave(),_audiobook.running=!1,_audiobook.abort=null}if(!allSegments2.length){if(_audiobook.cancel){view.done({stopped:!0,message:"Casting stopped before any passage completed."}),toast("Casting stopped before any passages were saved.","info");return}view.done(),toast("No segments produced","error");return}_audiobook.segments=allSegments2,_audiobook.lastText=text,_audiobook.roster=roster,_audiobook.narratedPassages=narrationOnly,_audiobook.degraded=degraded,_audiobook.completedChunks=_audiobook.cancel?completedChunks:chunks.length,_audiobook.completedTotal=chunks.length,_abSaveDraft(allSegments2,roster,text,_audiobook.completedChunks,chunks.length),audiobookSaveAsRehearsal({silent:!0}),_audiobook.cancel&&toast("Casting stopped early. Progress preserved.","info");const speakers=new Set(allSegments2.filter(s=>s.type==="dialogue"&&s.speaker).map(s=>s.speaker)),summary=`${_audiobook.cancel&&completedChunks{const i=+inp.dataset.i,v=inp.value.trim()||"Narrator";segs[i].speaker=v,segs[i].type=v.toLowerCase()==="narrator"?"narration":"dialogue"}),document.querySelectorAll("#audiobook-seglist .audiobook-seg-emo").forEach(inp=>{const i=+inp.dataset.i;segs[i].emotion=inp.value.trim()});const{script,emotions}=audiobookBuildScript(segs);audiobookOpenInRehearser(script,readerState.title||"Audiobook",emotions)}function audiobookBuildScript(segments){let script="";const emotions=[],marks=_audiobook.pageMarks||[],src=_audiobook.lastText||"";let markIdx=0,searchPos=0;marks.length&&marks[0].offset<=2&&(markIdx=1);for(const s of segments){const t=(s.text||"").trim();if(!t)continue;if(src&&markIdx=0?at:searchPos;for(at>=0&&(searchPos=at+probe.length);markIdx=marks[markIdx].offset;)script.trim()&&(script+=` \f${marks[markIdx].page+1} `),markIdx++}s.type==="dialogue"&&s.speaker&&s.speaker.toLowerCase()!=="narrator"?(script+=` `+s.speaker.toUpperCase()+` `+t+` `,emotions.push(s.emotion||"")):script+=` `+t+` `}return{script:script.trim(),emotions}}async function audiobookOpenInRehearser(script,title,dialogueEmotions){$("reh-script-text")&&($("reh-script-text").value=script),$("reh-script-title")&&($("reh-script-title").value=title),typeof navTo=="function"&&navTo("s-rehearser");const btn=$("reh-parse-btn");btn?btn.click():typeof parseScript=="function"&&(rehState.lines=parseScript(script));let speakers=0,lines=0;if(window.rehState&&Array.isArray(rehState.lines)){let k=0;rehState.lines.forEach(l=>{if(l.type==="dialog"){const e=dialogueEmotions[k++];e&&(l.emotion=e),lines++}}),speakers=Object.keys(rehState.cast||{}).filter(s=>!String(s).includes("NARRATOR")).length}let saved=!1;if(typeof saveToLibrary=="function")try{rehState.savedId=null,await saveToLibrary(),saved=!0}catch{}toast(`Cast ${speakers} character${speakers!==1?"s":""} \xB7 ${lines} lines`+(saved?" \u2014 saved to Rehearser \u2192 Bibliothek":""),"success")}function audiobookIsChapter(line){if(line.type==="act"||line.type==="scene")return!0;const t=(typeof stripMarkdown=="function"?stripMarkdown(line.text||""):line.text||"").trim();return!t||t.length>60?!1:/^(chapter|kapitel|chap\.?|part|book|prologue|epilogue|prolog|epilog|teil)\b/i.test(t)}function audiobookLineVoice(l){if(l.type==="dialog"){const c=rehState.cast[l.speaker]||{};return{voice:c.voice,instruct:typeof _buildInstruct=="function"?_buildInstruct(c.instruct,l.emotion):""}}return{voice:rehState.narratorVoice,instruct:""}}async function audiobookExport(){var _a2,_b2;if(_audiobook.running)return;if(!window.rehState||!(rehState.lines||[]).length){toast("Open a script in the rehearser first","error");return}if(!rehState.backend){toast("Select a TTS backend in the rehearser first","error");return}typeof _ensureNarrator=="function"&&_ensureNarrator();const speakable=i=>{const l=rehState.lines[i];if(!l||l.ignored||l.hidden)return!1;if(l.type==="dialog"){const c=rehState.cast[l.speaker];return!!(c&&c.voice&&c.voice!=="me")}return!!(rehState.narratorVoice&&(l.text||"").trim())},buckets=[];let cur=null;rehState.lines.forEach((l,i)=>{audiobookIsChapter(l)&&(cur={title:(typeof stripMarkdown=="function"?stripMarkdown(l.text):l.text).trim().slice(0,50),idx:[]},buckets.push(cur)),speakable(i)&&(cur||(cur={title:"",idx:[]},buckets.push(cur)),cur.idx.push(i))});const allIdx=buckets.flatMap(b=>b.idx);if(!allIdx.length){toast("Nothing to synthesise \u2014 cast voices first","error");return}_audiobook.running=!0,_audiobook.cancel=!1;const prog=audiobookProgress(allIdx.length),mp3=new Map;let done=0;const queue=allIdx.slice(),worker=async()=>{for(;queue.length&&!_audiobook.cancel;){const i=queue.shift(),l=rehState.lines[i],{voice,instruct}=audiobookLineVoice(l),text=typeof _rehInlineTone=="function"?_rehInlineTone(stripMarkdown(l.text),l.emotion):typeof stripMarkdown=="function"?stripMarkdown(l.text):l.text;try{mp3.set(i,await fetchTtsPreviewBlob(voice,text,"mp3",instruct,rehState.backend))}catch{}prog.update(++done,`Synthesising line ${done} / ${allIdx.length}\u2026`)}};try{await Promise.all(Array.from({length:Math.min(2,allIdx.length)},worker))}finally{prog.done(),_audiobook.running=!1}if(_audiobook.cancel){toast("Export cancelled","error");return}const title=typeof readerSafeName=="function"?readerSafeName(((_a2=$("reh-script-title"))==null?void 0:_a2.value)||"Audiobook"):((_b2=$("reh-script-title"))==null?void 0:_b2.value)||"Audiobook",realChapters=buckets.filter(b=>b.title).length>0;let files=0;for(let c=0;cmp3.get(i)).filter(Boolean);if(!blobs.length)continue;const blob=new Blob(blobs,{type:"audio/mpeg"}),ch=buckets[c].title?" "+readerSafeName(buckets[c].title):"",name=realChapters||buckets.length>1?`${title} - ${String(c+1).padStart(2,"0")}${ch}.mp3`:`${title}.mp3`;typeof readerDownload=="function"&&readerDownload(blob,name),files++,await new Promise(r=>setTimeout(r,400))}toast("Exported audiobook \xB7 "+files+(realChapters?" chapter MP3 file(s)":" MP3 file(s)"),"success")}(_lc=$("reader-audiobook-btn"))==null||_lc.addEventListener("click",audiobookOpenCastView),(_mc=$("reh-tb-audiobook"))==null||_mc.addEventListener("click",audiobookExport);async function _audiobookBuildRehRecord(segs){const{script,emotions}=audiobookBuildScript(segs),title=typeof readerState!="undefined"&&readerState.title?readerState.title:"Audiobook",lines=typeof parseScript=="function"?parseScript(script):[];if(emotions&&emotions.length){let eIdx=0;lines.forEach(l=>{l.type==="dialog"&&eIdx{cast[sp]={voice:def.voice,color:def.color,instruct:"",lang:"",gender:"",tags:"",soul:"",ignored:!1,hidden:!1,voiceData:null}})}let existing=null;if(_audiobook.rehId&&typeof window.rehDbGetById=="function")try{existing=await window.rehDbGetById(_audiobook.rehId)}catch{}existing&&existing.cast&&Object.entries(existing.cast).forEach(([sp,info])=>{cast[sp]?cast[sp]={...cast[sp],...info}:cast[sp]=info});const emotions_map={};return lines.forEach((l,i)=>{l.type==="dialog"&&l.emotion&&(emotions_map[i]=l.emotion)}),{title,script,cast,emotions:emotions_map,notes:existing?existing.notes||{}:{},ignored:existing?existing.ignored||{}:{},hidden:existing?existing.hidden||{}:{},backend:existing&&existing.backend||"",narratorVoice:existing&&existing.narratorVoice||"",lineIndex:existing&&existing.lineIndex||0,clips:existing?existing.clips||[]:[],created:existing?existing.created:new Date,updated:new Date}}let _abRehSaveTimer=null;async function audiobookSaveAsRehearsal(opts){const silent=opts&&opts.silent,segs=_audiobook.segments;if(!segs||!segs.length){silent||toast("No segments to save","error");return}if(typeof rehDbAdd!="function"){silent||toast("Rehearser DB not available","error");return}try{const rec=await _audiobookBuildRehRecord(segs);_audiobook.rehId?(rec.id=_audiobook.rehId,await rehDbPut(rec)):_audiobook.rehId=await rehDbAdd(rec),typeof renderLibraryList=="function"&&renderLibraryList(),silent||(_abClearDraft(),toast("Saved as Script Rehearsal","success"))}catch(e){silent?console.warn("[audiobook] auto-save failed:",e):toast("Failed to save rehearsal: "+e.message,"error")}}function _audiobookDebouncedSave(){clearTimeout(_abRehSaveTimer),_abRehSaveTimer=setTimeout(()=>audiobookSaveAsRehearsal({silent:!0}),1500)}const CS_CHUNK_CHARS=4e3,_cs={running:!1,cancel:!1,cache:{}},CS_DEFAULT_PROMPT=`You are an expert dramaturge, developmental editor, and tabletop RPG game master building rich character sheets passage by passage as a book is read. Extract playable, action-oriented sheets an actor can use to immediately know how to PLAY the character. PROGRESSIVE FILLING: you may be given the sheets built so far. For returning characters, ADD any NEW detail this passage reveals and refine vague fields; do not contradict solid earlier facts or blank out a field you cannot improve. In this cast-character pass, ONLY refine the already-casted roster and do NOT invent new profiles, places, or institutions. The text may be a focused evidence window around a mention, so use the nearby paragraphs as context. Leave a field empty if the book genuinely hasn't shown it yet (a later passage can fill it). Extrapolate from dialogue and actions when reasonable, and mark any deduced value with a trailing ' *'. For each character output these fields: - name: canonical display name for this one character. Use the real personal name if known; otherwise use the most stable role/title. - aliases: ONLY alternate names, roles, epithets, mistranscriptions, and titles proven to refer to the SAME character, comma-separated (max 6 items; e.g. 'Henker, Vampir, Zerwas der Henker'). Leave empty when uncertain. - first_name, last_name, full_name: split the character identity when known. Leave unknown parts empty. - title: nobility title only, if the text explicitly gives one (e.g. 'Graf', 'Baron', 'Ritter'). - profession: occupation / job / role in the story (e.g. 'Inquisitor', 'Soldier', 'Merchant', 'Priest'). - archetype: a two-word role summary (e.g. 'Ruthless Scholar') - gender: 'male', 'female', or 'nonbinary' \u2014 as apparent from the text (pronouns, roles, physical description). Leave empty if genuinely indeterminable. - physical: age, height, build, hair, eyes, skin, posture, gait, vocal quality. Use ONLY metric system. - clothing: distinctive clothing, armour, accessories \u2014 as observed in the text - alignment: strict moral code + the one line they will never cross - moral_alignment_score: integer 0\u2013100. 100 = purely good/heroic, 0 = purely evil/villainous, 50 = neutral/ambiguous - arc_direction: one of: 'stable-good', 'stable-bad', 'neutral', 'good-to-bad', 'bad-to-good', 'complex' - arc_note: one sentence explaining the arc or moral position visible so far - attribute_high / attribute_low: highest and lowest natural attribute (Charisma, Intelligence, Wisdom, Agility\u2026) - skills: what they are demonstrably good at in the story - capabilities: combat, magic, social, technical, or other demonstrated abilities - backstory: origin, formative background and history revealed in the text - relationships: key allies, family, rivals and enemies \u2014 name them and how they relate - motivation: the inner drive \u2014 WHY they pursue what they pursue (distinct from the win condition) - fears: their deepest fears, phobias or dread - mannerisms: habitual gestures, tics, body language, habits and quirks - voice_pattern: speech style \u2014 accent, pacing, vocabulary, register and verbal tics (for voice casting) - voice_design_prompt: concise English Qwen voice-design prompt (15-45 words). Include age impression, gender/androgyny if inferable, pitch, timbre, pace, accent/register, emotional baseline and suitability for audiobook dialogue. Do NOT mention plot spoilers. - image_prompt: detailed English image-generation prompt for this character. Include face, age impression, build, hair/eyes/skin if known, clothing, posture, props, mood, genre/style, and visible symbols. Mark inferred traits with '*'. - inventory: 1-3 defining items/props/clothing (array of short strings) - secret: dark secret or fatal flaw - conflict_style: fight, flight, or manipulate \u2014 how they act when cornered - win_condition: the specific event that would make them feel they have won - tier: 'main' or 'supporting' - sources: array of {page, quote, line_hint} \u2014 the page number from the nearest [p.N] marker, a short verbatim quote that supports the sheet (1-12 entries), and a brief label (e.g. 'physical', 'clothing', 'relationships', 'motivation'). Use null page if unknown. line_hint MUST name the supported field when possible: physical, clothing, relationships, motivation, fears, mannerisms, voice_pattern, backstory, alignment, skills, capabilities, secret, conflict_style, or win_condition. IDENTITY MERGING: A character may appear under multiple names in the book (first name, last name, title, role, alias, nickname). Examples: 'Zerwas', 'Henker', and 'Vampir' may all refer to ONE profile if context shows they are the same person. Do NOT create separate sheets for aliases/titles of the same person; put the alternate forms in aliases/title/full_name and keep one canonical name. Reuse the EXACT names from the known-characters list for returning characters when they are the canonical name or an alias of this character. Do not merge characters merely because their names appear near each other, in the known-character list, or in relationships. Respond with STRICT JSON only: {"sheets":[{"name":"","aliases":"","first_name":"","last_name":"","full_name":"","title":"","profession":"","archetype":"","gender":"","physical":"","clothing":"","alignment":"","moral_alignment_score":50,"arc_direction":"neutral","arc_note":"","attribute_high":"","attribute_low":"","skills":"","capabilities":"","backstory":"","relationships":"","motivation":"","fears":"","mannerisms":"","voice_pattern":"","voice_design_prompt":"","image_prompt":"","inventory":[],"secret":"","conflict_style":"","win_condition":"","tier":"main","sources":[{"page":1,"quote":"","line_hint":""}]}]} /no-think`;function csLlmUrl(){var _a2;const explicit=(_a2=$("reh-llm-url"))==null?void 0:_a2.value.trim();if(explicit)return explicit;const fromSettings=typeof _appSettings!="undefined"&&_appSettings&&_appSettings.llm_url?_appSettings.llm_url:"";return fromSettings&&!/localhost:11434|127\.0\.0\.1:11434/.test(fromSettings)?fromSettings:""}function csLlmModel(){var _a2;return((_a2=$("reh-llm-model"))==null?void 0:_a2.value)||""}function csLang(){var _a2;return((_a2=$("reh-design-lang"))==null?void 0:_a2.value)||""}function csReaderPageHost(){return $("reader-charsheets-panel")}function csReaderText(){var _a2,_b2,_c2;if(typeof readerScopeIndices!="function"||!((_a2=readerState==null?void 0:readerState.sentences)!=null&&_a2.length))return"";let out="",lastPage=-1;for(const i of readerScopeIndices()){const u=readerState.sentences[i],pg=(_c2=(_b2=u.words)==null?void 0:_b2[0])==null?void 0:_c2.page;readerState.mode==="pdf"&&pg!=null&&pg!==lastPage&&(out+=` [p.${pg+1}] `,lastPage=pg),out+=u.text+" "}return out.trim()}function csRehearserText(){if(!window.rehState||!(rehState.lines||[]).length)return"";let out="",page=1,started=!1;for(const l of rehState.lines){if(l.type==="pagebreak"){page++,out+=` [p.${page}] `;continue}const t=(typeof stripMarkdown=="function"?stripMarkdown(l.text||""):l.text||"").trim();t&&(started||(out+="[p.1] ",started=!0),out+=(l.type==="dialog"&&l.speaker?l.speaker+": ":"")+t+` `)}return out.trim()}const CS_SCALAR_FIELDS=["aliases","first_name","last_name","full_name","title","profession","archetype","physical","clothing","alignment","arc_note","attribute_high","attribute_low","skills","capabilities","backstory","relationships","motivation","fears","mannerisms","voice_pattern","secret","conflict_style","win_condition","voice_design_prompt","image_prompt","silly_tavern_prompt","concept_art_prompt"],CS_DETAIL_FIELDS=CS_SCALAR_FIELDS.filter(f=>!["aliases","first_name","last_name","full_name","title"].includes(f)),CS_IDENTITY_FIELDS=["name","aliases","first_name","last_name","full_name","title"],CS_ALIAS_MAX_TOKENS=12,CS_ALIAS_MAX_CHARS=500,CS_SOURCE_FIELDS={physical:["physical","appearance","body","look"],clothing:["clothing","appearance","armour","armor","item"],relationships:["relationship","ally","rival","enemy","family"],motivation:["motivation","intention","goal","desire"],fears:["fear","dread"],mannerisms:["mannerism","habit","gesture","voice"],voice_pattern:["voice","speech","dialogue"],backstory:["backstory","origin","history"],alignment:["alignment","ethos","morality"],profession:["profession","occupation","job","role"],skills:["skill","capability","ability"],capabilities:["capability","ability","combat","magic"],secret:["secret","flaw"],conflict_style:["conflict","fight","flight","manipulate"],win_condition:["win","goal"]};function csExistingSummary(map){if(!map.size)return"";const KEY_FIELDS=["physical","profession","backstory","motivation","voice_pattern","relationships"];return[...map.values()].slice(0,40).map(s=>{const missing=KEY_FIELDS.filter(f=>!(s[f]||"").trim()),suffix=missing.lengths.trim()).filter(Boolean).filter(s=>s.length<=80&&!/^needs?:/i.test(s)&&!/^complete$/i.test(s));return opts.aliases&&(raw.length>CS_ALIAS_MAX_CHARS||parts.length>CS_ALIAS_MAX_TOKENS)?[]:parts.slice(0,opts.aliases?CS_ALIAS_MAX_TOKENS:void 0)}function csCleanRoster(names){const out=[],seen=new Set;return(names||[]).forEach(n=>{const name=_csStr(n).trim(),key=name.toLowerCase();!name||seen.has(key)||key==="narrator"||/^unknown|unbekannt$/i.test(name)||(seen.add(key),out.push(name))}),out}function csRosterKey(v){return String(v||"").trim().toLowerCase()}function csKnownReaderRoster(){const names=[],add=name=>{name=_csStr(name).trim();const key=name.toLowerCase();!name||key==="narrator"||/^unknown|unbekannt$/i.test(name)||names.some(n=>n.toLowerCase()===key)||names.push(name)},ab=typeof _audiobook!="undefined"?_audiobook:window._audiobook;return ab&&((ab.roster||[]).forEach(add),[ab.segments,ab.liveSegments].forEach(segs=>{(segs||[]).forEach(s=>{(s==null?void 0:s.type)==="dialogue"&&add(s.speaker)})})),document.querySelectorAll("#ab-cv-chars .ab-char-item[data-name], #audiobook-roster option[value]").forEach(el=>{add(el.dataset.name||el.value||el.textContent)}),csCleanRoster(names)}function csBlankSheet(name){return{name,aliases:"",first_name:"",last_name:"",full_name:"",title:"",profession:"",archetype:"",gender:"",physical:"",clothing:"",alignment:"",arc_note:"",attribute_high:"",attribute_low:"",skills:"",capabilities:"",backstory:"",relationships:"",motivation:"",fears:"",mannerisms:"",voice_pattern:"",secret:"",conflict_style:"",win_condition:"",voice_design_prompt:"",image_prompt:"",inventory:[],sources:[],moral_alignment_score:50,arc_direction:"neutral",tier:"supporting"}}function csSeedSheet(raw){const name=_csStr(raw==null?void 0:raw.name).trim(),seed=csBlankSheet(name);if(!name)return seed;const src=raw!=null&&raw.sheet&&typeof raw.sheet=="object"?raw.sheet:raw;if(CS_SCALAR_FIELDS.forEach(f=>{src&&src[f]!=null&&(seed[f]=_csStr(src[f]))}),Array.isArray(src==null?void 0:src.inventory)?seed.inventory=[...src.inventory]:typeof(src==null?void 0:src.inventory)=="string"&&(seed.inventory=src.inventory.split(",").map(x=>x.trim()).filter(Boolean)),Array.isArray(src==null?void 0:src.sources)&&(seed.sources=src.sources.map(item=>item&&typeof item=="object"?{page:item.page!=null?item.page:null,quote:_csStr(item.quote).slice(0,240),line_hint:_csStr(item.line_hint).slice(0,60)}:null).filter(Boolean)),src!=null&&src.tier&&(seed.tier=String(src.tier).toLowerCase().startsWith("main")?"main":"supporting"),(src==null?void 0:src.moral_alignment_score)!=null){const mas=parseInt(src.moral_alignment_score,10);Number.isNaN(mas)||(seed.moral_alignment_score=Math.max(0,Math.min(100,mas)))}return src!=null&&src.arc_direction&&(seed.arc_direction=src.arc_direction),src!=null&&src.gender&&(seed.gender=String(src.gender).trim().toLowerCase()),(src==null?void 0:src.line_count)!=null&&(seed.line_count=src.line_count),seed}function csNameTokens(sheet){const out=new Set;for(const f of CS_IDENTITY_FIELDS){const raw=f==="name"?sheet==null?void 0:sheet.name:sheet==null?void 0:sheet[f];csSplitIdentityTokens(raw,{aliases:f==="aliases"}).forEach(s=>out.add(s.toLowerCase()))}return out}function csSheetMatchesRoster(sheet,roster=[]){const allowed=new Set((roster||[]).map(csRosterKey).filter(Boolean));if(!allowed.size)return!0;for(const token of csNameTokens(sheet))for(const name of allowed)if(token===name||token.includes(name)||name.includes(token))return!0;return!1}function csIdentityNeedles(value){return csSplitIdentityTokens(value,{aliases:!0}).filter(v=>{const n=String(v||"").trim();return n&&n.length>1&&!/^(die|der|das|den|dem|des|ein|eine|einer|er|sie|es|ich|du|wir|ihr)$/i.test(n)})}function csRecordNeedles(rec){var _a2,_b2,_c2,_d2;const seen=new Set,out=[],add=value=>{csIdentityNeedles(value).forEach(v=>{const key=v.toLowerCase();seen.has(key)||(seen.add(key),out.push(v))})};return add(rec==null?void 0:rec.name),add((_a2=rec==null?void 0:rec.sheet)==null?void 0:_a2.aliases),add((_b2=rec==null?void 0:rec.sheet)==null?void 0:_b2.full_name),add([(_c2=rec==null?void 0:rec.sheet)==null?void 0:_c2.first_name,(_d2=rec==null?void 0:rec.sheet)==null?void 0:_d2.last_name].filter(Boolean).join(" ")),out}function csParagraphBlocks(text){return String(text||"").replace(/\r\n/g,` `).split(/\n\s*\n+/).map(s=>s.trim()).filter(Boolean)}function csReaderParagraphBlocks(){var _a2,_b2,_c2;if(readerState.mode==="text"&&readerState.docText)return csParagraphBlocks(readerState.docText);const base=Array.isArray(readerState.baseSentences)&&readerState.baseSentences.length?readerState.baseSentences:Array.isArray(readerState.sentences)?readerState.sentences:[];if(!base.length)return csParagraphBlocks(csReaderText());const paras=[];let cur="",curPage=null;for(const s of base){const page=(_c2=(_b2=(_a2=s==null?void 0:s.words)==null?void 0:_a2[0])==null?void 0:_b2.page)!=null?_c2:null,pageChanged=readerState.mode==="pdf"&&page!=null&&curPage!=null&&page!==curPage;(s!=null&&s.paraStart||pageChanged)&&cur.trim()&&(paras.push(cur.trim()),cur=""),!cur&&readerState.mode==="pdf"&&page!=null&&(cur+=`[p.${page+1}] `),cur+=(cur&&!/\s$/.test(cur)?" ":"")+String((s==null?void 0:s.text)||"").trim(),curPage=page!=null?page:curPage}return cur.trim()&¶s.push(cur.trim()),paras}function csParagraphHasNeedle(paragraph,needle){const p=String(paragraph||""),n=String(needle||"").trim();if(!n||n.length<2)return!1;const lowerP=p.toLowerCase(),lowerN=n.toLowerCase();if(lowerN.includes(" "))return lowerP.includes(lowerN);const esc=lowerN.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`(^|[^\\p{L}\\p{N}_])${esc}(?=$|[^\\p{L}\\p{N}_])`,"iu").test(p)}function csEvidenceWindowText(text,needles,before=2,after=2){const paras=Array.isArray(text)?text.slice():csParagraphBlocks(text);if(!paras.length)return String(text||"").trim();const hits=new Set;return paras.forEach((para,idx)=>{if((needles||[]).some(n=>csParagraphHasNeedle(para,n)))for(let i=idx-before;i<=idx+after;i++)i>=0&&ia-b).map(i=>paras[i]).join(` `).trim():""}function csFindMergeKey(map,sheet){const incoming=csNameTokens(sheet);if(!incoming.size)return(sheet.name||"").trim().toLowerCase();for(const[key,existing]of map.entries()){const existingNames=csNameTokens(existing);for(const n of incoming)if(existingNames.has(n))return key}return(sheet.name||"").trim().toLowerCase()}function csMergeAliases(existing,incoming){const names=new Map,add=v=>csSplitIdentityTokens(v,{aliases:!0}).forEach(s=>names.set(s.toLowerCase(),s));return add(existing.aliases),add(incoming.aliases),incoming.name&&incoming.name!==existing.name&&add(incoming.name),[...names.values()].filter(n=>n.toLowerCase()!==String(existing.name||"").toLowerCase()).join(", ")}function csMerge(map,sheets){const changed=[];for(const s of sheets){const name=(s.name||"").trim();if(!name)continue;s.aliases=csMergeAliases({name,aliases:""},s);const key=csFindMergeKey(map,s);if(!map.has(key)){const copy={...s,name,inventory:[...s.inventory||[]],sources:[...s.sources||[]]};CS_SCALAR_FIELDS.forEach(f=>{copy[f]=_csStr(copy[f])}),map.set(key,copy),changed.push({key,name:copy.name,status:"new"});continue}const e=map.get(key),before=JSON.stringify(e),oldAliases=e.aliases;CS_SCALAR_FIELDS.forEach(f=>{const sv=_csStr(s[f]);sv.length>(e[f]||"").length&&(e[f]=sv)}),e.aliases=csMergeAliases({...e,aliases:oldAliases},s),s.tier==="main"&&(e.tier="main"),s.moral_alignment_score!=null&&(e.moral_alignment_score=e.moral_alignment_score!=null?Math.round((e.moral_alignment_score+s.moral_alignment_score)/2):s.moral_alignment_score),s.arc_direction&&s.arc_direction!=="neutral"&&(e.arc_direction=s.arc_direction),s.gender&&!e.gender&&(e.gender=s.gender),(s.inventory||[]).forEach(it=>{it&&!e.inventory.includes(it)&&e.inventory.length<3&&e.inventory.push(it)}),(s.sources||[]).forEach(src=>{src&&src.quote&&e.sources.length<12&&!e.sources.some(x=>x.quote===src.quote)&&e.sources.push(src)}),JSON.stringify(e)!==before&&changed.push({key,name:e.name,status:"updated"})}return changed}function csProgressSnapshot(map,changed=[]){const changedByKey=new Map(changed.map(x=>[x.key,x.status]));return[...map.entries()].map(([key,s])=>{const filled=CS_DETAIL_FIELDS.filter(f=>_csStr(s[f]).trim()).length;return{key,name:s.name||key,alias:[s.full_name,s.title,s.profession].filter(Boolean).join(" \xB7 "),status:changedByKey.get(key)||"",filled,main:s.tier==="main"}}).sort((a,b)=>(b.status?1:0)-(a.status?1:0)||b.filled-a.filled||a.name.localeCompare(b.name))}async function csGenerateStream(body,onDelta,outerSignal,idleTimeoutMs=6e4){const ctl=new AbortController,onAbort=()=>ctl.abort();outerSignal&&(outerSignal.aborted?ctl.abort():outerSignal.addEventListener("abort",onAbort,{once:!0}));let idleTimer=null;const armIdle=()=>{clearTimeout(idleTimer),idleTimer=setTimeout(()=>ctl.abort(),idleTimeoutMs)};try{armIdle();const r=await fetch("/api/character-sheets/stream",{method:"POST",headers:{"Content-Type":"application/json"},signal:ctl.signal,body:JSON.stringify(body)});if(!r.ok||!r.body)throw new Error("stream HTTP "+r.status);const reader=r.body.getReader(),dec=new TextDecoder;let buf="",result=null;for(;;){const{done,value}=await reader.read();if(done)break;armIdle(),buf+=dec.decode(value,{stream:!0});let at;for(;(at=buf.indexOf(` `))>=0;){const line=buf.slice(0,at).trim();if(buf=buf.slice(at+2),!line.startsWith("data:"))continue;let d;try{d=JSON.parse(line.slice(5))}catch{continue}if(d.t&&onDelta&&onDelta(d.t),d.error)throw new Error(d.error);d.done&&(result=d.result||null)}}if(!result)throw new Error("stream ended without result");return result}finally{clearTimeout(idleTimer),outerSignal&&outerSignal.removeEventListener("abort",onAbort)}}async function csGenerate(text,cacheKey,initialRoster,opts={}){if(_cs.running)return null;if(!text)return toast("Nothing to analyse","error"),null;const chunks=typeof splitTextIntoChunks=="function"?splitTextIntoChunks(text,CS_CHUNK_CHARS):[text];_cs.running=!0,_cs.cancel=!1;const prog=csProgress(chunks.length,opts.pageHost||null),llm_url=csLlmUrl(),model=csLlmModel();let language=csLang();if(typeof detectLang=="function"){const detected=detectLang(text);if(detected&&(!language||/^auto$/i.test(language)||language.toLowerCase()==="english"&&detected.toLowerCase()!=="english")){language=detected;const sel=$("reh-design-lang");if(sel){const o=[...sel.options].find(x=>x.value.toLowerCase()===detected.toLowerCase()||x.textContent.toLowerCase()===detected.toLowerCase());o&&(sel.value=o.value)}}}const map=new Map;(Array.isArray(opts.seedSheets)?opts.seedSheets:[]).forEach(seedRaw=>{const seed=csSeedSheet(seedRaw),name=String(seed.name||"").trim();name&&map.set(name.toLowerCase(),seed)});const roster=csCleanRoster(initialRoster),targetMode=roster.length>0,knownTotal=targetMode?roster.length:null;roster.forEach(name=>{const key=name.toLowerCase();map.has(key)||map.set(key,csBlankSheet(name))});try{for(let i=0;iCS_DETAIL_FIELDS.some(f=>(s[f]||"").trim())).length,charLabel=knownTotal?`${knownTotal} cast characters queued \xB7 ${detailed} profiles with details`:`${map.size} characters found`;prog.update(i,`Passage ${i+1} / ${chunks.length}\u2026`,charLabel,csProgressSnapshot(map),[...map.values()]),prog.startPassage(chunks[i]);try{const character_sheets_prompt=typeof _appSettings!="undefined"&&_appSettings.character_sheets_prompt||CS_DEFAULT_PROMPT,body={text:chunks[i],known_characters:roster.slice(0,120),target_mode:targetMode,existing:csExistingSummary(map),language,llm_url,model,character_sheets_prompt};let data=null,sawDelta=!1;try{data=await csGenerateStream(body,delta=>{sawDelta=!0,prog.thinking(delta)})}catch{sawDelta||prog.noStream();const r=await fetch("/api/character-sheets",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});if(!r.ok){let detail=`HTTP ${r.status}`;try{const e=await r.json();detail=e.detail||e.error||detail}catch{}throw new Error(detail)}const raw=await r.text();try{data=JSON.parse(raw)}catch{throw new Error("Invalid JSON from server (passage may be too large)")}}const incomingSheets=targetMode?(data.sheets||[]).filter(s=>csSheetMatchesRoster(s,roster)):data.sheets||[],changed=csMerge(map,incomingSheets);changed.length&&prog.focus(changed[0].name),targetMode||((data.characters||[]).forEach(n=>{roster.some(r=>r.toLowerCase()===String(n||"").toLowerCase())||roster.push(n)}),(data.sheets||[]).forEach(s=>{csNameTokens(s).forEach(n=>{const display=[s.name,s.full_name,s.title,s.profession,s.aliases].filter(Boolean).join(", ").split(/[,;/|]/).map(x=>x.trim()).find(x=>x.toLowerCase()===n)||n;display&&!roster.some(r=>r.toLowerCase()===display.toLowerCase())&&roster.push(display)})}));const newDetailed=[...map.values()].filter(s=>CS_DETAIL_FIELDS.some(f=>(s[f]||"").trim())).length,newCharLabel=knownTotal?`${knownTotal} cast characters queued \xB7 ${newDetailed} profiles with details`:`${map.size} characters found`;prog.update(i+1,`Passage ${i+1} / ${chunks.length}\u2026`,newCharLabel,csProgressSnapshot(map,changed),[...map.values()])}catch(e){console.error("Character sheets passage",i+1,"failed:",e),toast("Passage "+(i+1)+" failed: "+(e.message||String(e)),"error")}}}finally{prog.done(),_cs.running=!1}if(_cs.cancel)return toast("Cancelled","error"),null;const sheets=[...map.values()];return cacheKey&&(_cs.cache[cacheKey]=sheets),sheets}function csProgress(total,hostEl=null){var _a2,_b2;const usingHost=!!hostEl;let root=usingHost?hostEl:document.getElementById("cs-progress");if(!root){if(usingHost)return null;root=document.createElement("div"),root.id="cs-progress",root.className="audiobook-overlay",document.body.appendChild(root)}if(!root.querySelector("#cs-progress-fill")){usingHost&&(root.innerHTML=""),root.innerHTML=`
Character sheets
Analysing\u2026
Select a character to preview the sheet as it fills.
Passage / Live output watching\u2026
Passage

                
Live output

                
Characters found
`,(_a2=root.querySelector("#cs-progress-cancel"))==null||_a2.addEventListener("click",()=>{_cs.cancel=!0});const promptBtn=root.querySelector("#cs-prompt-btn"),promptPanel=root.querySelector("#cs-prompt-panel"),promptText=root.querySelector("#cs-prompt-text");promptText&&(promptText.value=typeof _appSettings!="undefined"&&_appSettings.character_sheets_prompt||CS_DEFAULT_PROMPT),promptBtn==null||promptBtn.addEventListener("click",()=>{if(!promptPanel)return;promptPanel.hidden=!promptPanel.hidden;const chevron=root.querySelector("#cs-prompt-chevron");chevron&&(chevron.className=promptPanel.hidden?"mdi mdi-chevron-down":"mdi mdi-chevron-up")});let savedCsPrompts=[];try{savedCsPrompts=JSON.parse(localStorage.getItem("ttsvc_cs_prompts")||"[]")}catch{savedCsPrompts=[]}const libSelect=root.querySelector("#cs-prompt-lib"),delBtn=root.querySelector("#cs-prompt-del"),nameInput=root.querySelector("#cs-prompt-name"),renderCsPromptLib=(selectedIdx=-1)=>{!libSelect||!delBtn||(libSelect.innerHTML=''+savedCsPrompts.map((p,i)=>``).join(""),selectedIdx>=0?(libSelect.value=selectedIdx,delBtn.style.display="inline-flex"):(libSelect.value="",delBtn.style.display="none"))};renderCsPromptLib(),libSelect==null||libSelect.addEventListener("change",()=>{const idx=parseInt(libSelect.value,10);!isNaN(idx)&&savedCsPrompts[idx]?(promptText&&(promptText.value=savedCsPrompts[idx].prompt),nameInput&&(nameInput.value=savedCsPrompts[idx].name),delBtn.style.display="inline-flex"):(nameInput&&(nameInput.value=""),delBtn.style.display="none")}),delBtn==null||delBtn.addEventListener("click",()=>{const idx=parseInt(libSelect.value,10);isNaN(idx)||confirm("Delete this saved prompt preset?")&&(savedCsPrompts.splice(idx,1),localStorage.setItem("ttsvc_cs_prompts",JSON.stringify(savedCsPrompts)),renderCsPromptLib(),toast("Prompt deleted","success"))}),(_b2=root.querySelector("#cs-prompt-save"))==null||_b2.addEventListener("click",async()=>{const val=(promptText==null?void 0:promptText.value.trim())||"";if(!val){toast("Prompt is empty","error");return}const name=(nameInput==null?void 0:nameInput.value.trim())||"Custom Prompt "+(savedCsPrompts.length+1);let targetIdx=parseInt((libSelect==null?void 0:libSelect.value)||"",10);!isNaN(targetIdx)&&savedCsPrompts[targetIdx]&&savedCsPrompts[targetIdx].name===name?savedCsPrompts[targetIdx].prompt=val:(savedCsPrompts.push({name,prompt:val}),targetIdx=savedCsPrompts.length-1),localStorage.setItem("ttsvc_cs_prompts",JSON.stringify(savedCsPrompts)),renderCsPromptLib(targetIdx),toast("Prompt preset saved","success")});let promptSaveTimer=null;promptText==null||promptText.addEventListener("input",()=>{clearTimeout(promptSaveTimer),promptSaveTimer=setTimeout(()=>{const val=promptText.value;typeof _appSettings!="undefined"&&(_appSettings.character_sheets_prompt=val),fetch("/api/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({character_sheets_prompt:val})}).catch(()=>{})},500)})}usingHost||(root.style.display="flex");const fill=root.querySelector("#cs-progress-fill"),msg=root.querySelector("#cs-progress-msg"),chars=root.querySelector("#cs-progress-chars"),sideTitle=root.querySelector("#cs-progress-side-title"),sideDone=root.querySelector("#cs-progress-side-done"),sideCollapse=root.querySelector("#cs-progress-side-collapse"),leftPane=root.querySelector(".cs-progress-left"),outputPanel=root.querySelector("#cs-progress-output"),outputCollapse=root.querySelector("#cs-progress-output-collapse"),outputResize=root.querySelector("#cs-progress-output-resize"),preview=root.querySelector("#cs-progress-preview"),search=root.querySelector("#cs-progress-search"),passagePre=root.querySelector("#cs-progress-passage-pre"),livePre=root.querySelector("#cs-progress-live-pre"),liveStatus=root.querySelector("#cs-progress-live-status");let currentRoster=[],currentSheets=[],selectedName="",searchQuery="";const keyFor=v=>String(v||"").trim().toLowerCase(),findSheet=(name,sheets=currentSheets)=>(Array.isArray(sheets)?sheets:[]).find(s=>keyFor(s==null?void 0:s.name)===keyFor(name))||null;let outputCollapsed=!1,outputExpandedHeight=0;const clampOutputHeight=h=>{const max=Math.max(140,Math.round(window.innerHeight*.82));return Math.max(140,Math.min(max,Math.round(h)))},setOutputHeight=(h,persist=!0)=>{const next=clampOutputHeight(h);if(outputExpandedHeight=next,!outputCollapsed&&outputPanel&&(outputPanel.style.height=`${next}px`,outputPanel.style.maxHeight=`${next}px`,outputPanel.dataset.csHeight=String(next)),persist)try{localStorage.setItem("ttsvc_cs_output_height",String(next))}catch{}},applyOutputCollapse=collapsed=>{if(outputCollapsed=!!collapsed,outputPanel==null||outputPanel.classList.toggle("is-collapsed",outputCollapsed),leftPane==null||leftPane.classList.toggle("is-output-collapsed",outputCollapsed),outputCollapse){const icon=outputCollapse.querySelector(".mdi");icon&&(icon.className="mdi "+(outputCollapsed?"mdi-chevron-up":"mdi-chevron-down")),outputCollapse.title=outputCollapsed?"Expand passage and live output":"Collapse passage and live output"}if(outputPanel)if(outputCollapsed){if(!outputExpandedHeight){const cur=parseInt(outputPanel.dataset.csHeight||"",10)||parseInt(outputPanel.style.height||"",10)||outputPanel.getBoundingClientRect().height;cur&&(outputExpandedHeight=clampOutputHeight(cur))}outputPanel.style.height="44px",outputPanel.style.maxHeight="44px"}else{const h=outputExpandedHeight||parseInt(outputPanel.dataset.csHeight||"",10)||260;outputPanel.style.height=`${clampOutputHeight(h)}px`,outputPanel.style.maxHeight=`${clampOutputHeight(h)}px`,outputPanel.dataset.csHeight=String(clampOutputHeight(h))}try{localStorage.setItem("ttsvc_cs_output_collapsed",outputCollapsed?"1":"0")}catch{}};let sideCollapsed=!1;const applySideCollapse=collapsed=>{var _a3,_b3;if(sideCollapsed=!!collapsed,(_a3=root.querySelector("#cs-progress-layout"))==null||_a3.classList.toggle("side-collapsed",sideCollapsed),(_b3=root.querySelector("#cs-progress-side"))==null||_b3.classList.toggle("is-collapsed",sideCollapsed),sideCollapse){const icon=sideCollapse.querySelector(".mdi");icon&&(icon.className="mdi "+(sideCollapsed?"mdi-chevron-left":"mdi-chevron-right")),sideCollapse.title=sideCollapsed?"Expand character list":"Collapse character list"}try{localStorage.setItem("ttsvc_cs_side_collapsed",sideCollapsed?"1":"0")}catch{}};try{sideCollapsed=localStorage.getItem("ttsvc_cs_side_collapsed")==="1"}catch{}try{const savedOutputCollapsed=localStorage.getItem("ttsvc_cs_output_collapsed");savedOutputCollapsed!==null&&(outputCollapsed=savedOutputCollapsed==="1");const savedOutputHeight=parseInt(localStorage.getItem("ttsvc_cs_output_height")||"",10);!Number.isNaN(savedOutputHeight)&&savedOutputHeight>0&&(outputExpandedHeight=clampOutputHeight(savedOutputHeight))}catch{}if(applySideCollapse(sideCollapsed),applyOutputCollapse(outputCollapsed),sideCollapse==null||sideCollapse.addEventListener("click",()=>applySideCollapse(!sideCollapsed)),outputCollapse==null||outputCollapse.addEventListener("click",()=>applyOutputCollapse(!outputCollapsed)),outputResize){let dragging=!1,startY=0,startH=0;const onMove=e=>{dragging&&(outputCollapsed&&applyOutputCollapse(!1),setOutputHeight(startH+(e.clientY-startY)),e.preventDefault())},stopDrag=()=>{dragging=!1,document.body.classList.remove("cs-output-resizing"),document.removeEventListener("mousemove",onMove),document.removeEventListener("mouseup",stopDrag)};outputResize.addEventListener("mousedown",e=>{e.preventDefault(),outputPanel&&(outputCollapsed&&applyOutputCollapse(!1),dragging=!0,startY=e.clientY,startH=parseInt(outputPanel.dataset.csHeight||"",10)||outputPanel.getBoundingClientRect().height||260,document.body.classList.add("cs-output-resizing"),document.addEventListener("mousemove",onMove),document.addEventListener("mouseup",stopDrag))})}const renderPreview=()=>{var _a3;if(!preview)return;const sheet=findSheet(selectedName)||currentSheets[0]||null;if(!sheet){preview.innerHTML='
Select a character to preview the sheet as it fills.
';return}preview.innerHTML=csCardHtml(sheet),(_a3=preview.querySelector(".cs-card"))==null||_a3.classList.add("cs-progress-preview-card"),preview.querySelectorAll(".cs-head-btns, .cs-sources").forEach(el=>el.remove()),preview.querySelectorAll(".cs-avatar").forEach(el=>{el.style.pointerEvents="none",el.style.cursor="default"})},renderChars=()=>{if(!chars)return;const list=currentRoster.filter(c=>{if(!searchQuery)return!0;const name=keyFor(c.name),alias=keyFor(c.alias);return name.includes(searchQuery)||alias.includes(searchQuery)});chars.innerHTML=list.length?list.map(c=>`
${escHtml((c.name||"?")[0].toUpperCase())} ${escHtml(c.name||"Unknown")} ${escHtml(String(c.filled||0))} ${CS_DETAIL_FIELDS.length}
`).join(""):'
reading\u2026
',chars.querySelectorAll(".ab-char-item").forEach(el=>{el.addEventListener("click",()=>{selectedName=keyFor(el.dataset.name),renderPreview()})})};return search==null||search.addEventListener("input",()=>{searchQuery=search.value.trim().toLowerCase(),renderChars()}),renderPreview(),{startPassage(text){passagePre&&(passagePre.textContent=text||""),livePre&&(livePre.textContent="Waiting for streamed JSON output\u2026"),liveStatus&&(liveStatus.textContent="watching\u2026",liveStatus.className="cs-progress-live-status"),outputPanel==null||outputPanel.classList.add("is-working")},thinking(delta){!livePre||!delta||(liveStatus&&(liveStatus.textContent="streaming",liveStatus.className="cs-progress-live-status is-live"),outputPanel==null||outputPanel.classList.add("is-working"),livePre.textContent=(livePre.textContent+delta).slice(-2e4),livePre.scrollTop=livePre.scrollHeight)},noStream(){liveStatus&&(liveStatus.textContent="no live output for this model",liveStatus.className="cs-progress-live-status")},focus(name){name&&(selectedName=keyFor(name),renderPreview())},update(d,label,charLabel,roster=[],sheets=[]){if(fill&&(fill.style.width=d/total*100+"%"),msg&&label&&(msg.textContent=label),sideTitle&&(sideTitle.textContent="Characters found"),sideDone&&(sideDone.innerHTML=charLabel?` ${escHtml(charLabel)}`:""),outputPanel==null||outputPanel.classList.add("is-working"),currentRoster=Array.isArray(roster)?roster:[],currentSheets=Array.isArray(sheets)?sheets:[],!selectedName&¤tRoster.length&&(selectedName=keyFor((currentRoster.find(c=>c.status)||currentRoster[0]||{}).name)),currentSheets.length&&!findSheet(selectedName)){const changedSheet=currentRoster.find(c=>c.status&&findSheet(c.name,currentSheets));changedSheet&&(selectedName=keyFor(changedSheet.name))}renderChars(),renderPreview()},done(){outputPanel==null||outputPanel.classList.remove("is-working"),usingHost||(root.style.display="none")}}}function csAlignmentBar(score,arcDirection,arcNote){if(score==null)return"";const pct=Math.max(0,Math.min(100,score)),arcMap={"stable-good":{arrow:"\u2192",label:"Stable Good",color:"#66bb6a"},"stable-bad":{arrow:"\u2192",label:"Stable Evil",color:"#aaa"},neutral:{arrow:"\u2192",label:"Neutral",color:"#aaa"},"good-to-bad":{arrow:"\u2198",label:"Descends toward evil",color:"#ff7043"},"bad-to-good":{arrow:"\u2197",label:"Redeems toward good",color:"#66bb6a"},complex:{arrow:"\u2195",label:"Complex arc",color:"#ab47bc"}},arc=arcMap[arcDirection]||arcMap.neutral,label=pct>=70?"Good":pct<=30?"Evil":"Neutral/Ambiguous",showArrow=arcDirection&&arcDirection!=="neutral";return`
EvilGood
${showArrow?`${arc.arrow}`:""}
${arcNote?`
${escHtml(arc.label)} \xB7 ${escHtml(arcNote)}
`:`
${escHtml(arc.label)}
`}
`}function csBuildImagePrompt(s){var _a2;if(_csStr(s.image_prompt).trim())return _csStr(s.image_prompt).trim();const parts=[];s.archetype&&parts.push(s.archetype),s.physical&&parts.push(s.physical),s.clothing&&parts.push(s.clothing),s.alignment&&parts.push(s.alignment);const pct=(_a2=s.moral_alignment_score)!=null?_a2:50;return parts.push(pct>=70?"benevolent expression":pct<=30?"dark and menacing presence":"ambiguous expression"),s.arc_direction==="bad-to-good"&&parts.push("redemptive aura"),s.arc_direction==="good-to-bad"&&parts.push("ominous aura, turning to darkness"),`Portrait of ${s.name}, ${parts.filter(Boolean).join(", ")}, fantasy character art, detailed face, dramatic lighting, high detail.`}function csBuildVoicePrompt(s){var _a2;if(_csStr(s.voice_design_prompt).trim())return _csStr(s.voice_design_prompt).trim();const parts=[];s.voice_pattern&&parts.push(s.voice_pattern),s.mannerisms&&parts.push(s.mannerisms),s.archetype&&parts.push(`archetype: ${s.archetype}`);const pct=(_a2=s.moral_alignment_score)!=null?_a2:50;return pct>=70?parts.push("warm, trustworthy tone"):pct<=30?parts.push("cold, threatening or sinister tone"):parts.push("neutral, measured tone"),parts.filter(Boolean).join(". ")}async function csDeepAnalysis(sheet,sourceText){const llm_url=csLlmUrl(),model=csLlmModel(),language=csLang(),modal=document.createElement("div");modal.className="audiobook-overlay",modal.innerHTML=`
Deep Analysis \u2014 ${escHtml(sheet.name)}

Running deep psychological analysis\u2026
`,document.body.appendChild(modal),modal.querySelector("#cs-deep-close").addEventListener("click",()=>modal.remove()),modal.addEventListener("click",e=>{e.target===modal&&modal.remove()});try{const summary=[sheet.archetype,sheet.alignment,sheet.secret,sheet.win_condition,sheet.conflict_style].filter(Boolean).join("; "),r=await fetch("/api/character-deep-analysis",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:sheet.name,role:sheet.tier,goal:sheet.win_condition,summary,text_excerpt:(sourceText||"").slice(0,8e3),language,llm_url,model})});if(!r.ok)throw new Error((await r.json().catch(()=>({}))).detail||r.statusText);const a=(await r.json()).analysis||{},section=(icon,title,content)=>content?`
${title}
${escHtml(String(content))}
`:"";modal.querySelector("#cs-deep-body").innerHTML=`
${escHtml(sheet.name)} ${sheet.archetype?`${escHtml(sheet.archetype)}`:""}
${csAlignmentBar(sheet.moral_alignment_score,sheet.arc_direction,sheet.arc_note)}
${section("mdi-heart-broken-outline","1 \u2014 Core Flaw & Desire",a.core_flaw)} ${section("mdi-run-fast","2 \u2014 Agency & Passivity",a.agency)} ${section("mdi-comment-quote-outline","3 \u2014 Dialogue & Voice",a.dialogue_voice)} ${section("mdi-timeline-outline","4 \u2014 Narrative Arc",a.narrative_arc)} ${section("mdi-infinity","5 \u2014 Paradox & Depth",a.paradox)} `}catch(err){modal.querySelector("#cs-deep-body").innerHTML=`
Error: ${escHtml(String(err.message))}
`}}function csSourcesForField(s,field){const keys=(CS_SOURCE_FIELDS[field]||[field]).map(x=>x.toLowerCase());return(s.sources||[]).filter(src=>{const hint=_csStr(src==null?void 0:src.line_hint).toLowerCase();return hint&&keys.some(k=>hint.includes(k))})}function csSourceBadgeHtml(s,field){const src=csSourcesForField(s,field)[0];if(!src)return"";const idx=(s.sources||[]).findIndex(x=>x===src),n=idx>=0?idx+1:1;return``}function csField(label,value,sheet,field){if(!value)return"";const badge=sheet&&field?csSourceBadgeHtml(sheet,field):"";return`
${label}
${escHtml(String(value))}${badge?` ${badge}`:""}
`}function csIdentityCell(label,value,sheet,field){if(!value)return"";const badge=sheet&&field?csSourceBadgeHtml(sheet,field):"";return`
${label}
${escHtml(String(value))}${badge?` ${badge}`:""}
`}function csPromptBox(label,value,sheetKey){const text=_csStr(value),has=!!text.trim();return`
${escHtml(label)}${has?"":' \u2014 not generated yet'}
${escHtml(text)}
`}function csAvatarHtml(s){const hue=Math.abs((s.name||"?").split("").reduce((h,c)=>(h*31+c.charCodeAt(0))%360,0));return s._image?`
${escHtml(s.name)}
`:`
${escHtml((s.name||"?")[0].toUpperCase())}
`}function csIdentityTagList(s){const tags=[],add=(label,value)=>{const text=String(value||"").trim();if(!text)return;const key=text.toLowerCase();tags.some(t=>t.key===key)||tags.push({label,text,key})};s.full_name&&String(s.full_name).trim()&&String(s.full_name).trim()!==String(s.name||"").trim()&&add("full name",s.full_name);const firstLast=[s.first_name,s.last_name].filter(Boolean).join(" ").trim();return firstLast&&firstLast!==String(s.full_name||"").trim()&&firstLast!==String(s.name||"").trim()&&add("first / last",firstLast),tags}function csCardHtml(s){const inv=(s.inventory||[]).filter(Boolean),fullName=String(s.full_name||"").trim(),metaTags=csIdentityTagList(s),identityHtml=`
${csIdentityCell("Name",s.name||fullName,s,"name")} ${csIdentityCell("First Name",s.first_name,s,"first_name")} ${csIdentityCell("Last Name",s.last_name,s,"last_name")} ${csIdentityCell("Full Name",fullName&&fullName.toLowerCase()!==String(s.name||"").toLowerCase()?fullName:"",s,"full_name")} ${csIdentityCell("Gender",s.gender,s,"gender")} ${csIdentityCell("Title",s.title,s,"title")} ${csIdentityCell("Occupation",s.profession,s,"profession")} ${csIdentityCell("Also Known As",s.aliases,s,"aliases")}
`,invHtml=inv.length?`
Signature Items
    ${inv.map(i=>`
  • ${escHtml(i)}
  • `).join("")}
`:"",attrs=s.attribute_high||s.attribute_low?`
Core Attributes
\u25B2 ${escHtml(s.attribute_high||"\u2014")}  \xB7  \u25BC ${escHtml(s.attribute_low||"\u2014")}
`:"",sources=(s.sources||[]).filter(x=>x&&(x.quote||x.page!=null||x.line_hint)),srcHtml=sources.length?`
Sources / Evidence
${sources.map((x,i)=>``).join("")}
`:"",promptHtml=`
${csPromptBox("Voice Design Prompt",s.voice_design_prompt,"voice_design_prompt")} ${csPromptBox("Character Image Prompt",s.image_prompt,"image_prompt")} ${csPromptBox("SillyTavern Character Prompt",s.silly_tavern_prompt,"silly_tavern_prompt")} ${csPromptBox("Character Concept Art Prompt",s.concept_art_prompt,"concept_art_prompt")}
`;return`
${csAvatarHtml(s)}
${escHtml(s.name)} ${s.archetype?`${escHtml(s.archetype)}`:""} ${s.tier==="main"?"Main":"Supporting"}
${metaTags.length?`
${metaTags.map(t=>`${escHtml(t.label)}${escHtml(t.text)}`).join("")}
`:""} ${csAlignmentBar(s.moral_alignment_score,s.arc_direction,s.arc_note)}
${identityHtml} ${csField("Physical",s.physical,s,"physical")} ${csField("Clothing & Appearance",s.clothing,s,"clothing")} ${csField("Alignment & Ethos",s.alignment,s,"alignment")} ${attrs} ${csField("Trained Skills",s.skills,s,"skills")} ${csField("Capabilities",s.capabilities,s,"capabilities")} ${invHtml} ${csField("Backstory & Origin",s.backstory,s,"backstory")} ${csField("Relationships",s.relationships,s,"relationships")} ${csField("Motivation",s.motivation,s,"motivation")} ${csField("Fears",s.fears,s,"fears")} ${csField("Mannerisms & Habits",s.mannerisms,s,"mannerisms")} ${s.voice_pattern?`
Voice & Speech
${escHtml(String(s.voice_pattern))}${csSourceBadgeHtml(s,"voice_pattern")?` ${csSourceBadgeHtml(s,"voice_pattern")}`:""}
`:""} ${csField("Dark Secret / Fatal Flaw",s.secret,s,"secret")} ${csField("Conflict Style",s.conflict_style,s,"conflict_style")} ${csField("Win Condition",s.win_condition,s,"win_condition")}
${srcHtml} ${promptHtml}
`}function csToMarkdown(sheets){var _a2;const sec=t=>` ## ${t} `;let md=`# Character Sheets `;for(const tier of["main","supp"]){const list=sheets.filter(s=>s.tier==="main"==(tier==="main"));if(list.length){md+=sec(tier==="main"?"Main characters":"Supporting characters");for(const s of list){md+=` ### ${s.name}${s.archetype?" \u2014 "+s.archetype:""} `,s.aliases&&(md+=`*also known as ${s.aliases}* `),s.full_name&&(md+=`- **Full name:** ${s.full_name} `),s.gender&&(md+=`- **Gender:** ${s.gender} `),s.title&&(md+=`- **Title:** ${s.title} `),s.profession&&(md+=`- **Occupation:** ${s.profession} `),(s.first_name||s.last_name)&&(md+=`- **Name parts:** ${[s.first_name,s.last_name].filter(Boolean).join(" ")} `);const mas=(_a2=s.moral_alignment_score)!=null?_a2:50,masLabel=mas>=70?"Good":mas<=30?"Evil":"Neutral";md+=`- **Moral alignment:** ${mas}/100 (${masLabel}) \u2014 Arc: ${s.arc_direction||"neutral"} `,s.arc_note&&(md+=` *${s.arc_note}* `);const f=(l,v)=>v?`- **${l}:** ${v} `:"";md+=f("Physical",s.physical)+f("Clothing",s.clothing)+f("Alignment & Ethos",s.alignment)+f("Core Attributes",[s.attribute_high&&"\u25B2 "+s.attribute_high,s.attribute_low&&"\u25BC "+s.attribute_low].filter(Boolean).join(" \xB7 "))+f("Trained Skills",s.skills)+f("Capabilities",s.capabilities)+f("Backstory & Origin",s.backstory)+f("Relationships",s.relationships)+f("Motivation",s.motivation)+f("Fears",s.fears)+f("Mannerisms & Habits",s.mannerisms)+f("Voice & Speech",s.voice_pattern)+f("Voice Design Prompt",s.voice_design_prompt)+f("Image Generation Prompt",s.image_prompt)+f("SillyTavern Character Prompt",s.silly_tavern_prompt)+f("Character Concept Art Prompt",s.concept_art_prompt)+f("Signature Items",(s.inventory||[]).join(", "))+f("Dark Secret / Fatal Flaw",s.secret)+f("Conflict Style",s.conflict_style)+f("Win Condition",s.win_condition);const src=(s.sources||[]).filter(x=>x&&x.quote).map(x=>`${x.line_hint?"["+x.line_hint+"] ":""}${x.page!=null?"p."+x.page+" ":""}"${x.quote}"`).join("; ");src&&(md+=`- *Sources:* ${src} `)}}}return md.trim()}function csWireResultInteractions(root,sheets,sourceText,book,inline=!1){var _a2,_b2;const copyBtn=root.querySelector("#cs-copy");copyBtn==null||copyBtn.addEventListener("click",()=>{var _a3;(_a3=navigator.clipboard)==null||_a3.writeText(csToMarkdown(sheets)).then(()=>toast("Copied as Markdown","success"),()=>toast("Copy failed","error"))});const closeBtn=root.querySelector("#cs-close");closeBtn==null||closeBtn.addEventListener("click",()=>{var _a3;inline?typeof navLibraryView=="function"&&navLibraryView("characters"):(_a3=document.getElementById("cs-overlay"))==null||_a3.remove()}),(_a2=root.querySelector("#cs-open-cast"))==null||_a2.addEventListener("click",()=>csGoToLibrary()),(_b2=root.querySelector("#cs-back-cast-audio"))==null||_b2.addEventListener("click",()=>{typeof navTo=="function"&&navTo("s-reader"),typeof showReaderView=="function"&&showReaderView("cast")});const bkCtx=book||"";root.querySelectorAll(".cs-avatar").forEach(av=>{av.addEventListener("click",e=>{e.stopPropagation();const name=av.dataset.name,inp=document.createElement("input");inp.type="file",inp.accept="image/*",inp.onchange=async()=>{const file=inp.files[0];if(!file)return;const reader=new FileReader;reader.onload=async ev=>{var _a3;const dataUrl=ev.target.result,id=`${bkCtx}::${name}`.toLowerCase();typeof clSetImage=="function"&&await clSetImage(id,dataUrl);const s=sheets.find(x=>x.name===name);s&&(s._image=dataUrl);const card=root.querySelector(`.cs-card[data-name="${CSS.escape(name)}"]`);if(card){const oldAv=card.querySelector(".cs-avatar");oldAv&&(oldAv.outerHTML=csAvatarHtml(s),(_a3=card.querySelector(".cs-avatar"))==null||_a3.addEventListener("click",av.onclick))}toast("Profile picture saved","success")},reader.readAsDataURL(file)},inp.click()})}),root.querySelectorAll(".cs-deep-btn").forEach(btn=>{btn.addEventListener("click",e=>{e.stopPropagation();const sheet=sheets.find(s=>s.name===btn.dataset.name);sheet&&csDeepAnalysis(sheet,sourceText)})}),root.querySelectorAll(".cs-img-btn").forEach(btn=>{btn.addEventListener("click",e=>{var _a3;e.stopPropagation();const sheet=sheets.find(s=>s.name===btn.dataset.name);sheet&&((_a3=navigator.clipboard)==null||_a3.writeText(csBuildImagePrompt(sheet)).then(()=>toast("Image prompt copied","success"),()=>toast("Copy failed","error")))})}),root.querySelectorAll(".cs-voice-btn").forEach(btn=>{btn.addEventListener("click",e=>{var _a3;e.stopPropagation();const sheet=sheets.find(s=>s.name===btn.dataset.name);sheet&&((_a3=navigator.clipboard)==null||_a3.writeText(csBuildVoicePrompt(sheet)).then(()=>toast("Voice prompt copied \u2014 paste into Design a Voice","success"),()=>toast("Copy failed","error")))})}),root.querySelectorAll(".cs-prompt-text[data-sheet-key]").forEach(el=>{el.addEventListener("input",()=>{const key=el.dataset.sheetKey,card=el.closest(".cs-card"),sheet=sheets.find(s=>s.name===(card==null?void 0:card.dataset.name));sheet&&key&&(sheet[key]=el.textContent||"")})}),root.querySelectorAll(".cs-prompt-copy").forEach(btn=>{btn.addEventListener("click",async e=>{var _a3,_b3,_c2;e.stopPropagation();const text=((_b3=(_a3=btn.closest(".lcd-prompt-body"))==null?void 0:_a3.querySelector(".cs-prompt-text"))==null?void 0:_b3.textContent.trim())||"";if(!text){toast("Nothing to copy yet \u2014 generate first","error");return}typeof copyText=="function"?await copyText(text):(_c2=navigator.clipboard)==null||_c2.writeText(text),toast("Prompt copied","success")})}),root.querySelectorAll(".cs-gen-prompt").forEach(btn=>{btn.addEventListener("click",async e=>{var _a3;e.stopPropagation();const key=btn.dataset.sheetKey,card=btn.closest(".cs-card"),name=card==null?void 0:card.dataset.name,sheet=sheets.find(s=>s.name===name);if(!sheet||!key)return;const orig=btn.innerHTML;btn.disabled=!0,btn.innerHTML=' Generating\u2026';try{const sample=[sheet.physical,sheet.backstory,sheet.motivation,sheet.relationships].filter(Boolean).join(" "),language=typeof detectLang=="function"&&sample&&detectLang(sample)||"",target=typeof statusLlmTarget=="function"?statusLlmTarget():{url:"",model:""},r=await fetch("/api/character-generate-prompts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:sheet.name,book:book||"",sheet,language,llm_url:target.url,model:target.model,fields:[key]})});if(!r.ok)throw new Error((await r.json().catch(()=>({}))).detail||r.statusText);const d=await r.json();if(!d[key])throw new Error("Empty response \u2014 try again");sheet[key]=d[key];const textEl=(_a3=btn.closest(".lcd-prompt-body"))==null?void 0:_a3.querySelector(".cs-prompt-text");textEl&&(textEl.textContent=d[key]),typeof csSaveToLibrary=="function"&&await csSaveToLibrary(book,sheets),toast("Prompt generated","success")}catch(err){toast("Prompt generation failed: "+(err.message||err),"error")}finally{btn.disabled=!1,btn.innerHTML=orig}})}),root.querySelectorAll(".cs-source-link, .cs-source-mark").forEach(btn=>{btn.addEventListener("click",e=>{e.stopPropagation();const pg=parseInt(btn.dataset.page,10);pg&&typeof window.readerJumpToPage=="function"?window.readerJumpToPage(pg):pg&&typeof toast=="function"&&toast(`Source: page ${pg}`,"info")})})}async function csShow(sheets,title,sourceText,book,hostEl=null){var _a2;const inline=!!hostEl;if((_a2=document.getElementById("cs-overlay"))==null||_a2.remove(),typeof clGetAll=="function")try{const bk=book||title||"",all=await clGetAll(),imgMap=new Map(all.filter(r=>r.image).map(r=>[r.id,r.image]));sheets.forEach(s=>{const id=`${bk}::${s.name}`.toLowerCase();imgMap.has(id)&&(s._image=imgMap.get(id))})}catch{}const main=sheets.filter(s=>s.tier==="main"),supp=sheets.filter(s=>s.tier!=="main"),group=(label,list)=>list.length?`
${label}
`+list.map(csCardHtml).join(""):"",ov=inline?hostEl:document.createElement("div");inline?ov.innerHTML=`
${escHtml(title||"Character sheets")} ${sheets.length} character${sheets.length!==1?"s":""}
Generated sheets are saved into the cast library. Use the buttons above to jump back to the audiobook cast or continue to the roster stage.
${group("Main characters",main)}${group("Supporting characters",supp)}
`:(ov.id="cs-overlay",ov.className="audiobook-overlay",ov.innerHTML=`
${escHtml(title||"Character sheets")} ${sheets.length} character${sheets.length!==1?"s":""}
${group("Main characters",main)}${group("Supporting characters",supp)}
`,document.body.appendChild(ov),ov.addEventListener("click",e=>{e.target===ov&&ov.remove()})),csWireResultInteractions(ov,sheets,sourceText,book,inline)}async function csSaveToLibrary(book,sheets){if(typeof clUpsertMany=="function")try{const n=await clUpsertMany(book,sheets);n&&toast(`${n} character${n!==1?"s":""} saved to library`,"success")}catch{}}function csGoToLibrary(){typeof navTo=="function"&&navTo("s-library"),typeof navLibraryView=="function"?navLibraryView("characters"):typeof libraryRender=="function"&&libraryRender("characters"),typeof refreshWorkflowCrumbs=="function"&&refreshWorkflowCrumbs("castlib")}function csAttachLineCounts(sheets,counts){!counts||!counts.size||sheets.forEach(function(s){const n=counts.get(String(s.name||"").trim().toLowerCase());n!=null&&(s.line_count=n)})}async function csForReader(){const text=csReaderText(),book=readerState.title||"Untitled book",key="reader:"+(readerState.title||"")+":"+(typeof readerScopeIndices=="function"?readerScopeIndices().length:0);typeof navTo=="function"&&navTo("s-reader"),typeof showReaderView=="function"&&showReaderView("chars");const pageHost=csReaderPageHost();if(_cs.cache[key]){await csSaveToLibrary(book,_cs.cache[key]),pageHost&&await csShow(_cs.cache[key],book,text,book,pageHost);return}const knownRoster=csKnownReaderRoster();let seedSheets=[];try{typeof clGetAllByTagOrBook=="function"&&(seedSheets=(await clGetAllByTagOrBook(book)||[]).map(rec=>csSeedSheet(rec)).filter(s=>String(s.name||"").trim()))}catch{seedSheets=[]}const sheets=await csGenerate(text,key,knownRoster,{pageHost,seedSheets});if(!sheets)return;if(!sheets.length){toast("No characters found","error");return}const ab=typeof _audiobook!="undefined"?_audiobook:window._audiobook;if(ab!=null&&ab.roster){const counts=new Map;ab.roster.forEach(function(info,name){counts.set(String(name).trim().toLowerCase(),info.count||0)}),csAttachLineCounts(sheets,counts)}await csSaveToLibrary(book,sheets),pageHost&&await csShow(sheets,book,text,book,pageHost),toast(sheets.length+" character sheets saved \u2014 Library \u2192 Cast","success")}async function csForReaderSelective(selectedNames){var _a2;const wanted=new Set((selectedNames||[]).map(n=>String(n).trim().toLowerCase()));if(!wanted.size){toast("No characters selected","error");return}const text=csReaderText(),book=readerState.title||"Untitled book";typeof navTo=="function"&&navTo("s-reader"),typeof showReaderView=="function"&&showReaderView("chars");const pageHost=csReaderPageHost();let records=[];try{records=typeof clGetAllByTagOrBook=="function"?await clGetAllByTagOrBook(book):[]}catch{records=[]}const picked=[],seen=new Set,matchesWanted=rec=>{var _a3,_b2,_c2,_d2;const tokens=csNameTokens({name:(rec==null?void 0:rec.name)||"",aliases:((_a3=rec==null?void 0:rec.sheet)==null?void 0:_a3.aliases)||"",first_name:((_b2=rec==null?void 0:rec.sheet)==null?void 0:_b2.first_name)||"",last_name:((_c2=rec==null?void 0:rec.sheet)==null?void 0:_c2.last_name)||"",full_name:((_d2=rec==null?void 0:rec.sheet)==null?void 0:_d2.full_name)||""});for(const t of tokens){const lower=String(t||"").toLowerCase();for(const w of wanted)if(lower===w||lower.includes(w)||w.includes(lower))return!0}return!1};if((records||[]).forEach(rec=>{if(!rec||!rec.name||!matchesWanted(rec))return;const key=String(rec.name||"").trim().toLowerCase();seen.has(key)||(seen.add(key),picked.push(rec))}),(selectedNames||[]).forEach(name=>{const key=String(name||"").trim().toLowerCase();if(!key||seen.has(key))return;const fallback={name:String(name||"").trim(),sheet:csBlankSheet(String(name||"").trim())};seen.add(key),picked.push(fallback)}),picked.sort((a,b)=>{var _a3,_b2;return(((_a3=b==null?void 0:b.sheet)==null?void 0:_a3.line_count)||0)-(((_b2=a==null?void 0:a.sheet)==null?void 0:_b2.line_count)||0)||String((a==null?void 0:a.name)||"").localeCompare(String((b==null?void 0:b.name)||""))}),!picked.length){toast("No matching cast characters found","error");return}const finalMap=new Map;for(const rec of picked){const targetName=String(rec.name||"").trim();if(!targetName)continue;const needles=csRecordNeedles(rec),scanText=csEvidenceWindowText(csReaderParagraphBlocks(),needles,2,2)||text,seedSheet=csSeedSheet(rec),sheets=await csGenerate(scanText,null,[targetName],{pageHost,seedSheets:[seedSheet]});if(!sheets)return;const filtered2=sheets.filter(s=>wanted.has(String(s.name||"").trim().toLowerCase()));filtered2.length&&(((_a2=rec==null?void 0:rec.sheet)==null?void 0:_a2.line_count)!=null&&filtered2.forEach(s=>{s.line_count=rec.sheet.line_count}),csMerge(finalMap,filtered2))}const filtered=[...finalMap.values()];if(!filtered.length){toast("None of the selected characters turned up in this pass \u2014 try again or pick different ones","error");return}const ab=typeof _audiobook!="undefined"?_audiobook:window._audiobook;if(ab!=null&&ab.roster){const counts=new Map;ab.roster.forEach(function(info,name){counts.set(String(name).trim().toLowerCase(),info.count||0)}),csAttachLineCounts(filtered,counts)}await csSaveToLibrary(book,filtered),pageHost&&await csShow(filtered,book,text,book,pageHost),toast(filtered.length+" character"+(filtered.length!==1?"s":"")+" defined","success")}async function csForRehearser(){var _a2,_b2,_c2,_d2;const title=((_a2=$("reh-script-title"))==null?void 0:_a2.value.trim())||"Character sheets",book=((_b2=$("reh-script-title"))==null?void 0:_b2.value.trim())||"Untitled script",text=csRehearserText(),key="reh:"+title+":"+(rehState.lines||[]).length;typeof navTo=="function"&&navTo("s-reader"),typeof showReaderView=="function"&&showReaderView("chars");const pageHost=csReaderPageHost();if(_cs.cache[key]){await csSaveToLibrary(book,_cs.cache[key]),pageHost&&await csShow(_cs.cache[key],title,text,book,pageHost);return}let seedSheets=[];try{typeof clGetAllByTagOrBook=="function"&&(seedSheets=(await clGetAllByTagOrBook(book)||[]).map(rec=>csSeedSheet(rec)).filter(s=>String(s.name||"").trim()))}catch{seedSheets=[]}const sheets=await csGenerate(text,key,null,{pageHost,seedSheets});if(sheets){if(!sheets.length){toast("No characters found","error");return}if((_d2=(_c2=window.rehState)==null?void 0:_c2.lines)!=null&&_d2.length){const counts=new Map;rehState.lines.forEach(function(l){if(l.type!=="dialog"||!l.speaker)return;const k=String(l.speaker).trim().toLowerCase();counts.set(k,(counts.get(k)||0)+1)}),csAttachLineCounts(sheets,counts)}await csSaveToLibrary(book,sheets),pageHost&&await csShow(sheets,title,text,book,pageHost),toast(sheets.length+" character sheets saved \u2014 Library \u2192 Cast","success")}}window.csForReader=csForReader,window.csForReaderSelective=csForReaderSelective,window.csForRehearser=csForRehearser,(_nc=$("reader-charsheets-btn"))==null||_nc.addEventListener("click",csForReader),(_oc=$("reh-charsheets-btn"))==null||_oc.addEventListener("click",csForRehearser);const CL_EDIT_FIELDS=[["name","Name"],["aliases","Aliases / also known as"],["first_name","First name"],["last_name","Last name"],["full_name","Full name"],["title","Title / role"],["archetype","Archetype"],["physical","Physical"],["clothing","Clothing & Appearance"],["alignment","Alignment & Ethos"],["arc_note","Arc note"],["skills","Trained Skills"],["capabilities","Capabilities"],["backstory","Backstory & Origin"],["relationships","Relationships"],["motivation","Motivation"],["fears","Fears"],["mannerisms","Mannerisms & Habits"],["voice_pattern","Voice & Speech"],["voice_design_prompt","Voice Design Prompt"],["image_prompt","Image Generation Prompt"],["silly_tavern_prompt","SillyTavern Character Prompt"],["concept_art_prompt","Concept Art Prompt"],["secret","Dark Secret / Fatal Flaw"],["conflict_style","Conflict Style"],["win_condition","Win Condition"]];async function clGetAll(){const r=await fetch("/api/characters");if(!r.ok)throw new Error("clGetAll failed: "+r.status);return(await r.json()).characters||[]}async function clGet(id){const r=await fetch("/api/characters/"+encodeURIComponent(id));if(r.status!==404){if(!r.ok)throw new Error("clGet failed: "+r.status);return r.json()}}async function clPut(rec){rec!=null&&rec.color&&(rec.sheet=rec.sheet||{},rec.sheet.color=clNormalizeColor(rec.color,rec.name));const r=await fetch("/api/characters/"+encodeURIComponent(rec.id),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(rec)});if(!r.ok)throw new Error("clPut failed: "+r.status);return r.json()}async function clDelete(id){const r=await fetch("/api/characters/"+encodeURIComponent(id),{method:"DELETE"});if(!r.ok)throw new Error("clDelete failed: "+r.status)}function clKey(book,name){return`${String(book||"").trim()}::${String(name||"").trim()}`.toLowerCase()}function clHslToHex(h,s,l){s/=100,l/=100;const k=n=>(n+h/30)%12,a=s*Math.min(l,1-l),f=n=>l-a*Math.max(-1,Math.min(k(n)-3,Math.min(9-k(n),1)));return"#"+[f(0),f(8),f(4)].map(x=>Math.round(255*x).toString(16).padStart(2,"0")).join("")}function clNameHue(name){return Math.abs((name||"?").split("").reduce((h,c)=>(h*31+c.charCodeAt(0))%360,0))}function clNormalizeColor(color,name){const c=String(color||"").trim();return/^#[0-9a-f]{6}$/i.test(c)?c:/^#[0-9a-f]{3}$/i.test(c)?"#"+c.slice(1).split("").map(ch=>ch+ch).join(""):clHslToHex(clNameHue(name),58,43)}const CL_IDENTITY_FIELDS=["name","aliases","first_name","last_name","full_name","title"],CL_ALIAS_MAX_TOKENS=12,CL_ALIAS_MAX_CHARS=500,CL_ALIAS_STOPWORDS=new Set(["die","der","das","den","dem","des","ein","eine","einer","er","sie","es","ich","du","wir","ihr","the","a","an","he","she","it","they","who"]);function clSplitIdentityTokens(v,opts={}){const raw=_clStr(v),parts=raw.split(/[,;/|]|\baka\b|\baka\.\b|\balias(?:es)?\b|\bgenannt\b|\bnamens\b|\bcalled\b|\bknown as\b/i).map(x=>x.trim()).filter(Boolean).filter(x=>x.length<=80&&!/^needs?:/i.test(x)&&!/^complete$/i.test(x)).filter(x=>!CL_ALIAS_STOPWORDS.has(x.toLowerCase()));return opts.aliases&&(raw.length>CL_ALIAS_MAX_CHARS||parts.length>CL_ALIAS_MAX_TOKENS)?[]:parts.slice(0,opts.aliases?CL_ALIAS_MAX_TOKENS:void 0)}function clIdentityNames(recOrSheet){const s=(recOrSheet==null?void 0:recOrSheet.sheet)||recOrSheet||{},out=new Set,add=(v,opts={})=>clSplitIdentityTokens(v,opts).forEach(x=>out.add(x.toLowerCase()));return add((recOrSheet==null?void 0:recOrSheet.name)||s.name),CL_IDENTITY_FIELDS.filter(k=>k!=="name").forEach(k=>add(s[k],{aliases:k==="aliases"})),out}function clMergeAliases(existing,incoming){const names=new Map,add=v=>clSplitIdentityTokens(v,{aliases:!0}).forEach(x=>names.set(x.toLowerCase(),x));return add(existing.aliases),add(incoming.aliases),incoming.name&&incoming.name!==existing.name&&add(incoming.name),[...names.values()].filter(n=>n.toLowerCase()!==String(existing.name||"").toLowerCase()).join(", ")}function clSameIdentity(rec,book,sheet){if(String((rec==null?void 0:rec.book)||"").trim().toLowerCase()!==String(book||"").trim().toLowerCase())return!1;const a=clIdentityNames(rec),b=clIdentityNames(sheet);for(const n of b)if(a.has(n))return!0;return!1}function _clStr(v){return v==null?"":typeof v=="string"?v:Array.isArray(v)?v.filter(Boolean).join(", "):JSON.stringify(v)}function _clSanitize(sheet){const out={...sheet};return(typeof CS_SCALAR_FIELDS!="undefined"?CS_SCALAR_FIELDS:CL_EDIT_FIELDS.map(f=>f[0]).filter(k=>k!=="name")).forEach(f=>{out[f]!=null&&(out[f]=_clStr(out[f]))}),out}function clMergeSheet(existing,incoming){const e={...existing},inc=_clSanitize(incoming),scalars=typeof CS_SCALAR_FIELDS!="undefined"?CS_SCALAR_FIELDS:CL_EDIT_FIELDS.map(f=>f[0]).filter(k=>k!=="name"),oldAliases=e.aliases;return scalars.forEach(f=>{(inc[f]||"").length>(e[f]||"").length&&(e[f]=inc[f])}),e.aliases=clMergeAliases({...e,aliases:oldAliases},incoming),incoming.tier==="main"&&(e.tier="main"),incoming.moral_alignment_score!=null&&(e.moral_alignment_score=e.moral_alignment_score!=null?Math.round((e.moral_alignment_score+incoming.moral_alignment_score)/2):incoming.moral_alignment_score),incoming.arc_direction&&incoming.arc_direction!=="neutral"&&(e.arc_direction=incoming.arc_direction),incoming.gender&&!e.gender&&(e.gender=incoming.gender),incoming.line_count!=null&&(e.line_count=incoming.line_count),e.inventory=[...existing.inventory||[]],(incoming.inventory||[]).forEach(it=>{it&&!e.inventory.includes(it)&&e.inventory.length<3&&e.inventory.push(it)}),e.sources=[...existing.sources||[]],(incoming.sources||[]).forEach(src=>{src&&src.quote&&e.sources.length<12&&!e.sources.some(x=>x.quote===src.quote)&&e.sources.push(src)}),e}function clMergeTags(...parts){const set=new Set;return parts.forEach(p=>String(p||"").split(",").map(t=>t.trim()).filter(Boolean).forEach(t=>set.add(t))),[...set].join(", ")}async function clUpsert(book,sheet){var _a2;const name=(sheet.name||"").trim();if(!name)return null;const bk=(book||"").trim()||"Unsorted",aliasPrev=(await clGetAll().catch(()=>[])).find(r=>clSameIdentity(r,bk,sheet)),id=(aliasPrev==null?void 0:aliasPrev.id)||clKey(bk,name),now=new Date,prev=aliasPrev||await clGet(id).catch(()=>null),merged=prev?clMergeSheet(prev.sheet||{},sheet):{..._clSanitize(sheet),name},canonicalName=(prev==null?void 0:prev.name)||name;merged.name=canonicalName;const tags=clMergeTags(prev==null?void 0:prev.tags,sheet.tags,bk),color=clNormalizeColor((prev==null?void 0:prev.color)||((_a2=prev==null?void 0:prev.sheet)==null?void 0:_a2.color)||sheet.color,canonicalName);merged.color=color;const rec={id,book:bk,name:canonicalName,tags,sheet:merged,color,analysis:(prev==null?void 0:prev.analysis)||null,voice:(prev==null?void 0:prev.voice)||sheet.voice||null,image:(prev==null?void 0:prev.image)||sheet.image||null,created:(prev==null?void 0:prev.created)||now,updated:now};return await clPut(rec),rec}async function clSetImage(id,dataUrl){const rec=await clGet(id).catch(()=>null);if(rec)return rec.image=dataUrl||null,rec.updated=new Date,await clPut(rec),rec}window.clSetImage=clSetImage;async function clGetAllByTagOrBook(title){const key=String(title||"").trim().toLowerCase();return key?(await clGetAll().catch(()=>[])).filter(r=>String(r.book||"").trim().toLowerCase()===key?!0:String(r.tags||"").split(",").some(t=>t.trim().toLowerCase()===key)):[]}async function clUpsertMany(book,sheets){let n=0;for(const s of sheets||[])await clUpsert(book,s)&&n++;return n}(async function(){try{if((await clGetAll()).length>0)return;const idbRecs=await _clIdbGetAll().catch(()=>[]);if(!idbRecs.length)return;const r=await fetch("/api/characters/migrate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(idbRecs)});if(r.ok){const d=await r.json();console.log(`[characters-library] migrated ${d.imported} records from IndexedDB \u2192 SQLite`)}}catch(e){console.warn("[characters-library] migration skipped:",e)}})();function _clIdbGetAll(){return new Promise((resolve,reject)=>{const req=indexedDB.open("character-library",1);req.onerror=()=>resolve([]),req.onsuccess=e=>{const db=e.target.result;if(!db.objectStoreNames.contains("characters")){db.close(),resolve([]);return}const all=db.transaction("characters","readonly").objectStore("characters").getAll();all.onsuccess=ev=>{db.close(),resolve(ev.target.result||[])},all.onerror=()=>{db.close(),resolve([])}}})}let _clRecords=[];async function clRender(){if(!document.getElementById("cl-grid"))return;const filterEl=document.getElementById("cl-book-filter"),searchEl=document.getElementById("cl-search");filterEl&&!filterEl.dataset.bound&&(filterEl.dataset.bound="1",filterEl.addEventListener("change",clApplyFilter)),searchEl&&!searchEl.dataset.bound&&(searchEl.dataset.bound="1",searchEl.addEventListener("input",clApplyFilter));try{_clRecords=await clGetAll()}catch{_clRecords=[]}const filter=document.getElementById("cl-book-filter"),prods=clAllProductions();if(filter){const cur=filter.value;filter.innerHTML=``+prods.map(b=>``).join(""),cur&&prods.includes(cur)&&(filter.value=cur)}clApplyFilter()}function clAllProductions(){const set=new Set;return _clRecords.forEach(r=>{r.book&&set.add(r.book),String(r.tags||"").split(",").map(t=>t.trim()).filter(Boolean).forEach(t=>set.add(t))}),[...set].sort((a,b)=>a.localeCompare(b))}function clApplyFilter(){var _a2,_b2;const grid=document.getElementById("cl-grid");if(!grid)return;const book=((_a2=document.getElementById("cl-book-filter"))==null?void 0:_a2.value)||"",q=(((_b2=document.getElementById("cl-search"))==null?void 0:_b2.value)||"").trim().toLowerCase();let recs=_clRecords.slice();if(book){const bk=book.toLowerCase();recs=recs.filter(r=>(r.book||"").toLowerCase()===bk||String(r.tags||"").split(",").some(t=>t.trim().toLowerCase()===bk))}if(q&&(recs=recs.filter(r=>{var _a3,_b3,_c2,_d2,_e2,_f2;return(r.name||"").toLowerCase().includes(q)||(((_a3=r.sheet)==null?void 0:_a3.aliases)||"").toLowerCase().includes(q)||(((_b3=r.sheet)==null?void 0:_b3.first_name)||"").toLowerCase().includes(q)||(((_c2=r.sheet)==null?void 0:_c2.last_name)||"").toLowerCase().includes(q)||(((_d2=r.sheet)==null?void 0:_d2.full_name)||"").toLowerCase().includes(q)||(((_e2=r.sheet)==null?void 0:_e2.title)||"").toLowerCase().includes(q)||(((_f2=r.sheet)==null?void 0:_f2.archetype)||"").toLowerCase().includes(q)||(r.tags||"").toLowerCase().includes(q)||(r.book||"").toLowerCase().includes(q)})),!recs.length){grid.innerHTML=`

${_clRecords.length?"No characters match your filter.":"No characters yet."}

Run Character sheets from Read Aloud or the Script Rehearser to populate your library.

`;return}const byBook=new Map;for(const r of recs)byBook.has(r.book)||byBook.set(r.book,[]),byBook.get(r.book).push(r);const groups=[...byBook.keys()].sort((a,b)=>a.localeCompare(b));grid.innerHTML=groups.map(bk=>{const list=byBook.get(bk).sort((a,b)=>{var _a3,_b3;return(((_a3=a.sheet)==null?void 0:_a3.tier)==="main"?0:1)-(((_b3=b.sheet)==null?void 0:_b3.tier)==="main"?0:1)||(a.name||"").localeCompare(b.name||"")});return`
${escHtml(bk)} ${list.length}
${list.map(clCardHtml).join("")}
`}).join("")}function clCardHtml(rec){const tags=String(rec.tags||"").split(",").map(t=>t.trim()).filter(Boolean),chips=tags.length?`
${tags.map(t=>`${escHtml(t)}`).join("")}
`:"";return`
${csCardHtml(rec.sheet)} ${chips}
`}function clEdit(id){var _a2;const rec=_clRecords.find(r=>r.id===id);if(!rec)return;const s=rec.sheet||{},modal=document.createElement("div");modal.className="audiobook-overlay";const rows=CL_EDIT_FIELDS.map(([k,label])=>{const multiline=!["name","aliases","archetype"].includes(k),val=escHtml(String(s[k]||""));return``}).join("");modal.innerHTML=`
Edit \u2014 ${escHtml(rec.name)}
${rows}
`,document.body.appendChild(modal);const close=()=>modal.remove();modal.querySelector("#cl-edit-cancel").addEventListener("click",close),modal.addEventListener("click",e=>{e.target===modal&&close()}),modal.querySelector("#cl-edit-save").addEventListener("click",async()=>{const patch={};modal.querySelectorAll("[data-field]").forEach(el=>{patch[el.dataset.field]=el.value});const newName=(patch.name||"").trim()||rec.name;let mas=parseInt(patch.moral_alignment_score,10);mas=isNaN(mas)?50:Math.max(0,Math.min(100,mas));const newSheet={...s,...patch,name:newName,moral_alignment_score:mas},tagsEl=modal.querySelector('[data-rec-field="tags"]'),newTags=tagsEl?clMergeTags(tagsEl.value,rec.book):rec.tags||rec.book,colorEl=modal.querySelector('[data-rec-field="color"]'),color=clNormalizeColor((colorEl==null?void 0:colorEl.value)||rec.color||s.color,newName),newId=clKey(rec.book,newName);newSheet.color=color,rec.sheet=newSheet,rec.name=newName,rec.tags=newTags,rec.color=color,rec.updated=new Date;try{newId!==rec.id&&(await clDelete(rec.id),rec.id=newId),await clPut(rec),toast("Character saved","success"),close(),clRender()}catch(e){toast("Save failed: "+(e.message||e),"error")}})}async function clDeleteRec(id){const rec=_clRecords.find(r=>r.id===id);if(rec&&confirm(`Delete "${rec.name}" from ${rec.book}?`))try{await clDelete(id),toast("Deleted","success"),clRender()}catch(e){toast("Delete failed: "+(e.message||e),"error")}}document.addEventListener("click",e=>{var _a2,_b2,_c2,_d2,_e2,_f2,_g2,_h2,_i2,_j2,_k2,_l2,_m2,_n2;const edit=(_b2=(_a2=e.target).closest)==null?void 0:_b2.call(_a2,".cl-edit");if(edit){e.stopPropagation(),clEdit(edit.dataset.id);return}const del=(_d2=(_c2=e.target).closest)==null?void 0:_d2.call(_c2,".cl-delete");if(del){e.stopPropagation(),clDeleteRec(del.dataset.id);return}const deep=(_f2=(_e2=e.target).closest)==null?void 0:_f2.call(_e2,".cl-card-wrap .cs-deep-btn");if(deep){e.stopPropagation();const rec=_clRecords.find(r=>{var _a3;return r.id===((_a3=deep.closest(".cl-card-wrap"))==null?void 0:_a3.dataset.id)});rec&&typeof csDeepAnalysis=="function"&&csDeepAnalysis(rec.sheet,"")}const img=(_h2=(_g2=e.target).closest)==null?void 0:_h2.call(_g2,".cl-card-wrap .cs-img-btn");if(img){e.stopPropagation();const rec=_clRecords.find(r=>{var _a3;return r.id===((_a3=img.closest(".cl-card-wrap"))==null?void 0:_a3.dataset.id)});rec&&typeof csBuildImagePrompt=="function"&&((_i2=navigator.clipboard)==null||_i2.writeText(csBuildImagePrompt(rec.sheet)).then(()=>toast("Image prompt copied","success"),()=>toast("Copy failed","error")));return}const voice=(_k2=(_j2=e.target).closest)==null?void 0:_k2.call(_j2,".cl-card-wrap .cs-voice-btn");if(voice){e.stopPropagation();const rec=_clRecords.find(r=>{var _a3;return r.id===((_a3=voice.closest(".cl-card-wrap"))==null?void 0:_a3.dataset.id)});rec&&typeof csBuildVoicePrompt=="function"&&((_l2=navigator.clipboard)==null||_l2.writeText(csBuildVoicePrompt(rec.sheet)).then(()=>toast("Voice prompt copied","success"),()=>toast("Copy failed","error")));return}const src=(_n2=(_m2=e.target).closest)==null?void 0:_n2.call(_m2,".cl-card-wrap .cs-source-link, .cl-card-wrap .cs-source-mark");if(src){e.stopPropagation();const pg=parseInt(src.dataset.page,10);pg&&typeof window.readerJumpToPage=="function"?window.readerJumpToPage(pg):pg&&toast(`Source: page ${pg}`,"info")}}),window.clRender=clRender,window.clUpsertMany=clUpsertMany,window.clUpsert=clUpsert,window.clGetAll=clGetAll,window.clPut=clPut,window.clGetAllByTagOrBook=clGetAllByTagOrBook;function stReadPngText(buf){const dv=new DataView(buf),sig=[137,80,78,71,13,10,26,10];for(let i=0;i<8;i++)if(dv.getUint8(i)!==sig[i])throw new Error("Not a PNG file");let off=8;const out={};for(;off+8<=dv.byteLength;){const len=dv.getUint32(off);off+=4;let type="";for(let i=0;i<4;i++)type+=String.fromCharCode(dv.getUint8(off+i));if(off+=4,type==="tEXt"){const bytes=new Uint8Array(buf,off,len);let sep=bytes.indexOf(0);sep===-1&&(sep=len);let keyword="";for(let i=0;im*1.3?"female":m>f*1.3?"male":""}function stToSheet(data){const tags=(Array.isArray(data.tags)?data.tags:String(data.tags||"").split(",")).map(t=>String(t||"").trim()).filter(Boolean).join(", "),ext=data.extensions||{};return{name:(data.name||data.char_name||"Unnamed").trim(),archetype:(data.creator_notes||"").split(` `)[0].slice(0,60)||"",backstory:data.description||data.char_persona||"",mannerisms:data.personality||"",arc_note:data.scenario||"",voice_pattern:data.mes_example||data.example_dialogue||data.first_mes||data.char_greeting||"",gender:ext.gender||stGuessGender((data.description||"")+" "+(data.personality||"")),note:data.creator_notes||"",tags,voice:ext.tts_voice||null,tier:"supporting",st_card:data}}function stFromRecord(rec){const sh=rec.sheet||{},prev=sh.st_card||{},descParts=[sh.physical,sh.clothing,sh.backstory,sh.alignment].map(x=>String(x||"").trim()).filter(Boolean),persona=[sh.archetype,sh.mannerisms,sh.motivation,sh.fears].map(x=>String(x||"").trim()).filter(Boolean).join(` `);return{spec:"chara_card_v2",spec_version:"2.0",data:{name:rec.name,description:prev.description||descParts.join(` `),personality:persona||prev.personality||"",scenario:rec.book||prev.scenario||"",first_mes:prev.first_mes||"",mes_example:sh.voice_pattern||prev.mes_example||"",creator_notes:"Exported from TTS Voice Creator"+(rec.book?" \xB7 "+rec.book:""),system_prompt:prev.system_prompt||"",post_history_instructions:prev.post_history_instructions||"",tags:String(rec.tags||rec.book||"").split(",").map(t=>t.trim()).filter(Boolean),creator:prev.creator||"",character_version:prev.character_version||"1.0",extensions:Object.assign({},prev.extensions,{tts_voice:rec.voice||""})}}}function stDownloadJson(card,filename){const blob=new Blob([JSON.stringify(card,null,2)],{type:"application/json"}),a=document.createElement("a");a.href=URL.createObjectURL(blob),a.download=filename,document.body.appendChild(a),a.click(),a.remove(),setTimeout(()=>URL.revokeObjectURL(a.href),1e3)}async function stImportCards(book,fileList){let n=0;for(const file of fileList)try{const data=await stParseFile(file),sheet=stToSheet(data);typeof clUpsert=="function"&&(await clUpsert(book||sheet.name,Object.assign({},sheet,{tags:book||""})),n++)}catch(e){typeof toast=="function"&&toast("\u201C"+(file.name||"card")+"\u201D: "+(e.message||e),"error")}return n}function stImportDialog(book,onDone){const inp=document.createElement("input");inp.type="file",inp.accept=".json,.png",inp.multiple=!0,inp.onchange=async()=>{if(!inp.files.length)return;const n=await stImportCards(book,inp.files);typeof toast=="function"&&toast(n?"Imported "+n+" character"+(n>1?"s":""):"Nothing imported",n?"success":"error"),typeof onDone=="function"&&onDone()},inp.click()}function stExportRecord(rec){const card=stFromRecord(rec),safe=String(rec.name||"character").replace(/[^\w\- ]+/g,"").trim().replace(/\s+/g,"_")||"character";stDownloadJson(card,safe+".card.json")}window.stParseFile=stParseFile,window.stToSheet=stToSheet,window.stFromRecord=stFromRecord,window.stImportCards=stImportCards,window.stImportDialog=stImportDialog,window.stExportRecord=stExportRecord;const LIB_READER_API="/api/reader/docs";function prodKey(title){return String(title||"").trim().toLowerCase()}window._libraryView=function(){try{return localStorage.getItem("ttsvc_library_view")||"books"}catch{return"books"}}(),window.navLibraryView=function(view){typeof navTo=="function"&&navTo("s-library"),window._libraryView=view;try{localStorage.setItem("ttsvc_library_view",view)}catch{}document.querySelectorAll("[data-library-view]").forEach(function(el){el.classList.toggle("is-active",el.dataset.libraryView===view)}),document.querySelectorAll("[data-library-panel]").forEach(function(el){el.classList.toggle("is-active",el.dataset.libraryPanel===view)}),view==="characters"&&typeof refreshWorkflowCrumbs=="function"&&refreshWorkflowCrumbs("castlib"),libraryRender(view)},window.libraryRender=function(view){view=view||window._libraryView||"books",document.querySelectorAll("[data-library-view]").forEach(function(el){el.classList.toggle("is-active",el.dataset.libraryView===view)}),document.querySelectorAll("[data-library-panel]").forEach(function(el){el.classList.toggle("is-active",el.dataset.libraryPanel===view)}),view==="books"?libraryRenderBooks():view==="plays"?libraryRenderPlays():view==="characters"&&typeof window.libraryRenderCharacters=="function"&&window.libraryRenderCharacters()};function _libSkeleton(n){return Array.from({length:n},()=>'
').join("")}async function libraryRenderBooks(){const list=document.getElementById("lib-books-list");if(!list)return;list.innerHTML=_libSkeleton(4);let all=[];try{const r=await fetch(LIB_READER_API);r.ok&&(all=(await r.json()).docs||[])}catch{all=[]}if(!all.length){list.innerHTML='

No books yet.

Open Read Aloud, import a PDF or text, and save it to your library.

';return}all.sort(function(a,b){return new Date(b.updated||0)-new Date(a.updated||0)}),list.innerHTML=all.map(function(rec){const total=rec.sentenceCount||0,synthPct=total?Math.round((rec.synthCount||0)/total*100):0,readPct=total?Math.round((rec.idx||0)/total*100):0,date=rec.updated?new Date(rec.updated).toLocaleDateString():"",cov=libBookCover(rec.title||"Untitled"),coverUrl=LIB_READER_API+"/"+rec.id+"/cover?t="+new Date(rec.updated||Date.now()).getTime(),bg=rec.hasCover?`style="background-image:linear-gradient(to bottom,rgba(0,0,0,.3),rgba(0,0,0,.8)),url('`+coverUrl+`');background-size:cover;background-position:center;color:#fff"`:'style="--bk1:'+cov.c1+";--bk2:"+cov.c2+'"';return'
'+escHtml(rec.title||"Untitled")+'
'+total+" sentences"+(rec.pageCount?" \xB7 "+rec.pageCount+" pg":"")+'
'+readPct+"% read \xB7 "+date+"
"}).join(""),list.querySelectorAll(".lib-book").forEach(function(el){const id=el.dataset.id,title=el.dataset.title;el.addEventListener("click",function(e){e.target.closest(".reh-book-act")||(window._readerStartView="main",typeof navTo=="function"&&navTo("s-reader"),typeof readerOpenLibraryDoc=="function"&&readerOpenLibraryDoc(id))});const reh=el.querySelector(".lib-act-rehearse");reh&&reh.addEventListener("click",function(e){e.stopPropagation(),productionOpenInRehearser(title)});const del=el.querySelector(".lib-act-del-book");del&&del.addEventListener("click",function(e){e.stopPropagation(),libConfirmDelete(el,"Delete book?","Audio files will be removed.",async function(){try{if(!(await fetch(LIB_READER_API+"/"+id,{method:"DELETE"})).ok)throw new Error("delete failed");toast("Book deleted","success"),libraryRenderBooks()}catch(err){toast(err.message||"Delete failed","error")}})})})}async function libraryRenderPlays(){const list=document.getElementById("lib-plays-list");if(!list)return;list.innerHTML=_libSkeleton(3);let all=[];try{all=typeof rehDbGetAll=="function"?await rehDbGetAll():[]}catch{all=[]}if(!all.length){list.innerHTML='

No theater plays yet.

Open Script Rehearsal \u2192 Import / Export to add a script, or cast a book as an audiobook.

';return}all.sort(function(a,b){return new Date(b.updated||0)-new Date(a.updated||0)}),list.innerHTML=all.map(function(rec){const speakers=Object.keys(rec.cast||{}),total=typeof parseScript=="function"?parseScript(rec.script||"").filter(function(l){return l.type==="dialog"}).length:0,pct=total?Math.round((rec.lineIndex||0)/total*100):0,date=rec.updated?new Date(rec.updated).toLocaleDateString():"\u2014",cov=libBookCover(rec.title||"Untitled"),avatars=speakers.slice(0,5).map(function(sp){return''+(sp[0]||"?").toUpperCase()+""}).join("");return'
'+escHtml(rec.title||"Untitled")+'
'+avatars+"
"+total+" lines \xB7 "+speakers.length+' cast
'+pct+"% \xB7 "+date+"
"}).join(""),list.querySelectorAll(".lib-play").forEach(function(el){const id=parseInt(el.dataset.id,10),title=el.dataset.title;el.addEventListener("click",function(e){e.target.closest(".reh-book-act")||openPlayInRehearser(id)});const ra=el.querySelector(".lib-act-readaloud");ra&&ra.addEventListener("click",function(e){e.stopPropagation(),productionOpenInReader(title)});const del=el.querySelector(".lib-act-del-play");del&&del.addEventListener("click",function(e){e.stopPropagation(),libConfirmDelete(el,"Delete rehearsal?","This cannot be undone.",async function(){try{typeof rehDbDelete=="function"&&await rehDbDelete(id),toast("Rehearsal deleted","success"),libraryRenderPlays()}catch(err){toast(err.message||"Delete failed","error")}})})})}async function openPlayInRehearser(id){try{const rec=typeof rehDbGetById=="function"?await rehDbGetById(id):null;rec&&typeof loadRecord=="function"?loadRecord(rec):toast("Rehearsal not found","error")}catch{toast("Could not open rehearsal","error")}}async function productionOpenInRehearser(title){const key=prodKey(title);try{const match=(typeof rehDbGetAll=="function"?await rehDbGetAll():[]).find(function(p){return prodKey(p.title)===key});if(match){openPlayInRehearser(match.id);return}}catch{}let books=[];try{const r=await fetch(LIB_READER_API);r.ok&&(books=(await r.json()).docs||[])}catch{}const book=books.find(function(b){return prodKey(b.title)===key});if(book&&book.kind!=="pdf")try{const sr=await fetch(LIB_READER_API+"/"+book.id+"/source"),text=sr.ok?await sr.text():"";if(text&&typeof audiobookOpenInRehearser=="function"){audiobookOpenInRehearser(text,title,[]);return}}catch{}if(book&&book.kind==="pdf"){typeof readerOpenLibraryDoc=="function"&&readerOpenLibraryDoc(book.id),toast('Open this PDF book, then use "Cast as audiobook" to build a rehearsal',"info");return}toast("No source to rehearse for this title yet","error")}async function productionOpenInReader(title){const key=prodKey(title);let books=[];try{const r=await fetch(LIB_READER_API);r.ok&&(books=(await r.json()).docs||[])}catch{}const book=books.find(function(b){return prodKey(b.title)===key});if(book&&typeof readerOpenLibraryDoc=="function"){readerOpenLibraryDoc(book.id);return}typeof navTo=="function"&&navTo("s-reader"),toast("No audiobook for this title yet \u2014 import its source in Read Aloud","info")}async function castForProduction(title){const out={};if(typeof clGetAllByTagOrBook!="function")return out;let recs=[];try{recs=await clGetAllByTagOrBook(title)}catch{recs=[]}return recs.forEach(function(r){const name=(r.name||"").trim();if(!name)return;const voice=r.voice&&r.voice.id?r.voice.id:typeof r.voice=="string"?r.voice:"";out[name.toLowerCase()]={name,voice:voice||"",gender:r.sheet&&r.sheet.gender||"",soul:r.sheet&&(r.sheet.voice_pattern||r.sheet.motivation)||"",tags:r.tags||""}}),out}window.castForProduction=castForProduction;async function castWriteBack(title,castMap){if(!title||!castMap||typeof clGetAllByTagOrBook!="function"||typeof clPut!="function")return;let recs=[];try{recs=await clGetAllByTagOrBook(title)}catch{return}if(!recs.length)return;const byName={};recs.forEach(function(r){byName[(r.name||"").trim().toLowerCase()]=r});let n=0;for(const sp of Object.keys(castMap)){if(String(sp).includes("NARRATOR"))continue;const voice=(castMap[sp]||{}).voice;if(!voice||voice==="me")continue;const rec=byName[String(sp).trim().toLowerCase()];if(!(!rec||(rec.voice&&rec.voice.id?rec.voice.id:typeof rec.voice=="string"?rec.voice:"")===voice)){rec.voice={id:voice},rec.updated=new Date;try{await clPut(rec),n++}catch{}}}return n}window.castWriteBack=castWriteBack;function libBookCover(title){let h=0;const s=String(title||"Untitled");for(let i=0;i'+heading+'
'+sub+'
',o.addEventListener("click",function(e){e.stopPropagation()}),o.querySelector("[data-lib-cancel]").addEventListener("click",function(e){e.stopPropagation(),o.remove()}),o.querySelector("[data-lib-ok]").addEventListener("click",async function(e){e.stopPropagation(),o.innerHTML='',await onConfirm()}),cardEl.appendChild(o)}window.libraryRenderBooks=libraryRenderBooks,window.libraryRenderPlays=libraryRenderPlays,window.productionOpenInRehearser=productionOpenInRehearser,window.productionOpenInReader=productionOpenInReader,window.prodKey=prodKey;async function libraryRenderCharacters(){const container=document.getElementById("lib-chars-list");if(!container)return;container.innerHTML='
Loading characters\u2026
';let all=[];try{all=typeof clGetAll=="function"?await clGetAll():[]}catch{all=[]}const byId=new Map(all.map(function(rec){return[rec.id,rec]}));if(!all.length){container.innerHTML='

No characters yet.

Open a book in Read Aloud, cast it as an audiobook, then click Cast Characters to generate character sheets \u2014 or import an existing cast from SillyTavern.

',container.querySelector("#lib-chars-import").addEventListener("click",function(){typeof stImportDialog=="function"&&stImportDialog("",function(){libraryRenderCharacters()})});return}const byBook={};all.forEach(function(rec){const bk=rec.book||"Unsorted";byBook[bk]||(byBook[bk]=[]),byBook[bk].push(rec)}),container.innerHTML="";const viewMode=localStorage.getItem("ttsvc_libchars_view")==="table"?"table":"cards",SORT_OPTIONS=[["tier","Rolle (Haupt zuerst)"],["alpha","Alphabet"],["lines","Anzahl Zeilen"],["gender","Geschlecht"],["voice","Stimme zugewiesen"]],sortMode=SORT_OPTIONS.some(function(o){return o[0]===localStorage.getItem("ttsvc_libchars_sort")})?localStorage.getItem("ttsvc_libchars_sort"):"tier",bar=document.createElement("div");bar.className="lib-chars-toolbar",bar.innerHTML='
',bar.querySelector("#lib-chars-import").addEventListener("click",function(){typeof stImportDialog=="function"&&stImportDialog("",function(){libraryRenderCharacters()})}),bar.querySelector("#lib-chars-sort-sel").addEventListener("change",function(){localStorage.setItem("ttsvc_libchars_sort",this.value),libraryRenderCharacters()}),bar.querySelectorAll(".lib-chars-view-toggle button").forEach(function(btn){btn.addEventListener("click",function(){localStorage.setItem("ttsvc_libchars_view",btn.dataset.view),libraryRenderCharacters()})}),container.appendChild(bar);const _charSortCmp={tier:function(a,b){var _a2,_b2,_c2,_d2;const tierOrder={main:0,supporting:1,minor:2},ta=(_b2=tierOrder[String(((_a2=a.sheet)==null?void 0:_a2.tier)||"minor").toLowerCase()])!=null?_b2:2,tb=(_d2=tierOrder[String(((_c2=b.sheet)==null?void 0:_c2.tier)||"minor").toLowerCase()])!=null?_d2:2;return ta-tb||(a.name||"").localeCompare(b.name||"")},alpha:function(a,b){return(a.name||"").localeCompare(b.name||"")},lines:function(a,b){var _a2,_b2;return(((_a2=b.sheet)==null?void 0:_a2.line_count)||0)-(((_b2=a.sheet)==null?void 0:_b2.line_count)||0)||(a.name||"").localeCompare(b.name||"")},gender:function(a,b){var _a2,_b2;const ga=String(((_a2=a.sheet)==null?void 0:_a2.gender)||"zzz"),gb=String(((_b2=b.sheet)==null?void 0:_b2.gender)||"zzz");return ga.localeCompare(gb)||(a.name||"").localeCompare(b.name||"")},voice:function(a,b){return(b.voice?1:0)-(a.voice?1:0)||(a.name||"").localeCompare(b.name||"")}},productions=document.createDocumentFragment();Object.keys(byBook).sort().forEach(function(book){const chars=byBook[book].sort(_charSortCmp[sortMode]||_charSortCmp.tier),cov=libBookCover(book),prod=document.createElement("div");prod.className="lib-chars-production",prod.innerHTML='
'+escHtml(book)+'
'+(viewMode==="table"?_charsTableHtml(chars):'
'+chars.map(function(rec){return _charCardHtml(rec,chars)}).join("")+"
"),prod.querySelector(".lib-chars-casting-btn").addEventListener("click",function(){typeof navTo=="function"&&navTo("s-reader")}),prod.querySelector(".lib-chars-cast-btn").addEventListener("click",function(){typeof productionOpenInReader=="function"&&productionOpenInReader(book),toast("Open the book in Read Aloud then click Cast Characters","info")}),prod.querySelector(".lib-chars-reh-btn").addEventListener("click",function(){typeof productionOpenInRehearser=="function"&&productionOpenInRehearser(book)}),prod.querySelector(".lib-chars-read-btn").addEventListener("click",function(){typeof productionOpenInReader=="function"&&productionOpenInReader(book)}),prod.querySelector(".lib-chars-imp-btn").addEventListener("click",function(){typeof stImportDialog=="function"&&stImportDialog(book,function(){libraryRenderCharacters()})});const bulkBtn=prod.querySelector(".lib-chars-bulk-voice-btn"),bulkCount=prod.querySelector(".lib-chars-bulk-count"),refreshBulkBtn=function(){const n=prod.querySelectorAll(".lib-char-select-cb:checked").length;bulkCount.textContent=n,bulkBtn.disabled=n===0};prod.querySelectorAll(".lib-char-select-cb").forEach(function(cb){cb.addEventListener("change",refreshBulkBtn)}),bulkBtn.addEventListener("click",async function(){const ids=[...prod.querySelectorAll(".lib-char-select-cb:checked")].map(function(cb){return cb.dataset.charId});if(!ids.length)return;bulkBtn.disabled=!0;const origLabel=bulkBtn.innerHTML;let done=0;for(const id of ids){const rec=byId.get(id);if(rec){bulkBtn.innerHTML=' Assigning '+ ++done+" / "+ids.length+"\u2026";try{await _autoAssignVoice(rec)}catch{}}}bulkBtn.innerHTML=origLabel,toast(`Voice assigned to ${done} character${done!==1?"s":""}`,"success"),libraryRenderCharacters()}),prod.querySelectorAll(".lib-char-card").forEach(function(card){var _a2,_b2,_c2,_d2,_e2,_f2;const charId=card.dataset.charId,rec=byId.get(charId);rec&&(card.addEventListener("click",function(e){e.target.closest("button, .lib-char-avatar, .lib-voice-picker-popup")||_charDetailPage(rec,chars)}),(_a2=card.querySelector(".lib-char-avatar"))==null||_a2.addEventListener("click",function(e){e.stopPropagation();const inp=document.createElement("input");inp.type="file",inp.accept="image/*",inp.onchange=async function(){const file=inp.files[0];if(!file)return;const fr=new FileReader;fr.onload=async function(ev){typeof clSetImage=="function"&&await clSetImage(rec.id,ev.target.result),toast("Profile picture saved","success"),libraryRenderCharacters()},fr.readAsDataURL(file)},inp.click()}),(_b2=card.querySelector(".lib-char-pick-voice"))==null||_b2.addEventListener("click",function(e){e.stopPropagation(),_openVoicePicker(card,rec,function(){libraryRenderCharacters()})}),(_c2=card.querySelector(".lib-char-auto-voice"))==null||_c2.addEventListener("click",async function(e){e.stopPropagation(),await _autoAssignVoice(rec),libraryRenderCharacters()}),(_d2=card.querySelector(".lib-char-export"))==null||_d2.addEventListener("click",function(e){e.stopPropagation(),typeof stExportRecord=="function"&&stExportRecord(rec)}),(_e2=card.querySelector(".lib-char-online-voice"))==null||_e2.addEventListener("click",function(e){e.stopPropagation(),_charSearchOnline(rec)}),(_f2=card.querySelector(".lib-char-gen-voice"))==null||_f2.addEventListener("click",function(e){e.stopPropagation(),_charDesignVoice(rec)}))}),productions.appendChild(prod)}),container.appendChild(productions)}function _charHue(name){return Math.abs((name||"?").split("").reduce(function(h,c){return(h*31+c.charCodeAt(0))%360},0))}function _charAlignHtml(sh){const score=sh.moral_alignment_score;if(score==null)return"";const pct=Math.max(0,Math.min(100,score)),arc=sh.arc_direction||"neutral",arrowMap={"good-to-bad":{ch:"\u2198",color:"#ff7043",tip:"Arc: Descends toward evil"},"bad-to-good":{ch:"\u2197",color:"#66bb6a",tip:"Arc: Redeems toward good"},complex:{ch:"\u2195",color:"#ab47bc",tip:"Arc: Complex / unpredictable"},"stable-good":{ch:"\u2192",color:"#66bb6a",tip:"Arc: Stable good"},"stable-bad":{ch:"\u2192",color:"#888",tip:"Arc: Stable evil"},neutral:{ch:"\u2192",color:"#aaa",tip:"Arc: Neutral"}},a=arrowMap[arc]||arrowMap.neutral;return'
\u25CF
\u25CF'+a.ch+"
"}function _libStr(v){return v==null?"":typeof v=="string"?v:Array.isArray(v)?v.filter(Boolean).join(", "):JSON.stringify(v)}function _charRelsHtml(rec,allChars){var _a2;if(!allChars||allChars.length<2)return"";const relText=_libStr((_a2=rec.sheet)==null?void 0:_a2.relationships).toLowerCase();if(!relText)return"";const hits=allChars.filter(function(c){return c.id!==rec.id&&(c.name||"").length>1}).map(function(c){const re=new RegExp(c.name.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"gi");return{c,n:(relText.match(re)||[]).length}}).filter(function(x){return x.n>0}).sort(function(a,b){return b.n-a.n}).slice(0,5);return hits.length?'
'+hits.map(function(x){return''+escHtml((x.c.name||"?")[0].toUpperCase())+""}).join("")+"
":""}function _charCardHtml(rec,allChars){const sh=rec.sheet||{},hue=_charHue(rec.name),hue2=(hue+40)%360,voiceId=rec.voice?typeof rec.voice=="object"?rec.voice.id||"":String(rec.voice):"",voiceLabel=voiceId?escHtml(voiceId):'Keine Stimme',tier=String(sh.tier||"").toLowerCase(),tierBadge=tier==="main"?'Haupt':tier==="supporting"?'Neben':"",gender=String(sh.gender||"").toLowerCase(),genderIcon=gender.startsWith("f")?"mdi-gender-female":gender.startsWith("m")?"mdi-gender-male":"mdi-gender-non-binary",snippet=_libStr(sh.mannerisms||sh.voice_pattern||sh.motivation||sh.backstory||"").slice(0,140),tagList=String(rec.tags||"").split(",").map(function(t){return t.trim()}).filter(Boolean),tagsHtml=tagList.length?'
'+tagList.map(function(t){return''+escHtml(t)+""}).join("")+"
":"",avatarInner=rec.image?''+escHtml(rec.name)+'':escHtml((rec.name||"?")[0].toUpperCase());return'
'+avatarInner+'
'+tierBadge+escHtml(rec.name)+'
'+(_libStr(sh.title)?'
'+escHtml(_libStr(sh.title))+"
":"")+(_libStr(sh.aliases)?'
aka '+escHtml(_libStr(sh.aliases))+"
":"")+(sh.archetype?'
'+escHtml(_libStr(sh.archetype))+"
":"")+(snippet?'
'+escHtml(snippet)+"
":"")+tagsHtml+_charAlignHtml(sh)+_charRelsHtml(rec,allChars)+'
'+voiceLabel+'
'}function _charsTableHtml(chars){return'
'+chars.map(function(rec){const sh=rec.sheet||{},hue=_charHue(rec.name),voiceId=rec.voice?typeof rec.voice=="object"?rec.voice.id||"":String(rec.voice):"",voiceLang=rec.voice&&typeof rec.voice=="object"&&rec.voice.language||"",tier=String(sh.tier||"").toLowerCase(),tierBadge=tier==="main"?'Haupt':tier==="supporting"?'Neben':"",gender=String(sh.gender||"").toLowerCase(),genderIcon=gender.startsWith("f")?"mdi-gender-female":gender.startsWith("m")?"mdi-gender-male":gender?"mdi-gender-non-binary":"",score=sh.moral_alignment_score,pct=score!=null?Math.max(0,Math.min(100,score)):null,tagList=String(rec.tags||"").split(",").map(function(t){return t.trim()}).filter(Boolean),avatarInner=rec.image?''+escHtml(rec.name)+'':escHtml((rec.name||"?")[0].toUpperCase()),hasPrompt=function(key){return!!(sh[key]&&String(sh[key]).trim())},promptCell=function(key,title){return''};return''}).join("")+"
Name\u26A5ZeilenSpracheGut/B\xF6seStimmeTagsSTTTSBild
'+avatarInner+'
'+tierBadge+escHtml(rec.name)+""+(genderIcon?'':'\u2014')+""+(sh.line_count!=null?sh.line_count:'\u2014')+""+(voiceLang?escHtml(voiceLang):'\u2014')+""+(pct!=null?'
':'\u2014')+'
'+(voiceId?escHtml(voiceId):'Keine Stimme')+'
'+tagList.map(function(t){return''+escHtml(t)+""}).join("")+"
"+promptCell("silly_tavern_prompt","SillyTavern")+""+promptCell("voice_design_prompt","TTS Voice")+""+promptCell("image_prompt","Bild")+'
"}function _lcdSourcesHtml(sources){const list=Array.isArray(sources)?sources.filter(function(s){return s&&(s.quote||s.page!=null)}):[];return list.length?'
'+list.map(function(s){const page=s.page!=null?"Seite "+s.page:"",hint=_libStr(s.line_hint||s.hint||"");return'
'+(page||hint?'
'+escHtml([page,hint].filter(Boolean).join(" \xB7 "))+"
":"")+(s.quote?'
\u201E'+escHtml(_libStr(s.quote))+'"
':"")+"
"}).join("")+"
":""}function _lcdField(label,value,multiline){const v=_libStr(value);return v?'
'+label+'
'+escHtml(v)+"
":""}function _lcdSection(icon,label,fields){const body=fields.join("");return body?'
"+body+"
":""}function _lcdSectionFull(icon,label,fields){const body=fields.join("");return body?'
"+body+"
":""}function _lcdPromptBox(label,value,sheetKey){const has=!!(value&&String(value).trim());return'
'+escHtml(label)+(has?"":' \u2014 not generated yet')+'
'+escHtml(value||"")+'
"}function _lcdFieldEdit(label,value,sheetKey,sourceIdxs){const v=_libStr(value),links=(sourceIdxs||[]).map(function(idx){return''+(sourceIdxs.indexOf(idx)+1)+""}).join("");return'
'+(label||links?'
'+escHtml(label)+(links?' '+links+"":"")+"
":"")+'
'+escHtml(v)+"
"}function _jumpToReaderPage(pageNum){typeof navTo=="function"&&navTo("s-reader"),setTimeout(function(){var _a2,_b2;const pages=(_a2=window.readerState)==null?void 0:_a2.pages;if(pages&&pages.length>=pageNum){const pg=pages[pageNum-1];if(pg!=null&&pg.pageDiv){pg.pageDiv.scrollIntoView({behavior:"smooth",block:"start"});return}}const sentences=(_b2=window.readerState)==null?void 0:_b2.sentences;if(sentences&&sentences.length){const target0=pageNum-1,idx=sentences.findIndex(function(s){return(s.words||[]).some(function(w){var _a3,_b3;return((_b3=(_a3=w.page)!=null?_a3:w.para)!=null?_b3:0)>=target0})});if(idx>=0&&typeof readerJumpTo=="function"){readerJumpTo(idx);return}}toast('\xD6ffne das Buch in \u201EVorlesen" und klicke nochmal auf die Quelle',"info")},300)}async function _charDetailPage(rec,allChars){var _a2,_b2,_c2,_d2;const container=document.getElementById("lib-chars-list");if(!container)return;window._libDetailRec=rec;const sh=rec.sheet||{},hue=_charHue(rec.name),hue2=(hue+40)%360,tier=String(sh.tier||"").toLowerCase(),tierLabel=tier==="main"?"Hauptcharakter":tier==="supporting"?"Nebencharakter":tier==="minor"?"Nebenfigur":"",gender=_libStr(sh.gender),genderIcon=gender.toLowerCase().startsWith("f")?"mdi-gender-female":gender.toLowerCase().startsWith("m")?"mdi-gender-male":"mdi-gender-non-binary",voiceId=rec.voice?typeof rec.voice=="object"?rec.voice.id||"":String(rec.voice):"",score=sh.moral_alignment_score,pct=score!=null?Math.max(0,Math.min(100,score)):null,arcMap={"good-to-bad":{ch:"\u2198",label:"Entwicklung zum B\xF6sen",color:"#ff7043"},"bad-to-good":{ch:"\u2197",label:"Wandel zum Guten",color:"#66bb6a"},complex:{ch:"\u2195",label:"Komplex / unvorhersehbar",color:"#ab47bc"},"stable-good":{ch:"\u2192",label:"Stabil gut",color:"#66bb6a"},"stable-bad":{ch:"\u2192",label:"Stabil b\xF6se",color:"#888"},neutral:{ch:"\u2192",label:"Neutral / stabil",color:"#aaa"}},arcInfo=arcMap[sh.arc_direction||"neutral"]||arcMap.neutral,avatarHtml=rec.image?'
'+escHtml(rec.name)+'
':'
'+escHtml((rec.name||"?")[0].toUpperCase())+"
",alignHtml=pct!=null?'
B\xF6seGut'+pct+'/100
'+arcInfo.ch+" "+arcInfo.label+(pct>=70?" \xB7 Rechtschaffen ("+pct+"/100)":pct<=30?" \xB7 B\xF6se ("+pct+"/100)":" \xB7 Moralisch ambivalent ("+pct+"/100)")+"
"+(_libStr(sh.alignment)?'
'+escHtml(_libStr(sh.alignment))+"
":"")+"
":"",promptsHtml='
'+_lcdPromptBox("Voice Design Prompt",sh.voice_design_prompt,"voice_design_prompt")+_lcdPromptBox("Character Image Prompt",sh.image_prompt,"image_prompt")+_lcdPromptBox("SillyTavern Character Prompt",sh.silly_tavern_prompt,"silly_tavern_prompt")+_lcdPromptBox("Concept Art Prompt",sh.concept_art_prompt,"concept_art_prompt")+"
",relText=_libStr(sh.relationships).toLowerCase(),relHits=(allChars||[]).filter(function(c){return c.id!==rec.id&&(c.name||"").length>1}).map(function(c){const re=new RegExp(c.name.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"gi");return{c,n:(relText.match(re)||[]).length}}).filter(function(x){return x.n>0}).sort(function(a,b){return b.n-a.n}).slice(0,8),relDotsHtml=relHits.length?'
'+relHits.map(function(x){return''+escHtml((x.c.name||"?")[0].toUpperCase())+""}).join("")+"
":"",sourcesList=Array.isArray(sh.sources)?sh.sources.filter(function(s){return s&&(s.quote||s.page!=null)}):[],sourcesByField={};sourcesList.forEach(function(s,idx){const key=_libStr(s.line_hint||s.hint||"").trim().toLowerCase();key&&(sourcesByField[key]=sourcesByField[key]||[]).push(idx)});const sourcesHtml=sourcesList.length?'
'+sourcesList.map(function(s,idx){const page=s.page!=null?"Seite "+s.page:"",hint=_libStr(s.line_hint||s.hint||"");return'
'+(page||hint?'
'+escHtml([page,hint].filter(Boolean).join(" \xB7 "))+"
":"")+(s.quote?'
\u201E'+escHtml(_libStr(s.quote))+'"
':"")+"
"}).join("")+"
":"",sidebarHtml=(allChars||[]).slice().sort(function(a,b){var _a3,_b3,_c3,_d3;return(((_b3=(_a3=b.sheet)==null?void 0:_a3.sources)==null?void 0:_b3.length)||0)-(((_d3=(_c3=a.sheet)==null?void 0:_c3.sources)==null?void 0:_d3.length)||0)}).map(function(c){var _a3;const h=_charHue(c.name),count=(((_a3=c.sheet)==null?void 0:_a3.sources)||[]).length;return'
'+escHtml((c.name||"?")[0].toUpperCase())+''+escHtml(c.name)+""+(count?''+count+"":"")+"
"}).join("");container.innerHTML="";const pg=document.createElement("div");pg.className="lib-char-page",pg.innerHTML='
'+avatarHtml+'
'+escHtml(rec.name)+"
"+(_libStr(sh.full_name)&&_libStr(sh.full_name).toLowerCase()!==String(rec.name||"").toLowerCase()?'
'+escHtml(_libStr(sh.full_name))+"
":"")+(_libStr(sh.title)?'
'+escHtml(_libStr(sh.title))+"
":"")+'
'+escHtml(_libStr(sh.aliases))+'
'+escHtml(_libStr(sh.archetype))+'
'+(tierLabel?''+tierLabel+"":"")+(gender?' '+escHtml(gender)+"":"")+'
'+(voiceId?escHtml(voiceId):'Noch keine Stimme zugewiesen')+'
'+alignHtml+'
'+_lcdSection("mdi-card-account-details-outline","Identit\xE4t",[_lcdFieldEdit("Voller Name",sh.full_name,"full_name",sourcesByField.full_name),_lcdFieldEdit("Vorname",sh.first_name,"first_name",sourcesByField.first_name),_lcdFieldEdit("Nachname",sh.last_name,"last_name",sourcesByField.last_name),_lcdFieldEdit("Geschlecht",sh.gender,"gender",sourcesByField.gender),_lcdFieldEdit("Titel",sh.title,"title",sourcesByField.title),_lcdFieldEdit("Beruf / Rolle",sh.profession,"profession",sourcesByField.profession),_lcdFieldEdit("Auch bekannt als",sh.aliases,"aliases",sourcesByField.aliases)])+_lcdSection("mdi-account-outline","Erscheinung",[_lcdFieldEdit("K\xF6rperlich",sh.physical,"physical",sourcesByField.physical),_lcdFieldEdit("Kleidung & Aussehen",sh.clothing,"clothing",sourcesByField.clothing)])+_lcdSection("mdi-drama-masks","Pers\xF6nlichkeit",[_lcdFieldEdit("Eigenheiten & Verhalten",sh.mannerisms,"mannerisms",sourcesByField.mannerisms),_lcdFieldEdit("Stimme & Sprache",sh.voice_pattern,"voice_pattern",sourcesByField.voice_pattern)])+_lcdSection("mdi-book-open-outline","Geschichte",[_lcdFieldEdit("Hintergrund & Herkunft",sh.backstory,"backstory",sourcesByField.backstory),_lcdFieldEdit("Motivation",sh.motivation,"motivation",sourcesByField.motivation),_lcdFieldEdit("\xC4ngste",sh.fears,"fears",sourcesByField.fears)])+_lcdSection("mdi-sword","F\xE4higkeiten",[_lcdFieldEdit("Fertigkeiten",sh.skills,"skills",sourcesByField.skills),_lcdFieldEdit("Besondere F\xE4higkeiten",sh.capabilities,"capabilities",sourcesByField.capabilities),_lcdFieldEdit("St\xE4rkstes Attribut",sh.attribute_high,"attribute_high"),_lcdFieldEdit("Schw\xE4chstes Attribut",sh.attribute_low,"attribute_low")])+_lcdSectionFull("mdi-account-group-outline","Beziehungen",[_lcdFieldEdit("",sh.relationships,"relationships",sourcesByField.relationships),relDotsHtml])+_lcdSection("mdi-shield-sword-outline","Konflikt & Strategie",[_lcdFieldEdit("Konfliktstil",sh.conflict_style,"conflict_style",sourcesByField.conflict_style),_lcdFieldEdit("Siegbedingung",sh.win_condition,"win_condition",sourcesByField.win_condition)])+_lcdSection("mdi-eye-outline","Geheimnisse & Bogen",[_lcdFieldEdit("Dunkles Geheimnis / fataler Fehler",sh.secret,"secret"),_lcdFieldEdit("Charakterentwicklung",sh.arc_note,"arc_note")])+promptsHtml+"
"+sourcesHtml+(rec.analysis?'
'+escHtml(String(rec.analysis))+"
":"")+'
Charaktere \xB7 '+escHtml(rec.book||"")+"
"+sidebarHtml+"
",container.appendChild(pg),pg.querySelector(".lib-cpg-back").addEventListener("click",function(){libraryRenderCharacters()}),pg.querySelector(".lcd-avatar-upload").addEventListener("click",function(){const inp=document.createElement("input");inp.type="file",inp.accept="image/*",inp.onchange=async function(){const file=inp.files[0];if(!file)return;const fr=new FileReader;fr.onload=async function(ev){typeof clSetImage=="function"&&await clSetImage(rec.id,ev.target.result),toast("Profilbild gespeichert","success"),rec.image=ev.target.result;const av=pg.querySelector(".lcd-avatar-upload");av&&(av.innerHTML=''+escHtml(rec.name)+'')},fr.readAsDataURL(file)},inp.click()}),pg.querySelectorAll(".lib-cpg-sidebar-item").forEach(function(item){item.addEventListener("click",async function(){const target=(allChars||[]).find(function(c){return c.id===item.dataset.charId});target&&_charDetailPage(target,allChars)})}),pg.querySelectorAll(".lcd-source-clickable").forEach(function(item){item.addEventListener("click",function(){const n=parseInt(item.dataset.page,10);isNaN(n)||_jumpToReaderPage(n)})}),(_a2=pg.querySelector(".lcd-pick-voice"))==null||_a2.addEventListener("click",async function(){_openVoicePicker(pg.querySelector(".lcd-voice-top"),rec,async function(){const all=await clGetAll().catch(()=>allChars),up=all.find(function(r){return r.id===rec.id})||rec;_charDetailPage(up,all.filter(function(r){return r.book===rec.book}))})}),(_b2=pg.querySelector(".lcd-auto-voice"))==null||_b2.addEventListener("click",async function(){await _autoAssignVoice(rec);const all=await clGetAll().catch(()=>allChars),up=all.find(function(r){return r.id===rec.id})||rec;_charDetailPage(up,all.filter(function(r){return r.book===rec.book}))}),(_c2=pg.querySelector(".lcd-online-voice"))==null||_c2.addEventListener("click",function(){_charSearchOnline(rec)}),(_d2=pg.querySelector(".lcd-gen-voice"))==null||_d2.addEventListener("click",function(){_charDesignVoice(rec)}),pg.querySelectorAll(".lcd-prompt-copy").forEach(function(btn){btn.addEventListener("click",async function(){var _a3;const box=btn.closest(".lcd-prompt-body"),text=((_a3=box==null?void 0:box.querySelector(".lcd-prompt-text"))==null?void 0:_a3.textContent.trim())||"";if(!text){toast("Nothing to copy yet \u2014 click Generate first","error");return}typeof copyText=="function"&&await copyText(text),toast("Prompt copied","success")})}),pg.querySelectorAll(".lcd-gen-prompt").forEach(function(btn){btn.addEventListener("click",async function(e){e.preventDefault();const key=btn.dataset.sheetKey,orig=btn.innerHTML;btn.disabled=!0,btn.innerHTML=' Generating\u2026';try{const sh2=rec.sheet||{},sample=[sh2.physical,sh2.backstory,sh2.motivation].filter(Boolean).join(" "),language=typeof detectLang=="function"&&sample&&detectLang(sample)||"",target=typeof statusLlmTarget=="function"?statusLlmTarget():{url:"",model:""},r=await fetch("/api/character-generate-prompts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:rec.name,book:rec.book||"",sheet:sh2,language,llm_url:target.url,model:target.model,fields:[key]})});if(!r.ok)throw new Error((await r.json().catch(function(){return{}})).detail||r.statusText);const d=await r.json();if(!d[key])throw new Error("Empty response \u2014 try again");rec.sheet||(rec.sheet={}),rec.sheet[key]=d[key],rec.updated=new Date,typeof clPut=="function"&&await clPut(rec),toast("Prompt generated","success"),_charDetailPage(rec,allChars)}catch(err){toast("Prompt generation failed: "+(err.message||err),"error"),btn.disabled=!1,btn.innerHTML=orig}})});const slider=pg.querySelector(".lcd-align-slider"),sliderVal=pg.querySelector(".lcd-align-slider-val"),arcEl=pg.querySelector(".lcd-align-arc");slider&&slider.addEventListener("input",async function(){const val=parseInt(slider.value,10);sliderVal&&(sliderVal.textContent=val+"/100"),arcEl&&(arcEl.textContent=arcInfo.ch+" "+arcInfo.label+(val>=70?" \xB7 Rechtschaffen ("+val+"/100)":val<=30?" \xB7 B\xF6se ("+val+"/100)":" \xB7 Moralisch ambivalent ("+val+"/100)")),arcEl&&(arcEl.style.color=arcInfo.color),rec.sheet.moral_alignment_score=val,rec.updated=new Date,typeof clPut=="function"&&await clPut(rec)});let _saveTimer=null;function _schedSave(key,value,isRecKey){clearTimeout(_saveTimer),_saveTimer=setTimeout(async function(){isRecKey?rec[key]=value:(rec.sheet||(rec.sheet={}),rec.sheet[key]=value),rec.updated=new Date,typeof clPut=="function"&&await clPut(rec)},900)}pg.querySelectorAll("[contenteditable][data-sheet-key]").forEach(function(el){el.addEventListener("input",function(){_schedSave(el.dataset.sheetKey,el.textContent.trim(),!1)})}),pg.querySelectorAll("[contenteditable][data-rec-key]").forEach(function(el){el.addEventListener("input",function(){_schedSave(el.dataset.recKey,el.textContent.trim(),!0)})})}window._charDetailPage=_charDetailPage;function _charDetailModal(rec,allChars){const sh=rec.sheet||{},cov=libBookCover(rec.name),hue=_charHue(rec.name),tier=String(sh.tier||"").toLowerCase(),tierLabel=tier==="main"?"Hauptcharakter":tier==="supporting"?"Nebencharakter":tier==="minor"?"Nebenfigur":"",gender=_libStr(sh.gender),genderIcon=gender.toLowerCase().startsWith("f")?"mdi-gender-female":gender.toLowerCase().startsWith("m")?"mdi-gender-male":"mdi-gender-non-binary",score=sh.moral_alignment_score,pct=score!=null?Math.max(0,Math.min(100,score)):null,arc=sh.arc_direction||"neutral",arcMap={"good-to-bad":{ch:"\u2198",label:"Entwicklung zum B\xF6sen",color:"#ff7043"},"bad-to-good":{ch:"\u2197",label:"Wandel zum Guten",color:"#66bb6a"},complex:{ch:"\u2195",label:"Komplex / unvorhersehbar",color:"#ab47bc"},"stable-good":{ch:"\u2192",label:"Stabil gut",color:"#66bb6a"},"stable-bad":{ch:"\u2192",label:"Stabil b\xF6se",color:"#888"},neutral:{ch:"\u2192",label:"Neutral / stabil",color:"#aaa"}},arcInfo=arcMap[arc]||arcMap.neutral,voiceId=rec.voice?typeof rec.voice=="object"?rec.voice.id||"":String(rec.voice):"",avatarHtml=rec.image?'
'+escHtml(rec.name)+'
':'
'+escHtml((rec.name||"?")[0].toUpperCase())+"
",alignHtml=pct!=null?'
B\xF6se
Gut
'+arcInfo.ch+" "+arcInfo.label+(pct>=70?" \xB7 Rechtschaffen ("+pct+"/100)":pct<=30?" \xB7 B\xF6se ("+pct+"/100)":" \xB7 Moralisch ambivalent ("+pct+"/100)")+"
"+(_libStr(sh.arc_note)?'
'+escHtml(_libStr(sh.arc_note))+"
":"")+(_libStr(sh.alignment)?'
'+escHtml(_libStr(sh.alignment))+"
":"")+"
":"",relText=_libStr(sh.relationships).toLowerCase(),relHits=(allChars||[]).filter(function(c){return c.id!==rec.id&&(c.name||"").length>1}).map(function(c){const re=new RegExp(c.name.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"gi");return{c,n:(relText.match(re)||[]).length}}).filter(function(x){return x.n>0}).sort(function(a,b){return b.n-a.n}).slice(0,8),relDotsHtml=relHits.length?'
'+relHits.map(function(x){return''+escHtml((x.c.name||"?")[0].toUpperCase())+""}).join("")+"
":"",ov=document.createElement("div");ov.className="lib-char-detail-ov",ov.innerHTML='
'+avatarHtml+'
'+escHtml(rec.name)+"
"+(_libStr(sh.full_name)&&_libStr(sh.full_name).toLowerCase()!==String(rec.name||"").toLowerCase()?'
'+escHtml(_libStr(sh.full_name))+"
":"")+(_libStr(sh.title)?'
'+escHtml(_libStr(sh.title))+"
":"")+(_libStr(sh.aliases)?'
auch bekannt als '+escHtml(_libStr(sh.aliases))+"
":"")+(_libStr(sh.archetype)?'
'+escHtml(_libStr(sh.archetype))+"
":"")+'
'+(tierLabel?''+tierLabel+"":"")+(gender?' '+escHtml(gender)+"":"")+'
'+(voiceId?escHtml(voiceId):'Noch keine Stimme zugewiesen')+'
'+alignHtml+'
'+_lcdSection("mdi-card-account-details-outline","Identit\xE4t",[_lcdField("Voller Name",sh.full_name,!0),_lcdField("Vorname",sh.first_name,!0),_lcdField("Nachname",sh.last_name,!0),_lcdField("Geschlecht",sh.gender,!0),_lcdField("Titel",sh.title,!0),_lcdField("Beruf / Rolle",sh.profession,!0),_lcdField("Auch bekannt als",sh.aliases,!0)])+_lcdSection("mdi-account-outline","Erscheinung",[_lcdField("K\xF6rperlich",sh.physical,!0),_lcdField("Kleidung & Aussehen",sh.clothing,!0)])+_lcdSection("mdi-drama-masks","Pers\xF6nlichkeit",[_lcdField("Eigenheiten & Verhalten",sh.mannerisms,!0),_lcdField("Stimme & Sprache",sh.voice_pattern,!0)])+_lcdSection("mdi-book-open-outline","Geschichte",[_lcdField("Hintergrund & Herkunft",sh.backstory,!0),_lcdField("Motivation",sh.motivation,!0),_lcdField("\xC4ngste",sh.fears,!0)])+_lcdSection("mdi-sword","F\xE4higkeiten",[_lcdField("Fertigkeiten",sh.skills,!0),_lcdField("Besondere F\xE4higkeiten",sh.capabilities,!0),_lcdField("St\xE4rkstes Attribut",sh.attribute_high,!1),_lcdField("Schw\xE4chstes Attribut",sh.attribute_low,!1)])+_lcdSectionFull("mdi-account-group-outline","Beziehungen",[_lcdField("",sh.relationships,!0),relDotsHtml])+_lcdSection("mdi-shield-sword-outline","Konflikt & Strategie",[_lcdField("Konfliktstil",sh.conflict_style,!0),_lcdField("Siegbedingung",sh.win_condition,!0)])+_lcdSection("mdi-eye-outline","Geheimnisse & Bogen",[_lcdField("Dunkles Geheimnis / fataler Fehler",sh.secret,!0),_lcdField("Charakterentwicklung",sh.arc_note,!0)])+"
"+_lcdSourcesHtml(sh.sources)+(rec.analysis?'
'+escHtml(String(rec.analysis))+"
":"")+"
",document.body.appendChild(ov);const close=function(){ov.remove()};ov.querySelector(".lcd-close-btn").addEventListener("click",close),ov.addEventListener("click",function(e){e.target===ov&&close()}),ov.querySelector(".lcd-edit-btn").addEventListener("click",function(){close(),typeof clEdit=="function"&&clEdit(rec.id)});const box=ov.querySelector(".lib-char-detail-box");ov.querySelector(".lcd-pick-voice").addEventListener("click",function(e){e.stopPropagation(),_openVoicePicker(box,rec,function(){close(),libraryRenderCharacters()})}),ov.querySelector(".lcd-auto-voice").addEventListener("click",async function(e){e.stopPropagation(),await _autoAssignVoice(rec),close(),libraryRenderCharacters()}),ov.querySelector(".lcd-online-voice").addEventListener("click",function(e){e.stopPropagation(),_charSearchOnline(rec)}),ov.querySelector(".lcd-gen-voice").addEventListener("click",function(e){e.stopPropagation(),_charDesignVoice(rec)}),ov.querySelector(".lcd-avatar").addEventListener("click",function(){const inp=document.createElement("input");inp.type="file",inp.accept="image/*",inp.onchange=async function(){const file=inp.files[0];if(!file)return;const fr=new FileReader;fr.onload=async function(ev){typeof clSetImage=="function"&&await clSetImage(rec.id,ev.target.result),toast("Profile picture saved","success"),close(),libraryRenderCharacters()},fr.readAsDataURL(file)},inp.click()})}window._charDetailModal=_charDetailModal;function _openVoicePicker(cardEl,rec,onDone){var _a2;document.querySelectorAll(".lib-voice-picker-popup").forEach(function(p){p.remove()});const voices=window._voices||[],gender=String(((_a2=rec.sheet)==null?void 0:_a2.gender)||"").toLowerCase(),genderMatch=gender.startsWith("f")?"f":gender.startsWith("m")?"m":"",popup=document.createElement("div");popup.className="lib-voice-picker-popup",popup.innerHTML='
';function renderList(filter){let list=voices.filter(function(v){return v.enabled!==!1});if(filter){const f=filter.toLowerCase();list=list.filter(function(v){return(v.id||"").toLowerCase().includes(f)||(v.name||"").toLowerCase().includes(f)})}else genderMatch&&(list=list.filter(function(v){const vg=String(v.gender||"").toLowerCase();return vg.startsWith(genderMatch)||!vg}).concat(list.filter(function(v){const vg=String(v.gender||"").toLowerCase();return vg&&!vg.startsWith(genderMatch)})));const ul=popup.querySelector(".lib-vp-list");ul.innerHTML=list.slice(0,60).map(function(v){return'
'+escHtml(v.id||v.name||"")+(v.gender?' \xB7 '+escHtml(v.gender)+"":"")+"
"}).join("")+(list.length===0?'
No voices found
':""),ul.querySelectorAll(".lib-vp-item").forEach(function(item){item.addEventListener("click",async function(){const vid=item.dataset.vid;await clUpsert(rec.book,Object.assign({},rec.sheet,{name:rec.name,voice:vid})),popup.remove(),onDone()})})}renderList(""),popup.querySelector(".lib-vp-input").addEventListener("input",function(e){renderList(e.target.value)}),cardEl.style.position="relative",cardEl.appendChild(popup),setTimeout(function(){function close(e){popup.contains(e.target)||(popup.remove(),document.removeEventListener("click",close))}document.addEventListener("click",close)},0),popup.querySelector(".lib-vp-input").focus()}async function _autoAssignVoice(rec){var _a2;const voices=(window._voices||[]).filter(function(v){return v.enabled!==!1});if(!voices.length){toast("Voice library not loaded","error");return}const gender=String(((_a2=rec.sheet)==null?void 0:_a2.gender)||"").toLowerCase().trim(),isFemale=gender.startsWith("f")||gender.startsWith("w"),isMale=!isFemale&&gender.startsWith("m"),gMatch=isFemale?"f":isMale?"m":"";let pool=gMatch?voices.filter(function(v){const vg=String(v.gender||"").toLowerCase();return gMatch==="f"?vg.startsWith("f")||vg.startsWith("w"):vg.startsWith("m")}):voices;pool.length||(pool=voices);const usedInBook=new Set;try{(await clGetAllByTagOrBook(rec.book)).forEach(function(r){r.voice&&r.id!==rec.id&&usedInBook.add(r.voice)})}catch{}const fresh=pool.filter(function(v){return!usedInBook.has(v.id)}),candidate=(fresh.length?fresh:pool).sort(function(a,b){return(b.rating||0)-(a.rating||0)})[0];if(!candidate){toast("No matching voice found","error");return}await clUpsert(rec.book,Object.assign({},rec.sheet,{name:rec.name,voice:candidate.id})),toast("Assigned "+candidate.id+" \u2192 "+rec.name,"success")}function _charLang(rec){const sh=rec.sheet||{},text=[sh.backstory,sh.voice_pattern,sh.mannerisms,sh.relationships,sh.motivation,sh.archetype].filter(Boolean).join(" ");return typeof detectLang=="function"?detectLang(text):""}function _buildVoicePrompt(rec){const sh=rec.sheet||{},g=String(sh.gender||"").toLowerCase(),genderWord=g.startsWith("f")?"female":g.startsWith("m")?"male":"",bits=[];return bits.push("A "+(genderWord?genderWord+" ":"")+"voice"+(sh.archetype?" for "+sh.archetype.toLowerCase():"")+"."),sh.voice_pattern&&bits.push(sh.voice_pattern),sh.mannerisms&&bits.push("Mannerisms: "+sh.mannerisms),sh.physical&&bits.push(sh.physical),sh.alignment&&bits.push("Disposition: "+sh.alignment),bits.join(" ").slice(0,600)}function _selectLoose(sel,val){if(!sel||!val)return;const v=String(val).toLowerCase(),opt=[...sel.options].find(function(o){const ov=o.value.toLowerCase(),ot=o.textContent.toLowerCase();return ov===v||ot===v||ov.startsWith(v)||ot.startsWith(v)||v.startsWith(ov)});opt&&(sel.value=opt.value,sel.dispatchEvent(new Event("change")))}function _charSearchOnline(rec){typeof navTo=="function"&&navTo("s-studio");const lang=_charLang(rec);setTimeout(function(){const fishTab=document.querySelector('#gvo-tabs .gvo-tab[data-src="fish"]');fishTab&&fishTab.click(),setTimeout(function(){const langSel=document.getElementById("fa-lang");langSel&&_selectLoose(langSel,lang);const search=document.getElementById("fa-search");search&&(search.value=rec.name,search.dispatchEvent(new KeyboardEvent("keydown",{key:"Enter",bubbles:!0})))},120)},120),toast("Searching online voices for "+rec.name+(lang?" ("+lang+")":""),"info")}function _charDesignVoice(rec){typeof navTo=="function"&&navTo("s-design");const sh=rec.sheet||{},lang=_charLang(rec);setTimeout(function(){_selectLoose(document.getElementById("design-gender"),sh.gender),_selectLoose(document.getElementById("design-language"),lang);const instruct=document.getElementById("design-instruct");instruct&&(instruct.value=_buildVoicePrompt(rec));const nm=document.getElementById("design-preset-name");nm&&(nm.value=rec.name)},140),toast("Voice design prepared for "+rec.name+(lang?" \xB7 "+lang:""),"info")}window._charSearchOnline=_charSearchOnline,window._charDesignVoice=_charDesignVoice,window.libraryRenderCharacters=libraryRenderCharacters;