Spaces:
Running
Running
| <html lang="zh-TW"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>GitHub 繁中快搜</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script>tailwind.config={darkMode:"class"}</script> | |
| <!-- Google tag (gtag.js) --> | |
| <script async src="https://www.googletagmanager.com/gtag/js?id=G-4M6KQ8KSY5"></script> | |
| <script> | |
| window.dataLayer = window.dataLayer || []; | |
| function gtag(){dataLayer.push(arguments);} | |
| gtag('js', new Date()); | |
| gtag('config', 'G-4M6KQ8KSY5'); | |
| </script> | |
| <style> | |
| body{font-family:'Inter',sans-serif;background:#020817;color:#f1f5f9;} | |
| @keyframes shimmer{0%{background-position:-800px 0}100%{background-position:800px 0}} | |
| .sk{animation:shimmer 1.5s infinite linear;background:linear-gradient(90deg,#1e293b 25%,#334155 50%,#1e293b 75%);background-size:800px 100%;border-radius:4px;} | |
| @keyframes fi{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}} | |
| .fi{animation:fi .25s ease forwards;} | |
| .card:hover{border-color:#6366f1;} | |
| </style> | |
| </head> | |
| <body class="dark min-h-screen"> | |
| <!-- HEADER --> | |
| <header class="sticky top-0 z-40 border-b border-slate-800 bg-slate-950/95 backdrop-blur"> | |
| <div class="max-w-6xl mx-auto px-4 h-14 flex items-center justify-between"> | |
| <div class="flex items-center gap-3"> | |
| <div class="bg-indigo-600 rounded-lg p-1.5"> | |
| <svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> | |
| <path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65S9 17.23 9 18v4"/> | |
| <path d="M9 18c-4.51 2-5-2-7-2"/> | |
| </svg> | |
| </div> | |
| <div> | |
| <h1 class="font-bold text-base flex items-center gap-2"> | |
| GitHub 繁中快搜 | |
| <span class="text-[10px] bg-indigo-500/20 text-indigo-400 border border-indigo-500/30 px-2 py-0.5 rounded-full">AI 版</span> | |
| </h1> | |
| <p class="text-[11px] text-slate-500 hidden sm:block">輸入中文需求 → 英文關鍵字 → GitHub 精準搜尋</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center gap-2"> | |
| <button id="btn-favs" onclick="toggleFavsView()" class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-amber-500/10 hover:bg-amber-500/20 border border-amber-500/30 text-amber-300 text-sm transition"> | |
| ⭐ 我的收藏 (<span id="fav-count">0</span>) | |
| </button> | |
| <select id="text-size" onchange="changeTextSize(this.value)" class="bg-slate-800 border border-slate-700 text-slate-300 text-sm rounded-lg px-2 py-1.5 focus:outline-none focus:border-indigo-500"> | |
| <option value="100%">大小: 100% (小)</option> | |
| <option value="150%">大小: 150% (中)</option> | |
| <option value="200%">大小: 200% (大)</option> | |
| </select> | |
| <button | |
| id="btn-settings" | |
| onclick="openModal()" | |
| class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 border border-slate-700 text-slate-300 text-sm transition" | |
| > | |
| <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> | |
| <circle cx="12" cy="12" r="3"/> | |
| <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/> | |
| </svg> | |
| 設定 | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- MAIN --> | |
| <main class="max-w-6xl mx-auto px-4 py-8 flex flex-col gap-6"> | |
| <!-- Search Panel --> | |
| <section class="bg-slate-900 rounded-2xl border border-slate-800 p-5"> | |
| <form id="search-form" onsubmit="handleSubmit(event)" class="flex flex-col gap-4"> | |
| <div class="relative flex items-center"> | |
| <svg class="w-4 h-4 absolute left-4 text-slate-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> | |
| <circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/> | |
| </svg> | |
| <input | |
| id="search-query" | |
| type="text" | |
| autocomplete="off" | |
| placeholder="輸入需求,例如:自動整理信件的 Python 工具、好用的 CLI 終端工具..." | |
| class="w-full pl-11 pr-28 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:border-indigo-500 text-slate-100 placeholder-slate-600 text-sm transition" | |
| /> | |
| <button | |
| type="submit" | |
| class="absolute right-2 px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white font-semibold rounded-lg text-sm transition" | |
| > | |
| 智慧搜尋 | |
| </button> | |
| </div> | |
| <!-- History --> | |
| <div id="history-bar" class="hidden flex-wrap items-center gap-2"> | |
| <span class="text-xs text-slate-600">最近:</span> | |
| <div id="history-chips" class="flex flex-wrap gap-1.5"></div> | |
| </div> | |
| <!-- Filters --> | |
| <div class="grid grid-cols-2 sm:grid-cols-5 gap-2"> | |
| <select id="f-lang" class="bg-slate-950 border border-slate-700 text-slate-300 text-xs rounded-lg px-3 py-2 focus:outline-none focus:border-indigo-500"> | |
| <option value="">全部語言</option> | |
| <option value="python">Python</option> | |
| <option value="javascript">JavaScript</option> | |
| <option value="typescript">TypeScript</option> | |
| <option value="rust">Rust</option> | |
| <option value="go">Go</option> | |
| <option value="cpp">C++</option> | |
| <option value="java">Java</option> | |
| <option value="shell">Shell</option> | |
| </select> | |
| <select id="f-sort" class="bg-slate-950 border border-slate-700 text-slate-300 text-xs rounded-lg px-3 py-2 focus:outline-none focus:border-indigo-500"> | |
| <option value="stars">最多 Stars</option> | |
| <option value="forks">最多 Forks</option> | |
| <option value="updated">最近更新</option> | |
| </select> | |
| <select id="f-stars" class="bg-slate-950 border border-slate-700 text-slate-300 text-xs rounded-lg px-3 py-2 focus:outline-none focus:border-indigo-500"> | |
| <option value="">不限 Stars</option> | |
| <option value="50">> 50</option> | |
| <option value="100">> 100</option> | |
| <option value="500">> 500</option> | |
| <option value="1000">> 1,000</option> | |
| <option value="5000">> 5,000</option> | |
| </select> | |
| <select id="f-time" class="bg-slate-950 border border-slate-700 text-slate-300 text-xs rounded-lg px-3 py-2 focus:outline-none focus:border-indigo-500"> | |
| <option value="">不限時間</option> | |
| <option value="30">最近 1 個月</option> | |
| <option value="180">最近半年</option> | |
| <option value="365">最近一年</option> | |
| </select> | |
| <select id="f-pp" class="bg-slate-950 border border-slate-700 text-slate-300 text-xs rounded-lg px-3 py-2 focus:outline-none focus:border-indigo-500"> | |
| <option value="12">12 筆/頁</option> | |
| <option value="24">24 筆/頁</option> | |
| <option value="30">30 筆/頁</option> | |
| </select> | |
| </div> | |
| </form> | |
| </section> | |
| <!-- Status Bar --> | |
| <div id="status-bar" style="display:none" class="bg-indigo-950/40 border border-indigo-800/40 rounded-xl p-4 flex flex-col gap-3"> | |
| <div class="flex items-center gap-3 text-indigo-300 text-sm"> | |
| <div id="spinner" style="display:none" class="w-4 h-4 border-2 border-indigo-400 border-t-transparent rounded-full animate-spin"></div> | |
| <span id="status-text">處理中...</span> | |
| </div> | |
| <div id="query-row" style="display:none" class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2"> | |
| <div class="flex-1"> | |
| <div class="text-[11px] text-indigo-500 mb-1">搜尋關鍵字(可修改後重搜):</div> | |
| <input id="query-display" type="text" class="w-full bg-transparent text-indigo-200 text-xs font-mono px-2 py-1 rounded border border-indigo-800/50 focus:outline-none focus:border-indigo-500"/> | |
| </div> | |
| <button onclick="reSearch()" class="px-3 py-1.5 bg-indigo-800/40 hover:bg-indigo-700/50 text-indigo-300 border border-indigo-700/40 rounded-lg text-xs transition whitespace-nowrap"> | |
| 以此語法重搜 | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Results --> | |
| <section> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h2 class="font-semibold text-slate-300"> | |
| 搜尋結果 | |
| <span id="result-count" style="display:none" class="text-xs text-slate-500 bg-slate-900 border border-slate-700 px-2 py-0.5 rounded ml-2 font-normal"></span> | |
| </h2> | |
| </div> | |
| <div id="empty-state" class="border border-dashed border-slate-800 rounded-2xl p-16 text-center"> | |
| <svg class="w-10 h-10 mx-auto text-slate-700 mb-3" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"> | |
| <circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/><path d="M11 8v6M8 11h6"/> | |
| </svg> | |
| <p id="empty-title" class="text-slate-500 font-medium">尚未搜尋</p> | |
| <p id="empty-desc" class="text-slate-600 text-sm mt-1">在上方輸入中文或英文需求,即可搜尋 GitHub 開源工具</p> | |
| </div> | |
| <div id="results-grid" style="display:none" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 pb-6"></div> | |
| <div id="pagination" style="display:none" class="flex items-center justify-center gap-4 pt-4"> | |
| <button onclick="changePage(-1)" id="btn-prev" class="px-4 py-2 bg-slate-900 border border-slate-700 text-slate-300 rounded-lg text-sm hover:bg-slate-800 disabled:opacity-40 disabled:cursor-not-allowed transition">← 上一頁</button> | |
| <span id="page-info" class="text-sm text-slate-500"></span> | |
| <button onclick="changePage(1)" id="btn-next" class="px-4 py-2 bg-slate-900 border border-slate-700 text-slate-300 rounded-lg text-sm hover:bg-slate-800 disabled:opacity-40 disabled:cursor-not-allowed transition">下一頁 →</button> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- FOOTER --> | |
| <footer class="border-t border-slate-800 bg-slate-950/50 mt-12 py-6 mt-auto"> | |
| <div class="max-w-6xl mx-auto px-4 text-center flex flex-col gap-1.5"> | |
| <p class="text-xs text-slate-500"> | |
| © 2026 GitHub 繁中快搜 | 專為精準開源搜尋打造 | |
| </p> | |
| <p class="text-[0.7rem] text-slate-600"> | |
| 開發者:Lowking Nowbucyang | |
| </p> | |
| </div> | |
| </footer> | |
| <!-- Settings Modal --> | |
| <div id="modal" style="display:none;position:fixed;inset:0;z-index:50;background:rgba(0,0,0,.7);align-items:center;justify-content:center;padding:1rem;"> | |
| <div class="bg-slate-900 border border-slate-700 rounded-2xl w-full max-w-md p-6 shadow-2xl flex flex-col gap-5"> | |
| <div class="flex items-center justify-between"> | |
| <h3 class="font-bold">API 設定</h3> | |
| <button onclick="closeModal()" class="text-slate-500 hover:text-slate-300 text-2xl leading-none">×</button> | |
| </div> | |
| <div class="space-y-4"> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <label class="text-xs text-slate-400 font-medium">Gemini API Key(建議填寫)</label> | |
| <a href="https://aistudio.google.com/app/apikey" target="_blank" class="text-xs text-indigo-400 hover:underline">取得 →</a> | |
| </div> | |
| <input id="input-gemini" type="password" placeholder="AIzaSyxxxxxxxxxxxx" | |
| class="w-full px-3 py-2 bg-slate-950 border border-slate-700 rounded-lg text-sm text-slate-200 focus:outline-none focus:border-indigo-500"/> | |
| <p class="text-[11px] text-slate-600 mt-1">填寫以啟用專案卡片上的 ✨ AI 專案分析報告功能</p> | |
| </div> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <label class="text-xs text-slate-400 font-medium">GitHub Token(選填)</label> | |
| <a href="https://github.com/settings/tokens" target="_blank" class="text-xs text-indigo-400 hover:underline">取得 →</a> | |
| </div> | |
| <input id="input-gh-token" type="password" placeholder="ghp_xxxxxxxxxxxx" | |
| class="w-full px-3 py-2 bg-slate-950 border border-slate-700 rounded-lg text-sm text-slate-200 focus:outline-none focus:border-indigo-500"/> | |
| <p class="text-[11px] text-slate-600 mt-1">不填時每小時限制 60 次,填入後提升至 5000 次</p> | |
| </div> | |
| </div> | |
| <button onclick="saveSettings()" class="w-full py-2.5 bg-indigo-600 hover:bg-indigo-500 text-white font-semibold rounded-lg text-sm transition"> | |
| 儲存設定 | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- AI Report Modal --> | |
| <div id="ai-modal" style="display:none;position:fixed;inset:0;z-index:60;background:rgba(0,0,0,.7);align-items:center;justify-content:center;padding:1rem;"> | |
| <div class="bg-slate-900 border border-slate-700 rounded-2xl w-full max-w-2xl max-h-[85vh] flex flex-col shadow-2xl overflow-hidden"> | |
| <!-- Header --> | |
| <div class="flex items-center justify-between p-4 border-b border-slate-800 bg-slate-950/50"> | |
| <h3 class="font-bold flex items-center gap-2 text-indigo-400"> | |
| ✨ AI 專案分析報告 | |
| <span id="ai-repo-name" class="text-slate-300 font-normal ml-2"></span> | |
| </h3> | |
| <div class="flex items-center gap-4"> | |
| <button id="btn-download-word" onclick="downloadWord()" style="display:none;" class="px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-white rounded text-xs transition flex items-center gap-1.5"> | |
| <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg> | |
| 下載 WORD | |
| </button> | |
| <button onclick="closeAiModal()" class="text-slate-500 hover:text-slate-300 text-2xl leading-none">×</button> | |
| </div> | |
| </div> | |
| <!-- Body --> | |
| <div class="p-6 overflow-y-auto custom-scrollbar flex-1 flex flex-col" style="background-color: #020817;"> | |
| <!-- Step 1: Loading README --> | |
| <div id="ai-step-fetch" style="display:flex;" class="flex-col items-center justify-center py-10 gap-4"> | |
| <div class="w-6 h-6 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin"></div> | |
| <p class="text-slate-400 text-sm">正在抓取 GitHub README 檔案...</p> | |
| </div> | |
| <!-- Step 2: Confirm Tokens --> | |
| <div id="ai-step-confirm" style="display:none;" class="flex-col items-center text-center py-8 gap-4"> | |
| <div class="bg-indigo-950/50 border border-indigo-500/30 rounded-xl p-5 max-w-sm w-full mx-auto"> | |
| <svg class="w-8 h-8 text-indigo-400 mx-auto mb-3" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg> | |
| <h4 class="text-slate-200 font-medium mb-1">準備就緒</h4> | |
| <p id="ai-token-estimate" class="text-slate-400 text-xs mb-4">README 長度:- 字元(預估約 - tokens)</p> | |
| <button id="btn-confirm-ai" class="w-full py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-sm transition"> | |
| 確定消耗 Token 並分析 | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Step 3: Analyzing --> | |
| <div id="ai-step-analyzing" style="display:none;" class="flex-col items-center justify-center py-10 gap-4"> | |
| <div class="w-6 h-6 border-2 border-fuchsia-500 border-t-transparent rounded-full animate-spin"></div> | |
| <p class="text-fuchsia-400 text-sm animate-pulse">Gemini 正在閱讀並撰寫報告中...</p> | |
| </div> | |
| <!-- Step 4: Report Result --> | |
| <div id="ai-step-result" style="display:none;" class="flex-col gap-4"> | |
| <style> | |
| .ai-markdown { color: #cbd5e1; font-size: 19px; line-height: 1.7; } | |
| .ai-markdown h3 { color: #818cf8; font-size: 21px; font-weight: 600; margin-top: 1em; margin-bottom: 0.5em; border-bottom: 1px solid #1e293b; padding-bottom: 4px; } | |
| .ai-markdown ul { list-style-type: disc; padding-left: 1.5em; margin-bottom: 1em; } | |
| .ai-markdown li { margin-bottom: 0.25em; } | |
| .ai-markdown p { margin-bottom: 1em; } | |
| .ai-markdown strong { color: #f8fafc; font-weight: 600; } | |
| </style> | |
| <div id="ai-report-content" class="ai-markdown"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Toast --> | |
| <div id="toast" style="display:none;position:fixed;bottom:1.25rem;right:1.25rem;z-index:50;" class="bg-slate-800 border border-slate-700 text-slate-100 px-4 py-3 rounded-xl shadow-2xl text-sm"> | |
| <span id="toast-msg"></span> | |
| </div> | |
| <script> | |
| // === Global State === | |
| var GH_TOKEN = '', GEMINI_KEY = '', HISTORY = [], CUR_PAGE = 1, TOTAL = 0, LAST_QUERY = '', BUSY = false; | |
| try { | |
| GH_TOKEN = localStorage.getItem('gh_tok') || ''; | |
| GEMINI_KEY = localStorage.getItem('gm_key') || ''; | |
| HISTORY = JSON.parse(localStorage.getItem('gh_hist') || '[]'); | |
| } catch(e) {} | |
| // === Modal === | |
| function openModal() { | |
| var elGemini = document.getElementById('input-gemini'); | |
| if (elGemini) elGemini.value = GEMINI_KEY; | |
| var elGh = document.getElementById('input-gh-token'); | |
| if (elGh) elGh.value = GH_TOKEN; | |
| var elModal = document.getElementById('modal'); | |
| if (elModal) elModal.style.display = 'flex'; | |
| } | |
| function closeModal() { | |
| var elModal = document.getElementById('modal'); | |
| if (elModal) elModal.style.display = 'none'; | |
| } | |
| function saveSettings() { | |
| var elGemini = document.getElementById('input-gemini'); | |
| if (elGemini) GEMINI_KEY = elGemini.value.trim(); | |
| var elGh = document.getElementById('input-gh-token'); | |
| if (elGh) GH_TOKEN = elGh.value.trim(); | |
| try { | |
| localStorage.setItem('gm_key', GEMINI_KEY); | |
| if (elGh) localStorage.setItem('gh_tok', GH_TOKEN); | |
| } catch(e) {} | |
| closeModal(); | |
| toast('已儲存設定!'); | |
| } | |
| // === Toast === | |
| var _toastTimer; | |
| function toast(msg, err) { | |
| var el = document.getElementById('toast'); | |
| document.getElementById('toast-msg').textContent = msg; | |
| el.style.background = err ? '#450a0a' : '#1e293b'; | |
| el.style.borderColor = err ? '#991b1b' : '#334155'; | |
| el.style.display = 'block'; | |
| clearTimeout(_toastTimer); | |
| _toastTimer = setTimeout(function(){ el.style.display='none'; }, 3000); | |
| } | |
| // === History === | |
| function addHistory(term) { | |
| HISTORY = HISTORY.filter(function(t){ return t !== term; }); | |
| HISTORY.unshift(term); | |
| if (HISTORY.length > 5) HISTORY = HISTORY.slice(0, 5); | |
| try { localStorage.setItem('gh_hist', JSON.stringify(HISTORY)); } catch(e) {} | |
| renderHistory(); | |
| } | |
| function renderHistory() { | |
| var bar = document.getElementById('history-bar'); | |
| if (!HISTORY.length) { bar.style.display = 'none'; return; } | |
| bar.style.display = 'flex'; | |
| var chips = document.getElementById('history-chips'); | |
| chips.innerHTML = HISTORY.map(function(t, i) { | |
| return '<button type="button" onclick="pickHistory(' + i + ')" style="padding:2px 10px;background:#1e293b;border:1px solid #334155;color:#94a3b8;border-radius:999px;font-size:11px;cursor:pointer;">' + esc(t) + '</button>'; | |
| }).join(''); | |
| } | |
| function pickHistory(i) { | |
| document.getElementById('search-query').value = HISTORY[i]; | |
| document.getElementById('search-form').dispatchEvent(new Event('submit')); | |
| } | |
| // === Utilities === | |
| function esc(s) { | |
| return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); | |
| } | |
| function isCJK(t) { return /[\u4e00-\u9fff]/.test(t); } | |
| function daysAgo(d) { var dt=new Date(); dt.setDate(dt.getDate()-d); return dt.toISOString().split('T')[0]; } | |
| function fmt(n) { return n>999?(n/1000).toFixed(1)+'k':n; } | |
| function setStatus(msg, spin) { | |
| document.getElementById('status-bar').style.display = 'flex'; | |
| document.getElementById('status-text').textContent = msg; | |
| document.getElementById('spinner').style.display = spin ? 'block' : 'none'; | |
| } | |
| function showQueryRow(q) { | |
| document.getElementById('query-display').value = q; | |
| document.getElementById('query-row').style.display = 'flex'; | |
| } | |
| // === Search Submit === | |
| function handleSubmit(e) { | |
| e.preventDefault(); | |
| var raw = document.getElementById('search-query').value.trim(); | |
| if (!raw || BUSY) return; | |
| addHistory(raw); | |
| CUR_PAGE = 1; | |
| doSearch(raw); | |
| } | |
| function reSearch() { | |
| var q = document.getElementById('query-display').value.trim(); | |
| if (!q || BUSY) return; | |
| LAST_QUERY = q; | |
| CUR_PAGE = 1; | |
| fetchAndRender(); | |
| } | |
| function changePage(d) { | |
| if (BUSY) return; | |
| CUR_PAGE += d; | |
| fetchAndRender(); | |
| window.scrollTo({top:0,behavior:'smooth'}); | |
| } | |
| function fallbackTranslate(raw) { | |
| return fetch('https://api.mymemory.translated.net/get?q='+encodeURIComponent(raw.slice(0,200))+'&langpair=zh-TW|en') | |
| .then(function(r){ return r.ok?r.json():null; }) | |
| .then(function(d){ return (d&&d.responseData&&d.responseData.translatedText)?d.responseData.translatedText.replace(/[^\w\s]/g,' ').trim()||raw : raw; }) | |
| .catch(function(){ return raw; }); | |
| } | |
| function generateQuery(raw) { | |
| if (!isCJK(raw)) return Promise.resolve(raw); | |
| return fallbackTranslate(raw); | |
| } | |
| function batchTranslate(items) { | |
| if (!GEMINI_KEY || items.length === 0) { | |
| items.forEach(function(item) { item.zhDesc = item.description || ''; }); | |
| return Promise.resolve(items); | |
| } | |
| var mapObj = {}; | |
| items.forEach(function(item, i) { | |
| if (item.description) mapObj[i] = item.description; | |
| }); | |
| if (Object.keys(mapObj).length === 0) { | |
| items.forEach(function(item) { item.zhDesc = ''; }); | |
| return Promise.resolve(items); | |
| } | |
| var prompt = "Translate the following JSON object's string values into Traditional Chinese (zh-TW). Keep technical terms in English. Return ONLY a valid JSON object with the exact same keys and the translated strings as values. Do NOT return markdown formatting.\\n\\n" + JSON.stringify(mapObj); | |
| return fetch('https://generativelanguage.googleapis.com/v1beta/models/gemini-3.6-flash:generateContent?key='+GEMINI_KEY, { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({contents:[{parts:[{text:prompt}]}]}) | |
| }) | |
| .then(function(r) { return r.ok ? r.json() : null; }) | |
| .then(function(d) { | |
| var out = (d && d.candidates && d.candidates[0] && d.candidates[0].content) ? d.candidates[0].content.parts[0].text.trim() : ""; | |
| out = out.replace(/^```json/i, '').replace(/^```/, '').replace(/```$/, '').trim(); | |
| var translated = {}; | |
| try { translated = JSON.parse(out); } catch(e) { console.error("Batch translate parse error", e, out); } | |
| items.forEach(function(item, i) { | |
| item.zhDesc = translated[i] || item.description || ''; | |
| }); | |
| return items; | |
| }) | |
| .catch(function(e) { | |
| console.error("Batch translate error:", e); | |
| items.forEach(function(item) { item.zhDesc = item.description || ''; }); | |
| return items; | |
| }); | |
| } | |
| // === Main Search Flow === | |
| function doSearch(raw) { | |
| document.getElementById('empty-state').style.display = 'block'; | |
| document.getElementById('results-grid').style.display = 'none'; | |
| document.getElementById('pagination').style.display = 'none'; | |
| document.getElementById('result-count').style.display = 'none'; | |
| document.getElementById('query-row').style.display = 'none'; | |
| setStatus('正在翻譯成英文關鍵字...', true); | |
| generateQuery(raw).then(function(q) { | |
| LAST_QUERY = q; | |
| showQueryRow(q); | |
| return fetchAndRender(); | |
| }); | |
| } | |
| function fetchAndRender() { | |
| if (BUSY) return Promise.resolve(); | |
| BUSY = true; | |
| setStatus('Searching GitHub...', true); | |
| document.getElementById('empty-state').style.display = 'none'; | |
| document.getElementById('pagination').style.display = 'none'; | |
| var grid = document.getElementById('results-grid'); | |
| grid.innerHTML = [0,1,2,3,4,5].map(function(){ | |
| return '<div style="background:#0f172a;border:1px solid #1e293b;border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:12px">' | |
| + '<div class="sk" style="height:16px;width:60%"></div>' | |
| + '<div class="sk" style="height:12px;width:100%"></div>' | |
| + '<div class="sk" style="height:12px;width:80%"></div>' | |
| + '</div>'; | |
| }).join(''); | |
| grid.style.display = 'grid'; | |
| var q = document.getElementById('query-display').value.trim() || LAST_QUERY; | |
| var lang = document.getElementById('f-lang').value; | |
| var sort = document.getElementById('f-sort').value; | |
| var star = document.getElementById('f-stars').value; | |
| var time = document.getElementById('f-time').value; | |
| var pp = document.getElementById('f-pp').value; | |
| if (lang) q += ' language:'+lang; | |
| if (star) q += ' stars:>'+star; | |
| if (time) q += ' pushed:>'+daysAgo(parseInt(time)); | |
| var url = 'https://api.github.com/search/repositories?q='+encodeURIComponent(q)+'&sort='+sort+'&order=desc&per_page='+pp+'&page='+CUR_PAGE; | |
| var headers = {'Accept':'application/vnd.github.v3+json'}; | |
| if (GH_TOKEN) headers['Authorization'] = 'token '+GH_TOKEN; | |
| return fetch(url, {headers:headers}) | |
| .then(function(r){ | |
| if (!r.ok){ | |
| if(r.status===403) throw new Error('GitHub API rate limit - please add a GitHub Token in Settings'); | |
| if(r.status===422) throw new Error('Search syntax error - please modify the query'); | |
| throw new Error('GitHub API error ('+r.status+')'); | |
| } | |
| return r.json(); | |
| }) | |
| .then(function(data){ | |
| TOTAL = data.total_count; | |
| var items = data.items||[]; | |
| var rc = document.getElementById('result-count'); | |
| rc.textContent = TOTAL.toLocaleString()+' results'; | |
| rc.style.display = 'inline'; | |
| if(!items.length){ | |
| grid.style.display='none'; | |
| document.getElementById('empty-state').style.display='block'; | |
| document.getElementById('empty-title').textContent='No results found'; | |
| document.getElementById('empty-desc').textContent='Try modifying the query or relaxing filters'; | |
| setStatus('Done - no results', false); | |
| BUSY=false; return; | |
| } | |
| setStatus('正在將結果翻譯為繁體中文...', true); | |
| return batchTranslate(items).then(function(translated) { | |
| setStatus('Done! '+TOTAL.toLocaleString()+' results found.', false); | |
| renderCards(translated); | |
| var totalPages = Math.ceil(TOTAL/parseInt(pp)); | |
| if(totalPages>1){ | |
| document.getElementById('page-info').textContent='Page '+CUR_PAGE+' / '+Math.min(totalPages,100); | |
| document.getElementById('btn-prev').disabled = CUR_PAGE<=1; | |
| document.getElementById('btn-next').disabled = CUR_PAGE>=totalPages||CUR_PAGE>=100; | |
| document.getElementById('pagination').style.display='flex'; | |
| } | |
| BUSY=false; | |
| }); | |
| }) | |
| .catch(function(err){ | |
| console.error(err); | |
| grid.style.display='none'; | |
| document.getElementById('empty-state').style.display='block'; | |
| document.getElementById('empty-title').textContent='Search failed'; | |
| document.getElementById('empty-desc').textContent=err.message; | |
| setStatus('Error', false); | |
| toast(err.message, true); | |
| BUSY=false; | |
| }); | |
| } | |
| // === Render Cards === | |
| function renderCards(items) { | |
| var grid = document.getElementById('results-grid'); | |
| grid.innerHTML = items.map(function(item){ | |
| var stars = fmt(item.stargazers_count); | |
| var forks = fmt(item.forks_count); | |
| var updated = new Date(item.updated_at).toLocaleDateString('zh-TW'); | |
| var topics = (item.topics||[]).slice(0,4).map(function(t){ | |
| return '<span style="font-size:10px;background:#1e293b;color:#94a3b8;border:1px solid #334155;padding:1px 6px;border-radius:999px;">'+esc(t)+'</span>'; | |
| }).join(''); | |
| var cloneUrl = item.clone_url; | |
| var owner = esc(item.owner.login); | |
| var repo = esc(item.name); | |
| return '<div class="fi" style="background:#0f172a;border:1px solid #1e293b;border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:10px;transition:border-color .2s;" onmouseover="this.style.borderColor=\'#6366f1\'" onmouseout="this.style.borderColor=\'#1e293b\'">' | |
| + '<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:8px;">' | |
| + '<div style="display:flex;align-items:center;gap:8px;min-width:0;">' | |
| + '<img src="'+esc(item.owner.avatar_url)+'" style="width:18px;height:18px;border-radius:50%;flex-shrink:0;" loading="lazy"/>' | |
| + '<a href="'+esc(item.html_url)+'" target="_blank" rel="noopener" style="font-weight:600;color:#f1f5f9;text-decoration:none;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;" onmouseover="this.style.color=\'#818cf8\'" onmouseout="this.style.color=\'#f1f5f9\'">'+esc(item.name)+'</a>' | |
| + '</div>' | |
| + (item.language?'<span style="font-size:10px;background:#1e293b;color:#94a3b8;border:1px solid #334155;padding:1px 8px;border-radius:4px;flex-shrink:0;">'+esc(item.language)+'</span>':'') | |
| + '</div>' | |
| + '<div style="font-size:11px;color:#475569;">'+esc(item.owner.login)+' • updated '+updated+'</div>' | |
| + '<p style="font-size:13px;color:#cbd5e1;line-height:1.6;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;">'+esc(item.zhDesc || 'No description provided.')+'</p>' | |
| + (item.description && item.zhDesc !== item.description ? '<details style="font-size:11px;"><summary style="color:#475569;cursor:pointer;">Original (EN)</summary><p style="color:#64748b;font-style:italic;margin-top:4px;">'+esc(item.description)+'</p></details>' : '') | |
| + (topics?'<div style="display:flex;flex-wrap:wrap;gap:4px;">'+topics+'</div>':'') | |
| + '<div style="display:flex;align-items:center;justify-content:space-between;padding-top:8px;border-top:1px solid #1e293b;margin-top:auto;">' | |
| + '<div style="display:flex;align-items:center;gap:12px;font-size:12px;color:#64748b;">' | |
| + '<span style="color:#f59e0b;font-weight:500;">★ '+stars+'</span>' | |
| + '<span>⑂ '+forks+'</span>' | |
| + '</div>' | |
| + '<div style="display:flex;gap:4px;">' | |
| + '<button onclick="openAiAnalysis(\''+owner+'\', \''+repo+'\')" style="padding:4px 8px;color:#818cf8;background:rgba(99,102,241,0.1);border:1px solid rgba(99,102,241,0.2);cursor:pointer;border-radius:4px;font-size:11px;font-weight:500;display:flex;align-items:center;gap:4px;" title="AI 分析報告" onmouseover="this.style.background=\'rgba(99,102,241,0.2)\'" onmouseout="this.style.background=\'rgba(99,102,241,0.1)\'">' | |
| + '✨ AI 分析' | |
| + '</button>' | |
| + '<button onclick="copyUrl(\'' + cloneUrl.replace(/'/g,"\\'") + '\')" style="padding:4px;color:#475569;background:none;border:none;cursor:pointer;border-radius:4px;" title="Copy Clone URL" onmouseover="this.style.color=\'#f1f5f9\';this.style.background=\'#1e293b\'" onmouseout="this.style.color=\'#475569\';this.style.background=\'none\'">' | |
| + '<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect width="14" height="14" x="8" y="8" rx="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>' | |
| + '</button>' | |
| + '<a href="'+esc(item.html_url)+'" target="_blank" rel="noopener" style="padding:4px;color:#475569;display:flex;border-radius:4px;" title="Open GitHub" onmouseover="this.style.color=\'#f1f5f9\';this.style.background=\'#1e293b\'" onmouseout="this.style.color=\'#475569\';this.style.background=\'none\'">' | |
| + '<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>' | |
| + '</a>' | |
| + '</div>' | |
| + '</div>' | |
| + '</div>'; | |
| }).join(''); | |
| } | |
| function copyUrl(url) { | |
| navigator.clipboard.writeText(url) | |
| .then(function(){ toast('Clone URL copied!'); }) | |
| .catch(function(){ toast('Copy failed', true); }); | |
| } | |
| // === AI Repo Analysis === | |
| var currentAiRepo = { owner: '', repo: '', readme: '' }; | |
| function openAiAnalysis(owner, repo) { | |
| if (!GEMINI_KEY) { | |
| toast('請先在「設定」中填寫 Gemini API Key', true); | |
| openModal(); | |
| return; | |
| } | |
| currentAiRepo.owner = owner; | |
| currentAiRepo.repo = repo; | |
| currentAiRepo.readme = ''; | |
| document.getElementById('ai-repo-name').textContent = owner + '/' + repo; | |
| // Show modal and reset state | |
| var modal = document.getElementById('ai-modal'); | |
| modal.style.display = 'flex'; | |
| document.getElementById('ai-step-fetch').style.display = 'flex'; | |
| document.getElementById('ai-step-confirm').style.display = 'none'; | |
| document.getElementById('ai-step-analyzing').style.display = 'none'; | |
| document.getElementById('ai-step-result').style.display = 'none'; | |
| document.getElementById('btn-download-word').style.display = 'none'; | |
| // Fetch README | |
| var headers = {'Accept': 'application/vnd.github.v3+json'}; | |
| if (GH_TOKEN) headers['Authorization'] = 'token ' + GH_TOKEN; | |
| fetch('https://api.github.com/repos/' + owner + '/' + repo + '/readme', {headers: headers}) | |
| .then(function(r) { | |
| if (!r.ok) { | |
| if (r.status === 404) throw new Error('這個專案沒有提供 README 文件,無法產生報告。'); | |
| throw new Error('無法取得 README (HTTP ' + r.status + ')'); | |
| } | |
| return r.json(); | |
| }) | |
| .then(function(data) { | |
| if (data.encoding === 'base64') { | |
| // Decode base64 UTF-8 string properly | |
| var bin = atob(data.content); | |
| var bytes = new Uint8Array(bin.length); | |
| for(var i=0; i<bin.length; i++) bytes[i] = bin.charCodeAt(i); | |
| currentAiRepo.readme = new TextDecoder('utf-8').decode(bytes); | |
| } else { | |
| throw new Error('未知的編碼格式'); | |
| } | |
| var chars = currentAiRepo.readme.length; | |
| var approxTokens = Math.ceil(chars / 3.5); | |
| document.getElementById('ai-step-fetch').style.display = 'none'; | |
| document.getElementById('ai-token-estimate').textContent = 'README 長度:' + chars.toLocaleString() + ' 字元(預估約消耗 ' + approxTokens.toLocaleString() + ' tokens)'; | |
| document.getElementById('ai-step-confirm').style.display = 'flex'; | |
| var btnConfirm = document.getElementById('btn-confirm-ai'); | |
| btnConfirm.onclick = runAiAnalysis; | |
| }) | |
| .catch(function(err) { | |
| document.getElementById('ai-step-fetch').style.display = 'none'; | |
| document.getElementById('ai-report-content').innerHTML = '<p class="text-red-400">' + err.message + '</p>'; | |
| document.getElementById('ai-step-result').style.display = 'flex'; | |
| }); | |
| } | |
| function closeAiModal() { | |
| document.getElementById('ai-modal').style.display = 'none'; | |
| } | |
| function runAiAnalysis() { | |
| if (!GEMINI_KEY) { | |
| closeAiModal(); | |
| openModal(); | |
| toast('🔑 請先設定 Gemini API Key 以啟用 AI 分析功能!', true); | |
| return; | |
| } | |
| var btnConfirm = document.getElementById('btn-confirm-ai'); | |
| if (btnConfirm) { | |
| btnConfirm.disabled = true; | |
| setTimeout(function() { btnConfirm.disabled = false; }, 3000); | |
| } | |
| document.getElementById('ai-step-confirm').style.display = 'none'; | |
| document.getElementById('ai-step-analyzing').style.display = 'flex'; | |
| var prompt = '你是一個專業的 AI 工程師。請閱讀以下 GitHub 專案的 README,並用繁體中文產生一份快速報告。\n\n' | |
| + '請包含以下三個段落:\n' | |
| + '<h3>一段話總結</h3>\n' | |
| + '<p>(一句話說明這個內容是什麼、解決什麼問題)</p>\n' | |
| + '<h3>核心功能與特點</h3>\n' | |
| + '<ul><li>(列出主要特點、架構或用途)</li></ul>\n' | |
| + '<h3>適用場景</h3>\n' | |
| + '<ul><li>(列出適合誰用、什麼時候用)</li></ul>\n\n' | |
| + '注意:請直接輸出帶有 h3, p, ul, li 標籤的 HTML 格式內容,不要使用 markdown 的 `**` 粗體或是 `##` 標題,也不要用 ``` 包裝。\n\n' | |
| + '專案名稱:' + currentAiRepo.owner + '/' + currentAiRepo.repo + '\n\n' | |
| + 'README 內容如下:\n' | |
| + (currentAiRepo.readme || '').substring(0, 3500); | |
| var payload = { | |
| contents: [{ parts: [{ text: prompt }] }], | |
| generationConfig: { | |
| maxOutputTokens: 2000 | |
| } | |
| }; | |
| var modelList = ['gemini-3.6-flash', 'gemini-2.0-flash', 'gemini-1.5-flash']; | |
| function tryModel(index) { | |
| if (index >= modelList.length) { | |
| throw new Error('Gemini API 觸發頻率限制 (429 Rate Limit):請至 Google AI Studio 檢查是否已綁定 Pay-As-You-Go 付費信用卡帳號,或稍候 1 分鐘再試。'); | |
| } | |
| var modelName = modelList[index]; | |
| var url = 'https://generativelanguage.googleapis.com/v1beta/models/' + modelName + ':generateContent?key=' + GEMINI_KEY; | |
| return fetch(url, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(payload) | |
| }).then(function(r) { | |
| if (!r.ok) { | |
| if (r.status === 429 || r.status === 404) { | |
| console.warn('Model ' + modelName + ' returned ' + r.status + ', trying next model...'); | |
| return tryModel(index + 1); | |
| } | |
| return r.text().then(function(errText) { | |
| throw new Error('Gemini API 錯誤 (' + r.status + '):' + errText.substring(0, 150)); | |
| }); | |
| } | |
| return r.json(); | |
| }); | |
| } | |
| tryModel(0) | |
| .then(function(d) { | |
| document.getElementById('ai-step-analyzing').style.display = 'none'; | |
| var out = ''; | |
| if (d && d.candidates && d.candidates[0] && d.candidates[0].content) { | |
| out = d.candidates[0].content.parts[0].text.trim(); | |
| out = out.replace(/^```html/i, '').replace(/^```/, '').replace(/```$/, '').trim(); | |
| document.getElementById('btn-download-word').style.display = 'flex'; | |
| } else { | |
| out = '<p class="text-red-400">AI 回傳格式異常:' + JSON.stringify(d).substring(0, 150) + '</p>'; | |
| } | |
| document.getElementById('ai-report-content').innerHTML = out; | |
| document.getElementById('ai-step-result').style.display = 'flex'; | |
| }) | |
| .catch(function(err) { | |
| document.getElementById('ai-step-analyzing').style.display = 'none'; | |
| document.getElementById('ai-report-content').innerHTML = '<p class="text-red-400">' + err.message + '</p>'; | |
| document.getElementById('ai-step-result').style.display = 'flex'; | |
| }); | |
| } | |
| // === Global State & Favorites & Text Size === | |
| var FAVS = {}; | |
| var SHOWING_FAVS = false; | |
| try { | |
| FAVS = JSON.parse(localStorage.getItem('hf_favs') || '{}'); | |
| } catch(e) {} | |
| function changeTextSize(val) { | |
| document.documentElement.style.fontSize = val; | |
| try { localStorage.setItem('text_size', val); } catch(e) {} | |
| } | |
| function updateFavCount() { | |
| var count = Object.keys(FAVS).length; | |
| var el = document.getElementById('fav-count'); | |
| if (el) el.textContent = count; | |
| } | |
| function toggleFav(id, cat, lib) { | |
| if (FAVS[id]) { | |
| delete FAVS[id]; | |
| toast('已從收藏夾移除'); | |
| } else { | |
| FAVS[id] = { id: id, _cat: cat, library_name: lib, savedAt: Date.now() }; | |
| toast('⭐ 已加入收藏夾!'); | |
| } | |
| try { localStorage.setItem('hf_favs', JSON.stringify(FAVS)); } catch(e) {} | |
| updateFavCount(); | |
| if (SHOWING_FAVS) renderFavs(); | |
| else fetchAndRender(); | |
| } | |
| function toggleFavsView() { | |
| SHOWING_FAVS = !SHOWING_FAVS; | |
| var btn = document.getElementById('btn-favs'); | |
| if (SHOWING_FAVS) { | |
| if (btn) btn.classList.add('ring-2', 'ring-amber-500'); | |
| renderFavs(); | |
| } else { | |
| if (btn) btn.classList.remove('ring-2', 'ring-amber-500'); | |
| fetchAndRender(); | |
| } | |
| } | |
| function clearFavs() { | |
| if (!confirm('確定要清空所有收藏嗎?')) return; | |
| FAVS = {}; | |
| try { localStorage.removeItem('hf_favs'); } catch(e) {} | |
| updateFavCount(); | |
| renderFavs(); | |
| toast('已清空所有收藏!'); | |
| } | |
| function renderFavs() { | |
| var items = Object.values(FAVS); | |
| var rc = document.getElementById('result-count'); | |
| if (rc) { | |
| rc.innerHTML = '收藏庫 (' + items.length + ') ' + (items.length ? '<button onclick="clearFavs()" class="ml-3 px-2 py-0.5 text-xs bg-red-500/20 text-red-300 border border-red-500/30 rounded hover:bg-red-500 hover:text-white transition">🗑️ 清空收藏</button>' : ''); | |
| rc.style.display = 'inline'; | |
| } | |
| if (!items.length) { | |
| document.getElementById('results-grid').style.display = 'none'; | |
| document.getElementById('empty-state').style.display = 'block'; | |
| document.getElementById('empty-title').textContent = '收藏庫是空的'; | |
| document.getElementById('empty-desc').textContent = '點擊卡片右上角的 ⭐ 即可收藏'; | |
| setStatus('Favorites empty', false); | |
| return; | |
| } | |
| setStatus('顯示收藏庫中 (' + items.length + ' 筆)', false); | |
| renderCards(items); | |
| } | |
| function copyPythonCode(e, id, cat, lib) { | |
| if (e && e.stopPropagation) e.stopPropagation(); | |
| var code = ''; | |
| if (cat === 'dataset') { | |
| code = 'from datasets import load_dataset\n\ndataset = load_dataset("' + id + '")'; | |
| } else if (cat === 'space') { | |
| code = 'import gradio as gr\n\ndemo = gr.load("' + id + '", src="spaces")\ndemo.launch()'; | |
| } else { | |
| if (lib === 'diffusers') { | |
| code = 'from diffusers import DiffusionPipeline\nimport torch\n\npipeline = DiffusionPipeline.from_pretrained("' + id + '")\n# pipeline.to("cuda")'; | |
| } else { | |
| code = 'from transformers import AutoModelForCausalLM, AutoTokenizer\n\ntokenizer = AutoTokenizer.from_pretrained("' + id + '")\nmodel = AutoModelForCausalLM.from_pretrained("' + id + '")'; | |
| } | |
| } | |
| navigator.clipboard.writeText(code).then(function() { | |
| toast('📋 已複製 Python 載入程式碼!'); | |
| }).catch(function() { | |
| toast('複製失敗', true); | |
| }); | |
| } | |
| // Init | |
| try { | |
| var savedSize = localStorage.getItem('text_size'); | |
| if (savedSize) { | |
| document.documentElement.style.fontSize = savedSize; | |
| document.getElementById('text-size').value = savedSize; | |
| } | |
| } catch(e) {} | |
| renderHistory(); | |
| </script> | |
| </body> | |
| </html> | |