Download scratch_replace.py from ILRDF-Lowking/truku-interactive-lesson: direct link, hf CLI and curl.
- Browser
- Download file 8.96 kB
-
https://huggingface.co/spaces/ILRDF-Lowking/truku-interactive-lesson/resolve/main/scratch_replace.py
- Command line
-
hf download hf://spaces/ILRDF-Lowking/truku-interactive-lesson/scratch_replace.py
-
curl -L -o scratch_replace.py https://huggingface.co/spaces/ILRDF-Lowking/truku-interactive-lesson/resolve/main/scratch_replace.py
8.96 kB
| # -*- coding: utf-8 -*- | |
| import re | |
| html_path = 'public/ai-tools.html' | |
| with open(html_path, 'r', encoding='utf-8') as f: | |
| html = f.read() | |
| # We want to replace everything from <!-- Part 2: ... --> to the end of <main> | |
| part2_start = html.find('<!-- Part 2:') | |
| if part2_start == -1: | |
| print("Could not find Part 2 comment.") | |
| else: | |
| main_end = html.find('</main>', part2_start) | |
| new_part2 = """ | |
| <!-- Part 2: 族語教學 30 款評量與遊戲提示詞百寶箱 (Prompt Library) --> | |
| <div class="tool-card" id="prompt-library-section" style="margin-top: 32px; border: none; padding: 0; box-shadow: 0 4px 20px rgba(0,0,0,0.08); overflow: hidden;"> | |
| <div class="tool-card-header" style="background: linear-gradient(135deg, #10b981 0%, #047857 100%); padding: 24px; border-bottom: none;"> | |
| <span class="tool-badge" style="background: rgba(255,255,255,0.25); color: white; margin-bottom: 12px; display: inline-block;">✨ 零門檻 AI 備課</span> | |
| <h3 style="color: white; margin: 0; font-size: 24px; font-weight: 800; text-shadow: 0 2px 4px rgba(0,0,0,0.2);">族語教學 20-30 款評量與遊戲「提示詞百寶箱」</h3> | |
| </div> | |
| <div style="padding: 24px;"> | |
| <div style="background: #f0fdf4; border: 1px solid #bbf7d0; padding: 20px; border-radius: 12px; margin-bottom: 24px;"> | |
| <p style="font-size: 16px; color: #166534; margin: 0; font-weight: 600; line-height: 1.6;"> | |
| 💡 <strong>免學寫程式!零技術門檻!</strong> <br> | |
| 只要找到喜歡的評量或遊戲,點擊下方的 <strong>「📋 一鍵複製提示詞」</strong>,然後貼到 Gemini 或是 ChatGPT,再附上您的課文內容,AI 就會瞬間為您產出可以直接印給學生玩的考卷、表格或桌遊卡牌! | |
| </p> | |
| </div> | |
| <!-- Category Filter Tabs --> | |
| <div class="prompt-tabs" id="prompt-tabs"> | |
| <button class="prompt-tab-btn active" data-category="all">🌟 全部</button> | |
| <button class="prompt-tab-btn" data-category="聽力評量 (Listening)">🎧 聽力</button> | |
| <button class="prompt-tab-btn" data-category="口說評量 (Speaking)">🗣️ 口說</button> | |
| <button class="prompt-tab-btn" data-category="閱讀評量 (Reading)">📖 閱讀</button> | |
| <button class="prompt-tab-btn" data-category="寫作/語法評量 (Writing)">✍️ 寫作</button> | |
| <button class="prompt-tab-btn" data-category="綜合互動與遊戲 (Games)">🎲 遊戲</button> | |
| </div> | |
| <!-- Prompt Grid Container --> | |
| <div class="prompt-grid" id="prompt-grid"> | |
| <div style="text-align: center; padding: 40px; color: #6b7280; font-size: 16px;"> | |
| <span class="step-icon" style="display:inline-block; margin-bottom: 10px; animation: spin 2s linear infinite;">🔄</span><br> | |
| 載入提示詞百寶箱中... | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| new_html = html[:part2_start] + new_part2 + "\n " + html[main_end:] | |
| with open(html_path, 'w', encoding='utf-8') as f: | |
| f.write(new_html) | |
| print("Replaced ai-tools.html Part 2 successfully.") | |
| css_path = 'public/css/app.css' | |
| with open(css_path, 'a', encoding='utf-8') as f: | |
| f.write(""" | |
| /* --- Prompt Library Styles --- */ | |
| .prompt-tabs { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 10px; | |
| margin-bottom: 24px; | |
| } | |
| .prompt-tab-btn { | |
| padding: 10px 20px; | |
| border-radius: 24px; | |
| border: 2px solid #e5e7eb; | |
| background: white; | |
| color: #4b5563; | |
| font-weight: 700; | |
| font-size: 15px; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| } | |
| .prompt-tab-btn:hover { | |
| background: #f3f4f6; | |
| border-color: #d1d5db; | |
| } | |
| .prompt-tab-btn.active { | |
| background: #047857; | |
| color: white; | |
| border-color: #047857; | |
| box-shadow: 0 4px 10px rgba(4,120,87,0.3); | |
| } | |
| .prompt-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | |
| gap: 20px; | |
| } | |
| .prompt-card { | |
| background: white; | |
| border: 1px solid #e5e7eb; | |
| border-radius: 16px; | |
| padding: 20px; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.02); | |
| display: flex; | |
| flex-direction: column; | |
| transition: transform 0.2s ease, box-shadow 0.2s ease; | |
| position: relative; | |
| } | |
| .prompt-card:hover { | |
| transform: translateY(-4px); | |
| box-shadow: 0 12px 24px rgba(0,0,0,0.1); | |
| border-color: #10b981; | |
| } | |
| .prompt-card-header { | |
| display: flex; | |
| align-items: flex-start; | |
| margin-bottom: 16px; | |
| } | |
| .prompt-icon { | |
| font-size: 28px; | |
| margin-right: 14px; | |
| background: #ecfdf5; | |
| width: 50px; | |
| height: 50px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 12px; | |
| flex-shrink: 0; | |
| } | |
| .prompt-title-wrap { | |
| flex: 1; | |
| } | |
| .prompt-id { | |
| font-size: 13px; | |
| color: #10b981; | |
| font-weight: 900; | |
| margin-bottom: 4px; | |
| letter-spacing: 0.5px; | |
| } | |
| .prompt-title { | |
| font-size: 18px; | |
| font-weight: 800; | |
| color: #111827; | |
| margin: 0; | |
| line-height: 1.3; | |
| } | |
| .prompt-tags { | |
| margin-bottom: 12px; | |
| } | |
| .prompt-tag { | |
| display: inline-block; | |
| background: #f3f4f6; | |
| color: #374151; | |
| font-size: 12px; | |
| padding: 4px 10px; | |
| border-radius: 12px; | |
| margin-right: 6px; | |
| font-weight: 600; | |
| } | |
| .prompt-desc { | |
| font-size: 14px; | |
| color: #4b5563; | |
| margin-bottom: 16px; | |
| flex: 1; | |
| line-height: 1.6; | |
| } | |
| .prompt-target { | |
| font-size: 13px; | |
| color: #374151; | |
| background: #f9fafb; | |
| padding: 12px; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| border-left: 4px solid #10b981; | |
| line-height: 1.5; | |
| } | |
| .btn-copy-prompt { | |
| background: #047857; | |
| color: white; | |
| border: none; | |
| border-radius: 10px; | |
| padding: 12px; | |
| font-weight: 800; | |
| font-size: 15px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| transition: all 0.2s; | |
| width: 100%; | |
| } | |
| .btn-copy-prompt:hover { | |
| background: #065f46; | |
| box-shadow: 0 4px 12px rgba(4,120,87,0.3); | |
| } | |
| .btn-copy-prompt.copied { | |
| background: #111827; | |
| pointer-events: none; | |
| } | |
| """) | |
| print("Appended CSS successfully.") | |
| js_path = 'public/js/app.js' | |
| with open(js_path, 'a', encoding='utf-8') as f: | |
| f.write(""" | |
| // --- Prompt Library Logic --- | |
| let allPrompts = []; | |
| async function loadPrompts() { | |
| const grid = document.getElementById('prompt-grid'); | |
| if (!grid) return; | |
| try { | |
| const res = await fetch('/data/prompts.json'); | |
| allPrompts = await res.json(); | |
| renderPrompts('all'); | |
| setupPromptTabs(); | |
| } catch (error) { | |
| console.error('Failed to load prompts:', error); | |
| grid.innerHTML = '<div style="color:#ef4444; padding:20px; font-weight:bold;">載入提示詞失敗,請檢查網路連線。</div>'; | |
| } | |
| } | |
| function renderPrompts(category) { | |
| const grid = document.getElementById('prompt-grid'); | |
| if (!grid) return; | |
| grid.innerHTML = ''; | |
| const filtered = category === 'all' | |
| ? allPrompts | |
| : allPrompts.filter(p => p.category === category); | |
| filtered.forEach(prompt => { | |
| const card = document.createElement('div'); | |
| card.className = 'prompt-card'; | |
| card.innerHTML = ` | |
| <div class="prompt-card-header"> | |
| <div class="prompt-icon">${prompt.icon}</div> | |
| <div class="prompt-title-wrap"> | |
| <div class="prompt-id">${prompt.id}</div> | |
| <h4 class="prompt-title">${prompt.title}</h4> | |
| </div> | |
| </div> | |
| <div class="prompt-tags"> | |
| <span class="prompt-tag">${prompt.category.split(' ')[0]}</span> | |
| </div> | |
| <div class="prompt-desc">${prompt.purpose}</div> | |
| <div class="prompt-target"><strong>🎯 訓練能力:</strong><br>${prompt.targetSkill}</div> | |
| <button class="btn-copy-prompt" onclick="copyPromptText(this, '${prompt.id}')"> | |
| 📋 一鍵複製提示詞 | |
| </button> | |
| `; | |
| grid.appendChild(card); | |
| }); | |
| } | |
| function setupPromptTabs() { | |
| const tabs = document.querySelectorAll('.prompt-tab-btn'); | |
| tabs.forEach(tab => { | |
| tab.addEventListener('click', (e) => { | |
| // Remove active class from all | |
| tabs.forEach(t => t.classList.remove('active')); | |
| // Add active to clicked | |
| e.target.classList.add('active'); | |
| const cat = e.target.getAttribute('data-category'); | |
| renderPrompts(cat); | |
| }); | |
| }); | |
| } | |
| window.copyPromptText = function(btn, promptId) { | |
| const promptData = allPrompts.find(p => p.id === promptId); | |
| if (!promptData) return; | |
| const textToCopy = promptData.promptTemplate; | |
| navigator.clipboard.writeText(textToCopy).then(() => { | |
| const originalText = btn.innerHTML; | |
| btn.innerHTML = '✅ 已複製!請貼到 Gemini'; | |
| btn.classList.add('copied'); | |
| setTimeout(() => { | |
| btn.innerHTML = originalText; | |
| btn.classList.remove('copied'); | |
| }, 3000); | |
| }).catch(err => { | |
| alert('複製失敗,請手動選取複製'); | |
| console.error(err); | |
| }); | |
| }; | |
| // Initialize if on ai-tools page | |
| document.addEventListener('DOMContentLoaded', () => { | |
| if (document.getElementById('prompt-library-section')) { | |
| loadPrompts(); | |
| } | |
| }); | |
| """) | |
| print("Appended JS logic successfully.") | |