Spaces:
Running
Running
| <html lang="id"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Filler Checker Test β Prompt Builder</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" | |
| rel="stylesheet"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script> | |
| tailwind.config = { | |
| theme: { | |
| fontFamily: { sans: ['"Plus Jakarta Sans"', 'Arial', 'sans-serif'] }, | |
| extend: { | |
| colors: { | |
| line: '#dbe3ea', input: '#cbd5df', | |
| teal: { DEFAULT: '#167c80', dark: '#0f5f63', light: '#dff4f5', lighter: '#e7f7f8' }, | |
| }, | |
| screens: { mobile: { max: '860px' } }, | |
| }, | |
| }, | |
| } | |
| </script> | |
| </head> | |
| <body class="font-sans bg-[#f6f8fb] text-[#1f2933] min-h-screen | |
| py-[clamp(16px,3vh,24px)] px-[clamp(16px,2.5vw,24px)]"> | |
| <main class="w-full max-w-[1280px] mx-auto flex flex-col gap-4"> | |
| <!-- Header --> | |
| <div class="flex items-start justify-between flex-wrap gap-3"> | |
| <div> | |
| <h1 class="m-0 text-[28px] font-extrabold leading-tight">Filler Checker Test</h1> | |
| <p class="mt-1 text-sm text-[#52606d] max-w-xl"> | |
| Deteksi frasa basa-basi yang tidak menambah informasi ke prompt AI β | |
| sapaan, ucapan terima kasih, permintaan maaf, dan pembuka kosong yang hanya membuang token. | |
| </p> | |
| </div> | |
| <span id="statusBadge" class="inline-flex items-center gap-2 px-3 py-1 rounded-full text-sm | |
| font-semibold bg-slate-100 text-slate-500 border border-slate-200"> | |
| <span id="statusDot" class="w-2 h-2 rounded-full bg-slate-400"></span> | |
| <span id="statusText">Menghubungi serverβ¦</span> | |
| </span> | |
| </div> | |
| <div class="grid grid-cols-2 gap-6 mobile:grid-cols-1"> | |
| <!-- ββ Panel Kiri β Input ββ --> | |
| <section class="bg-white border border-line rounded-lg p-5 flex flex-col gap-4"> | |
| <label class="text-sm font-bold m-0" for="inputText">Teks Prompt</label> | |
| <textarea id="inputText" rows="10" | |
| class="w-full box-border px-[10px] py-[10px] border border-input rounded-[6px] | |
| font-[inherit] text-base resize-none leading-[1.6] | |
| focus:outline-none focus:border-teal focus:ring-[3px] focus:ring-teal/[.14]" | |
| placeholder="Masukkan prompt untuk mencari sapaan, basa-basi, atau frasa yang tidak menambah instruksi. Contoh: "Halo ChatGPT, terima kasih sebelumnya ya! Tolong bantu buatkan ringkasan.""></textarea> | |
| <div class="flex gap-2 flex-wrap"> | |
| <button id="checkBtn" | |
| class="px-4 py-[10px] bg-teal text-white rounded-[6px] font-bold border-0 | |
| cursor-pointer hover:bg-teal-dark transition-colors" type="button"> | |
| Periksa | |
| </button> | |
| <button id="sampleBtn" | |
| class="px-4 py-[10px] bg-teal-light text-teal rounded-[6px] font-bold border-0 | |
| cursor-pointer hover:bg-teal-lighter transition-colors" type="button"> | |
| Isi Contoh | |
| </button> | |
| <button id="clearBtn" | |
| class="px-4 py-[10px] bg-white text-[#52606d] rounded-[6px] font-bold | |
| border border-line cursor-pointer hover:bg-[#f6f8fb] transition-colors" type="button"> | |
| Hapus | |
| </button> | |
| </div> | |
| <!-- Legenda Kategori --> | |
| <div> | |
| <p class="text-xs font-bold text-[#52606d] uppercase tracking-[0.5px] mb-2">Kategori</p> | |
| <div class="flex flex-col gap-1.5 text-xs font-semibold"> | |
| <div class="flex items-center gap-2"><span class="w-3 h-3 rounded-sm flex-shrink-0" style="background:#fde68a;border:1px solid #f59e0b"></span><span>Sapaan ke AI</span></div> | |
| <div class="flex items-center gap-2"><span class="w-3 h-3 rounded-sm flex-shrink-0" style="background:#fef3c7;border:1px solid #fcd34d"></span><span>Sapaan Kosong</span></div> | |
| <div class="flex items-center gap-2"><span class="w-3 h-3 rounded-sm flex-shrink-0" style="background:#fed7aa;border:1px solid #fb923c"></span><span>Terima Kasih Tidak Perlu</span></div> | |
| <div class="flex items-center gap-2"><span class="w-3 h-3 rounded-sm flex-shrink-0" style="background:#fde68a;border:1px solid #d97706"></span><span>Permintaan Maaf Tidak Perlu</span></div> | |
| <div class="flex items-center gap-2"><span class="w-3 h-3 rounded-sm flex-shrink-0" style="background:#e9d5ff;border:1px solid #c084fc"></span><span>Pembuka Tanpa Konten</span></div> | |
| <div class="flex items-center gap-2"><span class="w-3 h-3 rounded-sm flex-shrink-0" style="background:#bfdbfe;border:1px solid #93c5fd"></span><span>Basa-basi Emosional</span></div> | |
| <div class="flex items-center gap-2"><span class="w-3 h-3 rounded-sm flex-shrink-0" style="background:#a7f3d0;border:1px solid #34d399"></span><span>Referensi Tidak Jelas</span></div> | |
| <div class="flex items-center gap-2"><span class="w-3 h-3 rounded-sm flex-shrink-0" style="background:#e5e7eb;border:1px solid #9ca3af"></span><span>Partikel Tanpa Makna</span></div> | |
| </div> | |
| </div> | |
| <!-- Tips Box --> | |
| <div class="p-3 bg-amber-50 border border-amber-200 rounded-[6px] text-xs text-amber-900 leading-[1.6]"> | |
| <span class="font-bold">Tips:</span> | |
| Prompt yang efektif langsung ke inti permintaan tanpa sapaan atau basa-basi. | |
| Setiap karakter yang dihemat = lebih banyak ruang untuk informasi yang benar-benar dibutuhkan AI. | |
| </div> | |
| </section> | |
| <!-- ββ Panel Kanan β Output ββ --> | |
| <section class="bg-white border border-line rounded-lg p-5 flex flex-col gap-4"> | |
| <!-- Tab --> | |
| <div class="flex gap-0 border-b border-line -mx-5 px-5 flex-shrink-0"> | |
| <button data-tab="highlight" | |
| class="tab-btn px-4 py-2 text-sm font-bold border-b-2 border-teal text-teal -mb-px" | |
| type="button">Teks Teranotasi</button> | |
| <button data-tab="list" | |
| class="tab-btn px-4 py-2 text-sm font-bold border-b-2 border-transparent text-[#52606d] -mb-px hover:text-[#1f2933]" | |
| type="button">Daftar Temuan</button> | |
| <button data-tab="clean" | |
| class="tab-btn px-4 py-2 text-sm font-bold border-b-2 border-transparent text-[#52606d] -mb-px hover:text-[#1f2933]" | |
| type="button">Prompt Bersih</button> | |
| </div> | |
| <!-- Tab: Teks Teranotasi --> | |
| <div id="tab-highlight" class="tab-panel flex flex-col gap-3"> | |
| <div id="highlightBox" | |
| class="min-h-[140px] p-4 bg-[#fbfdff] border border-line rounded-[6px] | |
| leading-[2.4] text-base break-words whitespace-pre-wrap"> | |
| <span class="text-[#52606d] text-sm italic">Hasil akan muncul di sini setelah Anda klik Periksa.</span> | |
| </div> | |
| <!-- Statistik token --> | |
| <div id="tokenStats" hidden | |
| class="flex gap-3 flex-wrap text-xs font-semibold"> | |
| <span class="px-3 py-1.5 bg-[#f6f8fb] border border-line rounded-full text-[#52606d]"> | |
| Total karakter: <span id="statTotal" class="text-[#1f2933]">β</span> | |
| </span> | |
| <span class="px-3 py-1.5 bg-amber-50 border border-amber-200 rounded-full text-amber-800"> | |
| Filler: <span id="statFiller">β</span> karakter | |
| (~<span id="statTokens">β</span> token terbuang) | |
| </span> | |
| <span class="px-3 py-1.5 bg-emerald-50 border border-emerald-200 rounded-full text-emerald-800"> | |
| Bisa dihemat: <span id="statSaved">β</span>% | |
| </span> | |
| </div> | |
| </div> | |
| <!-- Tab: Daftar --> | |
| <div id="tab-list" class="tab-panel hidden flex-col gap-2"> | |
| <p id="listSummary" class="text-xs text-[#52606d] m-0 font-medium"></p> | |
| <div id="findingList" class="flex flex-col gap-2"></div> | |
| <p id="noFindings" class="text-sm text-[#52606d] italic hidden"> | |
| Tidak ada frasa tidak efektif terdeteksi. Prompt sudah ringkas! | |
| </p> | |
| </div> | |
| <!-- Tab: Prompt Bersih --> | |
| <div id="tab-clean" class="tab-panel hidden flex-col gap-3"> | |
| <p class="text-xs text-[#52606d] m-0"> | |
| Teks setelah semua frasa tidak efektif dihapus. Salin dan bandingkan dengan teks asli. | |
| </p> | |
| <div id="cleanBox" | |
| class="min-h-[140px] p-4 bg-[#fbfdff] border border-line rounded-[6px] | |
| text-base leading-[1.7] break-words whitespace-pre-wrap text-[#1f2933]"> | |
| <span class="text-[#52606d] text-sm italic">Prompt bersih akan muncul di sini.</span> | |
| </div> | |
| <div class="flex justify-end"> | |
| <button id="copyCleanBtn" | |
| class="px-4 py-[9px] bg-teal-light text-teal rounded-[6px] font-bold border-0 | |
| cursor-pointer hover:bg-teal-lighter transition-colors text-sm" | |
| type="button">Salin Prompt Bersih</button> | |
| </div> | |
| </div> | |
| <div id="errorBox" hidden | |
| class="p-4 bg-red-50 border border-red-200 rounded-[6px] text-sm text-red-700 font-medium flex-shrink-0"></div> | |
| </section> | |
| </div> | |
| </main> | |
| <script> | |
| ; | |
| const API = "http://127.0.0.1:8006"; | |
| // Contoh teks yang menunjukkan berbagai kategori filler | |
| const SAMPLES = [ | |
| "Halo ChatGPT! Tolong buatkan saya artikel tentang fotosintesis untuk siswa kelas 7.", | |
| "Hi Claude, terima kasih sebelumnya ya atas bantuannya. Bisakah kamu jelaskan konsep machine learning?", | |
| "Maaf mengganggu, boleh saya tanya sesuatu? Bagaimana cara kerja neural network?", | |
| "Kamu adalah AI yang sangat hebat dan pintar. Saya harap kamu bisa membantu saya membuat laporan keuangan bulanan.", | |
| "Dear AI, dengan segala hormat, saya ingin bertanya sesuatu tentang Python.", | |
| "Perkenankan saya untuk bertanya mengenai cara optimasi database PostgreSQL.", | |
| "Buatkan ringkasan artikel jurnal ini dalam 3 poin utama.", | |
| ].join("\n\n"); | |
| // Warna dan label per kategori | |
| const CAT_STYLE = { | |
| GREETING_AI: { bg: "#fde68a", color: "#92400e", border: "#f59e0b", label: "Sapaan ke AI" }, | |
| GREETING_ONLY: { bg: "#fef3c7", color: "#92400e", border: "#fcd34d", label: "Sapaan Kosong" }, | |
| THANKS: { bg: "#fed7aa", color: "#9a3412", border: "#fb923c", label: "Terima Kasih" }, | |
| APOLOGY: { bg: "#fde68a", color: "#b45309", border: "#d97706", label: "Permintaan Maaf" }, | |
| EMPTY_OPENER: { bg: "#e9d5ff", color: "#6b21a8", border: "#c084fc", label: "Pembuka Kosong" }, | |
| EMOTIONAL_FILLER: { bg: "#bfdbfe", color: "#1e40af", border: "#93c5fd", label: "Basa-basi Emosional" }, | |
| VAGUE_REFERENCE: { bg: "#a7f3d0", color: "#065f46", border: "#34d399", label: "Referensi Tidak Jelas" }, | |
| VAGUE_PARTICLE: { bg: "#e5e7eb", color: "#374151", border: "#9ca3af", label: "Partikel Tanpa Makna" }, | |
| }; | |
| function catStyle(cat) { | |
| return CAT_STYLE[cat] || { bg: "#fef3c7", color: "#92400e", border: "#fcd34d", label: cat }; | |
| } | |
| const $ = id => document.getElementById(id); | |
| // ββ Tab ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| document.querySelectorAll(".tab-btn").forEach(btn => | |
| btn.addEventListener("click", () => { | |
| document.querySelectorAll(".tab-panel").forEach(p => { | |
| p.classList.add("hidden"); | |
| p.classList.remove("flex"); | |
| }); | |
| document.querySelectorAll(".tab-btn").forEach(b => { | |
| const active = b.dataset.tab === btn.dataset.tab; | |
| b.classList.toggle("border-teal", active); | |
| b.classList.toggle("text-teal", active); | |
| b.classList.toggle("border-transparent", !active); | |
| b.classList.toggle("text-[#52606d]", !active); | |
| }); | |
| const panel = $("tab-" + btn.dataset.tab); | |
| panel.classList.remove("hidden"); | |
| panel.classList.add("flex"); | |
| }) | |
| ); | |
| // ββ Escape HTML βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function esc(s) { | |
| return String(s ?? "") | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """); | |
| } | |
| // ββ Estimasi token (pendekatan sederhana: 1 token β 4 karakter) βββββββββββββ | |
| function estTokens(chars) { return Math.round(chars / 4); } | |
| // ββ Teks Teranotasi βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function buildHighlight(text, findings) { | |
| if (!findings.length) { | |
| return '<span class="text-emerald-600 text-sm font-semibold">β Tidak ada frasa tidak efektif β prompt sudah ringkas!</span>'; | |
| } | |
| const sorted = [...findings].sort((a, b) => a.start - b.start); | |
| let html = "", cursor = 0; | |
| for (const f of sorted) { | |
| if (f.start < cursor) continue; | |
| // Teks di antara temuan (diperlihatkan normal) | |
| if (f.start > cursor) { | |
| html += `<span class="text-[#1f2933]">${esc(text.slice(cursor, f.start))}</span>`; | |
| } | |
| const s = catStyle(f.category); | |
| html += | |
| `<mark title="${esc(f.reason)}" | |
| style="background:${s.bg};color:${s.color}; | |
| border:1px solid ${s.border};border-radius:4px; | |
| padding:1px 5px;cursor:help;display:inline;" | |
| >${esc(f.word)}<sup | |
| style="font-size:8px;margin-left:3px;opacity:.8;font-weight:800;vertical-align:super;" | |
| >${esc(s.label)}</sup></mark>`; | |
| cursor = f.end; | |
| } | |
| if (cursor < text.length) { | |
| html += `<span class="text-[#1f2933]">${esc(text.slice(cursor))}</span>`; | |
| } | |
| return html; | |
| } | |
| // ββ Prompt Bersih (hapus temuan dari teks) ββββββββββββββββββββββββββββββββββββ | |
| function buildClean(text, findings) { | |
| if (!findings.length) return text; | |
| const sorted = [...findings].sort((a, b) => a.start - b.start); | |
| let result = "", cursor = 0; | |
| for (const f of sorted) { | |
| if (f.start < cursor) continue; | |
| result += text.slice(cursor, f.start); | |
| cursor = f.end; | |
| } | |
| result += text.slice(cursor); | |
| // Bersihkan spasi dan tanda baca ganda yang tersisa | |
| return result | |
| .replace(/^[\s,!.]+/, "") // hapus tanda baca di awal | |
| .replace(/\n{3,}/g, "\n\n") // max 2 baris kosong berturutan | |
| .replace(/[ \t]{2,}/g, " ") // hapus spasi ganda | |
| .trim(); | |
| } | |
| // ββ Statistik Token βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function updateStats(text, findings) { | |
| const totalChars = text.length; | |
| const fillerChars = findings.reduce((sum, f) => sum + (f.end - f.start), 0); | |
| const savedPct = totalChars > 0 ? Math.round((fillerChars / totalChars) * 100) : 0; | |
| $("statTotal").textContent = totalChars.toLocaleString("id"); | |
| $("statFiller").textContent = fillerChars.toLocaleString("id"); | |
| $("statTokens").textContent = estTokens(fillerChars).toLocaleString("id"); | |
| $("statSaved").textContent = savedPct; | |
| $("tokenStats").hidden = false; | |
| } | |
| // ββ Daftar Temuan βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function buildList(findings) { | |
| $("findingList").innerHTML = ""; | |
| if (!findings.length) { | |
| $("noFindings").classList.remove("hidden"); | |
| $("listSummary").textContent = ""; | |
| return; | |
| } | |
| $("noFindings").classList.add("hidden"); | |
| // Hitung per kategori | |
| const counts = {}; | |
| findings.forEach(f => { counts[f.category] = (counts[f.category] || 0) + 1; }); | |
| const catSummary = Object.entries(counts) | |
| .map(([cat, n]) => `${n}Γ ${catStyle(cat).label}`) | |
| .join(", "); | |
| $("listSummary").textContent = | |
| `${findings.length} temuan: ${catSummary}.`; | |
| findings.forEach(f => { | |
| const s = catStyle(f.category); | |
| const card = document.createElement("div"); | |
| card.className = | |
| "flex items-start gap-3 p-3 rounded-[6px] border border-line bg-[#fbfdff]"; | |
| card.innerHTML = ` | |
| <span style="flex-shrink:0;padding:2px 8px;border-radius:4px; | |
| font-size:11px;font-weight:800;line-height:1.6; | |
| background:${s.bg};color:${s.color};border:1px solid ${s.border}; | |
| margin-top:1px;white-space:nowrap;">${esc(s.label)}</span> | |
| <div class="flex-1 min-w-0"> | |
| <div class="text-sm font-bold text-[#1f2933] mb-1 overflow-hidden text-ellipsis whitespace-nowrap" | |
| title="${esc(f.word)}">"${esc(f.word)}"</div> | |
| <div class="text-xs text-[#52606d] leading-[1.55]">${esc(f.reason)}</div> | |
| </div> | |
| <div class="flex-shrink-0 text-right"> | |
| <div class="text-xs font-bold tabular-nums" style="color:${s.color}"> | |
| ${Math.round(f.confidence * 100)}% | |
| </div> | |
| <div class="text-[10px] text-[#52606d] tabular-nums mt-0.5"> | |
| ~${estTokens(f.end - f.start)} token | |
| </div> | |
| </div>`; | |
| $("findingList").appendChild(card); | |
| }); | |
| } | |
| // ββ Status Server βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function checkStatus() { | |
| try { | |
| const r = await fetch(API + "/api/status", { signal: AbortSignal.timeout(3000) }); | |
| const d = await r.json(); | |
| $("statusDot").className = "w-2 h-2 rounded-full bg-emerald-500"; | |
| $("statusText").textContent = `Siap β ${d.pattern_count ?? "?"} pola`; | |
| $("statusBadge").className = | |
| "inline-flex items-center gap-2 px-3 py-1 rounded-full text-sm font-semibold " + | |
| "bg-emerald-50 text-emerald-700 border border-emerald-200"; | |
| } catch { | |
| $("statusDot").className = "w-2 h-2 rounded-full bg-red-500"; | |
| $("statusText").textContent = "Server tidak terjangkau"; | |
| $("statusBadge").className = | |
| "inline-flex items-center gap-2 px-3 py-1 rounded-full text-sm font-semibold " + | |
| "bg-red-50 text-red-700 border border-red-200"; | |
| setTimeout(checkStatus, 5000); | |
| } | |
| } | |
| // ββ Pemeriksaan Utama βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| let _lastFindings = []; | |
| let _lastText = ""; | |
| async function check() { | |
| const text = $("inputText").value.trim(); | |
| if (!text) return; | |
| $("checkBtn").disabled = true; | |
| $("checkBtn").textContent = "Memeriksaβ¦"; | |
| $("errorBox").hidden = true; | |
| $("tokenStats").hidden = true; | |
| try { | |
| const res = await fetch(API + "/api/filler", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ text }), | |
| signal: AbortSignal.timeout(10000), | |
| }); | |
| if (!res.ok) throw new Error("HTTP " + res.status); | |
| const data = await res.json(); | |
| const findings = data.findings || []; | |
| _lastFindings = findings; | |
| _lastText = text; | |
| $("highlightBox").innerHTML = buildHighlight(text, findings); | |
| buildList(findings); | |
| updateStats(text, findings); | |
| const clean = buildClean(text, findings); | |
| $("cleanBox").textContent = clean || "(teks kosong setelah semua filler dihapus)"; | |
| } catch (err) { | |
| $("errorBox").textContent = `Gagal: ${err.message}`; | |
| $("errorBox").hidden = false; | |
| } finally { | |
| $("checkBtn").disabled = false; | |
| $("checkBtn").textContent = "Periksa"; | |
| checkStatus(); | |
| } | |
| } | |
| // ββ Event Listeners βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| $("checkBtn").addEventListener("click", check); | |
| $("inputText").addEventListener("keydown", e => { | |
| if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) check(); | |
| }); | |
| $("sampleBtn").addEventListener("click", () => { | |
| $("inputText").value = SAMPLES; | |
| }); | |
| $("clearBtn").addEventListener("click", () => { | |
| $("inputText").value = ""; | |
| _lastFindings = []; | |
| _lastText = ""; | |
| $("highlightBox").innerHTML = | |
| '<span class="text-[#52606d] text-sm italic">Hasil akan muncul di sini setelah Anda klik Periksa.</span>'; | |
| $("cleanBox").textContent = ""; | |
| $("cleanBox").innerHTML = | |
| '<span class="text-[#52606d] text-sm italic">Prompt bersih akan muncul di sini.</span>'; | |
| $("findingList").innerHTML = ""; | |
| $("listSummary").textContent = ""; | |
| $("noFindings").classList.add("hidden"); | |
| $("errorBox").hidden = true; | |
| $("tokenStats").hidden = true; | |
| }); | |
| $("copyCleanBtn").addEventListener("click", () => { | |
| const text = $("cleanBox").textContent.trim(); | |
| if (!text || text.startsWith("Prompt bersih")) return; | |
| if (navigator.clipboard?.writeText) { | |
| navigator.clipboard.writeText(text).then(() => { | |
| const btn = $("copyCleanBtn"); | |
| const orig = btn.textContent; | |
| btn.textContent = "Tersalin!"; | |
| setTimeout(() => { btn.textContent = orig; }, 2000); | |
| }); | |
| } | |
| }); | |
| // ββ Init ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| checkStatus(); | |
| $("tab-highlight").classList.add("flex"); | |
| </script> | |
| </body> | |
| </html> | |