File size: 8,148 Bytes
6ce4b4c
d7efe6e
63da521
d7efe6e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63da521
d7efe6e
 
 
 
 
6ce4b4c
63da521
 
d7efe6e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63da521
d7efe6e
 
63da521
d7efe6e
 
 
 
 
 
 
 
 
 
 
 
63da521
d7efe6e
 
 
 
 
63da521
d7efe6e
 
 
 
 
 
 
63da521
d7efe6e
 
63da521
d7efe6e
 
 
 
 
 
63da521
 
d7efe6e
 
 
 
 
 
 
63da521
d7efe6e
 
 
 
 
 
 
 
 
 
 
 
 
63da521
d7efe6e
 
 
 
 
 
 
 
 
 
63da521
d7efe6e
 
 
 
 
 
 
 
 
 
63da521
d7efe6e
 
 
6ce4b4c
 
63da521
d7efe6e
 
 
 
 
63da521
d7efe6e
 
 
 
 
 
 
 
 
 
 
63da521
d7efe6e
 
 
63da521
d7efe6e
 
63da521
d7efe6e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6ce4b4c
d7efe6e
63da521
d7efe6e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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>`;
    }
  });
  // ─────────────────────────────────────────────────────────────────────────
});