Bennie12 commited on
Commit
1ec883c
·
verified ·
1 Parent(s): aef356d

Upload 3 files

Browse files
Files changed (3) hide show
  1. index.html +44 -0
  2. script.js +174 -0
  3. style.css +232 -0
index.html ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-Hant">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>預測詐騙訊息</title>
7
+ <link rel="stylesheet" href="style.css"><!-- 引入外部 CSS 樣式 -->
8
+ <script src="script.js"></script> <!-- 引入 JavaScript 檔案 -->
9
+ </head>
10
+ <body>
11
+ <h1>檢查可疑訊息</h1><!-- <h1>字體最大標題,最小<h6>-->
12
+
13
+ <div class="main-container">
14
+
15
+ <!-- 使用者輸入區塊。textarea文字輸入的區塊。placeholder文字尚未輸入時,預設的顯示內容,無法在網頁端編輯。maxlength限制最大自數-->
16
+ <section id="input_area" class="panel">
17
+ <textarea id="predict_info" placeholder="請輸入內容 (最多5000字)" maxlength="5000" ></textarea>
18
+ <div class="button-group">
19
+ <button id="detect_button" type="submit">檢測</button><!---->
20
+ <button id="clear_button" type="reset">清除</button>
21
+
22
+ <!-- 圖片上傳與檢測功能 -->
23
+ <div class="image-upload-group" style="margin-top: 20px;">
24
+ <label for="imageInput">或上傳圖片進行詐騙檢測:</label><br>
25
+ <input type="file" id="imageInput" accept="image/*" />
26
+ <button id="image_button" type="button" style="margin-left: 10px;">圖片檢測</button>
27
+ </div>
28
+
29
+ </div>
30
+ </section>
31
+ <!-- 顯示模型預測結果的區塊 -->
32
+ <section id="output_area" class="panel">
33
+ <h2>檢測結果</h2>
34
+ <!--<p>是一般的文字標籤。<strong>包住的文字會變粗體</strong>。span行內元素(inline),這裡用途顯示後端輸出內容-->
35
+ <p><strong>是否為詐騙訊息:</strong> <span id="is_scam">待檢測</span></p>
36
+ <p><strong>模型預測可疑度:</strong> <span id="confidence_score">待檢測</span></p>
37
+ <p><strong>可疑詞句分析:</strong></p>
38
+ <div id="suspicious_phrases"><!--div和span一樣功用,差別在div能自動換行-->
39
+ <p>請輸入訊息並點擊「檢測」按鈕。</p>
40
+ </div>
41
+ </section>
42
+ </div>
43
+ </body>
44
+ </html>
script.js ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // frontend/script.js
2
+ // 當 DOM (文件物件模型) 完全載入後才執行裡面的程式碼
3
+ document.addEventListener('DOMContentLoaded', () => {
4
+ // 取得 HTML 元素:輸入、按鈕、顯示區塊。document.getElementById('對應的html元素id')
5
+ const inputTextArea = document.getElementById('predict_info'); // 輸入訊息的文字區域
6
+ const inputButton = document.getElementById('detect_button'); // 檢測按鈕
7
+ const clearButton = document.getElementById('clear_button'); // 清除按鈕
8
+
9
+ // 取得圖片上傳欄位與圖片按鈕
10
+ const imageInput = document.getElementById('imageInput');
11
+ const imageButton = document.getElementById('image_button');
12
+
13
+ // 取得顯示結果的 HTML 元素
14
+ const normalOrScam = document.getElementById('is_scam'); // 顯示正常或詐騙
15
+ const confidenceScoreSpan = document.getElementById('confidence_score'); // 顯示模型預測可信度
16
+ const suspiciousPhrasesDiv = document.getElementById('suspicious_phrases'); // 顯示可疑詞句列表
17
+
18
+ /*
19
+ 後端 FastAPI API 的 URL
20
+ 在開發階段,通常是 http://127.0.0.1:8000 或 http://localhost:8000
21
+ 請根據你實際運行 FastAPI 的位址和 Port 進行設定
22
+ */
23
+ const API_URL = "http://127.0.0.1:8000/predict";
24
+ const API_IMAGE_URL = "http://127.0.0.1:8000/predict-image"
25
+
26
+ // --- 檢測按鈕點擊事件監聽器 ---
27
+ // 當檢測按鈕被點擊時,執行非同步函數
28
+ //addEventListener('click', async () => {...})
29
+ // click 是一種 DOM 事件,代表使用者點擊按鈕。
30
+ // async 是「非同步函數」的關鍵字,允許你在函數中使用 await。它讓你可以像同步一樣撰寫非同步程式(例如網路請求)。
31
+
32
+ inputButton.addEventListener('click', async () => {
33
+ const message = inputTextArea.value.trim(); //.value取得<textarea>的輸入內容。
34
+ //.trim()刪掉文字開頭和結尾的空白,避免誤判空訊息。
35
+ // 檢查輸入框是否為空
36
+ if (message.length === 0) {//alert("輸出內容")是瀏覽器內建的彈出提示。視窗屬於 JavaScript 提供的「視覺提示方法」
37
+ alert('請輸入您想檢測的訊息內容。'); // alert彈出提示
38
+ return; // 終止函數執行
39
+ }
40
+
41
+ // 顯示載入中的狀態,給使用者視覺回饋
42
+ normalOrScam.textContent = '檢測中...'; //.textContent->插入純文字
43
+ normalOrScam.style.color = 'gray'; //styly.color改變顏色,style可以想為UI設計
44
+ confidenceScoreSpan.textContent = '計算中...';//.innerHTML->插入 HTML 語法
45
+ suspiciousPhrasesDiv.innerHTML = '<p>正在分析訊息,請稍候...</p>';//<></>大部分html語法長這樣
46
+
47
+ try {//try...catch處理程式錯誤。
48
+ //fetch(API_URL, {...})。API_URL第17段的變數。method:為html方法,POST送出請求。headers告訴伺服器傳送的資料格式是什麼。
49
+ //這段是用 fetch 來呼叫後端 API,送出 POST 請求:
50
+ const response = await fetch(API_URL, {
51
+ method: 'POST', // 指定 HTTP 方法為 POST
52
+ headers: { // 告訴伺服器發送的資料是 JSON 格式。選JSON原因:
53
+ //我們使用前端(JavaScript)與後端(Python FastAPI)是兩種不同的語言,而JSON是前後端通訊的「共通語言」,交換最通用、最方便、最安全的格式。
54
+ 'Content-Type': 'application/json'
55
+ },
56
+ //把JavaScript物件{text:message}轉換成JSON格式字串,字串作為請求的主體 (body)
57
+ body: JSON.stringify({ text: message}),
58
+ });
59
+
60
+ // 檢查 HTTP 回應是否成功 (例如:狀態碼 200 OK)
61
+ if (!response.ok) { // 如果回應狀態碼不是 2xx (成功),則拋出錯誤
62
+ //建立一個錯誤對象並丟出來,強制跳到catch{}區塊。response.status:HTTP狀態碼(如500)。
63
+ throw new Error(`伺服器錯誤: ${response.status} ${response.statusText} `);
64
+ }
65
+ // 變數data,儲存後端成功回傳的資料。
66
+ const data = await response.json();
67
+ // 因為後端回傳的欄位名稱status、confidence、suspicious_keywords跟傳進去的參數一致,所以拿的到後端回傳的資料。
68
+ updateResults( //呼叫function,分別對應function的
69
+ data.status, //isScam,輸出正常或詐騙
70
+ data.confidence, //confidence,輸出信心值
71
+ data.suspicious_keywords,//suspiciousParts,可疑關鍵字
72
+ data.highlighted_text
73
+ );
74
+ } catch (error) {// 捕獲並處理任何在 fetch 過程中發生的錯誤 (例如網路問題、CORS 錯誤)
75
+
76
+ console.error('訊息檢測失敗:', error);// 在開發者工具的控制台顯示錯誤
77
+ alert(`訊息檢測失敗,請檢查後端服務是否運行或網路連線。\n錯誤詳情: ${error.message}`); // 彈出錯誤提示
78
+ resetResults(); // 將介面恢復到初始狀態
79
+ }
80
+ });
81
+
82
+ imageButton.addEventListener('click', async()=>{
83
+ const file = imageInput.files[0]; //取得上傳相片
84
+ if (!file){
85
+ alert("請先選擇圖片");
86
+ return;
87
+ }
88
+ // 顯示載入中提示
89
+ normalOrScam.textContent = '圖片分析中...';
90
+ normalOrScam.style.color = 'gray';
91
+ confidenceScoreSpan.textContent = '計算中...';
92
+ suspiciousPhrasesDiv.innerHTML = '<p>正在從圖片擷取文字與分析中...</p>';
93
+
94
+ try{
95
+ const formData = new FormData();
96
+ formData.append("file", file); // 附加圖片檔案給後端
97
+ const response = await fetch(API_IMAGE_URL,{
98
+ method : "POST",
99
+ body : formData
100
+ });
101
+ if (!response.ok){
102
+ throw new Error(`圖片分析失敗: ${response.status} ${response.statusText}`);
103
+ }
104
+ const data = await response.json();
105
+ updateResults(
106
+ data.status,
107
+ data.confidence,
108
+ data.suspicious_keywords,
109
+ data.highlighted_text
110
+ )
111
+ }catch(error) {
112
+ console.error("圖片上傳失敗",error);
113
+ alert("圖片分析失敗")
114
+ resetResults();
115
+ }
116
+ });
117
+
118
+ function escapeRegExp(string) {
119
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
120
+ }
121
+ /*
122
+ function highlightSuspiciousWords(text, suspiciousParts) {
123
+ let highlighted = text;
124
+ suspiciousParts.forEach(word => {
125
+ if (word.length < 2) return; // 避免標記太短詞(如單個字或符號)
126
+ const pattern = new RegExp(escapeRegExp(word), 'g');
127
+ highlighted = highlighted.replace(pattern, `<span class="highlight">${word}</span>`);
128
+ });
129
+ return highlighted;
130
+ }
131
+ */
132
+ // --- 清除按鈕點擊事件監聽器 ---
133
+ // 當清除按鈕被點擊時,執行函數
134
+ clearButton.addEventListener('click', () => {
135
+ inputTextArea.value = '';// 清空輸入框內容
136
+ resetResults(); // 重置顯示結果
137
+ });
138
+
139
+ // --- 清除按鈕點擊事件監聽器 ---
140
+ // 當清除按鈕被點擊時,執行函數
141
+ clearButton.addEventListener('click', () => {
142
+ inputTextArea.value = '';// 清空輸入框內容
143
+ resetResults(); // 重置顯示結果
144
+ });
145
+
146
+ /**這是 JSDoc 註解格式,給開發者與編輯器看的,不會執行。
147
+ * 更新結果顯示的輔助函數
148
+ * @param {string} isScam - 是否為詐騙訊息 (從後端獲取)(原始要得"@"param {string} isScam )
149
+ * @param {number} confidence - 模型預測可信度 (從後端獲取)
150
+ * @param {string[]} suspiciousParts - 可疑詞句陣列 (從後端獲取)
151
+ */
152
+
153
+ //回傳輸出給index.html顯示
154
+ function updateResults(isScam, confidence, suspiciousParts, highlightedText) {
155
+ normalOrScam.textContent = isScam;
156
+ confidenceScoreSpan.textContent = confidence;
157
+
158
+ if (confidence < 15) {
159
+ suspiciousPhrasesDiv.innerHTML = '<p>此訊息為低風險,未發現可疑詞句。</p>';
160
+ } else {
161
+ suspiciousPhrasesDiv.innerHTML = highlightedText;
162
+ }
163
+ }
164
+
165
+ /**
166
+ * 重置所有顯示結果為初始狀態的輔助函數
167
+ */
168
+ function resetResults() {
169
+ normalOrScam.textContent = '待檢測';
170
+ normalOrScam.style.color = 'inherit'; // 恢復預設顏色
171
+ confidenceScoreSpan.textContent = '待檢測';
172
+ suspiciousPhrasesDiv.innerHTML = '<p>請輸入訊息並點擊「檢測!」按鈕。</p>';
173
+ }
174
+ });
style.css ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* style.css */
2
+
3
+ body {
4
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
5
+ margin: 0; /* 將 body 的 margin 設為 0,讓內容可以更貼近邊緣 */
6
+ padding: 20px; /* 內邊距留點空間 */
7
+ background-color: #f4f7f6;
8
+ color: #333;
9
+ line-height: 1.6;
10
+ display: flex; /* 讓 body 成為 flex 容器 */
11
+ flex-direction: column; /* 內容垂直排列 */
12
+ min-height: 100vh; /* 讓 body 至少佔滿整個視窗高度 */
13
+ align-items: center; /* 讓 h1 居中 */
14
+ }
15
+
16
+ .highlight {
17
+ background-color: #fc9c9cd5;
18
+ color: #000000;
19
+ font-weight: bold;
20
+ padding: 2px 4px;
21
+ border-radius: 4px;
22
+ }
23
+ .yellow-highlight {
24
+ background-color: #ffeb3b85; /* 半透明黃色 */
25
+ color: #000000;
26
+ font-weight: bold;
27
+ padding: 2px 4px;
28
+ border-radius: 4px;
29
+ }
30
+
31
+ .red-highlight {
32
+ background-color: #fc9c9cd5;
33
+ color: #000000;
34
+ font-weight: bold;
35
+ padding: 2px 4px;
36
+ border-radius: 4px;
37
+ }
38
+
39
+ h1 {
40
+ color: #2c3e50;
41
+ text-align: center;
42
+ margin-bottom: 30px; /* 增加標題下方的間距 */
43
+ font-size: 2.5em; /* 讓標題更大一點 */
44
+ }
45
+
46
+ h2 { /* 針對檢測結果的 h2 */
47
+ color: #2c3e50;
48
+ text-align: center;
49
+ margin-top: 0; /* 移除頂部 margin,讓它更靠近 panel 頂部 */
50
+ margin-bottom: 20px;
51
+ font-size: 1.8em;
52
+ }
53
+
54
+ /* --- 主容器 Flexbox 佈局 --- */
55
+ .main-container {
56
+ display: flex; /* 啟用 Flexbox */
57
+ flex-direction: row; /* 預設就是 row,讓子元素水平排列 */
58
+ gap: 30px; /* 左右兩個 panel 之間的間距 */
59
+ width: 100%; /* 佔滿可用寬度 */
60
+ max-width: 1200px; /* 設定最大寬度,避免在寬螢幕上過於分散 */
61
+ justify-content: center; /* 內容居中 */
62
+ flex-wrap: wrap; /* 當螢幕太小時,允許換行 */
63
+ }
64
+
65
+ .panel {
66
+ background-color: #ffffff;
67
+ padding: 30px; /* 增加內邊距 */
68
+ border-radius: 8px;
69
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1); /* 更明顯的陰影 */
70
+ flex: 1; /* 讓兩個 panel 平均分配空間 */
71
+ min-width: 380px; /* 設定每個 panel 的最小寬度,避免縮得太小 */
72
+ box-sizing: border-box; /* 確保 padding 和 border 不會增加元素總寬度 */
73
+ display: flex; /* 讓 panel 內部內容也是 flex 容器 */
74
+ flex-direction: column; /* 內部內容垂直排列 */
75
+ }
76
+
77
+ #input_area {
78
+ /* 特定於 input_area 的樣式,如果需要 */
79
+ align-items: center; /* 讓輸入框和按鈕在 input_area 中居中 */
80
+ }
81
+
82
+
83
+
84
+ textarea {
85
+ width: 100%; /* 佔滿 panel 寬度 */
86
+ height: 250px; /* 增加高度 */
87
+ padding: 15px;
88
+ margin-bottom: 25px; /* 增加與按鈕的間距 */
89
+ border: 1px solid #ddd;
90
+ border-radius: 5px;
91
+ font-size: 1.1rem; /* 稍微放大字體 */
92
+ box-sizing: border-box;
93
+ resize: vertical;
94
+ outline: none; /* 移除 focus 時的藍色邊框 */
95
+ transition: border-color 0.3s ease;
96
+ }
97
+
98
+ textarea:focus {
99
+ border-color: #4CAF50; /* focus 時邊框變色 */
100
+ }
101
+
102
+
103
+ .button-group {
104
+ display: flex;
105
+ gap: 20px; /* 按鈕間距 */
106
+ justify-content: center; /* 按鈕在 group 內部居中 */
107
+ width: 100%; /* 佔滿寬度 */
108
+ }
109
+
110
+ button {
111
+ padding: 12px 30px; /* 稍微增加按鈕大小 */
112
+ font-size: 1.1rem;
113
+ cursor: pointer;
114
+ border: none;
115
+ border-radius: 5px;
116
+ transition: background-color 0.3s ease, transform 0.2s ease; /* 增加 transform 過渡效果 */
117
+ font-weight: bold; /* 字體加粗 */
118
+ }
119
+
120
+ button[type="submit"] {
121
+ background-color: #4CAF50;
122
+ color: white;
123
+ }
124
+
125
+ button[type="submit"]:hover {
126
+ background-color: #45a049;
127
+ transform: translateY(-2px); /* 懸停時向上輕微移動 */
128
+ }
129
+
130
+ button[type="reset"] {
131
+ background-color: #f44336;
132
+ color: white;
133
+ }
134
+
135
+ button[type="reset"]:hover {
136
+ background-color: #da190b;
137
+ transform: translateY(-2px);
138
+ }
139
+
140
+
141
+ #output_area p {
142
+ font-size: 1.15rem; /* 稍微放大結果文字 */
143
+ margin-bottom: 12px;
144
+ }
145
+
146
+ #output_area strong {
147
+ color: #555;
148
+ font-weight: bold;
149
+ }
150
+
151
+ #is_scam, #confidence_score {
152
+ font-weight: bold; /* 結果狀態字體加粗 */
153
+ }
154
+
155
+ #suspicious_phrases {
156
+ background-color: #fffafa; /* 給可疑詞句區塊一個淺色背景 */
157
+ border: 1px dashed #e0baba; /* 虛線邊框 */
158
+ padding: 15px;
159
+ border-radius: 5px;
160
+ margin-top: 15px;
161
+ min-height: 80px; /* 確保高度,避免內容少時高度變化 */
162
+ }
163
+
164
+ #suspicious_phrases ul {
165
+ list-style-type: '🚨 '; /* 使用表情符號作為列表標記 */
166
+ padding-left: 20px;
167
+ margin: 0; /* 移除預設 margin */
168
+ }
169
+
170
+ #suspicious_phrases li {
171
+ margin-bottom: 8px;
172
+ color: #c0392b;
173
+ font-weight: 500;
174
+ }
175
+
176
+ #suspicious_phrases p {
177
+ font-style: italic;
178
+ color: #666;
179
+ margin: 0; /* 移除預設 margin */
180
+ }
181
+ label[for="modeSelect"] {
182
+ display: block;
183
+ margin-bottom: 8px;
184
+ font-weight: bold;
185
+ color: #495057;
186
+ font-size: 15px;
187
+ }
188
+
189
+ #modeSelect {
190
+ width: 100%;
191
+ padding: 10px;
192
+ font-size: 15px;
193
+ border-radius: 6px;
194
+ border: 1px solid #adb5bd;
195
+ background-color: #ffffff;
196
+ margin-bottom: 20px;
197
+ transition: 0.2s;
198
+ }
199
+
200
+ #modeSelect:focus {
201
+ border-color: #74c0fc;
202
+ box-shadow: 0 0 0 0.1rem rgba(116, 192, 252, 0.25);
203
+ outline: none;
204
+ }
205
+
206
+
207
+ /* --- 響應式設計:當螢幕較小時,垂直排列 --- */
208
+ @media (max-width: 768px) {
209
+ .main-container {
210
+ flex-direction: column; /* 小螢幕時改為垂直堆疊 */
211
+ gap: 20px; /* 垂直間距 */
212
+ padding: 0 15px; /* 左右邊距 */
213
+ }
214
+
215
+ .panel {
216
+ flex: none; /* 取消 flex 比例,讓他們各自佔據 100% 寬度 */
217
+ width: 100%;
218
+ max-width: none; /* 移除最大寬度限制 */
219
+ }
220
+
221
+ h1 {
222
+ font-size: 2em;
223
+ }
224
+
225
+ h2 {
226
+ font-size: 1.5em;
227
+ }
228
+
229
+ textarea {
230
+ height: 200px;
231
+ }
232
+ }