1<!doctype html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>FTIR Merger</title> 7 <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0" /> 8 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"> 9 <link rel="stylesheet" href="styles.css"> 10
10<script src="./d3.min.js"></script>
10 11
11<script src="./jcampconverter.min.js"></script>
11 12
12<script src="https://unpkg.com/[email protected]/dist/smiles-drawer.min.js"></script>
vendor: 1 bytes, line 12
12
13<script data-cfasync="false" nonce="0ba5534b-86c4-40c1-837e-3ceb686da802">try{(function(w,d){!function(kP,kQ,kR,kS){if(kP.zaraz)console.error("zaraz is loaded twice");else{kP[kR]=kP[kR]||{};kP[kR].executed=[];kP.zaraz={deferred:[],listeners:[]};kP.zaraz._v="20";kP.zaraz._n="0ba5534b-86c4-40c1-837e-3ceb686da802";kP.zaraz.q=[];kP.zaraz._f=function(kT){return async function(){var kU=Array.prototype.slice.call(arguments);kP.zaraz.q.push({m:kT,a:kU})}};for(const kV of["track","set","debug"])kP.zaraz[kV]=kP.zaraz._f(kV);kP.zaraz.init=()=>{var kW=kQ.getElementsByTagName(kS)[0],kX=kQ.createElement(kS),kY=kQ.getElementsByTagName("title")[0];kY&&(kP[kR].t=kQ.getElementsByTagName("title")[0].text);kP[kR].x=Math.random();kP[kR].w=kP.screen.width;kP[kR].h=kP.screen.height;kP[kR].j=kP.innerHeight;kP[kR].e=kP.innerWidth;kP[kR].l=kP.location.href;kP[kR].r=kQ.referrer;kP[kR].k=kP.screen.colorDepth;kP[kR].n=kQ.characterSet;kP[kR].o=(new Date).getTimezoneOffset();if(kP.dataLayer)for(const kZ of Object.entries(Object.entries(dataLayer).reduce((k$,la)=>({...k$[1],...la[1]}),{})))zaraz.set(kZ[0],kZ[1],{scope:"page"});kP[kR].q=[];for(;kP.zaraz.q.length;){const lb=kP.zaraz.q.shift();kP[kR].q.push(lb)}kX.defer=!0;for(const lc of[localStorage,sessionStorage])Object.keys(lc||{}).filter(le=>le.startsWith("_zaraz_")).forEach(ld=>{try{kP[kR]["z_"+ld.slice(7)]=JSON.parse(lc.getItem(ld))}catch{kP[kR]["z_"+ld.slice(7)]=lc.getItem(ld)}});kX.referrerPolicy="origin";kX.src="/cdn-cgi/zaraz/s.js?z="+btoa(encodeURIComponent(JSON.stringify(kP[kR])));kW.parentNode.insertBefore(kX,kW)};["complete","interactive"].includes(kQ.readyState)?zaraz.init():kP.addEventListener("DOMContentLoaded",zaraz.init)}}(w,d,"zarazData","script");window.zaraz._p=async kH=>new Promise(kI=>{if(kH){kH.e&&kH.e.forEach(kJ=>{try{const kK=d.querySelector("script[nonce]"),kL=kK?.nonce||kK?.getAttribute("nonce"),kM=d.createElement("script");kL&&(kM.nonce=kL);kM.innerHTML=kJ;kM.onload=()=>{d.head.removeChild(kM)};d.head.appendChild(kM)}catch(kN){console.error(`Error executing script: ${kJ}\n`,kN)}});Promise.allSettled((kH.f||[]).map(kO=>fetch(kO[0],kO[1])))}kI()});zaraz._p({"e":["(function(w,d){})(window,document)"]});})(window,document)}catch(e){throw fetch("/cdn-cgi/zaraz/t"),e;};</script>
13</head> 14<body> 15 <nav class="tool-nav" aria-label="Multitool"> 16 <a href="/">Multitool</a><a href="/search/">Search</a><a href="/qr/">QR</a><a href="/pdf/">PDF</a><a href="/badge/">Badge</a><a href="/ftir/" aria-current="page">FTIR</a> 17 </nav> 18 <div class="bg-accent"></div> 19 <div class="app-shell container-fluid px-1"> 20 <header class="topbar surface"> 21 <div class="brand"> 22 <img src="logo.png" alt="FTIR Merger logo" class="brand-logo" /> 23 <div class="brand-copy"> 24 <p class="eyebrow">Spectra workspace</p> 25 <strong>FTIR viewer</strong> 26 </div> 27 </div> 28 <div class="topbar-form toolbar"> 29 <input id="files" type="file" accept=".txt,.jdx,.dx,.jsm,.jcm,.csv,text/plain,application/octet-stream" multiple style="display:none;"> 30 <input id="importSessionInput" type="file" accept=".json" style="display:none;"> 31 <input id="importUserSettingsInput" type="file" accept=".json,application/json" style="display:none;"> 32 <div class="toolbar-group"> 33 <button id="selectFiles" class="icon-btn primary" type="button" title="Add files" aria-label="Add files"> 34 <span class="material-symbols-outlined">computer_arrow_up</span> 35 </button> 36 <button id="exportSession" class="icon-btn" type="button" title="Export session" aria-label="Export session"> 37 <span class="material-symbols-outlined">file_download</span> 38 </button> 39 <button id="importSession" class="icon-btn" type="button" title="Import session" aria-label="Import session"> 40 <span class="material-symbols-outlined">unarchive</span> 41 </button> 42 <button id="clearLocalSession" class="icon-btn" type="button" title="Clear local session" aria-label="Clear local session"> 43 <span class="material-symbols-outlined">delete_sweep</span> 44 </button> 45 <button id="copyPng" class="icon-btn saveimg" type="button" title="Copy chart PNG" aria-label="Copy chart PNG"> 46 <span class="material-symbols-outlined">image</span> 47 </button> 48 <button id="copySvg" class="icon-btn saveimg" type="button" title="Copy chart SVG" aria-label="Copy chart SVG"> 49 <span class="material-symbols-outlined">lasso_select</span> 50 </button> 51 <button id="saveCsv" class="icon-btn success" type="button" title="Save CSV" aria-label="Save CSV"> 52 <span class="material-symbols-outlined">save</span> 53 </button> 54 <button id="openAppSettings" class="icon-btn" type="button" title="Application settings" aria-label="Application settings"> 55 <span class="material-symbols-outlined">settings</span> 56 </button> 57 </div> 58 <div class="toolbar-group name-group"> 59 <label class="visually-hidden" for="fileName">CSV name</label> 60 <input id="fileName" type="text" class="save-name" value="merged.csv" aria-label="CSV file name"> 61 </div> 62 </div> 63 <div class="lang-switch"> 64 <a href="#" class="lang-link" data-lang="en">EN</a> 65 <a href="#" class="lang-link" data-lang="ru">RU</a> 66 <a href="#" class="lang-link" data-lang="sr">SR</a> 67 </div> 68 </header> 69 70 <div class="card surface"> 71 <div class="inline-row inline-row--sub"> 72 <p class="helper">Multi-select supported. Each column will match the file name.</p> 73 <div class="status-block"> 74 <div id="status"></div> 75 <div id="downloadLink"></div> 76 </div> 77 </div> 78 79 <div class="chart-tabs-row"> 80 <div id="chartLegend" class="spectrum-tabs nav nav-tabs" role="tablist" aria-label="Open spectra"></div> 81 <div class="chart-actions"> 82 <button id="searchLocalReferences" type="button" class="btn tonal chart-reference-search" hidden data-i18n="findReferenceMatches">Find reference matches</button> 83 <button id="openChartSettings" class="icon-btn chart-settings-trigger" type="button" title="Chart settings" aria-label="Chart settings" disabled> 84 <span class="material-symbols-outlined">tune</span> 85 </button> 86 </div> 87 </div> 88 <div class="chart-row is-hidden" id="chartRow"> 89 <div class="chart-main"> 90 <div id="zoneHint" class="zone-hint"></div> 91 <div class="chart-interaction-hint" data-i18n="chartInteractionHint">Ctrl/Cmd + scroll over the chart to zoom · middle mouse button to pan · arrow keys to pan</div> 92 <div id="chart"></div> 93 </div> 94 </div> 95 </div> 96 97 <aside class="reference-sidebar" id="referenceSearchCard" hidden aria-label="Reference search results"> 98 <div class="reference-card-header"> 99 <div> 100 <div class="eyebrow" data-i18n="referenceMatchesEyebrow">Whole-spectrum reference search</div> 101 <h3 data-i18n="referenceMatchesTitle">Reference matches</h3> 102 <p id="referenceSearchSpectrum" class="reference-spectrum-name"></p> 103 </div>
104 <span id="referenceSearchStatus" class="analysis-status"></span> 105 </div> 106 <div id="referenceSearchResult" class="reference-search-result" aria-live="polite"></div> 107 </aside> 108 <button id="referenceSidebarToggle" class="reference-sidebar-toggle" type="button" hidden aria-controls="referenceSearchCard" aria-expanded="false" title="Open reference results" aria-label="Open reference results"> 109 <span class="material-symbols-outlined">chevron_left</span> 110 </button> 111 112 <dialog id="chartSettingsDialog" class="chart-settings-dialog"> 113 <div class="dialog-header"> 114 <div> 115 <div class="eyebrow">Chart settings</div> 116 <h3>View and processing</h3> 117 </div> 118 <button id="closeChartSettings" class="icon-btn" type="button" aria-label="Close chart settings">Ã</button> 119 </div> 120 <div id="chartControls" class="controls chart-settings-content"> 121 <h3 data-i18n="chartSettings">Chart settings</h3> 122 <div class="control-group"> 123 <label data-i18n="xRange">X range (reverse):</label> 124 <div class="inline-inputs"> 125 <input id="xMax" type="number" value="4000" step="1" disabled> 126 <span>â</span> 127 <input id="xMin" type="number" value="500" step="1" disabled> 128 </div> 129 </div> 130 <div class="control-group"> 131 <label data-i18n="yRange">Y range:</label> 132 <div class="inline-inputs"> 133 <input id="yMin" type="number" step="0.1" placeholder="auto" disabled> 134 <span>â</span> 135 <input id="yMax" type="number" step="0.1" placeholder="auto" disabled> 136 </div> 137 </div> 138 <div class="control-group baseline-group"> 139 <label data-i18n="baselineSection">Baseline fit</label> 140 <div class="baseline-controls"> 141 <select id="baselineSeries"></select> 142 <input id="baselineDegree" type="number" min="1" max="6" step="1" value="2"> 143 <button id="baselinePreview" type="button" class="btn tonal" disabled data-i18n="baselinePreview">Preview</button> 144 <button id="baselineApply" type="button" class="btn tonal" disabled data-i18n="baselineApply">Apply</button> 145 <button id="baselineRevert" type="button" class="btn tonal" disabled data-i18n="baselineRevert">Revert</button> 146 </div> 147 </div> 148 <div class="control-actions"> 149 <button id="refreshChart" type="button" class="icon-btn" disabled title="Refresh chart" aria-label="Refresh chart"> 150 <span class="material-symbols-outlined">refresh</span> 151 </button> 152 <button id="resetZoom" type="button" class="icon-btn" disabled title="Reset zoom" aria-label="Reset zoom"> 153 <span class="material-symbols-outlined">zoom_out_map</span> 154 </button> 155 </div> 156 </div> 157 </dialog> 158 159 <div class="card surface peaks-card"> 160 <div class="peaks-header"> 161 <div> 162 <h3 data-i18n="peaksTitle">Peaks / stripes</h3> 163 <div id="peaksSpectrumTitle" class="selected-spectrum-title">No spectrum selected</div> 164 </div> 165 <div class="peaks-header-actions"> 166 <button id="peakDetectionHelp" type="button" class="icon-btn detector-help" title="Peak detector guide" aria-label="Peak detector guide"> 167 <span class="material-symbols-outlined">help</span> 168 </button> 169 <button id="togglePeaksTable" type="button" class="icon-btn" title="Collapse peaks table" aria-label="Collapse peaks table" aria-expanded="true" aria-controls="peaksTableWrap"> 170 <span class="material-symbols-outlined">unfold_less</span> 171 </button> 172 </div> 173 </div> 174 <div class="peaks-control-layout"> 175 <fieldset class="detector-fieldset detector-source-fieldset"> 176 <legend data-i18n="peakSearchLabel">Peak search</legend> 177 <div class="detector-source-grid"> 178 <div class="detector-source-heading">
179 <span data-i18n="detectorSignalLabel">Signal</span> 180 <div class="detector-icon-actions"> 181 <button id="openPeakSettings" type="button" class="icon-btn detector-help" title="Peak processing settings" aria-label="Peak processing settings" disabled> 182 <span class="material-symbols-outlined">tune</span> 183 </button> 184 </div> 185 </div> 186 <label class="detector-signal-field" for="detectorSignalType"> 187 <select id="detectorSignalType" aria-label="Signal type"> 188 <option value="unknown">Auto / unknown</option> 189 <option value="transmittance">%T transmittance</option> 190 <option value="absorbance">Absorbance / intensity</option> 191 </select> 192 </label> 193 <div class="detector-baseline-state"> 194 <span data-i18n="detectorBaseline">Baseline</span> 195 <strong id="detectorBaselineSummary" class="detector-baseline-summary">raw mode</strong> 196 </div> 197 </div> 198 </fieldset> 199 <fieldset class="detector-fieldset detector-parameters-fieldset"> 200 <legend data-i18n="detectorParameters">Detection parameters</legend> 201 <div class="detector-parameters-grid"> 202 <label class="detector-field" for="detectorMinProminence"><span data-i18n="detectorProminence">Prominence</span><input id="detectorMinProminence" type="number" min="0" step="0.01" value="0.02" aria-label="Minimum prominence" title="Minimum prominence"></label> 203 <label class="detector-field" for="detectorMinSeparation"><span data-i18n="detectorDistance">Distance</span><input id="detectorMinSeparation" type="number" min="0.1" step="0.1" value="8" aria-label="Minimum separation cm-1" title="Minimum separation (cm-1)"></label> 204 <label class="detector-field" for="detectorSmoothingWindow"><span data-i18n="detectorSmooth">Smooth</span><input id="detectorSmoothingWindow" type="number" min="1" max="101" step="2" value="5" aria-label="Smoothing window" title="Smoothing window"></label> 205 <button id="detectPeaks" type="button" class="btn tonal detector-run-btn" disabled>Detect</button> 206 </div> 207 <small id="detectorStatus">Not run.</small> 208 <div id="detectorConnectionStatus" class="detector-connection-status" role="alert" hidden></div> 209 </fieldset> 210 <fieldset class="detector-fieldset peaks-actions-fieldset"> 211 <legend data-i18n="peakActions">Peak table actions</legend> 212 <div class="peaks-actions"> 213 <button id="addStripe" type="button" class="btn tonal" data-i18n="addStripe">Add stripe</button> 214 <button id="copyStripes" type="button" class="btn tonal" data-i18n="copyStripes">Copy table</button> 215 <button id="copyConfirmedPayload" type="button" class="btn tonal">Copy confirmed JSON</button> 216 <button id="analyzeConfirmed" type="button" class="btn primary">Analyze confirmed</button> 217 </div> 218 <label class="analysis-prompt-field" for="analysisPrompt"> 219 <span data-i18n="analysisPromptLabel">Additional analysis context (optional)</span> 220 <textarea id="analysisPrompt" rows="2" maxlength="2000" data-i18n-placeholder="analysisPromptPlaceholder" placeholder="Add a question or context for the interpretation"></textarea> 221 </label> 222 </fieldset> 223 </div> 224 <p class="peaks-hint" data-i18n="peakHint">Click the chart to place a marker, then add it here.</p> 225 <div id="peaksTableWrap" class="peaks-table"> 226 <div class="stripe-sets"> 227 <button class="stripe-set-btn active" data-set="candidates">Candidates</button> 228 <button class="stripe-set-btn" data-set="confirmed">Confirmed</button> 229 <button id="confirmAllPeaks" type="button" class="stripe-table-action" disabled title="Confirm all candidates for the selected spectrum">Confirm all</button> 230 </div> 231 <div class="peaks-row peaks-head"> 232 <span>Â </span>
233 <span data-i18n="colWave">Wavenumber</span> 234 <span data-i18n="colLabel">Label</span> 235 <span data-i18n="colTip">Tip</span> 236 <span> </span> 237 </div> 238 <div id="peaksEmpty" class="peaks-empty" data-i18n="peaksEmpty">No stripes yet. Set a marker and add it here.</div> 239 <div id="peaksBody" class="peaks-body"></div> 240 </div> 241 </div> 242 <dialog id="spectrumSettingsDialog" class="spectrum-settings-dialog"> 243 <form method="dialog" id="spectrumSettingsForm"> 244 <div class="dialog-header"> 245 <div> 246 <div class="eyebrow">Spectrum settings</div> 247 <h3 id="spectrumSettingsTitle">Spectrum</h3> 248 </div> 249 <button type="button" class="icon-btn" id="closeSpectrumSettings" aria-label="Close settings">Ã</button> 250 </div> 251 <input id="spectrumSettingsId" type="hidden"> 252 <label for="spectrumSettingsName">Display name</label> 253 <input id="spectrumSettingsName" type="text" maxlength="255"> 254 <label for="spectrumSettingsOffset">Y offset</label> 255 <input id="spectrumSettingsOffset" type="number" step="0.1"> 256 <label for="spectrumSettingsRole" data-i18n="spectrumRole">Role in series</label> 257 <select id="spectrumSettingsRole" aria-label="Role in series"> 258 <option value="unknown" data-i18n="roleUnknown">Unknown</option> 259 <option value="before" data-i18n="roleBefore">Before</option> 260 <option value="after" data-i18n="roleAfter">After</option> 261 <option value="control" data-i18n="roleControl">Control</option> 262 <option value="repeat" data-i18n="roleRepeat">Repeat</option> 263 </select> 264 <div class="dialog-actions"> 265 <button type="button" class="btn danger" id="removeSpectrumFromDialog">Remove spectrum</button> 266 <button type="submit" class="btn primary" id="saveSpectrumSettings">Save</button> 267 </div> 268 </form> 269 </dialog> 270 <dialog id="peakDetectionHelpDialog" class="peak-help-dialog"> 271 <div class="dialog-header"> 272 <div> 273 <div class="eyebrow" data-i18n="peakHelpEyebrow">Peak search</div> 274 <h3 data-i18n="peakHelpTitle">Peak detector guide</h3> 275 </div> 276 <button id="closePeakDetectionHelp" class="icon-btn" type="button" aria-label="Close peak detector guide">Ã</button> 277 </div> 278 <p class="peak-help-intro" data-i18n="peakHelpIntro">These settings affect how automatic peaks are selected for the active spectrum.</p> 279 <div class="peak-help-list"> 280 <section><h4 data-i18n="peakHelpSignalType">Signal type</h4><p data-i18n="peakHelpSignalTypeText">Transmittance (%T) is converted to absorbance, so absorption bands are detected as peaks. For absorbance or intensity, the values are used directly.</p></section> 281 <section><h4 data-i18n="peakHelpBaseline">Baseline method</h4><p data-i18n="peakHelpBaselineText">Removes slow background curvature before peak search. arPLS is the default. For very broad bands, compare the corrected curve and try Linear or None if the band is being removed.</p></section> 282 <section><h4 data-i18n="peakHelpProminence">Prominence</h4><p data-i18n="peakHelpProminenceText">Minimum contrast against the local baseline. Increase it to remove noise and weak shoulders; decrease it to find weaker bands.</p></section> 283 <section><h4 data-i18n="peakHelpDistance">Distance</h4><p data-i18n="peakHelpDistanceText">Minimum separation between peaks in cmâ»Â¹. Increase it when one broad band is split into many nearby peaks.</p></section> 284 <section><h4 data-i18n="peakHelpSmooth">Smooth</h4><p data-i18n="peakHelpSmoothText">Moving-average window in data points. Increase it for noisy spectra, but a large value can flatten narrow peaks. Use an odd number.</p></section> 285 </div> 286 <p class="peak-help-tip" data-i18n="peakHelpQuick">Quick start: try Prominence 0.02, Distance 8, Smooth 5, then adjust one setting at a time.</p> 287 </dialog> 288 <dialog id="peakDetectorSettingsDialog" class="peak-detector-settings-dialog"> 289 <div class="dialog-header"> 290 <div> 291 <div class="eyebrow" data-i18n="detectorBaselineEyebrow">Peak processing</div> 292 <h3 data-i18n="detectorBaselineTitle">Baseline correction</h3> 293 </div> 294 <button id="closePeakSettings" class="icon-btn" type="button" aria-label="Close baseline settings">Ã</button> 295 </div> 296 <p class="peak-help-intro" data-i18n="detectorBaselineIntro">Choose how the slow background is removed from the active spectrum. Apply the correction before searching peaks.</p> 297 <label for="detectorBaselineMethod" data-i18n="detectorBaseline">Baseline method</label> 298 <select id="detectorBaselineMethod" aria-label="Baseline method"> 299 <option value="arpls">arPLS</option> 300 <option value="airpls">airPLS</option> 301 <option value="asls">AsLS</option> 302 <option value="snip">SNIP</option> 303 <option value="rubberband">Rubberband</option> 304 <option value="linear">Linear</option> 305 <option value="none">None</option> 306 </select> 307 <div class="peak-detector-settings-note" data-i18n="detectorBaselineNote">The main chart will switch to the corrected curve in the original display scale. Peak search will use the same applied correction.</div> 308 <div id="peakProcessingMeta" class="peak-processing-meta"></div> 309 <div id="peakProcessingChart" class="peak-processing-chart"></div> 310 <p class="peak-processing-hint" data-i18n="peakProcessingHint">The red line is the baseline used by the fine detector. The green curve is searched for peaks. The broad-pass detector keeps broad bands separately.</p> 311 <div class="dialog-actions"> 312 <button id="cancelPeakSettings" type="button" class="btn tonal" data-i18n="cancel">Cancel</button> 313 <button id="applyPeakBaseline" type="button" class="btn primary" data-i18n="applyBaseline">Apply baseline</button> 314 </div> 315 </dialog> 316 <div class="card surface analysis-card" id="analysisCard" hidden> 317 <div class="peaks-header"> 318 <h3>LLM interpretation</h3>
319 <span id="analysisStatus" class="analysis-status"></span> 320 </div> 321 <div id="analysisResult" class="analysis-result" aria-live="polite"></div> 322 </div> 323 <dialog id="appSettingsDialog" class="app-settings-dialog"> 324 <form id="appSettingsForm" method="dialog"> 325 <div class="dialog-header"> 326 <div> 327 <div class="eyebrow" data-i18n="settingsEyebrow">Local profile</div> 328 <h3 data-i18n="settingsTitle">Application settings</h3> 329 </div> 330 <button id="closeAppSettings" class="icon-btn" type="button" aria-label="Close settings">Ã</button> 331 </div> 332 <p class="settings-note" data-i18n="settingsSecretNote">Settings are stored only in this browser. Do not import an untrusted settings file.</p> 333 <fieldset class="settings-fieldset"> 334 <legend data-i18n="settingsModeTitle">Mode</legend> 335 <label for="settingsMode"><span data-i18n="settingsMode">Runtime mode</span> 336 <select id="settingsMode"><option value="development" data-i18n="settingsDevelopment">Development</option><option value="production" data-i18n="settingsProduction">Production</option></select> 337 </label> 338 </fieldset> 339 <fieldset class="settings-fieldset"> 340 <legend data-i18n="settingsConnectionsTitle">Service connections</legend> 341 <div class="settings-grid"> 342 <label for="settingsAnalysisApi"><span data-i18n="settingsAnalysisApi">Analysis API URL</span><input id="settingsAnalysisApi" type="url" inputmode="url" placeholder="https://â¦/api/analyze"></label> 343 <label for="settingsPeakApi"><span data-i18n="settingsPeakApi">Peak detector API URL</span><input id="settingsPeakApi" type="url" inputmode="url" placeholder="https://â¦/api/peaks/detect"></label> 344 <label for="settingsReferenceApi"><span data-i18n="settingsReferenceApi">Reference search API URL</span><input id="settingsReferenceApi" type="url" inputmode="url" placeholder="http://127.0.0.1:8088/api/v1/search"></label> 345 <label for="settingsApiCredentials"><span data-i18n="settingsCredentials">API credentials</span><select id="settingsApiCredentials"><option value="omit" data-i18n="settingsCredentialsOmit">Do not send cookies</option><option value="include" data-i18n="settingsCredentialsInclude">Send cookies</option></select></label> 346 </div> 347 </fieldset> 348 <fieldset class="settings-fieldset"> 349 <legend data-i18n="settingsLlmTitle">LLM preferences</legend> 350 <p class="settings-note" data-i18n="settingsByokNote">A personal AI key is sent only to a server with BYOK explicitly enabled. It is never included in logs.</p> 351 <div class="settings-grid settings-grid--three"> 352 <label for="settingsLlmProvider"><span data-i18n="settingsProvider">Provider</span><select id="settingsLlmProvider"><option value="server" data-i18n="settingsProviderServer">Server default</option><option value="gemini">Gemini</option><option value="mistral">Mistral</option><option value="mock">Mock</option></select></label> 353 <label for="settingsLlmModel"><span data-i18n="settingsModel">Model</span><input id="settingsLlmModel" type="text" maxlength="256"></label> 354 <label for="settingsLlmApiKey"><span data-i18n="settingsLlmApiKey">Personal AI API key</span><input id="settingsLlmApiKey" type="password" autocomplete="off" maxlength="2048"></label> 355 </div> 356 </fieldset> 357 <fieldset class="settings-fieldset"> 358 <legend data-i18n="settingsTokensTitle">Access tokens</legend> 359 <p class="settings-note settings-token-note"><span data-i18n="settingsTokenHelp">Reference token is SERVICE_TOKEN from the reference-service .env. The analysis token is needed only if your own analysis API requires Bearer authentication.</span> <a href="https://github.com/ksider/ftir-reference-service" target="_blank" rel="noopener" data-i18n="settingsReferenceRepoLink">Reference service repository</a> · <a href="https://github.com/ksider/FTIR-file-Merger/tree/main/server" target="_blank" rel="noopener" data-i18n="settingsAnalysisRepoLink">
359Analysis server repository</a></p> 360 <div class="settings-grid"> 361 <label for="settingsAnalysisToken"><span data-i18n="settingsAnalysisToken">Optional Bearer token for analysis API</span><input id="settingsAnalysisToken" type="password" autocomplete="off" maxlength="2048"></label> 362 <label for="settingsReferenceToken"><span data-i18n="settingsReferenceToken">SERVICE_TOKEN from reference service .env</span><input id="settingsReferenceToken" type="password" autocomplete="off" maxlength="2048"></label> 363 </div> 364 </fieldset> 365 <div class="settings-actions"> 366 <button id="exportUserSettings" type="button" class="btn tonal" data-i18n="settingsExport">Export without keys</button> 367 <button id="exportUserSettingsWithSecrets" type="button" class="btn tonal" data-i18n="settingsExportSecrets">Export encrypted keys</button> 368 <button id="importUserSettings" type="button" class="btn tonal" data-i18n="settingsImport">Import settings</button> 369 <button id="resetUserSettings" type="button" class="btn danger" data-i18n="settingsReset">Reset profile</button> 370 <button type="submit" class="btn primary" data-i18n="settingsSave">Save and reload</button> 371 </div> 372 </form> 373 </dialog> 374 <footer class="footer surface"> 375 <div class="footer-links"> 376 <a id="footerSite" href="#" rel="noopener" target="_blank">nikolaysemenov.ru</a> 377 <span class="dot">â¢</span> 378 <a id="footerGithub" href="#" rel="noopener" target="_blank">GitHub</a> 379 <span class="dot">â¢</span> 380 <a id="footerCoffee" href="#" rel="noopener" target="_blank">Сoindrop</a> 381 </div> 382 </footer> 383 </div> 384 385
385<script src="./config.js"></script>
385 386
386<script src="./settings.js"></script>
386 387
387<script src="./peak-db.js"></script>
387 388
388<script src="./app.js"></script>
388 389 390 <!-- Google tag (gtag.js) -->
vendor: 64 bytes, lines 390-391
390 391<script async src="https://www.googletagmanager.com/gtag/js?id=
391G-X25WXQ0818
vendor: 12 bytes, line 391
391"></script>
392<script> 393
vendor: 134 bytes, lines 393-397
393window.dataLayer = window.dataLayer || []; 394 function gtag(){dataLayer.push(arguments);} 395 gtag('js', new Date()); 396 397 gtag('config', '
397G-X25WXQ0818
vendor: 4 bytes, line 397
397');
398</script>
vendor: 1 bytes, line 398
398
399<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v4bc70e2c01a94c73b74392e4234840661791215815920" integrity="sha512-L0ha0OXavK/8okipN9F8BtP84dg9DUhPERbBXzwI6dgTA55d2+yweo3pn5CSFYs45/r8md2+xvUPtTdvTNRfjA==" data-cf-beacon='{"version":"2024.11.0","token":"f2ad2398c11046f08e3d89180cb7f86c","r":1,"spa":2}' crossorigin="anonymous"></script>
399 400</body> 401</html>
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.