1/* ============================================================================= 2 apikey.js · API Key é ç½®é¢æ¿ 3 ----------------------------------------------------------------------------- 4 访客èªå¤ Keyï¼åå è§ config.js 顶鍿³¨éï¼ãè¿ä¸ªæ¨¡å管两件äºï¼ 5 1. openApiKeyDialog() ââ 左侧ãAPI é ç½®ãç¹å¼çé£ä¸ªæ¡ï¼å¡« Keyãæ ¡éªãä¿å 6 2. ensureKey() ââ åä½åçå®å«ï¼æ²¡é Key 就弹æç¤ºå¹¶ç»ä¸ä¸ªç´è¾¾æé® 7 8 Key åªåè¿ localStorageï¼ä¸åå¾é¤ api.stepfun.com ä¹å¤çä»»ä½å°æ¹ã 9 ============================================================================= */ 10 11import { API, getKey, setKey, clearKey, maskKey, needsUserKey } from './config.js'; 12import { verifyKey } from './api.js'; 13import { t } from './i18n.js'; 14import { modal, icon, esc, toast } from './ui.js'; 15 16/* é ç½®åçååæ¶éç¥çé¢ï¼åä½é¡µçæé®ææ¡/é¢åº¦è¡è¦è·çåï¼ */ 17const listeners = new Set(); 18export const onKeyChange = fn => { listeners.add(fn); return () => listeners.delete(fn); }; 19const emit = () => listeners.forEach(fn => { try { fn(); } catch { /* å个订é è åºéä¸è¯¥æå®å ¶å® */ } }); 20 21/* ââ é ç½®é¢æ¿ âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 22export function openApiKeyDialog() { 23 const existing = getKey(); 24 25 const { host, close } = modal(` 26 <h3 id="mdlT">${esc(t('key.title'))}</h3> 27 28 <div class="key-promo"> 29 <span class="key-promo-txt">${esc(t('key.promo'))}</span> 30 <button type="button" class="btn btn-quiet btn-sm" data-a="go"> 31 ${esc(t('key.goPlatform'))}${icon('i-arrow', 'icon-sm', 2)} 32 </button> 33 </div> 34 35 <label class="field key-field"> 36 <span class="field-label">${esc(t('key.label'))}</span> 37 <span class="key-input-wrap"> 38 <input class="input" id="keyInput" type="password" spellcheck="false" 39 autocomplete="off" autocapitalize="off" placeholder="${esc(t('key.placeholder'))}"> 40 <button type="button" class="key-eye" data-a="peek" 41 aria-label="${esc(t('key.show'))}">${icon('i-eye', 'icon-sm', 1.9)}</button> 42 </span> 43 </label> 44 45 ${existing ? `<p class="key-cur">${esc(t('key.current'))} <code>${esc(maskKey(existing))}</code></p>` : ''} 46 <p class="key-note">${esc(t('key.privacy'))}</p> 47 <p class="key-status" id="keyStatus" role="status" aria-live="polite" hidden></p> 48 49 <div class="dlg-foot"> 50 ${existing ? `<button type="button" class="btn btn-ghost" data-a="clear">${esc(t('key.clear'))}</button>` : ''} 51 <button type="button" class="btn btn-ghost" data-a="cancel">${esc(t('confirm.cancel'))}</button> 52 <button type="button" class="btn btn-primary" data-a="save">${esc(t('key.save'))}</button> 53 </div>`); 54 55 const $ = s => host.querySelector(s); 56 const input = $('#keyInput'); 57 const status = $('#keyStatus'); 58 const saveBtn = $('[data-a="save"]'); 59 60 const show = (msg, kind) => { 61 status.hidden = false; 62 status.textContent = msg; 63 status.className = 'key-status' + (kind ? ' ' + kind : ''); 64 }; 65 66 // å·²æ Key æ¶é¢å¡«ï¼æ¹ä¾¿ç´æ¥æ¹ï¼ç¨ password åé¿å è©çª¥ 67 if (existing) input.value = existing; 68 69 $('[data-a="go"]').onclick = () => 70 window.open(API.PLATFORM_URL, '_blank', 'noopener,noreferrer'); 71 72 $('[data-a="peek"]').onclick = e => { 73 const b = e.currentTarget; 74 const showing = input.type === 'text'; 75 input.type = showing ? 'password' : 'text'; 76 b.setAttribute('aria-label', t(showing ? 'key.show' : 'key.hide')); 77 b.classList.toggle('on', !showing); 78 }; 79 80 $('[data-a="cancel"]').onclick = close; 81 82 host.querySelector('[data-a="clear"]')?.addEventListener('click', () => { 83 clearKey(); 84 emit(); 85 toast(t('key.cleared')); 86 close(); 87 }); 88 89 let busy = false; 90 const submit = async () => { 91 if (busy) return; 92 const k = input.value.trim(); 93 if (!k) { show(t('key.err.key_empty'), 'err'); input.focus(); return; } 94 95 busy = true; 96 saveBtn.disabled = true; 97 show(t('key.verifying'), 'busy'); 98 99 const r = await verifyKey(k); 100 busy = false;
101 saveBtn.disabled = false; 102 103 if (!r.ok) { 104 // æ ¡éªæ²¡è¿å°±ä¸è½ç ââ åä¸ä¸ªå Key åªä¼è®©å颿¯æ¬¡çæé½å¤±è´¥ 105 show(t('key.err.' + r.code) + (r.message ? `ï¼${r.message}ï¼` : ''), 'err'); 106 input.focus(); 107 input.select(); 108 return; 109 } 110 setKey(k); 111 emit(); 112 show(t('key.ok'), 'ok'); 113 toast(t(r.code === 'key_ok_throttled' ? 'key.savedThrottled' : 'key.saved')); 114 setTimeout(close, 600); // è®©ç¨æ·çå°é£ä¸è¡"å¯ç¨"åå ³ 115 }; 116 117 saveBtn.onclick = submit; 118 input.addEventListener('keydown', e => { 119 if (e.key === 'Enter') { e.preventDefault(); submit(); } 120 }); 121 122 input.focus(); 123 input.select(); 124} 125 126/* ââ åä½åçå®å« âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 127 128/** 129 * æ Key è¿å trueï¼æ²¡æåå¼¹æç¤ºï¼å¸¦ç´è¾¾é ç½®çæé®ï¼å¹¶è¿å falseã 130 * proxy 模å¼ä¸é¢åº¦ç±å¹³å°ç»ä¸ä¾ç»ï¼è®¿å®¢ä¸éè¦èªå¤ Keyï¼ç´æ¥æ¾è¡ã 131 */ 132export function ensureKey() { 133 if (!needsUserKey() || getKey()) return true; 134 135 const { host, close } = modal(` 136 <h3 id="mdlT">${esc(t('key.needTitle'))}</h3> 137 <p>${esc(t('key.needBody'))}</p> 138 <div class="dlg-foot"> 139 <button type="button" class="btn btn-ghost" data-a="later">${esc(t('key.later'))}</button> 140 <button type="button" class="btn btn-primary" data-a="setup"> 141 ${esc(t('key.goSetup'))}${icon('i-arrow', 'icon-sm', 2)} 142 </button> 143 </div>`); 144 145 host.querySelector('[data-a="later"]').onclick = close; 146 host.querySelector('[data-a="setup"]').onclick = () => { 147 close(); 148 // çä¸ä¸å±é®ç½©æ·¡åºåå¼ï¼é¿å ä¸¤å± scrim å å¨ä¸èµ·éªä¸ä¸ 149 setTimeout(openApiKeyDialog, 210); 150 }; 151 host.querySelector('[data-a="setup"]').focus(); 152 return false; 153}
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.