Spaces:
Sleeping
Sleeping
| document.addEventListener('DOMContentLoaded', () => { | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // 1. 文字分析相關元素 | |
| const inputTextArea = document.getElementById('predict_info'); | |
| const inputButton = document.getElementById('detect_button'); | |
| const clearButton = document.getElementById('clear_button'); | |
| const normalOrScam = document.getElementById('is_scam'); | |
| const confidenceScoreSpan = document.getElementById('confidence_score'); | |
| const suspiciousPhrasesDiv = document.getElementById('suspicious_phrases'); | |
| const feedbackArea = document.getElementById('feedback_area'); | |
| const feedbackCorrectBtn = document.getElementById('feedback_correct'); | |
| const feedbackWrongBtn = document.getElementById('feedback_wrong'); | |
| const feedbackStatus = document.getElementById('feedback_status'); | |
| let lastPrediction = null; | |
| const TEXT_API = '/predict'; | |
| const FEEDBACK_API = '/feedback'; | |
| // 文字檢測:當按「檢測!」時觸發 | |
| inputButton.addEventListener('click', async () => { | |
| const message = inputTextArea.value.trim(); | |
| if (!message) { | |
| alert('請輸入您想檢測的訊息內容。'); | |
| return; | |
| } | |
| // 顯示 loading | |
| normalOrScam.textContent = '檢測中...'; | |
| normalOrScam.style.color = 'gray'; | |
| confidenceScoreSpan.textContent = '計算中...'; | |
| suspiciousPhrasesDiv.innerHTML = '<p>正在分析訊息,請稍候...</p>'; | |
| feedbackArea.style.display = 'none'; | |
| feedbackStatus.textContent = ''; | |
| feedbackCorrectBtn.style.display = 'inline-block'; | |
| feedbackWrongBtn.style.display = 'inline-block'; | |
| try { | |
| const res = await fetch(TEXT_API, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ text: message }) | |
| }); | |
| if (!res.ok) throw new Error(`伺服器錯誤: ${res.status}`); | |
| const data = await res.json(); | |
| // 更新文字分析結果 | |
| normalOrScam.textContent = data.status; | |
| confidenceScoreSpan.textContent = data.confidence + '%'; | |
| // 可疑詞句 | |
| suspiciousPhrasesDiv.innerHTML = ''; | |
| if (Array.isArray(data.suspicious_keywords) && data.suspicious_keywords.length) { | |
| const ul = document.createElement('ul'); | |
| data.suspicious_keywords.forEach(kw => { | |
| const li = document.createElement('li'); | |
| li.textContent = kw; | |
| ul.appendChild(li); | |
| }); | |
| suspiciousPhrasesDiv.appendChild(ul); | |
| } else { | |
| suspiciousPhrasesDiv.innerHTML = '<p>沒有偵測到可疑詞句。</p>'; | |
| } | |
| // 顯示回饋區 | |
| feedbackArea.style.display = 'block'; | |
| lastPrediction = { text: message, model_status: data.status }; | |
| } catch (err) { | |
| console.error('文字檢測失敗:', err); | |
| alert(`文字檢測失敗,請檢查後端。\n錯誤:${err.message}`); | |
| // 還原預設文字區 | |
| normalOrScam.textContent = '待檢測'; | |
| normalOrScam.style.color = 'inherit'; | |
| confidenceScoreSpan.textContent = '待檢測'; | |
| suspiciousPhrasesDiv.innerHTML = '<p>請輸入訊息並點擊「檢測!」按鈕。</p>'; | |
| } | |
| }); | |
| // 「清除」按鈕:清空文字、圖片結果 | |
| clearButton.addEventListener('click', () => { | |
| inputTextArea.value = ''; | |
| // 清空文字檢測結果 | |
| normalOrScam.textContent = '待檢測'; | |
| normalOrScam.style.color = 'inherit'; | |
| confidenceScoreSpan.textContent = '待檢測'; | |
| suspiciousPhrasesDiv.innerHTML = '<p>請輸入訊息並點擊「檢測!」按鈕。</p>'; | |
| feedbackArea.style.display = 'none'; | |
| feedbackStatus.textContent = ''; | |
| // 清空圖片OCR結果 | |
| extractedTextSpan.textContent = '尚未上傳圖片'; | |
| imgIsScamSpan.textContent = '尚未上傳圖片'; | |
| imgConfidenceSpan.textContent = '尚未上傳圖片'; | |
| imgSuspiciousDiv.innerHTML = '<p>尚未上傳圖片。</p>'; | |
| imageUpload.value = ''; | |
| }); | |
| // 使用者回饋 | |
| feedbackCorrectBtn.addEventListener('click', () => submitFeedback('正確')); | |
| feedbackWrongBtn.addEventListener('click', () => submitFeedback('錯誤')); | |
| async function submitFeedback(user_feedback) { | |
| if (!lastPrediction) return; | |
| const payload = { ...lastPrediction, user_feedback }; | |
| try { | |
| const r = await fetch(FEEDBACK_API, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(payload) | |
| }); | |
| await r.json(); | |
| feedbackStatus.textContent = '✅ 感謝您的回饋!'; | |
| feedbackCorrectBtn.style.display = 'none'; | |
| feedbackWrongBtn.style.display = 'none'; | |
| } catch (e) { | |
| feedbackStatus.textContent = '❌ 回饋提交失敗'; | |
| } | |
| } | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // 2. 圖片OCR相關元素 | |
| const imageUpload = document.getElementById('image_upload'); | |
| const imageButton = document.getElementById('image_button'); | |
| const extractedTextSpan = document.getElementById('extracted_text'); | |
| const imgIsScamSpan = document.getElementById('img_is_scam'); | |
| const imgConfidenceSpan = document.getElementById('img_confidence'); | |
| const imgSuspiciousDiv = document.getElementById('img_suspicious_phrases'); | |
| const IMAGE_API = '/analyze-image'; | |
| imageButton.addEventListener('click', async () => { | |
| // 確認 user 已經選擇檔案 | |
| if (!imageUpload.files.length) { | |
| alert('請先選擇一個圖片檔案。'); | |
| return; | |
| } | |
| // 顯示 Loading | |
| extractedTextSpan.textContent = '辨識中...'; | |
| imgIsScamSpan.textContent = '辨識中...'; | |
| imgConfidenceSpan.textContent = '辨識中...'; | |
| imgSuspiciousDiv.innerHTML = '<p>正在分析圖片,請稍候...</p>'; | |
| // 建立 FormData,append 檔案 | |
| const formData = new FormData(); | |
| formData.append('file', imageUpload.files[0]); | |
| try { | |
| const resp = await fetch(IMAGE_API, { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!resp.ok) throw new Error(`伺服器錯誤: ${resp.status}`); | |
| const data = await resp.json(); | |
| // 此 data 格式:{ extracted_text: "...", analysis_result: { status, confidence, suspicious_keywords } } | |
| const extracted = data.extracted_text || ''; | |
| const result = data.analysis_result || {}; | |
| // 更新前端OCR結果 | |
| extractedTextSpan.textContent = extracted || '(無文字)'; | |
| imgIsScamSpan.textContent = result.status || '(無法辨識)'; | |
| imgConfidenceSpan.textContent = (result.confidence !== undefined) | |
| ? result.confidence + '%' | |
| : '(無法辨識)'; | |
| imgSuspiciousDiv.innerHTML = ''; | |
| if (Array.isArray(result.suspicious_keywords) && result.suspicious_keywords.length) { | |
| const ul2 = document.createElement('ul'); | |
| result.suspicious_keywords.forEach(kw => { | |
| const li2 = document.createElement('li'); | |
| li2.textContent = kw; | |
| ul2.appendChild(li2); | |
| }); | |
| imgSuspiciousDiv.appendChild(ul2); | |
| } else { | |
| imgSuspiciousDiv.innerHTML = '<p>無可疑詞句。</p>'; | |
| } | |
| } catch (err) { | |
| console.error('圖片辨識失敗:', err); | |
| extractedTextSpan.textContent = '(圖片辨識失敗)'; | |
| imgIsScamSpan.textContent = '(失敗)'; | |
| imgConfidenceSpan.textContent = '0%'; | |
| imgSuspiciousDiv.innerHTML = `<p style="color:red;">❌ ${err.message}</p>`; | |
| } | |
| }); | |
| // ───────────────────────────────────────────────────────────────────────── | |
| }); | |