1!function(){const e="xcCreateSetModal";let t=null,n=[];function o(e){return document.getElementById(e)}function c(e,t,n){return`\n <div style="display:flex;flex-direction:column;gap:4px;">\n <label style="font-size:12px;color:#aaa;">${e}</label>\n <input id="${t}" type="text" value="${n}" style="padding:6px;border-radius:4px;border:1px solid #333;background:#000;color:#fff;font-size:13px;">\n </div>\n `}async function s(){const t=o("xc-cs-proceed"),c=o("xc-cs-status");t.disabled=!0,c.style.display="block",c.textContent="Processing files...";try{const s=n;if(0===s.length)return alert("No .json files selected."),t.disabled=!1,void(c.style.display="none");const a=new Map,i=[];for(const e of s){const t=await e.getFile();let n;try{n=await t.text()}catch(t){i.push({fileName:e.name,xc_nr:"N/A",status:"Failed",reason:"File read error"});continue}let o=[];try{o=JSON.parse(n).annotations||[]}catch(t){i.push({fileName:e.name,xc_nr:"N/A",status:"Failed",reason:"Wrong format / Invalid JSON"});continue}if(0===o.length){i.push({fileName:e.name,xc_nr:"N/A",status:"Failed",reason:"No annotations found in file"});continue}const c=new Map,s=e.name.match(/XC0*(\d+)/i),r=s?Number(s[1]):null;for(const e of o){let t=e.xc_nr?String(e.xc_nr).trim():r?String(r):null;t||(t="Unknown"),c.has(t)||c.set(t,[]),c.get(t).push(e)}for(const[t,n]of c.entries()){const o={fileName:e.name,xc_nr:t,status:"Pending",reason:""};if(i.push(o),"Unknown"===t){o.status="Failed",o.reason="XC record number not available";continue}const c=a.get(t);if(c){let o=!1;(n.length>c.records.length||n.length===c.records.length&&e.name.localeCompare(c.name)<0)&&(o=!0),o&&a.set(t,{name:e.name,records:n})}else a.set(t,{name:e.name,records:n})}}for(const e of i)if("Pending"===e.status){const t=a.get(e.xc_nr);t&&t.name===e.fileName?(e.status="Success",e.reason="Included"):t&&(e.status="Failed",e.reason=`XC record number covered in ${t.name}`)}const r=[];for(const[e,t]of a.entries())for(let n of t.records){const{annotation_xc_id:t,signal_noise_ratio:o,annotation_speed_ratio:c,...s}=n;r.push({...s,xc_nr:e})}const l=new Date,d=e=>String(e).padStart(2,"0"),p=`XC_Annotation_Set_${`${l.getFullYear()}-${d(l.getMonth()+1)}-${d(l.getDate())}T${d(l.getHours())}-${d(l.getMinutes())}`}`;let u="Serial,JSON file name,XC record number,Success / Failed,Reason\n";i.forEach((e,t)=>{const n=`"${e.fileName.replace(/"/g,'""')}"`,o=`"${e.reason.replace(/"/g,'""')}"`;u+=`${t+1},${n},${e.xc_nr},${e.status},${o}\n`});const x=new Blob([u],{type:"text/csv;charset=utf-8"}),f=URL.createObjectURL(x),m=document.createElement("a");if(m.href=f,m.download=`Log_${p}.csv`,document.body.appendChild(m),m.click(),m.remove(),setTimeout(()=>URL.revokeObjectURL(f),2e3),0===r.length)return alert("No valid annotations with XC file numbers were found. Please check the downloaded Log file for details."),t.disabled=!1,void(c.style.display="none");const g={set_source:o("xc-cs-source").value.trim(),set_uri:o("xc-cs-uri").value.trim(),set_name:o("xc-cs-name").value.trim(),annotation_software_name_and_version:"Spectrolipi",set_creator:o("xc-cs-creator").value.trim(),set_creator_id:o("xc-cs-creator-id").value.trim(),set_owner:o("xc-cs-owner").value.trim(),set_license:o("xc-cs-license").value.trim(),project_uri:o("xc-cs-project-uri").value.trim(),project_name:o("xc-cs-project-name").value.trim(),funding:o("xc-cs-funding").value.trim(),scope:[{taxon_coverage:o("xc-cs-taxon").value.trim(),completeness:o("xc-cs-completeness").value.trim()}],annotations:r},y=o("xc-cs-action-export").checked,b=o("xc-cs-action-upload").checked;if(y){c.textContent="Exporting JSON...";const e=new Blob([JSON.stringify(g,null,2)],{type:"application/json"}),t=URL.createObjectURL(e),n=document.createElement("a");n.href=t,n.download=`${p}.json`,document.body.appendChild(n),n.click(),n.remove(),setTimeout(()=>URL.revokeObjectURL(t),2e3)}if(b){c.textContent="Uploading to Xeno-canto API...";const e=localStorage.getItem("xc.settings.v1");if(!e)throw new Error("XC settings not found. Please configure Xeno-canto settings first.");const t=JSON.parse(e)||{},n=t.apiKey||"";if(!n)throw new Error("XC API key missing in settings.");const o=t.endpoint||"https://xeno-canto.org/api/3/upload/annotation-set",s=await fetch(o,{method:"POST",headers:{key:n,Authorization:"Basic "+btoa("xc:xc"),"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(g)});if(!s.ok){const e=await s.text();throw new Error(`Upload failed (${s.status}): ${e}`)}}c.textContent=`Success! Included ${a.size} XC records (${r.length} annotations) from ${s.length} files scanned.`,setTimeout(()=>
1o(e).style.display="none",3500)}catch(e){alert("Error creating set: "+e.message),c.style.display="none"}finally{t.disabled=!1}}window.__openXcCreateSetModal=function(){let a=document.getElementById(e);a||(a=function(){const a=document.createElement("div");a.id=e,a.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,0.65);z-index:2147483655;display:none;align-items:center;justify-content:center;backdrop-filter:blur(2px);";const i=document.createElement("div");function r(){const e=a.querySelector("#xc-cs-proceed"),t=a.querySelector("#xc-cs-action-export").checked,o=a.querySelector("#xc-cs-action-upload").checked,c=a.querySelector("#xc-cs-name").value.trim();e.disabled=!(n.length>0&&(t||o)&&""!==c)}return i.style.cssText="background:#111;color:#fff;width:95%;max-width:850px;padding:20px;border-radius:10px;box-shadow:0 12px 36px rgba(0,0,0,0.4);border:1px solid rgba(255,255,255,0.05);font-family:system-ui,sans-serif;max-height:90vh;overflow-y:auto;",i.innerHTML=`\n <style>\n #xc-cs-proceed:disabled {\n background: #444 !important;\n color: #888 !important;\n cursor: not-allowed !important;\n }\n </style>\n <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">\n <h3 style="margin:0;font-size:18px;font-weight:600;">Create Xeno-canto Set</h3>\n <button id="xc-cs-close" style="background:transparent;border:0;color:#9ca3af;font-size:24px;cursor:pointer;line-height:1;">×</button>\n </div>\n \n <div style="margin-bottom:12px; display:flex; align-items:center; gap:16px;">\n <label style="font-size:13px;color:#ddd;display:flex;align-items:center;gap:6px;cursor:pointer;">\n <input type="radio" name="xc-cs-source-type" value="folder" checked> Select Folder\n </label>\n <label style="font-size:13px;color:#ddd;display:flex;align-items:center;gap:6px;cursor:pointer;">\n <input type="radio" name="xc-cs-source-type" value="files"> Select Files\n </label>\n </div>\n <div style="margin-bottom:16px; display:flex; align-items:center; gap:12px;">\n <button id="xc-cs-pick-btn" type="button" class="seg-btn" style="width:70px;height:auto;padding:4px 8px;font-size:12px;line-height:1.2;white-space:normal;">Select<br>Folder</button>\n <span id="xc-cs-selection-info" style="font-size:13px;color:#aaa;">No files selected</span>\n </div>\n \n <div style="display:grid; grid-template-columns:repeat(3, 1fr); gap:12px; margin-bottom:20px;">\n ${c("Set Source","xc-cs-source","")}\n ${c("Set URI","xc-cs-uri","")}\n ${c("Set Name *","xc-cs-name","")}\n ${c("Set Creator","xc-cs-creator","")}\n ${c("Set Creator ID","xc-cs-creator-id","")}\n ${c("Set Owner","xc-cs-owner","")}\n ${function(){return`\n <div style="display:flex;flex-direction:column;gap:4px;">\n <label style="font-size:12px;color:#aaa;">${"Set License"}</label>\n <select id="${"xc-cs-license"}" style="padding:6px;border-radius:4px;border:1px solid #333;background:#000;color:#fff;font-size:13px;">\n ${[{value:"",text:""},{value:"CC-BY-4.0",text:"CC-BY-4.0"},{value:"CC-BY-NC-4.0",text:"CC-BY-NC-4.0"}].map(e=>`<option value="${e.value}">${e.text}</option>`).join("")}\n </select>\n </div>\n `}()}\n ${c("Project URI","xc-cs-project-uri","")}\n ${c("Project Name","xc-cs-project-name","")}\n ${c("Funding","xc-cs-funding","")}\n ${c("Taxon Coverage","xc-cs-taxon","")}\n ${c("Completeness","xc-cs-completeness","")}\n </div>\n \n <div style="margin-bottom:20px;background:#1a1a1a;padding:12px;border-radius:6px;border:1px solid #333;display:flex;gap:24px;">\n <label style="display:flex;align-items:center;gap:8px;margin-bottom:8px;
1cursor:pointer;font-size:14px;">\n <input type="checkbox" id="xc-cs-action-export"> Save / Download JSON output\n </label>\n <label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;">\n <input type="checkbox" id="xc-cs-action-upload"> Upload directly to Xeno-canto\n </label>\n </div>\n \n <div id="xc-cs-status" style="margin-bottom:12px;font-size:13px;color:#34d399;display:none;"></div>\n \n <div style="display:flex;justify-content:flex-end;gap:10px;">\n <button id="xc-cs-cancel" type="button" style="background:transparent;border:1px solid #4b5563;color:#cbd5e1;padding:8px 16px;border-radius:6px;cursor:pointer;font-size:14px;">Cancel</button>\n <button id="xc-cs-proceed" type="button" class="btn" style="background:#2196F3;color:#fff;border:none;padding:8px 24px;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600;" disabled>Create Set</button>\n </div>\n `,a.appendChild(i),a.querySelectorAll('input[name="xc-cs-source-type"]').forEach(e=>{e.onchange=()=>{const o=a.querySelector("#xc-cs-pick-btn");"folder"===e.value?o.innerHTML="Select<br>Folder":o.innerHTML="Select<br>Files",t=null,n=[],a.querySelector("#xc-cs-selection-info").textContent="No files selected",r()}}),a.querySelector("#xc-cs-close").onclick=()=>a.style.display="none",a.querySelector("#xc-cs-cancel").onclick=()=>a.style.display="none",a.querySelector("#xc-cs-pick-btn").onclick=async()=>{const e=a.querySelector('input[name="xc-cs-source-type"]:checked').value;try{if("folder"===e){t=await window.showDirectoryPicker({mode:"readwrite"}),n=[];for await(const e of t.values())"file"===e.kind&&e.name.toLowerCase().endsWith(".json")&&n.push(e);o("xc-cs-selection-info").textContent=`${n.length} .json file(s) found in folder: ${t.name}`}else{const e=await window.showOpenFilePicker({multiple:!0,types:[{description:"JSON Files",accept:{"application/json":[".json"]}}]});t=null,n=e,o("xc-cs-selection-info").textContent=`${n.length} .json file(s) selected`}r()}catch(e){"AbortError"!==e.name&&alert("Selection failed: "+e.message)}},a.querySelector("#xc-cs-proceed").onclick=s,a.querySelector("#xc-cs-action-export").onchange=r,a.querySelector("#xc-cs-action-upload").onchange=r,a.querySelector("#xc-cs-name").addEventListener("input",r),a}(),document.body.appendChild(a));const i=document.getElementById("xc-cs-name");if(i){const e=new Date,t=e=>String(e).padStart(2,"0");i.value=`Annotation set created on ${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())}T${t(e.getHours())}:${t(e.getMinutes())}`,i.dispatchEvent(new Event("input"))}a.style.display="flex"}}();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.